{"record":{"id":"ed3e276a11bc6d90","repo":"slackhq/nebula","slug":"unable-to-unmarshal-pubkey-w-ed3e27","errorCode":null,"errorMessage":"unable to unmarshal pubkey: %w","messagePattern":"unable to unmarshal pubkey: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"noiseutil/pkcs11.go","lineNumber":36,"sourceCode":"}\n\nfunc newNISTP11Curve(name string, curve ecdh.Curve, byteLen int) nistP11Curve {\n\treturn nistP11Curve{\n\t\tnewNISTCurve(name, curve, byteLen),\n\t}\n}\n\nfunc (c nistP11Curve) DH(privkey, pubkey []byte) ([]byte, error) {\n\t//for this function \"privkey\" is actually a pkcs11 URI\n\tpkStr := string(privkey)\n\n\t//to set up a handshake, we need to also do non-pkcs11-DH. Handle that here.\n\tif !strings.HasPrefix(pkStr, \"pkcs11:\") {\n\t\treturn DHP256.DH(privkey, pubkey)\n\t}\n\tecdhPubKey, err := c.curve.NewPublicKey(pubkey)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"unable to unmarshal pubkey: %w\", err)\n\t}\n\n\t//this is not the most performant way to do this (a long-lived client would be better)\n\t//but, it works, and helps avoid problems with stale sessions and HSMs used by multiple users.\n\tclient, err := pkclient.FromUrl(pkStr)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer func(client *pkclient.PKClient) {\n\t\t_ = client.Close()\n\t}(client)\n\n\treturn client.DeriveNoise(ecdhPubKey.Bytes())\n}\n","sourceCodeStart":18,"sourceCodeEnd":51,"githubUrl":"https://github.com/slackhq/nebula/blob/dd8f660c0ac37903ec4080ca4d3c861ba9342ceb/noiseutil/pkcs11.go#L18-L51","documentation":"This error is returned by the pkcs11 curve's DH method when the peer public key fails c.curve.NewPublicKey parsing. Note the method first checks if the local private key is a 'pkcs11:' URI; if not it delegates to DHP256.DH, so this specific error is only reached when a pkcs11: key IS in use and the remote public key bytes are invalid for the curve. The underlying cause is wrapped with %w.","triggerScenarios":"Performing a Noise handshake where the local private key is a 'pkcs11:' URI (HSM-backed) and the remote peer's public key is empty, wrong-length, or not a valid EC point on the configured curve.","commonSituations":"HSM-backed handshake where the peer's fetched public key was corrupted, fetched from the wrong slot/object, or the peer is on a different curve; stale certificate/key material deployed alongside an HSM migration.","solutions":["Verify the peer's public key was read correctly from its source (cert/slot) and matches the expected curve","Confirm both peers use the same curve despite the HSM path","Log the wrapped cause (errors.Unwrap) to distinguish length vs point-validity errors","If the pubkey came from a pkcs11 object listing, re-export it and compare byte length against the curve's expected point size"],"exampleFix":"// before: trusting pubkey bytes straight from an external source\nres, err := pkcs11Curve.DH(pkcs11URI, remotePub)\n// after: sanity-check before DH\nif len(remotePub) != 65 { // P-256 uncompressed point\n    return nil, fmt.Errorf(\"remote pubkey has %d bytes, want 65\", len(remotePub))\n}\nres, err := pkcs11Curve.DH(pkcs11URI, remotePub)","handlingStrategy":"validation","validationCode":"func dhGuard(pkStr string, pub []byte, curveLen int) error {\n    if len(pub) != curveLen {\n        return fmt.Errorf(\"remote pubkey %d bytes, want %d\", len(pub), curveLen)\n    }\n    if strings.HasPrefix(pkStr, \"pkcs11:\") {\n        u, err := url.Parse(pkStr)\n        if err != nil || u.Scheme != \"pkcs11\" {\n            return fmt.Errorf(\"malformed pkcs11 URI\")\n        }\n    }\n    return nil\n}","typeGuard":"func isPkcs11PubError(err error) bool {\n    return err != nil && strings.Contains(err.Error(), \"unable to unmarshal pubkey\")\n}","tryCatchPattern":"res, err := pkcs11Curve.DH(pkStr, pub)\nif err != nil {\n    if isPkcs11PubError(err) {\n        return fmt.Errorf(\"remote key invalid for pkcs11 DH (verify peer cert/slot export): %w\", err)\n    }\n    return err\n}","preventionTips":["Verify HSM-exported peer public keys byte-for-byte against the peer certificate","Keep both endpoints on the same curve even when one side is HSM-backed","Re-export keys after HSM slot/object changes","Length-check remote pubkeys before invoking DH"],"tags":["go","noise-protocol","pkcs11","hsm","ecdh"],"backgroundTag":"invalid-public-key","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"}