docs/cryptoservice/import_export.go

314 lines
7.6 KiB
Go

package cryptoservice
import (
"archive/zip"
"crypto/x509"
"encoding/pem"
"errors"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
"github.com/docker/notary/passphrase"
"github.com/docker/notary/trustmanager"
)
const zipMadeByUNIX = 3 << 8
var (
// ErrNoValidPrivateKey is returned if a key being imported doesn't
// look like a private key
ErrNoValidPrivateKey = errors.New("no valid private key found")
// ErrRootKeyNotEncrypted is returned if a root key being imported is
// unencrypted
ErrRootKeyNotEncrypted = errors.New("only encrypted root keys may be imported")
// ErrNoKeysFoundForGUN is returned if no keys are found for the
// specified GUN during export
ErrNoKeysFoundForGUN = errors.New("no keys found for specified GUN")
)
// ExportKey exports the specified private key to an io.Writer in PEM format.
// The key's existing encryption is preserved.
func (cs *CryptoService) ExportKey(dest io.Writer, keyID, role string) error {
var (
pemBytes []byte
err error
)
for _, ks := range cs.keyStores {
pemBytes, err = ks.ExportKey(keyID)
if err != nil {
continue
}
}
if err != nil {
return err
}
nBytes, err := dest.Write(pemBytes)
if err != nil {
return err
}
if nBytes != len(pemBytes) {
return errors.New("Unable to finish writing exported key.")
}
return nil
}
// ExportKeyReencrypt exports the specified private key to an io.Writer in
// PEM format. The key is reencrypted with a new passphrase.
func (cs *CryptoService) ExportKeyReencrypt(dest io.Writer, keyID string, newPassphraseRetriever passphrase.Retriever) error {
privateKey, _, err := cs.GetPrivateKey(keyID)
if err != nil {
return err
}
keyInfo, err := cs.GetKeyInfo(keyID)
if err != nil {
return err
}
// Create temporary keystore to use as a staging area
tempBaseDir, err := ioutil.TempDir("", "notary-key-export-")
defer os.RemoveAll(tempBaseDir)
tempKeyStore, err := trustmanager.NewKeyFileStore(tempBaseDir, newPassphraseRetriever)
if err != nil {
return err
}
err = tempKeyStore.AddKey(keyInfo, privateKey)
if err != nil {
return err
}
pemBytes, err := tempKeyStore.ExportKey(keyID)
if err != nil {
return err
}
nBytes, err := dest.Write(pemBytes)
if err != nil {
return err
}
if nBytes != len(pemBytes) {
return errors.New("Unable to finish writing exported key.")
}
return nil
}
// ExportAllKeys exports all keys to an io.Writer in zip format.
// newPassphraseRetriever will be used to obtain passphrases to use to encrypt the existing keys.
func (cs *CryptoService) ExportAllKeys(dest io.Writer, newPassphraseRetriever passphrase.Retriever) error {
tempBaseDir, err := ioutil.TempDir("", "notary-key-export-")
defer os.RemoveAll(tempBaseDir)
// Create temporary keystore to use as a staging area
tempKeyStore, err := trustmanager.NewKeyFileStore(tempBaseDir, newPassphraseRetriever)
if err != nil {
return err
}
for _, ks := range cs.keyStores {
if err := moveKeys(ks, tempKeyStore); err != nil {
return err
}
}
zipWriter := zip.NewWriter(dest)
if err := addKeysToArchive(zipWriter, tempKeyStore); err != nil {
return err
}
zipWriter.Close()
return nil
}
// ImportKeysZip imports keys from a zip file provided as an zip.Reader. The
// keys in the root_keys directory are left encrypted, but the other keys are
// decrypted with the specified passphrase.
func (cs *CryptoService) ImportKeysZip(zipReader zip.Reader, retriever passphrase.Retriever) error {
// Temporarily store the keys in maps, so we can bail early if there's
// an error (for example, wrong passphrase), without leaving the key
// store in an inconsistent state
newKeys := make(map[string][]byte)
// Iterate through the files in the archive. Don't add the keys
for _, f := range zipReader.File {
fNameTrimmed := strings.TrimSuffix(f.Name, filepath.Ext(f.Name))
rc, err := f.Open()
if err != nil {
return err
}
defer rc.Close()
fileBytes, err := ioutil.ReadAll(rc)
if err != nil {
return nil
}
// Note that using / as a separator is okay here - the zip
// package guarantees that the separator will be /
if fNameTrimmed[len(fNameTrimmed)-5:] == "_root" {
if err = CheckRootKeyIsEncrypted(fileBytes); err != nil {
return err
}
}
newKeys[fNameTrimmed] = fileBytes
}
for keyName, pemBytes := range newKeys {
// Get the key role information as well as its data.PrivateKey representation
_, keyInfo, err := trustmanager.KeyInfoFromPEM(pemBytes, keyName)
if err != nil {
return err
}
privKey, err := trustmanager.ParsePEMPrivateKey(pemBytes, "")
if err != nil {
privKey, _, err = trustmanager.GetPasswdDecryptBytes(retriever, pemBytes, "", "imported "+keyInfo.Role)
if err != nil {
return err
}
}
// Add the key to our cryptoservice, will add to the first successful keystore
if err = cs.AddKey(keyInfo.Role, keyInfo.Gun, privKey); err != nil {
return err
}
}
return nil
}
// ExportKeysByGUN exports all keys associated with a specified GUN to an
// io.Writer in zip format. passphraseRetriever is used to select new passphrases to use to
// encrypt the keys.
func (cs *CryptoService) ExportKeysByGUN(dest io.Writer, gun string, passphraseRetriever passphrase.Retriever) error {
tempBaseDir, err := ioutil.TempDir("", "notary-key-export-")
defer os.RemoveAll(tempBaseDir)
// Create temporary keystore to use as a staging area
tempKeyStore, err := trustmanager.NewKeyFileStore(tempBaseDir, passphraseRetriever)
if err != nil {
return err
}
for _, ks := range cs.keyStores {
if err := moveKeysByGUN(ks, tempKeyStore, gun); err != nil {
return err
}
}
zipWriter := zip.NewWriter(dest)
if len(tempKeyStore.ListKeys()) == 0 {
return ErrNoKeysFoundForGUN
}
if err := addKeysToArchive(zipWriter, tempKeyStore); err != nil {
return err
}
zipWriter.Close()
return nil
}
func moveKeysByGUN(oldKeyStore, newKeyStore trustmanager.KeyStore, gun string) error {
for keyID, keyInfo := range oldKeyStore.ListKeys() {
// Skip keys that aren't associated with this GUN
if keyInfo.Gun != gun {
continue
}
privKey, _, err := oldKeyStore.GetKey(keyID)
if err != nil {
return err
}
err = newKeyStore.AddKey(keyInfo, privKey)
if err != nil {
return err
}
}
return nil
}
func moveKeys(oldKeyStore, newKeyStore trustmanager.KeyStore) error {
for keyID, keyInfo := range oldKeyStore.ListKeys() {
privateKey, _, err := oldKeyStore.GetKey(keyID)
if err != nil {
return err
}
err = newKeyStore.AddKey(keyInfo, privateKey)
if err != nil {
return err
}
}
return nil
}
func addKeysToArchive(zipWriter *zip.Writer, newKeyStore *trustmanager.KeyFileStore) error {
for _, relKeyPath := range newKeyStore.ListFiles() {
fullKeyPath, err := newKeyStore.GetPath(relKeyPath)
if err != nil {
return err
}
fi, err := os.Lstat(fullKeyPath)
if err != nil {
return err
}
infoHeader, err := zip.FileInfoHeader(fi)
if err != nil {
return err
}
relPath, err := filepath.Rel(newKeyStore.BaseDir(), fullKeyPath)
if err != nil {
return err
}
infoHeader.Name = relPath
zipFileEntryWriter, err := zipWriter.CreateHeader(infoHeader)
if err != nil {
return err
}
fileContents, err := ioutil.ReadFile(fullKeyPath)
if err != nil {
return err
}
if _, err = zipFileEntryWriter.Write(fileContents); err != nil {
return err
}
}
return nil
}
// CheckRootKeyIsEncrypted makes sure the root key is encrypted. We have
// internal assumptions that depend on this.
func CheckRootKeyIsEncrypted(pemBytes []byte) error {
block, _ := pem.Decode(pemBytes)
if block == nil {
return ErrNoValidPrivateKey
}
if !x509.IsEncryptedPEMBlock(block) {
return ErrRootKeyNotEncrypted
}
return nil
}