slackhq/nebula · error
got an empty secret key
Error message
got an empty secret key
What it means
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.
Source
Thrown at pkclient/pkclient_cgo.go:196
pkcs11.NewAttribute(pkcs11.CKA_EXTRACTABLE, true),
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_VALUE_LEN, NoiseKeySize),
}
// Set up the parameters which include the peer's public key
ecdhParams := pkcs11.NewECDH1DeriveParams(pkcs11.CKD_NULL, nil, peerPubKey)
mech := pkcs11.NewMechanism(pkcs11.CKM_ECDH1_DERIVE, ecdhParams)
sk := p11.PrivateKey(c.privKeyObj)
tmpKey, err := sk.Derive(*mech, attrTemplate)
if err != nil {
return nil, err
}
if tmpKey == nil || len(tmpKey) == 0 {
return nil, fmt.Errorf("got an empty secret key")
}
secret := make([]byte, NoiseKeySize)
copy(secret[:], tmpKey[:NoiseKeySize])
return secret, nil
}
func (c *PKClient) GetPubKey() ([]byte, error) {
d, err := c.privKeyObj.Attribute(pkcs11.CKA_PUBLIC_KEY_INFO)
if err != nil {
return nil, err
}
if d != nil && len(d) > 0 {
return formatPubkeyFromPublicKeyInfoAttr(d)
}
c.pubKeyObj, err = c.findDeriveKey(c.id, c.label, false)
if err != nil {
return nil, fmt.Errorf("pkcs11 module gave us a nil CKA_PUBLIC_KEY_INFO, and looking up the public key also failed: %w", err)
}View on GitHub (pinned to dd8f660c0a)
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.
Defensive patterns
Strategy: retry
Validate before calling
// Validate peer public key length before DeriveNoise
func validPeerKey(pub []byte) bool { return len(pub) == 65 && pub[0] == 0x04 } Type guard
func isUsableSecret(k []byte) bool { return len(k) >= NoiseKeySize } Try / catch
secret, err := client.DeriveNoise(peerPub)
if err != nil {
if strings.Contains(err.Error(), "got an empty secret key") {
// transient token state: one retry after relogin
client.Close()
if c2, e := pkclient.New(hsmPath, slot, pin, id, label); e == nil { client = c2; secret, err = client.DeriveNoise(peerPub) }
}
if err != nil { return err }
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- unable to unmarshal pubkey: %w
- got a key of %d bytes, expected %d
- notImplemented
- error while creating PKCS#11 client: %w
- error while getting public key with PKCS#11: %w
AI-assisted analysis of slackhq/nebula@dd8f660c0a (2026-09-03).
Data as JSON: /api/errors/d600c4d7e7a7ab00.
Report an issue: GitHub.