{"record":{"id":"e96fa0a2fd53b6b7","repo":"OpenNHP/opennhp","slug":"extractinitiatorstaticpubkey-gmsm-scheme-expected","errorCode":null,"errorMessage":"extractInitiatorStaticPubKey: gmsm scheme expected %d-byte pubkey, got %d","messagePattern":"extractInitiatorStaticPubKey: gmsm scheme expected (.+?)-byte pubkey, got (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"nhp/core/responder.go","lineNumber":196,"sourceCode":"\t// however many bytes the AEAD decrypted, then the caller would\n\t// either truncate them or hash trailing zero-padding).\n\t//\n\t// Validate the length explicitly before returning so future\n\t// breakage manifests as an error here, not as cookie failures\n\t// further down. Pass nil for the dst so Open allocates exactly\n\t// the right size.\n\tpeerPk, err := aead.Open(nil, header.NonceBytes(), header.StaticBytes(), chainHash.Sum(nil))\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"extractInitiatorStaticPubKey: open: %w\", err)\n\t}\n\tswitch header.CipherScheme() {\n\tcase common.CIPHER_SCHEME_CURVE:\n\t\tif len(peerPk) != PublicKeySize {\n\t\t\treturn nil, fmt.Errorf(\"extractInitiatorStaticPubKey: curve scheme expected %d-byte pubkey, got %d\", PublicKeySize, len(peerPk))\n\t\t}\n\tcase common.CIPHER_SCHEME_GMSM:\n\t\tif len(peerPk) != PublicKeySizeEx {\n\t\t\treturn nil, fmt.Errorf(\"extractInitiatorStaticPubKey: gmsm scheme expected %d-byte pubkey, got %d\", PublicKeySizeEx, len(peerPk))\n\t\t}\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"extractInitiatorStaticPubKey: unknown cipher scheme %d (pubkey length %d)\", header.CipherScheme(), len(peerPk))\n\t}\n\treturn peerPk, nil\n}\n\ntype ResponderScheme interface {\n\tCreatePacketParserData(d *Device, pd *PacketData) (ppd *PacketParserData, err error)\n\tDerivePacketParserDataFromPrevAssemblerData(mad *MsgAssemblerData, pkt *Packet, initTime int64) (ppd *PacketParserData)\n\tvalidatePeer(d *Device, ppd *PacketParserData) (err error)\n\tdecryptBody(d *Device, ppd *PacketParserData) (err error)\n}\n\ntype CookieStore struct {\n\tCurrCookie     [CookieSize]byte\n\tPrevCookie     [CookieSize]byte\n\tLastCookieTime int64","sourceCodeStart":178,"sourceCodeEnd":214,"githubUrl":"https://github.com/OpenNHP/opennhp/blob/6e04ca5ff03222a699c24205cd4bf8fee9af7ffe/nhp/core/responder.go#L178-L214","documentation":"Mirror of the CURVE case: after AEAD-opening the static field, the plaintext must be exactly PublicKeySizeEx (65 bytes for SM2) when the header declares CIPHER_SCHEME_GMSM. A different length means the decrypted bytes are not an SM2 public key — typically the initiator encrypted a Curve25519 key while marking the header GMSM, or a nonstandard plaintext length was produced.","triggerScenarios":"Agent built with curve keys but configured/flagged to use the GMSM scheme; custom cipher implementations whose ciphertext decrypts to a non-65-byte payload; sender/receiver version skew where PublicKeySizeEx changed.","commonSituations":"Deployments where agents run --curve keygen output but the packet header claims GMSM; forks with an additional scheme reusing the GMSM constant; config templates rendering mismatched key material into agent config.","solutions":["Verify the agent's keypair type (curve vs sm2 from keygen) matches the cipher scheme stamped into outgoing headers.","Regenerate agent keys with the matching keygen flag (./nhp-agentd keygen --sm2 for GMSM) and redeploy.","Keep agent and server on the same release so PublicKeySizeEx and the scheme table agree.","Add a startup self-check on the agent asserting len(ownPublicKey) == expected size for its configured scheme."],"exampleFix":"// before\n./nhp-agentd keygen --curve   # 32-byte key, but header says GMSM\n// after\n./nhp-agentd keygen --sm2     # 65-byte key matching CIPHER_SCHEME_GMSM","handlingStrategy":"validation","validationCode":"if cfg.CipherScheme == common.CIPHER_SCHEME_GMSM && len(agentPublicKey) != core.PublicKeySizeEx {\n\treturn fmt.Errorf(\"GMSM scheme requires %d-byte SM2 key, agent key is %d bytes\", core.PublicKeySizeEx, len(agentPublicKey))\n}","typeGuard":"func isSm2Key(pk []byte) bool { return len(pk) == core.PublicKeySizeEx }","tryCatchPattern":"if err := validateKeyAgainstScheme(cfg.Scheme, cfg.PrivateKey); err != nil {\n\tlog.Fatal(err) // misconfiguration — fail at startup, not per packet\n}","preventionTips":["Generate keys with the keygen flag matching your scheme (--sm2 for GMSM).","Validate key length at daemon startup against the configured scheme.","Render config templates with the scheme-consistent key fields.","Pin agent and server to the same version."],"tags":["go","protocol","key-size","sm2","scheme-mismatch"],"backgroundTag":"unexpected-response-shape","analyzedSha":"6e04ca5ff03222a699c24205cd4bf8fee9af7ffe","analyzedAt":"2026-09-07T15:44:59.941Z","contentChangedAt":"2026-09-07T15:44:59.941Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}