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