Skip to content

Instantly share code, notes, and snippets.

@jakecraige
Last active May 22, 2023 01:13
Show Gist options
  • Save jakecraige/2e57343ce00b059ea10f90669c7602bf to your computer and use it in GitHub Desktop.
Save jakecraige/2e57343ce00b059ea10f90669c7602bf to your computer and use it in GitHub Desktop.
Example of the differences between deriving a non-sensitive ECDH key with `CKM_ECDH1_DERIVE` with SoftHSM2, AWS CloudHSM and YubiHSM2's PKCS#11 interface
/*
* This script shows an example of how with PKCS#11 ECDH1 key derivation and how it differs when
* communicating with SoftHSM2 vs AWS CloudHSM vs YubiHSM2. This seems to boil down to how they treat
* the `CKA_SENSITIVE` attribute on the derived key.
* - SoftHSM2 does what you expect, if you say it is sensitive it won't allow exporting. If you say
* it's not it will.
* - CloudHSM errors when you attempt to mark the derived key as not-sensitive. It behaves as
* expected when you say it is. AWS docs seem to indicate that the derived key is available on the
* "client" but it's unclear what client they are referring to, and it doesn't seem to be
* retrievable with GetAttributeValue. https://docs.aws.amazon.com/en_pv/cloudhsm/latest/userguide/KnownIssues.html#ki-pkcs11-sdk
* - YubiHSM2 ignores the value and it is never considered sensitive and is always retrievable.
*
*
* ## SoftHSM2 Output
* Temporary key created
* Temporary key attributes:
* extractable = [0]
* sensitive = [1]
*
* Derived key created
* Derived key attributes:
* extractable = [1]
* sensitive = [0]
* value = [166 153 136 172 84 248 183 208 201 166 148 226 167 55 185 73 114 4 215 100 135 84 61 105 32 22 152 37 0 235 72 51]
*
* ## CloudHSM Output
* Temporary key created
* Temporary key attributes:
* extractable = [0]
* sensitive = [1]
*
*
* C_CreateObject failed with error CKR_ATTRIBUTE_VALUE_INVALID : 0x00000013
*
* C_DeriveKey failed with error CKR_ATTRIBUTE_VALUE_INVALID : 0x00000013
* panic: pkcs11: 0x13: CKR_ATTRIBUTE_VALUE_INVALID
*
* goroutine 1 [running]:
* main.main()
* /app/pkcs.go:132 +0x128d
* exit status 2
*
* ## YubiHSM2 Output
* Temporary key created
* Temporary key attributes:
* extractable = [0]
* sensitive = [1]
*
* Derived key created
* Derived key attributes:
* extractable = [1]
* sensitive = [0]
* value = [66 236 110 248 237 19 93 146 10 223 104 25 87 248 2 95 27 39 233 245 140 200 187 221 241 26 38 143 89 251 4 13]
*/
package main
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"encoding/asn1"
"fmt"
"github.com/miekg/pkcs11"
)
var (
libPath = "/usr/local/lib/softhsm/libsofthsm2.so"
tokenPin = "1234"
// libPath = "/opt/cloudhsm/lib/libcloudhsm_pkcs11.so"
// tokenPin = "user:password"
// libPath = "/Users/jakecraige/Downloads/yubihsm2-sdk/lib/pkcs11/yubihsm_pkcs11.dylib"
// tokenPin = "0001password"
attributeMap = map[uint]string{
pkcs11.CKA_VALUE: "value",
pkcs11.CKA_EXTRACTABLE: "extractable",
pkcs11.CKA_SENSITIVE: "sensitive",
}
)
func main() {
p := pkcs11.New(libPath)
err := p.Initialize()
if err != nil {
panic(err)
}
defer p.Destroy()
defer p.Finalize()
slots, err := p.GetSlotList(true)
if err != nil {
panic(err)
}
session, err := p.OpenSession(slots[0], pkcs11.CKF_SERIAL_SESSION|pkcs11.CKF_RW_SESSION)
if err != nil {
panic(err)
}
defer p.CloseSession(session)
err = p.Login(session, pkcs11.CKU_USER, tokenPin)
if err != nil {
panic(err)
}
defer p.Logout(session)
fmt.Println("Session initialized and logged in")
fmt.Println()
// P-256, from: https://github.com/ThalesIgnite/crypto11/blob/master/ecdsa.go#L84
curveParams := mustMarshal(asn1.ObjectIdentifier{1, 2, 840, 10045, 3, 1, 7})
public := []*pkcs11.Attribute{
pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_PUBLIC_KEY),
pkcs11.NewAttribute(pkcs11.CKA_KEY_TYPE, pkcs11.CKK_EC),
pkcs11.NewAttribute(pkcs11.CKA_VERIFY, true),
pkcs11.NewAttribute(pkcs11.CKA_ECDSA_PARAMS, curveParams),
}
private := []*pkcs11.Attribute{
pkcs11.NewAttribute(pkcs11.CKA_SIGN, true),
pkcs11.NewAttribute(pkcs11.CKA_DERIVE, true),
pkcs11.NewAttribute(pkcs11.CKA_SENSITIVE, true),
pkcs11.NewAttribute(pkcs11.CKA_EXTRACTABLE, false),
}
mech := []*pkcs11.Mechanism{pkcs11.NewMechanism(pkcs11.CKM_ECDSA_KEY_PAIR_GEN, nil)}
_, keyHandle, err := p.GenerateKeyPair(session, mech, public, private)
if err != nil {
panic(err)
}
fmt.Println("Temporary key created")
defer func() {
// cleanup afterwards
_ = p.DestroyObject(session, keyHandle)
}()
attrs, err := p.GetAttributeValue(session, keyHandle, []*pkcs11.Attribute{
pkcs11.NewAttribute(pkcs11.CKA_EXTRACTABLE, nil),
pkcs11.NewAttribute(pkcs11.CKA_SENSITIVE, nil),
})
if err != nil {
panic(err)
}
fmt.Println("Temporary key attributes:")
for _, attr := range attrs {
printAttr(attr)
}
fmt.Println()
// Generate local key to do exchange with
localKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
panic(err)
}
encodedPub := make([]byte, 65)
encodedPub[0] = 0x04
copy(encodedPub[1:], leftPad(localKey.X.Bytes(), 32))
copy(encodedPub[33:], leftPad(localKey.Y.Bytes(), 32))
// Do an ECDH derive of a generic 32 byte secret
ecdhParams := pkcs11.NewECDH1DeriveParams(pkcs11.CKD_NULL, nil, encodedPub)
ecdhMech := pkcs11.NewMechanism(pkcs11.CKM_ECDH1_DERIVE, ecdhParams)
derivedHandle, err := p.DeriveKey(session, []*pkcs11.Mechanism{ecdhMech}, keyHandle, []*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),
// SoftHSM2:
// If true, cannot get value.
// If false, can retrieve value.
// YubiHSM2:
// If true, can retrieve value. (it ignores the sensitive param as seen in log output of the attributes)
// If false, can retrieve value.
// CloudHSM:
// If true, receive error on retrieval as expected: `C_GetAttributeValue failed with error CKR_ATTRIBUTE_SENSITIVE : 0x00000011`
// If false, errors on Derive: `C_CreateObject failed with error CKR_ATTRIBUTE_VALUE_INVALID : 0x00000013, C_DeriveKey failed with error CKR_ATTRIBUTE_VALUE_INVALID : 0x00000013`
pkcs11.NewAttribute(pkcs11.CKA_SENSITIVE, true),
pkcs11.NewAttribute(pkcs11.CKA_EXTRACTABLE, true),
pkcs11.NewAttribute(pkcs11.CKA_VALUE_LEN, 32),
})
if err != nil {
panic(err)
}
fmt.Println("Derived key created")
attrs, err = p.GetAttributeValue(session, derivedHandle, []*pkcs11.Attribute{
pkcs11.NewAttribute(pkcs11.CKA_EXTRACTABLE, nil),
pkcs11.NewAttribute(pkcs11.CKA_SENSITIVE, nil),
pkcs11.NewAttribute(pkcs11.CKA_VALUE, nil),
})
if err != nil {
panic(err)
}
fmt.Println("Derived key attributes:")
for _, attr := range attrs {
printAttr(attr)
}
}
func mustMarshal(val interface{}) []byte {
if b, err := asn1.Marshal(val); err != nil {
panic(err)
} else {
return b
}
}
func leftPad(bytes []byte, targetLen uint) []byte {
out := make([]byte, targetLen)
paddingOffset := int(targetLen) - len(bytes)
for i, b := range bytes {
out[paddingOffset+i] = b
}
return out
}
func printAttr(attr *pkcs11.Attribute) {
name := attributeMap[attr.Type]
fmt.Println(" ", name, "=", attr.Value)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment