Fix new bug where adding a duplicate key to a yubikey added to the backup.

Added a test for this case as well - thanks @endophage!

Signed-off-by: Ying Li <ying.li@docker.com>
Signed-off-by: David Lawrence <david.lawrence@docker.com>

Signed-off-by: Ying Li <ying.li@docker.com> (github: endophage)
This commit is contained in:
Ying Li 2015-11-11 22:25:27 -08:00 committed by David Lawrence
parent 43f2d40e43
commit 87231d9a5d
3 changed files with 59 additions and 16 deletions

View File

@ -40,6 +40,8 @@ const (
// KeyStore is a generic interface for private key storage // KeyStore is a generic interface for private key storage
type KeyStore interface { type KeyStore interface {
// Add Key adds a key to the KeyStore, and if the key already exists,
// succeeds. Otherwise, returns an error if it cannot add.
AddKey(name, alias string, privKey data.PrivateKey) error AddKey(name, alias string, privKey data.PrivateKey) error
GetKey(name string) (data.PrivateKey, string, error) GetKey(name string) (data.PrivateKey, string, error)
ListKeys() map[string]string ListKeys() map[string]string

View File

@ -631,42 +631,49 @@ func (s *YubiKeyStore) ListKeys() map[string]string {
// AddKey puts a key inside the Yubikey, as well as writing it to the backup store // AddKey puts a key inside the Yubikey, as well as writing it to the backup store
func (s *YubiKeyStore) AddKey(keyID, role string, privKey data.PrivateKey) error { func (s *YubiKeyStore) AddKey(keyID, role string, privKey data.PrivateKey) error {
err := s.addKey(keyID, role, privKey) added, err := s.addKey(keyID, role, privKey)
if err != nil { if err != nil {
return err return err
} }
err = s.backupStore.AddKey(privKey.ID(), role, privKey) if added {
if err != nil { err = s.backupStore.AddKey(privKey.ID(), role, privKey)
defer s.RemoveKey(keyID) if err != nil {
return ErrBackupFailed{err: err.Error()} defer s.RemoveKey(keyID)
return ErrBackupFailed{err: err.Error()}
}
} }
return nil return nil
} }
func (s *YubiKeyStore) addKey(keyID, role string, privKey data.PrivateKey) error { // Only add if we haven't seen the key already. Return whether the key was
// added.
func (s *YubiKeyStore) addKey(keyID, role string, privKey data.PrivateKey) (
bool, error) {
// We only allow adding root keys for now // We only allow adding root keys for now
if role != data.CanonicalRootRole { if role != data.CanonicalRootRole {
return fmt.Errorf("yubikey only supports storing root keys, got %s for key: %s", role, keyID) return false, fmt.Errorf(
"yubikey only supports storing root keys, got %s for key: %s", role, keyID)
} }
ctx, session, err := SetupHSMEnv(pkcs11Lib, s.libLoader) ctx, session, err := SetupHSMEnv(pkcs11Lib, s.libLoader)
if err != nil { if err != nil {
logrus.Debugf("Failed to initialize PKCS11 environment: %s", err.Error()) logrus.Debugf("Failed to initialize PKCS11 environment: %s", err.Error())
return err return false, err
} }
defer cleanup(ctx, session) defer cleanup(ctx, session)
if k, ok := s.keys[keyID]; ok { if k, ok := s.keys[keyID]; ok {
if k.role == role { if k.role == role {
// already have the key and it's associated with the correct role // already have the key and it's associated with the correct role
return nil return false, nil
} }
} }
slot, err := getNextEmptySlot(ctx, session) slot, err := getNextEmptySlot(ctx, session)
if err != nil { if err != nil {
logrus.Debugf("Failed to get an empty yubikey slot: %s", err.Error()) logrus.Debugf("Failed to get an empty yubikey slot: %s", err.Error())
return err return false, err
} }
logrus.Debugf("Attempting to store key using yubikey slot %v", slot) logrus.Debugf("Attempting to store key using yubikey slot %v", slot)
@ -677,11 +684,11 @@ func (s *YubiKeyStore) addKey(keyID, role string, privKey data.PrivateKey) error
role: role, role: role,
slotID: slot, slotID: slot,
} }
return nil return true, nil
} }
logrus.Debugf("Failed to add key to yubikey: %v", err) logrus.Debugf("Failed to add key to yubikey: %v", err)
return err return false, err
} }
// GetKey retrieves a key from the Yubikey only (it does not look inside the // GetKey retrieves a key from the Yubikey only (it does not look inside the
@ -758,7 +765,8 @@ func (s *YubiKeyStore) ImportKey(pemBytes []byte, keyPath string) error {
if keyPath != data.CanonicalRootRole { if keyPath != data.CanonicalRootRole {
return fmt.Errorf("yubikey only supports storing root keys") return fmt.Errorf("yubikey only supports storing root keys")
} }
return s.addKey(privKey.ID(), "root", privKey) _, err = s.addKey(privKey.ID(), "root", privKey)
return err
} }
func cleanup(ctx IPKCS11Ctx, session pkcs11.SessionHandle) { func cleanup(ctx IPKCS11Ctx, session pkcs11.SessionHandle) {

View File

@ -247,6 +247,39 @@ func TestYubiAddKeyRollsBackIfCannotBackup(t *testing.T) {
assert.Len(t, cleanListKeys(t), 0) assert.Len(t, cleanListKeys(t), 0)
} }
// If, when adding a key to the Yubikey, and it already exists, we succeed
// without adding it to the backup store.
func TestYubiAddDuplicateKeySucceedsButDoesNotBackup(t *testing.T) {
if !YubikeyAccessible() {
t.Skip("Must have Yubikey access.")
}
clearAllKeys(t)
SetYubikeyKeyMode(KeymodeNone)
defer func() {
SetYubikeyKeyMode(KeymodeTouch | KeymodePinOnce)
}()
origStore, err := NewYubiKeyStore(trustmanager.NewKeyMemoryStore(ret), ret)
assert.NoError(t, err)
key, err := testAddKey(t, origStore)
assert.NoError(t, err)
backup := trustmanager.NewKeyMemoryStore(ret)
cleanStore, err := NewYubiKeyStore(backup, ret)
assert.NoError(t, err)
assert.Len(t, cleanStore.ListKeys(), 1)
err = cleanStore.AddKey(key.ID(), "root", key)
assert.NoError(t, err)
// there should be just 1 key on the yubikey
assert.Len(t, cleanListKeys(t), 1)
// nothing was added to the backup
assert.Len(t, backup.ListKeys(), 0)
}
// RemoveKey removes a key from the yubikey, but not from the backup store. // RemoveKey removes a key from the yubikey, but not from the backup store.
func TestYubiRemoveKey(t *testing.T) { func TestYubiRemoveKey(t *testing.T) {
if !YubikeyAccessible() { if !YubikeyAccessible() {
@ -875,7 +908,7 @@ func TestYubiRetrySignUntilSuccess(t *testing.T) {
// If Sign gives us an invalid signature, we retry until up to a maximum of 5 // If Sign gives us an invalid signature, we retry until up to a maximum of 5
// times, and if it's still invalid, fail. // times, and if it's still invalid, fail.
func TestYubiRetrySign5TimesAndFail(t *testing.T) { func TestYubiRetrySignUntilFail(t *testing.T) {
if !YubikeyAccessible() { if !YubikeyAccessible() {
t.Skip("Must have Yubikey access.") t.Skip("Must have Yubikey access.")
} }
@ -905,7 +938,7 @@ func TestYubiRetrySign5TimesAndFail(t *testing.T) {
badSigner := &SignInvalidSigCtx{ badSigner := &SignInvalidSigCtx{
Ctx: *pkcs11.New(pkcs11Lib), Ctx: *pkcs11.New(pkcs11Lib),
goodSig: goodSig, goodSig: goodSig,
failNum: 5, failNum: sigAttempts + 1,
} }
yubiPrivateKey.setLibLoader(func(string) IPKCS11Ctx { return badSigner }) yubiPrivateKey.setLibLoader(func(string) IPKCS11Ctx { return badSigner })
@ -914,7 +947,7 @@ func TestYubiRetrySign5TimesAndFail(t *testing.T) {
assert.Error(t, err) assert.Error(t, err)
// because the SignInvalidSigCtx returns the good signature, we can just // because the SignInvalidSigCtx returns the good signature, we can just
// deep equal instead of verifying // deep equal instead of verifying
assert.Equal(t, 5, badSigner.signCalls) assert.Equal(t, sigAttempts, badSigner.signCalls)
} }
// ----- Stubbed pkcs11 for testing error conditions ------ // ----- Stubbed pkcs11 for testing error conditions ------