455 lines
12 KiB
Go
455 lines
12 KiB
Go
//go:build pkcs11
|
|
|
|
/*
|
|
Copyright NetFoundry Inc.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
https://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package pkcs11
|
|
|
|
import (
|
|
"crypto"
|
|
"crypto/ecdsa"
|
|
"crypto/elliptic"
|
|
"crypto/rsa"
|
|
"encoding/asn1"
|
|
"encoding/binary"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"github.com/michaelquigley/pfxlog"
|
|
"github.com/miekg/pkcs11"
|
|
"github.com/openziti/identity/engines"
|
|
"io"
|
|
"math/big"
|
|
"net/url"
|
|
"strconv"
|
|
)
|
|
|
|
type engine struct {
|
|
}
|
|
|
|
func (*engine) Id() string {
|
|
return EngineId
|
|
}
|
|
|
|
// engine supporting generic PKCS#11 HSM driver
|
|
// possible key URLs:
|
|
// - `pkcs11:/usr/lib/softhsm/libsofthsm2.so?slot=0&id=2171` - full driver path
|
|
// - `pkcs11:softhsm2?slot=0&id=2171` - driver id, driver will be loaded according to following rules:
|
|
// driver id converted to OS specific library file name (on *nix `lib${driver}.so`)
|
|
// then loaded according to dynamic loader configuration (on *nix according to http://man7.org/linux/man-pages/man3/dlopen.3.html)
|
|
var e = &engine{}
|
|
|
|
func init() {
|
|
engines.RegisterEngine(e)
|
|
}
|
|
|
|
var contexts = map[string]*pkcs11.Ctx{}
|
|
|
|
var log = pfxlog.ContextLogger(EngineId)
|
|
|
|
type p11Signer struct {
|
|
c *pkcs11.Ctx
|
|
s pkcs11.SessionHandle
|
|
h pkcs11.ObjectHandle
|
|
m *pkcs11.Mechanism
|
|
label string
|
|
|
|
pub crypto.PublicKey
|
|
}
|
|
|
|
func (k *p11Signer) Sign(_ io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) {
|
|
|
|
switch k.pub.(type) {
|
|
case *ecdsa.PublicKey:
|
|
return k.signECDSA(digest)
|
|
case *rsa.PublicKey:
|
|
return k.signRSA(digest, opts)
|
|
|
|
default:
|
|
return nil, fmt.Errorf("unsupported key")
|
|
}
|
|
}
|
|
|
|
// copied from golang/crypto/rsa/rsa.go
|
|
var hashInfo = map[crypto.Hash]struct {
|
|
oid []byte
|
|
mgf uint
|
|
hash uint
|
|
}{
|
|
crypto.MD5: {[]byte{0x30, 0x20, 0x30, 0x0c, 0x06, 0x08, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x02, 0x05, 0x05, 0x00, 0x04, 0x10}, 0, pkcs11.CKM_MD5},
|
|
crypto.SHA1: {[]byte{0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1a, 0x05, 0x00, 0x04, 0x14}, pkcs11.CKG_MGF1_SHA1, pkcs11.CKM_SHA_1},
|
|
crypto.SHA224: {[]byte{0x30, 0x2d, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x04, 0x05, 0x00, 0x04, 0x1c}, pkcs11.CKG_MGF1_SHA224, pkcs11.CKM_SHA224},
|
|
crypto.SHA256: {[]byte{0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0x04, 0x20}, pkcs11.CKG_MGF1_SHA256, pkcs11.CKM_SHA256},
|
|
crypto.SHA384: {[]byte{0x30, 0x41, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02, 0x05, 0x00, 0x04, 0x30}, pkcs11.CKG_MGF1_SHA384, pkcs11.CKM_SHA384},
|
|
crypto.SHA512: {[]byte{0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05, 0x00, 0x04, 0x40}, pkcs11.CKG_MGF1_SHA512, pkcs11.CKM_SHA512},
|
|
crypto.MD5SHA1: {[]byte{}, 0, 0}, // A special TLS case which doesn't use an ASN1 prefix.
|
|
crypto.RIPEMD160: {[]byte{0x30, 0x20, 0x30, 0x08, 0x06, 0x06, 0x28, 0xcf, 0x06, 0x03, 0x00, 0x31, 0x04, 0x14}, 0, pkcs11.CKM_RIPEMD160},
|
|
}
|
|
|
|
func (k *p11Signer) signRSA(digest []byte, opts crypto.SignerOpts) ([]byte, error) {
|
|
switch opts.(type) {
|
|
case *rsa.PSSOptions:
|
|
hash := opts.HashFunc()
|
|
|
|
hashInfo, ok := hashInfo[hash]
|
|
if !ok {
|
|
return nil, fmt.Errorf("unknown hash function to match for a message digest function [%d]", hash)
|
|
}
|
|
|
|
pssOptions := opts.(*rsa.PSSOptions)
|
|
switch pssOptions.SaltLength {
|
|
case rsa.PSSSaltLengthAuto:
|
|
pub := k.Public().(*rsa.PublicKey)
|
|
pssOptions.SaltLength = (pub.N.BitLen()+7)/8 - 2 - hash.Size()
|
|
case rsa.PSSSaltLengthEqualsHash:
|
|
pssOptions.SaltLength = hash.Size()
|
|
}
|
|
|
|
params := pkcs11.NewPSSParams(hashInfo.hash, hashInfo.mgf, uint(pssOptions.SaltLength))
|
|
mech := []*pkcs11.Mechanism{pkcs11.NewMechanism(pkcs11.CKM_RSA_PKCS_PSS, params)}
|
|
|
|
if err := k.c.SignInit(k.s, mech, k.h); err == nil {
|
|
return k.c.Sign(k.s, digest)
|
|
} else {
|
|
return nil, err
|
|
}
|
|
default: /* PKCS1-v1_5 */
|
|
hash := opts.HashFunc()
|
|
hashInfo := hashInfo[hash]
|
|
input := append(hashInfo.oid, digest...)
|
|
mech := []*pkcs11.Mechanism{pkcs11.NewMechanism(pkcs11.CKM_RSA_PKCS, nil)}
|
|
if err := k.c.SignInit(k.s, mech, k.h); err == nil {
|
|
return k.c.Sign(k.s, input)
|
|
} else {
|
|
return nil, err
|
|
}
|
|
}
|
|
}
|
|
|
|
func (k *p11Signer) signECDSA(digest []byte) ([]byte, error) {
|
|
|
|
mech := []*pkcs11.Mechanism{pkcs11.NewMechanism(pkcs11.CKM_ECDSA, nil)}
|
|
if err := k.c.SignInit(k.s, mech, k.h); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
sigBytes, err := k.c.Sign(k.s, digest)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var sig struct {
|
|
R, S *big.Int
|
|
}
|
|
|
|
n := len(sigBytes) / 2
|
|
sig.R = new(big.Int)
|
|
sig.R.SetBytes(sigBytes[:n])
|
|
sig.S = new(big.Int)
|
|
sig.S.SetBytes(sigBytes[n:])
|
|
|
|
bytes, err := asn1.Marshal(sig)
|
|
return bytes, err
|
|
}
|
|
|
|
func (k *p11Signer) Public() crypto.PublicKey {
|
|
return k.pub
|
|
}
|
|
|
|
func (*engine) LoadKey(key *url.URL) (crypto.PrivateKey, error) {
|
|
log.WithField("url", key).Debug("loading key")
|
|
|
|
driver := key.Path
|
|
if driver == "" {
|
|
if key.Host == "" {
|
|
return nil, fmt.Errorf("driver not specified for PKCS#11 engine, see docs")
|
|
}
|
|
|
|
driver = key.Host
|
|
}
|
|
|
|
log.Infof("using driver: %v", driver)
|
|
ctx, err := getContext(driver)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
opts := key.Query()
|
|
|
|
slot := opts.Get("slot")
|
|
var slotId uint
|
|
if slot == "" { // use the first slot returned by token
|
|
if slots, err := ctx.GetSlotList(true); err != nil {
|
|
return nil, err
|
|
} else {
|
|
slotId = slots[0]
|
|
log.Warnf("slot not specified, using first slot reported by the driver (%d)", slotId)
|
|
}
|
|
} else {
|
|
n, err := strconv.ParseInt(slot, 0, 64)
|
|
if err != nil {
|
|
log.Errorf("slot with value [%v] could not be parsed.", slot)
|
|
}
|
|
slotId = uint(n)
|
|
log.Debugf("using slot id: %d", slotId)
|
|
}
|
|
|
|
session, err := ctx.OpenSession(slotId, pkcs11.CKF_SERIAL_SESSION|pkcs11.CKF_RW_SESSION)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
pin := opts.Get("pin")
|
|
if pin != "" {
|
|
err = ctx.Login(session, pkcs11.CKU_USER, pin)
|
|
if err != nil && err != pkcs11.Error(pkcs11.CKR_USER_ALREADY_LOGGED_IN) {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
// find the key
|
|
id := []byte{0}
|
|
if keyId := opts.Get("id"); keyId != "" {
|
|
id, _ = hex.DecodeString(keyId)
|
|
}
|
|
|
|
if len(id) == 0 {
|
|
id = []byte{0}
|
|
}
|
|
|
|
keyHandle, err := findHandle(ctx, session, pkcs11.CKO_PRIVATE_KEY, id)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
pubHandle, err := findHandle(ctx, session, pkcs11.CKO_PUBLIC_KEY, id)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
keyType, err := getObjectUintAttr(ctx, session, keyHandle, pkcs11.CKA_KEY_TYPE)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
mechId, err := getObjectUintAttr(ctx, session, keyHandle, pkcs11.CKA_ALLOWED_MECHANISMS)
|
|
if err != nil && err != pkcs11.Error(pkcs11.CKR_ATTRIBUTE_TYPE_INVALID) {
|
|
return nil, err
|
|
}
|
|
log.WithField("sign mechanism", mechId).Debug("found signing mechanism")
|
|
|
|
var pubKey crypto.PublicKey
|
|
var signMech *pkcs11.Mechanism
|
|
switch keyType {
|
|
case pkcs11.CKK_EC:
|
|
pubKey, err = loadECDSApub(ctx, session, pubHandle)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
signMech, err = getECDSAmechanism(ctx, slotId, pubKey.(*ecdsa.PublicKey))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
case pkcs11.CKK_RSA:
|
|
signMech = pkcs11.NewMechanism(pkcs11.CKM_RSA_PKCS, nil)
|
|
pubKey, err = loadRSApub(ctx, session, pubHandle)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
default:
|
|
return nil, fmt.Errorf("unsupported key type (%d)", keyType)
|
|
}
|
|
|
|
signer := &p11Signer{c: ctx, s: session, h: keyHandle, m: signMech, pub: pubKey}
|
|
signer.label, _ = getObjectStringAttr(ctx, session, keyHandle, pkcs11.CKA_LABEL)
|
|
|
|
return signer, nil
|
|
}
|
|
|
|
func getContext(driver string) (*pkcs11.Ctx, error) {
|
|
if ctx, ok := contexts[driver]; ok {
|
|
return ctx, nil
|
|
}
|
|
|
|
ctx := pkcs11.New(driver)
|
|
err := ctx.Initialize()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
contexts[driver] = ctx
|
|
return ctx, nil
|
|
}
|
|
|
|
// this mapping is lifted from standard golang crypto/x509.go
|
|
var oid2curve = map[string]elliptic.Curve{
|
|
"1.3.132.0.33": elliptic.P224(),
|
|
"1.2.840.10045.3.1.7": elliptic.P256(),
|
|
"1.3.132.0.34": elliptic.P384(),
|
|
"1.3.132.0.35": elliptic.P521(),
|
|
}
|
|
|
|
func getECDSAmechanism(ctx *pkcs11.Ctx, slot uint, pubKey *ecdsa.PublicKey) (*pkcs11.Mechanism, error) {
|
|
|
|
var signMech uint
|
|
switch pubKey.Curve.Params().BitSize {
|
|
case 512:
|
|
signMech = pkcs11.CKM_ECDSA_SHA512
|
|
case 384:
|
|
signMech = pkcs11.CKM_ECDSA_SHA384
|
|
case 256:
|
|
signMech = pkcs11.CKM_ECDSA_SHA256
|
|
case 224:
|
|
signMech = pkcs11.CKM_ECDSA_SHA224
|
|
default:
|
|
return nil, fmt.Errorf("unexpected key size curve(%s)", pubKey.Curve.Params().Name)
|
|
}
|
|
|
|
prefered := []uint{
|
|
signMech,
|
|
pkcs11.CKM_ECDSA, // fallback -- softhsm only reports this mechanism as available
|
|
}
|
|
|
|
for _, m := range prefered {
|
|
mech := []*pkcs11.Mechanism{
|
|
pkcs11.NewMechanism(m, nil),
|
|
}
|
|
|
|
_, err := ctx.GetMechanismInfo(slot, mech)
|
|
if err == nil {
|
|
return mech[0], nil
|
|
} else {
|
|
log.WithError(err).Warnf("failed to get mechanism info [%x]", m)
|
|
}
|
|
}
|
|
|
|
return nil, fmt.Errorf("token does not support ECDSA sign mechanisms")
|
|
}
|
|
|
|
func loadECDSApub(ctx *pkcs11.Ctx, session pkcs11.SessionHandle, ph pkcs11.ObjectHandle) (*ecdsa.PublicKey, error) {
|
|
templ := []*pkcs11.Attribute{
|
|
{Type: pkcs11.CKA_EC_PARAMS},
|
|
{Type: pkcs11.CKA_EC_POINT},
|
|
}
|
|
if attrs, err := ctx.GetAttributeValue(session, ph, templ); err != nil {
|
|
return nil, err
|
|
} else {
|
|
var oid asn1.ObjectIdentifier
|
|
rest, err := asn1.UnmarshalWithParams(attrs[0].Value, &oid, "")
|
|
log.Debugf("EC oid[%s], rest: %v, err: %v", oid, rest, err)
|
|
curve, found := oid2curve[oid.String()]
|
|
if !found {
|
|
return nil, fmt.Errorf("elliptic curve not found for oid[%s]", oid)
|
|
}
|
|
|
|
var pointBytes []byte
|
|
extra, err := asn1.Unmarshal(attrs[1].Value, &pointBytes)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("elliptic curve point is invalid ASN.1")
|
|
}
|
|
|
|
if len(extra) > 0 {
|
|
// We weren't expecting extra data
|
|
return nil, fmt.Errorf("unexpected data found when parsing elliptic curve point")
|
|
}
|
|
x, y := elliptic.Unmarshal(curve, pointBytes)
|
|
|
|
return &ecdsa.PublicKey{
|
|
Curve: curve,
|
|
X: x,
|
|
Y: y,
|
|
}, nil
|
|
}
|
|
}
|
|
|
|
func loadRSApub(ctx *pkcs11.Ctx, session pkcs11.SessionHandle, ph pkcs11.ObjectHandle) (crypto.PublicKey, error) {
|
|
templ := []*pkcs11.Attribute{
|
|
{Type: pkcs11.CKA_MODULUS},
|
|
{Type: pkcs11.CKA_PUBLIC_EXPONENT},
|
|
}
|
|
if attrs, err := ctx.GetAttributeValue(session, ph, templ); err != nil {
|
|
return nil, err
|
|
} else {
|
|
|
|
result := &rsa.PublicKey{
|
|
N: new(big.Int),
|
|
}
|
|
|
|
result.N.SetBytes(attrs[0].Value)
|
|
|
|
exp := new(big.Int)
|
|
exp.SetBytes(attrs[1].Value)
|
|
if exp.BitLen() > 32 || exp.Sign() < 1 {
|
|
return nil, fmt.Errorf("unxpected RSA exponent value")
|
|
}
|
|
result.E = int(exp.Uint64())
|
|
|
|
return result, nil
|
|
}
|
|
}
|
|
|
|
func findHandle(ctx *pkcs11.Ctx, session pkcs11.SessionHandle, cls uint, id []byte) (pkcs11.ObjectHandle, error) {
|
|
// find the key
|
|
query := make([]*pkcs11.Attribute, 1)
|
|
query[0] = pkcs11.NewAttribute(pkcs11.CKA_CLASS, cls)
|
|
|
|
if id != nil {
|
|
query = append(query, pkcs11.NewAttribute(pkcs11.CKA_ID, id))
|
|
}
|
|
|
|
err := ctx.FindObjectsInit(session, query)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
defer func() {
|
|
if e := ctx.FindObjectsFinal(session); e != nil {
|
|
log.Warnf("error: FindObjectFinal(): %v", e)
|
|
}
|
|
}()
|
|
|
|
objs, _, err := ctx.FindObjects(session, 1)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
if len(objs) == 0 {
|
|
return 0, fmt.Errorf("key not found")
|
|
}
|
|
return objs[0], nil
|
|
}
|
|
|
|
func getObjectStringAttr(ctx *pkcs11.Ctx, session pkcs11.SessionHandle, obj pkcs11.ObjectHandle, attr_id uint) (string, error) {
|
|
v, err := getObjectAttribute(ctx, session, obj, attr_id)
|
|
return string(v), err
|
|
}
|
|
|
|
func getObjectUintAttr(ctx *pkcs11.Ctx, session pkcs11.SessionHandle, obj pkcs11.ObjectHandle, attr_id uint) (uint, error) {
|
|
v, err := getObjectAttribute(ctx, session, obj, attr_id)
|
|
u, _ := binary.Uvarint(v)
|
|
return uint(u), err
|
|
}
|
|
|
|
func getObjectAttribute(ctx *pkcs11.Ctx, session pkcs11.SessionHandle, obj pkcs11.ObjectHandle, attr_id uint) ([]byte, error) {
|
|
templ := []*pkcs11.Attribute{
|
|
{Type: attr_id},
|
|
}
|
|
attrs, err := ctx.GetAttributeValue(session, obj, templ)
|
|
if err == pkcs11.Error(pkcs11.CKR_ATTRIBUTE_TYPE_INVALID) {
|
|
return nil, nil
|
|
} else if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return attrs[0].Value, nil
|
|
}
|