From 8f7bee4fb4766ca0e2072d9898fbb7eedbe0ad3c Mon Sep 17 00:00:00 2001 From: Avi Vaid Date: Sun, 7 Aug 2016 11:57:11 -0700 Subject: [PATCH] cleaned up a lot of the control flow in key imports and added a bunch of tests covering more cases, added a delegation add- import and publish flow test Signed-off-by: Avi Vaid --- cmd/notary/integration_test.go | 603 ++++++++++++++++++++++++++++ fixtures/precedence.example.com.key | 3 +- tuf/utils/x509.go | 1 + tuf/utils/x509_test.go | 16 + utils/keys.go | 73 ++-- utils/keys_test.go | 17 +- 6 files changed, 672 insertions(+), 41 deletions(-) diff --git a/cmd/notary/integration_test.go b/cmd/notary/integration_test.go index aa84a56cd1..e63b79e475 100644 --- a/cmd/notary/integration_test.go +++ b/cmd/notary/integration_test.go @@ -1299,4 +1299,607 @@ func TestMain(m *testing.M) { os.Exit(m.Run()) } +func TestPurge(t *testing.T) { + setUp(t) + + tempDir := tempDirWithConfig(t, "{}") + defer os.RemoveAll(tempDir) + + server := setupServer() + defer server.Close() + + startTime := time.Now() + endTime := startTime.AddDate(10, 0, 0) + + // Setup certificates + tempFile, err := ioutil.TempFile("", "pemfile") + require.NoError(t, err) + privKey, err := utils.GenerateECDSAKey(rand.Reader) + cert, err := cryptoservice.GenerateCertificate(privKey, "gun", startTime, endTime) + require.NoError(t, err) + _, err = tempFile.Write(utils.CertToPEM(cert)) + require.NoError(t, err) + tempFile.Close() + defer os.Remove(tempFile.Name()) + rawPubBytes, _ := ioutil.ReadFile(tempFile.Name()) + parsedPubKey, _ := utils.ParsePEMPublicKey(rawPubBytes) + keyID, err := utils.CanonicalKeyID(parsedPubKey) + require.NoError(t, err) + + delgName := "targets/delegation" + + _, err = runCommand(t, tempDir, "-s", server.URL, "init", "gun") + require.NoError(t, err) + + _, err = runCommand(t, tempDir, "delegation", "add", "gun", delgName, tempFile.Name(), "--all-paths") + require.NoError(t, err) + + _, err = runCommand(t, tempDir, "-s", server.URL, "publish", "gun") + require.NoError(t, err) + + // remove targets key and ensure we fail to publish + out, err := runCommand(t, tempDir, "key", "list") + require.NoError(t, err) + lines := splitLines(out) + if len(lines) == 1 && lines[0] == "No signing keys found." { + t.Fail() + } + var targetsKeyID string + for _, line := range lines[2:] { + parts := strings.Fields(line) + if strings.TrimSpace(parts[0]) == data.CanonicalTargetsRole { + targetsKeyID = strings.TrimSpace(parts[2]) + break + } + } + + if targetsKeyID == "" { + t.Fail() + } + + err = os.Remove(filepath.Join(tempDir, notary.PrivDir, notary.NonRootKeysSubdir, "gun", targetsKeyID+".key")) + require.NoError(t, err) + + _, err = runCommand(t, tempDir, "delegation", "purge", "gun", "--key", keyID) + require.NoError(t, err) + + // publish doesn't error because purge only updates the roles we have signing keys for + _, err = runCommand(t, tempDir, "-s", server.URL, "publish", "gun") + require.NoError(t, err) + + // check the delegation wasn't removed + out, err = runCommand(t, tempDir, "-s", server.URL, "delegation", "list", "gun") + require.NoError(t, err) + require.Contains(t, out, delgName) +} + +// Initialize repo and test witnessing. The following steps are performed: +// 1. init a repo +// 2. add a delegation with a key and --all-paths +// 3. add a target to the delegation +// 4. list targets and ensure it really is in the delegation +// 5 witness the valid delegation, make sure everything is successful +// 6. add a new (different) key to the delegation +// 7. remove the key from the delegation +// 8. list targets and ensure the target is no longer visible +// 9. witness the delegation +// 10. list targets and ensure target is visible again +// 11. witness an invalid role and check for error on publish +func TestWitness(t *testing.T) { + setUp(t) + + tempDir := tempDirWithConfig(t, "{}") + defer os.RemoveAll(tempDir) + + server := setupServer() + defer server.Close() + + startTime := time.Now() + endTime := startTime.AddDate(10, 0, 0) + + // Setup certificates + tempFile, err := ioutil.TempFile("", "pemfile") + require.NoError(t, err) + privKey, err := utils.GenerateECDSAKey(rand.Reader) + cert, err := cryptoservice.GenerateCertificate(privKey, "gun", startTime, endTime) + require.NoError(t, err) + _, err = tempFile.Write(utils.CertToPEM(cert)) + require.NoError(t, err) + tempFile.Close() + defer os.Remove(tempFile.Name()) + rawPubBytes, _ := ioutil.ReadFile(tempFile.Name()) + parsedPubKey, _ := utils.ParsePEMPublicKey(rawPubBytes) + keyID, err := utils.CanonicalKeyID(parsedPubKey) + require.NoError(t, err) + + tempFile2, err := ioutil.TempFile("", "pemfile") + require.NoError(t, err) + privKey2, err := utils.GenerateECDSAKey(rand.Reader) + cert2, err := cryptoservice.GenerateCertificate(privKey2, "gun", startTime, endTime) + require.NoError(t, err) + _, err = tempFile2.Write(utils.CertToPEM(cert2)) + require.NoError(t, err) + tempFile2.Close() + defer os.Remove(tempFile2.Name()) + + delgName := "targets/delegation" + targetName := "test_target" + targetHash := "9d9e890af64dd0f44b8a1538ff5fa0511cc31bf1ab89f3a3522a9a581a70fad8" // sha256 of README.md at time of writing test + + keyStore, err := trustmanager.NewKeyFileStore(tempDir, passphrase.ConstantRetriever(testPassphrase)) + require.NoError(t, err) + err = keyStore.AddKey( + trustmanager.KeyInfo{ + Gun: "gun", + Role: delgName, + }, + privKey, + ) + require.NoError(t, err) + + // 1. init a repo + _, err = runCommand(t, tempDir, "-s", server.URL, "init", "gun") + require.NoError(t, err) + + _, err = runCommand(t, tempDir, "-s", server.URL, "publish", "gun") + require.NoError(t, err) + + // 2. add a delegation with a key and --all-paths + _, err = runCommand(t, tempDir, "delegation", "add", "gun", delgName, tempFile.Name(), "--all-paths") + require.NoError(t, err) + + // 3. add a target to the delegation + _, err = runCommand(t, tempDir, "addhash", "gun", targetName, "100", "--sha256", targetHash, "-r", delgName) + require.NoError(t, err) + + _, err = runCommand(t, tempDir, "-s", server.URL, "publish", "gun") + require.NoError(t, err) + + // 4. list targets and ensure it really is in the delegation + output, err := runCommand(t, tempDir, "-s", server.URL, "list", "gun") + require.NoError(t, err) + require.Contains(t, output, targetName) + require.Contains(t, output, targetHash) + + // 5. witness the valid delegation, make sure everything is successful + _, err = runCommand(t, tempDir, "witness", "gun", delgName) + require.NoError(t, err) + + _, err = runCommand(t, tempDir, "-s", server.URL, "publish", "gun") + require.NoError(t, err) + + output, err = runCommand(t, tempDir, "-s", server.URL, "list", "gun") + require.NoError(t, err) + require.Contains(t, output, targetName) + require.Contains(t, output, targetHash) + + // 6. add a new (different) key to the delegation + _, err = runCommand(t, tempDir, "delegation", "add", "gun", delgName, tempFile2.Name(), "--all-paths") + require.NoError(t, err) + + // 7. remove the key from the delegation + _, err = runCommand(t, tempDir, "delegation", "remove", "gun", delgName, keyID) + require.NoError(t, err) + + _, err = runCommand(t, tempDir, "-s", server.URL, "publish", "gun") + require.NoError(t, err) + + // 8. list targets and ensure the target is no longer visible + output, err = runCommand(t, tempDir, "-s", server.URL, "list", "gun") + require.NoError(t, err) + require.NotContains(t, output, targetName) + require.NotContains(t, output, targetHash) + + err = keyStore.AddKey( + trustmanager.KeyInfo{ + Gun: "gun", + Role: delgName, + }, + privKey2, + ) + require.NoError(t, err) + + // 9. witness the delegation + _, err = runCommand(t, tempDir, "witness", "gun", delgName) + require.NoError(t, err) + + _, err = runCommand(t, tempDir, "-s", server.URL, "publish", "gun") + require.NoError(t, err) + + // 10. list targets and ensure target is visible again + output, err = runCommand(t, tempDir, "-s", server.URL, "list", "gun") + require.NoError(t, err) + require.Contains(t, output, targetName) + require.Contains(t, output, targetHash) + + // 11. witness an invalid role and check for error on publish + _, err = runCommand(t, tempDir, "witness", "gun", "targets/made/up") + require.NoError(t, err) + + _, err = runCommand(t, tempDir, "-s", server.URL, "publish", "gun") + require.Error(t, err) + + // 12. check non-targets base roles all fail + for _, role := range []string{data.CanonicalRootRole, data.CanonicalSnapshotRole, data.CanonicalTimestampRole} { + // clear any pending changes to ensure errors are only related to the specific role we're trying to witness + _, err = runCommand(t, tempDir, "status", "gun", "--reset") + require.NoError(t, err) + + _, err = runCommand(t, tempDir, "witness", "gun", role) + require.NoError(t, err) + + _, err = runCommand(t, tempDir, "-s", server.URL, "publish", "gun") + require.Error(t, err) + } +} + +// Tests import/export keys +func TestClientKeyImportExportOnly(t *testing.T) { + // -- setup -- + setUp(t) + tempDir := tempDirWithConfig(t, "{}") + defer os.RemoveAll(tempDir) + + server := setupServer() + defer server.Close() + + var ( + rootKeyID string + ) + + tempFile, err := ioutil.TempFile("", "pemfile") + require.NoError(t, err) + // close later, because we might need to write to it + defer os.Remove(tempFile.Name()) + + // -- tests -- + // test 1, no path but role=root included with non-encrypted key + privKey, err := utils.GenerateECDSAKey(rand.Reader) + require.NoError(t, err) + + pemBytes, err := utils.EncryptPrivateKey(privKey, "root", "", "") + require.NoError(t, err) + + nBytes, err := tempFile.Write(pemBytes) + require.NoError(t, err) + tempFile.Close() + require.Equal(t, len(pemBytes), nBytes) + rootKeyID = privKey.ID() + + // import the key + _, err = runCommand(t, tempDir, "key", "import", tempFile.Name()) + require.NoError(t, err) + + // if there is hardware available, root will only be on hardware, and not + // on disk + newRoot, _ := assertNumKeys(t, tempDir, 1, 0, !rootOnHardware()) + require.Equal(t, rootKeyID, newRoot[0]) + + // test 2, no path but role flag included with unencrypted key + + tempFile2, err := ioutil.TempFile("", "pemfile") + require.NoError(t, err) + // close later, because we might need to write to it + defer os.Remove(tempFile2.Name()) + + privKey, err = utils.GenerateECDSAKey(rand.Reader) + require.NoError(t, err) + + pemBytes, err = utils.EncryptPrivateKey(privKey, "", "", "") + require.NoError(t, err) + + nBytes, err = tempFile2.Write(pemBytes) + require.NoError(t, err) + tempFile2.Close() + require.Equal(t, len(pemBytes), nBytes) + + // import the key + _, err = runCommand(t, tempDir, "key", "import", tempFile2.Name(), "-r", "root") + require.NoError(t, err) + + // if there is hardware available, root will only be on hardware, and not + // on disk + _, _ = assertNumKeys(t, tempDir, 2, 0, !rootOnHardware()) + + // test 3, no path no role included with unencrypted key + + tempFile3, err := ioutil.TempFile("", "pemfile") + require.NoError(t, err) + // close later, because we might need to write to it + defer os.Remove(tempFile3.Name()) + + privKey, err = utils.GenerateECDSAKey(rand.Reader) + require.NoError(t, err) + + pemBytes, err = utils.EncryptPrivateKey(privKey, "", "", "") + require.NoError(t, err) + + nBytes, err = tempFile3.Write(pemBytes) + require.NoError(t, err) + tempFile2.Close() + require.Equal(t, len(pemBytes), nBytes) + + // import the key + _, err = runCommand(t, tempDir, "key", "import", tempFile3.Name()) + require.NoError(t, err) + + // if there is hardware available, root will only be on hardware, and not + // on disk + _, _ = assertNumKeys(t, tempDir, 2, 1, !rootOnHardware()) + + // test 4, no path non root role with non canonical role and gun flag with unencrypted key + + tempFile4, err := ioutil.TempFile("", "pemfile") + require.NoError(t, err) + // close later, because we might need to write to it + defer os.Remove(tempFile4.Name()) + + privKey, err = utils.GenerateECDSAKey(rand.Reader) + require.NoError(t, err) + + pemBytes, err = utils.EncryptPrivateKey(privKey, "", "", "") + require.NoError(t, err) + + nBytes, err = tempFile4.Write(pemBytes) + require.NoError(t, err) + tempFile4.Close() + require.Equal(t, len(pemBytes), nBytes) + newKeyID := privKey.ID() + + // import the key + _, err = runCommand(t, tempDir, "key", "import", tempFile4.Name(), "-r", "somerole", "-g", "somegun") + require.NoError(t, err) + + // if there is hardware available, root will only be on hardware, and not + // on disk + _, _ = assertNumKeys(t, tempDir, 2, 2, !rootOnHardware()) + _, err = os.Open(filepath.Join(tempDir, "private", notary.NonRootKeysSubdir, newKeyID+".key")) + require.NoError(t, err) + + // test 5, no path non root role with canonical role and gun flag with unencrypted key + + tempFile5, err := ioutil.TempFile("", "pemfile") + require.NoError(t, err) + // close later, because we might need to write to it + defer os.Remove(tempFile5.Name()) + + privKey, err = utils.GenerateECDSAKey(rand.Reader) + require.NoError(t, err) + + pemBytes, err = utils.EncryptPrivateKey(privKey, "", "", "") + require.NoError(t, err) + + nBytes, err = tempFile5.Write(pemBytes) + require.NoError(t, err) + tempFile5.Close() + require.Equal(t, len(pemBytes), nBytes) + newKeyID = privKey.ID() + + // import the key + _, err = runCommand(t, tempDir, "key", "import", tempFile5.Name(), "-r", "snapshot", "-g", "somegun") + require.NoError(t, err) + + // if there is hardware available, root will only be on hardware, and not + // on disk + _, _ = assertNumKeys(t, tempDir, 2, 3, !rootOnHardware()) + _, err = os.Open(filepath.Join(tempDir, "private", notary.NonRootKeysSubdir, "somegun", newKeyID+".key")) + require.NoError(t, err) + + // test6, no path but role=root included with encrypted key, should fail since we don't know what keyid to save to + + tempFile6, err := ioutil.TempFile("", "pemfile") + require.NoError(t, err) + // close later, because we might need to write to it + defer os.Remove(tempFile6.Name()) + + privKey, err = utils.GenerateECDSAKey(rand.Reader) + require.NoError(t, err) + + pemBytes, err = utils.EncryptPrivateKey(privKey, "root", "", testPassphrase) + require.NoError(t, err) + + nBytes, err = tempFile6.Write(pemBytes) + require.NoError(t, err) + tempFile6.Close() + require.Equal(t, len(pemBytes), nBytes) + + // import the key + _, err = runCommand(t, tempDir, "key", "import", tempFile6.Name()) + require.NoError(t, err) + + // if there is hardware available, root will only be on hardware, and not + // on disk + _, _ = assertNumKeys(t, tempDir, 2, 3, !rootOnHardware()) + + // test7, non root key with no path with no gun + + tempFile7, err := ioutil.TempFile("", "pemfile") + require.NoError(t, err) + // close later, because we might need to write to it + defer os.Remove(tempFile7.Name()) + + privKey, err = utils.GenerateECDSAKey(rand.Reader) + require.NoError(t, err) + + pemBytes, err = utils.EncryptPrivateKey(privKey, "", "", "") + require.NoError(t, err) + + nBytes, err = tempFile7.Write(pemBytes) + require.NoError(t, err) + tempFile7.Close() + require.Equal(t, len(pemBytes), nBytes) + newKeyID = privKey.ID() + + // import the key + _, err = runCommand(t, tempDir, "key", "import", tempFile7.Name(), "-r", "somerole") + require.NoError(t, err) + + // if there is hardware available, root will only be on hardware, and not + // on disk + _, _ = assertNumKeys(t, tempDir, 2, 4, !rootOnHardware()) + _, err = os.Open(filepath.Join(tempDir, "private", notary.NonRootKeysSubdir, newKeyID+".key")) + require.NoError(t, err) +} + +func TestAddDelImportKeyPublishFlow(t *testing.T) { + setUp(t) + + tempDir := tempDirWithConfig(t, "{}") + defer os.RemoveAll(tempDir) + + server := setupServer() + defer server.Close() + + // Setup certificate for delegation role + tempFile, err := ioutil.TempFile("", "pemfile") + require.NoError(t, err) + + privKey, err := utils.GenerateRSAKey(rand.Reader, 2048) + require.NoError(t, err) + startTime := time.Now() + endTime := startTime.AddDate(10, 0, 0) + cert, err := cryptoservice.GenerateCertificate(privKey, "gun", startTime, endTime) + require.NoError(t, err) + + // Setup key in a file for import + keyFile, err := ioutil.TempFile("", "pemfile") + require.NoError(t, err) + defer os.Remove(keyFile.Name()) + pemBytes, err := utils.EncryptPrivateKey(privKey, "", "", "") + require.NoError(t, err) + nBytes, err := keyFile.Write(pemBytes) + require.NoError(t, err) + keyFile.Close() + require.Equal(t, len(pemBytes), nBytes) + + _, err = tempFile.Write(utils.CertToPEM(cert)) + require.NoError(t, err) + tempFile.Close() + defer os.Remove(tempFile.Name()) + + rawPubBytes, _ := ioutil.ReadFile(tempFile.Name()) + parsedPubKey, _ := utils.ParsePEMPublicKey(rawPubBytes) + canonicalKeyID, err := utils.CanonicalKeyID(parsedPubKey) + require.NoError(t, err) + + // Set up targets for publishing + tempTargetFile, err := ioutil.TempFile("", "targetfile") + require.NoError(t, err) + tempTargetFile.Close() + defer os.Remove(tempTargetFile.Name()) + + var target = "sdgkadga" + + var output string + + // init repo + _, err = runCommand(t, tempDir, "-s", server.URL, "init", "gun") + require.NoError(t, err) + + // publish repo + _, err = runCommand(t, tempDir, "-s", server.URL, "publish", "gun") + require.NoError(t, err) + + // list delegations - none yet + output, err = runCommand(t, tempDir, "-s", server.URL, "delegation", "list", "gun") + 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) + + // rotate the snapshot key to server + output, err = runCommand(t, tempDir, "-s", server.URL, "key", "rotate", "gun", "snapshot", "-r") + require.NoError(t, err) + + // publish repo + _, err = runCommand(t, tempDir, "-s", server.URL, "publish", "gun") + require.NoError(t, err) + + // validate that we lost the snapshot signing key + _, signingKeyIDs := assertNumKeys(t, tempDir, 1, 1, true) + targetKeyID := signingKeyIDs[0] + + // add new valid delegation with single new cert + output, err = runCommand(t, tempDir, "delegation", "add", "gun", "targets/releases", tempFile.Name(), "--paths", "\"\"") + require.NoError(t, err) + require.Contains(t, output, "Addition of delegation role") + require.Contains(t, output, canonicalKeyID) + + // publish repo + _, err = runCommand(t, tempDir, "-s", server.URL, "publish", "gun") + require.NoError(t, err) + + // list delegations - we should see our one delegation + output, err = runCommand(t, tempDir, "-s", server.URL, "delegation", "list", "gun") + require.NoError(t, err) + require.NotContains(t, output, "No delegations present in this repository.") + + // remove the targets key to demonstrate that delegates don't need this key + keyDir := filepath.Join(tempDir, "private", "tuf_keys") + require.NoError(t, os.Remove(filepath.Join(keyDir, "gun", targetKeyID+".key"))) + + // we are now set up with the first part, now import the delegation key- add a target- publish + + // first test the negative case, should fail without the key import + + // add a target using the delegation -- will only add to targets/releases + _, err = runCommand(t, tempDir, "add", "gun", target, tempTargetFile.Name(), "--roles", "targets/releases") + require.NoError(t, err) + + // list targets for targets/releases - we should see no targets until we publish + output, err = runCommand(t, tempDir, "-s", server.URL, "list", "gun", "--roles", "targets/releases") + require.NoError(t, err) + require.Contains(t, output, "No targets") + + output, err = runCommand(t, tempDir, "-s", server.URL, "status", "gun") + require.NoError(t, err) + + // publish repo + _, err = runCommand(t, tempDir, "-s", server.URL, "publish", "gun") + require.Error(t, err) + + // list targets for targets/releases - we should not see our target! + output, err = runCommand(t, tempDir, "-s", server.URL, "list", "gun", "--roles", "targets/releases") + require.NoError(t, err) + require.Contains(t, output, "No targets present") + + // now test for the positive case, import the key and publish and it should work + + // the changelist still exists so no need to add the target again + // just import the key and publish + output, err = runCommand(t, tempDir, "-s", server.URL, "status", "gun") + require.NoError(t, err) + require.Contains(t, output, "targets/releases") + require.Contains(t, output, "sdgkadga") + + // import the key + _, err = runCommand(t, tempDir, "key", "import", keyFile.Name(), "-r", "targets/releases", "-g", "gun") + require.NoError(t, err) + + // make sure that it has been imported fine + // if there is hardware available, root will only be on hardware, and not + // on disk + _, err = os.Open(filepath.Join(tempDir, "private", notary.NonRootKeysSubdir, privKey.ID()+".key")) + require.NoError(t, err) + + // now try to publish + _, err = runCommand(t, tempDir, "-s", server.URL, "publish", "gun") + require.NoError(t, err) + + // check that changelist is applied + output, err = runCommand(t, tempDir, "-s", server.URL, "status", "gun") + require.NoError(t, err) + require.NotContains(t, output, "targets/releases") + + // list targets for targets/releases - we should see our target! + output, err = runCommand(t, tempDir, "-s", server.URL, "list", "gun", "--roles", "targets/releases") + require.NoError(t, err) + require.NotContains(t, output, "No targets present") + require.Contains(t, output, "sdgkadga") +} diff --git a/fixtures/precedence.example.com.key b/fixtures/precedence.example.com.key index eaa512bbfb..5eed272d7a 100644 --- a/fixtures/precedence.example.com.key +++ b/fixtures/precedence.example.com.key @@ -1,5 +1,6 @@ -----BEGIN RSA PRIVATE KEY----- -role: root +role: snapshot +gun: anothergun MIIEowIBAAKCAQEAmLYiYCTAWJBWAuxZLqVmV4FiUdGgEqoQvCbN73zF/mQfhq0C ITo6xSxs1QiGDOzUtkpzXzziSj4J5+et4JkFleeEKaMcHadeIsSlHGvVtXDv93oR diff --git a/tuf/utils/x509.go b/tuf/utils/x509.go index dd2d6663a2..18bdd69cdd 100644 --- a/tuf/utils/x509.go +++ b/tuf/utils/x509.go @@ -443,6 +443,7 @@ func EncryptPrivateKey(key data.PrivateKey, role, gun, passphrase string) ([]byt return nil, fmt.Errorf("unable to encrypt key - invalid PEM file produced") } encryptedPEMBlock.Headers["role"] = role + if gun != "" { encryptedPEMBlock.Headers["gun"] = gun } diff --git a/tuf/utils/x509_test.go b/tuf/utils/x509_test.go index 0b8c105fb2..9ceec136d6 100644 --- a/tuf/utils/x509_test.go +++ b/tuf/utils/x509_test.go @@ -139,16 +139,19 @@ func TestKeyOperations(t *testing.T) { stringEncryptedEDKey := string(encryptedEDKey) require.True(t, strings.Contains(stringEncryptedEDKey, "-----BEGIN ED25519 PRIVATE KEY-----")) require.True(t, strings.Contains(stringEncryptedEDKey, "Proc-Type: 4,ENCRYPTED")) + require.True(t, strings.Contains(stringEncryptedEDKey, "role: root")) // Check to see if EC key it is encrypted stringEncryptedECKey := string(encryptedECKey) require.True(t, strings.Contains(stringEncryptedECKey, "-----BEGIN EC PRIVATE KEY-----")) require.True(t, strings.Contains(stringEncryptedECKey, "Proc-Type: 4,ENCRYPTED")) + require.True(t, strings.Contains(stringEncryptedECKey, "role: root")) // Check to see if RSA key it is encrypted stringEncryptedRSAKey := string(encryptedRSAKey) require.True(t, strings.Contains(stringEncryptedRSAKey, "-----BEGIN RSA PRIVATE KEY-----")) require.True(t, strings.Contains(stringEncryptedRSAKey, "Proc-Type: 4,ENCRYPTED")) + require.True(t, strings.Contains(stringEncryptedRSAKey, "role: root")) // Decrypt our ED Key decryptedEDKey, err := ParsePEMPrivateKey(encryptedEDKey, "ponies") @@ -165,6 +168,19 @@ func TestKeyOperations(t *testing.T) { require.NoError(t, err) require.Equal(t, rsaKey.Private(), decryptedRSAKey.Private()) + // 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") + require.NoError(t, err) + + testNoGunKey, err := EncryptPrivateKey(rsaKey, "root", "", "ponies") + require.NoError(t, err) + + stringTestGunKey := string(testGunKey) + require.True(t, strings.Contains(stringTestGunKey, "gun: ilove")) + + stringTestNoGunKey := string(testNoGunKey) + require.False(t, strings.Contains(stringTestNoGunKey, "gun:")) } // X509PublickeyID returns the public key ID of a RSA X509 key rather than the diff --git a/utils/keys.go b/utils/keys.go index 9d973d0d17..f6c3dc1ca0 100644 --- a/utils/keys.go +++ b/utils/keys.go @@ -105,10 +105,7 @@ func ImportKeys(from io.Reader, to []Importer, fallbackRole string, fallbackGun toWrite []byte ) for block, rest := pem.Decode(data); block != nil; block, rest = pem.Decode(rest) { - if block.Headers["role"] == "" { - // no worries about if check as for GUN here because empty roles will get a role:notary.DefaultImportRole - block.Headers["role"] = fallbackRole - } + // if there is a path then we set the gun header from this path if rawPath := block.Headers["path"]; rawPath != "" { pathWOFileName := strings.TrimSuffix(rawPath, filepath.Base(rawPath)) if strings.HasPrefix(pathWOFileName, notary.NonRootKeysSubdir) { @@ -121,54 +118,44 @@ func ImportKeys(from io.Reader, to []Importer, fallbackRole string, fallbackGun block.Headers["gun"] = fallbackGun } } + if block.Headers["role"] == "" { + if fallbackRole == "" { + block.Headers["role"] = notary.DefaultImportRole + } else { + block.Headers["role"] = fallbackRole + } + } loc, ok := block.Headers["path"] // only if the path isn't specified do we get into this parsing path logic if !ok || loc == "" { - if block.Headers["role"] == "" { - // now we have no clue where to copy this key so we skip it since we have no path or role - logrus.Info("failed to import key to store: PEM headers did not contain import path") - continue - } // if the path isn't specified, we will try to infer the path rel to trust dir from the role (and then gun) - + // parse key for the keyID which we will save it by. + // if the key is encrypted at this point, we will generate an error and continue since we don't know the ID to save it by decodedKey, err := utils.ParsePEMPrivateKey(pem.EncodeToMemory(block), "") if err != nil { logrus.Info("failed to import key to store: Invalid key generated, key may be encrypted and does not contain path header") continue } keyID := decodedKey.ID() - if block.Headers["role"] == tufdata.CanonicalRootRole { - // does not make sense for root keys to have GUNs, so import it without the GUN even if specified + // this is a root key so import it to trustDir/root_keys/ + delete(block.Headers, "gun") loc = filepath.Join(notary.RootKeysSubdir, keyID) - } else { - // additional path inference from gun + } else if block.Headers["role"] == tufdata.CanonicalSnapshotRole || block.Headers["role"] == tufdata.CanonicalTargetsRole || block.Headers["role"] == tufdata.CanonicalTimestampRole { loc = filepath.Join(notary.NonRootKeysSubdir, block.Headers["gun"], keyID) + } else { + delete(block.Headers, "gun") + loc = filepath.Join(notary.NonRootKeysSubdir, keyID) } } - // this is the path + no-role case where we assume the key is a delegation key - if block.Headers["role"] == "" { - block.Headers["role"] = notary.DefaultImportRole - } - if loc != writeTo { - // next location is different from previous one. We've finished aggregating - // data for the previous file. If we have data, write the previous file, - // the clear toWrite and set writeTo to the next path we're going to write - if toWrite != nil { - if err = importToStores(to, writeTo, toWrite); err != nil { - return err - } - } - // set up for aggregating next file's data - toWrite = nil - writeTo = loc - } + // the path header is not of any use once we've imported the key so strip it away delete(block.Headers, "path") - // check if a key is not encrypted- if it isn't then ask for a passphrase and encrypt it - toSave := pem.EncodeToMemory(block) - if privKey, err := utils.ParsePEMPrivateKey(toSave, ""); err == nil { + // we are now all set for import but let's first encrypt the key + blockBytes := pem.EncodeToMemory(block) + // check if key is encrypted, note: if it is encrypted at this point, it will have had a path header + if privKey, err := utils.ParsePEMPrivateKey(blockBytes, ""); err == nil { // Key is not encrypted- ask for a passphrase and encrypt this key var chosenPassphrase string for attempts := 0; ; attempts++ { @@ -182,10 +169,24 @@ func ImportKeys(from io.Reader, to []Importer, fallbackRole string, fallbackGun } break } - toSave, _ = utils.EncryptPrivateKey(privKey, block.Headers["role"], block.Headers["gun"], chosenPassphrase) + blockBytes, _ = utils.EncryptPrivateKey(privKey, block.Headers["role"], block.Headers["gun"], chosenPassphrase) + } + + if loc != writeTo { + // next location is different from previous one. We've finished aggregating + // data for the previous file. If we have data, write the previous file, + // the clear toWrite and set writeTo to the next path we're going to write + if toWrite != nil { + if err = importToStores(to, writeTo, toWrite); err != nil { + return err + } + } + // set up for aggregating next file's data + toWrite = nil + writeTo = loc } - toWrite = append(toWrite, toSave...) + toWrite = append(toWrite, blockBytes...) } if toWrite != nil { // close out final iteration if there's data left return importToStores(to, writeTo, toWrite) diff --git a/utils/keys_test.go b/utils/keys_test.go index 25db830f6b..abfaa0c5e5 100644 --- a/utils/keys_test.go +++ b/utils/keys_test.go @@ -6,6 +6,7 @@ import ( "encoding/pem" "errors" "github.com/docker/notary" + "github.com/docker/notary/tuf/data" "github.com/stretchr/testify/require" "io/ioutil" "os" @@ -238,6 +239,8 @@ func TestImportKeys(t *testing.T) { c.Bytes, _ = ioutil.ReadAll(from) rand.Read(c.Bytes) c.Headers["path"] = "morpork" + c.Headers["role"] = data.CanonicalSnapshotRole + c.Headers["gun"] = "somegun" bBytes := pem.EncodeToMemory(b) cBytes := pem.EncodeToMemory(c) @@ -260,6 +263,8 @@ func TestImportKeys(t *testing.T) { require.Equal(t, c.Bytes, cFinal.Bytes) _, ok = cFinal.Headers["path"] require.False(t, ok, "expected no path header, should have been removed at import") + require.Equal(t, data.CanonicalSnapshotRole, cFinal.Headers["role"]) + require.Equal(t, "somegun", cFinal.Headers["gun"]) require.Len(t, cRest, 0) } @@ -301,11 +306,11 @@ func TestNonRootPathInference(t *testing.T) { in := bytes.NewBuffer(b.Bytes) - err := ImportKeys(in, []Importer{s}, "somerole", "somegun", passphraseRetriever) + err := ImportKeys(in, []Importer{s}, data.CanonicalSnapshotRole, "somegun", passphraseRetriever) require.NoError(t, err) for key := range s.data { - // no path but role included should work + // no path but role included should work and 12ba0e0a8e05e177bc2c3489bdb6d28836879469f078e68a4812fc8a2d521497 is the key ID of the fixture require.Equal(t, key, filepath.Join(notary.NonRootKeysSubdir, "somegun", "12ba0e0a8e05e177bc2c3489bdb6d28836879469f078e68a4812fc8a2d521497")) } } @@ -326,8 +331,12 @@ func TestBlockHeaderPrecedence(t *testing.T) { require.NoError(t, err) for key := range s.data { - // block header role should take precedence over command line flag - require.Equal(t, key, filepath.Join(notary.RootKeysSubdir, "12ba0e0a8e05e177bc2c3489bdb6d28836879469f078e68a4812fc8a2d521497")) + // 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") } }