The `hmac` field in `auth.proto` (line 25) is declared as `bytes hmac = 3;` with the comment
"Truncated HMAC-SHA256 (12 bytes)". That length is documented but carries no proto-level enforcement
and no instruction to receivers about what to do when the field is shorter or absent.
In proto3, an absent field and an explicitly empty field both deserialize as a zero-length byte slice.
A malformed or adversarial client can send an `Auth` message with `hmac = bytes{}` — valid on
the wire — and the Node receives an empty slice. Nothing in the proto file or its comments requires
the Node to check `len(hmac) == 12` before invoking the HMAC comparison routine. Language
implementations vary: Go's `hmac.Equal` returns false on a length mismatch, but a Node that
checks `bytes.HasPrefix(expected, received)` or compares only the first `len(received)` bytes
would accept any one-byte HMAC that happens to match the leading byte of the correct tag (probability
1/256 per attempt). The proto spec's silence makes this a per-implementation lottery rather than a
protocol guarantee.
The issue is structurally identical to the `client_id` length problem in #13, but more
security-critical: `hmac` is the sole proof of PSK possession, so any comparison that does not
first assert `len(hmac) == 12` weakens authentication in proportion to the implementation's
deviation from standard constant-time equality.
Add `// Receivers must assert len(hmac) == 12 and reject the Auth before any HMAC comparison`
alongside the `hmac` field comment, or apply `(validate.rules).bytes.len = 12` via
protoc-gen-validate, matching the approach recommended for `client_id` in #13.
The `hmac` field in `auth.proto` (line 25) is declared as `bytes hmac = 3;` with the comment
"Truncated HMAC-SHA256 (12 bytes)". That length is documented but carries no proto-level enforcement
and no instruction to receivers about what to do when the field is shorter or absent.
In proto3, an absent field and an explicitly empty field both deserialize as a zero-length byte slice.
A malformed or adversarial client can send an `Auth` message with `hmac = bytes{}` — valid on
the wire — and the Node receives an empty slice. Nothing in the proto file or its comments requires
the Node to check `len(hmac) == 12` before invoking the HMAC comparison routine. Language
implementations vary: Go's `hmac.Equal` returns false on a length mismatch, but a Node that
checks `bytes.HasPrefix(expected, received)` or compares only the first `len(received)` bytes
would accept any one-byte HMAC that happens to match the leading byte of the correct tag (probability
1/256 per attempt). The proto spec's silence makes this a per-implementation lottery rather than a
protocol guarantee.
The issue is structurally identical to the `client_id` length problem in #13, but more
security-critical: `hmac` is the sole proof of PSK possession, so any comparison that does not
first assert `len(hmac) == 12` weakens authentication in proportion to the implementation's
deviation from standard constant-time equality.
Add `// Receivers must assert len(hmac) == 12 and reject the Auth before any HMAC comparison`
alongside the `hmac` field comment, or apply `(validate.rules).bytes.len = 12` via
protoc-gen-validate, matching the approach recommended for `client_id` in #13.