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 <sykesmat@us.ibm.com>
  • Loading branch information
sykesm authored and mastersingh24 committed Mar 19, 2021
1 parent 7214be7 commit d2d031e
Show file tree
Hide file tree
Showing 5 changed files with 219 additions and 19 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.Pkcs11Opts
p11Opts := *config.Pkcs11Opts
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
}
}
42 changes: 42 additions & 0 deletions bccsp/factory/pkcs11factory_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,13 @@ SPDX-License-Identifier: Apache-2.0
package factory

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

"github.com/hyperledger/fabric/bccsp/pkcs11"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestPKCS11FactoryName(t *testing.T) {
Expand Down Expand Up @@ -111,3 +114,42 @@ func TestPKCS11FactoryGetEmptyKeyStorePath(t *testing.T) {
assert.NoError(t, err)
assert.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{
SecLevel: 256,
HashFamily: "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 @@ -76,9 +76,18 @@ type PKCS11Opts struct {
Ephemeral bool `mapstructure:"tempkeys,omitempty" json:"tempkeys,omitempty"`

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

// 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"`
}
51 changes: 39 additions & 12 deletions bccsp/pkcs11/pkcs11.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ type impl struct {
softVerify bool
immutable bool

getKeyIDForSKI func(ski []byte) []byte

sessLock sync.Mutex
sessPool chan pkcs11.SessionHandle
sessions map[pkcs11.SessionHandle]struct{}
Expand All @@ -55,9 +57,27 @@ type impl struct {
keyCache map[string]bccsp.Key
}

// New WithParams returns a new instance of the software-based BCCSP
// set at the passed security level, hash family and KeyStore.
func New(opts PKCS11Opts, keyStore bccsp.KeyStore) (bccsp.BCCSP, error) {
// An Option is used to configure the Provider.
type Option func(p *impl) 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(i *impl) error {
i.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, options ...Option) (bccsp.BCCSP, error) {
// Init config
conf := &config{}
err := conf.setSecurityLevel(opts.SecLevel, opts.HashFamily)
Expand All @@ -76,14 +96,21 @@ func New(opts PKCS11Opts, keyStore bccsp.KeyStore) (bccsp.BCCSP, error) {
}

csp := &impl{
BCCSP: swCSP,
conf: conf,
sessPool: sessPool,
sessions: map[pkcs11.SessionHandle]struct{}{},
handleCache: map[string]pkcs11.ObjectHandle{},
softVerify: opts.SoftVerify,
keyCache: map[string]bccsp.Key{},
immutable: opts.Immutable,
BCCSP: swCSP,
conf: conf,
getKeyIDForSKI: func(ski []byte) []byte { return ski },
sessPool: sessPool,
sessions: map[pkcs11.SessionHandle]struct{}{},
handleCache: map[string]pkcs11.ObjectHandle{},
softVerify: opts.SoftVerify,
keyCache: map[string]bccsp.Key{},
immutable: opts.Immutable,
}

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

return csp.initialize(opts)
Expand Down Expand Up @@ -685,7 +712,7 @@ func (csp *impl) findKeyPairFromSKI(session pkcs11.SessionHandle, ski []byte, ke

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
101 changes: 101 additions & 0 deletions bccsp/pkcs11/pkcs11_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -565,6 +565,107 @@ func TestECDSASign(t *testing.T) {
require.Contains(t, err.Error(), "Invalid digest. Cannot be empty")
}

func defaultOptions() PKCS11Opts {
lib, pin, label := FindPKCS11Lib()
return PKCS11Opts{
Library: lib,
Label: label,
Pin: pin,
HashFamily: "SHA2",
SecLevel: 256,
SoftVerify: false,
}
}

func newKeyStore(t *testing.T) (bccsp.KeyStore, func()) {
tempDir, err := ioutil.TempDir("", "pkcs11_ks")
require.NoError(t, err)
ks, err := sw.NewFileBasedKeyStore(nil, tempDir, false)
require.NoError(t, err)

return ks, func() { os.RemoveAll(tempDir) }
}

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

cleanup := func() {
csp.(*impl).ctx.Destroy()
ksCleanup()
}
return csp.(*impl), cleanup
}

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) {
k, err := currentBCCSP.KeyGen(&bccsp.ECDSAKeyGenOpts{Temporary: false})
if err != nil {
Expand Down

0 comments on commit d2d031e

Please sign in to comment.