docs/cryptoservice/import_export_test.go

492 lines
18 KiB
Go

package cryptoservice
import (
"archive/zip"
"fmt"
"io/ioutil"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"testing"
"github.com/docker/notary"
"github.com/docker/notary/trustmanager"
"github.com/docker/notary/tuf/data"
"github.com/stretchr/testify/require"
)
const timestampECDSAKeyJSON = `
{"keytype":"ecdsa","keyval":{"public":"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEgl3rzMPMEKhS1k/AX16MM4PdidpjJr+z4pj0Td+30QnpbOIARgpyR1PiFztU8BZlqG3cUazvFclr2q/xHvfrqw==","private":"MHcCAQEEIDqtcdzU7H3AbIPSQaxHl9+xYECt7NpK7B1+6ep5cv9CoAoGCCqGSM49AwEHoUQDQgAEgl3rzMPMEKhS1k/AX16MM4PdidpjJr+z4pj0Td+30QnpbOIARgpyR1PiFztU8BZlqG3cUazvFclr2q/xHvfrqw=="}}`
func createTestServer(t *testing.T) (*httptest.Server, *http.ServeMux) {
mux := http.NewServeMux()
// TUF will request /v2/docker.com/notary/_trust/tuf/timestamp.key
// Return a canned timestamp.key
mux.HandleFunc("/v2/docker.com/notary/_trust/tuf/timestamp.key", func(w http.ResponseWriter, r *http.Request) {
// Also contains the private key, but for the purpose of this
// test, we don't care
fmt.Fprint(w, timestampECDSAKeyJSON)
})
ts := httptest.NewServer(mux)
return ts, mux
}
var oldPassphrase = "oldPassphrase"
var exportPassphrase = "exportPassphrase"
var oldPassphraseRetriever = func(string, string, bool, int) (string, bool, error) { return oldPassphrase, false, nil }
var newPassphraseRetriever = func(string, string, bool, int) (string, bool, error) { return exportPassphrase, false, nil }
func TestImportExportZip(t *testing.T) {
gun := "docker.com/notary"
// 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 TestExportRootKey(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, oldPassphraseRetriever)
cs := NewCryptoService(fileStore)
pubKey, err := cs.Create(data.CanonicalRootRole, gun, data.ECDSAKey)
require.NoError(t, err)
rootKeyID := pubKey.ID()
tempKeyFile, err := ioutil.TempFile("", "notary-test-export-")
tempKeyFilePath := tempKeyFile.Name()
defer os.Remove(tempKeyFilePath)
err = cs.ExportKey(tempKeyFile, rootKeyID, data.CanonicalRootRole)
require.NoError(t, err)
tempKeyFile.Close()
// 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, oldPassphraseRetriever)
cs2 := NewCryptoService(fileStore2)
keyReader, err := os.Open(tempKeyFilePath)
require.NoError(t, err, "could not open key file")
pemImportBytes, err := ioutil.ReadAll(keyReader)
keyReader.Close()
require.NoError(t, err)
// Convert to a data.PrivateKey, potentially decrypting the key, and add it to the cryptoservice
privKey, _, err := trustmanager.GetPasswdDecryptBytes(oldPassphraseRetriever, pemImportBytes, "", "imported "+data.CanonicalRootRole)
require.NoError(t, err)
err = cs2.AddKey(data.CanonicalRootRole, gun, privKey)
require.NoError(t, err)
// 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, notary.PrivDir, notary.RootKeysSubdir, rootKeyFilename))
require.NoError(t, err, "missing root key")
}
func TestExportRootKeyReencrypt(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, oldPassphraseRetriever)
cs := NewCryptoService(fileStore)
pubKey, err := cs.Create(data.CanonicalRootRole, gun, data.ECDSAKey)
require.NoError(t, err)
rootKeyID := pubKey.ID()
tempKeyFile, err := ioutil.TempFile("", "notary-test-export-")
tempKeyFilePath := tempKeyFile.Name()
defer os.Remove(tempKeyFilePath)
err = cs.ExportKeyReencrypt(tempKeyFile, rootKeyID, newPassphraseRetriever)
require.NoError(t, err)
tempKeyFile.Close()
// 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)
keyReader, err := os.Open(tempKeyFilePath)
require.NoError(t, err, "could not open key file")
pemImportBytes, err := ioutil.ReadAll(keyReader)
keyReader.Close()
require.NoError(t, err)
// Convert to a data.PrivateKey, potentially decrypting the key, and add it to the cryptoservice
privKey, _, err := trustmanager.GetPasswdDecryptBytes(newPassphraseRetriever, pemImportBytes, "", "imported "+data.CanonicalRootRole)
require.NoError(t, err)
err = cs2.AddKey(data.CanonicalRootRole, gun, privKey)
require.NoError(t, err)
// 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, notary.PrivDir, notary.RootKeysSubdir, rootKeyFilename))
require.NoError(t, err, "missing root key")
// Should be able to unlock the root key with the new password
key, alias, err := cs2.GetPrivateKey(rootKeyID)
require.NoError(t, err, "could not unlock root key")
require.Equal(t, data.CanonicalRootRole, alias)
require.Equal(t, rootKeyID, key.ID())
}
func TestExportNonRootKey(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, oldPassphraseRetriever)
cs := NewCryptoService(fileStore)
pubKey, err := cs.Create(data.CanonicalTargetsRole, gun, data.ECDSAKey)
require.NoError(t, err)
targetsKeyID := pubKey.ID()
tempKeyFile, err := ioutil.TempFile("", "notary-test-export-")
tempKeyFilePath := tempKeyFile.Name()
defer os.Remove(tempKeyFilePath)
err = cs.ExportKey(tempKeyFile, targetsKeyID, data.CanonicalTargetsRole)
require.NoError(t, err)
tempKeyFile.Close()
// 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, oldPassphraseRetriever)
cs2 := NewCryptoService(fileStore2)
keyReader, err := os.Open(tempKeyFilePath)
require.NoError(t, err, "could not open key file")
pemBytes, err := ioutil.ReadAll(keyReader)
require.NoError(t, err, "could not read key file")
// Convert to a data.PrivateKey, potentially decrypting the key, and add it to the cryptoservice
privKey, _, err := trustmanager.GetPasswdDecryptBytes(oldPassphraseRetriever, pemBytes, "", "imported "+data.CanonicalTargetsRole)
require.NoError(t, err)
err = cs2.AddKey(data.CanonicalTargetsRole, gun, privKey)
require.NoError(t, err)
keyReader.Close()
// Look for repo's targets key in repo2
// There should be a file named after the key ID of the targets key we
// imported.
targetsKeyFilename := targetsKeyID + ".key"
_, err = os.Stat(filepath.Join(tempBaseDir2, notary.PrivDir, notary.NonRootKeysSubdir, "docker.com/notary", targetsKeyFilename))
require.NoError(t, err, "missing targets key")
// Check that the key is the same
key, alias, err := cs2.GetPrivateKey(targetsKeyID)
require.NoError(t, err, "could not unlock targets key")
require.Equal(t, data.CanonicalTargetsRole, alias)
require.Equal(t, targetsKeyID, key.ID())
}
func TestExportNonRootKeyReencrypt(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, oldPassphraseRetriever)
cs := NewCryptoService(fileStore)
pubKey, err := cs.Create(data.CanonicalSnapshotRole, gun, data.ECDSAKey)
require.NoError(t, err)
snapshotKeyID := pubKey.ID()
tempKeyFile, err := ioutil.TempFile("", "notary-test-export-")
tempKeyFilePath := tempKeyFile.Name()
defer os.Remove(tempKeyFilePath)
err = cs.ExportKeyReencrypt(tempKeyFile, snapshotKeyID, newPassphraseRetriever)
require.NoError(t, err)
tempKeyFile.Close()
// 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)
keyReader, err := os.Open(tempKeyFilePath)
require.NoError(t, err, "could not open key file")
pemBytes, err := ioutil.ReadAll(keyReader)
require.NoError(t, err, "could not read key file")
// Convert to a data.PrivateKey, potentially decrypting the key, and add it to the cryptoservice
privKey, _, err := trustmanager.GetPasswdDecryptBytes(newPassphraseRetriever, pemBytes, "", "imported "+data.CanonicalSnapshotRole)
require.NoError(t, err)
err = cs2.AddKey(data.CanonicalSnapshotRole, gun, privKey)
require.NoError(t, err)
keyReader.Close()
// Look for repo's snapshot key in repo2
// There should be a file named after the key ID of the snapshot key we
// imported.
snapshotKeyFilename := snapshotKeyID + ".key"
_, err = os.Stat(filepath.Join(tempBaseDir2, notary.PrivDir, notary.NonRootKeysSubdir, "docker.com/notary", snapshotKeyFilename))
require.NoError(t, err, "missing snapshot key")
// Should be able to unlock the root key with the new password
key, alias, err := cs2.GetPrivateKey(snapshotKeyID)
require.NoError(t, err, "could not unlock snapshot key")
require.Equal(t, data.CanonicalSnapshotRole, alias)
require.Equal(t, snapshotKeyID, key.ID())
}