Use RandomBytes for fake record envelope; reject trailing bytes in DeserializeConfiguration#121
Conversation
Extend TestGetFakeRecord to verify the envelope is not all-zero. Add TestDeserializeConfiguration_TrailingBytes next to the existing deserialization tests. Add TestGenerateKE2_FakeRecord to server_test.go as a smoke test across all cipher suites. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
GetFakeRecord: use RandomBytes for the envelope instead of zero-filled make(). The all-zero envelope was trivially distinguishable from real envelopes (random nonce + MAC), so fake records did not structurally match real ones. DeserializeConfiguration: check that all input bytes are consumed after decoding the context vector. Previously the offset return from DecodeVector was discarded. This matches the strict parsing already used in DecodeServerKeyMaterial (skm.go:184). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR hardens OPAQUE’s “fake record” generation and configuration deserialization to better match real-world encodings and reduce distinguishability/leniency.
Changes:
- Make
GetFakeRecordgenerate a random envelope (instead of zero-filled) to avoid trivially recognizable fake records at rest. - Make
DeserializeConfigurationreject any trailing bytes after the context vector for strict parsing. - Add/extend tests covering fake record KE2 generation, non-zero fake envelopes, and trailing-byte rejection in configuration parsing.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
opaque.go |
Randomizes fake record envelope; enforces no trailing bytes in configuration deserialization. |
tests/opaque_test.go |
Adds trailing-bytes test for DeserializeConfiguration; strengthens TestGetFakeRecord to ensure envelope isn’t all-zero. |
tests/server_test.go |
Adds TestGenerateKE2_FakeRecord to ensure KE2 generation works with fake records across suites. |
Comments suppressed due to low confidence (1)
opaque.go:205
- DeserializeConfiguration assigns Context directly from the DecodeVector slice, which aliases the caller-provided
encodedbuffer. This makes the returned Configuration mutable via external modifications toencodedand can lead to surprising behavior. Consider cloning the decoded context bytes (e.g., viaslices.Clone/append([]byte(nil), ctx...)) before storing them in the Configuration.
c := &Configuration{
OPRF: Group(encoded[0]),
AKE: Group(encoded[1]),
KSF: ksf.Identifier(encoded[2]),
KDF: crypto.Hash(encoded[3]),
MAC: crypto.Hash(encoded[4]),
Hash: crypto.Hash(encoded[5]),
Context: ctx,
}
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if len(ke2.Serialize()) != conf.internal.Sizes.KE2 { | ||
| t.Fatalf("KE2 size mismatch: got %d, want %d", | ||
| len(ke2.Serialize()), conf.internal.Sizes.KE2) |
There was a problem hiding this comment.
ke2.Serialize() is called twice in the KE2 size mismatch check, which can duplicate work and allocations if Serialize builds a new slice each call. Store the serialized bytes in a local variable and reuse it for both len(...) and the error message.
| if len(ke2.Serialize()) != conf.internal.Sizes.KE2 { | |
| t.Fatalf("KE2 size mismatch: got %d, want %d", | |
| len(ke2.Serialize()), conf.internal.Sizes.KE2) | |
| serializedKE2 := ke2.Serialize() | |
| if len(serializedKE2) != conf.internal.Sizes.KE2 { | |
| t.Fatalf("KE2 size mismatch: got %d, want %d", | |
| len(serializedKE2), conf.internal.Sizes.KE2) |
|
Hi @pquerna, thank you for your suggestions! The RFC states that the fake envelope should be an all 0 byte string, c.f. 6.3.2.2 towards the end. So I'd keep it like that. Have you encountered an incompatibility with other implementations? Regarding the configuration deserialization, I see your point, and support the strict defensive approach. Have you seen a scenario where this has led to some risk? If that's ok with you, I'd keep the deserialization patch and its test. Also, please rebase and cryptographically sign your commits, and sign them off following DCO. |
GetFakeRecordusesmake([]byte, conf.Sizes.Envelope)for the envelope, producing all zeros. Real envelopes contain a random nonce + MAC tag, so fake records are distinguishable at rest. The fix is the sameRandomBytescall already used forMaskingKeyon the line above.Separately,
DeserializeConfigurationdiscards the offset fromDecodeVector(_), silently accepting trailing bytes.DecodeServerKeyMaterialinskm.goalready checks this; this brings the config parser in line.Neither change affects on-wire behavior (the masking key is random, so the XOR output is indistinguishable either way).
Tests
First commit adds tests, second commit adds fixes.
TestGetFakeRecordextended -- envelope is not all-zeroTestDeserializeConfiguration_TrailingBytes-- rejects trailing bytes with empty and non-empty contextTestGenerateKE2_FakeRecord-- fake records still produce valid KE2