Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

extracting pkcs11 implementation from notary and replacing it with RPC interface #1465

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,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 @@ -67,7 +68,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
12 changes: 8 additions & 4 deletions client/client_pkcs11_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,19 @@

package client

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

// clear out all keys
func init() {
yubikey.SetYubikeyKeyMode(0)
if !yubikey.IsAccessible() {
ks := externalstore.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
}
22 changes: 7 additions & 15 deletions cmd/notary/integration_pkcs11_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,25 +9,17 @@ 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/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" {
return regRetriver(k, a, c, n)
}
return testPassphrase, false, nil
}
_retriever := passphrase.PromptRetriever()

// 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 +34,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 +52,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
14 changes: 7 additions & 7 deletions cmd/notary/keys_pkcs11.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,22 @@ 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 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
21 changes: 11 additions & 10 deletions cmd/notary/keys_pkcs11_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,14 @@ 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/tuf/data"
)

func TestImportWithYubikey(t *testing.T) {
if !yubikey.IsAccessible() {
t.Skip("Must have Yubikey access.")
func TestImportWithHardwareStore(t *testing.T) {

if !common.IsAccessible() {
t.Skip("Must have Hardwarestore access.")
}
setUp(t)
tempBaseDir, err := ioutil.TempDir("", "notary-test-")
Expand All @@ -47,7 +48,7 @@ func TestImportWithYubikey(t *testing.T) {

pubK, err := cs.Create(data.CanonicalRootRole, "ankh", data.ECDSAKey)
require.NoError(t, err)
bID := pubK.ID() // need to check presence in yubikey later
bID := pubK.ID() // need to check presence in hardwarestore later
bytes, err := memStore.Get(pubK.ID())
require.NoError(t, err)
b, _ := pem.Decode(bytes)
Expand All @@ -74,17 +75,17 @@ 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)
_, _, err = yks.GetKey(cID)
require.Error(t, err) // c is non-root, should not be in yubikey
require.Error(t, err) // c is non-root, should not be in hardwarestore

fileStore, err := store.NewPrivateKeyFileStorage(tempBaseDir, notary.KeyExtension)
require.NoError(t, err)
_, err = fileStore.Get("ankh")
require.Error(t, err) // b should only be in yubikey, not in filestore
require.Error(t, err) // b should only be in hardwarestore, not in filestore

cResult, err := fileStore.Get("morpork")
require.NoError(t, err)
Expand All @@ -95,8 +96,8 @@ func TestImportWithYubikey(t *testing.T) {
}

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

import "fmt"

// ErrNoKeysFound is returned when there are no keys in the HardwareStore
type ErrNoKeysFound struct {
HSM string
}

func (e ErrNoKeysFound) Error() string {
return fmt.Sprintf("no keys found in %s", e.HSM)
}
90 changes: 90 additions & 0 deletions trustmanager/pkcs11/common/hardwareprivatekey.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
//+build pkcs11

package common

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

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

const (
// SigAttempts defines maximum attempts to sign an artifact before aborting
SigAttempts = 5
)

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

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

// NewHardwarePrivateKey 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,
}
}

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

return publicKey
}

// CryptoSigner returns a crypto.Signer that 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 (hpk 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) {
session, err := hardwareKeyStore.SetupHSMEnv()
if err != nil {
return nil, err
}
defer hardwareKeyStore.Cleanup(session)

v := signed.Verifiers[data.ECDSASignature]
for i := 0; i < SigAttempts; i++ {
sig, err := hardwareKeyStore.Sign(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, fmt.Errorf("failed to generate signature on %s", hardwareName)
}
Loading