diff --git a/cmd/notary/integration_test.go b/cmd/notary/integration_test.go index 3147be08d8..77517be6a1 100644 --- a/cmd/notary/integration_test.go +++ b/cmd/notary/integration_test.go @@ -935,10 +935,6 @@ func TestClientDelegationsPublishing(t *testing.T) { require.NoError(t, err) require.Contains(t, output, "No delegations present in this repository.") - // publish repo - _, err = runCommand(t, tempDir, "-s", server.URL, "publish", "gun") - require.NoError(t, err) - // validate that we have all keys, including snapshot assertNumKeys(t, tempDir, 1, 2, true) @@ -1149,7 +1145,7 @@ func getUniqueKeys(t *testing.T, tempDir string) ([]string, []string) { placeToGo map[string]bool keyID string ) - if strings.TrimSpace(parts[0]) == "root" { + if strings.TrimSpace(parts[0]) == data.CanonicalRootRole { // no gun, so there are only 3 fields placeToGo, keyID = rootMap, parts[1] } else { @@ -2482,6 +2478,7 @@ func TestExportImportFlow(t *testing.T) { snapBytes, _ := ioutil.ReadAll(snapKey) snapString := string(snapBytes) require.Contains(t, snapString, "gun: gun") + require.True(t, strings.Contains(snapString, "role: snapshot") || strings.Contains(snapString, "role: target")) require.Contains(t, snapString, "role: snapshot") // validate targets is imported correctly @@ -2491,5 +2488,5 @@ func TestExportImportFlow(t *testing.T) { targBytes, _ := ioutil.ReadAll(targKey) targString := string(targBytes) require.Contains(t, targString, "gun: gun") - require.Contains(t, targString, "role: target") + require.True(t, strings.Contains(snapString, "role: snapshot") || strings.Contains(snapString, "role: target")) } diff --git a/cmd/notary/keys.go b/cmd/notary/keys.go index 889b123ce8..5d5d1b6560 100644 --- a/cmd/notary/keys.go +++ b/cmd/notary/keys.go @@ -101,9 +101,9 @@ func (k *keyCommander) GetCommand() *cobra.Command { cmdKeysImport := cmdKeyImportTemplate.ToCommand(k.importKeys) cmdKeysImport.Flags().StringVarP( - &k.keysImportRole, "role", "r", "", "Role to import key with - Notary can use this to infer the path to store the key. A specified path will take precedence") + &k.keysImportRole, "role", "r", "", "Role to import key with, if a role is not already given in a PEM header") cmdKeysImport.Flags().StringVarP( - &k.keysImportGUN, "gun", "g", "", "Gun to import key with - Notary can use this to infer the path to store the key. A specified path will take precedence") + &k.keysImportGUN, "gun", "g", "", "Gun to import key with, if a gun is not already given in a PEM header") cmd.AddCommand(cmdKeysImport) cmdExport := cmdKeyExportTemplate.ToCommand(k.exportKeys) cmdExport.Flags().StringSliceVar( @@ -415,8 +415,8 @@ func (k *keyCommander) importKeys(cmd *cobra.Command, args []string) error { for _, file := range args { from, err := os.OpenFile(file, os.O_RDONLY, notary.PrivKeyPerms) defer from.Close() - passRetreiver := k.getRetriever() - if err = utils.ImportKeys(from, importers, k.keysImportRole, k.keysImportGUN, passRetreiver); err != nil { + passRetriever := k.getRetriever() + if err = utils.ImportKeys(from, importers, k.keysImportRole, k.keysImportGUN, passRetriever); err != nil { return err } } diff --git a/fixtures/precedence.example.com.key b/fixtures/precedence.example.com.key deleted file mode 100644 index 5eed272d7a..0000000000 --- a/fixtures/precedence.example.com.key +++ /dev/null @@ -1,31 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -role: snapshot -gun: anothergun - -MIIEowIBAAKCAQEAmLYiYCTAWJBWAuxZLqVmV4FiUdGgEqoQvCbN73zF/mQfhq0C -ITo6xSxs1QiGDOzUtkpzXzziSj4J5+et4JkFleeEKaMcHadeIsSlHGvVtXDv93oR -3ydmfZO+ULRU8xHloqcLr1KrOP1daLfdMRbactd75UQgvw9XTsdeMVX5AlicSENV -KV+AQXvVpv8PT10MSvlBFam4reXuY/SkeMbIaW5pFu6AQv3Zmftt2ta0CB9kb1mY -d+OKru8Hnnq5aJw6R3GhP0TBd25P1PkiSxM2KGYZZk0W/NZqLK9/LTFKTNCv7VjC -bysVo7HxCY0bQe/bDP82v7SnLtb3aZogfva4HQIDAQABAoIBAQCLPj+X5MrRtkIH -BlTHGJ95mIr6yaYofpMlzEgoX1/1dnvcg/IWNA8UbE6L7Oq17FiEItyR8WTwhyLn -JrO/wCd8qQ40HPrs+wf1sdJPWPATMfhMcizLihSE2mtFETkILcByD9iyszFWlIdQ -jZ4NPaZP4rWgtf8Z1zYnqdf0Kk0T2imFya0qyoRLo40kxeb4p5K53JD7rPLQNyvO -YeFXTuKxBrFEMs6/wFjl+TO4nfHQXQlgQp4MNd9L5fEQBj+TvGVX+zcQEmzxljK8 -zFNXyxvXgjBPD+0V7yRhTYjrUfZJ4RX1yKDpdsva6BXL7t9hNEg/aGnKRDYF3i5q -WQz8csCBAoGBAMfdtAr3RCuCxe0TIVBon5wubau6HLOxorcXSvxO5PO2kzhy3+GY -xcCMJ+Wo0dTFXjQD3oxRKuDrPRK7AX/grYn7qJo6W7SM9xYEq3HspJJFGkcRsvem -MALt8bvG5NkGmLJD+pTOKVaTZRjW3BM6GcMzBgsLynQcLllRtNI8Hcw9AoGBAMOa -CMsWQfoOUjUffrXN0UnXLEPEeazPobnCHVtE244FdX/BFu5WMA7qqaPRyvnfK0Vl -vF5sGNiBCOnq1zjYee6FD2eyAzVmWJXM1DB4Ewp4ZaABS0ZCZgNfyd1badY4IZpw -pjYEQprguw+J8yZItNJRo+WBmnSgZy6o1bpDaflhAoGAYf61GS9VkFPlQbFAg1FY -+NXW1f1Bt2VgV48nKAByx3/8PRAt70ndo+PUaAlXIJDI+I3xHzFo6bDNWBKy0IVT -8TSf3UbB0gvP1k7h1NDnfAQ/txrZeg1Uuwr5nE0Pxc0zLyyffzh6EkXgqsYmT5MM -MKYiz2WvlTCAFTE3jGEHZy0CgYBti/cgxnZs9VhVKC5u47YzBK9lxMPgZOjOgEiw -tP/Bqo0D38BX+y0vLX2UogprpvE1DKVSvHetyZaUa1HeJF8llp/qE2h4n7k9LFoq -SxVe588CrbbawpUfjqYfsvKzZvxq4mw0FG65DuO08C2dY1rh75c7EjrO1obzOtt4 -VgkkAQKBgDnRyLnzlMfvjCyW9+cHbURQNe2iupfnlrXWEntg56USBVrFtfRQxDRp -fBtlq+0BNfDVdoVNasTCBW16UKoRBH1/k5idz5QPEbKY2055sNxHMVg0uzdb4HXr -73uaYzNrT8P7wyHFF3UL5bd0aO5DT1VYvGlHHgOhCyqcM+RBgPBS ------END RSA PRIVATE KEY----- - diff --git a/tuf/utils/x509_test.go b/tuf/utils/x509_test.go index 9ceec136d6..c30bc8f328 100644 --- a/tuf/utils/x509_test.go +++ b/tuf/utils/x509_test.go @@ -85,15 +85,15 @@ func TestKeyOperations(t *testing.T) { rsaKey, err := GenerateRSAKey(rand.Reader, 512) // Encode our ED private key - edPEM, err := KeyToPEM(edKey, "root") + edPEM, err := KeyToPEM(edKey, data.CanonicalRootRole) require.NoError(t, err) // Encode our EC private key - ecPEM, err := KeyToPEM(ecKey, "root") + ecPEM, err := KeyToPEM(ecKey, data.CanonicalRootRole) require.NoError(t, err) // Encode our RSA private key - rsaPEM, err := KeyToPEM(rsaKey, "root") + rsaPEM, err := KeyToPEM(rsaKey, data.CanonicalRootRole) require.NoError(t, err) // Check to see if ED key it is encoded @@ -124,15 +124,15 @@ func TestKeyOperations(t *testing.T) { require.Equal(t, rsaKey.Private(), decodedRSAKey.Private()) // Encrypt our ED Key - encryptedEDKey, err := EncryptPrivateKey(edKey, "root", "", "ponies") + encryptedEDKey, err := EncryptPrivateKey(edKey, data.CanonicalRootRole, "", "ponies") require.NoError(t, err) // Encrypt our EC Key - encryptedECKey, err := EncryptPrivateKey(ecKey, "root", "", "ponies") + encryptedECKey, err := EncryptPrivateKey(ecKey, data.CanonicalRootRole, "", "ponies") require.NoError(t, err) // Encrypt our RSA Key - encryptedRSAKey, err := EncryptPrivateKey(rsaKey, "root", "", "ponies") + encryptedRSAKey, err := EncryptPrivateKey(rsaKey, data.CanonicalRootRole, "", "ponies") require.NoError(t, err) // Check to see if ED key it is encrypted @@ -170,10 +170,10 @@ func TestKeyOperations(t *testing.T) { // quick test that gun headers are being added appropriately // Encrypt our RSA Key, one type of key should be enough since headers are treated the same - testGunKey, err := EncryptPrivateKey(rsaKey, "root", "ilove", "ponies") + testGunKey, err := EncryptPrivateKey(rsaKey, data.CanonicalRootRole, "ilove", "ponies") require.NoError(t, err) - testNoGunKey, err := EncryptPrivateKey(rsaKey, "root", "", "ponies") + testNoGunKey, err := EncryptPrivateKey(rsaKey, data.CanonicalRootRole, "", "ponies") require.NoError(t, err) stringTestGunKey := string(testGunKey) diff --git a/utils/keys.go b/utils/keys.go index baa6c8ea94..2c954f57a7 100644 --- a/utils/keys.go +++ b/utils/keys.go @@ -110,7 +110,10 @@ func ImportKeys(from io.Reader, to []Importer, fallbackRole string, fallbackGun pathWOFileName := strings.TrimSuffix(rawPath, filepath.Base(rawPath)) if strings.HasPrefix(pathWOFileName, notary.NonRootKeysSubdir) { gunName := strings.TrimPrefix(pathWOFileName, notary.NonRootKeysSubdir) - block.Headers["gun"] = gunName[1:(len(gunName) - 1)] //removes the slashes + gunName = gunName[1:(len(gunName) - 1)] // remove the slashes + if gunName != "" { + block.Headers["gun"] = gunName + } } } if block.Headers["gun"] == "" { diff --git a/utils/keys_test.go b/utils/keys_test.go index abfaa0c5e5..999862db16 100644 --- a/utils/keys_test.go +++ b/utils/keys_test.go @@ -7,6 +7,7 @@ import ( "errors" "github.com/docker/notary" "github.com/docker/notary/tuf/data" + "github.com/docker/notary/tuf/utils" "github.com/stretchr/testify/require" "io/ioutil" "os" @@ -257,6 +258,8 @@ func TestImportKeys(t *testing.T) { _, ok := bFinal.Headers["path"] require.False(t, ok, "expected no path header, should have been removed at import") require.Equal(t, notary.DefaultImportRole, bFinal.Headers["role"]) // if no role is specified we assume it is a delegation key + _, ok = bFinal.Headers["gun"] + require.False(t, ok, "expected no gun header, should have been removed at import") require.Len(t, bRest, 0) cFinal, cRest := pem.Decode(s.data["morpork"]) @@ -271,16 +274,13 @@ func TestImportKeys(t *testing.T) { func TestImportNoPath(t *testing.T) { s := NewTestImportStore() - b := &pem.Block{ - Headers: make(map[string]string), - } from, _ := os.OpenFile("../fixtures/secure.example.com.key", os.O_RDONLY, notary.PrivKeyPerms) defer from.Close() - b.Bytes, _ = ioutil.ReadAll(from) + fromBytes, _ := ioutil.ReadAll(from) - in := bytes.NewBuffer(b.Bytes) + in := bytes.NewBuffer(fromBytes) - err := ImportKeys(in, []Importer{s}, "root", "", passphraseRetriever) + err := ImportKeys(in, []Importer{s}, data.CanonicalRootRole, "", passphraseRetriever) require.NoError(t, err) for key := range s.data { @@ -288,23 +288,22 @@ func TestImportNoPath(t *testing.T) { require.Equal(t, key, filepath.Join(notary.RootKeysSubdir, "12ba0e0a8e05e177bc2c3489bdb6d28836879469f078e68a4812fc8a2d521497")) } + s = NewTestImportStore() + err = ImportKeys(in, []Importer{s}, "", "", passphraseRetriever) require.NoError(t, err) - require.Len(t, s.data, 1) // no path and no role should not work + require.Len(t, s.data, 0) // no path and no role should not work } func TestNonRootPathInference(t *testing.T) { s := NewTestImportStore() - b := &pem.Block{ - Headers: make(map[string]string), - } from, _ := os.OpenFile("../fixtures/secure.example.com.key", os.O_RDONLY, notary.PrivKeyPerms) defer from.Close() - b.Bytes, _ = ioutil.ReadAll(from) + fromBytes, _ := ioutil.ReadAll(from) - in := bytes.NewBuffer(b.Bytes) + in := bytes.NewBuffer(fromBytes) err := ImportKeys(in, []Importer{s}, data.CanonicalSnapshotRole, "somegun", passphraseRetriever) require.NoError(t, err) @@ -315,21 +314,50 @@ func TestNonRootPathInference(t *testing.T) { } } -func TestBlockHeaderPrecedence(t *testing.T) { +func TestBlockHeaderPrecedenceRoleAndGun(t *testing.T) { s := NewTestImportStore() - b := &pem.Block{ - Headers: make(map[string]string), + from, _ := os.OpenFile("../fixtures/secure.example.com.key", os.O_RDONLY, notary.PrivKeyPerms) + defer from.Close() + fromBytes, _ := ioutil.ReadAll(from) + b, _ := pem.Decode(fromBytes) + b.Headers["role"] = data.CanonicalSnapshotRole + b.Headers["gun"] = "anothergun" + bBytes := pem.EncodeToMemory(b) + + in := bytes.NewBuffer(bBytes) + + err := ImportKeys(in, []Importer{s}, "somerole", "somegun", passphraseRetriever) + require.NoError(t, err) + + require.Len(t, s.data, 1) + for key := range s.data { + // block header role= root should take precedence over command line flag + require.Equal(t, key, filepath.Join(notary.NonRootKeysSubdir, "anothergun", "12ba0e0a8e05e177bc2c3489bdb6d28836879469f078e68a4812fc8a2d521497")) + final, rest := pem.Decode(s.data[key]) + require.Len(t, rest, 0) + require.Equal(t, final.Headers["role"], "snapshot") + require.Equal(t, final.Headers["gun"], "anothergun") } - from, _ := os.OpenFile("../fixtures/precedence.example.com.key", os.O_RDONLY, notary.PrivKeyPerms) +} + +func TestBlockHeaderPrecedenceGunFromPath(t *testing.T) { + s := NewTestImportStore() + + from, _ := os.OpenFile("../fixtures/secure.example.com.key", os.O_RDONLY, notary.PrivKeyPerms) defer from.Close() - b.Bytes, _ = ioutil.ReadAll(from) + fromBytes, _ := ioutil.ReadAll(from) + b, _ := pem.Decode(fromBytes) + b.Headers["role"] = data.CanonicalSnapshotRole + b.Headers["path"] = filepath.Join(notary.NonRootKeysSubdir, "anothergun", "12ba0e0a8e05e177bc2c3489bdb6d28836879469f078e68a4812fc8a2d521497") + bBytes := pem.EncodeToMemory(b) - in := bytes.NewBuffer(b.Bytes) + in := bytes.NewBuffer(bBytes) err := ImportKeys(in, []Importer{s}, "somerole", "somegun", passphraseRetriever) require.NoError(t, err) + require.Len(t, s.data, 1) for key := range s.data { // block header role= root should take precedence over command line flag require.Equal(t, key, filepath.Join(notary.NonRootKeysSubdir, "anothergun", "12ba0e0a8e05e177bc2c3489bdb6d28836879469f078e68a4812fc8a2d521497")) @@ -380,6 +408,8 @@ func TestImportKeys2InOneFile(t *testing.T) { require.Equal(t, b.Bytes, bFinal.Bytes) _, ok := bFinal.Headers["path"] require.False(t, ok, "expected no path header, should have been removed at import") + role, _ := bFinal.Headers["role"] + require.Equal(t, notary.DefaultImportRole, role) b2Final, b2Rest := pem.Decode(bRest) require.Equal(t, b2.Bytes, b2Final.Bytes) @@ -397,30 +427,24 @@ func TestImportKeys2InOneFile(t *testing.T) { func TestImportKeys2InOneFileNoPath(t *testing.T) { s := NewTestImportStore() - b := &pem.Block{ - Headers: make(map[string]string), - } from, _ := os.OpenFile("../fixtures/secure.example.com.key", os.O_RDONLY, notary.PrivKeyPerms) - b.Bytes, _ = ioutil.ReadAll(from) - rand.Read(b.Bytes) - b.Headers["path"] = "ankh" + defer from.Close() + fromBytes, _ := ioutil.ReadAll(from) + b, _ := pem.Decode(fromBytes) + b.Headers["role"] = data.CanonicalSnapshotRole + bBytes := pem.EncodeToMemory(b) - b2 := &pem.Block{ - Headers: make(map[string]string), - } - b2.Bytes, _ = ioutil.ReadAll(from) - rand.Read(b2.Bytes) - b2.Headers["path"] = "ankh" + b2, _ := pem.Decode(fromBytes) + b2.Headers["role"] = data.CanonicalSnapshotRole + b2Bytes := pem.EncodeToMemory(b2) c := &pem.Block{ Headers: make(map[string]string), } - c.Bytes, _ = ioutil.ReadAll(from) + c.Bytes = make([]byte, 1000) rand.Read(c.Bytes) c.Headers["path"] = "morpork" - bBytes := pem.EncodeToMemory(b) - b2Bytes := pem.EncodeToMemory(b2) bBytes = append(bBytes, b2Bytes...) cBytes := pem.EncodeToMemory(c) @@ -431,22 +455,99 @@ func TestImportKeys2InOneFileNoPath(t *testing.T) { err := ImportKeys(in, []Importer{s}, "", "", passphraseRetriever) require.NoError(t, err) - bFinal, bRest := pem.Decode(s.data["ankh"]) - require.Equal(t, b.Bytes, bFinal.Bytes) - _, ok := bFinal.Headers["path"] - require.False(t, ok, "expected no path header, should have been removed at import") + bFinal, bRest := pem.Decode(s.data[filepath.Join(notary.NonRootKeysSubdir, "12ba0e0a8e05e177bc2c3489bdb6d28836879469f078e68a4812fc8a2d521497")]) + require.Equal(t, b.Headers["role"], bFinal.Headers["role"]) b2Final, b2Rest := pem.Decode(bRest) - require.Equal(t, b2.Bytes, b2Final.Bytes) - _, ok = b2Final.Headers["path"] - require.False(t, ok, "expected no path header, should have been removed at import") + require.Equal(t, b2.Headers["role"], b2Final.Headers["role"]) require.Len(t, b2Rest, 0) cFinal, cRest := pem.Decode(s.data["morpork"]) require.Equal(t, c.Bytes, cFinal.Bytes) - _, ok = cFinal.Headers["path"] + _, ok := cFinal.Headers["path"] require.False(t, ok, "expected no path header, should have been removed at import") require.Len(t, cRest, 0) +} + +// no path and encrypted key import should fail +func TestEncryptedKeyImportFail(t *testing.T) { + s := NewTestImportStore() + + privKey, err := utils.GenerateECDSAKey(rand.Reader) + require.NoError(t, err) + + pemBytes, err := utils.EncryptPrivateKey(privKey, data.CanonicalRootRole, "", cannedPassphrase) + require.NoError(t, err) + + in := bytes.NewBuffer(pemBytes) + + _ = ImportKeys(in, []Importer{s}, "", "", passphraseRetriever) + require.Len(t, s.data, 0) +} + +// path and encrypted key should succeed, tests gun inference from path as well +func TestEncryptedKeyImportSuccess(t *testing.T) { + s := NewTestImportStore() + + privKey, err := utils.GenerateECDSAKey(rand.Reader) + originalKey := privKey.Private() + require.NoError(t, err) + + pemBytes, err := utils.EncryptPrivateKey(privKey, data.CanonicalSnapshotRole, "", cannedPassphrase) + require.NoError(t, err) + + b, _ := pem.Decode(pemBytes) + b.Headers["path"] = filepath.Join(notary.NonRootKeysSubdir, "somegun", "encryptedkey") + pemBytes = pem.EncodeToMemory(b) + + in := bytes.NewBuffer(pemBytes) + + _ = ImportKeys(in, []Importer{s}, "", "", passphraseRetriever) + require.Len(t, s.data, 1) + + keyBytes := s.data[filepath.Join(notary.NonRootKeysSubdir, "somegun", "encryptedkey")] + + bFinal, bRest := pem.Decode(keyBytes) + require.Equal(t, "somegun", bFinal.Headers["gun"]) + require.Len(t, bRest, 0) + + // we should fail to parse it without the passphrase + privKey, err = utils.ParsePEMPrivateKey(keyBytes, "") + require.Equal(t, err, errors.New("could not decrypt private key")) + require.Nil(t, privKey) + + // we should succeed to parse it with the passphrase + privKey, err = utils.ParsePEMPrivateKey(keyBytes, cannedPassphrase) + require.NoError(t, err) + require.Equal(t, originalKey, privKey.Private()) +} + +func TestEncryption(t *testing.T) { + s := NewTestImportStore() + + privKey, err := utils.GenerateECDSAKey(rand.Reader) + originalKey := privKey.Private() + require.NoError(t, err) + + pemBytes, err := utils.EncryptPrivateKey(privKey, "", "", "") + require.NoError(t, err) + + in := bytes.NewBuffer(pemBytes) + + _ = ImportKeys(in, []Importer{s}, "", "", passphraseRetriever) + require.Len(t, s.data, 1) - require.Len(t, s.data, 2) + shouldBeEnc, ok := s.data[filepath.Join(notary.NonRootKeysSubdir, privKey.ID())] + // we should have got a key imported to this location + require.True(t, ok) + + // we should fail to parse it without the passphrase + privKey, err = utils.ParsePEMPrivateKey(shouldBeEnc, "") + require.Equal(t, err, errors.New("could not decrypt private key")) + require.Nil(t, privKey) + + // we should succeed to parse it with the passphrase + privKey, err = utils.ParsePEMPrivateKey(shouldBeEnc, cannedPassphrase) + require.NoError(t, err) + require.Equal(t, originalKey, privKey.Private()) }