nebula/pkclient/pkclient_cgo.go

230 lines
6.7 KiB
Go

//go:build cgo && pkcs11
package pkclient
import (
"encoding/asn1"
"errors"
"fmt"
"log"
"math/big"
"github.com/miekg/pkcs11"
"github.com/miekg/pkcs11/p11"
)
type PKClient struct {
module p11.Module
session p11.Session
id []byte
label []byte
privKeyObj p11.Object
pubKeyObj p11.Object
}
type ecdsaSignature struct {
R, S *big.Int
}
// New tries to open a session with the HSM, select the slot and login to it
func New(hsmPath string, slotId uint, pin string, id string, label string) (*PKClient, error) {
module, err := p11.OpenModule(hsmPath)
if err != nil {
return nil, fmt.Errorf("failed to load module library: %s", hsmPath)
}
slots, err := module.Slots()
if err != nil {
module.Destroy()
return nil, err
}
// Try to open a session on the slot
slotIdx := 0
for i, slot := range slots {
if slot.ID() == slotId {
slotIdx = i
break
}
}
client := &PKClient{
module: module,
id: []byte(id),
label: []byte(label),
}
client.session, err = slots[slotIdx].OpenWriteSession()
if err != nil {
module.Destroy()
return nil, fmt.Errorf("failed to open session on slot %d", slotId)
}
if len(pin) != 0 {
err = client.session.Login(pin)
if err != nil {
// ignore "already logged in"
if !errors.Is(err, pkcs11.Error(256)) {
_ = client.session.Close()
return nil, fmt.Errorf("unable to login. error: %w", err)
}
}
}
// Make sure the hsm has a private key for deriving
client.privKeyObj, err = client.findDeriveKey(client.id, client.label, true)
if err != nil {
_ = client.Close() //log out, close session, destroy module
return nil, fmt.Errorf("failed to find private key for deriving: %w", err)
}
return client, nil
}
// Close cleans up properly and logs out
func (c *PKClient) Close() error {
var err error = nil
if c.session != nil {
_ = c.session.Logout() //if logout fails, we still want to close
err = c.session.Close()
}
c.module.Destroy()
return err
}
// Try to find a suitable key on the hsm for key derivation
// parameter GET_PUB_KEY sets the search pattern for a public or private key
func (c *PKClient) findDeriveKey(id []byte, label []byte, private bool) (key p11.Object, err error) {
keyClass := pkcs11.CKO_PRIVATE_KEY
if !private {
keyClass = pkcs11.CKO_PUBLIC_KEY
}
keyAttrs := []*pkcs11.Attribute{
//todo, not all HSMs seem to report this, even if its true: pkcs11.NewAttribute(pkcs11.CKA_DERIVE, true),
pkcs11.NewAttribute(pkcs11.CKA_CLASS, keyClass),
}
if id != nil && len(id) != 0 {
keyAttrs = append(keyAttrs, pkcs11.NewAttribute(pkcs11.CKA_ID, id))
}
if label != nil && len(label) != 0 {
keyAttrs = append(keyAttrs, pkcs11.NewAttribute(pkcs11.CKA_LABEL, label))
}
return c.session.FindObject(keyAttrs)
}
func (c *PKClient) listDeriveKeys(id []byte, label []byte, private bool) {
keyClass := pkcs11.CKO_PRIVATE_KEY
if !private {
keyClass = pkcs11.CKO_PUBLIC_KEY
}
keyAttrs := []*pkcs11.Attribute{
pkcs11.NewAttribute(pkcs11.CKA_CLASS, keyClass),
}
if id != nil && len(id) != 0 {
keyAttrs = append(keyAttrs, pkcs11.NewAttribute(pkcs11.CKA_ID, id))
}
if label != nil && len(label) != 0 {
keyAttrs = append(keyAttrs, pkcs11.NewAttribute(pkcs11.CKA_LABEL, label))
}
objects, err := c.session.FindObjects(keyAttrs)
if err != nil {
return
}
for _, obj := range objects {
l, err := obj.Label()
log.Printf("%s, %v", l, err)
a, err := obj.Attribute(pkcs11.CKA_DERIVE)
log.Printf("DERIVE: %s %v, %v", l, a, err)
}
}
// SignASN1 signs some data. Returns the ASN.1 encoded signature.
func (c *PKClient) SignASN1(data []byte) ([]byte, error) {
mech := pkcs11.NewMechanism(pkcs11.CKM_ECDSA_SHA256, nil)
sk := p11.PrivateKey(c.privKeyObj)
rawSig, err := sk.Sign(*mech, data)
if err != nil {
return nil, err
}
// PKCS #11 Mechanisms v2.30:
// "The signature octets correspond to the concatenation of the ECDSA values r and s,
// both represented as an octet string of equal length of at most nLen with the most
// significant byte first. If r and s have different octet length, the shorter of both
// must be padded with leading zero octets such that both have the same octet length.
// Loosely spoken, the first half of the signature is r and the second half is s."
r := new(big.Int).SetBytes(rawSig[:len(rawSig)/2])
s := new(big.Int).SetBytes(rawSig[len(rawSig)/2:])
return asn1.Marshal(ecdsaSignature{r, s})
}
// DeriveNoise derives a shared secret using the input public key against the private key that was found during setup.
// Returns a fixed 32 byte array.
func (c *PKClient) DeriveNoise(peerPubKey []byte) ([]byte, error) {
// Before we call derive, we need to have an array of attributes which specify the type of
// key to be returned, in our case, it's the shared secret key, produced via deriving
// This template pulled from OpenSC pkclient-tool.c line 4038
attrTemplate := []*pkcs11.Attribute{
pkcs11.NewAttribute(pkcs11.CKA_TOKEN, false),
pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_SECRET_KEY),
pkcs11.NewAttribute(pkcs11.CKA_KEY_TYPE, pkcs11.CKK_GENERIC_SECRET),
pkcs11.NewAttribute(pkcs11.CKA_SENSITIVE, false),
pkcs11.NewAttribute(pkcs11.CKA_EXTRACTABLE, true),
pkcs11.NewAttribute(pkcs11.CKA_ENCRYPT, true),
pkcs11.NewAttribute(pkcs11.CKA_DECRYPT, true),
pkcs11.NewAttribute(pkcs11.CKA_WRAP, true),
pkcs11.NewAttribute(pkcs11.CKA_UNWRAP, true),
}
// Set up the parameters which include the peer's public key
ecdhParams := pkcs11.NewECDH1DeriveParams(pkcs11.CKD_NULL, nil, peerPubKey)
mech := pkcs11.NewMechanism(pkcs11.CKM_ECDH1_DERIVE, ecdhParams)
sk := p11.PrivateKey(c.privKeyObj)
tmpKey, err := sk.Derive(*mech, attrTemplate)
if err != nil {
return nil, err
}
if tmpKey == nil || len(tmpKey) == 0 {
return nil, fmt.Errorf("got an empty secret key")
}
secret := make([]byte, NoiseKeySize)
copy(secret[:], tmpKey[:NoiseKeySize])
return secret, nil
}
func (c *PKClient) GetPubKey() ([]byte, error) {
d, err := c.privKeyObj.Attribute(pkcs11.CKA_PUBLIC_KEY_INFO)
if err != nil {
return nil, err
}
if d != nil && len(d) > 0 {
return formatPubkeyFromPublicKeyInfoAttr(d)
}
c.pubKeyObj, err = c.findDeriveKey(c.id, c.label, false)
if err != nil {
return nil, fmt.Errorf("pkcs11 module gave us a nil CKA_PUBLIC_KEY_INFO, and looking up the public key also failed: %w", err)
}
d, err = c.pubKeyObj.Attribute(pkcs11.CKA_EC_POINT)
if err != nil {
return nil, fmt.Errorf("pkcs11 module gave us a nil CKA_PUBLIC_KEY_INFO, and reading CKA_EC_POINT also failed: %w", err)
}
if d == nil || len(d) < 1 {
return nil, fmt.Errorf("pkcs11 module gave us a nil or empty CKA_EC_POINT")
}
switch len(d) {
case 65: //length of 0x04 + len(X) + len(Y)
return d, nil
case 67: //as above, DER-encoded IIRC?
return d[2:], nil
default:
return nil, fmt.Errorf("unknown public key length: %d", len(d))
}
}