Merge pull request #54 from docker/verify_hw_sigs

add verification to yubikey signatures. Attempt to generate sig up to…

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

Signed-off-by: Diogo Mónica <diogo.monica@gmail.com> (github: endophage)
This commit is contained in:
Diogo Mónica 2015-11-11 19:09:25 -08:00 committed by David Lawrence
commit b894d98392
7 changed files with 45 additions and 33 deletions

View File

@ -11,6 +11,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"
)
@ -27,7 +28,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)

View File

@ -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)

View File

@ -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)
}

View File

@ -1,6 +1,6 @@
// +build pkcs11,darwin
package trustmanager
package yubikey
var possiblePkcs11Libs = []string{
"/usr/local/lib/libykcs11.dylib",

View File

@ -1,6 +1,6 @@
// +build pkcs11,linux
package trustmanager
package yubikey
var possiblePkcs11Libs = []string{
"/usr/lib/libykcs11.so",

View File

@ -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

View File

@ -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)