From 07f00651522a7eea97f7f0836b1ae194291bdde2 Mon Sep 17 00:00:00 2001 From: David Lawrence Date: Fri, 30 Oct 2015 16:23:56 -0700 Subject: [PATCH] ask for pin when signing Signed-off-by: David Lawrence (github: endophage) --- client/client.go | 48 ++++++++++++- cmd/notary/cert.go | 15 ++++- cmd/notary/keys.go | 50 ++++++++++++-- keystoremanager/import_export.go | 14 ++-- keystoremanager/keystoremanager.go | 11 +-- keystoremanager/keystoremanager_test.go | 13 +++- signer/api/ecdsa_hardware_crypto_service.go | 75 +++++++++++++++++---- 7 files changed, 184 insertions(+), 42 deletions(-) diff --git a/client/client.go b/client/client.go index 6bd95d7f82..eb43189a00 100644 --- a/client/client.go +++ b/client/client.go @@ -24,7 +24,9 @@ import ( "github.com/docker/notary/tuf/store" ) -const maxSize = 5 << 20 +const ( + maxSize = 5 << 20 +) func init() { data.SetDefaultExpiryTimes( @@ -96,6 +98,50 @@ func NewTarget(targetName string, targetPath string) (*Target, error) { return &Target{Name: targetName, Hashes: meta.Hashes, Length: meta.Length}, nil } +// NewNotaryRepository is a helper method that returns a new notary repository. +// It takes the base directory under where all the trust files will be stored +// (usually ~/.docker/trust/). +func NewNotaryRepository(baseDir, gun, baseURL string, rt http.RoundTripper, + passphraseRetriever passphrase.Retriever) (*NotaryRepository, error) { + + keysPath := filepath.Join(baseDir, keystoremanager.PrivDir) + fileKeyStore, err := trustmanager.NewKeyFileStore(keysPath, passphraseRetriever) + if err != nil { + return nil, err + } + + keyStoreManager, err := keystoremanager.NewKeyStoreManager(baseDir, fileKeyStore) + if err != nil { + return nil, err + } + + yubiKeyStore := api.NewYubiKeyStore(passphraseRetriever) + cryptoService := cryptoservice.NewCryptoService(gun, yubiKeyStore, fileKeyStore) + + nRepo := &NotaryRepository{ + gun: gun, + baseDir: baseDir, + baseURL: baseURL, + tufRepoPath: filepath.Join(baseDir, tufDir, filepath.FromSlash(gun)), + CryptoService: cryptoService, + roundTrip: rt, + KeyStoreManager: keyStoreManager, + } + + fileStore, err := store.NewFilesystemStore( + nRepo.tufRepoPath, + "metadata", + "json", + "", + ) + if err != nil { + return nil, err + } + nRepo.fileStore = fileStore + + return nRepo, nil +} + // Initialize creates a new repository by using rootKey as the root Key for the // TUF repository. func (r *NotaryRepository) Initialize(rootKeyID string) error { diff --git a/cmd/notary/cert.go b/cmd/notary/cert.go index 5b76397515..21223bcf8a 100644 --- a/cmd/notary/cert.go +++ b/cmd/notary/cert.go @@ -5,6 +5,7 @@ import ( "fmt" "math" "os" + "path/filepath" "time" "github.com/docker/notary/keystoremanager" @@ -55,7 +56,12 @@ func certRemove(cmd *cobra.Command, args []string) { parseConfig() - keyStoreManager, err := keystoremanager.NewKeyStoreManager(trustDir, retriever) + keysPath := filepath.Join(trustDir, keystoremanager.PrivDir) + fileKeyStore, err := trustmanager.NewKeyFileStore(keysPath, retriever) + if err != nil { + fatalf("failed to create private key store in directory: %s", keysPath) + } + keyStoreManager, err := keystoremanager.NewKeyStoreManager(trustDir, fileKeyStore) if err != nil { fatalf("failed to create a new truststore manager with directory: %s", trustDir) } @@ -119,7 +125,12 @@ func certList(cmd *cobra.Command, args []string) { parseConfig() - keyStoreManager, err := keystoremanager.NewKeyStoreManager(trustDir, retriever) + keysPath := filepath.Join(trustDir, keystoremanager.PrivDir) + fileKeyStore, err := trustmanager.NewKeyFileStore(keysPath, retriever) + if err != nil { + fatalf("failed to create private key store in directory: %s", keysPath) + } + keyStoreManager, err := keystoremanager.NewKeyStoreManager(trustDir, fileKeyStore) if err != nil { fatalf("failed to create a new truststore manager with directory: %s", trustDir) } diff --git a/cmd/notary/keys.go b/cmd/notary/keys.go index 04db681393..7cc2297e48 100644 --- a/cmd/notary/keys.go +++ b/cmd/notary/keys.go @@ -11,6 +11,7 @@ import ( notaryclient "github.com/docker/notary/client" "github.com/docker/notary/keystoremanager" "github.com/docker/notary/pkg/passphrase" + "github.com/docker/notary/trustmanager" "github.com/spf13/cobra" ) @@ -111,7 +112,12 @@ func keysRemoveKey(cmd *cobra.Command, args []string) { parseConfig() - keyStoreManager, err := keystoremanager.NewKeyStoreManager(trustDir, retriever) + keysPath := filepath.Join(trustDir, keystoremanager.PrivDir) + fileKeyStore, err := trustmanager.NewKeyFileStore(keysPath, retriever) + if err != nil { + fatalf("failed to create private key store in directory: %s", keysPath) + } + keyStoreManager, err := keystoremanager.NewKeyStoreManager(trustDir, fileKeyStore) if err != nil { fatalf("failed to create a new truststore manager with directory: %s", trustDir) } @@ -167,7 +173,12 @@ func keysList(cmd *cobra.Command, args []string) { parseConfig() - keyStoreManager, err := keystoremanager.NewKeyStoreManager(trustDir, retriever) + keysPath := filepath.Join(trustDir, keystoremanager.PrivDir) + fileKeyStore, err := trustmanager.NewKeyFileStore(keysPath, retriever) + if err != nil { + fatalf("failed to create private key store in directory: %s", keysPath) + } + keyStoreManager, err := keystoremanager.NewKeyStoreManager(trustDir, fileKeyStore) if err != nil { fatalf("failed to create a new truststore manager with directory: %s", trustDir) } @@ -220,7 +231,12 @@ func keysGenerateRootKey(cmd *cobra.Command, args []string) { parseConfig() - keyStoreManager, err := keystoremanager.NewKeyStoreManager(trustDir, retriever) + keysPath := filepath.Join(trustDir, keystoremanager.PrivDir) + fileKeyStore, err := trustmanager.NewKeyFileStore(keysPath, retriever) + if err != nil { + fatalf("failed to create private key store in directory: %s", keysPath) + } + keyStoreManager, err := keystoremanager.NewKeyStoreManager(trustDir, fileKeyStore) if err != nil { fatalf("failed to create a new truststore manager with directory: %s", trustDir) } @@ -244,7 +260,12 @@ func keysExport(cmd *cobra.Command, args []string) { parseConfig() - keyStoreManager, err := keystoremanager.NewKeyStoreManager(trustDir, retriever) + keysPath := filepath.Join(trustDir, keystoremanager.PrivDir) + fileKeyStore, err := trustmanager.NewKeyFileStore(keysPath, retriever) + if err != nil { + fatalf("failed to create private key store in directory: %s", keysPath) + } + keyStoreManager, err := keystoremanager.NewKeyStoreManager(trustDir, fileKeyStore) if err != nil { fatalf("failed to create a new truststore manager with directory: %s", trustDir) } @@ -287,7 +308,12 @@ func keysExportRoot(cmd *cobra.Command, args []string) { parseConfig() - keyStoreManager, err := keystoremanager.NewKeyStoreManager(trustDir, retriever) + keysPath := filepath.Join(trustDir, keystoremanager.PrivDir) + fileKeyStore, err := trustmanager.NewKeyFileStore(keysPath, retriever) + if err != nil { + fatalf("failed to create private key store in directory: %s", keysPath) + } + keyStoreManager, err := keystoremanager.NewKeyStoreManager(trustDir, fileKeyStore) if err != nil { fatalf("failed to create a new truststore manager with directory: %s", trustDir) } @@ -322,7 +348,12 @@ func keysImport(cmd *cobra.Command, args []string) { parseConfig() - keyStoreManager, err := keystoremanager.NewKeyStoreManager(trustDir, retriever) + keysPath := filepath.Join(trustDir, keystoremanager.PrivDir) + fileKeyStore, err := trustmanager.NewKeyFileStore(keysPath, retriever) + if err != nil { + fatalf("failed to create private key store in directory: %s", keysPath) + } + keyStoreManager, err := keystoremanager.NewKeyStoreManager(trustDir, fileKeyStore) if err != nil { fatalf("failed to create a new truststore manager with directory: %s", trustDir) } @@ -351,7 +382,12 @@ func keysImportRoot(cmd *cobra.Command, args []string) { parseConfig() - keyStoreManager, err := keystoremanager.NewKeyStoreManager(trustDir, retriever) + keysPath := filepath.Join(trustDir, keystoremanager.PrivDir) + fileKeyStore, err := trustmanager.NewKeyFileStore(keysPath, retriever) + if err != nil { + fatalf("failed to create private key store in directory: %s", keysPath) + } + keyStoreManager, err := keystoremanager.NewKeyStoreManager(trustDir, fileKeyStore) if err != nil { fatalf("failed to create a new truststore manager with directory: %s", trustDir) } diff --git a/keystoremanager/import_export.go b/keystoremanager/import_export.go index ce099f738f..6fd4735e3e 100644 --- a/keystoremanager/import_export.go +++ b/keystoremanager/import_export.go @@ -60,7 +60,7 @@ func (km *KeyStoreManager) ExportRootKeyReencrypt(dest io.Writer, keyID string, tempBaseDir, err := ioutil.TempDir("", "notary-key-export-") defer os.RemoveAll(tempBaseDir) - tempKeysPath := filepath.Join(tempBaseDir, privDir) + tempKeysPath := filepath.Join(tempBaseDir, PrivDir) tempKeyStore, err := trustmanager.NewKeyFileStore(tempKeysPath, newPassphraseRetriever) if err != nil { return err @@ -178,7 +178,7 @@ func (km *KeyStoreManager) ExportAllKeys(dest io.Writer, newPassphraseRetriever defer os.RemoveAll(tempBaseDir) // Create temporary keystore to use as a staging area - tempKeysPath := filepath.Join(tempBaseDir, privDir) + tempKeysPath := filepath.Join(tempBaseDir, PrivDir) tempKeyStore, err := trustmanager.NewKeyFileStore(tempKeysPath, newPassphraseRetriever) if err != nil { return err @@ -190,7 +190,7 @@ func (km *KeyStoreManager) ExportAllKeys(dest io.Writer, newPassphraseRetriever zipWriter := zip.NewWriter(dest) - if err := addKeysToArchive(zipWriter, tempKeyStore, privDir); err != nil { + if err := addKeysToArchive(zipWriter, tempKeyStore, PrivDir); err != nil { return err } @@ -224,14 +224,14 @@ func (km *KeyStoreManager) ImportKeysZip(zipReader zip.Reader) error { // Note that using / as a separator is okay here - the zip // package guarantees that the separator will be / - if strings.HasPrefix(fNameTrimmed, privDir) { + if strings.HasPrefix(fNameTrimmed, PrivDir) { if fNameTrimmed[len(fNameTrimmed)-5:] == "_root" { if err = checkRootKeyIsEncrypted(fileBytes); err != nil { rc.Close() return err } } - keyName := strings.TrimPrefix(fNameTrimmed, privDir) + keyName := strings.TrimPrefix(fNameTrimmed, PrivDir) newKeys[keyName] = fileBytes } else { // This path inside the zip archive doesn't look like a @@ -283,7 +283,7 @@ func (km *KeyStoreManager) ExportKeysByGUN(dest io.Writer, gun string, passphras defer os.RemoveAll(tempBaseDir) // Create temporary keystore to use as a staging area - tempKeysPath := filepath.Join(tempBaseDir, privDir) + tempKeysPath := filepath.Join(tempBaseDir, PrivDir) tempKeyStore, err := trustmanager.NewKeyFileStore(tempKeysPath, passphraseRetriever) if err != nil { return err @@ -299,7 +299,7 @@ func (km *KeyStoreManager) ExportKeysByGUN(dest io.Writer, gun string, passphras return ErrNoKeysFoundForGUN } - if err := addKeysToArchive(zipWriter, tempKeyStore, privDir); err != nil { + if err := addKeysToArchive(zipWriter, tempKeyStore, PrivDir); err != nil { return err } diff --git a/keystoremanager/keystoremanager.go b/keystoremanager/keystoremanager.go index 58cb5b6bc3..09f336de0b 100644 --- a/keystoremanager/keystoremanager.go +++ b/keystoremanager/keystoremanager.go @@ -10,7 +10,6 @@ import ( "time" "github.com/Sirupsen/logrus" - "github.com/docker/notary/pkg/passphrase" "github.com/docker/notary/trustmanager" "github.com/docker/notary/tuf/data" "github.com/docker/notary/tuf/signed" @@ -26,7 +25,7 @@ type KeyStoreManager struct { const ( trustDir = "trusted_certificates" - privDir = "private" + PrivDir = "private" rsaRootKeySize = 4096 // Used for new root keys ) @@ -56,13 +55,7 @@ func (err ErrRootRotationFail) Error() string { // NewKeyStoreManager returns an initialized KeyStoreManager, or an error // if it fails to create the KeyFileStores or load certificates -func NewKeyStoreManager(baseDir string, passphraseRetriever passphrase.Retriever) (*KeyStoreManager, error) { - keysPath := filepath.Join(baseDir, privDir) - keyStore, err := trustmanager.NewKeyFileStore(keysPath, passphraseRetriever) - if err != nil { - return nil, err - } - +func NewKeyStoreManager(baseDir string, keyStore *trustmanager.KeyFileStore) (*KeyStoreManager, error) { trustPath := filepath.Join(baseDir, trustDir) // Load all CAs that aren't expired and don't use SHA1 diff --git a/keystoremanager/keystoremanager_test.go b/keystoremanager/keystoremanager_test.go index 075689c6f0..bae645bdac 100644 --- a/keystoremanager/keystoremanager_test.go +++ b/keystoremanager/keystoremanager_test.go @@ -6,6 +6,7 @@ import ( "encoding/json" "io/ioutil" "os" + "path/filepath" "testing" "text/template" @@ -121,8 +122,12 @@ func TestValidateRoot(t *testing.T) { defer os.RemoveAll(tempBaseDir) assert.NoError(t, err, "failed to create a temporary directory: %s", err) + keysPath := filepath.Join(tempBaseDir, PrivDir) + fileKeyStore, err := trustmanager.NewKeyFileStore(keysPath, passphraseRetriever) + assert.NoError(t, err) + // Create a FileStoreManager - keyStoreManager, err := NewKeyStoreManager(tempBaseDir, passphraseRetriever) + keyStoreManager, err := NewKeyStoreManager(tempBaseDir, fileKeyStore) assert.NoError(t, err) // Execute our template @@ -229,8 +234,12 @@ func filestoreWithTwoCerts(t *testing.T, gun, keyAlg string) ( tempBaseDir, err := ioutil.TempDir("", "notary-test-") assert.NoError(t, err, "failed to create a temporary directory: %s", err) + keysPath := filepath.Join(tempBaseDir, PrivDir) + fileKeyStore, err := trustmanager.NewKeyFileStore(keysPath, passphraseRetriever) + assert.NoError(t, err) + // Create a FileStoreManager - keyStoreManager, err := NewKeyStoreManager(tempBaseDir, passphraseRetriever) + keyStoreManager, err := NewKeyStoreManager(tempBaseDir, fileKeyStore) assert.NoError(t, err) certs := make([]*x509.Certificate, 2) diff --git a/signer/api/ecdsa_hardware_crypto_service.go b/signer/api/ecdsa_hardware_crypto_service.go index 25ce36f85a..8c37feaea3 100644 --- a/signer/api/ecdsa_hardware_crypto_service.go +++ b/signer/api/ecdsa_hardware_crypto_service.go @@ -15,13 +15,17 @@ import ( "math/big" "github.com/Sirupsen/logrus" + "github.com/docker/notary/pkg/passphrase" "github.com/docker/notary/trustmanager" "github.com/docker/notary/tuf/data" "github.com/miekg/pkcs11" ) -const pkcs11Lib = "/usr/local/lib/libykcs11.so" -const USER_PIN = "123456" +const ( + pkcs11Lib = "/usr/local/lib/libykcs11.so" + USER_PIN = "123456" + SO_USER_PIN = "010203040506070801020304050607080102030405060708" +) // Hardcoded yubikey PKCS11 ID var YUBIKEY_ROOT_KEY_ID = []byte{2} @@ -29,14 +33,18 @@ var YUBIKEY_ROOT_KEY_ID = []byte{2} // YubiPrivateKey represents a private key inside of a yubikey type YubiPrivateKey struct { data.ECDSAPublicKey + passRetriever passphrase.Retriever } type YubikeySigner struct { YubiPrivateKey } -func NewYubiPrivateKey(pubKey data.ECDSAPublicKey) *YubiPrivateKey { - return &YubiPrivateKey{ECDSAPublicKey: pubKey} +func NewYubiPrivateKey(pubKey data.ECDSAPublicKey, passRetriever passphrase.Retriever) *YubiPrivateKey { + return &YubiPrivateKey{ + ECDSAPublicKey: pubKey, + passRetriever: passRetriever, + } } func (ys *YubikeySigner) Public() crypto.PublicKey { @@ -72,7 +80,7 @@ func (y *YubiPrivateKey) Sign(rand io.Reader, msg []byte, opts crypto.SignerOpts } defer cleanup(ctx, session) - sig, err := sign(ctx, session, YUBIKEY_ROOT_KEY_ID, msg) + sig, err := sign(ctx, session, YUBIKEY_ROOT_KEY_ID, y.passRetriever, msg) if err != nil { return nil, fmt.Errorf("failed to sign using Yubikey: %v", err) } @@ -81,11 +89,11 @@ func (y *YubiPrivateKey) Sign(rand io.Reader, msg []byte, opts crypto.SignerOpts } // addECDSAKey adds a key to the yubikey -func addECDSAKey(ctx *pkcs11.Ctx, session pkcs11.SessionHandle, privKey data.PrivateKey, pkcs11KeyID []byte) error { +func addECDSAKey(ctx *pkcs11.Ctx, session pkcs11.SessionHandle, privKey data.PrivateKey, pkcs11KeyID []byte, passRetriever passphrase.Retriever) error { logrus.Debugf("Got into add key with key: %s\n", privKey.ID()) // TODO(diogo): Figure out CKU_SO with yubikey - err := ctx.Login(session, pkcs11.CKU_SO, "010203040506070801020304050607080102030405060708") + err := login(ctx, session, passRetriever, pkcs11.CKU_SO, SO_USER_PIN) if err != nil { return err } @@ -206,8 +214,8 @@ func getECDSAKey(ctx *pkcs11.Ctx, session pkcs11.SessionHandle, pkcs11KeyID []by } // Sign returns a signature for a given signature request -func sign(ctx *pkcs11.Ctx, session pkcs11.SessionHandle, pkcs11KeyID, payload []byte) ([]byte, error) { - err := ctx.Login(session, pkcs11.CKU_USER, USER_PIN) +func sign(ctx *pkcs11.Ctx, session pkcs11.SessionHandle, pkcs11KeyID []byte, passRetriever passphrase.Retriever, payload []byte) ([]byte, error) { + err := login(ctx, session, passRetriever, pkcs11.CKU_USER, USER_PIN) if err != nil { return nil, fmt.Errorf("error logging in: %v", err) } @@ -253,10 +261,12 @@ func sign(ctx *pkcs11.Ctx, session pkcs11.SessionHandle, pkcs11KeyID, payload [] return sig[:], nil } -type YubiKeyStore struct{} +type YubiKeyStore struct { + passRetriever passphrase.Retriever +} -func NewYubiKeyStore() *YubiKeyStore { - return &YubiKeyStore{} +func NewYubiKeyStore(passphraseRetriever passphrase.Retriever) *YubiKeyStore { + return &YubiKeyStore{passRetriever: passphraseRetriever} } func (s *YubiKeyStore) ListKeys() map[string]string { @@ -285,7 +295,7 @@ func (s *YubiKeyStore) AddKey(keyID, alias string, privKey data.PrivateKey) erro } defer cleanup(ctx, session) - return addECDSAKey(ctx, session, privKey, YUBIKEY_ROOT_KEY_ID) + return addECDSAKey(ctx, session, privKey, YUBIKEY_ROOT_KEY_ID, s.passRetriever) } func (s *YubiKeyStore) GetKey(keyID string) (data.PrivateKey, string, error) { @@ -303,7 +313,7 @@ func (s *YubiKeyStore) GetKey(keyID string) (data.PrivateKey, string, error) { if pubKey.ID() != keyID { return nil, "", fmt.Errorf("expected root key: %s, but found: %s\n", keyID, pubKey.ID()) } - privKey := NewYubiPrivateKey(*pubKey) + privKey := NewYubiPrivateKey(*pubKey, s.passRetriever) if privKey == nil { return nil, "", errors.New("could not initialize new YubiPrivateKey") } @@ -367,3 +377,40 @@ func SetupHSMEnv(libraryPath string) (*pkcs11.Ctx, pkcs11.SessionHandle, error) return p, session, nil } + +func login(ctx *pkcs11.Ctx, session pkcs11.SessionHandle, passRetriever passphrase.Retriever, userFlag uint, defaultPassw string) error { + // try default password + err := ctx.Login(session, userFlag, defaultPassw) + if err == nil { + return nil + } + + // default failed, ask user for password + for attempts := 0; ; attempts++ { + var ( + giveup bool + err error + user string + ) + if userFlag == pkcs11.CKU_SO { + user = "SO Pin" + } else { + user = "Pin" + } + 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 trustmanager.ErrPasswordInvalid{} + } + if attempts > 2 { + return trustmanager.ErrAttemptsExceeded{} + } + + // Try to convert PEM encoded bytes back to a PrivateKey using the passphrase + err = ctx.Login(session, userFlag, passwd) + if err == nil { + return nil + } + } + return nil +}