Skip to content

Commit

Permalink
pkcs11: Add SKI to CKA_ID mapping for BCCSP
Browse files Browse the repository at this point in the history
This adds support for finding PKCS#11 public and private key objects
that do not have a CKA_ID that matches the SKI of the public key.

The PKCS#11 provider can be configured with a map of hex-encoded subject
key identifiers (hashes) to CKA_ID values (strings) of the associated
objects. When the 'KeyIDs' configuration is present, this mapping is
used to locate key objects.

'AltID' is provided to match the behavior of Fabric 1.4 where key
identifiers are ignored and all SKIs are mapped to a single PKCS#11
CKA_ID. This should be considered deprecated but generally works as most
Fabric components only use a single signing identity.

If both 'AltID' and 'KeyIDs' elements are present, the 'AltID' value is
used when a mapping is not found in 'KeyIDs'.

Signed-off-by: Matthew Sykes <[email protected]>
  • Loading branch information
sykesm authored and mastersingh24 committed Mar 19, 2021
1 parent 0fb8e5a commit 871d325
Show file tree
Hide file tree
Showing 5 changed files with 172 additions and 45 deletions.
25 changes: 23 additions & 2 deletions bccsp/factory/pkcs11factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ SPDX-License-Identifier: Apache-2.0
package factory

import (
"encoding/hex"

"github.com/hyperledger/fabric/bccsp"
"github.com/hyperledger/fabric/bccsp/pkcs11"
"github.com/hyperledger/fabric/bccsp/sw"
Expand All @@ -35,8 +37,27 @@ func (f *PKCS11Factory) Get(config *FactoryOpts) (bccsp.BCCSP, error) {
return nil, errors.New("Invalid config. It must not be nil.")
}

p11Opts := config.PKCS11
p11Opts := *config.PKCS11
ks := sw.NewDummyKeyStore()
mapper := skiMapper(p11Opts)

return pkcs11.New(p11Opts, ks, pkcs11.WithKeyMapper(mapper))
}

return pkcs11.New(*p11Opts, ks)
func skiMapper(p11Opts pkcs11.PKCS11Opts) func([]byte) []byte {
keyMap := map[string]string{}
for _, k := range p11Opts.KeyIDs {
keyMap[k.SKI] = k.ID
}

return func(ski []byte) []byte {
keyID := hex.EncodeToString(ski)
if id, ok := keyMap[keyID]; ok {
return []byte(id)
}
if p11Opts.AltID != "" {
return []byte(p11Opts.AltID)
}
return ski
}
}
76 changes: 42 additions & 34 deletions bccsp/factory/pkcs11factory_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ SPDX-License-Identifier: Apache-2.0
package factory

import (
"crypto/sha256"
"encoding/hex"
"testing"

"github.com/hyperledger/fabric/bccsp/pkcs11"
Expand Down Expand Up @@ -38,46 +40,13 @@ func TestPKCS11FactoryGetInvalidArgs(t *testing.T) {

func TestPKCS11FactoryGet(t *testing.T) {
f := &PKCS11Factory{}
lib, pin, label := pkcs11.FindPKCS11Lib()

opts := &FactoryOpts{
PKCS11: &pkcs11.PKCS11Opts{
Security: 256,
Hash: "SHA2",
Library: lib,
Pin: pin,
Label: label,
},
PKCS11: defaultOptions(),
}
csp, err := f.Get(opts)
require.NoError(t, err)
require.NotNil(t, csp)

opts = &FactoryOpts{
PKCS11: &pkcs11.PKCS11Opts{
Security: 256,
Hash: "SHA2",
Library: lib,
Pin: pin,
Label: label,
},
}
csp, err = f.Get(opts)
require.NoError(t, err)
require.NotNil(t, csp)

opts = &FactoryOpts{
PKCS11: &pkcs11.PKCS11Opts{
Security: 256,
Hash: "SHA2",
Library: lib,
Pin: pin,
Label: label,
},
}
csp, err = f.Get(opts)
require.NoError(t, err)
require.NotNil(t, csp)
}

func TestPKCS11FactoryGetEmptyKeyStorePath(t *testing.T) {
Expand Down Expand Up @@ -110,3 +79,42 @@ func TestPKCS11FactoryGetEmptyKeyStorePath(t *testing.T) {
require.NoError(t, err)
require.NotNil(t, csp)
}

func TestSKIMapper(t *testing.T) {
inputSKI := sha256.New().Sum([]byte("some-ski"))
tests := []struct {
name string
altID string
keyIDs map[string]string
expected []byte
}{
{name: "DefaultBehavior", expected: inputSKI},
{name: "AltIDOnly", altID: "alternate-ID", expected: []byte("alternate-ID")},
{name: "MapEntry", keyIDs: map[string]string{hex.EncodeToString(inputSKI): "mapped-id"}, expected: []byte("mapped-id")},
{name: "AltIDAsDefault", altID: "alternate-ID", keyIDs: map[string]string{"another-ski": "another-id"}, expected: []byte("alternate-ID")},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
options := defaultOptions()
options.AltID = tt.altID
for k, v := range tt.keyIDs {
options.KeyIDs = append(options.KeyIDs, pkcs11.KeyIDMapping{SKI: k, ID: v})
}

mapper := skiMapper(*options)
result := mapper(inputSKI)
require.Equal(t, tt.expected, result, "got %x, want %x", result, tt.expected)
})
}
}

func defaultOptions() *pkcs11.PKCS11Opts {
lib, pin, label := pkcs11.FindPKCS11Lib()
return &pkcs11.PKCS11Opts{
Security: 256,
Hash: "SHA2",
Library: lib,
Pin: pin,
Label: label,
}
}
19 changes: 14 additions & 5 deletions bccsp/pkcs11/conf.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,22 @@ type PKCS11Opts struct {
Hash string `json:"hash"`

// PKCS11 options
Library string `json:"library"`
Label string `json:"label"`
Pin string `json:"pin"`
SoftwareVerify bool `json:"softwareverify,omitempty"`
Immutable bool `json:"immutable,omitempty"`
Library string `json:"library"`
Label string `json:"label"`
Pin string `json:"pin"`
SoftwareVerify bool `json:"softwareverify,omitempty"`
Immutable bool `json:"immutable,omitempty"`
AltID string `json:"altid,omitempty"`
KeyIDs []KeyIDMapping `json:"keyids,omitempty" mapstructure:"keyids"`

sessionCacheSize int
createSessionRetries int
createSessionRetryDelay time.Duration
}

// A KeyIDMapping associates the CKA_ID attribute of a cryptoki object with a
// subject key identifer.
type KeyIDMapping struct {
SKI string `json:"ski,omitempty"`
ID string `json:"id,omitempty"`
}
25 changes: 23 additions & 2 deletions bccsp/pkcs11/pkcs11.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ type Provider struct {
softVerify bool
immutable bool

getKeyIDForSKI func(ski []byte) []byte
createSessionRetries int
createSessionRetryDelay time.Duration

Expand All @@ -56,14 +57,27 @@ type Provider struct {
// Ensure we satisfy the BCCSP interfaces.
var _ bccsp.BCCSP = (*Provider)(nil)

// An Option is used to configure the Provider.
type Option func(p *Provider) error

// WithKeyMapper returns an option that configures the Provider to use the
// provided function to map a subject key identifier to a cryptoki CKA_ID
// identifer.
func WithKeyMapper(mapper func([]byte) []byte) Option {
return func(p *Provider) error {
p.getKeyIDForSKI = mapper
return nil
}
}

// New returns a new instance of a BCCSP that uses PKCS#11 standard interfaces
// to generate and use elliptic curve key pairs for signing and verification using
// curves that satisfy the requested security level from opts.
//
// All other cryptographic functions are delegated to a software based BCCSP
// implementation that is configured to use the security level and hashing
// familly from opts and the key store that is provided.
func New(opts PKCS11Opts, keyStore bccsp.KeyStore) (*Provider, error) {
func New(opts PKCS11Opts, keyStore bccsp.KeyStore, options ...Option) (*Provider, error) {
curve, err := curveForSecurityLevel(opts.Security)
if err != nil {
return nil, errors.Wrapf(err, "Failed initializing configuration")
Expand Down Expand Up @@ -92,6 +106,7 @@ func New(opts PKCS11Opts, keyStore bccsp.KeyStore) (*Provider, error) {
csp := &Provider{
BCCSP: swCSP,
curve: curve,
getKeyIDForSKI: func(ski []byte) []byte { return ski },
createSessionRetries: opts.createSessionRetries,
createSessionRetryDelay: opts.createSessionRetryDelay,
sessPool: sessPool,
Expand All @@ -102,6 +117,12 @@ func New(opts PKCS11Opts, keyStore bccsp.KeyStore) (*Provider, error) {
immutable: opts.Immutable,
}

for _, o := range options {
if err := o(csp); err != nil {
return nil, err
}
}

return csp.initialize(opts)
}

Expand Down Expand Up @@ -697,7 +718,7 @@ func (csp *Provider) findKeyPairFromSKI(session pkcs11.SessionHandle, ski []byte

template := []*pkcs11.Attribute{
pkcs11.NewAttribute(pkcs11.CKA_CLASS, ktype),
pkcs11.NewAttribute(pkcs11.CKA_ID, ski),
pkcs11.NewAttribute(pkcs11.CKA_ID, csp.getKeyIDForSKI(ski)),
}
if err := csp.ctx.FindObjectsInit(session, template); err != nil {
return 0, err
Expand Down
72 changes: 70 additions & 2 deletions bccsp/pkcs11/pkcs11_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,9 @@ func newKeyStore(t *testing.T) (bccsp.KeyStore, func()) {
return ks, func() { os.RemoveAll(tempDir) }
}

func newProvider(t *testing.T, opts PKCS11Opts) (*Provider, func()) {
func newProvider(t *testing.T, opts PKCS11Opts, options ...Option) (*Provider, func()) {
ks, ksCleanup := newKeyStore(t)
csp, err := New(opts, ks)
csp, err := New(opts, ks, options...)
require.NoError(t, err)

cleanup := func() {
Expand Down Expand Up @@ -334,6 +334,74 @@ func TestECDSASign(t *testing.T) {
})
}

type mapper struct {
input []byte
result []byte
}

func (m *mapper) skiToID(ski []byte) []byte {
m.input = ski
return m.result
}

func TestKeyMapper(t *testing.T) {
mapper := &mapper{}
csp, cleanup := newProvider(t, defaultOptions(), WithKeyMapper(mapper.skiToID))
defer cleanup()

k, err := csp.KeyGen(&bccsp.ECDSAKeyGenOpts{Temporary: false})
require.NoError(t, err)

digest, err := csp.Hash([]byte("Hello World"), &bccsp.SHAOpts{})
require.NoError(t, err)

sess, err := csp.getSession()
require.NoError(t, err, "failed to get session")
defer csp.returnSession(sess)

newID := []byte("mapped-id")
updateKeyIdentifier(t, csp.ctx, sess, pkcs11.CKO_PUBLIC_KEY, k.SKI(), newID)
updateKeyIdentifier(t, csp.ctx, sess, pkcs11.CKO_PRIVATE_KEY, k.SKI(), newID)

t.Run("ToMissingID", func(t *testing.T) {
csp.clearCaches()
mapper.result = k.SKI()
_, err := csp.Sign(k, digest, nil)
require.Error(t, err)
require.Contains(t, err.Error(), "Private key not found")
require.Equal(t, k.SKI(), mapper.input, "expected mapper to receive ski %x, got %x", k.SKI(), mapper.input)
})
t.Run("ToNewID", func(t *testing.T) {
csp.clearCaches()
mapper.result = newID
signature, err := csp.Sign(k, digest, nil)
require.NoError(t, err)
require.NotEmpty(t, signature, "signature must not be empty")
require.Equal(t, k.SKI(), mapper.input, "expected mapper to receive ski %x, got %x", k.SKI(), mapper.input)
})
}

func updateKeyIdentifier(t *testing.T, pctx *pkcs11.Ctx, sess pkcs11.SessionHandle, class uint, currentID, newID []byte) {
pkt := []*pkcs11.Attribute{
pkcs11.NewAttribute(pkcs11.CKA_CLASS, class),
pkcs11.NewAttribute(pkcs11.CKA_ID, currentID),
}
err := pctx.FindObjectsInit(sess, pkt)
require.NoError(t, err)

objs, _, err := pctx.FindObjects(sess, 1)
require.NoError(t, err)
require.Len(t, objs, 1)

err = pctx.FindObjectsFinal(sess)
require.NoError(t, err)

err = pctx.SetAttributeValue(sess, objs[0], []*pkcs11.Attribute{
pkcs11.NewAttribute(pkcs11.CKA_ID, newID),
})
require.NoError(t, err)
}

func TestECDSAVerify(t *testing.T) {
csp, cleanup := newProvider(t, defaultOptions())
defer cleanup()
Expand Down

0 comments on commit 871d325

Please sign in to comment.