Skip to content

Commit

Permalink
refactored trustmanager to create abstraction
Browse files Browse the repository at this point in the history
resolves notaryproject#1289
Signed-off-by: Florian Eichin <florian.eichin@gmail.com>
  • Loading branch information
florianeichin committed Feb 16, 2018
1 parent 6008ff6 commit 3811106
Show file tree
Hide file tree
Showing 22 changed files with 817 additions and 656 deletions.
3 changes: 2 additions & 1 deletion client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ import (
"github.com/theupdateframework/notary/client/changelist"
"github.com/theupdateframework/notary/cryptoservice"
store "github.com/theupdateframework/notary/storage"
"github.com/theupdateframework/notary/trustmanager/pkcs11"
"github.com/theupdateframework/notary/trustpinning"
"github.com/theupdateframework/notary/tuf"
"github.com/theupdateframework/notary/tuf/data"
Expand Down Expand Up @@ -153,7 +154,7 @@ func NewFileCachedRepository(baseDir string, gun data.GUN, baseURL string, rt ht
if err != nil {
return nil, err
}

pkcs11.Setup()
keyStores, err := getKeyStores(baseDir, retriever)
if err != nil {
return nil, err
Expand Down
11 changes: 8 additions & 3 deletions client/client_pkcs11_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,20 @@

package client

import "github.com/theupdateframework/notary/trustmanager/yubikey"
import (
"github.com/theupdateframework/notary/trustmanager/pkcs11/common"
"github.com/theupdateframework/notary/trustmanager/pkcs11/yubikey"
)

// clear out all keys
func init() {
yubikey.SetYubikeyKeyMode(0)
if !yubikey.IsAccessible() {
ks := yubikey.NewKeyStore()
common.SetKeyStore(ks)
if !common.IsAccessible() {
return
}
store, err := yubikey.NewYubiStore(nil, nil)
store, err := common.NewHardwareStore(nil, nil)
if err == nil {
for k := range store.ListKeys() {
store.RemoveKey(k)
Expand Down
14 changes: 10 additions & 4 deletions client/repo_pkcs11.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,25 @@ import (

"github.com/theupdateframework/notary"
"github.com/theupdateframework/notary/trustmanager"
"github.com/theupdateframework/notary/trustmanager/yubikey"
"github.com/theupdateframework/notary/trustmanager/pkcs11"
"github.com/theupdateframework/notary/trustmanager/pkcs11/common"
)

func init() {
pkcs11.Setup()
}

func getKeyStores(baseDir string, retriever notary.PassRetriever) ([]trustmanager.KeyStore, error) {
fileKeyStore, err := trustmanager.NewKeyFileStore(baseDir, retriever)
if err != nil {
return nil, fmt.Errorf("failed to create private key store in directory: %s", baseDir)
}

keyStores := []trustmanager.KeyStore{fileKeyStore}
yubiKeyStore, _ := yubikey.NewYubiStore(fileKeyStore, retriever)
if yubiKeyStore != nil {
keyStores = []trustmanager.KeyStore{yubiKeyStore, fileKeyStore}
hardwareKeyStore, _ := common.NewHardwareStore(fileKeyStore, retriever)
if hardwareKeyStore != nil {
keyStores = []trustmanager.KeyStore{hardwareKeyStore, fileKeyStore}
return keyStores, nil
}
return keyStores, nil
}
14 changes: 7 additions & 7 deletions cmd/notary/integration_pkcs11_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@ import (
"github.com/stretchr/testify/require"
"github.com/theupdateframework/notary"
"github.com/theupdateframework/notary/passphrase"
"github.com/theupdateframework/notary/trustmanager/yubikey"
"github.com/theupdateframework/notary/trustmanager/pkcs11/common"
"github.com/theupdateframework/notary/trustmanager/pkcs11/yubikey"
"github.com/theupdateframework/notary/tuf/data"
)

var _retriever notary.PassRetriever

func init() {
yubikey.SetYubikeyKeyMode(yubikey.KeymodeNone)

regRetriver := passphrase.PromptRetriever()
_retriever := func(k, a string, c bool, n int) (string, bool, error) {
if k == "Yubikey" {
Expand All @@ -27,7 +27,7 @@ func init() {
}

// best effort at removing keys here, so nil is fine
s, err := yubikey.NewYubiStore(nil, _retriever)
s, err := common.NewHardwareStore(nil, _retriever)
if err != nil {
for k := range s.ListKeys() {
s.RemoveKey(k)
Expand All @@ -42,12 +42,12 @@ func init() {
}
}

var rootOnHardware = yubikey.IsAccessible
var rootOnHardware = common.IsAccessible

// Per-test set up deletes all keys on the yubikey
func setUp(t *testing.T) {
//we're just removing keys here, so nil is fine
s, err := yubikey.NewYubiStore(nil, _retriever)
s, err := common.NewHardwareStore(nil, _retriever)
require.NoError(t, err)
for k := range s.ListKeys() {
err := s.RemoveKey(k)
Expand All @@ -60,9 +60,9 @@ func setUp(t *testing.T) {
// on disk
func verifyRootKeyOnHardware(t *testing.T, rootKeyID string) {
// do not bother verifying if there is no yubikey available
if yubikey.IsAccessible() {
if common.IsAccessible() {
// //we're just getting keys here, so nil is fine
s, err := yubikey.NewYubiStore(nil, _retriever)
s, err := common.NewHardwareStore(nil, _retriever)
require.NoError(t, err)
privKey, role, err := s.GetKey(rootKeyID)
require.NoError(t, err)
Expand Down
18 changes: 11 additions & 7 deletions cmd/notary/keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -482,7 +482,7 @@ func (k *keyCommander) keyPassphraseChange(cmd *cobra.Command, args []string) er
var addingKeyStore trustmanager.KeyStore
switch foundKeyStore.Name() {
case "yubikey":
addingKeyStore, err = getYubiStore(nil, passChangeRetriever)
addingKeyStore, err = getHardwareStore(nil, passChangeRetriever)
keyInfo = trustmanager.KeyInfo{Role: data.CanonicalRootRole}
default:
addingKeyStore, err = trustmanager.NewKeyFileStore(config.GetString("trust_dir"), passChangeRetriever)
Expand Down Expand Up @@ -596,17 +596,21 @@ func (k *keyCommander) getKeyStores(
ks := []trustmanager.KeyStore{fileKeyStore}

if withHardware {
var yubiStore trustmanager.KeyStore
var hardwareErr error
var hardwareStore trustmanager.KeyStore
if hardwareBackup {
yubiStore, err = getYubiStore(fileKeyStore, retriever)
hardwareStore, hardwareErr = getHardwareStore(fileKeyStore, retriever)
} else {
yubiStore, err = getYubiStore(nil, retriever)
hardwareStore, hardwareErr = getHardwareStore(nil, retriever)

}
if err == nil && yubiStore != nil {
if hardwareErr == nil && hardwareStore != nil {
// Note that the order is important, since we want to prioritize
// the yubikey store
ks = []trustmanager.KeyStore{yubiStore, fileKeyStore}
// the hardware store
ks = []trustmanager.KeyStore{hardwareStore, fileKeyStore}
return ks, nil
}

}

return ks, nil
Expand Down
2 changes: 1 addition & 1 deletion cmd/notary/keys_nonpkcs11.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
"github.com/theupdateframework/notary/trustmanager"
)

func getYubiStore(fileKeyStore trustmanager.KeyStore, ret notary.PassRetriever) (trustmanager.KeyStore, error) {
func getHardwareStore(fileKeyStore trustmanager.KeyStore, ret notary.PassRetriever) (trustmanager.KeyStore, error) {
return nil, errors.New("Not built with hardware support")
}

Expand Down
18 changes: 11 additions & 7 deletions cmd/notary/keys_pkcs11.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,26 @@ import (
"github.com/theupdateframework/notary"
store "github.com/theupdateframework/notary/storage"
"github.com/theupdateframework/notary/trustmanager"
"github.com/theupdateframework/notary/trustmanager/yubikey"
"github.com/theupdateframework/notary/trustmanager/pkcs11"
"github.com/theupdateframework/notary/trustmanager/pkcs11/common"
)

func getYubiStore(fileKeyStore trustmanager.KeyStore, ret notary.PassRetriever) (*yubikey.YubiStore, error) {
return yubikey.NewYubiStore(fileKeyStore, ret)
func init() {
pkcs11.Setup()
}

func getImporters(baseDir string, ret notary.PassRetriever) ([]trustmanager.Importer, error) {
func getHardwareStore(fileKeyStore trustmanager.KeyStore, ret notary.PassRetriever) (*common.HardwareStore, error) {
return common.NewHardwareStore(fileKeyStore, ret)
}

func getImporters(baseDir string, ret notary.PassRetriever) ([]trustmanager.Importer, error) {
var importers []trustmanager.Importer
if yubikey.IsAccessible() {
yubiStore, err := getYubiStore(nil, ret)
if common.IsAccessible() {
yubiStore, err := getHardwareStore(nil, ret)
if err == nil {
importers = append(
importers,
yubikey.NewImporter(yubiStore, ret),
pkcs11.NewImporter(yubiStore, ret),
)
}
}
Expand Down
11 changes: 7 additions & 4 deletions cmd/notary/keys_pkcs11_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,15 @@ import (
"github.com/theupdateframework/notary/passphrase"
store "github.com/theupdateframework/notary/storage"
"github.com/theupdateframework/notary/trustmanager"
"github.com/theupdateframework/notary/trustmanager/yubikey"
"github.com/theupdateframework/notary/trustmanager/pkcs11/common"
"github.com/theupdateframework/notary/trustmanager/pkcs11/yubikey"
"github.com/theupdateframework/notary/tuf/data"
)

func TestImportWithYubikey(t *testing.T) {
if !yubikey.IsAccessible() {

common.SetKeyStore(yubikey.NewKeyStore())
if !common.IsAccessible() {
t.Skip("Must have Yubikey access.")
}
setUp(t)
Expand Down Expand Up @@ -74,7 +77,7 @@ func TestImportWithYubikey(t *testing.T) {
err = k.importKeys(&cobra.Command{}, []string{file})
require.NoError(t, err)

yks, err := yubikey.NewYubiStore(nil, k.getRetriever())
yks, err := common.NewHardwareStore(nil, k.getRetriever())
require.NoError(t, err)
_, _, err = yks.GetKey(bID)
require.NoError(t, err)
Expand All @@ -95,7 +98,7 @@ func TestImportWithYubikey(t *testing.T) {
}

func TestGetImporters(t *testing.T) {
if !yubikey.IsAccessible() {
if !common.IsAccessible() {
t.Skip("Must have Yubikey access.")
}
tempBaseDir, err := ioutil.TempDir("", "notary-test-")
Expand Down
96 changes: 96 additions & 0 deletions trustmanager/pkcs11/common/hardwareprivatekey.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
//+build pkcs11

package common

import (
"crypto"
"crypto/x509"
"errors"
"fmt"
"io"

"github.com/theupdateframework/notary"
"github.com/theupdateframework/notary/tuf/data"
"github.com/theupdateframework/notary/tuf/signed"
)

const (
SigAttempts = 5
)

// HardwarePrivateKey represents a private key inside of a Hardwarestore
type HardwarePrivateKey struct {
data.ECDSAPublicKey
passRetriever notary.PassRetriever
slot HardwareSlot
libLoader Pkcs11LibLoader
}

// hardwareSigner wraps a HardwarePrivateKey and implements the crypto.Signer interface
type hardwareSigner struct {
HardwarePrivateKey
}

// NewHwrdwarePrivateKey returns a HwardwarePrivateKey, which implements the data.PrivateKey
// interface except that the private material is inaccessible
func NewHardwarePrivateKey(slot HardwareSlot, pubKey data.ECDSAPublicKey, passRetriever notary.PassRetriever) *HardwarePrivateKey {
return &HardwarePrivateKey{
ECDSAPublicKey: pubKey,
passRetriever: passRetriever,
slot: slot,
libLoader: DefaultLoader,
}
}

// Public is a required method of the crypto.Signer interface
func (ys *hardwareSigner) Public() crypto.PublicKey {
publicKey, err := x509.ParsePKIXPublicKey(ys.HardwarePrivateKey.Public())
if err != nil {
return nil
}

return publicKey
}

func (hpk *HardwarePrivateKey) SetLibLoader(loader Pkcs11LibLoader) {
hpk.libLoader = loader
}

// CryptoSigner returns a crypto.Signer tha wraps the HardwarePrivateKey. Needed for
// Certificate generation only
func (hpk *HardwarePrivateKey) CryptoSigner() crypto.Signer {
return &hardwareSigner{HardwarePrivateKey: *hpk}
}

// Private is not implemented in hardware keys
func (hpk *HardwarePrivateKey) Private() []byte {
return nil
}

// SignatureAlgorithm returns which algorithm this key uses to sign - currently
// hardcoded to ECDSA
func (y HardwarePrivateKey) SignatureAlgorithm() data.SigAlgorithm {
return data.ECDSASignature
}

// Sign is a required method of the crypto.Signer interface and the data.PrivateKey
// interface
func (hpk *HardwarePrivateKey) Sign(rand io.Reader, msg []byte, opts crypto.SignerOpts) ([]byte, error) {
ctx, session, err := hardwareKeyStore.SetupHSMEnv(hpk.libLoader)
if err != nil {
return nil, err
}
defer Cleanup(ctx, session)

v := signed.Verifiers[data.ECDSASignature]
for i := 0; i < SigAttempts; i++ {
sig, err := hardwareKeyStore.Sign(ctx, session, hpk.slot, hpk.passRetriever, msg)
if err != nil {
return nil, fmt.Errorf("failed to sign using %s: %v", hardwareName, err)
}
if err := v.Verify(&hpk.ECDSAPublicKey, sig, msg); err == nil {
return sig, nil
}
}
return nil, errors.New(fmt.Sprintln("failed to generate signature on %s", hardwareName))
}
Loading

0 comments on commit 3811106

Please sign in to comment.