Skip to content

Commit

Permalink
Support passing in root keys on repo initialization
Browse files Browse the repository at this point in the history
Signed-off-by: Evan Cordell <[email protected]>
  • Loading branch information
ecordell committed Jul 8, 2016
1 parent b7f9050 commit a2426a7
Show file tree
Hide file tree
Showing 5 changed files with 190 additions and 29 deletions.
26 changes: 17 additions & 9 deletions client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,10 +173,14 @@ func rootCertKey(gun string, privKey data.PrivateKey) (data.PublicKey, error) {
// timestamp key and possibly other serverManagedRoles), but the created repository
// result is only stored on local disk, not published to the server. To do that,
// use r.Publish() eventually.
func (r *NotaryRepository) Initialize(rootKeyID string, serverManagedRoles ...string) error {
privKey, _, err := r.CryptoService.GetPrivateKey(rootKeyID)
if err != nil {
return err
func (r *NotaryRepository) Initialize(rootKeyIDs []string, serverManagedRoles ...string) error {
privKeys := []data.PrivateKey{}
for _, keyID := range rootKeyIDs {
privKey, _, err := r.CryptoService.GetPrivateKey(keyID)
if err != nil {
return err
}
privKeys = append(privKeys, privKey)
}

// currently we only support server managing timestamps and snapshots, and
Expand Down Expand Up @@ -206,16 +210,20 @@ func (r *NotaryRepository) Initialize(rootKeyID string, serverManagedRoles ...st
}
}

rootKey, err := rootCertKey(r.gun, privKey)
if err != nil {
return err
rootKeys := []data.PublicKey{}
for _, privKey := range privKeys {
rootKey, err := rootCertKey(r.gun, privKey)
if err != nil {
return err
}
rootKeys = append(rootKeys, rootKey)
}

var (
rootRole = data.NewBaseRole(
data.CanonicalRootRole,
notary.MinThreshold,
rootKey,
rootKeys...,
)
timestampRole data.BaseRole
snapshotRole data.BaseRole
Expand Down Expand Up @@ -271,7 +279,7 @@ func (r *NotaryRepository) Initialize(rootKeyID string, serverManagedRoles ...st

r.tufRepo = tuf.NewRepo(r.CryptoService)

err = r.tufRepo.InitRoot(
err := r.tufRepo.InitRoot(
rootRole,
timestampRole,
snapshotRole,
Expand Down
51 changes: 38 additions & 13 deletions client/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ func initializeRepo(t *testing.T, rootType, gun, url string,

repo, rec, rootPubKeyID := createRepoAndKey(t, rootType, tempBaseDir, gun, url)

err = repo.Initialize(rootPubKeyID, serverManagedRoles...)
err = repo.Initialize([]string{rootPubKeyID}, serverManagedRoles...)
if err != nil {
os.RemoveAll(tempBaseDir)
}
Expand Down Expand Up @@ -241,7 +241,7 @@ func TestInitRepositoryManagedRolesIncludingRoot(t *testing.T) {

repo, rec, rootPubKeyID := createRepoAndKey(
t, data.ECDSAKey, tempBaseDir, "docker.com/notary", "http://localhost")
err = repo.Initialize(rootPubKeyID, data.CanonicalRootRole)
err = repo.Initialize([]string{rootPubKeyID}, data.CanonicalRootRole)
require.Error(t, err)
require.IsType(t, ErrInvalidRemoteRole{}, err)
// Just testing the error message here in this one case
Expand All @@ -261,7 +261,7 @@ func TestInitRepositoryManagedRolesInvalidRole(t *testing.T) {

repo, rec, rootPubKeyID := createRepoAndKey(
t, data.ECDSAKey, tempBaseDir, "docker.com/notary", "http://localhost")
err = repo.Initialize(rootPubKeyID, "randomrole")
err = repo.Initialize([]string{rootPubKeyID}, "randomrole")
require.Error(t, err)
require.IsType(t, ErrInvalidRemoteRole{}, err)
// no key creation happened
Expand All @@ -278,7 +278,7 @@ func TestInitRepositoryManagedRolesIncludingTargets(t *testing.T) {

repo, rec, rootPubKeyID := createRepoAndKey(
t, data.ECDSAKey, tempBaseDir, "docker.com/notary", "http://localhost")
err = repo.Initialize(rootPubKeyID, data.CanonicalTargetsRole)
err = repo.Initialize([]string{rootPubKeyID}, data.CanonicalTargetsRole)
require.Error(t, err)
require.IsType(t, ErrInvalidRemoteRole{}, err)
// no key creation happened
Expand All @@ -298,12 +298,37 @@ func TestInitRepositoryManagedRolesIncludingTimestamp(t *testing.T) {

repo, rec, rootPubKeyID := createRepoAndKey(
t, data.ECDSAKey, tempBaseDir, "docker.com/notary", ts.URL)
err = repo.Initialize(rootPubKeyID, data.CanonicalTimestampRole)
err = repo.Initialize([]string{rootPubKeyID}, data.CanonicalTimestampRole)
require.NoError(t, err)

// generates the target role, the snapshot role
rec.requireCreated(t, []string{data.CanonicalTargetsRole, data.CanonicalSnapshotRole})
}

func TestInitRepositoryMultipleRootKeys(t *testing.T) {
// Temporary directory where test files will be created
tempBaseDir, err := ioutil.TempDir("/tmp", "notary-test-")
require.NoError(t, err, "failed to create a temporary directory")
defer os.RemoveAll(tempBaseDir)

ts, _, _ := simpleTestServer(t)
defer ts.Close()

repo, rec, rootPubKeyID := createRepoAndKey(
t, data.ECDSAKey, tempBaseDir, "docker.com/notary", ts.URL)
rootPubKey2, err := repo.CryptoService.Create("root", repo.gun, data.ECDSAKey)
require.NoError(t, err, "error generating second root key: %s", err)

err = repo.Initialize([]string{rootPubKeyID, rootPubKey2.ID()}, data.CanonicalTimestampRole)
require.NoError(t, err)

// generates the target role, the snapshot role
rec.requireCreated(t, []string{data.CanonicalTargetsRole, data.CanonicalSnapshotRole})

// has two root keys
require.Len(t, repo.tufRepo.Root.Signed.Roles[data.CanonicalRootRole].KeyIDs, 2)
}

// Initializing a new repo fails if unable to get the timestamp key, even if
// the snapshot key is available
func TestInitRepositoryNeedsRemoteTimestampKey(t *testing.T) {
Expand All @@ -317,7 +342,7 @@ func TestInitRepositoryNeedsRemoteTimestampKey(t *testing.T) {

repo, rec, rootPubKeyID := createRepoAndKey(
t, data.ECDSAKey, tempBaseDir, "docker.com/notary", ts.URL)
err = repo.Initialize(rootPubKeyID, data.CanonicalTimestampRole)
err = repo.Initialize([]string{rootPubKeyID}, data.CanonicalTimestampRole)
require.Error(t, err)
require.IsType(t, store.ErrMetaNotFound{}, err)

Expand All @@ -339,7 +364,7 @@ func TestInitRepositoryNeedsRemoteSnapshotKey(t *testing.T) {

repo, rec, rootPubKeyID := createRepoAndKey(
t, data.ECDSAKey, tempBaseDir, "docker.com/notary", ts.URL)
err = repo.Initialize(rootPubKeyID, data.CanonicalSnapshotRole)
err = repo.Initialize([]string{rootPubKeyID}, data.CanonicalSnapshotRole)
require.Error(t, err)
require.IsType(t, store.ErrMetaNotFound{}, err)

Expand Down Expand Up @@ -516,9 +541,9 @@ func testInitRepoSigningKeys(t *testing.T, rootType string, serverManagesSnapsho
repo, rec := newRepoToTestRepo(t, repo, false)

if serverManagesSnapshot {
err = repo.Initialize(rootPubKeyID, data.CanonicalSnapshotRole)
err = repo.Initialize([]string{rootPubKeyID}, data.CanonicalSnapshotRole)
} else {
err = repo.Initialize(rootPubKeyID)
err = repo.Initialize([]string{rootPubKeyID})
}

require.NoError(t, err, "error initializing repository")
Expand Down Expand Up @@ -563,7 +588,7 @@ func testInitRepoAttemptsExceeded(t *testing.T, rootType string) {
// private key unlocking we need a new repo instance.
repo, err = NewNotaryRepository(tempBaseDir, gun, ts.URL, http.DefaultTransport, retriever, trustpinning.TrustPinConfig{})
require.NoError(t, err, "error creating repo: %s", err)
err = repo.Initialize(rootPubKey.ID())
err = repo.Initialize([]string{rootPubKey.ID()})
require.EqualError(t, err, trustmanager.ErrAttemptsExceeded{}.Error())
}

Expand Down Expand Up @@ -600,7 +625,7 @@ func testInitRepoPasswordInvalid(t *testing.T, rootType string) {
// private key unlocking we need a new repo instance.
repo, err = NewNotaryRepository(tempBaseDir, gun, ts.URL, http.DefaultTransport, giveUpPassphraseRetriever, trustpinning.TrustPinConfig{})
require.NoError(t, err, "error creating repo: %s", err)
err = repo.Initialize(rootPubKey.ID())
err = repo.Initialize([]string{rootPubKey.ID()})
require.EqualError(t, err, trustmanager.ErrPasswordInvalid{}.Error())
}

Expand Down Expand Up @@ -1650,7 +1675,7 @@ func TestPublishUninitializedRepo(t *testing.T) {
rootPubKey, err := repo.CryptoService.Create("root", repo.gun, data.ECDSAKey)
require.NoError(t, err, "error generating root key: %s", err)

require.NoError(t, repo.Initialize(rootPubKey.ID()))
require.NoError(t, repo.Initialize([]string{rootPubKey.ID()}))

// now metadata is created
requireRepoHasExpectedMetadata(t, repo, data.CanonicalRootRole, true)
Expand Down Expand Up @@ -2011,7 +2036,7 @@ func TestPublishSnapshotLocalKeysCreatedFirst(t *testing.T) {

repo.CryptoService = cannotCreateKeys{CryptoService: cs}

err = repo.Initialize(rootPubKey.ID(), data.CanonicalSnapshotRole)
err = repo.Initialize([]string{rootPubKey.ID()}, data.CanonicalSnapshotRole)
require.Error(t, err)
require.Contains(t, err.Error(), "Oh no I cannot create keys")
require.False(t, requestMade)
Expand Down
92 changes: 92 additions & 0 deletions cmd/notary/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,98 @@ func setupServer() *httptest.Server {
return httptest.NewServer(setupServerHandler(storage.NewMemStorage()))
}

// Initializes a repo with existing key
func TestInitWithRootKey(t *testing.T) {
// -- setup --
setUp(t)

tempDir := tempDirWithConfig(t, "{}")
defer os.RemoveAll(tempDir)

server := setupServer()
defer server.Close()

tempFile, err := ioutil.TempFile("", "targetfile")
require.NoError(t, err)
tempFile.Close()
defer os.Remove(tempFile.Name())

// -- tests --

// create encrypted root key
privKey, err := trustmanager.GenerateECDSAKey(rand.Reader)
require.NoError(t, err)
encryptedPEMPrivKey, err := trustmanager.EncryptPrivateKey(privKey, data.CanonicalRootRole, testPassphrase)
require.NoError(t, err)
encryptedPEMKeyFilename := filepath.Join(tempDir, "encrypted_key.key")
err = ioutil.WriteFile(encryptedPEMKeyFilename, encryptedPEMPrivKey, 0644)
require.NoError(t, err)

// init repo
_, err = runCommand(t, tempDir, "-s", server.URL, "init", "gun", "--rootkey", encryptedPEMKeyFilename)
require.NoError(t, err)

// publish repo
_, err = runCommand(t, tempDir, "-s", server.URL, "publish", "gun")
require.NoError(t, err)

// check that the root key used for init is the one listed as root key
output, err := runCommand(t, tempDir, "key", "list")
require.NoError(t, err)
t.Logf(output)
require.True(t, strings.Contains(output, data.PublicKeyFromPrivate(privKey).ID()))

// check error if file doesn't exist
_, err = runCommand(t, tempDir, "-s", server.URL, "init", "gun2", "--rootkey", "bad_file")
require.Error(t, err, "Init with nonexistent key file should error")

// check error if file is invalid format
badKeyFilename := filepath.Join(tempDir, "bad_key.key")
nonPEMKey := []byte("thisisnotapemkey")
err = ioutil.WriteFile(badKeyFilename, nonPEMKey, 0644)
require.NoError(t, err)

_, err = runCommand(t, tempDir, "-s", server.URL, "init", "gun2", "--rootKey", badKeyFilename)
require.Error(t, err, "Init with non-PEM key should error")

// check error if unencrypted PEM used
unencryptedPrivKey, err := trustmanager.GenerateECDSAKey(rand.Reader)
require.NoError(t, err)
unencryptedPEMPrivKey, err := trustmanager.KeyToPEM(unencryptedPrivKey, data.CanonicalRootRole)
require.NoError(t, err)
unencryptedPEMKeyFilename := filepath.Join(tempDir, "unencrypted_key.key")
err = ioutil.WriteFile(unencryptedPEMKeyFilename, unencryptedPEMPrivKey, 0644)
require.NoError(t, err)

_, err = runCommand(t, tempDir, "-s", server.URL, "init", "gun2", "--rootKey", unencryptedPEMKeyFilename)
require.Error(t, err, "Init with unencrypted PEM key should error")

// check error if invalid password used
// instead of using a new retriever, we create a new key with a different pass
badPassPrivKey, err := trustmanager.GenerateECDSAKey(rand.Reader)
require.NoError(t, err)
badPassPEMPrivKey, err := trustmanager.EncryptPrivateKey(badPassPrivKey, data.CanonicalRootRole, "bad_pass")
require.NoError(t, err)
badPassPEMKeyFilename := filepath.Join(tempDir, "badpass_key.key")
err = ioutil.WriteFile(badPassPEMKeyFilename, badPassPEMPrivKey, 0644)
require.NoError(t, err)

_, err = runCommand(t, tempDir, "-s", server.URL, "init", "gun2", "--rootKey", badPassPEMKeyFilename)
require.Error(t, err, "Init with wrong password should error")

// check error if wrong role specified
snapshotPrivKey, err := trustmanager.GenerateECDSAKey(rand.Reader)
require.NoError(t, err)
snapshotPEMPrivKey, err := trustmanager.EncryptPrivateKey(snapshotPrivKey, data.CanonicalSnapshotRole, testPassphrase)
require.NoError(t, err)
snapshotPEMKeyFilename := filepath.Join(tempDir, "snapshot_key.key")
err = ioutil.WriteFile(snapshotPEMKeyFilename, snapshotPEMPrivKey, 0644)
require.NoError(t, err)

_, err = runCommand(t, tempDir, "-s", server.URL, "init", "gun2", "--rootKey", snapshotPEMKeyFilename)
require.Error(t, err, "Init with wrong role should error")
}

// Initializes a repo, adds a target, publishes the target, lists the target,
// verifies the target, and then removes the target.
func TestClientTUFInteraction(t *testing.T) {
Expand Down
2 changes: 1 addition & 1 deletion cmd/notary/keys_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -336,7 +336,7 @@ func setUpRepo(t *testing.T, tempBaseDir, gun string, ret notary.PassRetriever)
rootPubKey, err := repo.CryptoService.Create("root", "", data.ECDSAKey)
require.NoError(t, err, "error generating root key: %s", err)

err = repo.Initialize(rootPubKey.ID())
err = repo.Initialize([]string{rootPubKey.ID()})
require.NoError(t, err)

return ts, repo.CryptoService.ListAllKeys()
Expand Down
48 changes: 42 additions & 6 deletions cmd/notary/tuf.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"bufio"
"encoding/hex"
"fmt"
"io/ioutil"
"net"
"net/http"
"net/url"
Expand All @@ -19,6 +20,8 @@ import (
"github.com/docker/go-connections/tlsconfig"
"github.com/docker/notary"
notaryclient "github.com/docker/notary/client"
"github.com/docker/notary/cryptoservice"
"github.com/docker/notary/trustmanager"
"github.com/docker/notary/trustpinning"
"github.com/docker/notary/tuf/data"
"github.com/docker/notary/utils"
Expand Down Expand Up @@ -86,17 +89,21 @@ type tufCommander struct {
retriever notary.PassRetriever

// these are for command line parsing - no need to set
roles []string
sha256 string
sha512 string
roles []string
sha256 string
sha512 string
rootKey string

input string
output string
quiet bool
}

func (t *tufCommander) AddToCommand(cmd *cobra.Command) {
cmd.AddCommand(cmdTUFInitTemplate.ToCommand(t.tufInit))
cmdTUFInit := cmdTUFInitTemplate.ToCommand(t.tufInit)
cmdTUFInit.Flags().StringVar(&t.rootKey, "rootkey", "", "Root key to initialize the repository with")
cmd.AddCommand(cmdTUFInit)

cmd.AddCommand(cmdTUFStatusTemplate.ToCommand(t.tufStatus))
cmd.AddCommand(cmdTUFPublishTemplate.ToCommand(t.tufPublish))
cmd.AddCommand(cmdTUFLookupTemplate.ToCommand(t.tufLookup))
Expand Down Expand Up @@ -268,7 +275,36 @@ func (t *tufCommander) tufInit(cmd *cobra.Command, args []string) error {
return err
}

rootKeyList := nRepo.CryptoService.ListKeys(data.CanonicalRootRole)
var rootKeyList []string

if t.rootKey != "" {
keyFile, err := os.Open(t.rootKey)
if err != nil {
return fmt.Errorf("Opening file for import: %v", err)
}
defer keyFile.Close()

pemBytes, err := ioutil.ReadAll(keyFile)
if err != nil {
return fmt.Errorf("Error reading input file: %v", err)
}
if err = cryptoservice.CheckRootKeyIsEncrypted(pemBytes); err != nil {
return err
}

privKey, _, err := trustmanager.GetPasswdDecryptBytes(t.retriever, pemBytes, "", "imported root")
if err != nil {
return err
}

err = nRepo.CryptoService.AddKey(data.CanonicalRootRole, "", privKey)
if err != nil {
return fmt.Errorf("Error importing key: %v", err)
}
rootKeyList = []string{data.PublicKeyFromPrivate(privKey).ID()}
} else {
rootKeyList = nRepo.CryptoService.ListKeys(data.CanonicalRootRole)
}

var rootKeyID string
if len(rootKeyList) < 1 {
Expand All @@ -285,7 +321,7 @@ func (t *tufCommander) tufInit(cmd *cobra.Command, args []string) error {
cmd.Printf("Root key found, using: %s\n", rootKeyID)
}

if err = nRepo.Initialize(rootKeyID); err != nil {
if err = nRepo.Initialize([]string{rootKeyID}); err != nil {
return err
}
return nil
Expand Down

0 comments on commit a2426a7

Please sign in to comment.