//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)) } }