mirror of
https://github.com/outbackdingo/labca.git
synced 2026-01-27 10:19:34 +00:00
736 lines
21 KiB
Go
736 lines
21 KiB
Go
//go:build !standalone
|
|
|
|
package main
|
|
|
|
import (
|
|
"crypto"
|
|
"crypto/aes"
|
|
"crypto/cipher"
|
|
"crypto/ecdsa"
|
|
"crypto/elliptic"
|
|
"crypto/rand"
|
|
"crypto/rsa"
|
|
"crypto/subtle"
|
|
"crypto/x509"
|
|
"encoding/asn1"
|
|
"encoding/binary"
|
|
"encoding/hex"
|
|
"encoding/pem"
|
|
"errors"
|
|
"fmt"
|
|
"math/big"
|
|
"os"
|
|
"path"
|
|
"path/filepath"
|
|
"reflect"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/miekg/pkcs11"
|
|
)
|
|
|
|
const CERT_FILES_PATH = "/opt/boulder/labca/certs/webpki/"
|
|
|
|
type HSMConfig struct {
|
|
Module string
|
|
UserPIN string
|
|
SOPIN string
|
|
SlotID string
|
|
Label string
|
|
}
|
|
|
|
// HSMSession represents a session with a given PKCS#11 module. It is NOT safe for concurrent access.
|
|
type HSMSession struct {
|
|
Context PKCSCtx
|
|
Handle pkcs11.SessionHandle
|
|
}
|
|
|
|
type PKCSCtx interface {
|
|
CloseSession(pkcs11.SessionHandle) error
|
|
CreateObject(pkcs11.SessionHandle, []*pkcs11.Attribute) (pkcs11.ObjectHandle, error)
|
|
DestroyObject(pkcs11.SessionHandle, pkcs11.ObjectHandle) error
|
|
FindObjects(pkcs11.SessionHandle, int) ([]pkcs11.ObjectHandle, bool, error)
|
|
FindObjectsInit(pkcs11.SessionHandle, []*pkcs11.Attribute) error
|
|
FindObjectsFinal(pkcs11.SessionHandle) error
|
|
GenerateKey(pkcs11.SessionHandle, []*pkcs11.Mechanism, []*pkcs11.Attribute) (pkcs11.ObjectHandle, error)
|
|
GetAttributeValue(pkcs11.SessionHandle, pkcs11.ObjectHandle, []*pkcs11.Attribute) ([]*pkcs11.Attribute, error)
|
|
Logout(pkcs11.SessionHandle) error
|
|
WrapKey(pkcs11.SessionHandle, []*pkcs11.Mechanism, pkcs11.ObjectHandle, pkcs11.ObjectHandle) ([]byte, error)
|
|
}
|
|
|
|
func (cfg *HSMConfig) Initialize(ca_type string, seqnr string) {
|
|
cfg.Module = "/usr/lib/softhsm/libsofthsm2.so"
|
|
cfg.UserPIN = "1234"
|
|
cfg.SOPIN = "5678"
|
|
cfg.SlotID = "0"
|
|
if ca_type != "root" {
|
|
cfg.SlotID = "1"
|
|
}
|
|
cfg.Label = fmt.Sprintf("%s %s", ca_type, seqnr)
|
|
}
|
|
|
|
func (cfg *HSMConfig) CreateSlot() error {
|
|
s, err := strconv.ParseUint(cfg.SlotID, 10, 32)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to convert slot id '%s' to uint: %s", cfg.SlotID, err.Error())
|
|
}
|
|
id, err := cfg.createSlot(uint(s), cfg.Label)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create slot: %s", err.Error())
|
|
}
|
|
cfg.SlotID = id
|
|
|
|
return nil
|
|
}
|
|
|
|
func findSlotWithLabel(ctx *pkcs11.Ctx, label string, missing_ok bool) (string, error) {
|
|
slots, err := ctx.GetSlotList(true)
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to get slots list: %s", err)
|
|
}
|
|
|
|
for _, slot := range slots {
|
|
info, err := ctx.GetSlotInfo(slot)
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to get slot info: %s", err)
|
|
}
|
|
|
|
if info.Flags&pkcs11.CKF_TOKEN_PRESENT == pkcs11.CKF_TOKEN_PRESENT {
|
|
token, err := ctx.GetTokenInfo(slot)
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to get token info: %s", err)
|
|
}
|
|
|
|
if token.Label == label {
|
|
return fmt.Sprint(slot), nil
|
|
}
|
|
}
|
|
}
|
|
|
|
if missing_ok {
|
|
return "", nil
|
|
}
|
|
|
|
return "", errors.New("no slot found matching this label")
|
|
}
|
|
|
|
func (cfg *HSMConfig) createSlot(slotId uint, label string) (string, error) {
|
|
ctx := pkcs11.New(cfg.Module)
|
|
if ctx == nil {
|
|
return "", errors.New("failed to load pkcs11 module")
|
|
}
|
|
err := ctx.Initialize()
|
|
if err != nil && err.Error() != "pkcs11: 0x191: CKR_CRYPTOKI_ALREADY_INITIALIZED" {
|
|
return "", fmt.Errorf("failed to initialize pkcs11 context: %s", err)
|
|
}
|
|
|
|
slot, err := findSlotWithLabel(ctx, label, true)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if slot != "" {
|
|
return slot, nil
|
|
}
|
|
|
|
// No slot found with this token label, so create a new one
|
|
err = ctx.InitToken(slotId, cfg.SOPIN, label)
|
|
if err != nil {
|
|
if strings.Contains(err.Error(), "0x3: CKR_SLOT_ID_INVALID") {
|
|
slots, err := ctx.GetSlotList(true)
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to initialize token, failed to get slot list: %s", err)
|
|
}
|
|
slotId = uint(len(slots) - 1)
|
|
cfg.SlotID = fmt.Sprint(slotId)
|
|
err = ctx.InitToken(slotId, cfg.SOPIN, label)
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to initialize token with id %d: %s", slotId, err)
|
|
}
|
|
} else {
|
|
return "", fmt.Errorf("failed to initialize token: %s", err)
|
|
}
|
|
}
|
|
|
|
session, err := ctx.OpenSession(slotId, pkcs11.CKF_SERIAL_SESSION|pkcs11.CKF_RW_SESSION)
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to open session: %s", err)
|
|
}
|
|
defer func() { _ = ctx.CloseSession(session) }()
|
|
|
|
err = ctx.Login(session, pkcs11.CKU_SO, cfg.SOPIN)
|
|
if err != nil {
|
|
if err.Error() == "pkcs11: 0xA0: CKR_PIN_INCORRECT" {
|
|
return "", errors.New("incorrect SO PIN")
|
|
} else {
|
|
return "", fmt.Errorf("failed to login: %s", err)
|
|
}
|
|
}
|
|
defer func() { _ = ctx.Logout(session) }()
|
|
|
|
err = ctx.InitPIN(session, cfg.UserPIN)
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to initialize pin: %s", err)
|
|
}
|
|
|
|
// Forced reconnect to get the renumbered slots from SoftHSM2
|
|
_ = ctx.Finalize()
|
|
ctx.Destroy()
|
|
ctx = pkcs11.New(cfg.Module)
|
|
if ctx == nil {
|
|
return "", errors.New("failed to reload pkcs11 module")
|
|
}
|
|
err = ctx.Initialize()
|
|
if err != nil && err.Error() != "pkcs11: 0x191: CKR_CRYPTOKI_ALREADY_INITIALIZED" {
|
|
return "", fmt.Errorf("failed to reinitialize pkcs11 context: %s", err)
|
|
}
|
|
|
|
slot, err = findSlotWithLabel(ctx, label, false)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if slot != "" {
|
|
return slot, nil
|
|
}
|
|
|
|
return "", errors.New("failed to create slot")
|
|
}
|
|
|
|
// getSession establishes a logged in session on a pkcs11 slot.
|
|
//
|
|
// Don't forget to call .Close() on the resulting session when done!
|
|
func (cfg *HSMConfig) getSession() (*HSMSession, error) {
|
|
ctx := pkcs11.New(cfg.Module)
|
|
if ctx == nil {
|
|
return nil, errors.New("failed to load pkcs11 module")
|
|
}
|
|
err := ctx.Initialize()
|
|
if err != nil && err.Error() != "pkcs11: 0x191: CKR_CRYPTOKI_ALREADY_INITIALIZED" {
|
|
return nil, fmt.Errorf("failed to initialize pkcs11 context: %s", err)
|
|
}
|
|
|
|
slot, err := findSlotWithLabel(ctx, cfg.Label, true)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if slot == "" {
|
|
return nil, nil
|
|
}
|
|
|
|
s, err := strconv.ParseUint(slot, 10, 32)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to convert slot id '%s' to uint: %s", cfg.SlotID, err.Error())
|
|
}
|
|
|
|
session, err := ctx.OpenSession(uint(s), pkcs11.CKF_SERIAL_SESSION|pkcs11.CKF_RW_SESSION)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to open session: %s", err)
|
|
}
|
|
|
|
err = ctx.Login(session, pkcs11.CKU_USER, cfg.UserPIN)
|
|
if err != nil {
|
|
if err.Error() == "pkcs11: 0xA0: CKR_PIN_INCORRECT" {
|
|
return nil, errors.New("incorrect user PIN")
|
|
} else {
|
|
return nil, fmt.Errorf("failed to login: %s", err)
|
|
}
|
|
}
|
|
|
|
return &HSMSession{ctx, session}, nil
|
|
}
|
|
|
|
func (cfg *HSMConfig) ClearAll() error {
|
|
hs, err := cfg.getSession()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get session: %s", err)
|
|
}
|
|
defer hs.Close()
|
|
|
|
err = hs.DestroyAllObjects(cfg.Label)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func arrConcat(arrays ...[]byte) []byte {
|
|
out := make([]byte, len(arrays[0]))
|
|
copy(out, arrays[0])
|
|
for _, array := range arrays[1:] {
|
|
out = append(out, array...)
|
|
}
|
|
|
|
return out
|
|
}
|
|
|
|
func arrXor(arrL []byte, arrR []byte) []byte {
|
|
out := make([]byte, len(arrL))
|
|
for x := range arrL {
|
|
out[x] = arrL[x] ^ arrR[x]
|
|
}
|
|
return out
|
|
}
|
|
|
|
// AES Key Wrap algorithm is specified in RFC 3394
|
|
func UnwrapKey(block cipher.Block, cipherText []byte) ([]byte, error) {
|
|
//Initialize variables
|
|
a := make([]byte, 8)
|
|
n := (len(cipherText) / 8) - 1
|
|
|
|
r := make([][]byte, n)
|
|
for i := range r {
|
|
r[i] = make([]byte, 8)
|
|
copy(r[i], cipherText[(i+1)*8:])
|
|
}
|
|
copy(a, cipherText[:8])
|
|
|
|
//Compute intermediate values
|
|
for j := 5; j >= 0; j-- {
|
|
for i := n; i >= 1; i-- {
|
|
t := (n * j) + i
|
|
tBytes := make([]byte, 8)
|
|
binary.BigEndian.PutUint64(tBytes, uint64(t))
|
|
|
|
b := arrConcat(arrXor(a, tBytes), r[i-1])
|
|
block.Decrypt(b, b)
|
|
|
|
copy(a, b[:len(b)/2])
|
|
copy(r[i-1], b[len(b)/2:])
|
|
}
|
|
}
|
|
|
|
var defaultIV = []byte{0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6}
|
|
if subtle.ConstantTimeCompare(a, defaultIV) != 1 {
|
|
return nil, errors.New("integrity check failed - unexpected IV")
|
|
}
|
|
|
|
//Output
|
|
c := arrConcat(r...)
|
|
return c, nil
|
|
}
|
|
|
|
func (cfg *HSMConfig) GetPrivateKey() ([]byte, error) {
|
|
hs, err := cfg.getSession()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get session: %s", err)
|
|
}
|
|
defer hs.Close()
|
|
|
|
tmpl := []*pkcs11.Attribute{
|
|
pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_PRIVATE_KEY),
|
|
pkcs11.NewAttribute(pkcs11.CKA_LABEL, []byte(cfg.Label)),
|
|
}
|
|
|
|
keyHandle, err := hs.FindObject(tmpl)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to find private key with label='%s': %w", cfg.Label, err)
|
|
}
|
|
|
|
// Generate a temporary wrapping key in memory
|
|
mechs := []*pkcs11.Mechanism{
|
|
// pkcs11-tool --module /usr/lib/softhsm/libsofthsm2.so -M | grep -v generate_key_pair | grep generate
|
|
pkcs11.NewMechanism(pkcs11.CKM_AES_KEY_GEN, nil),
|
|
}
|
|
tmpl = []*pkcs11.Attribute{
|
|
pkcs11.NewAttribute(pkcs11.CKA_VALUE_LEN, 16),
|
|
pkcs11.NewAttribute(pkcs11.CKA_ENCRYPT, true),
|
|
pkcs11.NewAttribute(pkcs11.CKA_DECRYPT, true),
|
|
pkcs11.NewAttribute(pkcs11.CKA_WRAP, true),
|
|
pkcs11.NewAttribute(pkcs11.CKA_UNWRAP, true),
|
|
pkcs11.NewAttribute(pkcs11.CKA_EXTRACTABLE, true),
|
|
pkcs11.NewAttribute(pkcs11.CKA_TOKEN, false),
|
|
}
|
|
wrapKeyHandle, err := hs.GenerateKey(mechs, tmpl)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to generate wrapping key: %w", err)
|
|
}
|
|
|
|
// Extract the key value
|
|
tmpl = []*pkcs11.Attribute{
|
|
pkcs11.NewAttribute(pkcs11.CKA_VALUE, nil),
|
|
}
|
|
wrapKeyAttrs, err := hs.GetAttributeValue(wrapKeyHandle, tmpl)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get attribute values from object: %w", err)
|
|
}
|
|
var wrapKey []byte
|
|
for _, wrapKeyAttr := range wrapKeyAttrs {
|
|
switch wrapKeyAttr.Type {
|
|
case pkcs11.CKA_VALUE:
|
|
wrapKey = wrapKeyAttr.Value
|
|
default:
|
|
if wrapKeyAttr.Value == nil {
|
|
fmt.Printf("unexpected attribute #%d: nil\n", wrapKeyAttr.Type)
|
|
} else {
|
|
fmt.Printf("unexpected attribute #%d: %s / %s\n", wrapKeyAttr.Type, hex.EncodeToString(wrapKeyAttr.Value), wrapKeyAttr.Value)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Wrap the private key on the HSM
|
|
mechs = []*pkcs11.Mechanism{
|
|
// pkcs11-tool --module /usr/lib/softhsm/libsofthsm2.so -M | grep wrap
|
|
pkcs11.NewMechanism(pkcs11.CKM_AES_KEY_WRAP, nil),
|
|
}
|
|
wrappedKey, err := hs.WrapKey(mechs, wrapKeyHandle, keyHandle)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to wrap private key: %w", err)
|
|
}
|
|
|
|
// Unwrap the key locally
|
|
c, err := aes.NewCipher(wrapKey)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create new aes cipher: %w", err)
|
|
}
|
|
key, err := UnwrapKey(c, wrappedKey)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to unwrap key: %w", err)
|
|
}
|
|
|
|
return key, nil
|
|
}
|
|
|
|
func loadKey(filename string) (crypto.PrivateKey, crypto.PublicKey, error) {
|
|
var priv crypto.PrivateKey
|
|
var pub crypto.PublicKey
|
|
|
|
keyPEM, err := os.ReadFile(filename)
|
|
if err != nil {
|
|
return priv, pub, err
|
|
}
|
|
|
|
block, _ := pem.Decode(keyPEM)
|
|
if block == nil {
|
|
return priv, pub, fmt.Errorf("no data in key PEM file %s", filename)
|
|
}
|
|
|
|
parseResult, _ := x509.ParsePKCS8PrivateKey(block.Bytes)
|
|
if reflect.TypeOf(parseResult).String() == "*rsa.PrivateKey" {
|
|
k := parseResult.(*rsa.PrivateKey)
|
|
priv = k
|
|
pub = k.PublicKey
|
|
} else if reflect.TypeOf(parseResult).String() == "*ecdsa.PrivateKey" {
|
|
k := parseResult.(*ecdsa.PrivateKey)
|
|
priv = k
|
|
pub = k.PublicKey
|
|
} else {
|
|
return priv, pub, fmt.Errorf("unknown private key type '%s'", reflect.TypeOf(parseResult).String())
|
|
}
|
|
|
|
if priv == nil {
|
|
fmt.Printf("WARNING: unknown private key type for %+v\n", parseResult)
|
|
return priv, pub, errors.New("unknown private key type")
|
|
}
|
|
|
|
return priv, pub, nil
|
|
}
|
|
|
|
func loadCert(filename string) (*x509.Certificate, error) {
|
|
certPEM, err := os.ReadFile(filename)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
block, _ := pem.Decode(certPEM)
|
|
if block == nil {
|
|
return nil, fmt.Errorf("no data in certificate PEM file %s", filename)
|
|
}
|
|
|
|
parseResult, err := x509.ParseCertificate(block.Bytes)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("could not parse certificate: %s", err.Error())
|
|
}
|
|
|
|
return parseResult, nil
|
|
}
|
|
|
|
var curveToOIDDER = map[string][]byte{
|
|
elliptic.P224().Params().Name: {6, 5, 43, 129, 4, 0, 33},
|
|
elliptic.P256().Params().Name: {6, 8, 42, 134, 72, 206, 61, 3, 1, 7},
|
|
elliptic.P384().Params().Name: {6, 5, 43, 129, 4, 0, 34},
|
|
elliptic.P521().Params().Name: {6, 5, 43, 129, 4, 0, 35},
|
|
}
|
|
|
|
func storePubKey(hs *HSMSession, pubKey crypto.PublicKey, keyID []byte, label string) error {
|
|
tmpl := []*pkcs11.Attribute{
|
|
pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_PUBLIC_KEY),
|
|
pkcs11.NewAttribute(pkcs11.CKA_TOKEN, true),
|
|
pkcs11.NewAttribute(pkcs11.CKA_PRIVATE, false),
|
|
pkcs11.NewAttribute(pkcs11.CKA_VERIFY, true),
|
|
pkcs11.NewAttribute(pkcs11.CKA_ENCRYPT, true),
|
|
pkcs11.NewAttribute(pkcs11.CKA_WRAP, true),
|
|
pkcs11.NewAttribute(pkcs11.CKA_ID, keyID),
|
|
pkcs11.NewAttribute(pkcs11.CKA_LABEL, []byte(label)),
|
|
}
|
|
|
|
if reflect.TypeOf(pubKey).String() == "rsa.PublicKey" {
|
|
p := pubKey.(rsa.PublicKey)
|
|
|
|
tmpl = append(tmpl, pkcs11.NewAttribute(pkcs11.CKA_KEY_TYPE, pkcs11.CKK_RSA))
|
|
tmpl = append(tmpl, pkcs11.NewAttribute(pkcs11.CKA_MODULUS, p.N.Bytes()))
|
|
tmpl = append(tmpl, pkcs11.NewAttribute(pkcs11.CKA_PUBLIC_EXPONENT, big.NewInt(int64(p.E)).Bytes()))
|
|
} else if reflect.TypeOf(pubKey).String() == "ecdsa.PublicKey" {
|
|
p := pubKey.(ecdsa.PublicKey)
|
|
eh, err := p.ECDH()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to convert ecdsa pubkey to ecdh: %s", err.Error())
|
|
}
|
|
|
|
tmpl = append(tmpl, pkcs11.NewAttribute(pkcs11.CKA_KEY_TYPE, pkcs11.CKK_EC))
|
|
|
|
encodedCurve := curveToOIDDER[p.Curve.Params().Name]
|
|
tmpl = append(tmpl, pkcs11.NewAttribute(pkcs11.CKA_EC_PARAMS, encodedCurve))
|
|
|
|
rawValue := asn1.RawValue{
|
|
Tag: asn1.TagOctetString,
|
|
Bytes: eh.Bytes(),
|
|
}
|
|
marshalledPoint, err := asn1.Marshal(rawValue)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to marshall ecdsa point: %s", err.Error())
|
|
}
|
|
tmpl = append(tmpl, pkcs11.NewAttribute(pkcs11.CKA_EC_POINT, marshalledPoint))
|
|
|
|
} else {
|
|
return fmt.Errorf("unknown public key type '%s'", reflect.TypeOf(pubKey).String())
|
|
}
|
|
|
|
_, err := hs.CreateObject(tmpl)
|
|
if err != nil {
|
|
fmt.Printf("failed to create public key on HSM: %s\n", err.Error())
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func storePrivKey(hs *HSMSession, privKey crypto.PrivateKey, keyID []byte, label string, extractable bool) error {
|
|
tmpl := []*pkcs11.Attribute{
|
|
pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_PRIVATE_KEY),
|
|
pkcs11.NewAttribute(pkcs11.CKA_TOKEN, true),
|
|
pkcs11.NewAttribute(pkcs11.CKA_PRIVATE, true),
|
|
pkcs11.NewAttribute(pkcs11.CKA_SENSITIVE, true),
|
|
pkcs11.NewAttribute(pkcs11.CKA_EXTRACTABLE, extractable),
|
|
pkcs11.NewAttribute(pkcs11.CKA_SIGN, true),
|
|
pkcs11.NewAttribute(pkcs11.CKA_DECRYPT, true),
|
|
pkcs11.NewAttribute(pkcs11.CKA_DERIVE, true),
|
|
pkcs11.NewAttribute(pkcs11.CKA_WRAP_WITH_TRUSTED, false),
|
|
pkcs11.NewAttribute(pkcs11.CKA_UNWRAP, true),
|
|
pkcs11.NewAttribute(pkcs11.CKA_ID, keyID),
|
|
pkcs11.NewAttribute(pkcs11.CKA_LABEL, []byte(label)),
|
|
}
|
|
|
|
if reflect.TypeOf(privKey).String() == "*rsa.PrivateKey" {
|
|
k := privKey.(*rsa.PrivateKey)
|
|
|
|
tmpl = append(tmpl, pkcs11.NewAttribute(pkcs11.CKA_KEY_TYPE, pkcs11.CKK_RSA))
|
|
tmpl = append(tmpl, pkcs11.NewAttribute(pkcs11.CKA_MODULUS, k.N.Bytes()))
|
|
tmpl = append(tmpl, pkcs11.NewAttribute(pkcs11.CKA_PUBLIC_EXPONENT, big.NewInt(int64(k.PublicKey.E)).Bytes()))
|
|
tmpl = append(tmpl, pkcs11.NewAttribute(pkcs11.CKA_PRIVATE_EXPONENT, big.NewInt(int64(k.E)).Bytes()))
|
|
tmpl = append(tmpl, pkcs11.NewAttribute(pkcs11.CKA_PRIME_1, new(big.Int).Set(k.Primes[0]).Bytes()))
|
|
tmpl = append(tmpl, pkcs11.NewAttribute(pkcs11.CKA_PRIME_2, new(big.Int).Set(k.Primes[1]).Bytes()))
|
|
tmpl = append(tmpl, pkcs11.NewAttribute(pkcs11.CKA_EXPONENT_1, new(big.Int).Set(k.Precomputed.Dp).Bytes()))
|
|
tmpl = append(tmpl, pkcs11.NewAttribute(pkcs11.CKA_EXPONENT_2, new(big.Int).Set(k.Precomputed.Dq).Bytes()))
|
|
tmpl = append(tmpl, pkcs11.NewAttribute(pkcs11.CKA_COEFFICIENT, new(big.Int).Set(k.Precomputed.Qinv).Bytes()))
|
|
|
|
} else if reflect.TypeOf(privKey).String() == "*ecdsa.PrivateKey" {
|
|
k := privKey.(*ecdsa.PrivateKey)
|
|
|
|
tmpl = append(tmpl, pkcs11.NewAttribute(pkcs11.CKA_KEY_TYPE, pkcs11.CKK_EC))
|
|
encodedCurve := curveToOIDDER[k.Params().Name]
|
|
tmpl = append(tmpl, pkcs11.NewAttribute(pkcs11.CKA_EC_PARAMS, encodedCurve))
|
|
tmpl = append(tmpl, pkcs11.NewAttribute(pkcs11.CKA_VALUE, new(big.Int).Set(k.D).Bytes()))
|
|
|
|
} else {
|
|
return fmt.Errorf("unknown private key type '%s'", reflect.TypeOf(privKey).String())
|
|
}
|
|
|
|
_, err := hs.CreateObject(tmpl)
|
|
if err != nil {
|
|
fmt.Printf("failed to create private key on HSM: %s\n", err.Error())
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func storeCertificate(hs *HSMSession, certificate *x509.Certificate, keyID []byte, label string) error {
|
|
serial, err := asn1.Marshal(certificate.SerialNumber)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
tmpl := []*pkcs11.Attribute{
|
|
pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_CERTIFICATE),
|
|
pkcs11.NewAttribute(pkcs11.CKA_CERTIFICATE_TYPE, pkcs11.CKC_X_509),
|
|
pkcs11.NewAttribute(pkcs11.CKA_TOKEN, true),
|
|
pkcs11.NewAttribute(pkcs11.CKA_PRIVATE, false),
|
|
pkcs11.NewAttribute(pkcs11.CKA_SUBJECT, certificate.RawSubject),
|
|
pkcs11.NewAttribute(pkcs11.CKA_ISSUER, certificate.RawIssuer),
|
|
pkcs11.NewAttribute(pkcs11.CKA_SERIAL_NUMBER, serial),
|
|
pkcs11.NewAttribute(pkcs11.CKA_ID, keyID),
|
|
pkcs11.NewAttribute(pkcs11.CKA_LABEL, []byte(label)),
|
|
pkcs11.NewAttribute(pkcs11.CKA_VALUE, certificate.Raw),
|
|
}
|
|
|
|
_, err = hs.CreateObject(tmpl)
|
|
if err != nil {
|
|
fmt.Printf("failed to create certificate on HSM: %s\n", err.Error())
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (cfg *HSMConfig) ImportKeyCert(keyFile, certFile string) (crypto.PublicKey, error) {
|
|
hs, err := cfg.getSession()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get session: %s", err)
|
|
}
|
|
defer hs.Close()
|
|
|
|
privKey, pubKey, err := loadKey(keyFile)
|
|
if err != nil {
|
|
return pubKey, err
|
|
}
|
|
|
|
keyID := make([]byte, 4)
|
|
_, err = rand.Read(keyID)
|
|
if err != nil {
|
|
return pubKey, err
|
|
}
|
|
|
|
err = storePubKey(hs, pubKey, keyID, cfg.Label)
|
|
if err != nil {
|
|
fmt.Printf("failed to store public key on HSM: %s\n", err.Error())
|
|
return pubKey, err
|
|
}
|
|
|
|
extractable := true // For now, with SoftHSM, this is fine. In future we need to ask for informed consent!
|
|
|
|
err = storePrivKey(hs, privKey, keyID, cfg.Label, extractable)
|
|
if err != nil {
|
|
fmt.Printf("failed to store private key on HSM: %s\n", err.Error())
|
|
return pubKey, err
|
|
}
|
|
|
|
if strings.Index(filepath.Base(keyFile), "root-") != 0 {
|
|
jsonFile := path.Join(CERT_FILES_PATH, filepath.Base(keyFile))
|
|
jsonFile = strings.ReplaceAll(jsonFile, "-key.pem", ".pkcs11.json")
|
|
contents := fmt.Sprintf(`{"module": %q, "tokenLabel": %q, "pin": %q}`, cfg.Module, cfg.Label, cfg.UserPIN)
|
|
err = os.WriteFile(jsonFile, []byte(contents), 0644)
|
|
if err != nil {
|
|
return pubKey, fmt.Errorf("failed to write '%s' file: %s", jsonFile, err.Error())
|
|
}
|
|
}
|
|
|
|
if certFile != "" {
|
|
cert, err := loadCert(certFile)
|
|
if err != nil {
|
|
return pubKey, err
|
|
}
|
|
|
|
err = storeCertificate(hs, cert, keyID, cfg.Label)
|
|
if err != nil {
|
|
fmt.Printf("failed to store certificate on HSM: %s\n", err.Error())
|
|
return pubKey, err
|
|
}
|
|
}
|
|
|
|
return pubKey, nil
|
|
}
|
|
|
|
func (hs *HSMSession) Close() {
|
|
_ = hs.Context.CloseSession(hs.Handle)
|
|
_ = hs.Context.Logout(hs.Handle)
|
|
}
|
|
|
|
func (hs *HSMSession) CreateObject(tmpl []*pkcs11.Attribute) (pkcs11.ObjectHandle, error) {
|
|
return hs.Context.CreateObject(hs.Handle, tmpl)
|
|
}
|
|
|
|
func (hs *HSMSession) DestroyObject(object pkcs11.ObjectHandle) error {
|
|
return hs.Context.DestroyObject(hs.Handle, object)
|
|
}
|
|
|
|
func (hs *HSMSession) DestroyAllObjects(label string) error {
|
|
tmpl := []*pkcs11.Attribute{
|
|
pkcs11.NewAttribute(pkcs11.CKA_LABEL, []byte(label)),
|
|
}
|
|
|
|
keys, err := hs.FindObjects(tmpl)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to find objects with label='%s': %w", label, err)
|
|
}
|
|
|
|
for _, key := range keys {
|
|
err = hs.DestroyObject(key)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to destroy object '%+v': %w", key, err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (hs *HSMSession) FindObject(tmpl []*pkcs11.Attribute) (pkcs11.ObjectHandle, error) {
|
|
err := hs.Context.FindObjectsInit(hs.Handle, tmpl)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
handles, _, err := hs.Context.FindObjects(hs.Handle, 2)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
err = hs.Context.FindObjectsFinal(hs.Handle)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
if len(handles) == 0 {
|
|
return 0, errors.New("no objects found matching provided template")
|
|
}
|
|
if len(handles) > 1 {
|
|
return 0, fmt.Errorf("too many objects (%d) that match the provided template", len(handles))
|
|
}
|
|
return handles[0], nil
|
|
}
|
|
|
|
func (hs *HSMSession) FindObjects(tmpl []*pkcs11.Attribute) ([]pkcs11.ObjectHandle, error) {
|
|
result := []pkcs11.ObjectHandle{}
|
|
|
|
err := hs.Context.FindObjectsInit(hs.Handle, tmpl)
|
|
if err != nil {
|
|
return result, err
|
|
}
|
|
|
|
for {
|
|
handles, _, err := hs.Context.FindObjects(hs.Handle, 10)
|
|
if err != nil {
|
|
return result, err
|
|
}
|
|
if len(handles) == 0 {
|
|
break
|
|
}
|
|
result = append(result, handles...)
|
|
}
|
|
|
|
err = hs.Context.FindObjectsFinal(hs.Handle)
|
|
if err != nil {
|
|
return result, err
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
func (hs *HSMSession) GenerateKey(mechs []*pkcs11.Mechanism, tmpl []*pkcs11.Attribute) (pkcs11.ObjectHandle, error) {
|
|
return hs.Context.GenerateKey(hs.Handle, mechs, tmpl)
|
|
}
|
|
|
|
func (hs *HSMSession) GetAttributeValue(handle pkcs11.ObjectHandle, tmpl []*pkcs11.Attribute) ([]*pkcs11.Attribute, error) {
|
|
return hs.Context.GetAttributeValue(hs.Handle, handle, tmpl)
|
|
}
|
|
|
|
func (hs *HSMSession) WrapKey(mechs []*pkcs11.Mechanism, wkh pkcs11.ObjectHandle, kh pkcs11.ObjectHandle) ([]byte, error) {
|
|
return hs.Context.WrapKey(hs.Handle, mechs, wkh, kh)
|
|
}
|