mirror of https://github.com/docker/docs.git
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:
commit
b894d98392
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
// +build pkcs11,darwin
|
||||
|
||||
package trustmanager
|
||||
package yubikey
|
||||
|
||||
var possiblePkcs11Libs = []string{
|
||||
"/usr/local/lib/libykcs11.dylib",
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
// +build pkcs11,linux
|
||||
|
||||
package trustmanager
|
||||
package yubikey
|
||||
|
||||
var possiblePkcs11Libs = []string{
|
||||
"/usr/lib/libykcs11.so",
|
||||
|
|
@ -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
|
||||
|
|
@ -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)
|
||||
Loading…
Reference in New Issue