mirror of https://github.com/docker/docs.git
				
				
				
			Merge pull request #28 from docker/import_to_yubikey
Import to yubikey Signed-off-by: David Lawrence <david.lawrence@docker.com> Signed-off-by: David Lawrence <dclwrnc@gmail.com> (github: endophage)
This commit is contained in:
		
						commit
						28c3eca478
					
				|  | @ -7,9 +7,10 @@ import ( | |||
| 	"net/http" | ||||
| 	"path/filepath" | ||||
| 
 | ||||
| 	"github.com/docker/notary" | ||||
| 	"github.com/docker/notary/cryptoservice" | ||||
| 	"github.com/docker/notary/keystoremanager" | ||||
| 	"github.com/docker/notary/pkg/passphrase" | ||||
| 	"github.com/docker/notary/passphrase" | ||||
| 	"github.com/docker/notary/trustmanager" | ||||
| 	"github.com/docker/notary/tuf/store" | ||||
| ) | ||||
|  | @ -19,7 +20,7 @@ import ( | |||
| // (usually ~/.docker/trust/).
 | ||||
| func NewNotaryRepository(baseDir, gun, baseURL string, rt http.RoundTripper, | ||||
| 	retriever passphrase.Retriever) (*NotaryRepository, error) { | ||||
| 	keysPath := filepath.Join(baseDir, keystoremanager.PrivDir) | ||||
| 	keysPath := filepath.Join(baseDir, notary.PrivDir) | ||||
| 	fileKeyStore, err := trustmanager.NewKeyFileStore(keysPath, retriever) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("failed to create private key store in directory: %s", keysPath) | ||||
|  |  | |||
|  | @ -7,11 +7,13 @@ import ( | |||
| 	"net/http" | ||||
| 	"path/filepath" | ||||
| 
 | ||||
| 	"github.com/docker/notary" | ||||
| 	"github.com/docker/notary/cryptoservice" | ||||
| 	"github.com/docker/notary/keystoremanager" | ||||
| 	"github.com/docker/notary/pkg/passphrase" | ||||
| 	"github.com/docker/notary/passphrase" | ||||
| 	"github.com/docker/notary/signer/api" | ||||
| 	"github.com/docker/notary/trustmanager" | ||||
| 	"github.com/docker/notary/tuf/signed" | ||||
| 	"github.com/docker/notary/tuf/store" | ||||
| ) | ||||
| 
 | ||||
|  | @ -21,15 +23,20 @@ import ( | |||
| func NewNotaryRepository(baseDir, gun, baseURL string, rt http.RoundTripper, | ||||
| 	retriever passphrase.Retriever) (*NotaryRepository, error) { | ||||
| 
 | ||||
| 	keysPath := filepath.Join(baseDir, keystoremanager.PrivDir) | ||||
| 	keysPath := filepath.Join(baseDir, notary.PrivDir) | ||||
| 	fileKeyStore, err := trustmanager.NewKeyFileStore(keysPath, retriever) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("failed to create private key store in directory: %s", keysPath) | ||||
| 	} | ||||
| 
 | ||||
| 	keyStoreManager, err := keystoremanager.NewKeyStoreManager(baseDir, fileKeyStore) | ||||
| 	yubiKeyStore := api.NewYubiKeyStore(retriever) | ||||
| 	cryptoService := cryptoservice.NewCryptoService(gun, yubiKeyStore, keyStoreManager.KeyStore) | ||||
| 	yubiKeyStore, _ := api.NewYubiKeyStore(fileKeyStore, retriever) | ||||
| 	var cryptoService signed.CryptoService | ||||
| 	if yubiKeyStore == nil { | ||||
| 		cryptoService = cryptoservice.NewCryptoService(gun, keyStoreManager.KeyStore) | ||||
| 	} else { | ||||
| 		cryptoService = cryptoservice.NewCryptoService(gun, yubiKeyStore, keyStoreManager.KeyStore) | ||||
| 	} | ||||
| 
 | ||||
| 	nRepo := &NotaryRepository{ | ||||
| 		gun:             gun, | ||||
|  |  | |||
|  | @ -131,20 +131,6 @@ func main() { | |||
| 
 | ||||
| 	cryptoServices := make(signer.CryptoServiceIndex) | ||||
| 
 | ||||
| 	pin := mainViper.GetString(pinCode) | ||||
| 	pkcs11Lib := mainViper.GetString("crypto.pkcs11lib") | ||||
| 	if pkcs11Lib != "" { | ||||
| 		if pin == "" { | ||||
| 			log.Fatalf("Using PIN is mandatory with pkcs11") | ||||
| 		} | ||||
| 
 | ||||
| 		ctx, session := SetupHSMEnv(pkcs11Lib, pin) | ||||
| 
 | ||||
| 		defer cleanup(ctx, session) | ||||
| 
 | ||||
| 		cryptoServices[data.RSAKey] = api.NewRSAHardwareCryptoService(ctx, session) | ||||
| 	} | ||||
| 
 | ||||
| 	configDBType := strings.ToLower(mainViper.GetString("storage.backend")) | ||||
| 	dbURL := mainViper.GetString("storage.db_url") | ||||
| 	if configDBType != dbType || dbURL == "" { | ||||
|  |  | |||
|  | @ -8,6 +8,7 @@ import ( | |||
| 	"path/filepath" | ||||
| 	"time" | ||||
| 
 | ||||
| 	"github.com/docker/notary" | ||||
| 	"github.com/docker/notary/keystoremanager" | ||||
| 	"github.com/docker/notary/trustmanager" | ||||
| 
 | ||||
|  | @ -56,7 +57,7 @@ func certRemove(cmd *cobra.Command, args []string) { | |||
| 
 | ||||
| 	parseConfig() | ||||
| 
 | ||||
| 	keysPath := filepath.Join(trustDir, keystoremanager.PrivDir) | ||||
| 	keysPath := filepath.Join(trustDir, notary.PrivDir) | ||||
| 	fileKeyStore, err := trustmanager.NewKeyFileStore(keysPath, retriever) | ||||
| 	if err != nil { | ||||
| 		fatalf("failed to create private key store in directory: %s", keysPath) | ||||
|  | @ -125,7 +126,7 @@ func certList(cmd *cobra.Command, args []string) { | |||
| 
 | ||||
| 	parseConfig() | ||||
| 
 | ||||
| 	keysPath := filepath.Join(trustDir, keystoremanager.PrivDir) | ||||
| 	keysPath := filepath.Join(trustDir, notary.PrivDir) | ||||
| 	fileKeyStore, err := trustmanager.NewKeyFileStore(keysPath, retriever) | ||||
| 	if err != nil { | ||||
| 		fatalf("failed to create private key store in directory: %s", keysPath) | ||||
|  |  | |||
|  | @ -8,20 +8,20 @@ import ( | |||
| 	"sort" | ||||
| 	"strings" | ||||
| 
 | ||||
| 	"github.com/docker/notary" | ||||
| 	notaryclient "github.com/docker/notary/client" | ||||
| 	"github.com/docker/notary/keystoremanager" | ||||
| 	"github.com/docker/notary/pkg/passphrase" | ||||
| 	"github.com/docker/notary/cryptoservice" | ||||
| 	"github.com/docker/notary/passphrase" | ||||
| 	"github.com/docker/notary/signer/api" | ||||
| 	"github.com/docker/notary/trustmanager" | ||||
| 
 | ||||
| 	"github.com/docker/notary/tuf/data" | ||||
| 	"github.com/docker/notary/tuf/signed" | ||||
| 	"github.com/spf13/cobra" | ||||
| ) | ||||
| 
 | ||||
| func init() { | ||||
| 	cmdKey.AddCommand(cmdKeyList) | ||||
| 	cmdKey.AddCommand(cmdKeyRemoveKey) | ||||
| 	cmdKeyRemoveKey.Flags().StringVarP(&keyRemoveGUN, "gun", "g", "", "Globally unique name to remove keys for") | ||||
| 	cmdKeyRemoveKey.Flags().BoolVarP(&keyRemoveRoot, "root", "r", false, "Remove root keys") | ||||
| 	cmdKeyRemoveKey.Flags().BoolVarP(&keyRemoveYes, "yes", "y", false, "Answer yes to the removal question (no confirmation)") | ||||
| 	cmdKey.AddCommand(cmdKeyGenerateRootKey) | ||||
| 
 | ||||
| 	cmdKeyExport.Flags().StringVarP(&keysExportGUN, "gun", "g", "", "Globally unique name to export keys for") | ||||
|  | @ -53,17 +53,6 @@ var cmdRotateKey = &cobra.Command{ | |||
| 	Run:   keysRotate, | ||||
| } | ||||
| 
 | ||||
| var keyRemoveGUN string | ||||
| var keyRemoveRoot bool | ||||
| var keyRemoveYes bool | ||||
| 
 | ||||
| var cmdKeyRemoveKey = &cobra.Command{ | ||||
| 	Use:   "remove [ keyID ]", | ||||
| 	Short: "Removes the key with the given keyID.", | ||||
| 	Long:  "remove the key with the given keyID from the local host.", | ||||
| 	Run:   keysRemoveKey, | ||||
| } | ||||
| 
 | ||||
| var cmdKeyGenerateRootKey = &cobra.Command{ | ||||
| 	Use:   "generate [ algorithm ]", | ||||
| 	Short: "Generates a new root key with a given algorithm.", | ||||
|  | @ -103,68 +92,6 @@ var cmdKeyImportRoot = &cobra.Command{ | |||
| 	Run:   keysImportRoot, | ||||
| } | ||||
| 
 | ||||
| // keysRemoveKey deletes a private key based on ID
 | ||||
| func keysRemoveKey(cmd *cobra.Command, args []string) { | ||||
| 	if len(args) < 1 { | ||||
| 		cmd.Usage() | ||||
| 		fatalf("must specify the key ID of the key to remove") | ||||
| 	} | ||||
| 
 | ||||
| 	parseConfig() | ||||
| 
 | ||||
| 	keysPath := filepath.Join(trustDir, keystoremanager.PrivDir) | ||||
| 	fileKeyStore, err := trustmanager.NewKeyFileStore(keysPath, retriever) | ||||
| 	if err != nil { | ||||
| 		fatalf("failed to create private key store in directory: %s", keysPath) | ||||
| 	} | ||||
| 	keyStoreManager, err := keystoremanager.NewKeyStoreManager(trustDir, fileKeyStore) | ||||
| 	if err != nil { | ||||
| 		fatalf("failed to create a new truststore manager with directory: %s", trustDir) | ||||
| 	} | ||||
| 
 | ||||
| 	keyID := args[0] | ||||
| 
 | ||||
| 	// This is an invalid ID
 | ||||
| 	if len(keyID) != idSize { | ||||
| 		fatalf("invalid key ID provided: %s", keyID) | ||||
| 	} | ||||
| 
 | ||||
| 	// List the key about to be removed
 | ||||
| 	fmt.Println("Are you sure you want to remove the following key?") | ||||
| 	fmt.Printf("%s\n(yes/no)\n", keyID) | ||||
| 
 | ||||
| 	// Ask for confirmation before removing the key, unless -y is passed
 | ||||
| 	if !keyRemoveYes { | ||||
| 		confirmed := askConfirm() | ||||
| 		if !confirmed { | ||||
| 			fatalf("aborting action.") | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	// Choose the correct filestore to remove the key from
 | ||||
| 	keyMap := keyStoreManager.KeyStore.ListKeys() | ||||
| 
 | ||||
| 	// Attempt to find the full GUN to the key in the map
 | ||||
| 	// This is irrelevant for removing root keys, but does no harm
 | ||||
| 	var keyWithGUN string | ||||
| 	for k := range keyMap { | ||||
| 		if filepath.Base(k) == keyID { | ||||
| 			keyWithGUN = k | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	// If empty, we didn't find any matches
 | ||||
| 	if keyWithGUN == "" { | ||||
| 		fatalf("key with key ID: %s not found\n", keyID) | ||||
| 	} | ||||
| 
 | ||||
| 	// Attempt to remove the key
 | ||||
| 	err = keyStoreManager.KeyStore.RemoveKey(keyWithGUN) | ||||
| 	if err != nil { | ||||
| 		fatalf("failed to remove key with key ID: %s, %v", keyID, err) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func keysList(cmd *cobra.Command, args []string) { | ||||
| 	if len(args) > 0 { | ||||
| 		cmd.Usage() | ||||
|  | @ -173,18 +100,21 @@ func keysList(cmd *cobra.Command, args []string) { | |||
| 
 | ||||
| 	parseConfig() | ||||
| 
 | ||||
| 	keysPath := filepath.Join(trustDir, keystoremanager.PrivDir) | ||||
| 	keysPath := filepath.Join(trustDir, notary.PrivDir) | ||||
| 	fileKeyStore, err := trustmanager.NewKeyFileStore(keysPath, retriever) | ||||
| 	if err != nil { | ||||
| 		fatalf("failed to create private key store in directory: %s", keysPath) | ||||
| 	} | ||||
| 	keyStoreManager, err := keystoremanager.NewKeyStoreManager(trustDir, fileKeyStore) | ||||
| 	if err != nil { | ||||
| 		fatalf("failed to create a new truststore manager with directory: %s", trustDir) | ||||
| 	yubiStore, _ := api.NewYubiKeyStore(fileKeyStore, retriever) | ||||
| 	var cs signed.CryptoService | ||||
| 	if yubiStore == nil { | ||||
| 		cs = cryptoservice.NewCryptoService("", fileKeyStore) | ||||
| 	} else { | ||||
| 		cs = cryptoservice.NewCryptoService("", yubiStore, fileKeyStore) | ||||
| 	} | ||||
| 
 | ||||
| 	// Get a map of all the keys/roles
 | ||||
| 	keysMap := keyStoreManager.KeyStore.ListKeys() | ||||
| 	keysMap := cs.ListAllKeys() | ||||
| 
 | ||||
| 	fmt.Println("") | ||||
| 	fmt.Println("# Root keys: ") | ||||
|  | @ -231,22 +161,26 @@ func keysGenerateRootKey(cmd *cobra.Command, args []string) { | |||
| 
 | ||||
| 	parseConfig() | ||||
| 
 | ||||
| 	keysPath := filepath.Join(trustDir, keystoremanager.PrivDir) | ||||
| 	keysPath := filepath.Join(trustDir, notary.PrivDir) | ||||
| 	fileKeyStore, err := trustmanager.NewKeyFileStore(keysPath, retriever) | ||||
| 	if err != nil { | ||||
| 		fatalf("failed to create private key store in directory: %s", keysPath) | ||||
| 	} | ||||
| 	keyStoreManager, err := keystoremanager.NewKeyStoreManager(trustDir, fileKeyStore) | ||||
| 	yubiStore, err := api.NewYubiKeyStore(fileKeyStore, retriever) | ||||
| 	var cs signed.CryptoService | ||||
| 	if err != nil { | ||||
| 		fatalf("failed to create a new truststore manager with directory: %s", trustDir) | ||||
| 		cmd.Printf("No Yubikey detected, importing to local filesystem.") | ||||
| 		cs = cryptoservice.NewCryptoService("", fileKeyStore) | ||||
| 	} else { | ||||
| 		cs = cryptoservice.NewCryptoService("", yubiStore, fileKeyStore) | ||||
| 	} | ||||
| 
 | ||||
| 	keyID, err := keyStoreManager.GenRootKey(algorithm) | ||||
| 	pubKey, err := cs.Create(data.CanonicalRootRole, algorithm) | ||||
| 	if err != nil { | ||||
| 		fatalf("failed to create a new root key: %v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	fmt.Printf("Generated new %s key with keyID: %s\n", algorithm, keyID) | ||||
| 	fmt.Printf("Generated new %s root key with keyID: %s\n", algorithm, pubKey.ID()) | ||||
| } | ||||
| 
 | ||||
| // keysExport exports a collection of keys to a ZIP file
 | ||||
|  | @ -260,15 +194,12 @@ func keysExport(cmd *cobra.Command, args []string) { | |||
| 
 | ||||
| 	parseConfig() | ||||
| 
 | ||||
| 	keysPath := filepath.Join(trustDir, keystoremanager.PrivDir) | ||||
| 	keysPath := filepath.Join(trustDir, notary.PrivDir) | ||||
| 	fileKeyStore, err := trustmanager.NewKeyFileStore(keysPath, retriever) | ||||
| 	if err != nil { | ||||
| 		fatalf("failed to create private key store in directory: %s", keysPath) | ||||
| 	} | ||||
| 	keyStoreManager, err := keystoremanager.NewKeyStoreManager(trustDir, fileKeyStore) | ||||
| 	if err != nil { | ||||
| 		fatalf("failed to create a new truststore manager with directory: %s", trustDir) | ||||
| 	} | ||||
| 	cs := cryptoservice.NewCryptoService("", fileKeyStore) | ||||
| 
 | ||||
| 	exportFile, err := os.Create(exportFilename) | ||||
| 	if err != nil { | ||||
|  | @ -279,9 +210,9 @@ func keysExport(cmd *cobra.Command, args []string) { | |||
| 	// unlocking passphrase and reusing that.
 | ||||
| 	exportRetriever := passphrase.PromptRetriever() | ||||
| 	if keysExportGUN != "" { | ||||
| 		err = keyStoreManager.ExportKeysByGUN(exportFile, keysExportGUN, exportRetriever) | ||||
| 		err = cs.ExportKeysByGUN(exportFile, keysExportGUN, exportRetriever) | ||||
| 	} else { | ||||
| 		err = keyStoreManager.ExportAllKeys(exportFile, exportRetriever) | ||||
| 		err = cs.ExportAllKeys(exportFile, exportRetriever) | ||||
| 	} | ||||
| 
 | ||||
| 	exportFile.Close() | ||||
|  | @ -308,15 +239,12 @@ func keysExportRoot(cmd *cobra.Command, args []string) { | |||
| 
 | ||||
| 	parseConfig() | ||||
| 
 | ||||
| 	keysPath := filepath.Join(trustDir, keystoremanager.PrivDir) | ||||
| 	keysPath := filepath.Join(trustDir, notary.PrivDir) | ||||
| 	fileKeyStore, err := trustmanager.NewKeyFileStore(keysPath, retriever) | ||||
| 	if err != nil { | ||||
| 		fatalf("failed to create private key store in directory: %s", keysPath) | ||||
| 	} | ||||
| 	keyStoreManager, err := keystoremanager.NewKeyStoreManager(trustDir, fileKeyStore) | ||||
| 	if err != nil { | ||||
| 		fatalf("failed to create a new truststore manager with directory: %s", trustDir) | ||||
| 	} | ||||
| 	cs := cryptoservice.NewCryptoService("", fileKeyStore) | ||||
| 
 | ||||
| 	exportFile, err := os.Create(exportFilename) | ||||
| 	if err != nil { | ||||
|  | @ -326,9 +254,9 @@ func keysExportRoot(cmd *cobra.Command, args []string) { | |||
| 		// Must use a different passphrase retriever to avoid caching the
 | ||||
| 		// unlocking passphrase and reusing that.
 | ||||
| 		exportRetriever := passphrase.PromptRetriever() | ||||
| 		err = keyStoreManager.ExportRootKeyReencrypt(exportFile, keyID, exportRetriever) | ||||
| 		err = cs.ExportRootKeyReencrypt(exportFile, keyID, exportRetriever) | ||||
| 	} else { | ||||
| 		err = keyStoreManager.ExportRootKey(exportFile, keyID) | ||||
| 		err = cs.ExportRootKey(exportFile, keyID) | ||||
| 	} | ||||
| 	exportFile.Close() | ||||
| 	if err != nil { | ||||
|  | @ -348,15 +276,12 @@ func keysImport(cmd *cobra.Command, args []string) { | |||
| 
 | ||||
| 	parseConfig() | ||||
| 
 | ||||
| 	keysPath := filepath.Join(trustDir, keystoremanager.PrivDir) | ||||
| 	keysPath := filepath.Join(trustDir, notary.PrivDir) | ||||
| 	fileKeyStore, err := trustmanager.NewKeyFileStore(keysPath, retriever) | ||||
| 	if err != nil { | ||||
| 		fatalf("failed to create private key store in directory: %s", keysPath) | ||||
| 	} | ||||
| 	keyStoreManager, err := keystoremanager.NewKeyStoreManager(trustDir, fileKeyStore) | ||||
| 	if err != nil { | ||||
| 		fatalf("failed to create a new truststore manager with directory: %s", trustDir) | ||||
| 	} | ||||
| 	cs := cryptoservice.NewCryptoService("", fileKeyStore) | ||||
| 
 | ||||
| 	zipReader, err := zip.OpenReader(importFilename) | ||||
| 	if err != nil { | ||||
|  | @ -364,7 +289,7 @@ func keysImport(cmd *cobra.Command, args []string) { | |||
| 	} | ||||
| 	defer zipReader.Close() | ||||
| 
 | ||||
| 	err = keyStoreManager.ImportKeysZip(zipReader.Reader) | ||||
| 	err = cs.ImportKeysZip(zipReader.Reader) | ||||
| 
 | ||||
| 	if err != nil { | ||||
| 		fatalf("error importing keys: %v", err) | ||||
|  | @ -382,14 +307,18 @@ func keysImportRoot(cmd *cobra.Command, args []string) { | |||
| 
 | ||||
| 	parseConfig() | ||||
| 
 | ||||
| 	keysPath := filepath.Join(trustDir, keystoremanager.PrivDir) | ||||
| 	keysPath := filepath.Join(trustDir, notary.PrivDir) | ||||
| 	fileKeyStore, err := trustmanager.NewKeyFileStore(keysPath, retriever) | ||||
| 	if err != nil { | ||||
| 		fatalf("failed to create private key store in directory: %s", keysPath) | ||||
| 	} | ||||
| 	keyStoreManager, err := keystoremanager.NewKeyStoreManager(trustDir, fileKeyStore) | ||||
| 	yubiStore, err := api.NewYubiKeyStore(fileKeyStore, retriever) | ||||
| 	var cs signed.CryptoService | ||||
| 	if err != nil { | ||||
| 		fatalf("failed to create a new truststore manager with directory: %s", trustDir) | ||||
| 		cmd.Printf("No Yubikey detected, importing to local filesystem.") | ||||
| 		cs = cryptoservice.NewCryptoService("", fileKeyStore) | ||||
| 	} else { | ||||
| 		cs = cryptoservice.NewCryptoService("", yubiStore, fileKeyStore) | ||||
| 	} | ||||
| 
 | ||||
| 	importFile, err := os.Open(importFilename) | ||||
|  | @ -398,7 +327,7 @@ func keysImportRoot(cmd *cobra.Command, args []string) { | |||
| 	} | ||||
| 	defer importFile.Close() | ||||
| 
 | ||||
| 	err = keyStoreManager.ImportRootKey(importFile) | ||||
| 	err = cs.ImportRootKey(importFile) | ||||
| 
 | ||||
| 	if err != nil { | ||||
| 		fatalf("error importing root key: %v", err) | ||||
|  |  | |||
|  | @ -7,7 +7,7 @@ import ( | |||
| 	"strings" | ||||
| 
 | ||||
| 	"github.com/Sirupsen/logrus" | ||||
| 	"github.com/docker/notary/pkg/passphrase" | ||||
| 	"github.com/docker/notary/passphrase" | ||||
| 	"github.com/docker/notary/version" | ||||
| 	homedir "github.com/mitchellh/go-homedir" | ||||
| 	"github.com/spf13/cobra" | ||||
|  |  | |||
|  | @ -0,0 +1,9 @@ | |||
| package notary | ||||
| 
 | ||||
| // application wide constants
 | ||||
| const ( | ||||
| 	BackupDir    = "backup" | ||||
| 	PrivDir      = "private" | ||||
| 	PrivKeyPerms = 0700 | ||||
| 	PubCertPerms = 0755 | ||||
| ) | ||||
|  | @ -27,7 +27,7 @@ func NewCryptoService(gun string, keyStores ...trustmanager.KeyStore) *CryptoSer | |||
| } | ||||
| 
 | ||||
| // Create is used to generate keys for targets, snapshots and timestamps
 | ||||
| func (ccs *CryptoService) Create(role, algorithm string) (data.PublicKey, error) { | ||||
| func (cs *CryptoService) Create(role, algorithm string) (data.PublicKey, error) { | ||||
| 	var privKey data.PrivateKey | ||||
| 	var err error | ||||
| 
 | ||||
|  | @ -57,10 +57,10 @@ func (ccs *CryptoService) Create(role, algorithm string) (data.PublicKey, error) | |||
| 	if role == data.CanonicalRootRole { | ||||
| 		keyPath = privKey.ID() | ||||
| 	} else { | ||||
| 		keyPath = filepath.Join(ccs.gun, privKey.ID()) | ||||
| 		keyPath = filepath.Join(cs.gun, privKey.ID()) | ||||
| 	} | ||||
| 
 | ||||
| 	for _, ks := range ccs.keyStores { | ||||
| 	for _, ks := range cs.keyStores { | ||||
| 		err = ks.AddKey(keyPath, role, privKey) | ||||
| 		if err == nil { | ||||
| 			return data.PublicKeyFromPrivate(privKey), nil | ||||
|  | @ -77,11 +77,11 @@ func (ccs *CryptoService) Create(role, algorithm string) (data.PublicKey, error) | |||
| // without a GUN (in which case it's a root key).  If that fails, try to get
 | ||||
| // the key with the GUN (non-root key).
 | ||||
| // If that fails, then we don't have the key.
 | ||||
| func (ccs *CryptoService) GetPrivateKey(keyID string) (k data.PrivateKey, id string, err error) { | ||||
| 	keyPaths := []string{keyID, filepath.Join(ccs.gun, keyID)} | ||||
| 	for _, ks := range ccs.keyStores { | ||||
| func (cs *CryptoService) GetPrivateKey(keyID string) (k data.PrivateKey, role string, err error) { | ||||
| 	keyPaths := []string{keyID, filepath.Join(cs.gun, keyID)} | ||||
| 	for _, ks := range cs.keyStores { | ||||
| 		for _, keyPath := range keyPaths { | ||||
| 			k, id, err = ks.GetKey(keyPath) | ||||
| 			k, role, err = ks.GetKey(keyPath) | ||||
| 			if err != nil { | ||||
| 				continue | ||||
| 			} | ||||
|  | @ -92,8 +92,8 @@ func (ccs *CryptoService) GetPrivateKey(keyID string) (k data.PrivateKey, id str | |||
| } | ||||
| 
 | ||||
| // GetKey returns a key by ID
 | ||||
| func (ccs *CryptoService) GetKey(keyID string) data.PublicKey { | ||||
| 	privKey, _, err := ccs.GetPrivateKey(keyID) | ||||
| func (cs *CryptoService) GetKey(keyID string) data.PublicKey { | ||||
| 	privKey, _, err := cs.GetPrivateKey(keyID) | ||||
| 	if err != nil { | ||||
| 		return nil | ||||
| 	} | ||||
|  | @ -101,16 +101,11 @@ func (ccs *CryptoService) GetKey(keyID string) data.PublicKey { | |||
| } | ||||
| 
 | ||||
| // RemoveKey deletes a key by ID
 | ||||
| func (ccs *CryptoService) RemoveKey(keyID string) (err error) { | ||||
| 	keyPaths := []string{keyID, filepath.Join(ccs.gun, keyID)} | ||||
| 	for _, ks := range ccs.keyStores { | ||||
| func (cs *CryptoService) RemoveKey(keyID string) (err error) { | ||||
| 	keyPaths := []string{keyID, filepath.Join(cs.gun, keyID)} | ||||
| 	for _, ks := range cs.keyStores { | ||||
| 		for _, keyPath := range keyPaths { | ||||
| 			_, _, err = ks.GetKey(keyPath) | ||||
| 			if err != nil { | ||||
| 				continue | ||||
| 			} | ||||
| 			err = ks.RemoveKey(keyPath) | ||||
| 			return | ||||
| 			ks.RemoveKey(keyPath) | ||||
| 		} | ||||
| 	} | ||||
| 	return // returns whatever the final values were
 | ||||
|  | @ -119,12 +114,12 @@ func (ccs *CryptoService) RemoveKey(keyID string) (err error) { | |||
| // Sign returns the signatures for the payload with a set of keyIDs. It ignores
 | ||||
| // errors to sign and expects the called to validate if the number of returned
 | ||||
| // signatures is adequate.
 | ||||
| func (ccs *CryptoService) Sign(keyIDs []string, payload []byte) ([]data.Signature, error) { | ||||
| func (cs *CryptoService) Sign(keyIDs []string, payload []byte) ([]data.Signature, error) { | ||||
| 	signatures := make([]data.Signature, 0, len(keyIDs)) | ||||
| 	for _, keyid := range keyIDs { | ||||
| 		keyName := keyid | ||||
| 
 | ||||
| 		privKey, _, err := ccs.GetPrivateKey(keyName) | ||||
| 		privKey, _, err := cs.GetPrivateKey(keyName) | ||||
| 		if err != nil { | ||||
| 			logrus.Debugf("error attempting to retrieve private key: %s, %v", keyid, err) | ||||
| 			continue | ||||
|  | @ -152,9 +147,9 @@ func (ccs *CryptoService) Sign(keyIDs []string, payload []byte) ([]data.Signatur | |||
| } | ||||
| 
 | ||||
| // ListKeys returns a list of key IDs valid for the given role
 | ||||
| func (ccs *CryptoService) ListKeys(role string) []string { | ||||
| func (cs *CryptoService) ListKeys(role string) []string { | ||||
| 	var res []string | ||||
| 	for _, ks := range ccs.keyStores { | ||||
| 	for _, ks := range cs.keyStores { | ||||
| 		for k, r := range ks.ListKeys() { | ||||
| 			if r == role { | ||||
| 				res = append(res, k) | ||||
|  | @ -163,3 +158,14 @@ func (ccs *CryptoService) ListKeys(role string) []string { | |||
| 	} | ||||
| 	return res | ||||
| } | ||||
| 
 | ||||
| // ListAllKeys returns a map of key IDs to role
 | ||||
| func (cs *CryptoService) ListAllKeys() map[string]string { | ||||
| 	res := make(map[string]string) | ||||
| 	for _, ks := range cs.keyStores { | ||||
| 		for k, r := range ks.ListKeys() { | ||||
| 			res[k] = r // keys are content addressed so don't care about overwrites
 | ||||
| 		} | ||||
| 	} | ||||
| 	return res | ||||
| } | ||||
|  |  | |||
|  | @ -1,4 +1,4 @@ | |||
| package keystoremanager | ||||
| package cryptoservice | ||||
| 
 | ||||
| import ( | ||||
| 	"archive/zip" | ||||
|  | @ -11,7 +11,8 @@ import ( | |||
| 	"path/filepath" | ||||
| 	"strings" | ||||
| 
 | ||||
| 	"github.com/docker/notary/pkg/passphrase" | ||||
| 	"github.com/docker/notary" | ||||
| 	"github.com/docker/notary/passphrase" | ||||
| 	"github.com/docker/notary/trustmanager" | ||||
| ) | ||||
| 
 | ||||
|  | @ -33,11 +34,22 @@ var ( | |||
| 
 | ||||
| // ExportRootKey exports the specified root key to an io.Writer in PEM format.
 | ||||
| // The key's existing encryption is preserved.
 | ||||
| func (km *KeyStoreManager) ExportRootKey(dest io.Writer, keyID string) error { | ||||
| 	pemBytes, err := km.KeyStore.ExportKey(keyID) | ||||
| func (cs *CryptoService) ExportRootKey(dest io.Writer, keyID 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 | ||||
|  | @ -50,8 +62,8 @@ func (km *KeyStoreManager) ExportRootKey(dest io.Writer, keyID string) error { | |||
| 
 | ||||
| // ExportRootKeyReencrypt exports the specified root key to an io.Writer in
 | ||||
| // PEM format. The key is reencrypted with a new passphrase.
 | ||||
| func (km *KeyStoreManager) ExportRootKeyReencrypt(dest io.Writer, keyID string, newPassphraseRetriever passphrase.Retriever) error { | ||||
| 	privateKey, alias, err := km.KeyStore.GetKey(keyID) | ||||
| func (cs *CryptoService) ExportRootKeyReencrypt(dest io.Writer, keyID string, newPassphraseRetriever passphrase.Retriever) error { | ||||
| 	privateKey, role, err := cs.GetPrivateKey(keyID) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | @ -60,13 +72,13 @@ func (km *KeyStoreManager) ExportRootKeyReencrypt(dest io.Writer, keyID string, | |||
| 	tempBaseDir, err := ioutil.TempDir("", "notary-key-export-") | ||||
| 	defer os.RemoveAll(tempBaseDir) | ||||
| 
 | ||||
| 	tempKeysPath := filepath.Join(tempBaseDir, PrivDir) | ||||
| 	tempKeysPath := filepath.Join(tempBaseDir, notary.PrivDir) | ||||
| 	tempKeyStore, err := trustmanager.NewKeyFileStore(tempKeysPath, newPassphraseRetriever) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	err = tempKeyStore.AddKey(keyID, alias, privateKey) | ||||
| 	err = tempKeyStore.AddKey(keyID, role, privateKey) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | @ -85,25 +97,10 @@ func (km *KeyStoreManager) ExportRootKeyReencrypt(dest io.Writer, keyID string, | |||
| 	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 | ||||
| } | ||||
| 
 | ||||
| // ImportRootKey imports a root in PEM format key from an io.Reader
 | ||||
| // It prompts for the key's passphrase to verify the data and to determine
 | ||||
| // the key ID.
 | ||||
| func (km *KeyStoreManager) ImportRootKey(source io.Reader) error { | ||||
| func (cs *CryptoService) ImportRootKey(source io.Reader) error { | ||||
| 	pemBytes, err := ioutil.ReadAll(source) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
|  | @ -113,14 +110,160 @@ func (km *KeyStoreManager) ImportRootKey(source io.Reader) error { | |||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	if err = km.KeyStore.ImportKey(pemBytes, "root"); err != nil { | ||||
| 		return err | ||||
| 	for _, ks := range cs.keyStores { | ||||
| 		// don't redeclare err, we want the value carried out of the loop
 | ||||
| 		if err = ks.ImportKey(pemBytes, "root"); err != nil { | ||||
| 			continue | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return err | ||||
| } | ||||
| 
 | ||||
| func moveKeys(oldKeyStore, newKeyStore *trustmanager.KeyFileStore) error { | ||||
| // 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
 | ||||
| 	tempKeysPath := filepath.Join(tempBaseDir, notary.PrivDir) | ||||
| 	tempKeyStore, err := trustmanager.NewKeyFileStore(tempKeysPath, 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, notary.PrivDir); 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) 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 strings.HasPrefix(fNameTrimmed, notary.PrivDir) { | ||||
| 			if fNameTrimmed[len(fNameTrimmed)-5:] == "_root" { | ||||
| 				if err = checkRootKeyIsEncrypted(fileBytes); err != nil { | ||||
| 					return err | ||||
| 				} | ||||
| 			} | ||||
| 			keyName := strings.TrimPrefix(fNameTrimmed, notary.PrivDir) | ||||
| 			newKeys[keyName] = fileBytes | ||||
| 		} else { | ||||
| 			// This path inside the zip archive doesn't look like a
 | ||||
| 			// root key, non-root key, or alias. To avoid adding a file
 | ||||
| 			// to the filestore that we won't be able to use, skip
 | ||||
| 			// this file in the import.
 | ||||
| 			continue | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	for keyName, pemBytes := range newKeys { | ||||
| 		if keyName[len(keyName)-5:] == "_root" { | ||||
| 			keyName = "root" | ||||
| 		} | ||||
| 		for _, ks := range cs.keyStores { | ||||
| 			if err := ks.ImportKey(pemBytes, keyName); 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
 | ||||
| 	tempKeysPath := filepath.Join(tempBaseDir, notary.PrivDir) | ||||
| 	tempKeyStore, err := trustmanager.NewKeyFileStore(tempKeysPath, 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, notary.PrivDir); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	zipWriter.Close() | ||||
| 
 | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func moveKeysByGUN(oldKeyStore, newKeyStore trustmanager.KeyStore, gun string) error { | ||||
| 	for relKeyPath := range oldKeyStore.ListKeys() { | ||||
| 		// Skip keys that aren't associated with this GUN
 | ||||
| 		if !strings.HasPrefix(relKeyPath, filepath.FromSlash(gun)) { | ||||
| 			continue | ||||
| 		} | ||||
| 
 | ||||
| 		privKey, alias, err := oldKeyStore.GetKey(relKeyPath) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 
 | ||||
| 		err = newKeyStore.AddKey(relKeyPath, alias, privKey) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func moveKeys(oldKeyStore, newKeyStore trustmanager.KeyStore) error { | ||||
| 	for f := range oldKeyStore.ListKeys() { | ||||
| 		privateKey, alias, err := oldKeyStore.GetKey(f) | ||||
| 		if err != nil { | ||||
|  | @ -171,139 +314,17 @@ func addKeysToArchive(zipWriter *zip.Writer, newKeyStore *trustmanager.KeyFileSt | |||
| 	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 (km *KeyStoreManager) 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
 | ||||
| 	tempKeysPath := filepath.Join(tempBaseDir, PrivDir) | ||||
| 	tempKeyStore, err := trustmanager.NewKeyFileStore(tempKeysPath, newPassphraseRetriever) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| // 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 err := moveKeys(km.KeyStore, tempKeyStore); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	zipWriter := zip.NewWriter(dest) | ||||
| 
 | ||||
| 	if err := addKeysToArchive(zipWriter, tempKeyStore, PrivDir); 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 (km *KeyStoreManager) ImportKeysZip(zipReader zip.Reader) 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 | ||||
| 		} | ||||
| 
 | ||||
| 		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 strings.HasPrefix(fNameTrimmed, PrivDir) { | ||||
| 			if fNameTrimmed[len(fNameTrimmed)-5:] == "_root" { | ||||
| 				if err = checkRootKeyIsEncrypted(fileBytes); err != nil { | ||||
| 					rc.Close() | ||||
| 					return err | ||||
| 				} | ||||
| 			} | ||||
| 			keyName := strings.TrimPrefix(fNameTrimmed, PrivDir) | ||||
| 			newKeys[keyName] = fileBytes | ||||
| 		} else { | ||||
| 			// This path inside the zip archive doesn't look like a
 | ||||
| 			// root key, non-root key, or alias. To avoid adding a file
 | ||||
| 			// to the filestore that we won't be able to use, skip
 | ||||
| 			// this file in the import.
 | ||||
| 			rc.Close() | ||||
| 			continue | ||||
| 		} | ||||
| 
 | ||||
| 		rc.Close() | ||||
| 	} | ||||
| 
 | ||||
| 	for keyName, pemBytes := range newKeys { | ||||
| 		if err := km.KeyStore.Add(keyName, pemBytes); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	if !x509.IsEncryptedPEMBlock(block) { | ||||
| 		return ErrRootKeyNotEncrypted | ||||
| 	} | ||||
| 
 | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func moveKeysByGUN(oldKeyStore, newKeyStore *trustmanager.KeyFileStore, gun string) error { | ||||
| 	for relKeyPath := range oldKeyStore.ListKeys() { | ||||
| 		// Skip keys that aren't associated with this GUN
 | ||||
| 		if !strings.HasPrefix(relKeyPath, filepath.FromSlash(gun)) { | ||||
| 			continue | ||||
| 		} | ||||
| 
 | ||||
| 		privKey, alias, err := oldKeyStore.GetKey(relKeyPath) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 
 | ||||
| 		err = newKeyStore.AddKey(relKeyPath, alias, privKey) | ||||
| 		if 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 (km *KeyStoreManager) 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
 | ||||
| 	tempKeysPath := filepath.Join(tempBaseDir, PrivDir) | ||||
| 	tempKeyStore, err := trustmanager.NewKeyFileStore(tempKeysPath, passphraseRetriever) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	if err := moveKeysByGUN(km.KeyStore, tempKeyStore, gun); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	zipWriter := zip.NewWriter(dest) | ||||
| 
 | ||||
| 	if len(tempKeyStore.ListKeys()) == 0 { | ||||
| 		return ErrNoKeysFoundForGUN | ||||
| 	} | ||||
| 
 | ||||
| 	if err := addKeysToArchive(zipWriter, tempKeyStore, PrivDir); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	zipWriter.Close() | ||||
| 
 | ||||
| 	return nil | ||||
| } | ||||
|  | @ -1,4 +1,4 @@ | |||
| package keystoremanager_test | ||||
| package cryptoservice | ||||
| 
 | ||||
| import ( | ||||
| 	"archive/zip" | ||||
|  | @ -12,8 +12,6 @@ import ( | |||
| 	"strings" | ||||
| 	"testing" | ||||
| 
 | ||||
| 	"github.com/docker/notary/client" | ||||
| 	"github.com/docker/notary/keystoremanager" | ||||
| 	"github.com/docker/notary/trustmanager" | ||||
| 	"github.com/docker/notary/tuf/data" | ||||
| 	"github.com/stretchr/testify/assert" | ||||
|  | @ -48,26 +46,20 @@ func TestImportExportZip(t *testing.T) { | |||
| 	// Temporary directory where test files will be created
 | ||||
| 	tempBaseDir, err := ioutil.TempDir("", "notary-test-") | ||||
| 	defer os.RemoveAll(tempBaseDir) | ||||
| 
 | ||||
| 	assert.NoError(t, err, "failed to create a temporary directory: %s", err) | ||||
| 
 | ||||
| 	ts, _ := createTestServer(t) | ||||
| 	defer ts.Close() | ||||
| 	fileStore, err := trustmanager.NewKeyFileStore(filepath.Join(tempBaseDir, "private"), newPassphraseRetriever) | ||||
| 	cs := NewCryptoService(gun, fileStore) | ||||
| 	pubKey, err := cs.Create(data.CanonicalRootRole, data.ECDSAKey) | ||||
| 	assert.NoError(t, err) | ||||
| 
 | ||||
| 	repo, err := client.NewNotaryRepository(tempBaseDir, gun, ts.URL, http.DefaultTransport, oldPassphraseRetriever) | ||||
| 	assert.NoError(t, err, "error creating repo: %s", err) | ||||
| 
 | ||||
| 	rootKeyID, err := repo.KeyStoreManager.GenRootKey(data.ECDSAKey) | ||||
| 	assert.NoError(t, err, "error generating root key: %s", err) | ||||
| 
 | ||||
| 	err = repo.Initialize(rootKeyID) | ||||
| 	assert.NoError(t, err, "error creating repository: %s", err) | ||||
| 	rootKeyID := pubKey.ID() | ||||
| 
 | ||||
| 	tempZipFile, err := ioutil.TempFile("", "notary-test-export-") | ||||
| 	tempZipFilePath := tempZipFile.Name() | ||||
| 	defer os.Remove(tempZipFilePath) | ||||
| 
 | ||||
| 	err = repo.KeyStoreManager.ExportAllKeys(tempZipFile, newPassphraseRetriever) | ||||
| 	err = cs.ExportAllKeys(tempZipFile, newPassphraseRetriever) | ||||
| 	tempZipFile.Close() | ||||
| 	assert.NoError(t, err) | ||||
| 
 | ||||
|  | @ -80,9 +72,9 @@ func TestImportExportZip(t *testing.T) { | |||
| 
 | ||||
| 	// Add non-root keys to the map. These should use the new passphrase
 | ||||
| 	// because the passwords were chosen by the newPassphraseRetriever.
 | ||||
| 	privKeyMap := repo.KeyStoreManager.KeyStore.ListKeys() | ||||
| 	privKeyMap := cs.ListAllKeys() | ||||
| 	for privKeyName := range privKeyMap { | ||||
| 		_, alias, err := repo.KeyStoreManager.KeyStore.GetKey(privKeyName) | ||||
| 		_, alias, err := cs.GetPrivateKey(privKeyName) | ||||
| 		assert.NoError(t, err, "privKey %s has no alias", privKeyName) | ||||
| 
 | ||||
| 		if alias == "root" { | ||||
|  | @ -129,31 +121,25 @@ func TestImportExportZip(t *testing.T) { | |||
| 	// Create new repo to test import
 | ||||
| 	tempBaseDir2, err := ioutil.TempDir("", "notary-test-") | ||||
| 	defer os.RemoveAll(tempBaseDir2) | ||||
| 
 | ||||
| 	assert.NoError(t, err, "failed to create a temporary directory: %s", err) | ||||
| 
 | ||||
| 	repo2, err := client.NewNotaryRepository(tempBaseDir2, gun, ts.URL, http.DefaultTransport, newPassphraseRetriever) | ||||
| 	assert.NoError(t, err, "error creating repo: %s", err) | ||||
| 
 | ||||
| 	rootKeyID2, err := repo2.KeyStoreManager.GenRootKey(data.ECDSAKey) | ||||
| 	assert.NoError(t, err, "error generating root key: %s", err) | ||||
| 
 | ||||
| 	err = repo2.Initialize(rootKeyID2) | ||||
| 	assert.NoError(t, err, "error creating repository: %s", err) | ||||
| 	fileStore2, err := trustmanager.NewKeyFileStore(filepath.Join(tempBaseDir2, "private"), newPassphraseRetriever) | ||||
| 	assert.NoError(t, err) | ||||
| 	cs2 := NewCryptoService(gun, fileStore2) | ||||
| 
 | ||||
| 	// Reopen the zip file for importing
 | ||||
| 	zipReader, err = zip.OpenReader(tempZipFilePath) | ||||
| 	assert.NoError(t, err, "could not open zip file") | ||||
| 
 | ||||
| 	// Now try with a valid passphrase. This time it should succeed.
 | ||||
| 	err = repo2.KeyStoreManager.ImportKeysZip(zipReader.Reader) | ||||
| 	err = cs2.ImportKeysZip(zipReader.Reader) | ||||
| 	assert.NoError(t, err) | ||||
| 	zipReader.Close() | ||||
| 
 | ||||
| 	// Look for keys in private. The filenames should match the key IDs
 | ||||
| 	// in the repo's private key store.
 | ||||
| 	for privKeyName := range privKeyMap { | ||||
| 		_, alias, err := repo.KeyStoreManager.KeyStore.GetKey(privKeyName) | ||||
| 		_, alias, err := cs2.GetPrivateKey(privKeyName) | ||||
| 		assert.NoError(t, err, "privKey %s has no alias", privKeyName) | ||||
| 
 | ||||
| 		if alias == "root" { | ||||
|  | @ -162,7 +148,7 @@ func TestImportExportZip(t *testing.T) { | |||
| 		relKeyPath := filepath.Join("private", "tuf_keys", privKeyName+"_"+alias+".key") | ||||
| 		privKeyFileName := filepath.Join(tempBaseDir2, relKeyPath) | ||||
| 		_, err = os.Stat(privKeyFileName) | ||||
| 		assert.NoError(t, err, "missing private key: %s", privKeyName) | ||||
| 		assert.NoError(t, err, "missing private key for role %s: %s", alias, privKeyName) | ||||
| 	} | ||||
| 
 | ||||
| 	// Look for keys in root_keys
 | ||||
|  | @ -179,31 +165,25 @@ func TestImportExportGUN(t *testing.T) { | |||
| 	// Temporary directory where test files will be created
 | ||||
| 	tempBaseDir, err := ioutil.TempDir("", "notary-test-") | ||||
| 	defer os.RemoveAll(tempBaseDir) | ||||
| 
 | ||||
| 	assert.NoError(t, err, "failed to create a temporary directory: %s", err) | ||||
| 
 | ||||
| 	ts, _ := createTestServer(t) | ||||
| 	defer ts.Close() | ||||
| 
 | ||||
| 	repo, err := client.NewNotaryRepository(tempBaseDir, gun, ts.URL, http.DefaultTransport, oldPassphraseRetriever) | ||||
| 	assert.NoError(t, err, "error creating repo: %s", err) | ||||
| 
 | ||||
| 	rootKeyID, err := repo.KeyStoreManager.GenRootKey(data.ECDSAKey) | ||||
| 	assert.NoError(t, err, "error generating root key: %s", err) | ||||
| 
 | ||||
| 	err = repo.Initialize(rootKeyID) | ||||
| 	assert.NoError(t, err, "error creating repository: %s", err) | ||||
| 	fileStore, err := trustmanager.NewKeyFileStore(filepath.Join(tempBaseDir, "private"), newPassphraseRetriever) | ||||
| 	cs := NewCryptoService(gun, fileStore) | ||||
| 	_, err = cs.Create(data.CanonicalRootRole, data.ECDSAKey) | ||||
| 	_, err = cs.Create(data.CanonicalTargetsRole, data.ECDSAKey) | ||||
| 	_, err = cs.Create(data.CanonicalSnapshotRole, data.ECDSAKey) | ||||
| 	assert.NoError(t, err) | ||||
| 
 | ||||
| 	tempZipFile, err := ioutil.TempFile("", "notary-test-export-") | ||||
| 	tempZipFilePath := tempZipFile.Name() | ||||
| 	defer os.Remove(tempZipFilePath) | ||||
| 
 | ||||
| 	err = repo.KeyStoreManager.ExportKeysByGUN(tempZipFile, gun, newPassphraseRetriever) | ||||
| 	err = cs.ExportKeysByGUN(tempZipFile, gun, newPassphraseRetriever) | ||||
| 	assert.NoError(t, err) | ||||
| 
 | ||||
| 	// With an invalid GUN, this should return an error
 | ||||
| 	err = repo.KeyStoreManager.ExportKeysByGUN(tempZipFile, "does.not.exist/in/repository", newPassphraseRetriever) | ||||
| 	assert.EqualError(t, err, keystoremanager.ErrNoKeysFoundForGUN.Error()) | ||||
| 	err = cs.ExportKeysByGUN(tempZipFile, "does.not.exist/in/repository", newPassphraseRetriever) | ||||
| 	assert.EqualError(t, err, ErrNoKeysFoundForGUN.Error()) | ||||
| 
 | ||||
| 	tempZipFile.Close() | ||||
| 
 | ||||
|  | @ -216,9 +196,9 @@ func TestImportExportGUN(t *testing.T) { | |||
| 
 | ||||
| 	// Add keys non-root keys to the map. These should use the new passphrase
 | ||||
| 	// because they were formerly unencrypted.
 | ||||
| 	privKeyMap := repo.KeyStoreManager.KeyStore.ListKeys() | ||||
| 	privKeyMap := cs.ListAllKeys() | ||||
| 	for privKeyName := range privKeyMap { | ||||
| 		_, alias, err := repo.KeyStoreManager.KeyStore.GetKey(privKeyName) | ||||
| 		_, alias, err := cs.GetPrivateKey(privKeyName) | ||||
| 		if err != nil { | ||||
| 			t.Fatalf("privKey %s has no alias", privKeyName) | ||||
| 		} | ||||
|  | @ -263,31 +243,27 @@ func TestImportExportGUN(t *testing.T) { | |||
| 	// Create new repo to test import
 | ||||
| 	tempBaseDir2, err := ioutil.TempDir("", "notary-test-") | ||||
| 	defer os.RemoveAll(tempBaseDir2) | ||||
| 
 | ||||
| 	assert.NoError(t, err, "failed to create a temporary directory: %s", err) | ||||
| 
 | ||||
| 	repo2, err := client.NewNotaryRepository(tempBaseDir2, gun, ts.URL, http.DefaultTransport, oldPassphraseRetriever) | ||||
| 	assert.NoError(t, err, "error creating repo: %s", err) | ||||
| 
 | ||||
| 	rootKeyID2, err := repo2.KeyStoreManager.GenRootKey(data.ECDSAKey) | ||||
| 	assert.NoError(t, err, "error generating root key: %s", err) | ||||
| 
 | ||||
| 	err = repo2.Initialize(rootKeyID2) | ||||
| 	assert.NoError(t, err, "error creating repository: %s", err) | ||||
| 	fileStore2, err := trustmanager.NewKeyFileStore(filepath.Join(tempBaseDir2, "private"), newPassphraseRetriever) | ||||
| 	cs2 := NewCryptoService(gun, fileStore2) | ||||
| 
 | ||||
| 	// Reopen the zip file for importing
 | ||||
| 	zipReader, err = zip.OpenReader(tempZipFilePath) | ||||
| 	assert.NoError(t, err, "could not open zip file") | ||||
| 
 | ||||
| 	// Now try with a valid passphrase. This time it should succeed.
 | ||||
| 	err = repo2.KeyStoreManager.ImportKeysZip(zipReader.Reader) | ||||
| 	err = cs2.ImportKeysZip(zipReader.Reader) | ||||
| 	assert.NoError(t, err) | ||||
| 	zipReader.Close() | ||||
| 
 | ||||
| 	// Look for keys in private. The filenames should match the key IDs
 | ||||
| 	// in the repo's private key store.
 | ||||
| 	for privKeyName := range privKeyMap { | ||||
| 		_, alias, err := repo.KeyStoreManager.KeyStore.GetKey(privKeyName) | ||||
| 	for privKeyName, role := range privKeyMap { | ||||
| 		if role == "root" { | ||||
| 			continue | ||||
| 		} | ||||
| 		_, alias, err := cs2.GetPrivateKey(privKeyName) | ||||
| 		if err != nil { | ||||
| 			t.Fatalf("privKey %s has no alias", privKeyName) | ||||
| 		} | ||||
|  | @ -297,6 +273,7 @@ func TestImportExportGUN(t *testing.T) { | |||
| 		relKeyPath := filepath.Join("private", "tuf_keys", privKeyName+"_"+alias+".key") | ||||
| 		privKeyFileName := filepath.Join(tempBaseDir2, relKeyPath) | ||||
| 		_, err = os.Stat(privKeyFileName) | ||||
| 		assert.NoError(t, err) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
|  | @ -306,48 +283,35 @@ func TestImportExportRootKey(t *testing.T) { | |||
| 	// Temporary directory where test files will be created
 | ||||
| 	tempBaseDir, err := ioutil.TempDir("", "notary-test-") | ||||
| 	defer os.RemoveAll(tempBaseDir) | ||||
| 
 | ||||
| 	assert.NoError(t, err, "failed to create a temporary directory: %s", err) | ||||
| 
 | ||||
| 	ts, _ := createTestServer(t) | ||||
| 	defer ts.Close() | ||||
| 	fileStore, err := trustmanager.NewKeyFileStore(filepath.Join(tempBaseDir, "private"), oldPassphraseRetriever) | ||||
| 	cs := NewCryptoService(gun, fileStore) | ||||
| 	pubKey, err := cs.Create(data.CanonicalRootRole, data.ECDSAKey) | ||||
| 	assert.NoError(t, err) | ||||
| 
 | ||||
| 	repo, err := client.NewNotaryRepository(tempBaseDir, gun, ts.URL, http.DefaultTransport, oldPassphraseRetriever) | ||||
| 	assert.NoError(t, err, "error creating repo: %s", err) | ||||
| 
 | ||||
| 	rootKeyID, err := repo.KeyStoreManager.GenRootKey(data.ECDSAKey) | ||||
| 	assert.NoError(t, err, "error generating root key: %s", err) | ||||
| 
 | ||||
| 	err = repo.Initialize(rootKeyID) | ||||
| 	assert.NoError(t, err, "error creating repository: %s", err) | ||||
| 	rootKeyID := pubKey.ID() | ||||
| 
 | ||||
| 	tempKeyFile, err := ioutil.TempFile("", "notary-test-export-") | ||||
| 	tempKeyFilePath := tempKeyFile.Name() | ||||
| 	defer os.Remove(tempKeyFilePath) | ||||
| 
 | ||||
| 	err = repo.KeyStoreManager.ExportRootKey(tempKeyFile, rootKeyID) | ||||
| 	err = cs.ExportRootKey(tempKeyFile, rootKeyID) | ||||
| 	assert.NoError(t, err) | ||||
| 	tempKeyFile.Close() | ||||
| 
 | ||||
| 	// Create new repo to test import
 | ||||
| 	tempBaseDir2, err := ioutil.TempDir("", "notary-test-") | ||||
| 	defer os.RemoveAll(tempBaseDir2) | ||||
| 
 | ||||
| 	assert.NoError(t, err, "failed to create a temporary directory: %s", err) | ||||
| 
 | ||||
| 	repo2, err := client.NewNotaryRepository(tempBaseDir2, gun, ts.URL, http.DefaultTransport, oldPassphraseRetriever) | ||||
| 	assert.NoError(t, err, "error creating repo: %s", err) | ||||
| 
 | ||||
| 	rootKeyID2, err := repo2.KeyStoreManager.GenRootKey(data.ECDSAKey) | ||||
| 	assert.NoError(t, err, "error generating root key: %s", err) | ||||
| 
 | ||||
| 	err = repo2.Initialize(rootKeyID2) | ||||
| 	assert.NoError(t, err, "error creating repository: %s", err) | ||||
| 	fileStore2, err := trustmanager.NewKeyFileStore(filepath.Join(tempBaseDir2, "private"), oldPassphraseRetriever) | ||||
| 	cs2 := NewCryptoService(gun, fileStore2) | ||||
| 
 | ||||
| 	keyReader, err := os.Open(tempKeyFilePath) | ||||
| 	assert.NoError(t, err, "could not open key file") | ||||
| 
 | ||||
| 	err = repo2.KeyStoreManager.ImportRootKey(keyReader) | ||||
| 	err = cs2.ImportRootKey(keyReader) | ||||
| 	assert.NoError(t, err) | ||||
| 	keyReader.Close() | ||||
| 
 | ||||
|  | @ -367,15 +331,15 @@ func TestImportExportRootKey(t *testing.T) { | |||
| 	decryptedPEMBytes, err := trustmanager.KeyToPEM(privKey) | ||||
| 	assert.NoError(t, err, "could not convert key to PEM") | ||||
| 
 | ||||
| 	err = repo2.KeyStoreManager.ImportRootKey(bytes.NewReader(decryptedPEMBytes)) | ||||
| 	assert.EqualError(t, err, keystoremanager.ErrRootKeyNotEncrypted.Error()) | ||||
| 	err = cs2.ImportRootKey(bytes.NewReader(decryptedPEMBytes)) | ||||
| 	assert.EqualError(t, err, ErrRootKeyNotEncrypted.Error()) | ||||
| 
 | ||||
| 	// Try to import garbage and make sure it doesn't succeed
 | ||||
| 	err = repo2.KeyStoreManager.ImportRootKey(strings.NewReader("this is not PEM")) | ||||
| 	assert.EqualError(t, err, keystoremanager.ErrNoValidPrivateKey.Error()) | ||||
| 	err = cs2.ImportRootKey(strings.NewReader("this is not PEM")) | ||||
| 	assert.EqualError(t, err, ErrNoValidPrivateKey.Error()) | ||||
| 
 | ||||
| 	// Should be able to unlock the root key with the old password
 | ||||
| 	key, alias, err := repo2.KeyStoreManager.KeyStore.GetKey(rootKeyID) | ||||
| 	key, alias, err := cs2.GetPrivateKey(rootKeyID) | ||||
| 	assert.NoError(t, err, "could not unlock root key") | ||||
| 	assert.Equal(t, "root", alias) | ||||
| 	assert.Equal(t, rootKeyID, key.ID()) | ||||
|  | @ -387,48 +351,35 @@ func TestImportExportRootKeyReencrypt(t *testing.T) { | |||
| 	// Temporary directory where test files will be created
 | ||||
| 	tempBaseDir, err := ioutil.TempDir("", "notary-test-") | ||||
| 	defer os.RemoveAll(tempBaseDir) | ||||
| 
 | ||||
| 	assert.NoError(t, err, "failed to create a temporary directory: %s", err) | ||||
| 
 | ||||
| 	ts, _ := createTestServer(t) | ||||
| 	defer ts.Close() | ||||
| 	fileStore, err := trustmanager.NewKeyFileStore(filepath.Join(tempBaseDir, "private"), oldPassphraseRetriever) | ||||
| 	cs := NewCryptoService(gun, fileStore) | ||||
| 	pubKey, err := cs.Create(data.CanonicalRootRole, data.ECDSAKey) | ||||
| 	assert.NoError(t, err) | ||||
| 
 | ||||
| 	repo, err := client.NewNotaryRepository(tempBaseDir, gun, ts.URL, http.DefaultTransport, oldPassphraseRetriever) | ||||
| 	assert.NoError(t, err, "error creating repo: %s", err) | ||||
| 
 | ||||
| 	rootKeyID, err := repo.KeyStoreManager.GenRootKey(data.ECDSAKey) | ||||
| 	assert.NoError(t, err, "error generating root key: %s", err) | ||||
| 
 | ||||
| 	err = repo.Initialize(rootKeyID) | ||||
| 	assert.NoError(t, err, "error creating repository: %s", err) | ||||
| 	rootKeyID := pubKey.ID() | ||||
| 
 | ||||
| 	tempKeyFile, err := ioutil.TempFile("", "notary-test-export-") | ||||
| 	tempKeyFilePath := tempKeyFile.Name() | ||||
| 	defer os.Remove(tempKeyFilePath) | ||||
| 
 | ||||
| 	err = repo.KeyStoreManager.ExportRootKeyReencrypt(tempKeyFile, rootKeyID, newPassphraseRetriever) | ||||
| 	err = cs.ExportRootKeyReencrypt(tempKeyFile, rootKeyID, newPassphraseRetriever) | ||||
| 	assert.NoError(t, err) | ||||
| 	tempKeyFile.Close() | ||||
| 
 | ||||
| 	// Create new repo to test import
 | ||||
| 	tempBaseDir2, err := ioutil.TempDir("", "notary-test-") | ||||
| 	defer os.RemoveAll(tempBaseDir2) | ||||
| 
 | ||||
| 	assert.NoError(t, err, "failed to create a temporary directory: %s", err) | ||||
| 
 | ||||
| 	repo2, err := client.NewNotaryRepository(tempBaseDir2, gun, ts.URL, http.DefaultTransport, newPassphraseRetriever) | ||||
| 	assert.NoError(t, err, "error creating repo: %s", err) | ||||
| 
 | ||||
| 	rootKeyID2, err := repo2.KeyStoreManager.GenRootKey(data.ECDSAKey) | ||||
| 	assert.NoError(t, err, "error generating root key: %s", err) | ||||
| 
 | ||||
| 	err = repo2.Initialize(rootKeyID2) | ||||
| 	assert.NoError(t, err, "error creating repository: %s", err) | ||||
| 	fileStore2, err := trustmanager.NewKeyFileStore(filepath.Join(tempBaseDir2, "private"), newPassphraseRetriever) | ||||
| 	cs2 := NewCryptoService(gun, fileStore2) | ||||
| 
 | ||||
| 	keyReader, err := os.Open(tempKeyFilePath) | ||||
| 	assert.NoError(t, err, "could not open key file") | ||||
| 
 | ||||
| 	err = repo2.KeyStoreManager.ImportRootKey(keyReader) | ||||
| 	err = cs2.ImportRootKey(keyReader) | ||||
| 	assert.NoError(t, err) | ||||
| 	keyReader.Close() | ||||
| 
 | ||||
|  | @ -440,7 +391,7 @@ func TestImportExportRootKeyReencrypt(t *testing.T) { | |||
| 	assert.NoError(t, err, "missing root key") | ||||
| 
 | ||||
| 	// Should be able to unlock the root key with the new password
 | ||||
| 	key, alias, err := repo2.KeyStoreManager.KeyStore.GetKey(rootKeyID) | ||||
| 	key, alias, err := cs2.GetPrivateKey(rootKeyID) | ||||
| 	assert.NoError(t, err, "could not unlock root key") | ||||
| 	assert.Equal(t, "root", alias) | ||||
| 	assert.Equal(t, rootKeyID, key.ID()) | ||||
|  | @ -25,8 +25,6 @@ type KeyStoreManager struct { | |||
| 
 | ||||
| const ( | ||||
| 	trustDir       = "trusted_certificates" | ||||
| 	// PrivDir is the name of the private directory
 | ||||
| 	PrivDir        = "private" | ||||
| 	rsaRootKeySize = 4096 // Used for new root keys
 | ||||
| ) | ||||
| 
 | ||||
|  |  | |||
|  | @ -10,6 +10,7 @@ import ( | |||
| 	"testing" | ||||
| 	"text/template" | ||||
| 
 | ||||
| 	"github.com/docker/notary" | ||||
| 	"github.com/docker/notary/cryptoservice" | ||||
| 	"github.com/docker/notary/trustmanager" | ||||
| 	"github.com/docker/notary/tuf/data" | ||||
|  | @ -122,7 +123,7 @@ func TestValidateRoot(t *testing.T) { | |||
| 	defer os.RemoveAll(tempBaseDir) | ||||
| 	assert.NoError(t, err, "failed to create a temporary directory: %s", err) | ||||
| 
 | ||||
| 	keysPath := filepath.Join(tempBaseDir, PrivDir) | ||||
| 	keysPath := filepath.Join(tempBaseDir, notary.PrivDir) | ||||
| 	fileKeyStore, err := trustmanager.NewKeyFileStore(keysPath, passphraseRetriever) | ||||
| 	assert.NoError(t, err) | ||||
| 
 | ||||
|  | @ -234,7 +235,7 @@ func filestoreWithTwoCerts(t *testing.T, gun, keyAlg string) ( | |||
| 	tempBaseDir, err := ioutil.TempDir("", "notary-test-") | ||||
| 	assert.NoError(t, err, "failed to create a temporary directory: %s", err) | ||||
| 
 | ||||
| 	keysPath := filepath.Join(tempBaseDir, PrivDir) | ||||
| 	keysPath := filepath.Join(tempBaseDir, notary.PrivDir) | ||||
| 	fileKeyStore, err := trustmanager.NewKeyFileStore(keysPath, passphraseRetriever) | ||||
| 	assert.NoError(t, err) | ||||
| 
 | ||||
|  |  | |||
|  | @ -113,22 +113,23 @@ func PromptRetrieverWithInOut(in io.Reader, out io.Writer, aliasMap map[string]s | |||
| 			indexOfLastSeparator = 0 | ||||
| 		} | ||||
| 
 | ||||
| 		var shortName string | ||||
| 		if len(keyName) > indexOfLastSeparator+idBytesToDisplay { | ||||
| 			if indexOfLastSeparator > 0 { | ||||
| 				keyNamePrefix := keyName[:indexOfLastSeparator] | ||||
| 				keyNameID := keyName[indexOfLastSeparator+1 : indexOfLastSeparator+idBytesToDisplay+1] | ||||
| 				keyName = keyNamePrefix + " (" + keyNameID + ")" | ||||
| 				shortName = keyNamePrefix + " (" + keyNameID + ")" | ||||
| 			} else { | ||||
| 				keyName = keyName[indexOfLastSeparator : indexOfLastSeparator+idBytesToDisplay] | ||||
| 				shortName = keyName[indexOfLastSeparator : indexOfLastSeparator+idBytesToDisplay] | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		if createNew { | ||||
| 			fmt.Fprintf(out, "Enter passphrase for new %s key with id %s: ", displayAlias, keyName) | ||||
| 			fmt.Fprintf(out, "Enter passphrase for new %s key with id %s: ", displayAlias, shortName) | ||||
| 		} else if displayAlias == "yubikey" { | ||||
| 			fmt.Fprintf(out, "Enter the %s for the attached Yubikey: ", keyName) | ||||
| 		} else { | ||||
| 			fmt.Fprintf(out, "Enter passphrase for %s key with id %s: ", displayAlias, keyName) | ||||
| 			fmt.Fprintf(out, "Enter passphrase for %s key with id %s: ", displayAlias, shortName) | ||||
| 		} | ||||
| 
 | ||||
| 		passphrase, err := stdin.ReadBytes('\n') | ||||
|  | @ -15,6 +15,7 @@ import ( | |||
| 	"github.com/docker/notary/signer/api" | ||||
| 	"github.com/docker/notary/trustmanager" | ||||
| 	"github.com/docker/notary/tuf/data" | ||||
| 	"github.com/docker/notary/tuf/signed" | ||||
| 	"github.com/stretchr/testify/assert" | ||||
| 
 | ||||
| 	pb "github.com/docker/notary/proto" | ||||
|  | @ -131,7 +132,7 @@ func TestHSMCreateKeyHandler(t *testing.T) { | |||
| 	defer ctx.CloseSession(session) | ||||
| 	defer ctx.Logout(session) | ||||
| 
 | ||||
| 	cryptoService := api.NewRSAHardwareCryptoService(ctx, session) | ||||
| 	cryptoService := signed.NewEd25519() | ||||
| 	setup(signer.CryptoServiceIndex{data.RSAKey: cryptoService}) | ||||
| 
 | ||||
| 	createKeyURL := fmt.Sprintf("%s/%s", createKeyBaseURL, data.RSAKey) | ||||
|  | @ -182,7 +183,7 @@ func TestHSMSignHandler(t *testing.T) { | |||
| 	defer ctx.CloseSession(session) | ||||
| 	defer ctx.Logout(session) | ||||
| 
 | ||||
| 	cryptoService := api.NewRSAHardwareCryptoService(ctx, session) | ||||
| 	cryptoService := signed.NewEd25519() | ||||
| 	setup(signer.CryptoServiceIndex{data.RSAKey: cryptoService}) | ||||
| 
 | ||||
| 	tufKey, _ := cryptoService.Create("", data.RSAKey) | ||||
|  |  | |||
|  | @ -15,7 +15,7 @@ import ( | |||
| 	"math/big" | ||||
| 
 | ||||
| 	"github.com/Sirupsen/logrus" | ||||
| 	"github.com/docker/notary/pkg/passphrase" | ||||
| 	"github.com/docker/notary/passphrase" | ||||
| 	"github.com/docker/notary/trustmanager" | ||||
| 	"github.com/docker/notary/tuf/data" | ||||
| 	"github.com/miekg/pkcs11" | ||||
|  | @ -24,25 +24,41 @@ import ( | |||
| const ( | ||||
| 	USER_PIN    = "123456" | ||||
| 	SO_USER_PIN = "010203040506070801020304050607080102030405060708" | ||||
| 	numSlots    = 4 // number of slots in the yubikey
 | ||||
| ) | ||||
| 
 | ||||
| // Hardcoded yubikey PKCS11 ID
 | ||||
| var YUBIKEY_ROOT_KEY_ID = []byte{2} | ||||
| 
 | ||||
| type ErrBackupFailed struct { | ||||
| 	err string | ||||
| } | ||||
| 
 | ||||
| func (err ErrBackupFailed) Error() string { | ||||
| 	return fmt.Sprintf("Failed to backup private key to: %s", err.err) | ||||
| } | ||||
| 
 | ||||
| type yubiSlot struct { | ||||
| 	role   string | ||||
| 	slotID []byte | ||||
| } | ||||
| 
 | ||||
| // YubiPrivateKey represents a private key inside of a yubikey
 | ||||
| type YubiPrivateKey struct { | ||||
| 	data.ECDSAPublicKey | ||||
| 	passRetriever passphrase.Retriever | ||||
| 	slot          []byte | ||||
| } | ||||
| 
 | ||||
| type YubikeySigner struct { | ||||
| 	YubiPrivateKey | ||||
| } | ||||
| 
 | ||||
| func NewYubiPrivateKey(pubKey data.ECDSAPublicKey, passRetriever passphrase.Retriever) *YubiPrivateKey { | ||||
| func NewYubiPrivateKey(slot []byte, pubKey data.ECDSAPublicKey, passRetriever passphrase.Retriever) *YubiPrivateKey { | ||||
| 	return &YubiPrivateKey{ | ||||
| 		ECDSAPublicKey: pubKey, | ||||
| 		passRetriever:  passRetriever, | ||||
| 		slot:           slot, | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
|  | @ -79,7 +95,7 @@ func (y *YubiPrivateKey) Sign(rand io.Reader, msg []byte, opts crypto.SignerOpts | |||
| 	} | ||||
| 	defer cleanup(ctx, session) | ||||
| 
 | ||||
| 	sig, err := sign(ctx, session, YUBIKEY_ROOT_KEY_ID, y.passRetriever, msg) | ||||
| 	sig, err := sign(ctx, session, y.slot, y.passRetriever, msg) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("failed to sign using Yubikey: %v", err) | ||||
| 	} | ||||
|  | @ -88,7 +104,15 @@ func (y *YubiPrivateKey) Sign(rand io.Reader, msg []byte, opts crypto.SignerOpts | |||
| } | ||||
| 
 | ||||
| // addECDSAKey adds a key to the yubikey
 | ||||
| func addECDSAKey(ctx *pkcs11.Ctx, session pkcs11.SessionHandle, privKey data.PrivateKey, pkcs11KeyID []byte, passRetriever passphrase.Retriever) error { | ||||
| func addECDSAKey( | ||||
| 	ctx *pkcs11.Ctx, | ||||
| 	session pkcs11.SessionHandle, | ||||
| 	privKey data.PrivateKey, | ||||
| 	pkcs11KeyID []byte, | ||||
| 	passRetriever passphrase.Retriever, | ||||
| 	role string, | ||||
| 	backupStore trustmanager.KeyStore, | ||||
| ) error { | ||||
| 	logrus.Debugf("Got into add key with key: %s\n", privKey.ID()) | ||||
| 
 | ||||
| 	// TODO(diogo): Figure out CKU_SO with yubikey
 | ||||
|  | @ -107,7 +131,7 @@ func addECDSAKey(ctx *pkcs11.Ctx, session pkcs11.SessionHandle, privKey data.Pri | |||
| 	ecdsaPrivKeyD := ecdsaPrivKey.D.Bytes() | ||||
| 	logrus.Debugf("Getting D bytes: %v\n", ecdsaPrivKeyD) | ||||
| 
 | ||||
| 	template, err := trustmanager.NewCertificate(data.CanonicalRootRole) | ||||
| 	template, err := trustmanager.NewCertificate(role) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("failed to create the certificate template: %v", err) | ||||
| 	} | ||||
|  | @ -145,6 +169,11 @@ func addECDSAKey(ctx *pkcs11.Ctx, session pkcs11.SessionHandle, privKey data.Pri | |||
| 		return fmt.Errorf("error importing: %v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	err = backupStore.AddKey(privKey.ID(), role, privKey) | ||||
| 	if err != nil { | ||||
| 		return ErrBackupFailed{err: err.Error()} | ||||
| 	} | ||||
| 
 | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
|  | @ -256,32 +285,239 @@ func sign(ctx *pkcs11.Ctx, session pkcs11.SessionHandle, pkcs11KeyID []byte, pas | |||
| 	return sig[:], nil | ||||
| } | ||||
| 
 | ||||
| type YubiKeyStore struct { | ||||
| 	passRetriever passphrase.Retriever | ||||
| func removeKey(ctx *pkcs11.Ctx, session pkcs11.SessionHandle, pkcs11KeyID []byte, passRetriever passphrase.Retriever, keyID string) error { | ||||
| 	err := login(ctx, session, passRetriever, pkcs11.CKU_SO, SO_USER_PIN) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	defer ctx.Logout(session) | ||||
| 
 | ||||
| 	template := []*pkcs11.Attribute{ | ||||
| 		pkcs11.NewAttribute(pkcs11.CKA_TOKEN, true), | ||||
| 		pkcs11.NewAttribute(pkcs11.CKA_ID, pkcs11KeyID), | ||||
| 		//pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_PRIVATE_KEY),
 | ||||
| 		pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_CERTIFICATE), | ||||
| 	} | ||||
| 
 | ||||
| func NewYubiKeyStore(passphraseRetriever passphrase.Retriever) *YubiKeyStore { | ||||
| 	return &YubiKeyStore{passRetriever: passphraseRetriever} | ||||
| 	if err := ctx.FindObjectsInit(session, template); err != nil { | ||||
| 		logrus.Printf("Failed to init: %s\n", err.Error()) | ||||
| 		return err | ||||
| 	} | ||||
| 	obj, b, err := ctx.FindObjects(session, 1) | ||||
| 	if err != nil { | ||||
| 		logrus.Printf("Failed to find: %s %v\n", err.Error(), b) | ||||
| 		return err | ||||
| 	} | ||||
| 	if err := ctx.FindObjectsFinal(session); err != nil { | ||||
| 		logrus.Printf("Failed to finalize: %s\n", err.Error()) | ||||
| 		return err | ||||
| 	} | ||||
| 	if len(obj) != 1 { | ||||
| 		logrus.Printf("should have found one object") | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	// Delete the certificate
 | ||||
| 	err = ctx.DestroyObject(session, obj[0]) | ||||
| 	if err != nil { | ||||
| 		logrus.Printf("Failed to delete cert") | ||||
| 		return err | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func listKeys(ctx *pkcs11.Ctx, session pkcs11.SessionHandle) (keys map[string]yubiSlot, err error) { | ||||
| 	keys = make(map[string]yubiSlot) | ||||
| 	findTemplate := []*pkcs11.Attribute{ | ||||
| 		pkcs11.NewAttribute(pkcs11.CKA_TOKEN, true), | ||||
| 		//pkcs11.NewAttribute(pkcs11.CKA_ID, pkcs11KeyID),
 | ||||
| 		pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_CERTIFICATE), | ||||
| 	} | ||||
| 
 | ||||
| 	attrTemplate := []*pkcs11.Attribute{ | ||||
| 		pkcs11.NewAttribute(pkcs11.CKA_ID, []byte{0}), | ||||
| 		pkcs11.NewAttribute(pkcs11.CKA_VALUE, []byte{0}), | ||||
| 	} | ||||
| 
 | ||||
| 	if err = ctx.FindObjectsInit(session, findTemplate); err != nil { | ||||
| 		logrus.Debugf("Failed to init: %s\n", err.Error()) | ||||
| 		return | ||||
| 	} | ||||
| 	objs, b, err := ctx.FindObjects(session, numSlots) | ||||
| 	for err == nil { | ||||
| 		var o []pkcs11.ObjectHandle | ||||
| 		o, b, err = ctx.FindObjects(session, numSlots) | ||||
| 		if err != nil { | ||||
| 			continue | ||||
| 		} | ||||
| 		if len(o) == 0 { | ||||
| 			break | ||||
| 		} | ||||
| 		objs = append(objs, o...) | ||||
| 	} | ||||
| 	if err != nil { | ||||
| 		logrus.Debugf("Failed to find: %s %v\n", err.Error(), b) | ||||
| 		if len(objs) == 0 { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 	} | ||||
| 	if err = ctx.FindObjectsFinal(session); err != nil { | ||||
| 		logrus.Debugf("Failed to finalize: %s\n", err.Error()) | ||||
| 		return | ||||
| 	} | ||||
| 	if len(objs) == 0 { | ||||
| 		return nil, errors.New("No keys found in yubikey.") | ||||
| 	} | ||||
| 	logrus.Debugf("Found %d objects matching list filters", len(objs)) | ||||
| 	for _, obj := range objs { | ||||
| 		var ( | ||||
| 			cert *x509.Certificate | ||||
| 			slot []byte | ||||
| 		) | ||||
| 		// Retrieve the public-key material to be able to create a new HSMRSAKey
 | ||||
| 		attr, err := ctx.GetAttributeValue(session, obj, attrTemplate) | ||||
| 		if err != nil { | ||||
| 			logrus.Debugf("Failed to get Attribute for: %v\n", obj) | ||||
| 			continue | ||||
| 		} | ||||
| 
 | ||||
| 		// Iterate through all the attributes of this key and saves CKA_PUBLIC_EXPONENT and CKA_MODULUS. Removes ordering specific issues.
 | ||||
| 		for _, a := range attr { | ||||
| 			if a.Type == pkcs11.CKA_ID { | ||||
| 				slot = a.Value | ||||
| 			} | ||||
| 			if a.Type == pkcs11.CKA_VALUE { | ||||
| 				cert, err = x509.ParseCertificate(a.Value) | ||||
| 				if err != nil { | ||||
| 					continue | ||||
| 				} | ||||
| 				if !data.ValidRole(cert.Subject.CommonName) { | ||||
| 					continue | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 		var ecdsaPubKey *ecdsa.PublicKey | ||||
| 		switch cert.PublicKeyAlgorithm { | ||||
| 		case x509.ECDSA: | ||||
| 			ecdsaPubKey = cert.PublicKey.(*ecdsa.PublicKey) | ||||
| 		default: | ||||
| 			logrus.Infof("Unsupported x509 PublicKeyAlgorithm: %d", cert.PublicKeyAlgorithm) | ||||
| 			continue | ||||
| 		} | ||||
| 
 | ||||
| 		pubBytes, err := x509.MarshalPKIXPublicKey(ecdsaPubKey) | ||||
| 		if err != nil { | ||||
| 			logrus.Debugf("Failed to Marshal public key") | ||||
| 			continue | ||||
| 		} | ||||
| 
 | ||||
| 		keys[data.NewECDSAPublicKey(pubBytes).ID()] = yubiSlot{ | ||||
| 			role:   cert.Subject.CommonName, | ||||
| 			slotID: slot, | ||||
| 		} | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| func getNextEmptySlot(ctx *pkcs11.Ctx, session pkcs11.SessionHandle) ([]byte, error) { | ||||
| 	findTemplate := []*pkcs11.Attribute{ | ||||
| 		pkcs11.NewAttribute(pkcs11.CKA_TOKEN, true), | ||||
| 	} | ||||
| 	attrTemplate := []*pkcs11.Attribute{ | ||||
| 		pkcs11.NewAttribute(pkcs11.CKA_ID, []byte{0}), | ||||
| 	} | ||||
| 
 | ||||
| 	if err := ctx.FindObjectsInit(session, findTemplate); err != nil { | ||||
| 		logrus.Debugf("Failed to init: %s\n", err.Error()) | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	objs, b, err := ctx.FindObjects(session, numSlots) | ||||
| 	for err == nil { | ||||
| 		var o []pkcs11.ObjectHandle | ||||
| 		o, b, err = ctx.FindObjects(session, numSlots) | ||||
| 		if err != nil { | ||||
| 			continue | ||||
| 		} | ||||
| 		if len(o) == 0 { | ||||
| 			break | ||||
| 		} | ||||
| 		objs = append(objs, o...) | ||||
| 	} | ||||
| 	taken := make(map[int]bool) | ||||
| 	if err != nil { | ||||
| 		logrus.Debugf("Failed to find: %s %v\n", err.Error(), b) | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	for _, obj := range objs { | ||||
| 		// Retrieve the slot ID
 | ||||
| 		attr, err := ctx.GetAttributeValue(session, obj, attrTemplate) | ||||
| 		if err != nil { | ||||
| 			logrus.Debugf("Failed to get Attribute for: %v\n", obj) | ||||
| 			continue | ||||
| 		} | ||||
| 
 | ||||
| 		// Iterate through attributes. If an ID attr was found, mark it as taken
 | ||||
| 		for _, a := range attr { | ||||
| 			if a.Type == pkcs11.CKA_ID { | ||||
| 				if len(a.Value) < 1 { | ||||
| 					continue | ||||
| 				} | ||||
| 				// a byte will always be capable of representing all slot IDs
 | ||||
| 				// for the Yubikeys
 | ||||
| 				slotNum := int(a.Value[0]) | ||||
| 				if slotNum >= len(taken) { | ||||
| 					// defensive
 | ||||
| 					continue | ||||
| 				} | ||||
| 				taken[slotNum] = true | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	for i := 0; i < numSlots; i++ { | ||||
| 		if !taken[i] { | ||||
| 			return []byte{byte(i)}, nil | ||||
| 		} | ||||
| 	} | ||||
| 	return nil, errors.New("Yubikey has no available slots.") | ||||
| } | ||||
| 
 | ||||
| type YubiKeyStore struct { | ||||
| 	passRetriever passphrase.Retriever | ||||
| 	keys          map[string]yubiSlot | ||||
| 	backupStore   trustmanager.KeyStore | ||||
| } | ||||
| 
 | ||||
| func NewYubiKeyStore(backupStore trustmanager.KeyStore, passphraseRetriever passphrase.Retriever) (*YubiKeyStore, error) { | ||||
| 	s := &YubiKeyStore{ | ||||
| 		passRetriever: passphraseRetriever, | ||||
| 		keys:          make(map[string]yubiSlot), | ||||
| 		backupStore:   backupStore, | ||||
| 	} | ||||
| 	s.ListKeys() // populate keys field
 | ||||
| 	return s, nil | ||||
| } | ||||
| 
 | ||||
| func (s *YubiKeyStore) ListKeys() map[string]string { | ||||
| 	if len(s.keys) > 0 { | ||||
| 		return buildKeyMap(s.keys) | ||||
| 	} | ||||
| 	ctx, session, err := SetupHSMEnv(pkcs11Lib) | ||||
| 	if err != nil { | ||||
| 		return nil | ||||
| 	} | ||||
| 	defer cleanup(ctx, session) | ||||
| 	ecdsaPubKey, alias, err := getECDSAKey(ctx, session, YUBIKEY_ROOT_KEY_ID) | ||||
| 	keys, err := listKeys(ctx, session) | ||||
| 	if err != nil { | ||||
| 		logrus.Debugf("error while listing keys from Yubikey: %s", err) | ||||
| 		return nil | ||||
| 	} | ||||
| 	return map[string]string{ecdsaPubKey.ID(): alias} | ||||
| 	s.keys = keys | ||||
| 	return buildKeyMap(keys) | ||||
| } | ||||
| 
 | ||||
| func (s *YubiKeyStore) AddKey(keyID, alias string, privKey data.PrivateKey) error { | ||||
| func (s *YubiKeyStore) AddKey(keyID, role string, privKey data.PrivateKey) error { | ||||
| 	// We only allow adding root keys for now
 | ||||
| 	if alias != data.CanonicalRootRole { | ||||
| 		return fmt.Errorf("yubikey only supports storing root keys, got %s for key: %s\n", alias, keyID) | ||||
| 	if role != data.CanonicalRootRole { | ||||
| 		return fmt.Errorf("yubikey only supports storing root keys, got %s for key: %s\n", role, keyID) | ||||
| 	} | ||||
| 
 | ||||
| 	ctx, session, err := SetupHSMEnv(pkcs11Lib) | ||||
|  | @ -290,7 +526,27 @@ func (s *YubiKeyStore) AddKey(keyID, alias string, privKey data.PrivateKey) erro | |||
| 	} | ||||
| 	defer cleanup(ctx, session) | ||||
| 
 | ||||
| 	return addECDSAKey(ctx, session, privKey, YUBIKEY_ROOT_KEY_ID, s.passRetriever) | ||||
| 	if k, ok := s.keys[keyID]; ok { | ||||
| 		if k.role == role { | ||||
| 			// already have the key and it's associated with the correct role
 | ||||
| 			return nil | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	slot, err := getNextEmptySlot(ctx, session) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	logrus.Debugf("Using yubikey slot %v", slot) | ||||
| 	err = addECDSAKey(ctx, session, privKey, slot, s.passRetriever, role, s.backupStore) | ||||
| 	if err == nil { | ||||
| 		s.keys[privKey.ID()] = yubiSlot{ | ||||
| 			role:   role, | ||||
| 			slotID: slot, | ||||
| 		} | ||||
| 		return nil | ||||
| 	} | ||||
| 	return err | ||||
| } | ||||
| 
 | ||||
| func (s *YubiKeyStore) GetKey(keyID string) (data.PrivateKey, string, error) { | ||||
|  | @ -300,7 +556,12 @@ func (s *YubiKeyStore) GetKey(keyID string) (data.PrivateKey, string, error) { | |||
| 	} | ||||
| 	defer cleanup(ctx, session) | ||||
| 
 | ||||
| 	pubKey, alias, err := getECDSAKey(ctx, session, YUBIKEY_ROOT_KEY_ID) | ||||
| 	key, ok := s.keys[keyID] | ||||
| 	if !ok { | ||||
| 		return nil, "", errors.New("no matching keys found inside of yubikey") | ||||
| 	} | ||||
| 
 | ||||
| 	pubKey, alias, err := getECDSAKey(ctx, session, key.slotID) | ||||
| 	if err != nil { | ||||
| 		return nil, "", err | ||||
| 	} | ||||
|  | @ -308,7 +569,7 @@ func (s *YubiKeyStore) GetKey(keyID string) (data.PrivateKey, string, error) { | |||
| 	if pubKey.ID() != keyID { | ||||
| 		return nil, "", fmt.Errorf("expected root key: %s, but found: %s\n", keyID, pubKey.ID()) | ||||
| 	} | ||||
| 	privKey := NewYubiPrivateKey(*pubKey, s.passRetriever) | ||||
| 	privKey := NewYubiPrivateKey(key.slotID, *pubKey, s.passRetriever) | ||||
| 	if privKey == nil { | ||||
| 		return nil, "", errors.New("could not initialize new YubiPrivateKey") | ||||
| 	} | ||||
|  | @ -317,21 +578,30 @@ func (s *YubiKeyStore) GetKey(keyID string) (data.PrivateKey, string, error) { | |||
| } | ||||
| 
 | ||||
| func (s *YubiKeyStore) RemoveKey(keyID string) error { | ||||
| 	// TODO(diogo): actually implement this
 | ||||
| 	logrus.Debugf("Attempting to remove: %s key inside of YubiKeyStore", keyID) | ||||
| 	ctx, session, err := SetupHSMEnv(pkcs11Lib) | ||||
| 	if err != nil { | ||||
| 		return nil | ||||
| 	} | ||||
| 	defer cleanup(ctx, session) | ||||
| 	key, ok := s.keys[keyID] | ||||
| 	if !ok { | ||||
| 		return errors.New("Key not present in yubikey") | ||||
| 	} | ||||
| 	return removeKey(ctx, session, key.slotID, s.passRetriever, keyID) | ||||
| } | ||||
| 
 | ||||
| func (s *YubiKeyStore) ExportKey(keyID string) ([]byte, error) { | ||||
| 	// TODO(diogo): actually implement this
 | ||||
| 	logrus.Debugf("Attempting to export: %s key inside of YubiKeyStore", keyID) | ||||
| 	return nil, nil | ||||
| 	return nil, errors.New("Keys cannot be exported from a Yubikey.") | ||||
| } | ||||
| 
 | ||||
| func (s *YubiKeyStore) ImportKey(pemBytes []byte, keyID string) error { | ||||
| 	// TODO(diogo): actually implement this
 | ||||
| 	logrus.Debugf("Attempting to import: %s key inside of YubiKeyStore", keyID) | ||||
| 	return nil | ||||
| 	privKey, _, err := trustmanager.GetPasswdDecryptBytes(s.passRetriever, pemBytes, "imported", "root") | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return s.AddKey(privKey.ID(), "root", privKey) | ||||
| } | ||||
| 
 | ||||
| func cleanup(ctx *pkcs11.Ctx, session pkcs11.SessionHandle) { | ||||
|  | @ -407,3 +677,11 @@ func login(ctx *pkcs11.Ctx, session pkcs11.SessionHandle, passRetriever passphra | |||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func buildKeyMap(keys map[string]yubiSlot) map[string]string { | ||||
| 	res := make(map[string]string) | ||||
| 	for k, v := range keys { | ||||
| 		res[k] = v.role | ||||
| 	} | ||||
| 	return res | ||||
| } | ||||
|  |  | |||
|  | @ -7,7 +7,7 @@ import ( | |||
| 	"testing" | ||||
| 
 | ||||
| 	"github.com/docker/notary/cryptoservice" | ||||
| 	"github.com/docker/notary/pkg/passphrase" | ||||
| 	"github.com/docker/notary/passphrase" | ||||
| 	"github.com/docker/notary/signer" | ||||
| 	"github.com/docker/notary/signer/api" | ||||
| 	"github.com/docker/notary/trustmanager" | ||||
|  |  | |||
|  | @ -1,212 +0,0 @@ | |||
| // +build pkcs11
 | ||||
| 
 | ||||
| package api | ||||
| 
 | ||||
| import ( | ||||
| 	"crypto" | ||||
| 	"crypto/rand" | ||||
| 	"crypto/rsa" | ||||
| 	"crypto/sha256" | ||||
| 	"crypto/x509" | ||||
| 	"errors" | ||||
| 	"log" | ||||
| 	"math/big" | ||||
| 
 | ||||
| 	"github.com/docker/notary/signer/keys" | ||||
| 	"github.com/docker/notary/tuf/data" | ||||
| 	"github.com/miekg/pkcs11" | ||||
| ) | ||||
| 
 | ||||
| // RSAHardwareCryptoService is an implementation of SigningService
 | ||||
| type RSAHardwareCryptoService struct { | ||||
| 	keys    map[string]*keys.HSMRSAKey | ||||
| 	context *pkcs11.Ctx | ||||
| 	session pkcs11.SessionHandle | ||||
| } | ||||
| 
 | ||||
| // ListKeys not implemented yet
 | ||||
| func (s *RSAHardwareCryptoService) ListKeys(role string) []string { | ||||
| 	return []string{} | ||||
| } | ||||
| 
 | ||||
| // Create creates a key and returns its public components
 | ||||
| func (s *RSAHardwareCryptoService) Create(role, algo string) (data.PublicKey, error) { | ||||
| 	// For now generate random labels for keys
 | ||||
| 	// (diogo): add link between keyID and label in database so we can support multiple keys
 | ||||
| 	randomLabel := make([]byte, 32) | ||||
| 	_, err := rand.Read(randomLabel) | ||||
| 	if err != nil { | ||||
| 		return nil, errors.New("Could not generate a random key label.") | ||||
| 	} | ||||
| 
 | ||||
| 	// Set the public key template
 | ||||
| 	// CKA_TOKEN: Guarantees key persistence in hardware
 | ||||
| 	// CKA_LABEL: Identifies this specific key inside of the HSM
 | ||||
| 	publicKeyTemplate := []*pkcs11.Attribute{ | ||||
| 		pkcs11.NewAttribute(pkcs11.CKA_TOKEN, true), | ||||
| 		pkcs11.NewAttribute(pkcs11.CKA_PUBLIC_EXPONENT, []byte{3}), | ||||
| 		pkcs11.NewAttribute(pkcs11.CKA_MODULUS_BITS, 2048), | ||||
| 		pkcs11.NewAttribute(pkcs11.CKA_LABEL, string(randomLabel)), | ||||
| 	} | ||||
| 	privateKeyTemplate := []*pkcs11.Attribute{ | ||||
| 		pkcs11.NewAttribute(pkcs11.CKA_TOKEN, true), | ||||
| 		pkcs11.NewAttribute(pkcs11.CKA_PRIVATE, true), | ||||
| 		pkcs11.NewAttribute(pkcs11.CKA_SIGN, true), | ||||
| 		pkcs11.NewAttribute(pkcs11.CKA_LABEL, string(randomLabel)), | ||||
| 	} | ||||
| 
 | ||||
| 	// Generate a new RSA private/public keypair inside of the HSM
 | ||||
| 	pub, priv, err := s.context.GenerateKeyPair(s.session, | ||||
| 		[]*pkcs11.Mechanism{pkcs11.NewMechanism(pkcs11.CKM_RSA_PKCS_KEY_PAIR_GEN, nil)}, | ||||
| 		publicKeyTemplate, privateKeyTemplate) | ||||
| 	if err != nil { | ||||
| 		return nil, errors.New("Could not generate a new key inside of the HSM.") | ||||
| 	} | ||||
| 
 | ||||
| 	// (diogo): This template is used for the GetAttribute
 | ||||
| 	template := []*pkcs11.Attribute{ | ||||
| 		pkcs11.NewAttribute(pkcs11.CKA_PUBLIC_EXPONENT, nil), | ||||
| 		pkcs11.NewAttribute(pkcs11.CKA_MODULUS_BITS, nil), | ||||
| 		pkcs11.NewAttribute(pkcs11.CKA_MODULUS, nil), | ||||
| 	} | ||||
| 
 | ||||
| 	// Retrieve the public-key material to be able to create a new HSMRSAKey
 | ||||
| 	attr, err := s.context.GetAttributeValue(s.session, pub, template) | ||||
| 	if err != nil { | ||||
| 		return nil, errors.New("Failed to get Attribute value.") | ||||
| 	} | ||||
| 
 | ||||
| 	// We're going to store the elements of the RSA Public key, exponent and Modulus inside of exp and mod
 | ||||
| 	var exp int | ||||
| 	mod := big.NewInt(0) | ||||
| 
 | ||||
| 	// Iterate through all the attributes of this key and saves CKA_PUBLIC_EXPONENT and CKA_MODULUS. Removes ordering specific issues.
 | ||||
| 	for _, a := range attr { | ||||
| 		if a.Type == pkcs11.CKA_PUBLIC_EXPONENT { | ||||
| 			exp, _ = readInt(a.Value) | ||||
| 		} | ||||
| 
 | ||||
| 		if a.Type == pkcs11.CKA_MODULUS { | ||||
| 			mod.SetBytes(a.Value) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	rsaPublicKey := rsa.PublicKey{N: mod, E: exp} | ||||
| 	// Using x509 to Marshal the Public key into der encoding
 | ||||
| 	pubBytes, err := x509.MarshalPKIXPublicKey(&rsaPublicKey) | ||||
| 	if err != nil { | ||||
| 		return nil, errors.New("Failed to Marshal public key.") | ||||
| 	} | ||||
| 
 | ||||
| 	// (diogo): Ideally I would like to return base64 PEM encoded public keys to the client
 | ||||
| 	k := keys.NewHSMRSAKey(pubBytes, priv) | ||||
| 
 | ||||
| 	keyID := k.ID() | ||||
| 
 | ||||
| 	s.keys[keyID] = k | ||||
| 
 | ||||
| 	return k, nil | ||||
| } | ||||
| 
 | ||||
| // RemoveKey removes a key from the key database
 | ||||
| func (s *RSAHardwareCryptoService) RemoveKey(keyID string) error { | ||||
| 	if _, ok := s.keys[keyID]; !ok { | ||||
| 		return keys.ErrInvalidKeyID | ||||
| 	} | ||||
| 
 | ||||
| 	delete(s.keys, keyID) | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // GetKey returns the public components of a particular key
 | ||||
| func (s *RSAHardwareCryptoService) GetKey(keyID string) data.PublicKey { | ||||
| 	key, ok := s.keys[keyID] | ||||
| 	if !ok { | ||||
| 		return nil | ||||
| 	} | ||||
| 	return key | ||||
| } | ||||
| 
 | ||||
| // GetPrivateKey is not implemented
 | ||||
| func (s *RSAHardwareCryptoService) GetPrivateKey(keyID string) (data.PrivateKey, string, error) { | ||||
| 	return nil, "", errors.New("Not yet implemented") | ||||
| } | ||||
| 
 | ||||
| // Sign returns a signature for a given signature request
 | ||||
| func (s *RSAHardwareCryptoService) Sign(keyIDs []string, payload []byte) ([]data.Signature, error) { | ||||
| 	signatures := make([]data.Signature, 0, len(keyIDs)) | ||||
| 	for _, keyid := range keyIDs { | ||||
| 		privateKey, present := s.keys[keyid] | ||||
| 		if !present { | ||||
| 			// We skip keys that aren't found
 | ||||
| 			continue | ||||
| 		} | ||||
| 
 | ||||
| 		priv := privateKey.PKCS11ObjectHandle() | ||||
| 		var sig []byte | ||||
| 		var err error | ||||
| 		for i := 0; i < 3; i++ { | ||||
| 			s.context.SignInit(s.session, []*pkcs11.Mechanism{pkcs11.NewMechanism(pkcs11.CKM_SHA256_RSA_PKCS, nil)}, priv) | ||||
| 
 | ||||
| 			sig, err = s.context.Sign(s.session, payload) | ||||
| 			if err != nil { | ||||
| 				log.Printf("Error while signing: %s", err) | ||||
| 				continue | ||||
| 			} | ||||
| 
 | ||||
| 			digest := sha256.Sum256(payload) | ||||
| 			pub, err := x509.ParsePKIXPublicKey(privateKey.Public()) | ||||
| 			if err != nil { | ||||
| 				log.Printf("Failed to parse public key: %s\n", err) | ||||
| 				return nil, err | ||||
| 			} | ||||
| 
 | ||||
| 			rsaPub, ok := pub.(*rsa.PublicKey) | ||||
| 			if !ok { | ||||
| 				log.Printf("Value returned from ParsePKIXPublicKey was not an RSA public key") | ||||
| 				return nil, err | ||||
| 			} | ||||
| 
 | ||||
| 			err = rsa.VerifyPKCS1v15(rsaPub, crypto.SHA256, digest[:], sig) | ||||
| 			if err != nil { | ||||
| 				log.Printf("Failed verification. Retrying: %s", err) | ||||
| 				continue | ||||
| 			} | ||||
| 			break | ||||
| 		} | ||||
| 
 | ||||
| 		if sig == nil { | ||||
| 			return nil, errors.New("Failed to create signature") | ||||
| 		} | ||||
| 
 | ||||
| 		signatures = append(signatures, data.Signature{ | ||||
| 			KeyID:     keyid, | ||||
| 			Method:    data.RSAPKCS1v15Signature, | ||||
| 			Signature: sig[:], | ||||
| 		}) | ||||
| 	} | ||||
| 
 | ||||
| 	return signatures, nil | ||||
| } | ||||
| 
 | ||||
| // NewRSAHardwareCryptoService returns an instance of RSAHardwareCryptoService
 | ||||
| func NewRSAHardwareCryptoService(ctx *pkcs11.Ctx, session pkcs11.SessionHandle) *RSAHardwareCryptoService { | ||||
| 	return &RSAHardwareCryptoService{ | ||||
| 		keys:    make(map[string]*keys.HSMRSAKey), | ||||
| 		context: ctx, | ||||
| 		session: session, | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // readInt converts a []byte into an int. It is used to convert the RSA Public key exponent into an int to create a crypto.PublicKey
 | ||||
| func readInt(data []byte) (int, error) { | ||||
| 	var ret int | ||||
| 	if len(data) > 4 { | ||||
| 		return 0, errors.New("Cannot convert byte array due to size") | ||||
| 	} | ||||
| 
 | ||||
| 	for i, a := range data { | ||||
| 		ret |= (int(a) << uint(i*8)) | ||||
| 	} | ||||
| 	return ret, nil | ||||
| } | ||||
|  | @ -6,7 +6,7 @@ import ( | |||
| 	"fmt" | ||||
| 	"sync" | ||||
| 
 | ||||
| 	"github.com/docker/notary/pkg/passphrase" | ||||
| 	"github.com/docker/notary/passphrase" | ||||
| 	"github.com/docker/notary/trustmanager" | ||||
| 	"github.com/docker/notary/tuf/data" | ||||
| 	jose "github.com/dvsekhvalnov/jose2go" | ||||
|  |  | |||
|  | @ -4,6 +4,7 @@ import ( | |||
| 	"crypto/tls" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"net" | ||||
| 	"time" | ||||
| 
 | ||||
|  | @ -105,6 +106,11 @@ func (trust *NotarySigner) ListKeys(role string) []string { | |||
| 	return []string{} | ||||
| } | ||||
| 
 | ||||
| // ListAllKeys not supported for NotarySigner
 | ||||
| func (trust *NotarySigner) ListAllKeys() map[string]string { | ||||
| 	return map[string]string{} | ||||
| } | ||||
| 
 | ||||
| // CheckHealth checks the health of one of the clients, since both clients run
 | ||||
| // from the same GRPC server.
 | ||||
| func (trust *NotarySigner) CheckHealth(timeout time.Duration) error { | ||||
|  | @ -126,3 +132,9 @@ func (trust *NotarySigner) CheckHealth(timeout time.Duration) error { | |||
| 	} | ||||
| 	return err | ||||
| } | ||||
| 
 | ||||
| // ImportRootKey satisfies the CryptoService interface. It should not be implemented
 | ||||
| // for a NotarySigner.
 | ||||
| func (trust *NotarySigner) ImportRootKey(r io.Reader) error { | ||||
| 	return errors.New("Importing a root key to NotarySigner is not supported") | ||||
| } | ||||
|  |  | |||
|  | @ -3,6 +3,7 @@ package trustmanager | |||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"github.com/docker/notary" | ||||
| 	"io/ioutil" | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
|  | @ -11,8 +12,8 @@ import ( | |||
| ) | ||||
| 
 | ||||
| const ( | ||||
| 	visible os.FileMode = 0755 | ||||
| 	private os.FileMode = 0700 | ||||
| 	visible = notary.PubCertPerms | ||||
| 	private = notary.PrivKeyPerms | ||||
| ) | ||||
| 
 | ||||
| var ( | ||||
|  |  | |||
|  | @ -5,7 +5,7 @@ import ( | |||
| 	"strings" | ||||
| 	"sync" | ||||
| 
 | ||||
| 	"github.com/docker/notary/pkg/passphrase" | ||||
| 	"github.com/docker/notary/passphrase" | ||||
| 	"github.com/docker/notary/tuf/data" | ||||
| ) | ||||
| 
 | ||||
|  | @ -197,7 +197,7 @@ func getKey(s LimitedFileStore, passphraseRetriever passphrase.Retriever, cached | |||
| 	// See if the key is encrypted. If its encrypted we'll fail to parse the private key
 | ||||
| 	privKey, err := ParsePEMPrivateKey(keyBytes, "") | ||||
| 	if err != nil { | ||||
| 		privKey, _, retErr = getPasswdDecryptBytes(s, passphraseRetriever, keyBytes, name, string(keyAlias)) | ||||
| 		privKey, _, retErr = GetPasswdDecryptBytes(passphraseRetriever, keyBytes, name, string(keyAlias)) | ||||
| 	} | ||||
| 	if retErr != nil { | ||||
| 		return nil, "", retErr | ||||
|  | @ -268,9 +268,9 @@ func getRawKey(s LimitedFileStore, name string) ([]byte, string, error) { | |||
| 	return keyBytes, keyAlias, nil | ||||
| } | ||||
| 
 | ||||
| // Get the password to decript the given pem bytes.  Return the password,
 | ||||
| // because it is useful for importing
 | ||||
| func getPasswdDecryptBytes(s LimitedFileStore, passphraseRetriever passphrase.Retriever, pemBytes []byte, name, alias string) (data.PrivateKey, string, error) { | ||||
| // GetPasswdDecryptBytes gets the password to decript the given pem bytes.
 | ||||
| // Returns the password and private key
 | ||||
| func GetPasswdDecryptBytes(passphraseRetriever passphrase.Retriever, pemBytes []byte, name, alias string) (data.PrivateKey, string, error) { | ||||
| 	var ( | ||||
| 		passwd  string | ||||
| 		retErr  error | ||||
|  | @ -329,12 +329,17 @@ func encryptAndAddKey(s LimitedFileStore, passwd string, cachedKeys map[string]* | |||
| 
 | ||||
| func importKey(s LimitedFileStore, passphraseRetriever passphrase.Retriever, cachedKeys map[string]*cachedKey, alias string, pemBytes []byte) error { | ||||
| 
 | ||||
| 	privKey, passphrase, err := getPasswdDecryptBytes(s, passphraseRetriever, pemBytes, "imported", alias) | ||||
| 	if alias != data.CanonicalRootRole { | ||||
| 		return s.Add(alias, pemBytes) | ||||
| 	} | ||||
| 
 | ||||
| 	privKey, passphrase, err := GetPasswdDecryptBytes(passphraseRetriever, pemBytes, "imported", alias) | ||||
| 
 | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	return encryptAndAddKey( | ||||
| 		s, passphrase, cachedKeys, privKey.ID(), alias, privKey) | ||||
| 	var name string | ||||
| 	name = privKey.ID() | ||||
| 	return encryptAndAddKey(s, passphrase, cachedKeys, name, alias, privKey) | ||||
| } | ||||
|  |  | |||
|  | @ -8,7 +8,7 @@ import ( | |||
| 	"path/filepath" | ||||
| 	"testing" | ||||
| 
 | ||||
| 	"github.com/docker/notary/pkg/passphrase" | ||||
| 	"github.com/docker/notary/passphrase" | ||||
| 	"github.com/docker/notary/tuf/data" | ||||
| 	"github.com/stretchr/testify/assert" | ||||
| ) | ||||
|  |  | |||
|  | @ -3,27 +3,37 @@ package signed | |||
| import ( | ||||
| 	"crypto/rand" | ||||
| 	"errors" | ||||
| 	"io" | ||||
| 	"io/ioutil" | ||||
| 
 | ||||
| 	"github.com/agl/ed25519" | ||||
| 	"github.com/docker/notary/tuf/data" | ||||
| ) | ||||
| 
 | ||||
| type edCryptoKey struct { | ||||
| 	role    string | ||||
| 	privKey data.PrivateKey | ||||
| } | ||||
| 
 | ||||
| // Ed25519 implements a simple in memory cryptosystem for ED25519 keys
 | ||||
| type Ed25519 struct { | ||||
| 	keys map[string]data.PrivateKey | ||||
| 	keys map[string]edCryptoKey | ||||
| } | ||||
| 
 | ||||
| // NewEd25519 initializes a new empty Ed25519 CryptoService that operates
 | ||||
| // entirely in memory
 | ||||
| func NewEd25519() *Ed25519 { | ||||
| 	return &Ed25519{ | ||||
| 		make(map[string]data.PrivateKey), | ||||
| 		make(map[string]edCryptoKey), | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // addKey allows you to add a private key
 | ||||
| func (e *Ed25519) addKey(k data.PrivateKey) { | ||||
| 	e.keys[k.ID()] = k | ||||
| func (e *Ed25519) addKey(role string, k data.PrivateKey) { | ||||
| 	e.keys[k.ID()] = edCryptoKey{ | ||||
| 		role:    role, | ||||
| 		privKey: k, | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // RemoveKey deletes a key from the signer
 | ||||
|  | @ -41,12 +51,21 @@ func (e *Ed25519) ListKeys(role string) []string { | |||
| 	return keyIDs | ||||
| } | ||||
| 
 | ||||
| // ListAllKeys returns the map of keys IDs to role
 | ||||
| func (e *Ed25519) ListAllKeys() map[string]string { | ||||
| 	keys := make(map[string]string) | ||||
| 	for id, edKey := range e.keys { | ||||
| 		keys[id] = edKey.role | ||||
| 	} | ||||
| 	return keys | ||||
| } | ||||
| 
 | ||||
| // Sign generates an Ed25519 signature over the data
 | ||||
| func (e *Ed25519) Sign(keyIDs []string, toSign []byte) ([]data.Signature, error) { | ||||
| 	signatures := make([]data.Signature, 0, len(keyIDs)) | ||||
| 	for _, keyID := range keyIDs { | ||||
| 		priv := [ed25519.PrivateKeySize]byte{} | ||||
| 		copy(priv[:], e.keys[keyID].Private()) | ||||
| 		copy(priv[:], e.keys[keyID].privKey.Private()) | ||||
| 		sig := ed25519.Sign(&priv, toSign) | ||||
| 		signatures = append(signatures, data.Signature{ | ||||
| 			KeyID:     keyID, | ||||
|  | @ -74,7 +93,7 @@ func (e *Ed25519) Create(role, algorithm string) (data.PublicKey, error) { | |||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	e.addKey(private) | ||||
| 	e.addKey(role, private) | ||||
| 	return public, nil | ||||
| } | ||||
| 
 | ||||
|  | @ -83,8 +102,8 @@ func (e *Ed25519) Create(role, algorithm string) (data.PublicKey, error) { | |||
| func (e *Ed25519) PublicKeys(keyIDs ...string) (map[string]data.PublicKey, error) { | ||||
| 	k := make(map[string]data.PublicKey) | ||||
| 	for _, keyID := range keyIDs { | ||||
| 		if key, ok := e.keys[keyID]; ok { | ||||
| 			k[keyID] = data.PublicKeyFromPrivate(key) | ||||
| 		if edKey, ok := e.keys[keyID]; ok { | ||||
| 			k[keyID] = data.PublicKeyFromPrivate(edKey.privKey) | ||||
| 		} | ||||
| 	} | ||||
| 	return k, nil | ||||
|  | @ -92,10 +111,29 @@ func (e *Ed25519) PublicKeys(keyIDs ...string) (map[string]data.PublicKey, error | |||
| 
 | ||||
| // GetKey returns a single public key based on the ID
 | ||||
| func (e *Ed25519) GetKey(keyID string) data.PublicKey { | ||||
| 	return data.PublicKeyFromPrivate(e.keys[keyID]) | ||||
| 	return data.PublicKeyFromPrivate(e.keys[keyID].privKey) | ||||
| } | ||||
| 
 | ||||
| // GetPrivateKey returns a single private key based on the ID
 | ||||
| func (e *Ed25519) GetPrivateKey(keyID string) (data.PrivateKey, string, error) { | ||||
| 	return e.keys[keyID], "", nil | ||||
| 	return e.keys[keyID].privKey, "", nil | ||||
| } | ||||
| 
 | ||||
| // ImportRootKey adds an Ed25519 key to the store as a root key
 | ||||
| func (e *Ed25519) ImportRootKey(r io.Reader) error { | ||||
| 	raw, err := ioutil.ReadAll(r) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	dataSize := ed25519.PublicKeySize + ed25519.PrivateKeySize | ||||
| 	if len(raw) < dataSize || len(raw) > dataSize { | ||||
| 		return errors.New("Wrong length of data for Ed25519 Key Import") | ||||
| 	} | ||||
| 	public := data.NewED25519PublicKey(raw[:ed25519.PublicKeySize]) | ||||
| 	private, err := data.NewED25519PrivateKey(*public, raw[ed25519.PublicKeySize:]) | ||||
| 	e.keys[private.ID()] = edCryptoKey{ | ||||
| 		role:    "root", | ||||
| 		privKey: private, | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  |  | |||
|  | @ -2,6 +2,7 @@ package signed | |||
| 
 | ||||
| import ( | ||||
| 	"github.com/docker/notary/tuf/data" | ||||
| 	"io" | ||||
| ) | ||||
| 
 | ||||
| // SigningService defines the necessary functions to determine
 | ||||
|  | @ -32,8 +33,15 @@ type KeyService interface { | |||
| 	// RemoveKey deletes the specified key
 | ||||
| 	RemoveKey(keyID string) error | ||||
| 
 | ||||
| 	// ListKeys returns a map of IDs to role
 | ||||
| 	// ListKeys returns a list of key IDs for the role
 | ||||
| 	ListKeys(role string) []string | ||||
| 
 | ||||
| 	// ListAllKeys returns a map of all available signing key IDs to role
 | ||||
| 	ListAllKeys() map[string]string | ||||
| 
 | ||||
| 	// ImportRootKey imports a root key to the highest priority keystore associated with
 | ||||
| 	// the cryptoservice
 | ||||
| 	ImportRootKey(source io.Reader) error | ||||
| } | ||||
| 
 | ||||
| // CryptoService defines a unified Signing and Key Service as this
 | ||||
|  |  | |||
|  | @ -6,6 +6,7 @@ import ( | |||
| 	"crypto/x509" | ||||
| 	"encoding/pem" | ||||
| 	"errors" | ||||
| 	"io" | ||||
| 	"testing" | ||||
| 
 | ||||
| 	"github.com/docker/notary/trustmanager" | ||||
|  | @ -38,6 +39,15 @@ func (mts *FailingCryptoService) ListKeys(role string) []string { | |||
| 	return []string{mts.testKey.ID()} | ||||
| } | ||||
| 
 | ||||
| func (mts *FailingCryptoService) ListAllKeys() map[string]string { | ||||
| 	return map[string]string{ | ||||
| 		mts.testKey.ID(): "root", | ||||
| 		mts.testKey.ID(): "targets", | ||||
| 		mts.testKey.ID(): "snapshot", | ||||
| 		mts.testKey.ID(): "timestamp", | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (mts *FailingCryptoService) GetKey(keyID string) data.PublicKey { | ||||
| 	if keyID == "testID" { | ||||
| 		return mts.testKey | ||||
|  | @ -53,6 +63,10 @@ func (mts *FailingCryptoService) RemoveKey(keyID string) error { | |||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func (mts *FailingCryptoService) ImportRootKey(r io.Reader) error { | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| type MockCryptoService struct { | ||||
| 	testKey data.PublicKey | ||||
| } | ||||
|  | @ -80,6 +94,15 @@ func (mts *MockCryptoService) ListKeys(role string) []string { | |||
| 	return []string{mts.testKey.ID()} | ||||
| } | ||||
| 
 | ||||
| func (mts *MockCryptoService) ListAllKeys() map[string]string { | ||||
| 	return map[string]string{ | ||||
| 		mts.testKey.ID(): "root", | ||||
| 		mts.testKey.ID(): "targets", | ||||
| 		mts.testKey.ID(): "snapshot", | ||||
| 		mts.testKey.ID(): "timestamp", | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (mts *MockCryptoService) GetPrivateKey(keyID string) (data.PrivateKey, string, error) { | ||||
| 	return nil, "", errors.New("Not implemented") | ||||
| } | ||||
|  | @ -88,6 +111,10 @@ func (mts *MockCryptoService) RemoveKey(keyID string) error { | |||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func (mts *MockCryptoService) ImportRootKey(r io.Reader) error { | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| var _ CryptoService = &MockCryptoService{} | ||||
| 
 | ||||
| type StrictMockCryptoService struct { | ||||
|  | @ -115,6 +142,19 @@ func (mts *StrictMockCryptoService) ListKeys(role string) []string { | |||
| 	return []string{mts.testKey.ID()} | ||||
| } | ||||
| 
 | ||||
| func (mts *StrictMockCryptoService) ListAllKeys() map[string]string { | ||||
| 	return map[string]string{ | ||||
| 		mts.testKey.ID(): "root", | ||||
| 		mts.testKey.ID(): "targets", | ||||
| 		mts.testKey.ID(): "snapshot", | ||||
| 		mts.testKey.ID(): "timestamp", | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (mts *StrictMockCryptoService) ImportRootKey(r io.Reader) error { | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // Test signing and ensure the expected signature is added
 | ||||
| func TestBasicSign(t *testing.T) { | ||||
| 	testKey, _ := pem.Decode([]byte(testKeyPEM1)) | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue