383 lines
13 KiB
Go
383 lines
13 KiB
Go
package pkcs11helpers
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto"
|
|
"crypto/ecdsa"
|
|
"crypto/elliptic"
|
|
"crypto/rsa"
|
|
"encoding/asn1"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"math/big"
|
|
|
|
"github.com/miekg/pkcs11"
|
|
)
|
|
|
|
type PKCtx interface {
|
|
GenerateKeyPair(pkcs11.SessionHandle, []*pkcs11.Mechanism, []*pkcs11.Attribute, []*pkcs11.Attribute) (pkcs11.ObjectHandle, pkcs11.ObjectHandle, error)
|
|
GetAttributeValue(pkcs11.SessionHandle, pkcs11.ObjectHandle, []*pkcs11.Attribute) ([]*pkcs11.Attribute, error)
|
|
SignInit(pkcs11.SessionHandle, []*pkcs11.Mechanism, pkcs11.ObjectHandle) error
|
|
Sign(pkcs11.SessionHandle, []byte) ([]byte, error)
|
|
GenerateRandom(pkcs11.SessionHandle, int) ([]byte, error)
|
|
FindObjectsInit(sh pkcs11.SessionHandle, temp []*pkcs11.Attribute) error
|
|
FindObjects(sh pkcs11.SessionHandle, max int) ([]pkcs11.ObjectHandle, bool, error)
|
|
FindObjectsFinal(sh pkcs11.SessionHandle) error
|
|
}
|
|
|
|
// Session represents a session with a given PKCS#11 module. It is not safe for
|
|
// concurrent access.
|
|
type Session struct {
|
|
Module PKCtx
|
|
Session pkcs11.SessionHandle
|
|
}
|
|
|
|
func Initialize(module string, slot uint, pin string) (*Session, error) {
|
|
ctx := pkcs11.New(module)
|
|
if ctx == nil {
|
|
return nil, errors.New("failed to load module")
|
|
}
|
|
err := ctx.Initialize()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("couldn't initialize context: %s", err)
|
|
}
|
|
|
|
session, err := ctx.OpenSession(slot, pkcs11.CKF_SERIAL_SESSION|pkcs11.CKF_RW_SESSION)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("couldn't open session: %s", err)
|
|
}
|
|
|
|
err = ctx.Login(session, pkcs11.CKU_USER, pin)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("couldn't login: %s", err)
|
|
}
|
|
|
|
return &Session{ctx, session}, nil
|
|
}
|
|
|
|
func (s *Session) GetAttributeValue(object pkcs11.ObjectHandle, attributes []*pkcs11.Attribute) ([]*pkcs11.Attribute, error) {
|
|
return s.Module.GetAttributeValue(s.Session, object, attributes)
|
|
}
|
|
|
|
func (s *Session) GenerateKeyPair(m []*pkcs11.Mechanism, pubAttrs []*pkcs11.Attribute, privAttrs []*pkcs11.Attribute) (pkcs11.ObjectHandle, pkcs11.ObjectHandle, error) {
|
|
return s.Module.GenerateKeyPair(s.Session, m, pubAttrs, privAttrs)
|
|
}
|
|
|
|
func (s *Session) GetRSAPublicKey(object pkcs11.ObjectHandle) (*rsa.PublicKey, error) {
|
|
// Retrieve the public exponent and modulus for the public key
|
|
attrs, err := s.Module.GetAttributeValue(s.Session, object, []*pkcs11.Attribute{
|
|
pkcs11.NewAttribute(pkcs11.CKA_PUBLIC_EXPONENT, nil),
|
|
pkcs11.NewAttribute(pkcs11.CKA_MODULUS, nil),
|
|
})
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Failed to retrieve key attributes: %s", err)
|
|
}
|
|
|
|
// Attempt to build the public key from the retrieved attributes
|
|
pubKey := &rsa.PublicKey{}
|
|
gotMod, gotExp := false, false
|
|
for _, a := range attrs {
|
|
switch a.Type {
|
|
case pkcs11.CKA_PUBLIC_EXPONENT:
|
|
pubKey.E = int(big.NewInt(0).SetBytes(a.Value).Int64())
|
|
gotExp = true
|
|
case pkcs11.CKA_MODULUS:
|
|
pubKey.N = big.NewInt(0).SetBytes(a.Value)
|
|
gotMod = true
|
|
}
|
|
}
|
|
// Fail if we are missing either the public exponent or modulus
|
|
if !gotExp || !gotMod {
|
|
return nil, errors.New("Couldn't retrieve modulus and exponent")
|
|
}
|
|
return pubKey, nil
|
|
}
|
|
|
|
// oidDERToCurve maps the hex of the DER encoding of the various curve OIDs to
|
|
// the relevant curve parameters
|
|
var oidDERToCurve = map[string]elliptic.Curve{
|
|
"06052B81040021": elliptic.P224(),
|
|
"06082A8648CE3D030107": elliptic.P256(),
|
|
"06052B81040022": elliptic.P384(),
|
|
"06052B81040023": elliptic.P521(),
|
|
}
|
|
|
|
func (s *Session) GetECDSAPublicKey(object pkcs11.ObjectHandle) (*ecdsa.PublicKey, error) {
|
|
// Retrieve the curve and public point for the generated public key
|
|
attrs, err := s.Module.GetAttributeValue(s.Session, object, []*pkcs11.Attribute{
|
|
pkcs11.NewAttribute(pkcs11.CKA_EC_PARAMS, nil),
|
|
pkcs11.NewAttribute(pkcs11.CKA_EC_POINT, nil),
|
|
})
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Failed to retrieve key attributes: %s", err)
|
|
}
|
|
|
|
pubKey := &ecdsa.PublicKey{}
|
|
var pointBytes []byte
|
|
for _, a := range attrs {
|
|
switch a.Type {
|
|
case pkcs11.CKA_EC_PARAMS:
|
|
rCurve, present := oidDERToCurve[fmt.Sprintf("%X", a.Value)]
|
|
if !present {
|
|
return nil, errors.New("Unknown curve OID value returned")
|
|
}
|
|
pubKey.Curve = rCurve
|
|
case pkcs11.CKA_EC_POINT:
|
|
pointBytes = a.Value
|
|
}
|
|
}
|
|
if pointBytes == nil || pubKey.Curve == nil {
|
|
return nil, errors.New("Couldn't retrieve EC point and EC parameters")
|
|
}
|
|
|
|
x, y := elliptic.Unmarshal(pubKey.Curve, pointBytes)
|
|
if x == nil {
|
|
// http://docs.oasis-open.org/pkcs11/pkcs11-curr/v2.40/os/pkcs11-curr-v2.40-os.html#_ftn1
|
|
// PKCS#11 v2.20 specified that the CKA_EC_POINT was to be stored in a DER-encoded
|
|
// OCTET STRING.
|
|
var point asn1.RawValue
|
|
_, err = asn1.Unmarshal(pointBytes, &point)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Failed to unmarshal returned CKA_EC_POINT: %s", err)
|
|
}
|
|
if len(point.Bytes) == 0 {
|
|
return nil, errors.New("Invalid CKA_EC_POINT value returned, OCTET string is empty")
|
|
}
|
|
x, y = elliptic.Unmarshal(pubKey.Curve, point.Bytes)
|
|
if x == nil {
|
|
return nil, errors.New("Invalid CKA_EC_POINT value returned, point is malformed")
|
|
}
|
|
}
|
|
pubKey.X, pubKey.Y = x, y
|
|
|
|
return pubKey, nil
|
|
}
|
|
|
|
type KeyType int
|
|
|
|
const (
|
|
RSAKey KeyType = iota
|
|
ECDSAKey
|
|
)
|
|
|
|
// Hash identifiers required for PKCS#11 RSA signing. Only support SHA-256, SHA-384,
|
|
// and SHA-512
|
|
var hashIdentifiers = map[crypto.Hash][]byte{
|
|
crypto.SHA256: {0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0x04, 0x20},
|
|
crypto.SHA384: {0x30, 0x41, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02, 0x05, 0x00, 0x04, 0x30},
|
|
crypto.SHA512: {0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05, 0x00, 0x04, 0x40},
|
|
}
|
|
|
|
func (s *Session) Sign(object pkcs11.ObjectHandle, keyType KeyType, digest []byte, hash crypto.Hash) ([]byte, error) {
|
|
if len(digest) != hash.Size() {
|
|
return nil, errors.New("digest length doesn't match hash length")
|
|
}
|
|
|
|
mech := make([]*pkcs11.Mechanism, 1)
|
|
switch keyType {
|
|
case RSAKey:
|
|
mech[0] = pkcs11.NewMechanism(pkcs11.CKM_RSA_PKCS, nil)
|
|
prefix, ok := hashIdentifiers[hash]
|
|
if !ok {
|
|
return nil, errors.New("unsupported hash function")
|
|
}
|
|
digest = append(prefix, digest...)
|
|
case ECDSAKey:
|
|
mech[0] = pkcs11.NewMechanism(pkcs11.CKM_ECDSA, nil)
|
|
}
|
|
|
|
err := s.Module.SignInit(s.Session, mech, object)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to initialize signing operation: %s", err)
|
|
}
|
|
signature, err := s.Module.Sign(s.Session, digest)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to sign data: %s", err)
|
|
}
|
|
|
|
return signature, nil
|
|
}
|
|
|
|
var ErrNoObject = errors.New("no objects found matching provided template")
|
|
|
|
// FindObject looks up a PKCS#11 object handle based on the provided template.
|
|
// In the case where zero or more than one objects are found to match the
|
|
// template an error is returned.
|
|
func (s *Session) FindObject(tmpl []*pkcs11.Attribute) (pkcs11.ObjectHandle, error) {
|
|
if err := s.Module.FindObjectsInit(s.Session, tmpl); err != nil {
|
|
return 0, err
|
|
}
|
|
handles, _, err := s.Module.FindObjects(s.Session, 2)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
if err := s.Module.FindObjectsFinal(s.Session); err != nil {
|
|
return 0, err
|
|
}
|
|
if len(handles) == 0 {
|
|
return 0, ErrNoObject
|
|
}
|
|
if len(handles) > 1 {
|
|
return 0, fmt.Errorf("too many objects (%d) that match the provided template", len(handles))
|
|
}
|
|
return handles[0], nil
|
|
}
|
|
|
|
// X509Signer is a convenience wrapper used for converting between the
|
|
// PKCS#11 ECDSA signature format and the RFC 5480 one which is required
|
|
// for X.509 certificates
|
|
type X509Signer struct {
|
|
session *Session
|
|
objectHandle pkcs11.ObjectHandle
|
|
keyType KeyType
|
|
|
|
pub crypto.PublicKey
|
|
}
|
|
|
|
// Sign signs a digest. If the signing key is ECDSA then the signature
|
|
// is converted from the PKCS#11 format to the RFC 5480 format. For RSA keys a
|
|
// conversion step is not needed.
|
|
func (p *X509Signer) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) {
|
|
signature, err := p.session.Sign(p.objectHandle, p.keyType, digest, opts.HashFunc())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if p.keyType == ECDSAKey {
|
|
// Convert from the PKCS#11 format to the RFC 5480 format so that
|
|
// it can be used in a X.509 certificate
|
|
r := big.NewInt(0).SetBytes(signature[:len(signature)/2])
|
|
s := big.NewInt(0).SetBytes(signature[len(signature)/2:])
|
|
signature, err = asn1.Marshal(struct {
|
|
R, S *big.Int
|
|
}{R: r, S: s})
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to convert signature to RFC 5480 format: %s", err)
|
|
}
|
|
}
|
|
return signature, nil
|
|
}
|
|
|
|
func (p *X509Signer) Public() crypto.PublicKey {
|
|
return p.pub
|
|
}
|
|
|
|
// NewSigner constructs an X509Signer for the private key object associated with the
|
|
// given label and ID. Unlike letsencrypt/pkcs11key this method doesn't rely on
|
|
// having the actual public key object in order to retrieve the private key
|
|
// handle. This is because we already have the key pair object ID, and as such
|
|
// do not need to query the HSM to retrieve it.
|
|
func (s *Session) NewSigner(label string, id []byte) (crypto.Signer, error) {
|
|
// Retrieve the private key handle that will later be used for the certificate
|
|
// signing operation
|
|
privateHandle, err := s.FindObject([]*pkcs11.Attribute{
|
|
pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_PRIVATE_KEY),
|
|
pkcs11.NewAttribute(pkcs11.CKA_LABEL, label),
|
|
pkcs11.NewAttribute(pkcs11.CKA_ID, id),
|
|
})
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to retrieve private key handle: %s", err)
|
|
}
|
|
attrs, err := s.GetAttributeValue(privateHandle, []*pkcs11.Attribute{
|
|
pkcs11.NewAttribute(pkcs11.CKA_KEY_TYPE, nil)},
|
|
)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to retrieve key type: %s", err)
|
|
}
|
|
if len(attrs) == 0 {
|
|
return nil, errors.New("failed to retrieve key attributes")
|
|
}
|
|
|
|
// Retrieve the public key handle with the same CKA_ID as the private key
|
|
// and construct a {rsa,ecdsa}.PublicKey for use in x509.CreateCertificate
|
|
pubHandle, err := s.FindObject([]*pkcs11.Attribute{
|
|
pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_PUBLIC_KEY),
|
|
pkcs11.NewAttribute(pkcs11.CKA_LABEL, label),
|
|
pkcs11.NewAttribute(pkcs11.CKA_ID, id),
|
|
pkcs11.NewAttribute(pkcs11.CKA_KEY_TYPE, attrs[0].Value),
|
|
})
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to retrieve public key handle: %s", err)
|
|
}
|
|
var pub crypto.PublicKey
|
|
var keyType KeyType
|
|
switch {
|
|
// 0x00000000, CKK_RSA
|
|
case bytes.Equal(attrs[0].Value, []byte{0, 0, 0, 0, 0, 0, 0, 0}):
|
|
keyType = RSAKey
|
|
pub, err = s.GetRSAPublicKey(pubHandle)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to retrieve public key: %s", err)
|
|
}
|
|
// 0x00000003, CKK_ECDSA
|
|
case bytes.Equal(attrs[0].Value, []byte{3, 0, 0, 0, 0, 0, 0, 0}):
|
|
keyType = ECDSAKey
|
|
pub, err = s.GetECDSAPublicKey(pubHandle)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to retrieve public key: %s", err)
|
|
}
|
|
default:
|
|
return nil, errors.New("unsupported key type")
|
|
}
|
|
|
|
return &X509Signer{
|
|
session: s,
|
|
objectHandle: privateHandle,
|
|
keyType: keyType,
|
|
pub: pub,
|
|
}, nil
|
|
}
|
|
|
|
func NewMock() *MockCtx {
|
|
return &MockCtx{}
|
|
}
|
|
|
|
func NewSessionWithMock() (*Session, *MockCtx) {
|
|
ctx := NewMock()
|
|
return &Session{ctx, 0}, ctx
|
|
}
|
|
|
|
type MockCtx struct {
|
|
GenerateKeyPairFunc func(pkcs11.SessionHandle, []*pkcs11.Mechanism, []*pkcs11.Attribute, []*pkcs11.Attribute) (pkcs11.ObjectHandle, pkcs11.ObjectHandle, error)
|
|
GetAttributeValueFunc func(pkcs11.SessionHandle, pkcs11.ObjectHandle, []*pkcs11.Attribute) ([]*pkcs11.Attribute, error)
|
|
SignInitFunc func(pkcs11.SessionHandle, []*pkcs11.Mechanism, pkcs11.ObjectHandle) error
|
|
SignFunc func(pkcs11.SessionHandle, []byte) ([]byte, error)
|
|
GenerateRandomFunc func(pkcs11.SessionHandle, int) ([]byte, error)
|
|
FindObjectsInitFunc func(sh pkcs11.SessionHandle, temp []*pkcs11.Attribute) error
|
|
FindObjectsFunc func(sh pkcs11.SessionHandle, max int) ([]pkcs11.ObjectHandle, bool, error)
|
|
FindObjectsFinalFunc func(sh pkcs11.SessionHandle) error
|
|
}
|
|
|
|
func (mc MockCtx) GenerateKeyPair(s pkcs11.SessionHandle, m []*pkcs11.Mechanism, a1 []*pkcs11.Attribute, a2 []*pkcs11.Attribute) (pkcs11.ObjectHandle, pkcs11.ObjectHandle, error) {
|
|
return mc.GenerateKeyPairFunc(s, m, a1, a2)
|
|
}
|
|
|
|
func (mc MockCtx) GetAttributeValue(s pkcs11.SessionHandle, o pkcs11.ObjectHandle, a []*pkcs11.Attribute) ([]*pkcs11.Attribute, error) {
|
|
return mc.GetAttributeValueFunc(s, o, a)
|
|
}
|
|
|
|
func (mc MockCtx) SignInit(s pkcs11.SessionHandle, m []*pkcs11.Mechanism, o pkcs11.ObjectHandle) error {
|
|
return mc.SignInitFunc(s, m, o)
|
|
}
|
|
|
|
func (mc MockCtx) Sign(s pkcs11.SessionHandle, m []byte) ([]byte, error) {
|
|
return mc.SignFunc(s, m)
|
|
}
|
|
|
|
func (mc MockCtx) GenerateRandom(s pkcs11.SessionHandle, c int) ([]byte, error) {
|
|
return mc.GenerateRandomFunc(s, c)
|
|
}
|
|
|
|
func (mc MockCtx) FindObjectsInit(sh pkcs11.SessionHandle, temp []*pkcs11.Attribute) error {
|
|
return mc.FindObjectsInitFunc(sh, temp)
|
|
}
|
|
|
|
func (mc MockCtx) FindObjects(sh pkcs11.SessionHandle, max int) ([]pkcs11.ObjectHandle, bool, error) {
|
|
return mc.FindObjectsFunc(sh, max)
|
|
}
|
|
|
|
func (mc MockCtx) FindObjectsFinal(sh pkcs11.SessionHandle) error {
|
|
return mc.FindObjectsFinalFunc(sh)
|
|
}
|