EdgexAgent/device-gps-go/vendor/github.com/openziti/identity/engines/pkcs11/engine.go
2025-07-10 20:30:06 +08:00

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
}