keystore/keystore.go
// Copyright © 2016 Asteris, LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package keystore
import (
"bytes"
"fmt"
"io"
"io/ioutil"
"os"
"os/user"
"path"
"path/filepath"
"github.com/pkg/errors"
"golang.org/x/crypto/openpgp"
)
// A Keystore represents a repository of trusted public keys which can be
// used to verify PGP signatures.
type Keystore struct {
LocalPath string
UserPath string
SystemPath string
keyring openpgp.KeyRing
}
// New returns a new Keystore backed by the provided paths.
func New(localPath, userPath, systemPath string) *Keystore {
return &Keystore{
LocalPath: localPath,
UserPath: userPath,
SystemPath: systemPath,
}
}
var defaultKeystore *Keystore
// Default returns a keystore backed by the default local, user, and system paths.
func Default() *Keystore {
if defaultKeystore == nil {
userPath := ""
usr, err := user.Current()
if err == nil {
userPath = filepath.Join(usr.HomeDir, ".converge/trustedkeys")
}
defaultKeystore = &Keystore{
LocalPath: "trustedkeys",
UserPath: userPath,
SystemPath: "/usr/lib/converge/trustedkeys",
}
}
return defaultKeystore
}
// StoreTrustedKey stores the contents of the public key.
func (ks *Keystore) StoreTrustedKey(pubkeyBytes []byte) (string, error) {
if err := os.MkdirAll(ks.UserPath, 0755); err != nil {
return "", err
}
keyring, err := openpgp.ReadArmoredKeyRing(bytes.NewReader(pubkeyBytes))
if err != nil {
return "", err
}
if len(keyring) < 1 {
return "", errors.New("cannot store trusted key: empty keyring")
}
pubKey := keyring[0].PrimaryKey
trustedKeyPath := path.Join(ks.UserPath, fmt.Sprintf("%x", pubKey.Fingerprint))
if err := ioutil.WriteFile(trustedKeyPath, pubkeyBytes, 0644); err != nil {
return "", err
}
return trustedKeyPath, nil
}
// DeleteTrustedKey deletes the trusted key identified by fingerprint.
func (ks *Keystore) DeleteTrustedKey(fingerprint string) error {
return os.Remove(path.Join(ks.UserPath, fingerprint))
}
// MaskTrustedSystemKey masks the system trusted key identified by fingerprint.
func (ks *Keystore) MaskTrustedSystemKey(fingerprint string) (string, error) {
dst := path.Join(ks.UserPath, fingerprint)
return dst, ioutil.WriteFile(dst, []byte(""), 0644)
}
// CheckSignature takes a signed file and a detached signature and verifies if it is signed by a trusted signer.
func (ks *Keystore) CheckSignature(signed, signature io.Reader) error {
if ks.keyring == nil {
keyring, err := loadKeyring(ks)
if err != nil {
return errors.Wrap(err, "error loading keyring")
}
ks.keyring = keyring
}
signer, err := openpgp.CheckArmoredDetachedSignature(ks.keyring, signed, signature)
// openpgp has a weird api so we do some custom error handling.
if err != nil {
if err == io.EOF {
return errors.New("no valid signatures found in signature file")
}
return err
}
if signer == nil {
return errors.New("invalid signer")
}
return nil
}
func loadKeyring(ks *Keystore) (openpgp.KeyRing, error) {
var keyring openpgp.EntityList
trustedKeys := make(map[string]*openpgp.Entity)
for _, p := range []string{ks.SystemPath, ks.UserPath, ks.LocalPath} {
files, err := ioutil.ReadDir(p)
if err != nil {
if os.IsNotExist(err) {
continue
}
return nil, err
}
for _, file := range files {
if file.Size() == 0 {
delete(trustedKeys, file.Name())
continue
}
trustedKey, err := os.Open(filepath.Join(p, file.Name()))
if err != nil {
return nil, err
}
defer trustedKey.Close()
keys, err := openpgp.ReadArmoredKeyRing(trustedKey)
if err != nil {
return nil, err
}
if len(keys) < 1 {
return nil, errors.New("empty keyring")
}
fingerprint := fmt.Sprintf("%x", keys[0].PrimaryKey.Fingerprint)
if file.Name() != fingerprint {
return nil, fmt.Errorf("fingerprint mismatch: %q:%q", file.Name(), fingerprint)
}
trustedKeys[fingerprint] = keys[0]
}
}
for _, v := range trustedKeys {
keyring = append(keyring, v)
}
return keyring, nil
}