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/keystoremanager"
|
||||||
"github.com/docker/notary/passphrase"
|
"github.com/docker/notary/passphrase"
|
||||||
"github.com/docker/notary/trustmanager"
|
"github.com/docker/notary/trustmanager"
|
||||||
|
"github.com/docker/notary/trustmanager/yubikey"
|
||||||
"github.com/docker/notary/tuf/signed"
|
"github.com/docker/notary/tuf/signed"
|
||||||
"github.com/docker/notary/tuf/store"
|
"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)
|
keyStoreManager, err := keystoremanager.NewKeyStoreManager(baseDir, fileKeyStore)
|
||||||
yubiKeyStore, _ := trustmanager.NewYubiKeyStore(fileKeyStore, retriever)
|
yubiKeyStore, _ := yubikey.NewYubiKeyStore(fileKeyStore, retriever)
|
||||||
var cryptoService signed.CryptoService
|
var cryptoService signed.CryptoService
|
||||||
if yubiKeyStore == nil {
|
if yubiKeyStore == nil {
|
||||||
cryptoService = cryptoservice.NewCryptoService(gun, keyStoreManager.KeyStore)
|
cryptoService = cryptoservice.NewCryptoService(gun, keyStoreManager.KeyStore)
|
||||||
|
|
|
||||||
|
|
@ -6,12 +6,12 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/docker/notary/passphrase"
|
"github.com/docker/notary/passphrase"
|
||||||
"github.com/docker/notary/trustmanager"
|
"github.com/docker/notary/trustmanager/yubikey"
|
||||||
"github.com/docker/notary/tuf/data"
|
"github.com/docker/notary/tuf/data"
|
||||||
"github.com/stretchr/testify/assert"
|
"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:
|
// Per-test set up that returns a cleanup function. This set up:
|
||||||
// - changes the passphrase retriever to always produce a constant passphrase
|
// - changes the passphrase retriever to always produce a constant passphrase
|
||||||
|
|
@ -29,10 +29,10 @@ func setUp(t *testing.T) func() {
|
||||||
|
|
||||||
retriever = fake
|
retriever = fake
|
||||||
getRetriever = func() passphrase.Retriever { return 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
|
// //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)
|
assert.NoError(t, err)
|
||||||
for k := range s.ListKeys() {
|
for k := range s.ListKeys() {
|
||||||
err := s.RemoveKey(k)
|
err := s.RemoveKey(k)
|
||||||
|
|
@ -42,7 +42,7 @@ func setUp(t *testing.T) func() {
|
||||||
return func() {
|
return func() {
|
||||||
retriever = oldRetriever
|
retriever = oldRetriever
|
||||||
getRetriever = getPassphraseRetriever
|
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
|
// on disk
|
||||||
func verifyRootKeyOnHardware(t *testing.T, rootKeyID string) {
|
func verifyRootKeyOnHardware(t *testing.T, rootKeyID string) {
|
||||||
// do not bother verifying if there is no yubikey available
|
// 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
|
// //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)
|
assert.NoError(t, err)
|
||||||
privKey, role, err := s.GetKey(rootKeyID)
|
privKey, role, err := s.GetKey(rootKeyID)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,9 @@ package main
|
||||||
import (
|
import (
|
||||||
"github.com/docker/notary/passphrase"
|
"github.com/docker/notary/passphrase"
|
||||||
"github.com/docker/notary/trustmanager"
|
"github.com/docker/notary/trustmanager"
|
||||||
|
"github.com/docker/notary/trustmanager/yubikey"
|
||||||
)
|
)
|
||||||
|
|
||||||
func getYubiKeyStore(fileKeyStore trustmanager.KeyStore, ret passphrase.Retriever) (trustmanager.KeyStore, error) {
|
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
|
// +build pkcs11,darwin
|
||||||
|
|
||||||
package trustmanager
|
package yubikey
|
||||||
|
|
||||||
var possiblePkcs11Libs = []string{
|
var possiblePkcs11Libs = []string{
|
||||||
"/usr/local/lib/libykcs11.dylib",
|
"/usr/local/lib/libykcs11.dylib",
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
// +build pkcs11,linux
|
// +build pkcs11,linux
|
||||||
|
|
||||||
package trustmanager
|
package yubikey
|
||||||
|
|
||||||
var possiblePkcs11Libs = []string{
|
var possiblePkcs11Libs = []string{
|
||||||
"/usr/lib/libykcs11.so",
|
"/usr/lib/libykcs11.so",
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
// +build pkcs11
|
// +build pkcs11
|
||||||
|
|
||||||
package trustmanager
|
package yubikey
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto"
|
"crypto"
|
||||||
|
|
@ -17,7 +17,9 @@ import (
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
"github.com/docker/notary/passphrase"
|
"github.com/docker/notary/passphrase"
|
||||||
|
"github.com/docker/notary/trustmanager"
|
||||||
"github.com/docker/notary/tuf/data"
|
"github.com/docker/notary/tuf/data"
|
||||||
|
"github.com/docker/notary/tuf/signed"
|
||||||
"github.com/miekg/pkcs11"
|
"github.com/miekg/pkcs11"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -33,6 +35,8 @@ const (
|
||||||
|
|
||||||
// the key size, when importing a key into yubikey, MUST be 32 bytes
|
// the key size, when importing a key into yubikey, MUST be 32 bytes
|
||||||
ecdsaPrivateKeySize = 32
|
ecdsaPrivateKeySize = 32
|
||||||
|
|
||||||
|
sigAttempts = 5
|
||||||
)
|
)
|
||||||
|
|
||||||
// what key mode to use when generating keys
|
// what key mode to use when generating keys
|
||||||
|
|
@ -155,13 +159,18 @@ func (y *YubiPrivateKey) Sign(rand io.Reader, msg []byte, opts crypto.SignerOpts
|
||||||
}
|
}
|
||||||
defer cleanup(ctx, session)
|
defer cleanup(ctx, session)
|
||||||
|
|
||||||
|
v := signed.Verifiers[data.ECDSASignature]
|
||||||
|
for i := 0; i < sigAttempts; i++ {
|
||||||
sig, err := sign(ctx, session, y.slot, y.passRetriever, msg)
|
sig, err := sign(ctx, session, y.slot, y.passRetriever, msg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to sign using Yubikey: %v", err)
|
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
|
// If a byte array is less than the number of bytes specified by
|
||||||
// ecdsaPrivateKeySize, left-zero-pad the byte array until
|
// ecdsaPrivateKeySize, left-zero-pad the byte array until
|
||||||
|
|
@ -183,7 +192,7 @@ func addECDSAKey(
|
||||||
pkcs11KeyID []byte,
|
pkcs11KeyID []byte,
|
||||||
passRetriever passphrase.Retriever,
|
passRetriever passphrase.Retriever,
|
||||||
role string,
|
role string,
|
||||||
backupStore KeyStore,
|
backupStore trustmanager.KeyStore,
|
||||||
) error {
|
) error {
|
||||||
logrus.Debugf("Attempting to add key to yubikey with ID: %s", privKey.ID())
|
logrus.Debugf("Attempting to add key to yubikey with ID: %s", privKey.ID())
|
||||||
|
|
||||||
|
|
@ -201,7 +210,7 @@ func addECDSAKey(
|
||||||
|
|
||||||
ecdsaPrivKeyD := ensurePrivateKeySize(ecdsaPrivKey.D.Bytes())
|
ecdsaPrivKeyD := ensurePrivateKeySize(ecdsaPrivKey.D.Bytes())
|
||||||
|
|
||||||
template, err := NewCertificate(role)
|
template, err := trustmanager.NewCertificate(role)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to create the certificate template: %v", err)
|
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 {
|
type YubiKeyStore struct {
|
||||||
passRetriever passphrase.Retriever
|
passRetriever passphrase.Retriever
|
||||||
keys map[string]yubiSlot
|
keys map[string]yubiSlot
|
||||||
backupStore KeyStore
|
backupStore trustmanager.KeyStore
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewYubiKeyStore returns a YubiKeyStore, given a backup key store to write any
|
// NewYubiKeyStore returns a YubiKeyStore, given a backup key store to write any
|
||||||
// generated keys to (usually a KeyFileStore)
|
// generated keys to (usually a KeyFileStore)
|
||||||
func NewYubiKeyStore(backupStore KeyStore, passphraseRetriever passphrase.Retriever) (
|
func NewYubiKeyStore(backupStore trustmanager.KeyStore, passphraseRetriever passphrase.Retriever) (
|
||||||
*YubiKeyStore, error) {
|
*YubiKeyStore, error) {
|
||||||
|
|
||||||
s := &YubiKeyStore{
|
s := &YubiKeyStore{
|
||||||
|
|
@ -720,7 +729,7 @@ func (s *YubiKeyStore) ExportKey(keyID string) ([]byte, error) {
|
||||||
// ImportKey imports a root key into a Yubikey
|
// ImportKey imports a root key into a Yubikey
|
||||||
func (s *YubiKeyStore) ImportKey(pemBytes []byte, keyPath string) error {
|
func (s *YubiKeyStore) ImportKey(pemBytes []byte, keyPath string) error {
|
||||||
logrus.Debugf("Attempting to import: %s key inside of YubiKeyStore", keyPath)
|
logrus.Debugf("Attempting to import: %s key inside of YubiKeyStore", keyPath)
|
||||||
privKey, _, err := GetPasswdDecryptBytes(
|
privKey, _, err := trustmanager.GetPasswdDecryptBytes(
|
||||||
s.passRetriever, pemBytes, "", "imported root")
|
s.passRetriever, pemBytes, "", "imported root")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Debugf("Failed to get and retrieve a key from: %s", keyPath)
|
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)
|
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
|
// Check if the passphrase retriever got an error or if it is telling us to give up
|
||||||
if giveup || err != nil {
|
if giveup || err != nil {
|
||||||
return ErrPasswordInvalid{}
|
return trustmanager.ErrPasswordInvalid{}
|
||||||
}
|
}
|
||||||
if attempts > 2 {
|
if attempts > 2 {
|
||||||
return ErrAttemptsExceeded{}
|
return trustmanager.ErrAttemptsExceeded{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to convert PEM encoded bytes back to a PrivateKey using the passphrase
|
// Try to convert PEM encoded bytes back to a PrivateKey using the passphrase
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
// +build pkcs11
|
// +build pkcs11
|
||||||
|
|
||||||
package trustmanager
|
package yubikey
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/docker/notary/passphrase"
|
"github.com/docker/notary/passphrase"
|
||||||
|
"github.com/docker/notary/trustmanager"
|
||||||
"github.com/docker/notary/tuf/data"
|
"github.com/docker/notary/tuf/data"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
@ -17,7 +18,7 @@ func clearAllKeys(t *testing.T) {
|
||||||
// removing and then adding with the same YubiKeyStore causes
|
// removing and then adding with the same YubiKeyStore causes
|
||||||
// non-deterministic failures at least on Mac OS
|
// non-deterministic failures at least on Mac OS
|
||||||
ret := passphrase.ConstantRetriever("passphrase")
|
ret := passphrase.ConstantRetriever("passphrase")
|
||||||
store, err := NewYubiKeyStore(NewKeyMemoryStore(ret), ret)
|
store, err := NewYubiKeyStore(trustmanager.NewKeyMemoryStore(ret), ret)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
for k := range store.ListKeys() {
|
for k := range store.ListKeys() {
|
||||||
|
|
@ -53,7 +54,7 @@ func TestEnsurePrivateKeySizePadsLessThanRequiredSizeArrays(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func testAddKey(t *testing.T, store *YubiKeyStore) (data.PrivateKey, error) {
|
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)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
err = store.AddKey(privKey.ID(), data.CanonicalRootRole, privKey)
|
err = store.AddKey(privKey.ID(), data.CanonicalRootRole, privKey)
|
||||||
|
|
@ -67,7 +68,7 @@ func TestAddKeyToNextEmptyYubikeySlot(t *testing.T) {
|
||||||
clearAllKeys(t)
|
clearAllKeys(t)
|
||||||
|
|
||||||
ret := passphrase.ConstantRetriever("passphrase")
|
ret := passphrase.ConstantRetriever("passphrase")
|
||||||
store, err := NewYubiKeyStore(NewKeyMemoryStore(ret), ret)
|
store, err := NewYubiKeyStore(trustmanager.NewKeyMemoryStore(ret), ret)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
SetYubikeyKeyMode(KeymodeNone)
|
SetYubikeyKeyMode(KeymodeNone)
|
||||||
defer func() {
|
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
|
// 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)
|
assert.NoError(t, err)
|
||||||
listedKeys := store.ListKeys()
|
listedKeys := store.ListKeys()
|
||||||
assert.Len(t, listedKeys, numSlots)
|
assert.Len(t, listedKeys, numSlots)
|
||||||
|
|
@ -114,7 +115,7 @@ func TestImportKey(t *testing.T) {
|
||||||
clearAllKeys(t)
|
clearAllKeys(t)
|
||||||
|
|
||||||
ret := passphrase.ConstantRetriever("passphrase")
|
ret := passphrase.ConstantRetriever("passphrase")
|
||||||
backup := NewKeyMemoryStore(ret)
|
backup := trustmanager.NewKeyMemoryStore(ret)
|
||||||
store, err := NewYubiKeyStore(backup, ret)
|
store, err := NewYubiKeyStore(backup, ret)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
SetYubikeyKeyMode(KeymodeNone)
|
SetYubikeyKeyMode(KeymodeNone)
|
||||||
|
|
@ -123,10 +124,10 @@ func TestImportKey(t *testing.T) {
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// generate key and import it
|
// generate key and import it
|
||||||
privKey, err := GenerateECDSAKey(rand.Reader)
|
privKey, err := trustmanager.GenerateECDSAKey(rand.Reader)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
pemBytes, err := EncryptPrivateKey(privKey, "passphrase")
|
pemBytes, err := trustmanager.EncryptPrivateKey(privKey, "passphrase")
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
err = store.ImportKey(pemBytes, "root")
|
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
|
// ensure key is in Yubikey - create a new store, to make sure we're not
|
||||||
// just using the keys cache
|
// 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())
|
gottenKey, role, err := store.GetKey(privKey.ID())
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, data.CanonicalRootRole, role)
|
assert.Equal(t, data.CanonicalRootRole, role)
|
||||||
Loading…
Reference in New Issue