go_study/fabric-main/bccsp/pkcs11/pkcs11_test.go

875 lines
26 KiB
Go

//go:build pkcs11
// +build pkcs11
/*
Copyright IBM Corp. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/
package pkcs11
import (
"crypto/elliptic"
"crypto/rand"
"crypto/sha256"
"encoding/asn1"
"strconv"
"strings"
"testing"
"time"
"github.com/hyperledger/fabric/bccsp"
"github.com/hyperledger/fabric/bccsp/sw"
"github.com/hyperledger/fabric/bccsp/utils"
"github.com/miekg/pkcs11"
"github.com/stretchr/testify/require"
)
func defaultOptions() PKCS11Opts {
lib, pin, label := FindPKCS11Lib()
return PKCS11Opts{
Library: lib,
Label: label,
Pin: pin,
Hash: "SHA2",
Security: 256,
SoftwareVerify: false,
createSessionRetryDelay: time.Millisecond,
}
}
func newKeyStore(t *testing.T) bccsp.KeyStore {
tempDir := t.TempDir()
ks, err := sw.NewFileBasedKeyStore(nil, tempDir, false)
require.NoError(t, err)
return ks
}
func newSWProvider(t *testing.T) bccsp.BCCSP {
ks := newKeyStore(t)
swCsp, err := sw.NewDefaultSecurityLevelWithKeystore(ks)
require.NoError(t, err)
return swCsp
}
func newProvider(t *testing.T, opts PKCS11Opts, options ...Option) (*Provider, func()) {
ks := newKeyStore(t)
csp, err := New(opts, ks, options...)
require.NoError(t, err)
cleanup := func() {
csp.ctx.Destroy()
}
return csp, cleanup
}
func TestNew(t *testing.T) {
ks := newKeyStore(t)
t.Run("DefaultConfig", func(t *testing.T) {
opts := defaultOptions()
opts.createSessionRetryDelay = 0
csp, err := New(opts, ks)
require.NoError(t, err)
defer func() { csp.ctx.Destroy() }()
curve, err := curveForSecurityLevel(opts.Security)
require.NoError(t, err)
require.NotNil(t, csp.BCCSP)
require.Equal(t, opts.Pin, csp.pin)
require.NotNil(t, csp.ctx)
require.True(t, curve.Equal(csp.curve))
require.Equal(t, opts.SoftwareVerify, csp.softVerify)
require.Equal(t, opts.Immutable, csp.immutable)
require.Equal(t, defaultCreateSessionRetries, csp.createSessionRetries)
require.Equal(t, defaultCreateSessionRetryDelay, csp.createSessionRetryDelay)
require.Equal(t, defaultSessionCacheSize, cap(csp.sessPool))
})
t.Run("ConditionalOverride", func(t *testing.T) {
opts := defaultOptions()
opts.createSessionRetries = 3
opts.createSessionRetryDelay = time.Second
opts.sessionCacheSize = -1
csp, err := New(opts, ks)
require.NoError(t, err)
defer func() { csp.ctx.Destroy() }()
require.Equal(t, 3, csp.createSessionRetries)
require.Equal(t, time.Second, csp.createSessionRetryDelay)
require.Nil(t, csp.sessPool)
})
}
func TestInvalidNewParameter(t *testing.T) {
ks := newKeyStore(t)
t.Run("BadSecurityLevel", func(t *testing.T) {
opts := defaultOptions()
opts.Security = 0
_, err := New(opts, ks)
require.EqualError(t, err, "Failed initializing configuration: Security level not supported [0]")
})
t.Run("BadHashFamily", func(t *testing.T) {
opts := defaultOptions()
opts.Hash = "SHA8"
_, err := New(opts, ks)
require.EqualError(t, err, "Failed initializing fallback SW BCCSP: Failed initializing configuration at [256,SHA8]: Hash Family not supported [SHA8]")
})
t.Run("BadKeyStore", func(t *testing.T) {
_, err := New(defaultOptions(), nil)
require.EqualError(t, err, "Failed initializing fallback SW BCCSP: Invalid bccsp.KeyStore instance. It must be different from nil.")
})
t.Run("MissingLibrary", func(t *testing.T) {
opts := defaultOptions()
opts.Library = ""
_, err := New(opts, ks)
require.Error(t, err)
require.Contains(t, err.Error(), "pkcs11: library path not provided")
})
}
func TestFindPKCS11LibEnvVars(t *testing.T) {
const (
dummy_PKCS11_LIB = "/usr/lib/pkcs11"
dummy_PKCS11_PIN = "23456789"
dummy_PKCS11_LABEL = "testing"
)
t.Run("ExplicitEnvironment", func(t *testing.T) {
t.Setenv("PKCS11_LIB", dummy_PKCS11_LIB)
t.Setenv("PKCS11_PIN", dummy_PKCS11_PIN)
t.Setenv("PKCS11_LABEL", dummy_PKCS11_LABEL)
lib, pin, label := FindPKCS11Lib()
require.EqualValues(t, dummy_PKCS11_LIB, lib, "FindPKCS11Lib did not return expected library")
require.EqualValues(t, dummy_PKCS11_PIN, pin, "FindPKCS11Lib did not return expected pin")
require.EqualValues(t, dummy_PKCS11_LABEL, label, "FindPKCS11Lib did not return expected label")
})
t.Run("MissingEnvironment", func(t *testing.T) {
t.Setenv("PKCS11_LIB", "")
t.Setenv("PKCS11_PIN", "")
t.Setenv("PKCS11_LABEL", "")
_, pin, label := FindPKCS11Lib()
require.EqualValues(t, "98765432", pin, "FindPKCS11Lib did not return expected pin")
require.EqualValues(t, "ForFabric", label, "FindPKCS11Lib did not return expected label")
})
}
func TestInvalidSKI(t *testing.T) {
csp, cleanup := newProvider(t, defaultOptions())
defer cleanup()
_, err := csp.GetKey(nil)
require.EqualError(t, err, "Failed getting key for SKI [[]]: invalid SKI. Cannot be of zero length")
_, err = csp.GetKey([]byte{0, 1, 2, 3, 4, 5, 6})
require.Error(t, err)
require.True(t, strings.HasPrefix(err.Error(), "Failed getting key for SKI [[0 1 2 3 4 5 6]]: "))
}
func TestKeyGenECDSAOpts(t *testing.T) {
tests := map[string]struct {
curve elliptic.Curve
immutable bool
opts bccsp.KeyGenOpts
}{
"Default": {elliptic.P256(), false, &bccsp.ECDSAKeyGenOpts{Temporary: false}},
"P256": {elliptic.P256(), false, &bccsp.ECDSAP256KeyGenOpts{Temporary: false}},
"P384": {elliptic.P384(), false, &bccsp.ECDSAP384KeyGenOpts{Temporary: false}},
"Immutable": {elliptic.P384(), true, &bccsp.ECDSAP384KeyGenOpts{Temporary: false}},
"Ephemeral/Default": {elliptic.P256(), false, &bccsp.ECDSAKeyGenOpts{Temporary: true}},
"Ephemeral/P256": {elliptic.P256(), false, &bccsp.ECDSAP256KeyGenOpts{Temporary: true}},
"Ephemeral/P384": {elliptic.P384(), false, &bccsp.ECDSAP384KeyGenOpts{Temporary: true}},
"Ephemeral/Immutable": {elliptic.P384(), true, &bccsp.ECDSAP384KeyGenOpts{Temporary: false}},
}
for name, tt := range tests {
t.Run(name, func(t *testing.T) {
opts := defaultOptions()
opts.Immutable = tt.immutable
csp, cleanup := newProvider(t, opts)
defer cleanup()
k, err := csp.KeyGen(tt.opts)
require.NoError(t, err)
require.True(t, k.Private(), "key should be private")
require.False(t, k.Symmetric(), "key should be asymmetric")
ecdsaKey := k.(*ecdsaPrivateKey).pub
require.Equal(t, tt.curve, ecdsaKey.pub.Curve, "wrong curve")
raw, err := k.Bytes()
require.EqualError(t, err, "Not supported.")
require.Empty(t, raw, "result should be empty")
pk, err := k.PublicKey()
require.NoError(t, err)
require.NotNil(t, pk)
sess, err := csp.getSession()
require.NoError(t, err)
defer csp.returnSession(sess)
for _, kt := range []keyType{publicKeyType, privateKeyType} {
handle, err := csp.findKeyPairFromSKI(sess, k.SKI(), kt)
require.NoError(t, err)
attr, err := csp.ctx.GetAttributeValue(sess, handle, []*pkcs11.Attribute{{Type: pkcs11.CKA_TOKEN}})
require.NoError(t, err)
require.Len(t, attr, 1)
if tt.opts.Ephemeral() {
require.Equal(t, []byte{0}, attr[0].Value)
} else {
require.Equal(t, []byte{1}, attr[0].Value)
}
attr, err = csp.ctx.GetAttributeValue(sess, handle, []*pkcs11.Attribute{{Type: pkcs11.CKA_MODIFIABLE}})
require.NoError(t, err)
require.Len(t, attr, 1)
if tt.immutable {
require.Equal(t, []byte{0}, attr[0].Value)
} else {
require.Equal(t, []byte{1}, attr[0].Value)
}
}
})
}
}
func TestKeyGenMissingOpts(t *testing.T) {
csp, cleanup := newProvider(t, defaultOptions())
defer cleanup()
_, err := csp.KeyGen(bccsp.KeyGenOpts(nil))
require.Error(t, err)
require.Contains(t, err.Error(), "Invalid Opts parameter. It must not be nil")
}
func TestECDSAGetKeyBySKI(t *testing.T) {
csp, cleanup := newProvider(t, defaultOptions())
defer cleanup()
k, err := csp.KeyGen(&bccsp.ECDSAKeyGenOpts{Temporary: false})
require.NoError(t, err)
k2, err := csp.GetKey(k.SKI())
require.NoError(t, err)
require.True(t, k2.Private(), "key should be private")
require.False(t, k2.Symmetric(), "key should be asymmetric")
require.Equalf(t, k.SKI(), k2.SKI(), "expected %x got %x", k.SKI(), k2.SKI())
}
func TestECDSAPublicKeyFromPrivateKey(t *testing.T) {
csp, cleanup := newProvider(t, defaultOptions())
defer cleanup()
k, err := csp.KeyGen(&bccsp.ECDSAKeyGenOpts{Temporary: false})
require.NoError(t, err)
pk, err := k.PublicKey()
require.NoError(t, err)
require.False(t, pk.Private(), "key should be public")
require.False(t, pk.Symmetric(), "key should be asymmetric")
require.Equal(t, k.SKI(), pk.SKI(), "SKI should be the same")
raw, err := pk.Bytes()
require.NoError(t, err)
require.NotEmpty(t, raw, "marshaled ECDSA public key must not be empty")
}
func TestECDSASign(t *testing.T) {
csp, cleanup := newProvider(t, defaultOptions())
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)
signature, err := csp.Sign(k, digest, nil)
require.NoError(t, err)
require.NotEmpty(t, signature, "signature must not be empty")
t.Run("NoKey", func(t *testing.T) {
_, err := csp.Sign(nil, digest, nil)
require.Error(t, err)
require.Contains(t, err.Error(), "Invalid Key. It must not be nil")
})
t.Run("BadSKI", func(t *testing.T) {
_, err := csp.Sign(&ecdsaPrivateKey{ski: []byte("bad-ski")}, digest, nil)
require.Error(t, err)
require.Contains(t, err.Error(), "Private key not found")
})
t.Run("MissingDigest", func(t *testing.T) {
_, err = csp.Sign(k, nil, nil)
require.Error(t, err)
require.Contains(t, err.Error(), "Invalid digest. Cannot be empty")
})
}
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.ErrorContains(t, err, "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 TestVerify(t *testing.T) {
pkcs11CSP, cleanup := newProvider(t, defaultOptions())
defer cleanup()
digest, err := pkcs11CSP.Hash([]byte("Hello, World."), &bccsp.SHAOpts{})
require.NoError(t, err)
otherDigest, err := pkcs11CSP.Hash([]byte("Bye, World."), &bccsp.SHAOpts{})
require.NoError(t, err)
pkcs11Key, err := pkcs11CSP.KeyGen(&bccsp.ECDSAKeyGenOpts{Temporary: true})
require.NoError(t, err)
pkcs11PublicKey, err := pkcs11Key.PublicKey()
require.NoError(t, err)
b, err := pkcs11PublicKey.Bytes()
require.NoError(t, err)
swCSP := newSWProvider(t)
swKey, err := swCSP.KeyImport(b, &bccsp.ECDSAPKIXPublicKeyImportOpts{Temporary: false})
require.NoError(t, err)
signature, err := pkcs11CSP.Sign(pkcs11Key, digest, nil)
require.NoError(t, err)
swPublicKey, err := swKey.PublicKey()
require.NoError(t, err)
valid, err := pkcs11CSP.Verify(swPublicKey, signature, digest, nil)
require.NoError(t, err)
require.True(t, valid, "signature should be valid from software public key")
valid, err = pkcs11CSP.Verify(swPublicKey, signature, otherDigest, nil)
require.NoError(t, err)
require.False(t, valid, "signature should not be valid from software public key")
valid, err = pkcs11CSP.Verify(pkcs11Key, signature, digest, nil)
require.Error(t, err)
require.False(t, valid, "Verify should not handle a pkcs11 key")
}
func TestECDSAVerify(t *testing.T) {
csp, cleanup := newProvider(t, defaultOptions())
defer cleanup()
k, err := csp.KeyGen(&bccsp.ECDSAKeyGenOpts{Temporary: false})
require.NoError(t, err)
pk, err := k.PublicKey()
require.NoError(t, err)
digest, err := csp.Hash([]byte("Hello, World."), &bccsp.SHAOpts{})
require.NoError(t, err)
otherDigest, err := csp.Hash([]byte("Bye, World."), &bccsp.SHAOpts{})
require.NoError(t, err)
signature, err := csp.Sign(k, digest, nil)
require.NoError(t, err)
tests := map[string]bool{
"WithSoftVerify": true,
"WithoutSoftVerify": false,
}
pkcs11PublicKey := pk.(*ecdsaPublicKey)
for name, softVerify := range tests {
t.Run(name, func(t *testing.T) {
opts := defaultOptions()
opts.SoftwareVerify = softVerify
csp, cleanup := newProvider(t, opts)
defer cleanup()
valid, err := csp.verifyECDSA(*pkcs11PublicKey, signature, digest)
require.NoError(t, err)
require.True(t, valid, "signature should be valid from public key")
valid, err = csp.verifyECDSA(*pkcs11PublicKey, signature, otherDigest)
require.NoError(t, err)
require.False(t, valid, "signature should not be valid from public key")
})
}
t.Run("MissingKey", func(t *testing.T) {
_, err := csp.Verify(nil, signature, digest, nil)
require.Error(t, err)
require.Contains(t, err.Error(), "Invalid Key. It must not be nil")
})
t.Run("MissingSignature", func(t *testing.T) {
_, err := csp.Verify(pk, nil, digest, nil)
require.Error(t, err)
require.Contains(t, err.Error(), "Invalid signature. Cannot be empty")
})
t.Run("MissingDigest", func(t *testing.T) {
_, err = csp.Verify(pk, signature, nil, nil)
require.Error(t, err)
require.Contains(t, err.Error(), "Invalid digest. Cannot be empty")
})
}
func TestECDSALowS(t *testing.T) {
csp, cleanup := newProvider(t, defaultOptions())
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)
// Ensure that signature with low-S are generated
t.Run("GeneratesLowS", func(t *testing.T) {
signature, err := csp.Sign(k, digest, nil)
require.NoError(t, err)
_, S, err := utils.UnmarshalECDSASignature(signature)
require.NoError(t, err)
if S.Cmp(utils.GetCurveHalfOrdersAt(k.(*ecdsaPrivateKey).pub.pub.Curve)) >= 0 {
t.Fatal("Invalid signature. It must have low-S")
}
pk, err := k.PublicKey()
require.NoError(t, err)
valid, err := csp.verifyECDSA(*(pk.(*ecdsaPublicKey)), signature, digest)
require.NoError(t, err)
require.True(t, valid, "signature should be valid")
})
// Ensure that signature with high-S are rejected.
t.Run("RejectsHighS", func(t *testing.T) {
for {
R, S, err := csp.signP11ECDSA(k.SKI(), digest)
require.NoError(t, err)
if S.Cmp(utils.GetCurveHalfOrdersAt(k.(*ecdsaPrivateKey).pub.pub.Curve)) > 0 {
sig, err := utils.MarshalECDSASignature(R, S)
require.NoError(t, err)
valid, err := csp.Verify(k, sig, digest, nil)
require.Error(t, err, "verification must fail for a signature with high-S")
require.False(t, valid, "signature must not be valid with high-S")
return
}
}
})
}
func TestInitialize(t *testing.T) {
// Setup PKCS11 library and provide initial set of values
lib, pin, label := FindPKCS11Lib()
t.Run("MissingLibrary", func(t *testing.T) {
_, err := (&Provider{}).initialize(PKCS11Opts{Library: "", Pin: pin, Label: label})
require.Error(t, err)
require.Contains(t, err.Error(), "pkcs11: library path not provided")
})
t.Run("BadLibraryPath", func(t *testing.T) {
_, err := (&Provider{}).initialize(PKCS11Opts{Library: "badLib", Pin: pin, Label: label})
require.Error(t, err)
require.Contains(t, err.Error(), "pkcs11: instantiation failed for badLib")
})
t.Run("BadLabel", func(t *testing.T) {
_, err := (&Provider{}).initialize(PKCS11Opts{Library: lib, Pin: pin, Label: "badLabel"})
require.Error(t, err)
require.Contains(t, err.Error(), "could not find token with label")
})
t.Run("MissingPin", func(t *testing.T) {
_, err := (&Provider{}).initialize(PKCS11Opts{Library: lib, Pin: "", Label: label})
require.Error(t, err)
require.Contains(t, err.Error(), "Login failed: pkcs11")
})
}
func TestNamedCurveFromOID(t *testing.T) {
tests := map[string]struct {
oid asn1.ObjectIdentifier
curve elliptic.Curve
}{
"P224": {oidNamedCurveP224, elliptic.P224()},
"P256": {oidNamedCurveP256, elliptic.P256()},
"P384": {oidNamedCurveP384, elliptic.P384()},
"P521": {oidNamedCurveP521, elliptic.P521()},
"unknown": {asn1.ObjectIdentifier{4, 9, 15, 1}, nil},
}
for name, tt := range tests {
t.Run(name, func(t *testing.T) {
require.Equal(t, tt.curve, namedCurveFromOID(tt.oid))
})
}
}
func TestCurveForSecurityLevel(t *testing.T) {
tests := map[int]struct {
expectedErr string
curve asn1.ObjectIdentifier
}{
256: {curve: oidNamedCurveP256},
384: {curve: oidNamedCurveP384},
512: {expectedErr: "Security level not supported [512]"},
}
for level, tt := range tests {
t.Run(strconv.Itoa(level), func(t *testing.T) {
curve, err := curveForSecurityLevel(level)
if tt.expectedErr != "" {
require.EqualError(t, err, tt.expectedErr)
return
}
require.NoError(t, err)
require.Equal(t, tt.curve, curve)
})
}
}
func TestPKCS11GetSession(t *testing.T) {
opts := defaultOptions()
opts.sessionCacheSize = 5
csp, cleanup := newProvider(t, opts)
defer cleanup()
sessionCacheSize := opts.sessionCacheSize
var sessions []pkcs11.SessionHandle
for i := 0; i < 3*sessionCacheSize; i++ {
session, err := csp.getSession()
require.NoError(t, err)
sessions = append(sessions, session)
}
// Return all sessions, should leave sessionCacheSize cached
for _, session := range sessions {
csp.returnSession(session)
}
// Should be able to get sessionCacheSize cached sessions
sessions = nil
for i := 0; i < sessionCacheSize; i++ {
session, err := csp.getSession()
require.NoError(t, err)
sessions = append(sessions, session)
}
// Cleanup
for _, session := range sessions {
csp.returnSession(session)
}
}
func TestSessionHandleCaching(t *testing.T) {
verifyHandleCache := func(t *testing.T, csp *Provider, sess pkcs11.SessionHandle, k bccsp.Key) {
pubHandle, err := csp.findKeyPairFromSKI(sess, k.SKI(), publicKeyType)
require.NoError(t, err)
h, ok := csp.cachedHandle(publicKeyType, k.SKI())
require.True(t, ok)
require.Equal(t, h, pubHandle)
privHandle, err := csp.findKeyPairFromSKI(sess, k.SKI(), privateKeyType)
require.NoError(t, err)
h, ok = csp.cachedHandle(privateKeyType, k.SKI())
require.True(t, ok)
require.Equal(t, h, privHandle)
}
t.Run("SessionCacheDisabled", func(t *testing.T) {
opts := defaultOptions()
opts.sessionCacheSize = -1
csp, cleanup := newProvider(t, opts)
defer cleanup()
require.Nil(t, csp.sessPool, "sessPool channel should be nil")
require.Empty(t, csp.sessions, "sessions set should be empty")
require.Empty(t, csp.handleCache, "handleCache should be empty")
sess1, err := csp.getSession()
require.NoError(t, err)
require.Len(t, csp.sessions, 1, "expected one open session")
sess2, err := csp.getSession()
require.NoError(t, err)
require.Len(t, csp.sessions, 2, "expected two open sessions")
// Generate a key
k, err := csp.KeyGen(&bccsp.ECDSAP256KeyGenOpts{Temporary: false})
require.NoError(t, err)
verifyHandleCache(t, csp, sess1, k)
require.Len(t, csp.handleCache, 2, "expected two handles in handle cache")
csp.returnSession(sess1)
require.Len(t, csp.sessions, 1, "expected one open session")
verifyHandleCache(t, csp, sess1, k)
require.Len(t, csp.handleCache, 2, "expected two handles in handle cache")
csp.returnSession(sess2)
require.Empty(t, csp.sessions, "expected sessions to be empty")
require.Empty(t, csp.handleCache, "expected handles to be cleared")
})
t.Run("SessionCacheEnabled", func(t *testing.T) {
opts := defaultOptions()
opts.sessionCacheSize = 1
csp, cleanup := newProvider(t, opts)
defer cleanup()
require.NotNil(t, csp.sessPool, "sessPool channel should not be nil")
require.Equal(t, 1, cap(csp.sessPool))
require.Len(t, csp.sessions, 1, "sessions should contain login session")
require.Len(t, csp.sessPool, 1, "sessionPool should hold login session")
require.Empty(t, csp.handleCache, "handleCache should be empty")
sess1, err := csp.getSession()
require.NoError(t, err)
require.Len(t, csp.sessions, 1, "expected one open session (sess1 from login)")
require.Len(t, csp.sessPool, 0, "sessionPool should be empty")
sess2, err := csp.getSession()
require.NoError(t, err)
require.Len(t, csp.sessions, 2, "expected two open sessions (sess1 and sess2)")
require.Len(t, csp.sessPool, 0, "sessionPool should be empty")
// Generate a key
k, err := csp.KeyGen(&bccsp.ECDSAP256KeyGenOpts{Temporary: false})
require.NoError(t, err)
verifyHandleCache(t, csp, sess1, k)
require.Len(t, csp.handleCache, 2, "expected two handles in handle cache")
csp.returnSession(sess1)
require.Len(t, csp.sessions, 2, "expected two open sessions (sess2 in-use, sess1 cached)")
require.Len(t, csp.sessPool, 1, "sessionPool should have one handle (sess1)")
verifyHandleCache(t, csp, sess1, k)
require.Len(t, csp.handleCache, 2, "expected two handles in handle cache")
csp.returnSession(sess2)
require.Len(t, csp.sessions, 1, "expected one cached session (sess1)")
require.Len(t, csp.sessPool, 1, "sessionPool should have one handle (sess1)")
require.Len(t, csp.handleCache, 2, "expected two handles in handle cache")
_, err = csp.getSession()
require.NoError(t, err)
require.Len(t, csp.sessions, 1, "expected one open session (sess1)")
require.Len(t, csp.sessPool, 0, "sessionPool should be empty")
require.Len(t, csp.handleCache, 2, "expected two handles in handle cache")
})
}
func TestKeyCache(t *testing.T) {
opts := defaultOptions()
opts.sessionCacheSize = 1
csp, cleanup := newProvider(t, opts)
defer cleanup()
require.Empty(t, csp.keyCache)
_, err := csp.GetKey([]byte("nonsense-key"))
require.Error(t, err) // message comes from software keystore
require.Empty(t, csp.keyCache)
k, err := csp.KeyGen(&bccsp.ECDSAP256KeyGenOpts{Temporary: false})
require.NoError(t, err)
_, ok := csp.cachedKey(k.SKI())
require.False(t, ok, "created keys are not (currently) cached")
key, err := csp.GetKey(k.SKI())
require.NoError(t, err)
cached, ok := csp.cachedKey(k.SKI())
require.True(t, ok, "key should be cached")
require.Same(t, key, cached, "key from cache should be what was found")
// Retrieve last session
sess, err := csp.getSession()
require.NoError(t, err)
require.Empty(t, csp.sessPool, "sessionPool should be empty")
// Force caches to be cleared by closing last session
csp.closeSession(sess)
require.Empty(t, csp.sessions, "sessions should be empty")
require.Empty(t, csp.keyCache, "key cache should be empty")
_, ok = csp.cachedKey(k.SKI())
require.False(t, ok, "key should not be in cache")
}
// This helps verify that we're delegating to the software provider.
// This is not intended to test the software provider implementation.
func TestDelegation(t *testing.T) {
csp, cleanup := newProvider(t, defaultOptions())
defer cleanup()
k, err := csp.KeyGen(&bccsp.AES256KeyGenOpts{})
require.NoError(t, err)
t.Run("KeyGen", func(t *testing.T) {
k, err := csp.KeyGen(&bccsp.AES256KeyGenOpts{})
require.NoError(t, err)
require.True(t, k.Private())
require.True(t, k.Symmetric())
})
t.Run("KeyDeriv", func(t *testing.T) {
k, err := csp.KeyDeriv(k, &bccsp.HMACDeriveKeyOpts{Arg: []byte{1}})
require.NoError(t, err)
require.True(t, k.Private())
})
t.Run("KeyImport", func(t *testing.T) {
raw := make([]byte, 32)
_, err := rand.Read(raw)
require.NoError(t, err)
k, err := csp.KeyImport(raw, &bccsp.AES256ImportKeyOpts{})
require.NoError(t, err)
require.True(t, k.Private())
})
t.Run("GetKey", func(t *testing.T) {
k, err := csp.GetKey(k.SKI())
require.NoError(t, err)
require.True(t, k.Private())
})
t.Run("Hash", func(t *testing.T) {
digest, err := csp.Hash([]byte("message"), &bccsp.SHA3_384Opts{})
require.NoError(t, err)
require.NotEmpty(t, digest)
})
t.Run("GetHash", func(t *testing.T) {
h, err := csp.GetHash(&bccsp.SHA256Opts{})
require.NoError(t, err)
require.Equal(t, sha256.New(), h)
})
t.Run("Sign", func(t *testing.T) {
_, err := csp.Sign(k, []byte("message"), nil)
require.EqualError(t, err, "Unsupported 'SignKey' provided [*sw.aesPrivateKey]")
})
t.Run("Verify", func(t *testing.T) {
_, err := csp.Verify(k, []byte("signature"), []byte("digest"), nil)
require.Error(t, err)
require.Contains(t, err.Error(), "Unsupported 'VerifyKey' provided")
})
t.Run("EncryptDecrypt", func(t *testing.T) {
msg := []byte("message")
ct, err := csp.Encrypt(k, msg, &bccsp.AESCBCPKCS7ModeOpts{})
require.NoError(t, err)
pt, err := csp.Decrypt(k, ct, &bccsp.AESCBCPKCS7ModeOpts{})
require.NoError(t, err)
require.Equal(t, msg, pt)
})
}
func TestHandleSessionReturn(t *testing.T) {
opts := defaultOptions()
opts.sessionCacheSize = 5
csp, cleanup := newProvider(t, opts)
defer cleanup()
// Retrieve and destroy default session created during initialization
session, err := csp.getSession()
require.NoError(t, err)
csp.closeSession(session)
// Verify session pool is empty and place invalid session in pool
require.Empty(t, csp.sessPool, "sessionPool should be empty")
csp.returnSession(pkcs11.SessionHandle(^uint(0)))
// Attempt to generate key with invalid session
_, err = csp.KeyGen(&bccsp.ECDSAP256KeyGenOpts{Temporary: false})
require.EqualError(t, err, "Failed generating ECDSA P256 key: P11: keypair generate failed [pkcs11: 0xB3: CKR_SESSION_HANDLE_INVALID]")
require.Empty(t, csp.sessPool, "sessionPool should be empty")
}