add tests, consts and fixup

Signed-off-by: Riyaz Faizullabhoy <riyaz.faizullabhoy@docker.com>
This commit is contained in:
Riyaz Faizullabhoy 2016-02-02 15:35:36 -08:00
parent 690fcb96da
commit caa9581bcc
7 changed files with 212 additions and 45 deletions

View File

@ -674,6 +674,47 @@ func TestClientDelegationsPublishing(t *testing.T) {
output, err = runCommand(t, tempDir, "-s", server.URL, "list", "gun", "--roles", "targets/releases")
assert.NoError(t, err)
assert.Contains(t, output, "targets/releases")
// remove the target for this role only
_, err = runCommand(t, tempDir, "remove", "gun", target, "--roles", "targets/releases")
assert.NoError(t, err)
// publish repo
_, err = runCommand(t, tempDir, "-s", server.URL, "publish", "gun")
assert.NoError(t, err)
// Now try using the key import command to instantiate the private key with this role
assert.NoError(t, os.Remove(filepath.Join(keyDir, canonicalKeyID+".key")))
tempPrivFile, err := ioutil.TempFile("/tmp", "privfile")
assert.NoError(t, err)
defer os.Remove(tempPrivFile.Name())
// Write the private key to a file so we can import it
_, err = tempPrivFile.Write(privKeyBytesNoRole)
assert.NoError(t, err)
tempPrivFile.Close()
// Import the private key, associating it with our delegation role
_, err = runCommand(t, tempDir, "key", "import", tempPrivFile.Name(), "--role", "targets/releases")
assert.NoError(t, err)
// add a target using the delegation -- will only add to targets/releases
_, err = runCommand(t, tempDir, "add", "gun", target, tempTargetFile.Name(), "--roles", "targets/releases")
assert.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")
assert.NoError(t, err)
assert.Contains(t, output, "No targets")
// publish repo
_, err = runCommand(t, tempDir, "-s", server.URL, "publish", "gun")
assert.NoError(t, err)
// list targets for targets/releases - we should see our target!
output, err = runCommand(t, tempDir, "-s", server.URL, "list", "gun", "--roles", "targets/releases")
assert.NoError(t, err)
assert.Contains(t, output, "targets/releases")
}
// Splits a string into lines, and returns any lines that are not empty (

View File

@ -103,7 +103,7 @@ func (k *keyCommander) GetCommand() *cobra.Command {
cmd.AddCommand(cmdKeysRestoreTemplate.ToCommand(k.keysRestore))
cmdKeysImport := cmdKeyImportTemplate.ToCommand(k.keysImport)
cmdKeysImport.Flags().StringVarP(
&k.keysExportGUN, "gun", "g", "", "Globally Unique Name to import key to")
&k.keysImportGUN, "gun", "g", "", "Globally Unique Name to import key to")
cmdKeysImport.Flags().StringVarP(
&k.keysImportRole, "role", "r", data.CanonicalRootRole, "Role to import key to")
cmd.AddCommand(cmdKeysImport)
@ -267,7 +267,7 @@ func (k *keyCommander) keysExport(cmd *cobra.Command, args []string) error {
return err
}
// Search for this key, determine whether this key has a GUN
// Search for this key in all of our keystores, determine whether this key has a GUN
keyGun := ""
keyRole := ""
for _, store := range ks {
@ -340,7 +340,7 @@ func (k *keyCommander) keysRestore(cmd *cobra.Command, args []string) error {
return nil
}
// keysImport imports a private key from a PEM file
// 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()
@ -368,7 +368,7 @@ func (k *keyCommander) keysImport(cmd *cobra.Command, args []string) error {
if k.keysImportRole == data.CanonicalRootRole {
err = cs.ImportRootKey(importFile)
} else {
err = cs.ImportRoleKey(importFile, k.keysImportRole)
err = cs.ImportRoleKey(importFile, k.keysImportRole, k.retriever)
}
if err != nil {

View File

@ -18,4 +18,10 @@ const (
Sha256HexSize = 64
// TrustedCertsDir is the directory, under the notary repo base directory, where trusted certs are stored
TrustedCertsDir = "trusted_certificates"
// PrivDir is the directory, under the notary repo base directory, where private keys are stored
PrivDir = "private"
// RootKeysSubdir is the subdirectory under PrivDir where root private keys are stored
RootKeysSubdir = "root_keys"
// NonRootKeysSubdir is the subdirectory under PrivDir where non-root private keys are stored
NonRootKeysSubdir = "tuf_keys"
)

View File

@ -11,6 +11,7 @@ import (
"path/filepath"
"strings"
"github.com/docker/notary"
"github.com/docker/notary/passphrase"
"github.com/docker/notary/trustmanager"
"github.com/docker/notary/tuf/data"
@ -32,7 +33,7 @@ var (
ErrNoKeysFoundForGUN = errors.New("no keys found for specified GUN")
)
// ExportKey exports the specified key to an io.Writer in PEM format.
// ExportKey exports the specified private key to an io.Writer in PEM format.
// The key's existing encryption is preserved.
func (cs *CryptoService) ExportKey(dest io.Writer, keyID, role string) error {
var (
@ -63,7 +64,7 @@ func (cs *CryptoService) ExportKey(dest io.Writer, keyID, role string) error {
return nil
}
// ExportRootKeyReencrypt exports the specified private key to an io.Writer in
// ExportKeyReencrypt exports the specified private key to an io.Writer in
// PEM format. The key is reencrypted with a new passphrase.
func (cs *CryptoService) ExportKeyReencrypt(dest io.Writer, keyID string, newPassphraseRetriever passphrase.Retriever) error {
privateKey, role, err := cs.GetPrivateKey(keyID)
@ -103,27 +104,44 @@ func (cs *CryptoService) ExportKeyReencrypt(dest io.Writer, keyID string, newPas
// It prompts for the key's passphrase to verify the data and to determine
// the key ID.
func (cs *CryptoService) ImportRootKey(source io.Reader) error {
return cs.ImportRoleKey(source, data.CanonicalRootRole)
return cs.ImportRoleKey(source, data.CanonicalRootRole, nil)
}
// ImportRoleKey imports a private key in PEM format key from an io.Reader
// It prompts for the key's passphrase to verify the data and to determine
// the key ID.
func (cs *CryptoService) ImportRoleKey(source io.Reader, role string) error {
func (cs *CryptoService) ImportRoleKey(source io.Reader, role string, newPassphraseRetriever passphrase.Retriever) error {
pemBytes, err := ioutil.ReadAll(source)
if err != nil {
return err
}
var alias string
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, role); err == nil {
if err = ks.ImportKey(pemBytes, alias); err == nil {
return nil //bail on the first keystore we import to
}
}

View File

@ -384,3 +384,109 @@ func TestImportExportRootKeyReencrypt(t *testing.T) {
assert.Equal(t, "root", alias)
assert.Equal(t, rootKeyID, key.ID())
}
func TestImportExportNonRootKey(t *testing.T) {
gun := "docker.com/notary"
// Temporary directory where test files will be created
tempBaseDir, err := ioutil.TempDir("", "notary-test-")
fmt.Println(tempBaseDir)
//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.CanonicalTargetsRole, data.ECDSAKey)
assert.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)
assert.NoError(t, err)
tempKeyFile.Close()
// Create new repo to test import
tempBaseDir2, err := ioutil.TempDir("", "notary-test-")
fmt.Println(tempBaseDir2)
//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.ImportRoleKey(keyReader, data.CanonicalTargetsRole, oldPassphraseRetriever)
assert.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, "private", "tuf_keys", "docker.com/notary", targetsKeyFilename))
assert.NoError(t, err, "missing targets key")
// Check that the key is the same
key, alias, err := cs2.GetPrivateKey(targetsKeyID)
assert.NoError(t, err, "could not unlock targets key")
assert.Equal(t, "targets", alias)
assert.Equal(t, targetsKeyID, key.ID())
}
func TestImportExportNonRootKeyReencrypt(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.CanonicalSnapshotRole, data.ECDSAKey)
assert.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)
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, newPassphraseRetriever)
cs2 := NewCryptoService(gun, fileStore2)
keyReader, err := os.Open(tempKeyFilePath)
assert.NoError(t, err, "could not open key file")
err = cs2.ImportRoleKey(keyReader, "snapshot", newPassphraseRetriever)
assert.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, "private", "tuf_keys", "docker.com/notary", snapshotKeyFilename))
assert.NoError(t, err, "missing snapshot key")
// Should be able to unlock the root key with the new password
key, alias, err := cs2.GetPrivateKey(snapshotKeyID)
assert.NoError(t, err, "could not unlock snapshot key")
assert.Equal(t, "snapshot", alias)
assert.Equal(t, snapshotKeyID, key.ID())
}

View File

@ -8,16 +8,11 @@ import (
"sync"
"github.com/Sirupsen/logrus"
"github.com/docker/notary"
"github.com/docker/notary/passphrase"
"github.com/docker/notary/tuf/data"
)
const (
rootKeysSubdir = "root_keys"
nonRootKeysSubdir = "tuf_keys"
privDir = "private"
)
// KeyFileStore persists and manages private keys on disk
type KeyFileStore struct {
sync.Mutex
@ -37,7 +32,7 @@ type KeyMemoryStore struct {
// NewKeyFileStore returns a new KeyFileStore creating a private directory to
// hold the keys.
func NewKeyFileStore(baseDir string, passphraseRetriever passphrase.Retriever) (*KeyFileStore, error) {
baseDir = filepath.Join(baseDir, privDir)
baseDir = filepath.Join(baseDir, notary.PrivDir)
fileStore, err := NewPrivateSimpleFileStore(baseDir, keyExtension)
if err != nil {
return nil, err
@ -242,10 +237,10 @@ func listKeys(s LimitedFileStore) map[string]string {
for _, f := range s.ListFiles() {
// Remove the prefix of the directory from the filename
var keyIDFull string
if strings.HasPrefix(f, rootKeysSubdir+"/") {
keyIDFull = strings.TrimPrefix(f, rootKeysSubdir+"/")
if strings.HasPrefix(f, notary.RootKeysSubdir+"/") {
keyIDFull = strings.TrimPrefix(f, notary.RootKeysSubdir+"/")
} else {
keyIDFull = strings.TrimPrefix(f, nonRootKeysSubdir+"/")
keyIDFull = strings.TrimPrefix(f, notary.NonRootKeysSubdir+"/")
}
keyIDFull = strings.TrimSpace(keyIDFull)
@ -302,9 +297,9 @@ func removeKey(s LimitedFileStore, cachedKeys map[string]*cachedKey, name string
// Assumes 2 subdirectories, 1 containing root keys and 1 containing tuf keys
func getSubdir(alias string) string {
if alias == "root" {
return rootKeysSubdir
return notary.RootKeysSubdir
}
return nonRootKeysSubdir
return notary.NonRootKeysSubdir
}
// Given a key ID, gets the bytes and alias belonging to that key if the key
@ -327,7 +322,7 @@ func getRawKey(s LimitedFileStore, name string) ([]byte, string, error) {
return keyBytes, role, nil
}
// GetPasswdDecryptBytes gets the password to decript the given pem bytes.
// GetPasswdDecryptBytes gets the password to decrypt the given pem bytes.
// Returns the password and private key
func GetPasswdDecryptBytes(passphraseRetriever passphrase.Retriever, pemBytes []byte, name, alias string) (data.PrivateKey, string, error) {
var (

View File

@ -9,6 +9,7 @@ import (
"path/filepath"
"testing"
"github.com/docker/notary"
"github.com/docker/notary/passphrase"
"github.com/docker/notary/tuf/data"
"github.com/stretchr/testify/assert"
@ -25,11 +26,11 @@ var passphraseRetriever = func(keyID string, alias string, createNew bool, numAt
}
func TestAddKey(t *testing.T) {
testAddKeyWithRole(t, data.CanonicalRootRole, rootKeysSubdir)
testAddKeyWithRole(t, data.CanonicalTargetsRole, nonRootKeysSubdir)
testAddKeyWithRole(t, data.CanonicalSnapshotRole, nonRootKeysSubdir)
testAddKeyWithRole(t, "targets/a/b/c", nonRootKeysSubdir)
testAddKeyWithRole(t, "invalidRole", nonRootKeysSubdir)
testAddKeyWithRole(t, data.CanonicalRootRole, notary.RootKeysSubdir)
testAddKeyWithRole(t, data.CanonicalTargetsRole, notary.NonRootKeysSubdir)
testAddKeyWithRole(t, data.CanonicalSnapshotRole, notary.NonRootKeysSubdir)
testAddKeyWithRole(t, "targets/a/b/c", notary.NonRootKeysSubdir)
testAddKeyWithRole(t, "invalidRole", notary.NonRootKeysSubdir)
}
func testAddKeyWithRole(t *testing.T, role, expectedSubdir string) {
@ -42,7 +43,7 @@ func testAddKeyWithRole(t *testing.T, role, expectedSubdir string) {
defer os.RemoveAll(tempBaseDir)
// Since we're generating this manually we need to add the extension '.'
expectedFilePath := filepath.Join(tempBaseDir, privDir, expectedSubdir, testName+"."+testExt)
expectedFilePath := filepath.Join(tempBaseDir, notary.PrivDir, expectedSubdir, testName+"."+testExt)
// Create our store
store, err := NewKeyFileStore(tempBaseDir, passphraseRetriever)
@ -73,22 +74,22 @@ func TestGet(t *testing.T) {
// Root role needs to go in the rootKeySubdir to be read.
// All other roles can go in the nonRootKeysSubdir, possibly under a GUN
rootKeysSubdirWithGUN := filepath.Clean(filepath.Join(rootKeysSubdir, gun))
nonRootKeysSubdirWithGUN := filepath.Clean(filepath.Join(nonRootKeysSubdir, gun))
rootKeysSubdirWithGUN := filepath.Clean(filepath.Join(notary.RootKeysSubdir, gun))
nonRootKeysSubdirWithGUN := filepath.Clean(filepath.Join(notary.NonRootKeysSubdir, gun))
testGetKeyWithRole(t, "", data.CanonicalRootRole, rootKeysSubdir, true)
testGetKeyWithRole(t, "", data.CanonicalRootRole, notary.RootKeysSubdir, true)
testGetKeyWithRole(t, gun, data.CanonicalRootRole, rootKeysSubdirWithGUN, true)
for _, role := range nonRootRolesToTest {
testGetKeyWithRole(t, "", role, nonRootKeysSubdir, true)
testGetKeyWithRole(t, "", role, notary.NonRootKeysSubdir, true)
testGetKeyWithRole(t, gun, role, nonRootKeysSubdirWithGUN, true)
}
// Root cannot go in the nonRootKeysSubdir, or it won't be able to be read,
// and vice versa
testGetKeyWithRole(t, "", data.CanonicalRootRole, nonRootKeysSubdir, false)
testGetKeyWithRole(t, "", data.CanonicalRootRole, notary.NonRootKeysSubdir, false)
testGetKeyWithRole(t, gun, data.CanonicalRootRole, nonRootKeysSubdirWithGUN, false)
for _, role := range nonRootRolesToTest {
testGetKeyWithRole(t, "", role, rootKeysSubdir, false)
testGetKeyWithRole(t, "", role, notary.RootKeysSubdir, false)
testGetKeyWithRole(t, gun, role, rootKeysSubdirWithGUN, false)
}
}
@ -136,7 +137,7 @@ EMl3eFOJXjIch/wIesRSN+2dGOsl7neercjMh1i9RvpCwHDx/E0=
defer os.RemoveAll(tempBaseDir)
// Since we're generating this manually we need to add the extension '.'
filePath := filepath.Join(tempBaseDir, privDir, expectedSubdir, testName+"."+testExt)
filePath := filepath.Join(tempBaseDir, notary.PrivDir, expectedSubdir, testName+"."+testExt)
os.MkdirAll(filepath.Dir(filePath), perms)
err = ioutil.WriteFile(filePath, testData, perms)
assert.NoError(t, err, "failed to write test file")
@ -206,7 +207,7 @@ EMl3eFOJXjIch/wIesRSN+2dGOsl7neercjMh1i9RvpCwHDx/E0=
defer os.RemoveAll(tempBaseDir)
// Since we're generating this manually we need to add the extension '.'
filePath := filepath.Join(tempBaseDir, privDir, rootKeysSubdir, testName+"_"+testAlias+"."+testExt)
filePath := filepath.Join(tempBaseDir, notary.PrivDir, notary.RootKeysSubdir, testName+"_"+testAlias+"."+testExt)
os.MkdirAll(filepath.Dir(filePath), perms)
err = ioutil.WriteFile(filePath, testData, perms)
@ -258,7 +259,7 @@ func TestListKeys(t *testing.T) {
}
// Write an invalid filename to the directory
filePath := filepath.Join(tempBaseDir, privDir, rootKeysSubdir, "fakekeyname.key")
filePath := filepath.Join(tempBaseDir, notary.PrivDir, notary.RootKeysSubdir, "fakekeyname.key")
err = ioutil.WriteFile(filePath, []byte("data"), perms)
assert.NoError(t, err, "failed to write test file")
@ -312,7 +313,7 @@ func TestGetDecryptedWithTamperedCipherText(t *testing.T) {
assert.NoError(t, err, "failed to add key to store")
// Since we're generating this manually we need to add the extension '.'
expectedFilePath := filepath.Join(tempBaseDir, privDir, rootKeysSubdir, privKey.ID()+"."+testExt)
expectedFilePath := filepath.Join(tempBaseDir, notary.PrivDir, notary.RootKeysSubdir, privKey.ID()+"."+testExt)
// Get file description, open file
fp, err := os.OpenFile(expectedFilePath, os.O_WRONLY, 0600)
@ -409,11 +410,11 @@ func testGetDecryptedWithInvalidPassphrase(t *testing.T, store KeyStore, newStor
}
func TestRemoveKey(t *testing.T) {
testRemoveKeyWithRole(t, data.CanonicalRootRole, rootKeysSubdir)
testRemoveKeyWithRole(t, data.CanonicalTargetsRole, nonRootKeysSubdir)
testRemoveKeyWithRole(t, data.CanonicalSnapshotRole, nonRootKeysSubdir)
testRemoveKeyWithRole(t, "targets/a/b/c", nonRootKeysSubdir)
testRemoveKeyWithRole(t, "invalidRole", nonRootKeysSubdir)
testRemoveKeyWithRole(t, data.CanonicalRootRole, notary.RootKeysSubdir)
testRemoveKeyWithRole(t, data.CanonicalTargetsRole, notary.NonRootKeysSubdir)
testRemoveKeyWithRole(t, data.CanonicalSnapshotRole, notary.NonRootKeysSubdir)
testRemoveKeyWithRole(t, "targets/a/b/c", notary.NonRootKeysSubdir)
testRemoveKeyWithRole(t, "invalidRole", notary.NonRootKeysSubdir)
}
func testRemoveKeyWithRole(t *testing.T, role, expectedSubdir string) {
@ -426,7 +427,7 @@ func testRemoveKeyWithRole(t *testing.T, role, expectedSubdir string) {
defer os.RemoveAll(tempBaseDir)
// Since we're generating this manually we need to add the extension '.'
expectedFilePath := filepath.Join(tempBaseDir, privDir, expectedSubdir, testName+"."+testExt)
expectedFilePath := filepath.Join(tempBaseDir, notary.PrivDir, expectedSubdir, testName+"."+testExt)
// Create our store
store, err := NewKeyFileStore(tempBaseDir, passphraseRetriever)