diff --git a/.gitignore b/.gitignore index b230993319..f5a0193ff9 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,5 @@ bin cross .cover *.swp +.idea +*.iml diff --git a/circle.yml b/circle.yml index 90e31701b1..dc9caefa61 100644 --- a/circle.yml +++ b/circle.yml @@ -49,7 +49,7 @@ test: pwd: $BASE_STABLE # VET - - gvm use stable && go vet ./...: + - gvm use stable && test -z "$(go tool vet -printf=false . 2>&1 | grep -v Godeps/_workspace/src/ | tee /dev/stderr)": pwd: $BASE_STABLE # LINT diff --git a/client/client.go b/client/client.go index 045d3b9474..1f61dd5611 100644 --- a/client/client.go +++ b/client/client.go @@ -29,8 +29,6 @@ const maxSize = 5 << 20 // notary repository type ErrRepoNotInitialized struct{} -type passwordRetriever func() (string, error) - // ErrRepoNotInitialized is returned when trying to can publish on an uninitialized // notary repository func (err *ErrRepoNotInitialized) Error() string { @@ -85,13 +83,15 @@ func NewTarget(targetName string, targetPath string) (*Target, error) { // NewNotaryRepository is a helper method that returns a new notary repository. // It takes the base directory under where all the trust files will be stored // (usually ~/.docker/trust/). -func NewNotaryRepository(baseDir, gun, baseURL string, rt http.RoundTripper) (*NotaryRepository, error) { - keyStoreManager, err := keystoremanager.NewKeyStoreManager(baseDir) +func NewNotaryRepository(baseDir, gun, baseURL string, rt http.RoundTripper, + passphraseRetriever trustmanager.PassphraseRetriever) (*NotaryRepository, error) { + + keyStoreManager, err := keystoremanager.NewKeyStoreManager(baseDir, passphraseRetriever) if err != nil { return nil, err } - cryptoService := cryptoservice.NewCryptoService(gun, keyStoreManager.NonRootKeyStore(), "") + cryptoService := cryptoservice.NewCryptoService(gun, keyStoreManager.NonRootKeyStore()) nRepo := &NotaryRepository{ gun: gun, @@ -138,7 +138,7 @@ func (r *NotaryRepository) Initialize(uCryptoService *cryptoservice.UnlockedCryp // is associated with. This is used to be able to retrieve the root private key // associated with a particular certificate logrus.Debugf("Linking %s to %s.", rootKey.ID(), uCryptoService.ID()) - err = r.KeyStoreManager.RootKeyStore().Link(uCryptoService.ID(), rootKey.ID()) + err = r.KeyStoreManager.RootKeyStore().Link(uCryptoService.ID()+"_root", rootKey.ID()+"_root") if err != nil { return err } @@ -300,7 +300,7 @@ func (r *NotaryRepository) GetTargetByName(name string) (*Target, error) { // Publish pushes the local changes in signed material to the remote notary-server // Conceptually it performs an operation similar to a `git rebase` -func (r *NotaryRepository) Publish(getPass passwordRetriever) error { +func (r *NotaryRepository) Publish() error { var updateRoot bool var root *data.Signed // attempt to initialize the repo from the remote store @@ -356,12 +356,11 @@ func (r *NotaryRepository) Publish(getPass passwordRetriever) error { // check if our root file is nearing expiry. Resign if it is. if nearExpiry(r.tufRepo.Root) || r.tufRepo.Root.Dirty { - passphrase, err := getPass() if err != nil { return err } rootKeyID := r.tufRepo.Root.Signed.Roles["root"].KeyIDs[0] - rootCryptoService, err := r.KeyStoreManager.GetRootCryptoService(rootKeyID, passphrase) + rootCryptoService, err := r.KeyStoreManager.GetRootCryptoService(rootKeyID) if err != nil { return err } diff --git a/client/client_root_validation_test.go b/client/client_root_validation_test.go index 6a34add76a..68612fb863 100644 --- a/client/client_root_validation_test.go +++ b/client/client_root_validation_test.go @@ -21,6 +21,8 @@ type SignedRSARootTemplate struct { RootPem string } +var passphraseRetriever = func(string, string, bool, int) (string, bool, error) { return "passphrase", false, nil } + const validPEMEncodedRSARoot = `LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUZLekNDQXhXZ0F3SUJBZ0lRUnlwOVFxY0pmZDNheXFkaml6OHhJREFMQmdrcWhraUc5dzBCQVFzd09ERWEKTUJnR0ExVUVDaE1SWkc5amEyVnlMbU52YlM5dWIzUmhjbmt4R2pBWUJnTlZCQU1URVdSdlkydGxjaTVqYjIwdgpibTkwWVhKNU1CNFhEVEUxTURjeE56QTJNelF5TTFvWERURTNNRGN4TmpBMk16UXlNMW93T0RFYU1CZ0dBMVVFCkNoTVJaRzlqYTJWeUxtTnZiUzl1YjNSaGNua3hHakFZQmdOVkJBTVRFV1J2WTJ0bGNpNWpiMjB2Ym05MFlYSjUKTUlJQ0lqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FnOEFNSUlDQ2dLQ0FnRUFvUWZmcnpzWW5zSDh2R2Y0Smg1NQpDajV3cmpVR3pEL3NIa2FGSHB0ako2VG9KR0p2NXlNQVB4enlJbnU1c0lvR0xKYXBuWVZCb0FVMFlnSTlxbEFjCllBNlN4YVN3Z202cnB2bW5sOFFuMHFjNmdlcjNpbnBHYVVKeWxXSHVQd1drdmNpbVFBcUhaeDJkUXRMN2c2a3AKcm1LZVRXcFdvV0x3M0pvQVVaVVZoWk1kNmEyMlpML0R2QXcrSHJvZ2J6NFhleWFoRmI5SUg0MDJ6UHhONnZnYQpKRUZURjBKaTFqdE5nME1vNHBiOVNIc01zaXcrTFpLN1NmZkhWS1B4dmQyMW0vYmlObXdzZ0V4QTNVOE9PRzhwCnV5Z2ZhY3lzNWM4K1pyWCtaRkcvY3Z3S3owazYvUWZKVTQwczZNaFh3NUMyV3R0ZFZtc0c5LzdyR0ZZakhvSUoKd2VEeXhnV2s3dnhLelJKSS91bjdjYWdESWFRc0tySlFjQ0hJR0ZSbHBJUjVUd1g3dmwzUjdjUm5jckRSTVZ2YwpWU0VHMmVzeGJ3N2p0eklwL3lwblZSeGNPbnk3SXlweWpLcVZlcVo2SGd4WnRUQlZyRjFPL2FIbzJrdmx3eVJTCkF1czRrdmg2ejMranpUbTlFemZYaVBRelk5QkVrNWdPTHhoVzlyYzZVaGxTK3BlNWxrYU4vSHlxeS9sUHVxODkKZk1yMnJyN2xmNVdGZEZuemU2V05ZTUFhVzdkTkE0TkUwZHlENTM0MjhaTFh4TlZQTDRXVTY2R2FjNmx5blE4bApyNXRQc1lJRlh6aDZGVmFSS0dRVXRXMWh6OWVjTzZZMjdSaDJKc3lpSXhnVXFrMm9veEU2OXVONDJ0K2R0cUtDCjFzOEcvN1Z0WThHREFMRkxZVG56THZzQ0F3RUFBYU0xTURNd0RnWURWUjBQQVFIL0JBUURBZ0NnTUJNR0ExVWQKSlFRTU1Bb0dDQ3NHQVFVRkJ3TURNQXdHQTFVZEV3RUIvd1FDTUFBd0N3WUpLb1pJaHZjTkFRRUxBNElDQVFCTQpPbGwzRy9YQno4aWRpTmROSkRXVWgrNXczb2ptd2FuclRCZENkcUVrMVdlbmFSNkR0Y2ZsSng2WjNmL213VjRvCmIxc2tPQVgxeVg1UkNhaEpIVU14TWljei9RMzhwT1ZlbEdQclduYzNUSkIrVktqR3lIWGxRRFZrWkZiKzQrZWYKd3RqN0huZ1hoSEZGRFNnam0zRWRNbmR2Z0RRN1NRYjRza09uQ05TOWl5WDdlWHhoRkJDWm1aTCtIQUxLQmoyQgp5aFY0SWNCRHFtcDUwNHQxNHJ4OS9KdnR5MGRHN2ZZN0k1MWdFUXBtNFMwMkpNTDV4dlRtMXhmYm9XSWhaT0RJCnN3RUFPK2VrQm9GSGJTMVE5S01QaklBdzNUckNISDh4OFhacTV6c1l0QUMxeVpIZENLYTI2YVdkeTU2QTllSGoKTzFWeHp3bWJOeVhSZW5WdUJZUCswd3IzSFZLRkc0Sko0WlpwTlp6UVcvcHFFUGdoQ1RKSXZJdWVLNjUyQnlVYwovL3N2K25YZDVmMTlMZUVTOXBmMGwyNTNORGFGWlBiNmFlZ0tmcXVXaDhxbFFCbVVRMkd6YVRMYnRtTmQyOE02Clc3aUw3dGtLWmUxWm5CejlSS2d0UHJEampXR1pJbmpqY09VOEV0VDRTTHE3a0NWRG1QczVNRDh2YUFtOTZKc0UKam1MQzNVdS80azdIaURZWDBpMG1PV2tGalpRTWRWYXRjSUY1RlBTcHB3c1NiVzhRaWRuWHQ1NFV0d3RGREVQegpscGpzN3liZVFFNzFKWGNNWm5WSUs0YmpSWHNFRlBJOThScElsRWRlZGJTVWRZQW5jTE5KUlQ3SFpCTVBHU3daCjBQTkp1Z2xubHIzc3JWemRXMWR6MnhRamR2THd4eTZtTlVGNnJiUUJXQT09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K` const validCAPEMEncodeRSARoot = `LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tDQpNSUlHTXpDQ0JCdWdBd0lCQWdJQkFUQU5CZ2txaGtpRzl3MEJBUXNGQURCZk1Rc3dDUVlEVlFRR0V3SlZVekVMDQpNQWtHQTFVRUNBd0NRMEV4RmpBVUJnTlZCQWNNRFZOaGJpQkdjbUZ1WTJselkyOHhEekFOQmdOVkJBb01Ca1J2DQpZMnRsY2pFYU1CZ0dBMVVFQXd3UlRtOTBZWEo1SUZSbGMzUnBibWNnUTBFd0hoY05NVFV3TnpFMk1EUXlOVEF6DQpXaGNOTWpVd056RXpNRFF5TlRBeldqQmZNUm93R0FZRFZRUUREQkZPYjNSaGNua2dWR1Z6ZEdsdVp5QkRRVEVMDQpNQWtHQTFVRUJoTUNWVk14RmpBVUJnTlZCQWNNRFZOaGJpQkdjbUZ1WTJselkyOHhEekFOQmdOVkJBb01Ca1J2DQpZMnRsY2pFTE1Ba0dBMVVFQ0F3Q1EwRXdnZ0lpTUEwR0NTcUdTSWIzRFFFQkFRVUFBNElDRHdBd2dnSUtBb0lDDQpBUUN3VlZENHBLN3o3cFhQcEpiYVoxSGc1ZVJYSWNhWXRiRlBDbk4waXF5OUhzVkVHbkVuNUJQTlNFc3VQK20wDQo1TjBxVlY3REdiMVNqaWxvTFhEMXFERHZoWFdrK2dpUzlwcHFQSFBMVlBCNGJ2enNxd0RZcnRwYnFrWXZPMFlLDQowU0wza3hQWFVGZGxrRmZndTB4amxjem0yUGhXRzNKZDhhQXRzcEwvTCtWZlBBMTNKVWFXeFNMcHVpMUluOHJoDQpnQXlRVEs2UTRPZjZHYkpZVG5BSGI1OVVvTFhTekI1QWZxaVVxNkw3bkVZWUtvUGZsUGJSQUlXTC9VQm0wYytIDQpvY21zNzA2UFlwbVBTMlJRdjNpT0dtbm45aEVWcDNQNmpxN1dBZXZiQTRhWUd4NUVzYlZ0WUFCcUpCYkZXQXV3DQp3VEdSWW16bjBNajBlVE1nZTl6dFlCMi8yc3hkVGU2dWhtRmdwVVhuZ0RxSkk1TzlOM3pQZnZsRUltQ2t5M0hNDQpqSm9MN2c1c21xWDlvMVArRVNMaDBWWnpoaDdJRFB6UVRYcGNQSVMvNnowbDIyUUdrSy8xTjFQYUFEYVVIZExMDQp2U2F2M3kyQmFFbVB2ZjJma1pqOHlQNWVZZ2k3Q3c1T05oSExEWUhGY2w5Wm0veXdtZHhISkVUejluZmdYbnNXDQpITnhEcXJrQ1ZPNDZyL3U2clNyVXQ2aHIzb2RkSkc4czhKbzA2ZWFydzZYVTNNek0rM2dpd2tLMFNTTTN1UlBxDQo0QXNjUjFUditFMzFBdU9BbWpxWVFvVDI5Yk1JeG9TemVsamovWW5lZHdqVzQ1cFd5YzNKb0hhaWJEd3ZXOVVvDQpHU1pCVnk0aHJNL0ZhN1hDV3YxV2ZITlcxZ0R3YUxZd0RubDVqRm1SQnZjZnVRSURBUUFCbzRINU1JSDJNSUdSDQpCZ05WSFNNRWdZa3dnWWFBRkhVTTFVM0U0V3lMMW52RmQrZFBZOGY0TzJoWm9XT2tZVEJmTVFzd0NRWURWUVFHDQpFd0pWVXpFTE1Ba0dBMVVFQ0F3Q1EwRXhGakFVQmdOVkJBY01EVk5oYmlCR2NtRnVZMmx6WTI4eER6QU5CZ05WDQpCQW9NQmtSdlkydGxjakVhTUJnR0ExVUVBd3dSVG05MFlYSjVJRlJsYzNScGJtY2dRMEdDQ1FEQ2VETGJlbUlUDQpTekFTQmdOVkhSTUJBZjhFQ0RBR0FRSC9BZ0VBTUIwR0ExVWRKUVFXTUJRR0NDc0dBUVVGQndNQ0JnZ3JCZ0VGDQpCUWNEQVRBT0JnTlZIUThCQWY4RUJBTUNBVVl3SFFZRFZSME9CQllFRkhlNDhoY0JjQXAwYlVWbFR4WGVSQTRvDQpFMTZwTUEwR0NTcUdTSWIzRFFFQkN3VUFBNElDQVFBV1V0QVBkVUZwd1JxK04xU3pHVWVqU2lrZU1HeVBac2NaDQpKQlVDbWhab0Z1ZmdYR2JMTzVPcGNSTGFWM1hkYTB0LzVQdGRHTVNFemN6ZW9aSFdrbkR0dys3OU9CaXR0UFBqDQpTaDFvRkR1UG8zNVI3ZVA2MjRsVUNjaC9JblpDcGhUYUx4OW9ETEdjYUszYWlsUTl3akJkS2RsQmw4S05LSVpwDQphMTNhUDVyblNtMkp2YSt0WHkveWkzQlNkczNkR0Q4SVRLWnlJLzZBRkh4R3ZPYnJESUJwbzRGRi96Y1dYVkRqDQpwYU9teHBsUnRNNEhpdG0rc1hHdmZxSmU0eDVEdU9YT25QclQzZEh2UlQ2dlNaVW9Lb2J4TXFtUlRPY3JPSVBhDQpFZU1wT29ic2hPUnVSbnRNRFl2dmdPM0Q2cDZpY2lEVzJWcDlONnJkTWRmT1dFUU44SlZXdkI3SXhSSGs5cUtKDQp2WU9XVmJjekF0MHFwTXZYRjNQWExqWmJVTTBrbk9kVUtJRWJxUDRZVWJnZHp4NlJ0Z2lpWTkzMEFqNnRBdGNlDQowZnBnTmx2ak1ScFNCdVdUbEFmTk5qRy9ZaG5kTXo5dUk2OFRNZkZwUjNQY2dWSXYzMGtydy85VnpvTGkyRHBlDQpvdzZEckdPNm9pK0RoTjc4UDRqWS9POVVjelpLMnJvWkwxT2k1UDBSSXhmMjNVWkM3eDFEbGNOM25CcjRzWVN2DQpyQng0Y0ZUTU5wd1UrbnpzSWk0ZGpjRkRLbUpkRU95ak1ua1AydjBMd2U3eXZLMDhwWmRFdSswemJycTE3a3VlDQpYcFhMYzdLNjhRQjE1eXh6R3lsVTVyUnd6bUMvWXNBVnlFNGVvR3U4UHhXeHJFUnZIYnk0QjhZUDB2QWZPcmFMDQpsS21YbEs0ZFRnPT0NCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0NCg==` @@ -49,13 +51,13 @@ func validateRootSuccessfully(t *testing.T, rootType data.KeyAlgorithm) { ts, mux := createTestServer(t) defer ts.Close() - repo, err := NewNotaryRepository(tempBaseDir, gun, ts.URL, http.DefaultTransport) + repo, err := NewNotaryRepository(tempBaseDir, gun, ts.URL, http.DefaultTransport, passphraseRetriever) assert.NoError(t, err, "error creating repository: %s", err) - rootKeyID, err := repo.KeyStoreManager.GenRootKey(rootType.String(), "passphrase") + rootKeyID, err := repo.KeyStoreManager.GenRootKey(rootType.String()) assert.NoError(t, err, "error generating root key: %s", err) - rootCryptoService, err := repo.KeyStoreManager.GetRootCryptoService(rootKeyID, "passphrase") + rootCryptoService, err := repo.KeyStoreManager.GetRootCryptoService(rootKeyID) assert.NoError(t, err, "error retrieving root key: %s", err) err = repo.Initialize(rootCryptoService) @@ -71,7 +73,7 @@ func validateRootSuccessfully(t *testing.T, rootType data.KeyAlgorithm) { var tempKey data.TUFKey json.Unmarshal([]byte(timestampECDSAKeyJSON), &tempKey) - repo.KeyStoreManager.NonRootKeyStore().AddKey(filepath.Join(filepath.FromSlash(gun), tempKey.ID()), &tempKey) + repo.KeyStoreManager.NonRootKeyStore().AddKey(filepath.Join(filepath.FromSlash(gun), tempKey.ID()), "root", &tempKey) // Because ListTargets will clear this savedTUFRepo := repo.tufRepo @@ -200,7 +202,7 @@ func TestValidateRootWithInvalidData(t *testing.T) { assert.NoError(t, err, "failed to create a temporary directory: %s", err) // Create a FileStoreManager - keyStoreManager, err := keystoremanager.NewKeyStoreManager(tempBaseDir) + keyStoreManager, err := keystoremanager.NewKeyStoreManager(tempBaseDir, passphraseRetriever) assert.NoError(t, err) // Execute our template diff --git a/client/client_test.go b/client/client_test.go index c5602544c8..451e31e0b6 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -63,13 +63,13 @@ func testInitRepo(t *testing.T, rootType data.KeyAlgorithm) { ts, _ := createTestServer(t) defer ts.Close() - repo, err := NewNotaryRepository(tempBaseDir, gun, ts.URL, http.DefaultTransport) + repo, err := NewNotaryRepository(tempBaseDir, gun, ts.URL, http.DefaultTransport, passphraseRetriever) assert.NoError(t, err, "error creating repo: %s", err) - rootKeyID, err := repo.KeyStoreManager.GenRootKey(rootType.String(), "passphrase") + rootKeyID, err := repo.KeyStoreManager.GenRootKey(rootType.String()) assert.NoError(t, err, "error generating root key: %s", err) - rootCryptoService, err := repo.KeyStoreManager.GetRootCryptoService(rootKeyID, "passphrase") + rootCryptoService, err := repo.KeyStoreManager.GetRootCryptoService(rootKeyID) assert.NoError(t, err, "error retrieving root key: %s", err) err = repo.Initialize(rootCryptoService) @@ -95,14 +95,15 @@ func testInitRepo(t *testing.T, rootType data.KeyAlgorithm) { // in the private key store. privKeyList := repo.KeyStoreManager.NonRootKeyStore().ListFiles(true) for _, privKeyName := range privKeyList { - _, err := os.Stat(privKeyName) + privKeyFileName := filepath.Join(repo.KeyStoreManager.NonRootKeyStore().BaseDir(), privKeyName) + _, err := os.Stat(privKeyFileName) assert.NoError(t, err, "missing private key: %s", privKeyName) } // Look for keys in root_keys // There should be a file named after the key ID of the root key we // passed in. - rootKeyFilename := rootCryptoService.ID() + ".key" + rootKeyFilename := rootCryptoService.ID() + "_root.key" _, err = os.Stat(filepath.Join(tempBaseDir, "private", "root_keys", rootKeyFilename)) assert.NoError(t, err, "missing root key") @@ -114,7 +115,7 @@ func testInitRepo(t *testing.T, rootType data.KeyAlgorithm) { certID, err := trustmanager.FingerprintCert(certificates[0]) assert.NoError(t, err, "unable to fingerprint the certificate") - actualDest, err := os.Readlink(filepath.Join(tempBaseDir, "private", "root_keys", certID+".key")) + actualDest, err := os.Readlink(filepath.Join(tempBaseDir, "private", "root_keys", certID+"_root"+".key")) assert.NoError(t, err, "missing symlink to root key") assert.Equal(t, rootKeyFilename, actualDest, "symlink to root key has wrong destination") @@ -206,13 +207,13 @@ func testAddListTarget(t *testing.T, rootType data.KeyAlgorithm) { ts, mux := createTestServer(t) defer ts.Close() - repo, err := NewNotaryRepository(tempBaseDir, gun, ts.URL, http.DefaultTransport) + repo, err := NewNotaryRepository(tempBaseDir, gun, ts.URL, http.DefaultTransport, passphraseRetriever) assert.NoError(t, err, "error creating repository: %s", err) - rootKeyID, err := repo.KeyStoreManager.GenRootKey(rootType.String(), "passphrase") + rootKeyID, err := repo.KeyStoreManager.GenRootKey(rootType.String()) assert.NoError(t, err, "error generating root key: %s", err) - rootCryptoService, err := repo.KeyStoreManager.GetRootCryptoService(rootKeyID, "passphrase") + rootCryptoService, err := repo.KeyStoreManager.GetRootCryptoService(rootKeyID) assert.NoError(t, err, "error retreiving root key: %s", err) err = repo.Initialize(rootCryptoService) @@ -311,7 +312,7 @@ func testAddListTarget(t *testing.T, rootType data.KeyAlgorithm) { var tempKey data.TUFKey json.Unmarshal([]byte(timestampECDSAKeyJSON), &tempKey) - repo.KeyStoreManager.NonRootKeyStore().AddKey(filepath.Join(filepath.FromSlash(gun), tempKey.ID()), &tempKey) + repo.KeyStoreManager.NonRootKeyStore().AddKey(filepath.Join(filepath.FromSlash(gun), tempKey.ID()), "nonroot", &tempKey) // Because ListTargets will clear this savedTUFRepo := repo.tufRepo @@ -395,13 +396,13 @@ func testValidateRootKey(t *testing.T, rootType data.KeyAlgorithm) { ts, _ := createTestServer(t) defer ts.Close() - repo, err := NewNotaryRepository(tempBaseDir, gun, ts.URL, http.DefaultTransport) + repo, err := NewNotaryRepository(tempBaseDir, gun, ts.URL, http.DefaultTransport, passphraseRetriever) assert.NoError(t, err, "error creating repository: %s", err) - rootKeyID, err := repo.KeyStoreManager.GenRootKey(rootType.String(), "passphrase") + rootKeyID, err := repo.KeyStoreManager.GenRootKey(rootType.String()) assert.NoError(t, err, "error generating root key: %s", err) - rootCryptoService, err := repo.KeyStoreManager.GetRootCryptoService(rootKeyID, "passphrase") + rootCryptoService, err := repo.KeyStoreManager.GetRootCryptoService(rootKeyID) assert.NoError(t, err, "error retreiving root key: %s", err) err = repo.Initialize(rootCryptoService) @@ -459,7 +460,8 @@ func testPublish(t *testing.T, rootType data.KeyAlgorithm) { // Set up server ctx := context.WithValue(context.Background(), "metaStore", storage.NewMemStorage()) - hand := utils.RootHandlerFactory(nil, ctx, cryptoservice.NewCryptoService("", trustmanager.NewKeyMemoryStore(), "")) + hand := utils.RootHandlerFactory(nil, ctx, + cryptoservice.NewCryptoService("", trustmanager.NewKeyMemoryStore(passphraseRetriever))) r := mux.NewRouter() r.Methods("POST").Path("/v2/{imageName:.*}/_trust/tuf/").Handler(hand(handlers.AtomicUpdateHandler, "push", "pull")) @@ -471,13 +473,13 @@ func testPublish(t *testing.T, rootType data.KeyAlgorithm) { ts := httptest.NewServer(r) - repo, err := NewNotaryRepository(tempBaseDir, gun, ts.URL, http.DefaultTransport) + repo, err := NewNotaryRepository(tempBaseDir, gun, ts.URL, http.DefaultTransport, passphraseRetriever) assert.NoError(t, err, "error creating repository: %s", err) - rootKeyID, err := repo.KeyStoreManager.GenRootKey(rootType.String(), "passphrase") + rootKeyID, err := repo.KeyStoreManager.GenRootKey(rootType.String()) assert.NoError(t, err, "error generating root key: %s", err) - rootCryptoService, err := repo.KeyStoreManager.GetRootCryptoService(rootKeyID, "passphrase") + rootCryptoService, err := repo.KeyStoreManager.GetRootCryptoService(rootKeyID) assert.NoError(t, err, "error retreiving root key: %s", err) err = repo.Initialize(rootCryptoService) @@ -561,9 +563,7 @@ func testPublish(t *testing.T, rootType data.KeyAlgorithm) { changelistDir.Close() // Now test Publish - err = repo.Publish(func() (string, error) { - return "passphrase", nil - }) + err = repo.Publish() assert.NoError(t, err) changelistDir, err = os.Open(changelistDirPath) @@ -579,7 +579,7 @@ func testPublish(t *testing.T, rootType data.KeyAlgorithm) { assert.NoError(t, err, "failed to create a temporary directory: %s", err) - repo2, err := NewNotaryRepository(tempBaseDir, gun, ts.URL, http.DefaultTransport) + repo2, err := NewNotaryRepository(tempBaseDir, gun, ts.URL, http.DefaultTransport, passphraseRetriever) assert.NoError(t, err, "error creating repository: %s", err) targets, err := repo2.ListTargets() diff --git a/cmd/notary-server/config.json b/cmd/notary-server/config.json index 3a33df247b..42345fe96b 100644 --- a/cmd/notary-server/config.json +++ b/cmd/notary-server/config.json @@ -3,11 +3,10 @@ "addr": ":4443" }, "trust_service": { - "type": "remote", - "hostname": "notarysigner", - "port": "7899", - "tls_ca_file": "./fixtures/root-ca.crt" - }, + "type": "remote", + "hostname": "notarysigner", + "port": "7899", + "tls_ca_file": "./fixtures/root-ca.crt" }, "logging": { "level": 5 }, diff --git a/cmd/notary-signer/main.go b/cmd/notary-signer/main.go index ee697cd351..2107a0b9c9 100644 --- a/cmd/notary-signer/main.go +++ b/cmd/notary-signer/main.go @@ -41,6 +41,12 @@ func init() { flag.BoolVar(&debug, "debug", false, "show the version and exit") } +func passphraseRetriever(keyName, alias string, createNew bool, attempts int) (passphrase string, giveup bool, err error) { + + //TODO(mccauley) Read from config once we have locked keys in notary-signer + return "", false, nil +} + func main() { flag.Usage = usage flag.Parse() @@ -83,8 +89,8 @@ func main() { cryptoServices[data.RSAKey] = api.NewRSAHardwareCryptoService(ctx, session) } - keyStore := trustmanager.NewKeyMemoryStore() - cryptoService := cryptoservice.NewCryptoService("", keyStore, "") + keyStore := trustmanager.NewKeyMemoryStore(passphraseRetriever) + cryptoService := cryptoservice.NewCryptoService("", keyStore) cryptoServices[data.ED25519Key] = cryptoService cryptoServices[data.ECDSAKey] = cryptoService diff --git a/cmd/notary/main.go b/cmd/notary/main.go index 174401d24d..5ecf6327b5 100644 --- a/cmd/notary/main.go +++ b/cmd/notary/main.go @@ -86,11 +86,11 @@ func init() { fatalf("could not create Certificate X509FileStore: %v", err) } - privKeyStore, err = trustmanager.NewKeyFileStore(finalPrivDir) + privKeyStore, err = trustmanager.NewKeyFileStore(finalPrivDir, + func(string, string, bool, int) (string, bool, error) { return "", false, nil }) if err != nil { fatalf("could not create KeyFileStore: %v", err) } - } func main() { diff --git a/cmd/notary/tuf.go b/cmd/notary/tuf.go index b658f888de..08904859c4 100644 --- a/cmd/notary/tuf.go +++ b/cmd/notary/tuf.go @@ -2,7 +2,6 @@ package main import ( "bufio" - "bytes" "crypto/sha256" "crypto/tls" "errors" @@ -14,13 +13,21 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/docker/pkg/term" notaryclient "github.com/docker/notary/client" + "github.com/docker/notary/trustmanager" "github.com/spf13/cobra" "github.com/spf13/viper" + "strings" ) // FIXME: This should not be hardcoded const hardcodedBaseURL = "https://notary-server:4443" +var retriever trustmanager.PassphraseRetriever + +func init() { + retriever = getNotaryPassphraseRetriever() +} + var remoteTrustServer string var cmdTufList = &cobra.Command{ @@ -82,7 +89,8 @@ func tufAdd(cmd *cobra.Command, args []string) { targetName := args[1] targetPath := args[2] - repo, err := notaryclient.NewNotaryRepository(viper.GetString("baseTrustDir"), gun, hardcodedBaseURL, getInsecureTransport()) + repo, err := notaryclient.NewNotaryRepository(viper.GetString("baseTrustDir"), gun, hardcodedBaseURL, + getInsecureTransport(), retriever) if err != nil { fatalf(err.Error()) } @@ -106,35 +114,26 @@ func tufInit(cmd *cobra.Command, args []string) { gun := args[0] - nRepo, err := notaryclient.NewNotaryRepository(viper.GetString("baseTrustDir"), gun, hardcodedBaseURL, getInsecureTransport()) + nRepo, err := notaryclient.NewNotaryRepository(viper.GetString("baseTrustDir"), gun, hardcodedBaseURL, + getInsecureTransport(), retriever) if err != nil { fatalf(err.Error()) } keysList := nRepo.KeyStoreManager.RootKeyStore().ListKeys() - var passphrase string var rootKeyID string if len(keysList) < 1 { fmt.Println("No root keys found. Generating a new root key...") - passphrase, err = passphraseRetriever() - if err != nil { - fatalf(err.Error()) - } - rootKeyID, err = nRepo.KeyStoreManager.GenRootKey("ECDSA", passphrase) + rootKeyID, err = nRepo.KeyStoreManager.GenRootKey("ECDSA") if err != nil { fatalf(err.Error()) } } else { rootKeyID = keysList[0] fmt.Println("Root key found.") - fmt.Printf("Enter passphrase for: %s (%d)\n", rootKeyID, len(rootKeyID)) - passphrase, err = passphraseRetriever() - if err != nil { - fatalf(err.Error()) - } } - rootCryptoService, err := nRepo.KeyStoreManager.GetRootCryptoService(rootKeyID, passphrase) + rootCryptoService, err := nRepo.KeyStoreManager.GetRootCryptoService(rootKeyID) if err != nil { fatalf(err.Error()) } @@ -151,8 +150,8 @@ func tufList(cmd *cobra.Command, args []string) { fatalf("must specify a GUN") } gun := args[0] - - repo, err := notaryclient.NewNotaryRepository(viper.GetString("baseTrustDir"), gun, hardcodedBaseURL, getInsecureTransport()) + repo, err := notaryclient.NewNotaryRepository(viper.GetString("baseTrustDir"), gun, hardcodedBaseURL, + getInsecureTransport(), retriever) if err != nil { fatalf(err.Error()) } @@ -177,7 +176,8 @@ func tufLookup(cmd *cobra.Command, args []string) { gun := args[0] targetName := args[1] - repo, err := notaryclient.NewNotaryRepository(viper.GetString("baseTrustDir"), gun, hardcodedBaseURL, getInsecureTransport()) + repo, err := notaryclient.NewNotaryRepository(viper.GetString("baseTrustDir"), gun, hardcodedBaseURL, + getInsecureTransport(), retriever) if err != nil { fatalf(err.Error()) } @@ -201,12 +201,13 @@ func tufPublish(cmd *cobra.Command, args []string) { fmt.Println("Pushing changes to ", gun, ".") - repo, err := notaryclient.NewNotaryRepository(viper.GetString("baseTrustDir"), gun, hardcodedBaseURL, getInsecureTransport()) + repo, err := notaryclient.NewNotaryRepository(viper.GetString("baseTrustDir"), gun, hardcodedBaseURL, + getInsecureTransport(), retriever) if err != nil { fatalf(err.Error()) } - err = repo.Publish(passphraseRetriever) + err = repo.Publish() if err != nil { fatalf(err.Error()) } @@ -246,7 +247,8 @@ func verify(cmd *cobra.Command, args []string) { //TODO (diogo): This code is copy/pasted from lookup. gun := args[0] targetName := args[1] - repo, err := notaryclient.NewNotaryRepository(viper.GetString("baseTrustDir"), gun, hardcodedBaseURL, getInsecureTransport()) + repo, err := notaryclient.NewNotaryRepository(viper.GetString("baseTrustDir"), gun, hardcodedBaseURL, + getInsecureTransport(), retriever) if err != nil { fatalf(err.Error()) } @@ -270,58 +272,91 @@ func verify(cmd *cobra.Command, args []string) { return } -func passphraseRetriever() (string, error) { - fmt.Println("Please provide a passphrase for this root key: ") - var passphrase string - _, err := fmt.Scanln(&passphrase) - if err != nil { - return "", err - } - if len(passphrase) < 8 { - fmt.Println("Please use a password manager to generate and store a good random passphrase.") - return "", errors.New("Passphrase too short") - } - return passphrase, nil -} +func getNotaryPassphraseRetriever() trustmanager.PassphraseRetriever { + userEnteredTargetsSnapshotsPass := false + targetsSnapshotsPass := "" + userEnteredRootsPass := false + rootsPass := "" -func getPassphrase(confirm bool) ([]byte, error) { - if pass := os.Getenv("NOTARY_ROOT_PASSPHRASE"); pass != "" { - return []byte(pass), nil - } + return func(keyID string, alias string, createNew bool, numAttempts int) (string, bool, error) { - state, err := term.SaveState(0) - if err != nil { - return nil, err - } - term.DisableEcho(0, state) - defer term.RestoreTerminal(0, state) + // First, check if we have a password cached for this alias. + if numAttempts == 0 { + if userEnteredTargetsSnapshotsPass && (alias == "snapshot" || alias == "targets") { + return targetsSnapshotsPass, false, nil + } + if userEnteredRootsPass && (alias == "root") { + return rootsPass, false, nil + } + } - stdin := bufio.NewReader(os.Stdin) + if numAttempts > 3 && !createNew { + return "", true, errors.New("Too many attempts") + } - fmt.Printf("Enter root key passphrase: ") - passphrase, err := stdin.ReadBytes('\n') - fmt.Println() - if err != nil { - return nil, err - } - passphrase = passphrase[0 : len(passphrase)-1] + state, err := term.SaveState(0) + if err != nil { + return "", false, err + } + term.DisableEcho(0, state) + defer term.RestoreTerminal(0, state) - if !confirm { - return passphrase, nil - } + stdin := bufio.NewReader(os.Stdin) - fmt.Printf("Repeat root key passphrase: ") - confirmation, err := stdin.ReadBytes('\n') - fmt.Println() - if err != nil { - return nil, err - } - confirmation = confirmation[0 : len(confirmation)-1] + if createNew { + fmt.Printf("Enter passphrase for new %s key with id %s: ", alias, keyID) + } else { + fmt.Printf("Enter key passphrase for %s key with id %s: ", alias, keyID) + } - if !bytes.Equal(passphrase, confirmation) { - return nil, errors.New("The entered passphrases do not match") + passphrase, err := stdin.ReadBytes('\n') + fmt.Println() + if err != nil { + return "", false, err + } + + retPass := strings.TrimSpace(string(passphrase)) + + if !createNew { + if alias == "snapshot" || alias == "targets" { + userEnteredTargetsSnapshotsPass = true + targetsSnapshotsPass = retPass + } + if alias == "root" { + userEnteredRootsPass = true + rootsPass = retPass + } + return retPass, false, nil + } + + if len(retPass) < 8 { + fmt.Println("Please use a password manager to generate and store a good random passphrase.") + return "", false, errors.New("Passphrase too short") + } + + fmt.Printf("Repeat passphrase for new %s key with id %s: ", alias, keyID) + confirmation, err := stdin.ReadBytes('\n') + fmt.Println() + if err != nil { + return "", false, err + } + confirmationStr := strings.TrimSpace(string(confirmation)) + + if retPass != confirmationStr { + return "", false, errors.New("The entered passphrases do not match") + } + + if alias == "snapshot" || alias == "targets" { + userEnteredTargetsSnapshotsPass = true + targetsSnapshotsPass = retPass + } + if alias == "root" { + userEnteredRootsPass = true + rootsPass = retPass + } + + return retPass, false, nil } - return passphrase, nil } func getInsecureTransport() *http.Transport { diff --git a/cryptoservice/crypto_service.go b/cryptoservice/crypto_service.go index 27f069a524..061c0541d4 100644 --- a/cryptoservice/crypto_service.go +++ b/cryptoservice/crypto_service.go @@ -23,14 +23,13 @@ const ( // CryptoService implements Sign and Create, holding a specific GUN and keystore to // operate on type CryptoService struct { - gun string - passphrase string - keyStore trustmanager.KeyStore + gun string + keyStore trustmanager.KeyStore } // NewCryptoService returns an instance of CryptoService -func NewCryptoService(gun string, keyStore trustmanager.KeyStore, passphrase string) *CryptoService { - return &CryptoService{gun: gun, keyStore: keyStore, passphrase: passphrase} +func NewCryptoService(gun string, keyStore trustmanager.KeyStore) *CryptoService { + return &CryptoService{gun: gun, keyStore: keyStore} } // Create is used to generate keys for targets, snapshots and timestamps @@ -59,8 +58,8 @@ func (ccs *CryptoService) Create(role string, algorithm data.KeyAlgorithm) (data } logrus.Debugf("generated new %s key for role: %s and keyID: %s", algorithm, role, privKey.ID()) - // Store the private key into our keystore with the name being: /GUN/ID.key - err = ccs.keyStore.AddKey(filepath.Join(ccs.gun, privKey.ID()), privKey) + // Store the private key into our keystore with the name being: /GUN/ID.key with an alias of role + err = ccs.keyStore.AddKey(filepath.Join(ccs.gun, privKey.ID()), role, privKey) if err != nil { return nil, fmt.Errorf("failed to add key to filestore: %v", err) } @@ -78,7 +77,7 @@ func (ccs *CryptoService) GetKey(keyID string) data.PublicKey { // RemoveKey deletes a key by ID func (ccs *CryptoService) RemoveKey(keyID string) error { - return ccs.keyStore.Remove(keyID) + return ccs.keyStore.RemoveKey(keyID) } // Sign returns the signatures for the payload with a set of keyIDs. It ignores @@ -93,14 +92,9 @@ func (ccs *CryptoService) Sign(keyIDs []string, payload []byte) ([]data.Signatur var privKey data.PrivateKey var err error - // Read PrivateKey from file and decrypt it if there is a passphrase. - if ccs.passphrase != "" { - privKey, err = ccs.keyStore.GetDecryptedKey(keyName, ccs.passphrase) - } else { - privKey, err = ccs.keyStore.GetKey(keyName) - } + privKey, err = ccs.keyStore.GetKey(keyName) if err != nil { - // Note that GetDecryptedKey always fails on InitRepo. + // Note that GetKey always fails on InitRepo. // InitRepo gets a signer that doesn't have access to // the root keys. Continuing here is safe because we // end up not returning any signatures. diff --git a/cryptoservice/crypto_service_test.go b/cryptoservice/crypto_service_test.go index 3e97d9621c..be763b4f99 100644 --- a/cryptoservice/crypto_service_test.go +++ b/cryptoservice/crypto_service_test.go @@ -17,11 +17,13 @@ func TestCryptoService(t *testing.T) { } } +var passphraseRetriever = func(string, string, bool, int) (string, bool, error) { return "", false, nil } + func testCryptoService(t *testing.T, keyAlgo data.KeyAlgorithm, verifier signed.Verifier) { content := []byte("this is a secret") - keyStore := trustmanager.NewKeyMemoryStore() - cryptoService := NewCryptoService("", keyStore, "") + keyStore := trustmanager.NewKeyMemoryStore(passphraseRetriever) + cryptoService := NewCryptoService("", keyStore) // Test Create tufKey, err := cryptoService.Create("", keyAlgo) diff --git a/cryptoservice/unlocked_crypto_service_test.go b/cryptoservice/unlocked_crypto_service_test.go index 4624f0b66d..1197ae7b2d 100644 --- a/cryptoservice/unlocked_crypto_service_test.go +++ b/cryptoservice/unlocked_crypto_service_test.go @@ -13,13 +13,12 @@ func TestUnlockedSigner(t *testing.T) { privKey, err := trustmanager.GenerateECDSAKey(rand.Reader) assert.NoError(t, err, "could not generate key") - keyStore := trustmanager.NewKeyMemoryStore() + keyStore := trustmanager.NewKeyMemoryStore(passphraseRetriever) - passphrase := "passphrase" - err = keyStore.AddEncryptedKey(privKey.ID(), privKey, passphrase) + err = keyStore.AddKey(privKey.ID(), "root", privKey) assert.NoError(t, err, "could not add key to store") - cryptoService := NewCryptoService("", keyStore, passphrase) + cryptoService := NewCryptoService("", keyStore) uCryptoService := NewUnlockedCryptoService(privKey, cryptoService) // Check ID method diff --git a/keystoremanager/import_export.go b/keystoremanager/import_export.go index 936496e35c..1a86157f0a 100644 --- a/keystoremanager/import_export.go +++ b/keystoremanager/import_export.go @@ -13,7 +13,6 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/notary/trustmanager" - "github.com/endophage/gotuf/data" ) var ( @@ -25,10 +24,6 @@ var ( // unencrypted ErrRootKeyNotEncrypted = errors.New("only encrypted root keys may be imported") - // ErrNonRootKeyEncrypted is returned if a non-root key is found to - // be encrypted while exporting - ErrNonRootKeyEncrypted = errors.New("found encrypted non-root key") - // ErrNoKeysFoundForGUN is returned if no keys are found for the // specified GUN during export ErrNoKeysFoundForGUN = errors.New("no keys found for specified GUN") @@ -37,7 +32,7 @@ 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.rootKeyStore.Get(keyID) + pemBytes, err := km.rootKeyStore.Get(keyID + "_root") if err != nil { return err } @@ -75,43 +70,27 @@ func (km *KeyStoreManager) ImportRootKey(source io.Reader, keyID string) error { return err } - if err = km.rootKeyStore.Add(keyID, pemBytes); err != nil { + if err = km.rootKeyStore.Add(keyID+"_root", pemBytes); err != nil { return err } return err } -func moveKeysWithNewPassphrase(oldKeyStore, newKeyStore *trustmanager.KeyFileStore, outputPassphrase string) error { +func moveKeys(oldKeyStore, newKeyStore *trustmanager.KeyFileStore) error { // List all files but no symlinks - for _, f := range oldKeyStore.ListFiles(false) { - fullKeyPath := strings.TrimSpace(strings.TrimSuffix(f, filepath.Ext(f))) - relKeyPath := strings.TrimPrefix(fullKeyPath, oldKeyStore.BaseDir()) - relKeyPath = strings.TrimPrefix(relKeyPath, string(filepath.Separator)) - - pemBytes, err := oldKeyStore.Get(relKeyPath) + for _, f := range oldKeyStore.ListKeys() { + pemBytes, err := oldKeyStore.GetKey(f) if err != nil { return err } - block, _ := pem.Decode(pemBytes) - if block == nil { - return ErrNoValidPrivateKey + alias, err := oldKeyStore.GetKeyAlias(f) + if err != nil { + return err } - if !x509.IsEncryptedPEMBlock(block) { - // Key is not encrypted. Parse it, and add it - // to the temporary store as an encrypted key. - privKey, err := trustmanager.ParsePEMPrivateKey(pemBytes, "") - if err != nil { - return err - } - err = newKeyStore.AddEncryptedKey(relKeyPath, privKey, outputPassphrase) - } else { - // Encrypted key - pass it through without - // decrypting - err = newKeyStore.Add(relKeyPath, pemBytes) - } + err = newKeyStore.AddKey(f, alias, pemBytes) if err != nil { return err @@ -121,11 +100,10 @@ func moveKeysWithNewPassphrase(oldKeyStore, newKeyStore *trustmanager.KeyFileSto return nil } -func addKeysToArchive(zipWriter *zip.Writer, newKeyStore *trustmanager.KeyFileStore, tempBaseDir string) error { +func addKeysToArchive(zipWriter *zip.Writer, newKeyStore *trustmanager.KeyFileStore, subDir string) error { // List all files but no symlinks - for _, fullKeyPath := range newKeyStore.ListFiles(false) { - relKeyPath := strings.TrimPrefix(fullKeyPath, tempBaseDir) - relKeyPath = strings.TrimPrefix(relKeyPath, string(filepath.Separator)) + for _, relKeyPath := range newKeyStore.ListFiles(false) { + fullKeyPath := filepath.Join(newKeyStore.BaseDir(), relKeyPath) fi, err := os.Stat(fullKeyPath) if err != nil { @@ -137,7 +115,7 @@ func addKeysToArchive(zipWriter *zip.Writer, newKeyStore *trustmanager.KeyFileSt return err } - infoHeader.Name = relKeyPath + infoHeader.Name = filepath.Join(subDir, relKeyPath) zipFileEntryWriter, err := zipWriter.CreateHeader(infoHeader) if err != nil { return err @@ -156,40 +134,41 @@ func addKeysToArchive(zipWriter *zip.Writer, newKeyStore *trustmanager.KeyFileSt } // ExportAllKeys exports all keys to an io.Writer in zip format. -// outputPassphrase is the new passphrase to use to encrypt the existing keys. -// If blank, the keys will not be encrypted. Note that keys which are already -// encrypted are not re-encrypted. They will be included in the zip with their -// original encryption. -func (km *KeyStoreManager) ExportAllKeys(dest io.Writer, outputPassphrase string) error { +// newPassphraseRetriever will be used to obtain passphrases to use to encrypt the existing keys. +func (km *KeyStoreManager) ExportAllKeys(dest io.Writer, newPassphraseRetriever trustmanager.PassphraseRetriever) error { tempBaseDir, err := ioutil.TempDir("", "notary-key-export-") defer os.RemoveAll(tempBaseDir) + privNonRootKeysSubdir := filepath.Join(privDir, nonRootKeysSubdir) + privRootKeysSubdir := filepath.Join(privDir, rootKeysSubdir) + // Create temporary keystores to use as a staging area - tempNonRootKeysPath := filepath.Join(tempBaseDir, privDir, nonRootKeysSubdir) - tempNonRootKeyStore, err := trustmanager.NewKeyFileStore(tempNonRootKeysPath) + tempNonRootKeysPath := filepath.Join(tempBaseDir, privNonRootKeysSubdir) + tempNonRootKeyStore, err := trustmanager.NewKeyFileStore(tempNonRootKeysPath, newPassphraseRetriever) if err != nil { return err } - tempRootKeysPath := filepath.Join(tempBaseDir, privDir, rootKeysSubdir) - tempRootKeyStore, err := trustmanager.NewKeyFileStore(tempRootKeysPath) + tempRootKeysPath := filepath.Join(tempBaseDir, privRootKeysSubdir) + tempRootKeyStore, err := trustmanager.NewKeyFileStore(tempRootKeysPath, newPassphraseRetriever) if err != nil { return err } - if err := moveKeysWithNewPassphrase(km.rootKeyStore, tempRootKeyStore, outputPassphrase); err != nil { + if err := moveKeys(km.rootKeyStore, tempRootKeyStore); err != nil { return err } - if err := moveKeysWithNewPassphrase(km.nonRootKeyStore, tempNonRootKeyStore, outputPassphrase); err != nil { + if err := moveKeys(km.nonRootKeyStore, tempNonRootKeyStore); err != nil { return err } zipWriter := zip.NewWriter(dest) - if err := addKeysToArchive(zipWriter, tempRootKeyStore, tempBaseDir); err != nil { + if err := addKeysToArchive(zipWriter, tempRootKeyStore, privRootKeysSubdir); err != nil { return err } - if err := addKeysToArchive(zipWriter, tempNonRootKeyStore, tempBaseDir); err != nil { + if err := addKeysToArchive(zipWriter, tempNonRootKeyStore, privNonRootKeysSubdir); err != nil { + return err } @@ -201,12 +180,12 @@ func (km *KeyStoreManager) ExportAllKeys(dest io.Writer, outputPassphrase string // ImportKeysZip imports keys from a zip file provided as an io.ReaderAt. 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, passphrase string) error { +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 newRootKeys := make(map[string][]byte) - newNonRootKeys := make(map[string]data.PrivateKey) + newNonRootKeys := make(map[string][]byte) // Note that using / as a separator is okay here - the zip package // guarantees that the separator will be / @@ -222,7 +201,7 @@ func (km *KeyStoreManager) ImportKeysZip(zipReader zip.Reader, passphrase string return err } - pemBytes, err := ioutil.ReadAll(rc) + fileBytes, err := ioutil.ReadAll(rc) if err != nil { return nil } @@ -231,25 +210,20 @@ func (km *KeyStoreManager) ImportKeysZip(zipReader zip.Reader, passphrase string // Note that using / as a separator is okay here - the zip // package guarantees that the separator will be / if strings.HasPrefix(fNameTrimmed, rootKeysPrefix) { - if err = checkRootKeyIsEncrypted(pemBytes); err != nil { + if err = checkRootKeyIsEncrypted(fileBytes); err != nil { rc.Close() return err } // Root keys are preserved without decrypting keyName := strings.TrimPrefix(fNameTrimmed, rootKeysPrefix) - newRootKeys[keyName] = pemBytes + newRootKeys[keyName] = fileBytes } else if strings.HasPrefix(fNameTrimmed, nonRootKeysPrefix) { - // Non-root keys need to be decrypted - key, err := trustmanager.ParsePEMPrivateKey(pemBytes, passphrase) - if err != nil { - rc.Close() - return err - } + // Nonroot keys are preserved without decrypting keyName := strings.TrimPrefix(fNameTrimmed, nonRootKeysPrefix) - newNonRootKeys[keyName] = key + newNonRootKeys[keyName] = fileBytes } else { // This path inside the zip archive doesn't look like a - // root key or a non-root key. To avoid adding a file + // 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. logrus.Warnf("skipping import of key with a path that doesn't begin with %s or %s: %s", rootKeysPrefix, nonRootKeysPrefix, f.Name) @@ -266,8 +240,8 @@ func (km *KeyStoreManager) ImportKeysZip(zipReader zip.Reader, passphrase string } } - for keyName, privKey := range newNonRootKeys { - if err := km.nonRootKeyStore.AddKey(keyName, privKey); err != nil { + for keyName, pemBytes := range newNonRootKeys { + if err := km.nonRootKeyStore.Add(keyName, pemBytes); err != nil { return err } } @@ -275,39 +249,26 @@ func (km *KeyStoreManager) ImportKeysZip(zipReader zip.Reader, passphrase string return nil } -func moveKeysByGUN(oldKeyStore, newKeyStore *trustmanager.KeyFileStore, gun, outputPassphrase string) error { +func moveKeysByGUN(oldKeyStore, newKeyStore *trustmanager.KeyFileStore, gun string) error { // List all files but no symlinks - for _, f := range oldKeyStore.ListFiles(false) { - fullKeyPath := strings.TrimSpace(strings.TrimSuffix(f, filepath.Ext(f))) - relKeyPath := strings.TrimPrefix(fullKeyPath, oldKeyStore.BaseDir()) - relKeyPath = strings.TrimPrefix(relKeyPath, string(filepath.Separator)) + for _, relKeyPath := range oldKeyStore.ListKeys() { // Skip keys that aren't associated with this GUN if !strings.HasPrefix(relKeyPath, filepath.FromSlash(gun)) { continue } - pemBytes, err := oldKeyStore.Get(relKeyPath) + privKey, err := oldKeyStore.GetKey(relKeyPath) if err != nil { return err } - block, _ := pem.Decode(pemBytes) - if block == nil { - return ErrNoValidPrivateKey - } - - if x509.IsEncryptedPEMBlock(block) { - return ErrNonRootKeyEncrypted - } - - // Key is not encrypted. Parse it, and add it - // to the temporary store as an encrypted key. - privKey, err := trustmanager.ParsePEMPrivateKey(pemBytes, "") + alias, err := oldKeyStore.GetKeyAlias(relKeyPath) if err != nil { return err } - err = newKeyStore.AddEncryptedKey(relKeyPath, privKey, outputPassphrase) + + err = newKeyStore.AddKey(relKeyPath, alias, privKey) if err != nil { return err } @@ -317,20 +278,22 @@ func moveKeysByGUN(oldKeyStore, newKeyStore *trustmanager.KeyFileStore, gun, out } // ExportKeysByGUN exports all keys associated with a specified GUN to an -// io.Writer in zip format. outputPassphrase is the new passphrase to use to -// encrypt the keys. If blank, the keys will not be encrypted. -func (km *KeyStoreManager) ExportKeysByGUN(dest io.Writer, gun, outputPassphrase string) error { +// 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 trustmanager.PassphraseRetriever) error { tempBaseDir, err := ioutil.TempDir("", "notary-key-export-") defer os.RemoveAll(tempBaseDir) + privNonRootKeysSubdir := filepath.Join(privDir, nonRootKeysSubdir) + // Create temporary keystore to use as a staging area - tempNonRootKeysPath := filepath.Join(tempBaseDir, privDir, nonRootKeysSubdir) - tempNonRootKeyStore, err := trustmanager.NewKeyFileStore(tempNonRootKeysPath) + tempNonRootKeysPath := filepath.Join(tempBaseDir, privNonRootKeysSubdir) + tempNonRootKeyStore, err := trustmanager.NewKeyFileStore(tempNonRootKeysPath, passphraseRetriever) if err != nil { return err } - if err := moveKeysByGUN(km.nonRootKeyStore, tempNonRootKeyStore, gun, outputPassphrase); err != nil { + if err := moveKeysByGUN(km.nonRootKeyStore, tempNonRootKeyStore, gun); err != nil { return err } @@ -340,7 +303,7 @@ func (km *KeyStoreManager) ExportKeysByGUN(dest io.Writer, gun, outputPassphrase return ErrNoKeysFoundForGUN } - if err := addKeysToArchive(zipWriter, tempNonRootKeyStore, tempBaseDir); err != nil { + if err := addKeysToArchive(zipWriter, tempNonRootKeyStore, privNonRootKeysSubdir); err != nil { return err } diff --git a/keystoremanager/import_export_test.go b/keystoremanager/import_export_test.go index 08dc3cb437..47247f95c9 100644 --- a/keystoremanager/import_export_test.go +++ b/keystoremanager/import_export_test.go @@ -37,10 +37,13 @@ func createTestServer(t *testing.T) (*httptest.Server, *http.ServeMux) { return ts, mux } +var oldPassphrase = "oldPassphrase" +var exportPassphrase = "exportPassphrase" +var oldPassphraseRetriever = func(string, string, bool, int) (string, bool, error) { return oldPassphrase, false, nil } +var newPassphraseRetriever = func(string, string, bool, int) (string, bool, error) { return exportPassphrase, false, nil } + func TestImportExportZip(t *testing.T) { gun := "docker.com/notary" - oldPassphrase := "oldPassphrase" - exportPassphrase := "exportPassphrase" // Temporary directory where test files will be created tempBaseDir, err := ioutil.TempDir("", "notary-test-") @@ -51,13 +54,13 @@ func TestImportExportZip(t *testing.T) { ts, _ := createTestServer(t) defer ts.Close() - repo, err := client.NewNotaryRepository(tempBaseDir, gun, ts.URL, http.DefaultTransport) + 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.String(), oldPassphrase) + rootKeyID, err := repo.KeyStoreManager.GenRootKey(data.ECDSAKey.String()) assert.NoError(t, err, "error generating root key: %s", err) - rootCryptoService, err := repo.KeyStoreManager.GetRootCryptoService(rootKeyID, oldPassphrase) + rootCryptoService, err := repo.KeyStoreManager.GetRootCryptoService(rootKeyID) assert.NoError(t, err, "error retrieving root key: %s", err) err = repo.Initialize(rootCryptoService) @@ -67,7 +70,7 @@ func TestImportExportZip(t *testing.T) { tempZipFilePath := tempZipFile.Name() defer os.Remove(tempZipFilePath) - err = repo.KeyStoreManager.ExportAllKeys(tempZipFile, exportPassphrase) + err = repo.KeyStoreManager.ExportAllKeys(tempZipFile, newPassphraseRetriever) tempZipFile.Close() assert.NoError(t, err) @@ -79,17 +82,20 @@ func TestImportExportZip(t *testing.T) { passphraseByFile := make(map[string]string) // Add non-root keys to the map. These should use the new passphrase - // because they were formerly unencrypted. - privKeyList := repo.KeyStoreManager.NonRootKeyStore().ListFiles(false) + // because the passwords were chosen by the newPassphraseRetriever. + privKeyList := repo.KeyStoreManager.NonRootKeyStore().ListKeys() for _, privKeyName := range privKeyList { - relName := strings.TrimPrefix(privKeyName, tempBaseDir+string(filepath.Separator)) - passphraseByFile[relName] = exportPassphrase + alias, err := repo.KeyStoreManager.NonRootKeyStore().GetKeyAlias(privKeyName) + assert.NoError(t, err, "privKey %s has no alias", privKeyName) + + relKeyPath := filepath.Join("private", "tuf_keys", privKeyName+"_"+alias+".key") + passphraseByFile[relKeyPath] = exportPassphrase } - // Add root key to the map. This will use the old passphrase because it - // won't be reencrypted. - relRootKey := filepath.Join("private", "root_keys", rootCryptoService.ID()+".key") - passphraseByFile[relRootKey] = oldPassphrase + // Add root key to the map. This will use the export passphrase because it + // will be reencrypted. + relRootKey := filepath.Join("private", "root_keys", rootCryptoService.ID()+"_root.key") + passphraseByFile[relRootKey] = exportPassphrase // Iterate through the files in the archive, checking that the files // exist and are encrypted with the expected passphrase. @@ -126,13 +132,13 @@ func TestImportExportZip(t *testing.T) { assert.NoError(t, err, "failed to create a temporary directory: %s", err) - repo2, err := client.NewNotaryRepository(tempBaseDir2, gun, ts.URL, http.DefaultTransport) + 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.String(), "oldPassphrase") + rootKeyID2, err := repo2.KeyStoreManager.GenRootKey(data.ECDSAKey.String()) assert.NoError(t, err, "error generating root key: %s", err) - rootCryptoService2, err := repo2.KeyStoreManager.GetRootCryptoService(rootKeyID2, "oldPassphrase") + rootCryptoService2, err := repo2.KeyStoreManager.GetRootCryptoService(rootKeyID2) assert.NoError(t, err, "error retrieving root key: %s", err) err = repo2.Initialize(rootCryptoService2) @@ -142,44 +148,33 @@ func TestImportExportZip(t *testing.T) { zipReader, err = zip.OpenReader(tempZipFilePath) assert.NoError(t, err, "could not open zip file") - // First try with an incorrect passphrase - err = repo2.KeyStoreManager.ImportKeysZip(zipReader.Reader, "wrongpassphrase") - // Don't use EqualError here because occasionally decrypting with the - // wrong passphrase returns a parse error - assert.Error(t, err) - zipReader.Close() - - // 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, exportPassphrase) + err = repo2.KeyStoreManager.ImportKeysZip(zipReader.Reader) assert.NoError(t, err) zipReader.Close() - // Look for repo's keys in repo2 - // Look for keys in private. The filenames should match the key IDs // in the repo's private key store. for _, privKeyName := range privKeyList { - privKeyRel := strings.TrimPrefix(privKeyName, tempBaseDir) - _, err := os.Stat(filepath.Join(tempBaseDir2, privKeyRel)) + alias, err := repo.KeyStoreManager.NonRootKeyStore().GetKeyAlias(privKeyName) + assert.NoError(t, err, "privKey %s has no alias", privKeyName) + + 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) } // Look for keys in root_keys // There should be a file named after the key ID of the root key we // passed in. - rootKeyFilename := rootCryptoService.ID() + ".key" + rootKeyFilename := rootCryptoService.ID() + "_root.key" _, err = os.Stat(filepath.Join(tempBaseDir2, "private", "root_keys", rootKeyFilename)) assert.NoError(t, err, "missing root key") } func TestImportExportGUN(t *testing.T) { gun := "docker.com/notary" - oldPassphrase := "oldPassphrase" - exportPassphrase := "exportPassphrase" // Temporary directory where test files will be created tempBaseDir, err := ioutil.TempDir("", "notary-test-") @@ -190,13 +185,13 @@ func TestImportExportGUN(t *testing.T) { ts, _ := createTestServer(t) defer ts.Close() - repo, err := client.NewNotaryRepository(tempBaseDir, gun, ts.URL, http.DefaultTransport) + 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.String(), oldPassphrase) + rootKeyID, err := repo.KeyStoreManager.GenRootKey(data.ECDSAKey.String()) assert.NoError(t, err, "error generating root key: %s", err) - rootCryptoService, err := repo.KeyStoreManager.GetRootCryptoService(rootKeyID, oldPassphrase) + rootCryptoService, err := repo.KeyStoreManager.GetRootCryptoService(rootKeyID) assert.NoError(t, err, "error retrieving root key: %s", err) err = repo.Initialize(rootCryptoService) @@ -206,11 +201,11 @@ func TestImportExportGUN(t *testing.T) { tempZipFilePath := tempZipFile.Name() defer os.Remove(tempZipFilePath) - err = repo.KeyStoreManager.ExportKeysByGUN(tempZipFile, gun, exportPassphrase) + err = repo.KeyStoreManager.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", exportPassphrase) + err = repo.KeyStoreManager.ExportKeysByGUN(tempZipFile, "does.not.exist/in/repository", newPassphraseRetriever) assert.EqualError(t, err, keystoremanager.ErrNoKeysFoundForGUN.Error()) tempZipFile.Close() @@ -224,15 +219,21 @@ func TestImportExportGUN(t *testing.T) { // Add keys non-root keys to the map. These should use the new passphrase // because they were formerly unencrypted. - privKeyList := repo.KeyStoreManager.NonRootKeyStore().ListFiles(false) + privKeyList := repo.KeyStoreManager.NonRootKeyStore().ListKeys() for _, privKeyName := range privKeyList { - relName := strings.TrimPrefix(privKeyName, tempBaseDir+string(filepath.Separator)) - passphraseByFile[relName] = exportPassphrase + alias, err := repo.KeyStoreManager.NonRootKeyStore().GetKeyAlias(privKeyName) + if err != nil { + t.Fatalf("privKey %s has no alias", privKeyName) + } + relKeyPath := filepath.Join("private", "tuf_keys", privKeyName+"_"+alias+".key") + + passphraseByFile[relKeyPath] = exportPassphrase } // Iterate through the files in the archive, checking that the files // exist and are encrypted with the expected passphrase. for _, f := range zipReader.File { + expectedPassphrase, present := passphraseByFile[f.Name] if !present { t.Fatalf("unexpected file %s in zip file", f.Name) @@ -265,13 +266,13 @@ func TestImportExportGUN(t *testing.T) { assert.NoError(t, err, "failed to create a temporary directory: %s", err) - repo2, err := client.NewNotaryRepository(tempBaseDir2, gun, ts.URL, http.DefaultTransport) + 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.String(), "oldPassphrase") + rootKeyID2, err := repo2.KeyStoreManager.GenRootKey(data.ECDSAKey.String()) assert.NoError(t, err, "error generating root key: %s", err) - rootCryptoService2, err := repo2.KeyStoreManager.GetRootCryptoService(rootKeyID2, "oldPassphrase") + rootCryptoService2, err := repo2.KeyStoreManager.GetRootCryptoService(rootKeyID2) assert.NoError(t, err, "error retrieving root key: %s", err) err = repo2.Initialize(rootCryptoService2) @@ -281,36 +282,26 @@ func TestImportExportGUN(t *testing.T) { zipReader, err = zip.OpenReader(tempZipFilePath) assert.NoError(t, err, "could not open zip file") - // First try with an incorrect passphrase - err = repo2.KeyStoreManager.ImportKeysZip(zipReader.Reader, "wrongpassphrase") - // Don't use EqualError here because occasionally decrypting with the - // wrong passphrase returns a parse error - assert.Error(t, err) - zipReader.Close() - - // 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, exportPassphrase) + err = repo2.KeyStoreManager.ImportKeysZip(zipReader.Reader) assert.NoError(t, err) zipReader.Close() - // Look for repo's non-root keys in repo2 - // Look for keys in private. The filenames should match the key IDs // in the repo's private key store. for _, privKeyName := range privKeyList { - privKeyRel := strings.TrimPrefix(privKeyName, tempBaseDir) - _, err := os.Stat(filepath.Join(tempBaseDir2, privKeyRel)) - assert.NoError(t, err, "missing private key: %s", privKeyName) + alias, err := repo.KeyStoreManager.NonRootKeyStore().GetKeyAlias(privKeyName) + if err != nil { + t.Fatalf("privKey %s has no alias", privKeyName) + } + relKeyPath := filepath.Join("private", "tuf_keys", privKeyName+"_"+alias+".key") + privKeyFileName := filepath.Join(tempBaseDir2, relKeyPath) + _, err = os.Stat(privKeyFileName) } } func TestImportExportRootKey(t *testing.T) { gun := "docker.com/notary" - oldPassphrase := "oldPassphrase" // Temporary directory where test files will be created tempBaseDir, err := ioutil.TempDir("", "notary-test-") @@ -321,13 +312,13 @@ func TestImportExportRootKey(t *testing.T) { ts, _ := createTestServer(t) defer ts.Close() - repo, err := client.NewNotaryRepository(tempBaseDir, gun, ts.URL, http.DefaultTransport) + 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.String(), oldPassphrase) + rootKeyID, err := repo.KeyStoreManager.GenRootKey(data.ECDSAKey.String()) assert.NoError(t, err, "error generating root key: %s", err) - rootCryptoService, err := repo.KeyStoreManager.GetRootCryptoService(rootKeyID, oldPassphrase) + rootCryptoService, err := repo.KeyStoreManager.GetRootCryptoService(rootKeyID) assert.NoError(t, err, "error retrieving root key: %s", err) err = repo.Initialize(rootCryptoService) @@ -347,13 +338,13 @@ func TestImportExportRootKey(t *testing.T) { assert.NoError(t, err, "failed to create a temporary directory: %s", err) - repo2, err := client.NewNotaryRepository(tempBaseDir2, gun, ts.URL, http.DefaultTransport) + 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.String(), oldPassphrase) + rootKeyID2, err := repo2.KeyStoreManager.GenRootKey(data.ECDSAKey.String()) assert.NoError(t, err, "error generating root key: %s", err) - rootCryptoService2, err := repo2.KeyStoreManager.GetRootCryptoService(rootKeyID2, oldPassphrase) + rootCryptoService2, err := repo2.KeyStoreManager.GetRootCryptoService(rootKeyID2) assert.NoError(t, err, "error retrieving root key: %s", err) err = repo2.Initialize(rootCryptoService2) @@ -370,7 +361,7 @@ func TestImportExportRootKey(t *testing.T) { // Look for repo's root key in repo2 // There should be a file named after the key ID of the root key we // imported. - rootKeyFilename := rootKeyID + ".key" + rootKeyFilename := rootKeyID + "_root.key" _, err = os.Stat(filepath.Join(tempBaseDir2, "private", "root_keys", rootKeyFilename)) assert.NoError(t, err, "missing root key") diff --git a/keystoremanager/keystoremanager.go b/keystoremanager/keystoremanager.go index 70abf1c33a..3c8da66c2f 100644 --- a/keystoremanager/keystoremanager.go +++ b/keystoremanager/keystoremanager.go @@ -60,16 +60,16 @@ func (err ErrRootRotationFail) Error() string { // NewKeyStoreManager returns an initialized KeyStoreManager, or an error // if it fails to create the KeyFileStores or load certificates -func NewKeyStoreManager(baseDir string) (*KeyStoreManager, error) { +func NewKeyStoreManager(baseDir string, passphraseRetriever trustmanager.PassphraseRetriever) (*KeyStoreManager, error) { nonRootKeysPath := filepath.Join(baseDir, privDir, nonRootKeysSubdir) - nonRootKeyStore, err := trustmanager.NewKeyFileStore(nonRootKeysPath) + nonRootKeyStore, err := trustmanager.NewKeyFileStore(nonRootKeysPath, passphraseRetriever) if err != nil { return nil, err } // Load the keystore that will hold all of our encrypted Root Private Keys rootKeysPath := filepath.Join(baseDir, privDir, rootKeysSubdir) - rootKeyStore, err := trustmanager.NewKeyFileStore(rootKeysPath) + rootKeyStore, err := trustmanager.NewKeyFileStore(rootKeysPath, passphraseRetriever) if err != nil { return nil, err } @@ -142,8 +142,8 @@ func (km *KeyStoreManager) AddTrustedCACert(cert *x509.Certificate) { km.trustedCAStore.AddCert(cert) } -// GenRootKey generates a new root key protected by a given passphrase -func (km *KeyStoreManager) GenRootKey(algorithm, passphrase string) (string, error) { +// GenRootKey generates a new root key +func (km *KeyStoreManager) GenRootKey(algorithm string) (string, error) { var err error var privKey data.PrivateKey @@ -164,19 +164,20 @@ func (km *KeyStoreManager) GenRootKey(algorithm, passphrase string) (string, err } // Changing the root - km.rootKeyStore.AddEncryptedKey(privKey.ID(), privKey, passphrase) + km.rootKeyStore.AddKey(privKey.ID(), "root", privKey) return privKey.ID(), nil } -// GetRootCryptoService retreives a root key and a cryptoservice to use with it -func (km *KeyStoreManager) GetRootCryptoService(rootKeyID, passphrase string) (*cryptoservice.UnlockedCryptoService, error) { - privKey, err := km.rootKeyStore.GetDecryptedKey(rootKeyID, passphrase) +// GetRootCryptoService retrieves a root key and a cryptoservice to use with it +// TODO(mccauley): remove this as its no longer needed once we have key caching in the keystores +func (km *KeyStoreManager) GetRootCryptoService(rootKeyID string) (*cryptoservice.UnlockedCryptoService, error) { + privKey, err := km.rootKeyStore.GetKey(rootKeyID) if err != nil { return nil, fmt.Errorf("could not get decrypted root key with keyID: %s, %v", rootKeyID, err) } - cryptoService := cryptoservice.NewCryptoService("", km.rootKeyStore, passphrase) + cryptoService := cryptoservice.NewCryptoService("", km.rootKeyStore) return cryptoservice.NewUnlockedCryptoService(privKey, cryptoService), nil } diff --git a/signer/api/api_test.go b/signer/api/api_test.go index 8d5cfb5bb0..fb70be6800 100644 --- a/signer/api/api_test.go +++ b/signer/api/api_test.go @@ -23,12 +23,13 @@ import ( ) var ( - server *httptest.Server - reader io.Reader - deleteKeyBaseURL string - createKeyBaseURL string - keyInfoBaseURL string - signBaseURL string + server *httptest.Server + reader io.Reader + deleteKeyBaseURL string + createKeyBaseURL string + keyInfoBaseURL string + signBaseURL string + passphraseRetriever = func(string, string, bool, int) (string, bool, error) { return "passphrase", false, nil } ) func SetupHSMEnv(t *testing.T) (*pkcs11.Ctx, pkcs11.SessionHandle) { @@ -73,8 +74,8 @@ func setup(cryptoServices signer.CryptoServiceIndex) { } func TestDeleteKeyHandlerReturns404WithNonexistentKey(t *testing.T) { - keyStore := trustmanager.NewKeyMemoryStore() - cryptoService := cryptoservice.NewCryptoService("", keyStore, "") + keyStore := trustmanager.NewKeyMemoryStore(passphraseRetriever) + cryptoService := cryptoservice.NewCryptoService("", keyStore) setup(signer.CryptoServiceIndex{data.ED25519Key: cryptoService, data.RSAKey: cryptoService, data.ECDSAKey: cryptoService}) fakeID := "c62e6d68851cef1f7e55a9d56e3b0c05f3359f16838cad43600f0554e7d3b54d" @@ -93,11 +94,12 @@ func TestDeleteKeyHandlerReturns404WithNonexistentKey(t *testing.T) { } func TestDeleteKeyHandler(t *testing.T) { - keyStore := trustmanager.NewKeyMemoryStore() - cryptoService := cryptoservice.NewCryptoService("", keyStore, "") + keyStore := trustmanager.NewKeyMemoryStore(passphraseRetriever) + cryptoService := cryptoservice.NewCryptoService("", keyStore) setup(signer.CryptoServiceIndex{data.ED25519Key: cryptoService, data.RSAKey: cryptoService, data.ECDSAKey: cryptoService}) tufKey, _ := cryptoService.Create("", data.ED25519Key) + assert.NotNil(t, tufKey) requestJson, _ := json.Marshal(&pb.KeyID{ID: tufKey.ID()}) reader = strings.NewReader(string(requestJson)) @@ -112,11 +114,12 @@ func TestDeleteKeyHandler(t *testing.T) { } func TestKeyInfoHandler(t *testing.T) { - keyStore := trustmanager.NewKeyMemoryStore() - cryptoService := cryptoservice.NewCryptoService("", keyStore, "") + keyStore := trustmanager.NewKeyMemoryStore(passphraseRetriever) + cryptoService := cryptoservice.NewCryptoService("", keyStore) setup(signer.CryptoServiceIndex{data.ED25519Key: cryptoService, data.RSAKey: cryptoService, data.ECDSAKey: cryptoService}) tufKey, _ := cryptoService.Create("", data.ED25519Key) + assert.NotNil(t, tufKey) keyInfoURL := fmt.Sprintf("%s/%s", keyInfoBaseURL, tufKey.ID()) @@ -140,8 +143,8 @@ func TestKeyInfoHandler(t *testing.T) { func TestKeyInfoHandlerReturns404WithNonexistentKey(t *testing.T) { // We associate both key types with this signing service to bypass the // ID -> keyType logic in the tests - keyStore := trustmanager.NewKeyMemoryStore() - cryptoService := cryptoservice.NewCryptoService("", keyStore, "") + keyStore := trustmanager.NewKeyMemoryStore(passphraseRetriever) + cryptoService := cryptoservice.NewCryptoService("", keyStore) setup(signer.CryptoServiceIndex{data.ED25519Key: cryptoService, data.RSAKey: cryptoService, data.ECDSAKey: cryptoService}) fakeID := "c62e6d68851cef1f7e55a9d56e3b0c05f3359f16838cad43600f0554e7d3b54d" @@ -185,8 +188,8 @@ func TestHSMCreateKeyHandler(t *testing.T) { } func TestSoftwareCreateKeyHandler(t *testing.T) { - keyStore := trustmanager.NewKeyMemoryStore() - cryptoService := cryptoservice.NewCryptoService("", keyStore, "") + keyStore := trustmanager.NewKeyMemoryStore(passphraseRetriever) + cryptoService := cryptoservice.NewCryptoService("", keyStore) setup(signer.CryptoServiceIndex{data.ED25519Key: cryptoService, data.RSAKey: cryptoService, data.ECDSAKey: cryptoService}) createKeyURL := fmt.Sprintf("%s/%s", createKeyBaseURL, data.ED25519Key) @@ -243,8 +246,8 @@ func TestHSMSignHandler(t *testing.T) { } func TestSoftwareSignHandler(t *testing.T) { - keyStore := trustmanager.NewKeyMemoryStore() - cryptoService := cryptoservice.NewCryptoService("", keyStore, "") + keyStore := trustmanager.NewKeyMemoryStore(passphraseRetriever) + cryptoService := cryptoservice.NewCryptoService("", keyStore) setup(signer.CryptoServiceIndex{data.ED25519Key: cryptoService, data.RSAKey: cryptoService, data.ECDSAKey: cryptoService}) tufKey, err := cryptoService.Create("", data.ED25519Key) @@ -275,8 +278,8 @@ func TestSoftwareSignHandler(t *testing.T) { } func TestSoftwareSignWithInvalidRequestHandler(t *testing.T) { - keyStore := trustmanager.NewKeyMemoryStore() - cryptoService := cryptoservice.NewCryptoService("", keyStore, "") + keyStore := trustmanager.NewKeyMemoryStore(passphraseRetriever) + cryptoService := cryptoservice.NewCryptoService("", keyStore) setup(signer.CryptoServiceIndex{data.ED25519Key: cryptoService, data.RSAKey: cryptoService, data.ECDSAKey: cryptoService}) requestJson := "{\"blob\":\"7d16f1d0b95310a7bc557747fc4f20fcd41c1c5095ae42f189df0717e7d7f4a0a2b55debce630f43c4ac099769c612965e3fda3cd4c0078ee6a460f14fa19307\"}" @@ -299,8 +302,8 @@ func TestSoftwareSignWithInvalidRequestHandler(t *testing.T) { } func TestSignHandlerReturns404WithNonexistentKey(t *testing.T) { - keyStore := trustmanager.NewKeyMemoryStore() - cryptoService := cryptoservice.NewCryptoService("", keyStore, "") + keyStore := trustmanager.NewKeyMemoryStore(passphraseRetriever) + cryptoService := cryptoservice.NewCryptoService("", keyStore) setup(signer.CryptoServiceIndex{data.ED25519Key: cryptoService, data.RSAKey: cryptoService, data.ECDSAKey: cryptoService}) fakeID := "c62e6d68851cef1f7e55a9d56e3b0c05f3359f16838cad43600f0554e7d3b54d" diff --git a/signer/api/rpc_api_test.go b/signer/api/rpc_api_test.go index 0c51dbda13..042e415f15 100644 --- a/signer/api/rpc_api_test.go +++ b/signer/api/rpc_api_test.go @@ -24,11 +24,13 @@ var ( sClient pb.SignerClient grpcServer *grpc.Server void *pb.Void + pr trustmanager.PassphraseRetriever ) func init() { - keyStore := trustmanager.NewKeyMemoryStore() - cryptoService := cryptoservice.NewCryptoService("", keyStore, "") + pr = func(string, string, bool, int) (string, bool, error) { return "passphrase", false, nil } + keyStore := trustmanager.NewKeyMemoryStore(pr) + cryptoService := cryptoservice.NewCryptoService("", keyStore) cryptoServices := signer.CryptoServiceIndex{data.ED25519Key: cryptoService, data.RSAKey: cryptoService, data.ECDSAKey: cryptoService} void = &pb.Void{} //server setup diff --git a/trustmanager/filestore.go b/trustmanager/filestore.go index a304cb38a9..d573888d6a 100644 --- a/trustmanager/filestore.go +++ b/trustmanager/filestore.go @@ -172,6 +172,11 @@ func (f *SimpleFileStore) list(path string, symlinks bool) []string { matched, _ := filepath.Match("*"+f.fileExt, fi.Name()) if matched { + // Find the relative path for this file relative to the base path. + fp, err = filepath.Rel(path, fp) + if err != nil { + return err + } files = append(files, fp) } return nil diff --git a/trustmanager/keyfilestore.go b/trustmanager/keyfilestore.go index 65d2837026..b4a6080b57 100644 --- a/trustmanager/keyfilestore.go +++ b/trustmanager/keyfilestore.go @@ -4,6 +4,8 @@ import ( "path/filepath" "strings" + "errors" + "fmt" "github.com/endophage/gotuf/data" ) @@ -15,53 +17,55 @@ const ( type KeyStore interface { LimitedFileStore - AddKey(name string, privKey data.PrivateKey) error + AddKey(name, alias string, privKey data.PrivateKey) error GetKey(name string) (data.PrivateKey, error) - AddEncryptedKey(name string, privKey data.PrivateKey, passphrase string) error - GetDecryptedKey(name string, passphrase string) (data.PrivateKey, error) + GetKeyAlias(name string) (string, error) ListKeys() []string + RemoveKey(name string) error } +// PassphraseRetriever is a callback function that should retrieve a passphrase +// for a given named key. If it should be treated as new passphrase (e.g. with +// confirmation), createNew will be true. Attempts is passed in so that implementers +// decide how many chances to give to a human, for example. +type PassphraseRetriever func(keyId, alias string, createNew bool, attempts int) (passphrase string, giveup bool, err error) + // KeyFileStore persists and manages private keys on disk type KeyFileStore struct { SimpleFileStore + PassphraseRetriever } // KeyMemoryStore manages private keys in memory type KeyMemoryStore struct { MemoryFileStore + PassphraseRetriever } // NewKeyFileStore returns a new KeyFileStore creating a private directory to // hold the keys. -func NewKeyFileStore(baseDir string) (*KeyFileStore, error) { +func NewKeyFileStore(baseDir string, passphraseRetriever PassphraseRetriever) (*KeyFileStore, error) { fileStore, err := NewPrivateSimpleFileStore(baseDir, keyExtension) if err != nil { return nil, err } - return &KeyFileStore{*fileStore}, nil + return &KeyFileStore{*fileStore, passphraseRetriever}, nil } // AddKey stores the contents of a PEM-encoded private key as a PEM block -func (s *KeyFileStore) AddKey(name string, privKey data.PrivateKey) error { - return addKey(s, name, privKey) +func (s *KeyFileStore) AddKey(name, alias string, privKey data.PrivateKey) error { + return addKey(s, s.PassphraseRetriever, name, alias, privKey) } // GetKey returns the PrivateKey given a KeyID func (s *KeyFileStore) GetKey(name string) (data.PrivateKey, error) { - return getKey(s, name) + return getKey(s, s.PassphraseRetriever, name) } -// AddEncryptedKey stores the contents of a PEM-encoded private key as an encrypted PEM block -func (s *KeyFileStore) AddEncryptedKey(name string, privKey data.PrivateKey, passphrase string) error { - return addEncryptedKey(s, name, privKey, passphrase) -} - -// GetDecryptedKey decrypts and returns the PEM Encoded private key given a flename -// and a passphrase -func (s *KeyFileStore) GetDecryptedKey(name string, passphrase string) (data.PrivateKey, error) { - return getDecryptedKey(s, name, passphrase) +// GetKeyAlias returns the PrivateKey's alias given a KeyID +func (s *KeyFileStore) GetKeyAlias(name string) (string, error) { + return getKeyAlias(s, name) } // ListKeys returns a list of unique PublicKeys present on the KeyFileStore. @@ -71,32 +75,31 @@ func (s *KeyFileStore) ListKeys() []string { return listKeys(s) } +// RemoveKey removes the key from the keyfilestore +func (s *KeyFileStore) RemoveKey(name string) error { + return removeKey(s, name) +} + // NewKeyMemoryStore returns a new KeyMemoryStore which holds keys in memory -func NewKeyMemoryStore() *KeyMemoryStore { +func NewKeyMemoryStore(passphraseRetriever PassphraseRetriever) *KeyMemoryStore { memStore := NewMemoryFileStore() - return &KeyMemoryStore{*memStore} + return &KeyMemoryStore{*memStore, passphraseRetriever} } // AddKey stores the contents of a PEM-encoded private key as a PEM block -func (s *KeyMemoryStore) AddKey(name string, privKey data.PrivateKey) error { - return addKey(s, name, privKey) +func (s *KeyMemoryStore) AddKey(name, alias string, privKey data.PrivateKey) error { + return addKey(s, s.PassphraseRetriever, name, alias, privKey) } // GetKey returns the PrivateKey given a KeyID func (s *KeyMemoryStore) GetKey(name string) (data.PrivateKey, error) { - return getKey(s, name) + return getKey(s, s.PassphraseRetriever, name) } -// AddEncryptedKey stores the contents of a PEM-encoded private key as an encrypted PEM block -func (s *KeyMemoryStore) AddEncryptedKey(name string, privKey data.PrivateKey, passphrase string) error { - return addEncryptedKey(s, name, privKey, passphrase) -} - -// GetDecryptedKey decrypts and returns the PEM Encoded private key given a flename -// and a passphrase -func (s *KeyMemoryStore) GetDecryptedKey(name string, passphrase string) (data.PrivateKey, error) { - return getDecryptedKey(s, name, passphrase) +// GetKeyAlias returns the PrivateKey's alias given a KeyID +func (s *KeyMemoryStore) GetKeyAlias(name string) (string, error) { + return getKeyAlias(s, name) } // ListKeys returns a list of unique PublicKeys present on the KeyFileStore. @@ -106,64 +109,119 @@ func (s *KeyMemoryStore) ListKeys() []string { return listKeys(s) } -func addKey(s LimitedFileStore, name string, privKey data.PrivateKey) error { +// RemoveKey removes the key from the keystore +func (s *KeyMemoryStore) RemoveKey(name string) error { + return removeKey(s, name) +} + +func addKey(s LimitedFileStore, passphraseRetriever PassphraseRetriever, name, alias string, privKey data.PrivateKey) error { pemPrivKey, err := KeyToPEM(privKey) if err != nil { return err } - return s.Add(name, pemPrivKey) + attempts := 0 + passphrase := "" + giveup := false + for { + passphrase, giveup, err = passphraseRetriever(name, alias, true, attempts) + if err != nil { + attempts++ + continue + } + if giveup { + return errors.New("obtaining passphrase failed") + } + if attempts > 10 { + return errors.New("maximum number of passphrase attempts exceeded") + } + break + } + + if passphrase != "" { + pemPrivKey, err = EncryptPrivateKey(privKey, passphrase) + if err != nil { + return err + } + } + + return s.Add(name+"_"+alias, pemPrivKey) } -func getKey(s LimitedFileStore, name string) (data.PrivateKey, error) { - keyBytes, err := s.Get(name) +func getKeyAlias(s LimitedFileStore, keyID string) (string, error) { + files := s.ListFiles(true) + name := strings.TrimSpace(strings.TrimSuffix(filepath.Base(keyID), filepath.Ext(keyID))) + + for _, file := range files { + filename := filepath.Base(file) + + if strings.HasPrefix(filename, name) { + aliasPlusDotKey := strings.TrimPrefix(filename, name+"_") + retVal := strings.TrimSuffix(aliasPlusDotKey, "."+keyExtension) + return retVal, nil + } + } + + return "", fmt.Errorf("keyId %s has no alias", name) +} + +// GetKey returns the PrivateKey given a KeyID +func getKey(s LimitedFileStore, passphraseRetriever PassphraseRetriever, name string) (data.PrivateKey, error) { + keyAlias, err := getKeyAlias(s, name) if err != nil { return nil, err } - // Convert PEM encoded bytes back to a PrivateKey + keyBytes, err := s.Get(name + "_" + keyAlias) + if err != nil { + return nil, err + } + + // See if the key is encrypted. If its encrypted we'll fail to parse the private key privKey, err := ParsePEMPrivateKey(keyBytes, "") if err != nil { - return nil, err - } - - return privKey, nil -} - -func addEncryptedKey(s LimitedFileStore, name string, privKey data.PrivateKey, passphrase string) error { - encryptedPrivKey, err := EncryptPrivateKey(privKey, passphrase) - if err != nil { - return err - } - - return s.Add(name, encryptedPrivKey) -} - -func getDecryptedKey(s LimitedFileStore, name string, passphrase string) (data.PrivateKey, error) { - keyBytes, err := s.Get(name) - if err != nil { - return nil, err - } - - // Gets an unencrypted PrivateKey. - privKey, err := ParsePEMPrivateKey(keyBytes, passphrase) - if err != nil { - return nil, err - } + // We need to decrypt the key, lets get a passphrase + for attempts := 0; ; attempts++ { + passphrase, giveup, err := passphraseRetriever(name, string(keyAlias), false, attempts) + // Check if the passphrase retriever got an error or if it is telling us to give up + if giveup || err != nil { + return nil, errors.New("obtaining passphrase failed") + } + if attempts > 10 { + return nil, errors.New("maximum number of passphrase attempts exceeded") + } + // Try to convert PEM encoded bytes back to a PrivateKey using the passphrase + privKey, err = ParsePEMPrivateKey(keyBytes, passphrase) + if err == nil { + // We managed to parse the PrivateKey. We've succeeded! + break + } + } + } return privKey, nil } +// ListKeys returns a list of unique PublicKeys present on the KeyFileStore. +// There might be symlinks associating Certificate IDs to Public Keys, so this +// method only returns the IDs that aren't symlinks func listKeys(s LimitedFileStore) []string { var keyIDList []string + for _, f := range s.ListFiles(false) { - keyID := strings.TrimSpace(strings.TrimSuffix(filepath.Base(f), filepath.Ext(f))) + keyID := strings.TrimSpace(strings.TrimSuffix(f, filepath.Ext(f))) + keyID = keyID[:strings.LastIndex(keyID, "_")] keyIDList = append(keyIDList, keyID) } return keyIDList } // RemoveKey removes the key from the keyfilestore -func (s *KeyFileStore) RemoveKey(name string) error { - return s.Remove(name) +func removeKey(s LimitedFileStore, name string) error { + keyAlias, err := getKeyAlias(s, name) + if err != nil { + return err + } + + return s.Remove(name + "_" + keyAlias) } diff --git a/trustmanager/keyfilestore_test.go b/trustmanager/keyfilestore_test.go index a40c810fe7..a04f026183 100644 --- a/trustmanager/keyfilestore_test.go +++ b/trustmanager/keyfilestore_test.go @@ -3,6 +3,7 @@ package trustmanager import ( "bytes" "crypto/rand" + "errors" "io/ioutil" "os" "path/filepath" @@ -10,9 +11,18 @@ import ( "testing" ) +var passphraseRetriever = func(keyID string, alias string, createNew bool, numAttempts int) (string, bool, error) { + if numAttempts > 5 { + giveup := true + return "", giveup, errors.New("passPhraseRetriever failed after too many requests") + } + return "passphrase", false, nil +} + func TestAddKey(t *testing.T) { testName := "docker.com/notary/root" testExt := "key" + testAlias := "root" // Temporary directory where test files will be created tempBaseDir, err := ioutil.TempDir("", "notary-test-") @@ -22,10 +32,10 @@ func TestAddKey(t *testing.T) { defer os.RemoveAll(tempBaseDir) // Since we're generating this manually we need to add the extension '.' - expectedFilePath := filepath.Join(tempBaseDir, testName+"."+testExt) + expectedFilePath := filepath.Join(tempBaseDir, testName+"_"+testAlias+"."+testExt) // Create our store - store, err := NewKeyFileStore(tempBaseDir) + store, err := NewKeyFileStore(tempBaseDir, passphraseRetriever) if err != nil { t.Fatalf("failed to create new key filestore: %v", err) } @@ -36,7 +46,7 @@ func TestAddKey(t *testing.T) { } // Call the AddKey function - err = store.AddKey(testName, privKey) + err = store.AddKey(testName, "root", privKey) if err != nil { t.Fatalf("failed to add file to store: %v", err) } @@ -83,8 +93,11 @@ EMl3eFOJXjIch/wIesRSN+2dGOsl7neercjMh1i9RvpCwHDx/E0= `) testName := "docker.com/notary/root" testExt := "key" + testAlias := "root" perms := os.FileMode(0755) + emptyPassphraseRetriever := func(string, string, bool, int) (string, bool, error) { return "", false, nil } + // Temporary directory where test files will be created tempBaseDir, err := ioutil.TempDir("", "notary-test-") if err != nil { @@ -93,7 +106,7 @@ EMl3eFOJXjIch/wIesRSN+2dGOsl7neercjMh1i9RvpCwHDx/E0= defer os.RemoveAll(tempBaseDir) // Since we're generating this manually we need to add the extension '.' - filePath := filepath.Join(tempBaseDir, testName+"."+testExt) + filePath := filepath.Join(tempBaseDir, testName+"_"+testAlias+"."+testExt) os.MkdirAll(filepath.Dir(filePath), perms) if err = ioutil.WriteFile(filePath, testData, perms); err != nil { @@ -101,7 +114,7 @@ EMl3eFOJXjIch/wIesRSN+2dGOsl7neercjMh1i9RvpCwHDx/E0= } // Create our store - store, err := NewKeyFileStore(tempBaseDir) + store, err := NewKeyFileStore(tempBaseDir, emptyPassphraseRetriever) if err != nil { t.Fatalf("failed to create new key filestore: %v", err) } @@ -124,9 +137,10 @@ EMl3eFOJXjIch/wIesRSN+2dGOsl7neercjMh1i9RvpCwHDx/E0= func TestAddGetKeyMemStore(t *testing.T) { testName := "docker.com/notary/root" + testAlias := "root" // Create our store - store := NewKeyMemoryStore() + store := NewKeyMemoryStore(passphraseRetriever) privKey, err := GenerateRSAKey(rand.Reader, 512) if err != nil { @@ -134,7 +148,7 @@ func TestAddGetKeyMemStore(t *testing.T) { } // Call the AddKey function - err = store.AddKey(testName, privKey) + err = store.AddKey(testName, testAlias, privKey) if err != nil { t.Fatalf("failed to add file to store: %v", err) } @@ -145,62 +159,24 @@ func TestAddGetKeyMemStore(t *testing.T) { t.Fatalf("failed to get key from store: %v", err) } + // Check to see if alias exists + retrievedAlias, err := store.GetKeyAlias(testName) + if err != nil { + t.Fatalf("failed to get key from store: %v", err) + } + + if retrievedAlias != testAlias { + t.Fatalf("retrievedAlias differs getAlias") + } + if !bytes.Equal(retrievedKey.Public(), privKey.Public()) || !bytes.Equal(retrievedKey.Private(), privKey.Private()) { t.Fatalf("key contents differs after add/get") } } - -func TestAddEncryptedAndGetDecrypted(t *testing.T) { - testExt := "key" - - // Temporary directory where test files will be created - tempBaseDir, err := ioutil.TempDir("", "notary-test-") - if err != nil { - t.Fatalf("failed to create a temporary directory: %v", err) - } - defer os.RemoveAll(tempBaseDir) - - // Create our FileStore - store, err := NewKeyFileStore(tempBaseDir) - if err != nil { - t.Fatalf("failed to create new key filestore: %v", err) - } - - // Generate new PrivateKey - privKey, err := GenerateRSAKey(rand.Reader, 512) - if err != nil { - t.Fatalf("could not generate private key: %v", err) - } - - // Call the AddEncryptedKey function - err = store.AddEncryptedKey(privKey.ID(), privKey, "diogomonica") - if err != nil { - t.Fatalf("failed to add file to store: %v", err) - } - - // Since we're generating this manually we need to add the extension '.' - expectedFilePath := filepath.Join(tempBaseDir, privKey.ID()+"."+testExt) - - // Check to see if file exists - _, err = ioutil.ReadFile(expectedFilePath) - if err != nil { - t.Fatalf("expected file not found: %v", err) - } - - // Call the GetDecryptedKey function - readPrivKey, err := store.GetDecryptedKey(privKey.ID(), "diogomonica") - if err != nil { - t.Fatalf("could not decrypt private key: %v", err) - } - - if !bytes.Equal(privKey.Private(), readPrivKey.Private()) { - t.Fatalf("written key and loaded key do not match") - } -} - func TestGetDecryptedWithTamperedCipherText(t *testing.T) { testExt := "key" + testAlias := "root" // Temporary directory where test files will be created tempBaseDir, err := ioutil.TempDir("", "notary-test-") @@ -210,7 +186,7 @@ func TestGetDecryptedWithTamperedCipherText(t *testing.T) { defer os.RemoveAll(tempBaseDir) // Create our FileStore - store, err := NewKeyFileStore(tempBaseDir) + store, err := NewKeyFileStore(tempBaseDir, passphraseRetriever) if err != nil { t.Fatalf("failed to create new key filestore: %v", err) } @@ -222,13 +198,13 @@ func TestGetDecryptedWithTamperedCipherText(t *testing.T) { } // Call the AddEncryptedKey function - err = store.AddEncryptedKey(privKey.ID(), privKey, "diogomonica") + err = store.AddKey(privKey.ID(), testAlias, privKey) if err != nil { t.Fatalf("failed to add file to store: %v", err) } // Since we're generating this manually we need to add the extension '.' - expectedFilePath := filepath.Join(tempBaseDir, privKey.ID()+"."+testExt) + expectedFilePath := filepath.Join(tempBaseDir, privKey.ID()+"_"+testAlias+"."+testExt) // Get file description, open file fp, err := os.OpenFile(expectedFilePath, os.O_WRONLY, 0600) @@ -240,13 +216,26 @@ func TestGetDecryptedWithTamperedCipherText(t *testing.T) { fp.WriteAt([]byte("a"), int64(1)) // Try to decrypt the file - _, err = store.GetDecryptedKey(privKey.ID(), "diogomonica") + _, err = store.GetKey(privKey.ID()) if err == nil { t.Fatalf("expected error while decrypting the content due to invalid cipher text") } } func TestGetDecryptedWithInvalidPassphrase(t *testing.T) { + + // Make a passphraseRetriever that always returns a different passphrase in order to test + // decryption failure + a := "a" + var invalidPassphraseRetriever = func(keyId string, alias string, createNew bool, numAttempts int) (string, bool, error) { + if numAttempts > 5 { + giveup := true + return "", giveup, nil + } + a = a + a + return a, false, nil + } + // Temporary directory where test files will be created tempBaseDir, err := ioutil.TempDir("", "notary-test-") if err != nil { @@ -255,14 +244,15 @@ func TestGetDecryptedWithInvalidPassphrase(t *testing.T) { defer os.RemoveAll(tempBaseDir) // Test with KeyFileStore - fileStore, err := NewKeyFileStore(tempBaseDir) + fileStore, err := NewKeyFileStore(tempBaseDir, invalidPassphraseRetriever) if err != nil { t.Fatalf("failed to create new key filestore: %v", err) } + testGetDecryptedWithInvalidPassphrase(t, fileStore) // Test with KeyMemoryStore - memStore := NewKeyMemoryStore() + memStore := NewKeyMemoryStore(invalidPassphraseRetriever) if err != nil { t.Fatalf("failed to create new key memorystore: %v", err) } @@ -270,8 +260,40 @@ func TestGetDecryptedWithInvalidPassphrase(t *testing.T) { } +func TestGetDecryptedWithConsistentlyInvalidPassphrase(t *testing.T) { + // Make a passphraseRetriever that always returns a different passphrase in order to test + // decryption failure + a := "aaaaaaaaaaaaa" + var consistentlyInvalidPassphraseRetriever = func(keyID string, alias string, createNew bool, numAttempts int) (string, bool, error) { + a = a + "a" + return a, false, nil + } + + // Temporary directory where test files will be created + tempBaseDir, err := ioutil.TempDir("", "notary-test-") + if err != nil { + t.Fatalf("failed to create a temporary directory: %v", err) + } + defer os.RemoveAll(tempBaseDir) + + // Test with KeyFileStore + fileStore, err := NewKeyFileStore(tempBaseDir, consistentlyInvalidPassphraseRetriever) + if err != nil { + t.Fatalf("failed to create new key filestore: %v", err) + } + + testGetDecryptedWithInvalidPassphrase(t, fileStore) + + // Test with KeyMemoryStore + memStore := NewKeyMemoryStore(consistentlyInvalidPassphraseRetriever) + if err != nil { + t.Fatalf("failed to create new key memorystore: %v", err) + } + testGetDecryptedWithInvalidPassphrase(t, memStore) +} + func testGetDecryptedWithInvalidPassphrase(t *testing.T, store KeyStore) { - testName := "docker.com/notary/root" + testAlias := "root" // Generate a new random RSA Key privKey, err := GenerateRSAKey(rand.Reader, 512) @@ -279,14 +301,14 @@ func testGetDecryptedWithInvalidPassphrase(t *testing.T, store KeyStore) { t.Fatalf("could not generate private key: %v", err) } - // Call the AddEncryptedKey function - err = store.AddEncryptedKey(privKey.ID(), privKey, "diogomonica") + // Call the AddKey function + err = store.AddKey(privKey.ID(), testAlias, privKey) if err != nil { - t.Fatalf("failed to add file to stoAFre: %v", err) + t.Fatalf("failed to add file to store: %v", err) } // Try to decrypt the file with an invalid passphrase - _, err = store.GetDecryptedKey(testName, "diegomonica") + _, err = store.GetKey(privKey.ID()) if err == nil { t.Fatalf("expected error while decrypting the content due to invalid passphrase") } @@ -295,6 +317,7 @@ func testGetDecryptedWithInvalidPassphrase(t *testing.T, store KeyStore) { func TestRemoveKey(t *testing.T) { testName := "docker.com/notary/root" testExt := "key" + testAlias := "alias" // Temporary directory where test files will be created tempBaseDir, err := ioutil.TempDir("", "notary-test-") @@ -304,10 +327,10 @@ func TestRemoveKey(t *testing.T) { defer os.RemoveAll(tempBaseDir) // Since we're generating this manually we need to add the extension '.' - expectedFilePath := filepath.Join(tempBaseDir, testName+"."+testExt) + expectedFilePath := filepath.Join(tempBaseDir, testName+"_"+testAlias+"."+testExt) // Create our store - store, err := NewKeyFileStore(tempBaseDir) + store, err := NewKeyFileStore(tempBaseDir, passphraseRetriever) if err != nil { t.Fatalf("failed to create new key filestore: %v", err) } @@ -318,7 +341,7 @@ func TestRemoveKey(t *testing.T) { } // Call the AddKey function - err = store.AddKey(testName, privKey) + err = store.AddKey(testName, testAlias, privKey) if err != nil { t.Fatalf("failed to add file to store: %v", err) } diff --git a/trustmanager/x509filestore.go b/trustmanager/x509filestore.go index 21ae716b53..9e561d4f19 100644 --- a/trustmanager/x509filestore.go +++ b/trustmanager/x509filestore.go @@ -67,7 +67,7 @@ func (s *X509FileStore) AddCert(cert *x509.Certificate) error { return nil } -// addNamedCert allows adding a certificate while controling the filename it gets +// addNamedCert allows adding a certificate while controlling the filename it gets // stored under. If the file does not exist on disk, saves it. func (s *X509FileStore) addNamedCert(cert *x509.Certificate) error { fileName, certID, err := fileName(cert) diff --git a/trustmanager/x509utils.go b/trustmanager/x509utils.go index f3800b7f74..35d0248afb 100644 --- a/trustmanager/x509utils.go +++ b/trustmanager/x509utils.go @@ -314,7 +314,7 @@ func RSAToPrivateKey(rsaPrivKey *rsa.PrivateKey) (data.PrivateKey, error) { return data.NewPrivateKey(data.RSAKey, rsaPubBytes, rsaPrivBytes), nil } -// GenerateECDSAKey generates an ECDSA private key and returns a TUF PrivateKey +// GenerateECDSAKey generates an ECDSA Private key and returns a TUF PrivateKey func GenerateECDSAKey(random io.Reader) (data.PrivateKey, error) { ecdsaPrivKey, err := ecdsa.GenerateKey(elliptic.P256(), random) if err != nil {