mirror of https://github.com/slackhq/nebula.git
230 lines
6.7 KiB
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))
|
|
}
|
|
}
|