{"record":{"id":"d600c4d7e7a7ab00","repo":"slackhq/nebula","slug":"got-an-empty-secret-key","errorCode":null,"errorMessage":"got an empty secret key","messagePattern":"got an empty secret key","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkclient/pkclient_cgo.go","lineNumber":196,"sourceCode":"\t\tpkcs11.NewAttribute(pkcs11.CKA_EXTRACTABLE, true),\n\t\tpkcs11.NewAttribute(pkcs11.CKA_ENCRYPT, true),\n\t\tpkcs11.NewAttribute(pkcs11.CKA_DECRYPT, true),\n\t\tpkcs11.NewAttribute(pkcs11.CKA_WRAP, true),\n\t\tpkcs11.NewAttribute(pkcs11.CKA_UNWRAP, true),\n\t\tpkcs11.NewAttribute(pkcs11.CKA_VALUE_LEN, NoiseKeySize),\n\t}\n\n\t// Set up the parameters which include the peer's public key\n\tecdhParams := pkcs11.NewECDH1DeriveParams(pkcs11.CKD_NULL, nil, peerPubKey)\n\tmech := pkcs11.NewMechanism(pkcs11.CKM_ECDH1_DERIVE, ecdhParams)\n\tsk := p11.PrivateKey(c.privKeyObj)\n\n\ttmpKey, err := sk.Derive(*mech, attrTemplate)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif tmpKey == nil || len(tmpKey) == 0 {\n\t\treturn nil, fmt.Errorf(\"got an empty secret key\")\n\t}\n\tsecret := make([]byte, NoiseKeySize)\n\tcopy(secret[:], tmpKey[:NoiseKeySize])\n\treturn secret, nil\n}\n\nfunc (c *PKClient) GetPubKey() ([]byte, error) {\n\td, err := c.privKeyObj.Attribute(pkcs11.CKA_PUBLIC_KEY_INFO)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif d != nil && len(d) > 0 {\n\t\treturn formatPubkeyFromPublicKeyInfoAttr(d)\n\t}\n\tc.pubKeyObj, err = c.findDeriveKey(c.id, c.label, false)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"pkcs11 module gave us a nil CKA_PUBLIC_KEY_INFO, and looking up the public key also failed: %w\", err)\n\t}","sourceCodeStart":178,"sourceCodeEnd":214,"githubUrl":"https://github.com/slackhq/nebula/blob/dd8f660c0ac37903ec4080ca4d3c861ba9342ceb/pkclient/pkclient_cgo.go#L178-L214","documentation":"DeriveNoise performs an ECDH derive via the HSM and requires at least NoiseKeySize bytes of derived key material. If the PKCS#11 C_Derive call returns a nil or zero-length secret key, this error is returned instead of silently producing a useless Noise key.","triggerScenarios":"Calling DeriveNoise (directly or via Test) when the HSM's derive operation succeeds but returns an empty/nil key object — e.g. a mechanism the token claims to support but produces no output, or a malformed peer public key attribute.","commonSituations":"HSM firmware bug or misbehaving PKCS#11 module returning success with no key; peer public key bytes corrupted before the call; token in a bad state after login/logout races.","solutions":["Validate the peer public key (65-byte uncompressed EC point) before calling DeriveNoise.","Retry the derive once; transient token states can produce empty outputs.","Update or replace the HSM vendor PKCS#11 module if empty derives reproduce consistently.","Run pkclient.Test() against the token to verify the derive path works end-to-end."],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// Validate peer public key length before DeriveNoise\nfunc validPeerKey(pub []byte) bool { return len(pub) == 65 && pub[0] == 0x04 }","typeGuard":"func isUsableSecret(k []byte) bool { return len(k) >= NoiseKeySize }","tryCatchPattern":"secret, err := client.DeriveNoise(peerPub)\nif err != nil {\n  if strings.Contains(err.Error(), \"got an empty secret key\") {\n    // transient token state: one retry after relogin\n    client.Close()\n    if c2, e := pkclient.New(hsmPath, slot, pin, id, label); e == nil { client = c2; secret, err = client.DeriveNoise(peerPub) }\n  }\n  if err != nil { return err }\n}","preventionTips":["Always pass a full 65-byte uncompressed EC point as the peer key.","Run pkclient.Test() against the token before relying on derive operations.","Keep HSM vendor PKCS#11 modules updated; empty derives are often module bugs.","Handle login/logout races so sessions don't degrade mid-operation."],"tags":["pkcs11","hsm","ecdh","derive"],"backgroundTag":"hsm-empty-derive-output","analyzedSha":"dd8f660c0ac37903ec4080ca4d3c861ba9342ceb","analyzedAt":"2026-09-03T11:13:55.444Z","contentChangedAt":"2026-09-03T11:13:55.444Z","schemaVersion":2},"datasetVersion":"2026-09-10T17:17:09.494Z"}