// ImportRoleKey imports a private key in PEM format key from a byte array // It prompts for the key's passphrase to verify the data and to determine // the key ID. func (cs *CryptoService) ImportRoleKey(pemBytes []byte, role string, newPassphraseRetriever passphrase.Retriever) error { var alias string var err error if role == data.CanonicalRootRole { alias = role if err = checkRootKeyIsEncrypted(pemBytes); err != nil { return err } } else { // Parse the private key to get the key ID so that we can import it to the correct location privKey, err := trustmanager.ParsePEMPrivateKey(pemBytes, "") if err != nil { privKey, _, err = trustmanager.GetPasswdDecryptBytes(newPassphraseRetriever, pemBytes, role, string(role)) if err != nil { return err } } // Since we're importing a non-root role, we need to pass the path as an alias alias = filepath.Join(notary.NonRootKeysSubdir, cs.gun, privKey.ID()) // We also need to ensure that the role is properly set in the PEM headers pemBytes, err = trustmanager.KeyToPEM(privKey, role) if err != nil { return err } } for _, ks := range cs.keyStores { // don't redeclare err, we want the value carried out of the loop if err = ks.ImportKey(pemBytes, alias); err == nil { return nil //bail on the first keystore we import to } } return err }
// ImportKeysZip imports keys from a zip file provided as an zip.Reader. The // keys in the root_keys directory are left encrypted, but the other keys are // decrypted with the specified passphrase. func (cs *CryptoService) ImportKeysZip(zipReader zip.Reader, retriever notary.PassRetriever) error { // Temporarily store the keys in maps, so we can bail early if there's // an error (for example, wrong passphrase), without leaving the key // store in an inconsistent state newKeys := make(map[string][]byte) // Iterate through the files in the archive. Don't add the keys for _, f := range zipReader.File { fNameTrimmed := strings.TrimSuffix(f.Name, filepath.Ext(f.Name)) rc, err := f.Open() if err != nil { return err } defer rc.Close() fileBytes, err := ioutil.ReadAll(rc) if err != nil { return nil } // Note that using / as a separator is okay here - the zip // package guarantees that the separator will be / if fNameTrimmed[len(fNameTrimmed)-5:] == "_root" { if err = CheckRootKeyIsEncrypted(fileBytes); err != nil { return err } } newKeys[fNameTrimmed] = fileBytes } for keyName, pemBytes := range newKeys { // Get the key role information as well as its data.PrivateKey representation _, keyInfo, err := trustmanager.KeyInfoFromPEM(pemBytes, keyName) if err != nil { return err } privKey, err := trustmanager.ParsePEMPrivateKey(pemBytes, "") if err != nil { privKey, _, err = trustmanager.GetPasswdDecryptBytes(retriever, pemBytes, "", "imported "+keyInfo.Role) if err != nil { return err } } // Add the key to our cryptoservice, will add to the first successful keystore if err = cs.AddKey(keyInfo.Role, keyInfo.Gun, privKey); err != nil { return err } } return nil }
func moveKeysByGUN(oldKeyStore, newKeyStore *trustmanager.KeyFileStore, gun, outputPassphrase 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)) // Skip keys that aren't associated with this GUN if !strings.HasPrefix(relKeyPath, filepath.FromSlash(gun)) { continue } pemBytes, err := oldKeyStore.Get(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, "") if err != nil { return err } err = newKeyStore.AddEncryptedKey(relKeyPath, privKey, outputPassphrase) if err != nil { return err } } return nil }
func moveKeysWithNewPassphrase(oldKeyStore, newKeyStore *trustmanager.KeyFileStore, outputPassphrase 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)) pemBytes, err := oldKeyStore.Get(relKeyPath) if err != nil { return err } block, _ := pem.Decode(pemBytes) if block == nil { return ErrNoValidPrivateKey } 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) } if err != nil { return err } } return nil }
func TestImportExportZip(t *testing.T) { gun := "docker.com/notary" // Temporary directory where test files will be created tempBaseDir, err := ioutil.TempDir("", "notary-test-") defer os.RemoveAll(tempBaseDir) assert.NoError(t, err, "failed to create a temporary directory: %s", err) ts, _ := createTestServer(t) defer ts.Close() repo, err := client.NewNotaryRepository(tempBaseDir, gun, ts.URL, http.DefaultTransport, oldPassphraseRetriever) assert.NoError(t, err, "error creating repo: %s", err) rootKeyID, err := repo.KeyStoreManager.GenRootKey(data.ECDSAKey.String()) assert.NoError(t, err, "error generating root key: %s", err) rootCryptoService, err := repo.KeyStoreManager.GetRootCryptoService(rootKeyID) assert.NoError(t, err, "error retrieving root key: %s", err) err = repo.Initialize(rootCryptoService) assert.NoError(t, err, "error creating repository: %s", err) tempZipFile, err := ioutil.TempFile("", "notary-test-export-") tempZipFilePath := tempZipFile.Name() defer os.Remove(tempZipFilePath) err = repo.KeyStoreManager.ExportAllKeys(tempZipFile, newPassphraseRetriever) tempZipFile.Close() assert.NoError(t, err) // Reopen the zip file for importing zipReader, err := zip.OpenReader(tempZipFilePath) assert.NoError(t, err, "could not open zip file") // Map of files to expect in the zip file, with the passphrases passphraseByFile := make(map[string]string) // Add non-root keys to the map. These should use the new passphrase // because the passwords were chosen by the newPassphraseRetriever. privKeyMap := repo.KeyStoreManager.NonRootKeyStore().ListKeys() for privKeyName := range privKeyMap { _, alias, err := repo.KeyStoreManager.NonRootKeyStore().GetKey(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 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. for _, f := range zipReader.File { if keystoremanager.IsZipSymlink(f) { continue } expectedPassphrase, present := passphraseByFile[f.Name] if !present { t.Fatalf("unexpected file %s in zip file", f.Name) } delete(passphraseByFile, f.Name) rc, err := f.Open() assert.NoError(t, err, "could not open file inside zip archive") pemBytes, err := ioutil.ReadAll(rc) assert.NoError(t, err, "could not read file from zip") _, err = trustmanager.ParsePEMPrivateKey(pemBytes, expectedPassphrase) assert.NoError(t, err, "PEM not encrypted with the expected passphrase") rc.Close() } zipReader.Close() // Are there any keys that didn't make it to the zip? for fileNotFound := range passphraseByFile { t.Fatalf("%s not found in zip", fileNotFound) } // Create new repo to test import tempBaseDir2, err := ioutil.TempDir("", "notary-test-") defer os.RemoveAll(tempBaseDir2) assert.NoError(t, err, "failed to create a temporary directory: %s", err) repo2, err := client.NewNotaryRepository(tempBaseDir2, gun, ts.URL, http.DefaultTransport, newPassphraseRetriever) assert.NoError(t, err, "error creating repo: %s", err) rootKeyID2, err := repo2.KeyStoreManager.GenRootKey(data.ECDSAKey.String()) assert.NoError(t, err, "error generating root key: %s", err) rootCryptoService2, err := repo2.KeyStoreManager.GetRootCryptoService(rootKeyID2) assert.NoError(t, err, "error retrieving root key: %s", err) err = repo2.Initialize(rootCryptoService2) assert.NoError(t, err, "error creating repository: %s", err) // 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) assert.NoError(t, err) zipReader.Close() // Look for keys in private. The filenames should match the key IDs // in the repo's private key store. for privKeyName := range privKeyMap { _, alias, err := repo.KeyStoreManager.NonRootKeyStore().GetKey(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() + "_root.key" _, err = os.Stat(filepath.Join(tempBaseDir2, "private", "root_keys", rootKeyFilename)) assert.NoError(t, err, "missing root key") // Look for symlinks in the new repo matching symlinks in the old repo numSymlinks := 0 oldRootKeyStore := repo.KeyStoreManager.RootKeyStore() newRootKeyStore := repo2.KeyStoreManager.RootKeyStore() for _, relKeyPath := range oldRootKeyStore.ListFiles(true) { fullKeyPath := filepath.Join(oldRootKeyStore.BaseDir(), relKeyPath) fi, err := os.Lstat(fullKeyPath) assert.NoError(t, err, "lstat failed") if (fi.Mode() & os.ModeSymlink) != 0 { numSymlinks++ oldTarget, err := os.Readlink(fullKeyPath) assert.NoError(t, err, "readlink failed on old repo") newTarget, err := os.Readlink(filepath.Join(newRootKeyStore.BaseDir(), relKeyPath)) assert.NoError(t, err, "symlink not found in new repo") assert.Equal(t, oldTarget, newTarget, "symlink targets do not match") } } if numSymlinks == 0 { t.Fatal("no symlinks found in original repo") } }
func TestImportExportRootKey(t *testing.T) { gun := "docker.com/notary" // Temporary directory where test files will be created tempBaseDir, err := ioutil.TempDir("", "notary-test-") defer os.RemoveAll(tempBaseDir) assert.NoError(t, err, "failed to create a temporary directory: %s", err) ts, _ := createTestServer(t) defer ts.Close() repo, err := client.NewNotaryRepository(tempBaseDir, gun, ts.URL, http.DefaultTransport, oldPassphraseRetriever) assert.NoError(t, err, "error creating repo: %s", err) rootKeyID, err := repo.KeyStoreManager.GenRootKey(data.ECDSAKey.String()) assert.NoError(t, err, "error generating root key: %s", err) rootCryptoService, err := repo.KeyStoreManager.GetRootCryptoService(rootKeyID) assert.NoError(t, err, "error retrieving root key: %s", err) err = repo.Initialize(rootCryptoService) assert.NoError(t, err, "error creating repository: %s", err) tempKeyFile, err := ioutil.TempFile("", "notary-test-export-") tempKeyFilePath := tempKeyFile.Name() defer os.Remove(tempKeyFilePath) err = repo.KeyStoreManager.ExportRootKey(tempKeyFile, rootKeyID) assert.NoError(t, err) tempKeyFile.Close() // Create new repo to test import tempBaseDir2, err := ioutil.TempDir("", "notary-test-") defer os.RemoveAll(tempBaseDir2) assert.NoError(t, err, "failed to create a temporary directory: %s", err) repo2, err := client.NewNotaryRepository(tempBaseDir2, gun, ts.URL, http.DefaultTransport, oldPassphraseRetriever) assert.NoError(t, err, "error creating repo: %s", err) rootKeyID2, err := repo2.KeyStoreManager.GenRootKey(data.ECDSAKey.String()) assert.NoError(t, err, "error generating root key: %s", err) rootCryptoService2, err := repo2.KeyStoreManager.GetRootCryptoService(rootKeyID2) assert.NoError(t, err, "error retrieving root key: %s", err) err = repo2.Initialize(rootCryptoService2) assert.NoError(t, err, "error creating repository: %s", err) keyReader, err := os.Open(tempKeyFilePath) assert.NoError(t, err, "could not open key file") err = repo2.KeyStoreManager.ImportRootKey(keyReader, rootKeyID) assert.NoError(t, err) keyReader.Close() // 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 + "_root.key" _, err = os.Stat(filepath.Join(tempBaseDir2, "private", "root_keys", rootKeyFilename)) assert.NoError(t, err, "missing root key") // Try to import a decrypted version of the root key and make sure it // doesn't succeed pemBytes, err := ioutil.ReadFile(tempKeyFilePath) assert.NoError(t, err, "could not read key file") privKey, err := trustmanager.ParsePEMPrivateKey(pemBytes, oldPassphrase) assert.NoError(t, err, "could not decrypt key file") decryptedPEMBytes, err := trustmanager.KeyToPEM(privKey) assert.NoError(t, err, "could not convert key to PEM") err = repo2.KeyStoreManager.ImportRootKey(bytes.NewReader(decryptedPEMBytes), rootKeyID) assert.EqualError(t, err, keystoremanager.ErrRootKeyNotEncrypted.Error()) // Try to import garbage and make sure it doesn't succeed err = repo2.KeyStoreManager.ImportRootKey(strings.NewReader("this is not PEM"), rootKeyID) assert.EqualError(t, err, keystoremanager.ErrNoValidPrivateKey.Error()) // Should be able to unlock the root key with the old password key, alias, err := repo2.KeyStoreManager.RootKeyStore().GetKey(rootKeyID) assert.NoError(t, err, "could not unlock root key") assert.Equal(t, "root", alias) assert.Equal(t, rootKeyID, key.ID()) }
func TestImportExportGUN(t *testing.T) { gun := "docker.com/notary" // Temporary directory where test files will be created tempBaseDir, err := ioutil.TempDir("", "notary-test-") defer os.RemoveAll(tempBaseDir) assert.NoError(t, err, "failed to create a temporary directory: %s", err) ts, _ := createTestServer(t) defer ts.Close() repo, err := client.NewNotaryRepository(tempBaseDir, gun, ts.URL, http.DefaultTransport, oldPassphraseRetriever) assert.NoError(t, err, "error creating repo: %s", err) rootKeyID, err := repo.KeyStoreManager.GenRootKey(data.ECDSAKey.String()) assert.NoError(t, err, "error generating root key: %s", err) rootCryptoService, err := repo.KeyStoreManager.GetRootCryptoService(rootKeyID) assert.NoError(t, err, "error retrieving root key: %s", err) err = repo.Initialize(rootCryptoService) assert.NoError(t, err, "error creating repository: %s", err) tempZipFile, err := ioutil.TempFile("", "notary-test-export-") tempZipFilePath := tempZipFile.Name() defer os.Remove(tempZipFilePath) 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", newPassphraseRetriever) assert.EqualError(t, err, keystoremanager.ErrNoKeysFoundForGUN.Error()) tempZipFile.Close() // Reopen the zip file for importing zipReader, err := zip.OpenReader(tempZipFilePath) assert.NoError(t, err, "could not open zip file") // Map of files to expect in the zip file, with the passphrases passphraseByFile := make(map[string]string) // Add keys non-root keys to the map. These should use the new passphrase // because they were formerly unencrypted. privKeyMap := repo.KeyStoreManager.NonRootKeyStore().ListKeys() for privKeyName := range privKeyMap { _, alias, err := repo.KeyStoreManager.NonRootKeyStore().GetKey(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) } delete(passphraseByFile, f.Name) rc, err := f.Open() assert.NoError(t, err, "could not open file inside zip archive") pemBytes, err := ioutil.ReadAll(rc) assert.NoError(t, err, "could not read file from zip") _, err = trustmanager.ParsePEMPrivateKey(pemBytes, expectedPassphrase) assert.NoError(t, err, "PEM not encrypted with the expected passphrase") rc.Close() } zipReader.Close() // Are there any keys that didn't make it to the zip? for fileNotFound := range passphraseByFile { t.Fatalf("%s not found in zip", fileNotFound) } // Create new repo to test import tempBaseDir2, err := ioutil.TempDir("", "notary-test-") defer os.RemoveAll(tempBaseDir2) assert.NoError(t, err, "failed to create a temporary directory: %s", err) repo2, err := client.NewNotaryRepository(tempBaseDir2, gun, ts.URL, http.DefaultTransport, oldPassphraseRetriever) assert.NoError(t, err, "error creating repo: %s", err) rootKeyID2, err := repo2.KeyStoreManager.GenRootKey(data.ECDSAKey.String()) assert.NoError(t, err, "error generating root key: %s", err) rootCryptoService2, err := repo2.KeyStoreManager.GetRootCryptoService(rootKeyID2) assert.NoError(t, err, "error retrieving root key: %s", err) err = repo2.Initialize(rootCryptoService2) assert.NoError(t, err, "error creating repository: %s", err) // 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) assert.NoError(t, err) zipReader.Close() // Look for keys in private. The filenames should match the key IDs // in the repo's private key store. for privKeyName := range privKeyMap { _, alias, err := repo.KeyStoreManager.NonRootKeyStore().GetKey(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 TestImportExportZip(t *testing.T) { gun := "docker.com/notary" // Temporary directory where test files will be created tempBaseDir, err := ioutil.TempDir("", "notary-test-") defer os.RemoveAll(tempBaseDir) require.NoError(t, err, "failed to create a temporary directory: %s", err) fileStore, err := trustmanager.NewKeyFileStore(tempBaseDir, newPassphraseRetriever) cs := NewCryptoService(fileStore) pubKey, err := cs.Create(data.CanonicalRootRole, gun, data.ECDSAKey) require.NoError(t, err) rootKeyID := pubKey.ID() tempZipFile, err := ioutil.TempFile("", "notary-test-export-") tempZipFilePath := tempZipFile.Name() defer os.Remove(tempZipFilePath) err = cs.ExportAllKeys(tempZipFile, newPassphraseRetriever) tempZipFile.Close() require.NoError(t, err) // Reopen the zip file for importing zipReader, err := zip.OpenReader(tempZipFilePath) require.NoError(t, err, "could not open zip file") // Map of files to expect in the zip file, with the passphrases passphraseByFile := make(map[string]string) // Add non-root keys to the map. These should use the new passphrase // because the passwords were chosen by the newPassphraseRetriever. privKeyMap := cs.ListAllKeys() for privKeyName := range privKeyMap { _, alias, err := cs.GetPrivateKey(privKeyName) require.NoError(t, err, "privKey %s has no alias", privKeyName) if alias == data.CanonicalRootRole { continue } relKeyPath := filepath.Join(notary.NonRootKeysSubdir, privKeyName+".key") passphraseByFile[relKeyPath] = exportPassphrase } // Add root key to the map. This will use the export passphrase because it // will be reencrypted. relRootKey := filepath.Join(notary.RootKeysSubdir, rootKeyID+".key") passphraseByFile[relRootKey] = 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] require.True(t, present, "unexpected file %s in zip file", f.Name) delete(passphraseByFile, f.Name) rc, err := f.Open() require.NoError(t, err, "could not open file inside zip archive") pemBytes, err := ioutil.ReadAll(rc) require.NoError(t, err, "could not read file from zip") _, err = trustmanager.ParsePEMPrivateKey(pemBytes, expectedPassphrase) require.NoError(t, err, "PEM not encrypted with the expected passphrase") rc.Close() } zipReader.Close() // Are there any keys that didn't make it to the zip? require.Len(t, passphraseByFile, 0) // Create new repo to test import tempBaseDir2, err := ioutil.TempDir("", "notary-test-") defer os.RemoveAll(tempBaseDir2) require.NoError(t, err, "failed to create a temporary directory: %s", err) fileStore2, err := trustmanager.NewKeyFileStore(tempBaseDir2, newPassphraseRetriever) require.NoError(t, err) cs2 := NewCryptoService(fileStore2) // Reopen the zip file for importing zipReader, err = zip.OpenReader(tempZipFilePath) require.NoError(t, err, "could not open zip file") // Now try with a valid passphrase. This time it should succeed. err = cs2.ImportKeysZip(zipReader.Reader, newPassphraseRetriever) require.NoError(t, err) zipReader.Close() // Look for keys in private. The filenames should match the key IDs // in the repo's private key store. for privKeyName := range privKeyMap { _, alias, err := cs2.GetPrivateKey(privKeyName) require.NoError(t, err, "privKey %s has no alias", privKeyName) if alias == data.CanonicalRootRole { continue } relKeyPath := filepath.Join(notary.NonRootKeysSubdir, privKeyName+".key") privKeyFileName := filepath.Join(tempBaseDir2, notary.PrivDir, relKeyPath) _, err = os.Stat(privKeyFileName) require.NoError(t, err, "missing private key for role %s: %s", alias, 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 := rootKeyID + ".key" _, err = os.Stat(filepath.Join(tempBaseDir2, notary.PrivDir, notary.RootKeysSubdir, rootKeyFilename)) require.NoError(t, err, "missing root key") }
func TestImportExportGUN(t *testing.T) { gun := "docker.com/notary" // Temporary directory where test files will be created tempBaseDir, err := ioutil.TempDir("", "notary-test-") defer os.RemoveAll(tempBaseDir) require.NoError(t, err, "failed to create a temporary directory: %s", err) fileStore, err := trustmanager.NewKeyFileStore(tempBaseDir, newPassphraseRetriever) cs := NewCryptoService(fileStore) _, err = cs.Create(data.CanonicalRootRole, gun, data.ECDSAKey) _, err = cs.Create(data.CanonicalTargetsRole, gun, data.ECDSAKey) _, err = cs.Create(data.CanonicalSnapshotRole, gun, data.ECDSAKey) require.NoError(t, err) tempZipFile, err := ioutil.TempFile("", "notary-test-export-") tempZipFilePath := tempZipFile.Name() defer os.Remove(tempZipFilePath) err = cs.ExportKeysByGUN(tempZipFile, gun, newPassphraseRetriever) require.NoError(t, err) // With an invalid GUN, this should return an error err = cs.ExportKeysByGUN(tempZipFile, "does.not.exist/in/repository", newPassphraseRetriever) require.EqualError(t, err, ErrNoKeysFoundForGUN.Error()) tempZipFile.Close() // Reopen the zip file for importing zipReader, err := zip.OpenReader(tempZipFilePath) require.NoError(t, err, "could not open zip file") // Map of files to expect in the zip file, with the passphrases passphraseByFile := make(map[string]string) // Add keys non-root keys to the map. These should use the new passphrase // because they were formerly unencrypted. privKeyMap := cs.ListAllKeys() for privKeyName := range privKeyMap { _, alias, err := cs.GetPrivateKey(privKeyName) require.NoError(t, err, "privKey %s has no alias", privKeyName) if alias == data.CanonicalRootRole { continue } relKeyPath := filepath.Join(notary.NonRootKeysSubdir, gun, privKeyName+".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] require.True(t, present, "unexpected file %s in zip file", f.Name) delete(passphraseByFile, f.Name) rc, err := f.Open() require.NoError(t, err, "could not open file inside zip archive") pemBytes, err := ioutil.ReadAll(rc) require.NoError(t, err, "could not read file from zip") _, err = trustmanager.ParsePEMPrivateKey(pemBytes, expectedPassphrase) require.NoError(t, err, "PEM not encrypted with the expected passphrase") rc.Close() } zipReader.Close() // Are there any keys that didn't make it to the zip? require.Len(t, passphraseByFile, 0) // Create new repo to test import tempBaseDir2, err := ioutil.TempDir("", "notary-test-") defer os.RemoveAll(tempBaseDir2) require.NoError(t, err, "failed to create a temporary directory: %s", err) fileStore2, err := trustmanager.NewKeyFileStore(tempBaseDir2, newPassphraseRetriever) cs2 := NewCryptoService(fileStore2) // Reopen the zip file for importing zipReader, err = zip.OpenReader(tempZipFilePath) require.NoError(t, err, "could not open zip file") // Now try with a valid passphrase. This time it should succeed. err = cs2.ImportKeysZip(zipReader.Reader, newPassphraseRetriever) require.NoError(t, err) zipReader.Close() // Look for keys in private. The filenames should match the key IDs // in the repo's private key store. for privKeyName, role := range privKeyMap { if role == data.CanonicalRootRole { continue } _, alias, err := cs2.GetPrivateKey(privKeyName) require.NoError(t, err, "privKey %s has no alias", privKeyName) if alias == data.CanonicalRootRole { continue } relKeyPath := filepath.Join(notary.NonRootKeysSubdir, gun, privKeyName+".key") privKeyFileName := filepath.Join(tempBaseDir2, notary.PrivDir, relKeyPath) _, err = os.Stat(privKeyFileName) require.NoError(t, err) } }
func TestImportExportRootKey(t *testing.T) { gun := "docker.com/notary" // Temporary directory where test files will be created tempBaseDir, err := ioutil.TempDir("", "notary-test-") defer os.RemoveAll(tempBaseDir) assert.NoError(t, err, "failed to create a temporary directory: %s", err) fileStore, err := trustmanager.NewKeyFileStore(tempBaseDir, oldPassphraseRetriever) cs := NewCryptoService(gun, fileStore) pubKey, err := cs.Create(data.CanonicalRootRole, data.ECDSAKey) assert.NoError(t, err) rootKeyID := pubKey.ID() tempKeyFile, err := ioutil.TempFile("", "notary-test-export-") tempKeyFilePath := tempKeyFile.Name() defer os.Remove(tempKeyFilePath) err = cs.ExportRootKey(tempKeyFile, rootKeyID) assert.NoError(t, err) tempKeyFile.Close() // Create new repo to test import tempBaseDir2, err := ioutil.TempDir("", "notary-test-") defer os.RemoveAll(tempBaseDir2) assert.NoError(t, err, "failed to create a temporary directory: %s", err) fileStore2, err := trustmanager.NewKeyFileStore(tempBaseDir2, oldPassphraseRetriever) cs2 := NewCryptoService(gun, fileStore2) keyReader, err := os.Open(tempKeyFilePath) assert.NoError(t, err, "could not open key file") err = cs2.ImportRootKey(keyReader) assert.NoError(t, err) keyReader.Close() // 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" _, err = os.Stat(filepath.Join(tempBaseDir2, "private", "root_keys", rootKeyFilename)) assert.NoError(t, err, "missing root key") // Try to import a decrypted version of the root key and make sure it // doesn't succeed pemBytes, err := ioutil.ReadFile(tempKeyFilePath) assert.NoError(t, err, "could not read key file") privKey, err := trustmanager.ParsePEMPrivateKey(pemBytes, oldPassphrase) assert.NoError(t, err, "could not decrypt key file") decryptedPEMBytes, err := trustmanager.KeyToPEM(privKey, "root") assert.NoError(t, err, "could not convert key to PEM") err = cs2.ImportRootKey(bytes.NewReader(decryptedPEMBytes)) assert.EqualError(t, err, ErrRootKeyNotEncrypted.Error()) // Try to import garbage and make sure it doesn't succeed err = cs2.ImportRootKey(strings.NewReader("this is not PEM")) assert.EqualError(t, err, ErrNoValidPrivateKey.Error()) // Should be able to unlock the root key with the old password key, alias, err := cs2.GetPrivateKey(rootKeyID) assert.NoError(t, err, "could not unlock root key") assert.Equal(t, "root", alias) assert.Equal(t, rootKeyID, key.ID()) }
// keysImport imports a private key from a PEM file for a role func (k *keyCommander) keysImport(cmd *cobra.Command, args []string) error { if len(args) != 1 { cmd.Usage() return fmt.Errorf("Must specify input filename for import") } config, err := k.configGetter() if err != nil { return err } ks, err := k.getKeyStores(config, true, false) if err != nil { return err } importFilename := args[0] importFile, err := os.Open(importFilename) if err != nil { return fmt.Errorf("Opening file for import: %v", err) } defer importFile.Close() pemBytes, err := ioutil.ReadAll(importFile) if err != nil { return fmt.Errorf("Error reading input file: %v", err) } pemRole := trustmanager.ReadRoleFromPEM(pemBytes) // If the PEM key doesn't have a role in it, we must have --role set if pemRole == "" && k.keysImportRole == "" { return fmt.Errorf("Could not infer role, and no role was specified for key") } // If both PEM role and a --role are provided and they don't match, error if pemRole != "" && k.keysImportRole != "" && pemRole != k.keysImportRole { return fmt.Errorf("Specified role %s does not match role %s in PEM headers", k.keysImportRole, pemRole) } // Determine which role to add to between PEM headers and --role flag: var importRole string if k.keysImportRole != "" { importRole = k.keysImportRole } else { importRole = pemRole } // If we're importing to targets or snapshot, we need a GUN if (importRole == data.CanonicalTargetsRole || importRole == data.CanonicalSnapshotRole) && k.keysImportGUN == "" { return fmt.Errorf("Must specify GUN for %s key", importRole) } // Root keys must be encrypted if importRole == data.CanonicalRootRole { if err = cryptoservice.CheckRootKeyIsEncrypted(pemBytes); err != nil { return err } } cs := cryptoservice.NewCryptoService(ks...) // Convert to a data.PrivateKey, potentially decrypting the key privKey, err := trustmanager.ParsePEMPrivateKey(pemBytes, "") if err != nil { privKey, _, err = trustmanager.GetPasswdDecryptBytes(k.getRetriever(), pemBytes, "", "imported "+importRole) if err != nil { return err } } err = cs.AddKey(importRole, k.keysImportGUN, privKey) if err != nil { return fmt.Errorf("Error importing key: %v", err) } return nil }
func TestValidateRootWithPinnedCA(t *testing.T) { var testSignedRoot data.Signed var signedRootBytes bytes.Buffer // Temporary directory where test files will be created tempBaseDir, err := ioutil.TempDir("", "notary-test-") defer os.RemoveAll(tempBaseDir) require.NoError(t, err, "failed to create a temporary directory: %s", err) templ, _ := template.New("SignedRSARootTemplate").Parse(signedRSARootTemplate) templ.Execute(&signedRootBytes, SignedRSARootTemplate{RootPem: validPEMEncodedRSARoot}) // Unmarshal our signedRoot json.Unmarshal(signedRootBytes.Bytes(), &testSignedRoot) typedSignedRoot, err := data.RootFromSigned(&testSignedRoot) require.NoError(t, err) // This call to trustpinning.ValidateRoot will fail because we have an invalid path for the CA _, err = trustpinning.ValidateRoot(nil, &testSignedRoot, "docker.com/notary", trustpinning.TrustPinConfig{CA: map[string]string{"docker.com/notary": filepath.Join(tempBaseDir, "nonexistent")}}) require.Error(t, err) // This call to trustpinning.ValidateRoot will fail because we have no valid GUNs to use, and TOFUS is disabled _, err = trustpinning.ValidateRoot(nil, &testSignedRoot, "docker.com/notary", trustpinning.TrustPinConfig{CA: map[string]string{"othergun": filepath.Join(tempBaseDir, "nonexistent")}, DisableTOFU: true}) require.Error(t, err) // This call to trustpinning.ValidateRoot will succeed because we have no valid GUNs to use and we fall back to enabled TOFUS validatedRoot, err := trustpinning.ValidateRoot(nil, &testSignedRoot, "docker.com/notary", trustpinning.TrustPinConfig{CA: map[string]string{"othergun": filepath.Join(tempBaseDir, "nonexistent")}, DisableTOFU: false}) require.NoError(t, err) generateRootKeyIDs(typedSignedRoot) require.Equal(t, typedSignedRoot, validatedRoot) // Write an invalid CA cert (not even a PEM) to the tempDir and ensure validation fails when using it invalidCAFilepath := filepath.Join(tempBaseDir, "invalid.ca") require.NoError(t, ioutil.WriteFile(invalidCAFilepath, []byte("ABSOLUTELY NOT A PEM"), 0644)) // Using this invalid CA cert should fail on trustpinning.ValidateRoot _, err = trustpinning.ValidateRoot(nil, &testSignedRoot, "docker.com/notary", trustpinning.TrustPinConfig{CA: map[string]string{"docker.com/notary": invalidCAFilepath}, DisableTOFU: true}) require.Error(t, err) validCAFilepath := "../fixtures/root-ca.crt" // If we pass an invalid Certs entry in addition to this valid CA entry, since Certs has priority for pinning we will fail _, err = trustpinning.ValidateRoot(nil, &testSignedRoot, "docker.com/notary", trustpinning.TrustPinConfig{Certs: map[string][]string{"docker.com/notary": {"invalidID"}}, CA: map[string]string{"docker.com/notary": validCAFilepath}, DisableTOFU: true}) require.Error(t, err) // Now construct a new root with a valid cert chain, such that signatures are correct over the 'notary-signer' GUN. Pin the root-ca and validate leafCert, err := trustmanager.LoadCertFromFile("../fixtures/notary-signer.crt") require.NoError(t, err) intermediateCert, err := trustmanager.LoadCertFromFile("../fixtures/intermediate-ca.crt") require.NoError(t, err) pemChainBytes, err := trustmanager.CertChainToPEM([]*x509.Certificate{leafCert, intermediateCert}) require.NoError(t, err) newRootKey := data.NewPublicKey(data.RSAx509Key, pemChainBytes) rootRole, err := data.NewRole(data.CanonicalRootRole, 1, []string{newRootKey.ID()}, nil) require.NoError(t, err) testRoot, err := data.NewRoot( map[string]data.PublicKey{newRootKey.ID(): newRootKey}, map[string]*data.RootRole{ data.CanonicalRootRole: &rootRole.RootRole, data.CanonicalTimestampRole: &rootRole.RootRole, data.CanonicalTargetsRole: &rootRole.RootRole, data.CanonicalSnapshotRole: &rootRole.RootRole}, false, ) testRoot.Signed.Version = 1 require.NoError(t, err, "Failed to create new root") keyReader, err := os.Open("../fixtures/notary-signer.key") require.NoError(t, err, "could not open key file") pemBytes, err := ioutil.ReadAll(keyReader) require.NoError(t, err, "could not read key file") privKey, err := trustmanager.ParsePEMPrivateKey(pemBytes, "") require.NoError(t, err) store, err := trustmanager.NewKeyFileStore(tempBaseDir, passphraseRetriever) require.NoError(t, err) cs := cryptoservice.NewCryptoService(store) err = store.AddKey(trustmanager.KeyInfo{Role: data.CanonicalRootRole, Gun: "notary-signer"}, privKey) require.NoError(t, err) newTestSignedRoot, err := testRoot.ToSigned() require.NoError(t, err) err = signed.Sign(cs, newTestSignedRoot, []data.PublicKey{newRootKey}, 1, nil) require.NoError(t, err) newTypedSignedRoot, err := data.RootFromSigned(newTestSignedRoot) require.NoError(t, err) // Check that we validate correctly against a pinned CA and provided bundle validatedRoot, err = trustpinning.ValidateRoot(nil, newTestSignedRoot, "notary-signer", trustpinning.TrustPinConfig{CA: map[string]string{"notary-signer": validCAFilepath}, DisableTOFU: true}) require.NoError(t, err) generateRootKeyIDs(newTypedSignedRoot) require.Equal(t, newTypedSignedRoot, validatedRoot) // Add an expired CA for the same gun to our previous pinned bundle, ensure that we still validate correctly goodRootCABundle, err := trustmanager.LoadCertBundleFromFile(validCAFilepath) require.NoError(t, err) memKeyStore := trustmanager.NewKeyMemoryStore(passphraseRetriever) cryptoService := cryptoservice.NewCryptoService(memKeyStore) testPubKey, err := cryptoService.Create("root", "notary-signer", data.ECDSAKey) require.NoError(t, err) testPrivKey, _, err := memKeyStore.GetKey(testPubKey.ID()) require.NoError(t, err) expiredCert, err := generateExpiredTestingCertificate(testPrivKey, "notary-signer") require.NoError(t, err) bundleWithExpiredCert, err := trustmanager.CertChainToPEM(append(goodRootCABundle, expiredCert)) require.NoError(t, err) bundleWithExpiredCertPath := filepath.Join(tempBaseDir, "bundle_with_expired_cert.pem") require.NoError(t, ioutil.WriteFile(bundleWithExpiredCertPath, bundleWithExpiredCert, 0644)) // Check that we validate correctly against a pinned CA and provided bundle validatedRoot, err = trustpinning.ValidateRoot(nil, newTestSignedRoot, "notary-signer", trustpinning.TrustPinConfig{CA: map[string]string{"notary-signer": bundleWithExpiredCertPath}, DisableTOFU: true}) require.NoError(t, err) require.Equal(t, newTypedSignedRoot, validatedRoot) testPubKey2, err := cryptoService.Create("root", "notary-signer", data.ECDSAKey) require.NoError(t, err) testPrivKey2, _, err := memKeyStore.GetKey(testPubKey2.ID()) require.NoError(t, err) expiredCert2, err := generateExpiredTestingCertificate(testPrivKey2, "notary-signer") require.NoError(t, err) allExpiredCertBundle, err := trustmanager.CertChainToPEM([]*x509.Certificate{expiredCert, expiredCert2}) require.NoError(t, err) allExpiredCertPath := filepath.Join(tempBaseDir, "all_expired_cert.pem") require.NoError(t, ioutil.WriteFile(allExpiredCertPath, allExpiredCertBundle, 0644)) // Now only use expired certs in the bundle, we should fail _, err = trustpinning.ValidateRoot(nil, newTestSignedRoot, "notary-signer", trustpinning.TrustPinConfig{CA: map[string]string{"notary-signer": allExpiredCertPath}, DisableTOFU: true}) require.Error(t, err) // Add a CA cert for a that won't validate against the root leaf certificate testPubKey3, err := cryptoService.Create("root", "notary-signer", data.ECDSAKey) require.NoError(t, err) testPrivKey3, _, err := memKeyStore.GetKey(testPubKey3.ID()) require.NoError(t, err) validCert, err := cryptoservice.GenerateCertificate(testPrivKey3, "notary-signer", time.Now(), time.Now().AddDate(1, 0, 0)) require.NoError(t, err) bundleWithWrongCert, err := trustmanager.CertChainToPEM([]*x509.Certificate{validCert}) require.NoError(t, err) bundleWithWrongCertPath := filepath.Join(tempBaseDir, "bundle_with_expired_cert.pem") require.NoError(t, ioutil.WriteFile(bundleWithWrongCertPath, bundleWithWrongCert, 0644)) _, err = trustpinning.ValidateRoot(nil, newTestSignedRoot, "notary-signer", trustpinning.TrustPinConfig{CA: map[string]string{"notary-signer": bundleWithWrongCertPath}, DisableTOFU: true}) require.Error(t, err) }
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-") defer os.RemoveAll(tempBaseDir) assert.NoError(t, err, "failed to create a temporary directory: %s", err) ts, _ := createTestServer(t) defer ts.Close() repo, err := client.NewNotaryRepository(tempBaseDir, gun, ts.URL, http.DefaultTransport) assert.NoError(t, err, "error creating repo: %s", err) rootKeyID, err := repo.KeyStoreManager.GenRootKey(data.ECDSAKey.String(), oldPassphrase) assert.NoError(t, err, "error generating root key: %s", err) rootCryptoService, err := repo.KeyStoreManager.GetRootCryptoService(rootKeyID, oldPassphrase) assert.NoError(t, err, "error retrieving root key: %s", err) err = repo.Initialize(rootCryptoService) assert.NoError(t, err, "error creating repository: %s", err) tempZipFile, err := ioutil.TempFile("", "notary-test-export-") tempZipFilePath := tempZipFile.Name() defer os.Remove(tempZipFilePath) err = repo.KeyStoreManager.ExportAllKeys(tempZipFile, exportPassphrase) tempZipFile.Close() assert.NoError(t, err) // Reopen the zip file for importing zipReader, err := zip.OpenReader(tempZipFilePath) assert.NoError(t, err, "could not open zip file") // Map of files to expect in the zip file, with the passphrases 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) for _, privKeyName := range privKeyList { relName := strings.TrimPrefix(privKeyName, tempBaseDir+string(filepath.Separator)) passphraseByFile[relName] = 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 // 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) } delete(passphraseByFile, f.Name) rc, err := f.Open() assert.NoError(t, err, "could not open file inside zip archive") pemBytes, err := ioutil.ReadAll(rc) assert.NoError(t, err, "could not read file from zip") _, err = trustmanager.ParsePEMPrivateKey(pemBytes, expectedPassphrase) assert.NoError(t, err, "PEM not encrypted with the expected passphrase") rc.Close() } zipReader.Close() // Are there any keys that didn't make it to the zip? for fileNotFound := range passphraseByFile { t.Fatalf("%s not found in zip", fileNotFound) } // Create new repo to test import tempBaseDir2, err := ioutil.TempDir("", "notary-test-") defer os.RemoveAll(tempBaseDir2) assert.NoError(t, err, "failed to create a temporary directory: %s", err) repo2, err := client.NewNotaryRepository(tempBaseDir2, gun, ts.URL, http.DefaultTransport) assert.NoError(t, err, "error creating repo: %s", err) rootKeyID2, err := repo2.KeyStoreManager.GenRootKey(data.ECDSAKey.String(), "oldPassphrase") assert.NoError(t, err, "error generating root key: %s", err) rootCryptoService2, err := repo2.KeyStoreManager.GetRootCryptoService(rootKeyID2, "oldPassphrase") assert.NoError(t, err, "error retrieving root key: %s", err) err = repo2.Initialize(rootCryptoService2) assert.NoError(t, err, "error creating repository: %s", err) // Reopen the zip file for importing 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) 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)) 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" _, err = os.Stat(filepath.Join(tempBaseDir2, "private", "root_keys", rootKeyFilename)) assert.NoError(t, err, "missing root key") }
// 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 { // 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) // Note that using / as a separator is okay here - the zip package // guarantees that the separator will be / rootKeysPrefix := privDir + "/" + rootKeysSubdir + "/" nonRootKeysPrefix := privDir + "/" + nonRootKeysSubdir + "/" // Iterate through the files in the archive. Don't add the keys for _, f := range zipReader.File { fNameTrimmed := strings.TrimSuffix(f.Name, filepath.Ext(f.Name)) rc, err := f.Open() if err != nil { return err } pemBytes, err := ioutil.ReadAll(rc) if err != nil { return nil } // Is this in the root_keys directory? // 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 { rc.Close() return err } // Root keys are preserved without decrypting keyName := strings.TrimPrefix(fNameTrimmed, rootKeysPrefix) newRootKeys[keyName] = pemBytes } 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 } keyName := strings.TrimPrefix(fNameTrimmed, nonRootKeysPrefix) newNonRootKeys[keyName] = key } else { // This path inside the zip archive doesn't look like a // root key or a non-root key. 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) rc.Close() continue } rc.Close() } for keyName, pemBytes := range newRootKeys { if err := km.rootKeyStore.Add(keyName, pemBytes); err != nil { return err } } for keyName, privKey := range newNonRootKeys { if err := km.nonRootKeyStore.AddKey(keyName, privKey); err != nil { return err } } return nil }