Last active
May 22, 2023 01:13
-
-
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 file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 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