From 9b8645c39f2c315f54a8adc7c04445921b46edf4 Mon Sep 17 00:00:00 2001 From: David Lawrence Date: Wed, 11 Nov 2015 18:27:26 -0800 Subject: [PATCH] add verification to yubikey signatures. Attempt to generate sig up to 5 times, fail if all 5 are invalid Signed-off-by: David Lawrence (github: endophage) --- client/repo_pkcs11.go | 3 +- cmd/notary/integration_pkcs11_test.go | 14 ++++---- cmd/notary/keys_pkcs11.go | 3 +- trustmanager/{ => yubikey}/pkcs11_darwin.go | 2 +- trustmanager/{ => yubikey}/pkcs11_linux.go | 2 +- trustmanager/{ => yubikey}/yubikeystore.go | 35 ++++++++++++------- .../{ => yubikey}/yubikeystore_test.go | 19 +++++----- 7 files changed, 45 insertions(+), 33 deletions(-) rename trustmanager/{ => yubikey}/pkcs11_darwin.go (86%) rename trustmanager/{ => yubikey}/pkcs11_linux.go (88%) rename trustmanager/{ => yubikey}/yubikeystore.go (96%) rename trustmanager/{ => yubikey}/yubikeystore_test.go (86%) diff --git a/client/repo_pkcs11.go b/client/repo_pkcs11.go index ed82bd555b..1d2813f922 100644 --- a/client/repo_pkcs11.go +++ b/client/repo_pkcs11.go @@ -12,6 +12,7 @@ import ( "github.com/docker/notary/keystoremanager" "github.com/docker/notary/passphrase" "github.com/docker/notary/trustmanager" + "github.com/docker/notary/trustmanager/yubikey" "github.com/docker/notary/tuf/signed" "github.com/docker/notary/tuf/store" ) @@ -29,7 +30,7 @@ func NewNotaryRepository(baseDir, gun, baseURL string, rt http.RoundTripper, } keyStoreManager, err := keystoremanager.NewKeyStoreManager(baseDir, fileKeyStore) - yubiKeyStore, _ := trustmanager.NewYubiKeyStore(fileKeyStore, retriever) + yubiKeyStore, _ := yubikey.NewYubiKeyStore(fileKeyStore, retriever) var cryptoService signed.CryptoService if yubiKeyStore == nil { cryptoService = cryptoservice.NewCryptoService(gun, keyStoreManager.KeyStore) diff --git a/cmd/notary/integration_pkcs11_test.go b/cmd/notary/integration_pkcs11_test.go index 6f29ad0c08..77ffc0e991 100644 --- a/cmd/notary/integration_pkcs11_test.go +++ b/cmd/notary/integration_pkcs11_test.go @@ -6,12 +6,12 @@ import ( "testing" "github.com/docker/notary/passphrase" - "github.com/docker/notary/trustmanager" + "github.com/docker/notary/trustmanager/yubikey" "github.com/docker/notary/tuf/data" "github.com/stretchr/testify/assert" ) -var rootOnHardware = trustmanager.YubikeyAccessible +var rootOnHardware = yubikey.YubikeyAccessible // Per-test set up that returns a cleanup function. This set up: // - changes the passphrase retriever to always produce a constant passphrase @@ -29,10 +29,10 @@ func setUp(t *testing.T) func() { retriever = fake getRetriever = func() passphrase.Retriever { return fake } - trustmanager.SetYubikeyKeyMode(trustmanager.KeymodeNone) + yubikey.SetYubikeyKeyMode(yubikey.KeymodeNone) // //we're just removing keys here, so nil is fine - s, err := trustmanager.NewYubiKeyStore(nil, retriever) + s, err := yubikey.NewYubiKeyStore(nil, retriever) assert.NoError(t, err) for k := range s.ListKeys() { err := s.RemoveKey(k) @@ -42,7 +42,7 @@ func setUp(t *testing.T) func() { return func() { retriever = oldRetriever getRetriever = getPassphraseRetriever - trustmanager.SetYubikeyKeyMode(trustmanager.KeymodeTouch | trustmanager.KeymodePinOnce) + yubikey.SetYubikeyKeyMode(yubikey.KeymodeTouch | yubikey.KeymodePinOnce) } } @@ -51,9 +51,9 @@ func setUp(t *testing.T) func() { // on disk func verifyRootKeyOnHardware(t *testing.T, rootKeyID string) { // do not bother verifying if there is no yubikey available - if trustmanager.YubikeyAccessible() { + if yubikey.YubikeyAccessible() { // //we're just getting keys here, so nil is fine - s, err := trustmanager.NewYubiKeyStore(nil, retriever) + s, err := yubikey.NewYubiKeyStore(nil, retriever) assert.NoError(t, err) privKey, role, err := s.GetKey(rootKeyID) assert.NoError(t, err) diff --git a/cmd/notary/keys_pkcs11.go b/cmd/notary/keys_pkcs11.go index 87af9dad65..97b7af416d 100644 --- a/cmd/notary/keys_pkcs11.go +++ b/cmd/notary/keys_pkcs11.go @@ -5,8 +5,9 @@ package main import ( "github.com/docker/notary/passphrase" "github.com/docker/notary/trustmanager" + "github.com/docker/notary/trustmanager/yubikey" ) func getYubiKeyStore(fileKeyStore trustmanager.KeyStore, ret passphrase.Retriever) (trustmanager.KeyStore, error) { - return trustmanager.NewYubiKeyStore(fileKeyStore, ret) + return yubikey.NewYubiKeyStore(fileKeyStore, ret) } diff --git a/trustmanager/pkcs11_darwin.go b/trustmanager/yubikey/pkcs11_darwin.go similarity index 86% rename from trustmanager/pkcs11_darwin.go rename to trustmanager/yubikey/pkcs11_darwin.go index 63f1888b38..9d0040a31d 100644 --- a/trustmanager/pkcs11_darwin.go +++ b/trustmanager/yubikey/pkcs11_darwin.go @@ -1,6 +1,6 @@ // +build pkcs11,darwin -package trustmanager +package yubikey var possiblePkcs11Libs = []string{ "/usr/local/lib/libykcs11.dylib", diff --git a/trustmanager/pkcs11_linux.go b/trustmanager/yubikey/pkcs11_linux.go similarity index 88% rename from trustmanager/pkcs11_linux.go rename to trustmanager/yubikey/pkcs11_linux.go index 9765299914..61c7f14810 100644 --- a/trustmanager/pkcs11_linux.go +++ b/trustmanager/yubikey/pkcs11_linux.go @@ -1,6 +1,6 @@ // +build pkcs11,linux -package trustmanager +package yubikey var possiblePkcs11Libs = []string{ "/usr/lib/libykcs11.so", diff --git a/trustmanager/yubikeystore.go b/trustmanager/yubikey/yubikeystore.go similarity index 96% rename from trustmanager/yubikeystore.go rename to trustmanager/yubikey/yubikeystore.go index ac51db00b8..c3e55da0b7 100644 --- a/trustmanager/yubikeystore.go +++ b/trustmanager/yubikey/yubikeystore.go @@ -1,6 +1,6 @@ // +build pkcs11 -package trustmanager +package yubikey import ( "crypto" @@ -17,7 +17,9 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/notary/passphrase" + "github.com/docker/notary/trustmanager" "github.com/docker/notary/tuf/data" + "github.com/docker/notary/tuf/signed" "github.com/miekg/pkcs11" ) @@ -33,6 +35,8 @@ const ( // the key size, when importing a key into yubikey, MUST be 32 bytes ecdsaPrivateKeySize = 32 + + sigAttempts = 5 ) // what key mode to use when generating keys @@ -155,12 +159,17 @@ func (y *YubiPrivateKey) Sign(rand io.Reader, msg []byte, opts crypto.SignerOpts } defer cleanup(ctx, session) - sig, err := sign(ctx, session, y.slot, y.passRetriever, msg) - if err != nil { - return nil, fmt.Errorf("failed to sign using Yubikey: %v", err) + v := signed.Verifiers[data.ECDSASignature] + for i := 0; i < sigAttempts; i++ { + sig, err := sign(ctx, session, y.slot, y.passRetriever, msg) + if err != nil { + return nil, fmt.Errorf("failed to sign using Yubikey: %v", err) + } + if err := v.Verify(&y.ECDSAPublicKey, sig, msg); err == nil { + return sig, nil + } } - - return sig, nil + return nil, errors.New("Failed to generate signature on Yubikey.") } // If a byte array is less than the number of bytes specified by @@ -183,7 +192,7 @@ func addECDSAKey( pkcs11KeyID []byte, passRetriever passphrase.Retriever, role string, - backupStore KeyStore, + backupStore trustmanager.KeyStore, ) error { logrus.Debugf("Attempting to add key to yubikey with ID: %s", privKey.ID()) @@ -201,7 +210,7 @@ func addECDSAKey( ecdsaPrivKeyD := ensurePrivateKeySize(ecdsaPrivKey.D.Bytes()) - template, err := NewCertificate(role) + template, err := trustmanager.NewCertificate(role) if err != nil { return fmt.Errorf("failed to create the certificate template: %v", err) } @@ -561,12 +570,12 @@ func getNextEmptySlot(ctx *pkcs11.Ctx, session pkcs11.SessionHandle) ([]byte, er type YubiKeyStore struct { passRetriever passphrase.Retriever keys map[string]yubiSlot - backupStore KeyStore + backupStore trustmanager.KeyStore } // NewYubiKeyStore returns a YubiKeyStore, given a backup key store to write any // generated keys to (usually a KeyFileStore) -func NewYubiKeyStore(backupStore KeyStore, passphraseRetriever passphrase.Retriever) ( +func NewYubiKeyStore(backupStore trustmanager.KeyStore, passphraseRetriever passphrase.Retriever) ( *YubiKeyStore, error) { s := &YubiKeyStore{ @@ -720,7 +729,7 @@ func (s *YubiKeyStore) ExportKey(keyID string) ([]byte, error) { // ImportKey imports a root key into a Yubikey func (s *YubiKeyStore) ImportKey(pemBytes []byte, keyPath string) error { logrus.Debugf("Attempting to import: %s key inside of YubiKeyStore", keyPath) - privKey, _, err := GetPasswdDecryptBytes( + privKey, _, err := trustmanager.GetPasswdDecryptBytes( s.passRetriever, pemBytes, "", "imported root") if err != nil { logrus.Debugf("Failed to get and retrieve a key from: %s", keyPath) @@ -807,10 +816,10 @@ func login(ctx *pkcs11.Ctx, session pkcs11.SessionHandle, passRetriever passphra passwd, giveup, err := passRetriever(user, "yubikey", false, attempts) // Check if the passphrase retriever got an error or if it is telling us to give up if giveup || err != nil { - return ErrPasswordInvalid{} + return trustmanager.ErrPasswordInvalid{} } if attempts > 2 { - return ErrAttemptsExceeded{} + return trustmanager.ErrAttemptsExceeded{} } // Try to convert PEM encoded bytes back to a PrivateKey using the passphrase diff --git a/trustmanager/yubikeystore_test.go b/trustmanager/yubikey/yubikeystore_test.go similarity index 86% rename from trustmanager/yubikeystore_test.go rename to trustmanager/yubikey/yubikeystore_test.go index 6bbd9b8bc3..9d7863d62b 100644 --- a/trustmanager/yubikeystore_test.go +++ b/trustmanager/yubikey/yubikeystore_test.go @@ -1,6 +1,6 @@ // +build pkcs11 -package trustmanager +package yubikey import ( "crypto/rand" @@ -8,6 +8,7 @@ import ( "testing" "github.com/docker/notary/passphrase" + "github.com/docker/notary/trustmanager" "github.com/docker/notary/tuf/data" "github.com/stretchr/testify/assert" ) @@ -17,7 +18,7 @@ func clearAllKeys(t *testing.T) { // removing and then adding with the same YubiKeyStore causes // non-deterministic failures at least on Mac OS ret := passphrase.ConstantRetriever("passphrase") - store, err := NewYubiKeyStore(NewKeyMemoryStore(ret), ret) + store, err := NewYubiKeyStore(trustmanager.NewKeyMemoryStore(ret), ret) assert.NoError(t, err) for k := range store.ListKeys() { @@ -53,7 +54,7 @@ func TestEnsurePrivateKeySizePadsLessThanRequiredSizeArrays(t *testing.T) { } func testAddKey(t *testing.T, store *YubiKeyStore) (data.PrivateKey, error) { - privKey, err := GenerateECDSAKey(rand.Reader) + privKey, err := trustmanager.GenerateECDSAKey(rand.Reader) assert.NoError(t, err) err = store.AddKey(privKey.ID(), data.CanonicalRootRole, privKey) @@ -67,7 +68,7 @@ func TestAddKeyToNextEmptyYubikeySlot(t *testing.T) { clearAllKeys(t) ret := passphrase.ConstantRetriever("passphrase") - store, err := NewYubiKeyStore(NewKeyMemoryStore(ret), ret) + store, err := NewYubiKeyStore(trustmanager.NewKeyMemoryStore(ret), ret) assert.NoError(t, err) SetYubikeyKeyMode(KeymodeNone) defer func() { @@ -84,7 +85,7 @@ func TestAddKeyToNextEmptyYubikeySlot(t *testing.T) { } // create a new store, to make sure we're not just using the keys cache - store, err = NewYubiKeyStore(NewKeyMemoryStore(ret), ret) + store, err = NewYubiKeyStore(trustmanager.NewKeyMemoryStore(ret), ret) assert.NoError(t, err) listedKeys := store.ListKeys() assert.Len(t, listedKeys, numSlots) @@ -114,7 +115,7 @@ func TestImportKey(t *testing.T) { clearAllKeys(t) ret := passphrase.ConstantRetriever("passphrase") - backup := NewKeyMemoryStore(ret) + backup := trustmanager.NewKeyMemoryStore(ret) store, err := NewYubiKeyStore(backup, ret) assert.NoError(t, err) SetYubikeyKeyMode(KeymodeNone) @@ -123,10 +124,10 @@ func TestImportKey(t *testing.T) { }() // generate key and import it - privKey, err := GenerateECDSAKey(rand.Reader) + privKey, err := trustmanager.GenerateECDSAKey(rand.Reader) assert.NoError(t, err) - pemBytes, err := EncryptPrivateKey(privKey, "passphrase") + pemBytes, err := trustmanager.EncryptPrivateKey(privKey, "passphrase") assert.NoError(t, err) err = store.ImportKey(pemBytes, "root") @@ -138,7 +139,7 @@ func TestImportKey(t *testing.T) { // ensure key is in Yubikey - create a new store, to make sure we're not // just using the keys cache - store, err = NewYubiKeyStore(NewKeyMemoryStore(ret), ret) + store, err = NewYubiKeyStore(trustmanager.NewKeyMemoryStore(ret), ret) gottenKey, role, err := store.GetKey(privKey.ID()) assert.NoError(t, err) assert.Equal(t, data.CanonicalRootRole, role)