Merge pull request #24 from docker/fix-cryptoservice-create-get-key

Fix the problem where root was being searched for in root_keys/repo/####_root.key

Signed-off-by: David Lawrence <david.lawrence@docker.com>

Signed-off-by: Ying Li <cyli@users.noreply.github.com> (github: endophage)
This commit is contained in:
Ying Li 2015-11-05 13:56:09 -08:00 committed by David Lawrence
commit 2cb072667c
3 changed files with 159 additions and 56 deletions

View File

@ -9,7 +9,7 @@ import (
"github.com/stretchr/testify/assert"
)
func TestUnlockedSigner(t *testing.T) {
func TestGenerateCertificate(t *testing.T) {
privKey, err := trustmanager.GenerateECDSAKey(rand.Reader)
assert.NoError(t, err, "could not generate key")

View File

@ -53,8 +53,15 @@ func (ccs *CryptoService) Create(role, algorithm string) (data.PublicKey, error)
logrus.Debugf("generated new %s key for role: %s and keyID: %s", algorithm, role, privKey.ID())
// Store the private key into our keystore with the name being: /GUN/ID.key with an alias of role
var keyPath string
if role == data.CanonicalRootRole {
keyPath = privKey.ID()
} else {
keyPath = filepath.Join(ccs.gun, privKey.ID())
}
for _, ks := range ccs.keyStores {
err = ks.AddKey(filepath.Join(ccs.gun, privKey.ID()), role, privKey)
err = ks.AddKey(keyPath, role, privKey)
if err == nil {
return data.PublicKeyFromPrivate(privKey), nil
}
@ -66,40 +73,47 @@ func (ccs *CryptoService) Create(role, algorithm string) (data.PublicKey, error)
}
// GetPrivateKey returns a private key by ID
// GetPrivateKey returns a private key by ID. It tries to get the key first
// without a GUN (in which case it's a root key). If that fails, try to get
// the key with the GUN (non-root key).
// If that fails, then we don't have the key.
func (ccs *CryptoService) GetPrivateKey(keyID string) (k data.PrivateKey, id string, err error) {
keyPaths := []string{keyID, filepath.Join(ccs.gun, keyID)}
for _, ks := range ccs.keyStores {
k, id, err = ks.GetKey(keyID)
if k == nil || err != nil {
continue
for _, keyPath := range keyPaths {
k, id, err = ks.GetKey(keyPath)
if err != nil {
continue
}
return
}
return
}
return // returns whatever the final values were
}
// GetKey returns a key by ID
func (ccs *CryptoService) GetKey(keyID string) data.PublicKey {
for _, ks := range ccs.keyStores {
k, _, err := ks.GetKey(keyID)
if k == nil || err != nil {
continue
}
return data.PublicKeyFromPrivate(k)
privKey, _, err := ccs.GetPrivateKey(keyID)
if err != nil {
return nil
}
return nil // returns whatever the final values were
return data.PublicKeyFromPrivate(privKey)
}
// RemoveKey deletes a key by ID
func (ccs *CryptoService) RemoveKey(keyID string) (err error) {
keyPaths := []string{keyID, filepath.Join(ccs.gun, keyID)}
for _, ks := range ccs.keyStores {
e := ks.RemoveKey(keyID)
if e != nil {
err = e
for _, keyPath := range keyPaths {
_, _, err = ks.GetKey(keyPath)
if err != nil {
continue
}
err = ks.RemoveKey(keyPath)
return
}
}
return // returns last error if any
return // returns whatever the final values were
}
// Sign returns the signatures for the payload with a set of keyIDs. It ignores
@ -110,25 +124,18 @@ func (ccs *CryptoService) Sign(keyIDs []string, payload []byte) ([]data.Signatur
for _, keyid := range keyIDs {
keyName := keyid
// Try to get the key first without a GUN (in which case it's a root
// key). If that fails, try to get the key with the GUN (non-root
// key). If that fails, then we don't have the key.
privKey, _, err := ccs.GetPrivateKey(keyName)
if err != nil {
logrus.Debugf("error attempting to retrieve private key: %s, %v", keyid, err)
keyName = filepath.Join(ccs.gun, keyid)
privKey, _, err = ccs.GetPrivateKey(keyName)
if err != nil {
logrus.Debugf("error attempting to retrieve key ID: %s, %v", keyid, err)
return nil, err
}
continue
}
sigAlgo := privKey.SignatureAlgorithm()
sig, err := privKey.Sign(rand.Reader, payload, nil)
if err != nil {
logrus.Debugf("ignoring error attempting to %s sign with keyID: %s, %v", privKey.Algorithm(), keyid, err)
return nil, err
logrus.Debugf("ignoring error attempting to %s sign with keyID: %s, %v",
privKey.Algorithm(), keyid, err)
continue
}
logrus.Debugf("appending %s signature with Key ID: %s", privKey.Algorithm(), keyid)

View File

@ -1,51 +1,147 @@
package cryptoservice
import (
"github.com/stretchr/testify/assert"
"fmt"
"runtime"
"testing"
"github.com/stretchr/testify/assert"
"github.com/docker/notary/trustmanager"
"github.com/docker/notary/tuf/data"
"github.com/docker/notary/tuf/signed"
)
func TestCryptoService(t *testing.T) {
testCryptoService(t, data.ECDSAKey, signed.ECDSAVerifier{})
testCryptoService(t, data.ED25519Key, signed.Ed25519Verifier{})
if !testing.Short() {
testCryptoService(t, data.RSAKey, signed.RSAPSSVerifier{})
}
var algoToSigType = map[string]data.SigAlgorithm{
data.ECDSAKey: data.ECDSASignature,
data.ED25519Key: data.EDDSASignature,
data.RSAKey: data.RSAPSSSignature,
}
var passphraseRetriever = func(string, string, bool, int) (string, bool, error) { return "", false, nil }
func testCryptoService(t *testing.T, keyAlgo string, verifier signed.Verifier) {
content := []byte("this is a secret")
type CryptoServiceTester struct {
cryptoServiceFactory func() *CryptoService
role string
keyAlgo string
}
keyStore := trustmanager.NewKeyMemoryStore(passphraseRetriever)
cryptoService := NewCryptoService("", keyStore)
// asserts that created key exists
func (c CryptoServiceTester) TestCreateAndGetKey(t *testing.T) {
cryptoService := c.cryptoServiceFactory()
// Test Create
tufKey, err := cryptoService.Create("", keyAlgo)
assert.NoError(t, err, "error creating key")
// Test Sign
signatures, err := cryptoService.Sign([]string{tufKey.ID()}, content)
assert.NoError(t, err, "signing failed")
assert.Len(t, signatures, 1, "wrong number of signatures")
err = verifier.Verify(tufKey, signatures[0].Signature, content)
assert.NoError(t, err, "verification failed for %s key type", keyAlgo)
tufKey, err := cryptoService.Create(c.role, c.keyAlgo)
assert.NoError(t, err, c.errorMsg("error creating key"))
// Test GetKey
retrievedKey := cryptoService.GetKey(tufKey.ID())
assert.Equal(t, tufKey.Public(), retrievedKey.Public(), "retrieved key didn't match")
assert.NotNil(t, retrievedKey,
c.errorMsg("Could not find key ID %s", tufKey.ID()))
assert.Equal(t, tufKey.Public(), retrievedKey.Public(),
c.errorMsg("retrieved public key didn't match"))
assert.Nil(t, cryptoService.GetKey("boguskeyid"), "non-nil result for bogus keyid")
// Test GetPrivateKey
retrievedKey, alias, err := cryptoService.GetPrivateKey(tufKey.ID())
assert.NoError(t, err)
assert.Equal(t, tufKey.ID(), retrievedKey.ID(),
c.errorMsg("retrieved private key didn't have the right ID"))
assert.Equal(t, c.role, alias)
}
// asserts that getting key fails for a non-existent key
func (c CryptoServiceTester) TestGetNonexistentKey(t *testing.T) {
cryptoService := c.cryptoServiceFactory()
assert.Nil(t, cryptoService.GetKey("boguskeyid"),
c.errorMsg("non-nil result for bogus keyid"))
_, _, err := cryptoService.GetPrivateKey("boguskeyid")
assert.NotNil(t, err)
}
// asserts that signing with a created key creates a valid signature
func (c CryptoServiceTester) TestSignWithKey(t *testing.T) {
cryptoService := c.cryptoServiceFactory()
content := []byte("this is a secret")
tufKey, err := cryptoService.Create(c.role, c.keyAlgo)
assert.NoError(t, err, c.errorMsg("error creating key"))
// Test Sign
signatures, err := cryptoService.Sign([]string{tufKey.ID()}, content)
assert.NoError(t, err, c.errorMsg("signing failed"))
assert.Len(t, signatures, 1, c.errorMsg("wrong number of signatures"))
verifier, ok := signed.Verifiers[algoToSigType[c.keyAlgo]]
assert.True(t, ok, c.errorMsg("Unknown verifier for algorithm"))
err = verifier.Verify(tufKey, signatures[0].Signature, content)
assert.NoError(t, err,
c.errorMsg("verification failed for %s key type", c.keyAlgo))
}
// asserts that removing key that exists succeeds
func (c CryptoServiceTester) TestRemoveCreatedKey(t *testing.T) {
cryptoService := c.cryptoServiceFactory()
tufKey, err := cryptoService.Create(c.role, c.keyAlgo)
assert.NoError(t, err, c.errorMsg("error creating key"))
assert.NotNil(t, cryptoService.GetKey(tufKey.ID()))
// Test RemoveKey
err = cryptoService.RemoveKey(tufKey.ID())
assert.NoError(t, err, "could not remove key")
retrievedKey = cryptoService.GetKey(tufKey.ID())
assert.Nil(t, retrievedKey, "remove didn't work")
assert.NoError(t, err, c.errorMsg("could not remove key"))
retrievedKey := cryptoService.GetKey(tufKey.ID())
assert.Nil(t, retrievedKey, c.errorMsg("remove didn't work"))
}
// Prints out an error message with information about the key algorithm,
// role, and test name. Ideally we could generate different tests given
// data, without having to put for loops in one giant test function, but
// that involves a lot of boilerplate. So as a compromise, everything will
// still be run in for loops in one giant test function, but we can at
// least provide an error message stating what data/helper test function
// failed.
func (c CryptoServiceTester) errorMsg(message string, args ...interface{}) string {
pc := make([]uintptr, 10) // at least 1 entry needed
runtime.Callers(2, pc) // the caller of errorMsg
f := runtime.FuncForPC(pc[0])
return fmt.Sprintf("%s (role: %s, keyAlgo: %s): %s", f.Name(), c.role,
c.keyAlgo, fmt.Sprintf(message, args...))
}
func testCryptoService(t *testing.T, gun string) {
getCryptoService := func() *CryptoService {
return NewCryptoService(
gun, trustmanager.NewKeyMemoryStore(passphraseRetriever))
}
roles := []string{
data.CanonicalRootRole,
data.CanonicalTargetsRole,
data.CanonicalSnapshotRole,
data.CanonicalTimestampRole,
}
for _, role := range roles {
for algo := range algoToSigType {
cst := CryptoServiceTester{
cryptoServiceFactory: getCryptoService,
role: role,
keyAlgo: algo,
}
cst.TestCreateAndGetKey(t)
cst.TestGetNonexistentKey(t)
cst.TestSignWithKey(t)
cst.TestRemoveCreatedKey(t)
}
}
}
func TestCryptoServiceWithNonEmptyGUN(t *testing.T) {
testCryptoService(t, "org/repo")
}
func TestCryptoServiceWithEmptyGUN(t *testing.T) {
testCryptoService(t, "")
}