Files
Joseph Doherty ce27b63010 [M5] auth: deterministic HMAC fixture test rules out crypto stack
Adds end-to-end byte-equality test against a `.NET reference fixture
captured via the new `MxAsbClient.Probe --dump-deterministic-hmac`
flag. All inputs are pinned (passphrase, prime, generator, private-
key bytes, remote-pub bytes, message number, connection ID, AES IV,
consumer-data + IV bytes), so the test reproduces .NET's exact
output for every crypto step:

1. shared = remote_pub^private_key mod prime —  matches
2. crypto_key = shared || passphrase_utf8 —  matches
3. hmac = HMAC-SHA1(crypto_key, xml_utf8) —  matches
4. aes_key = PBKDF2-SHA1(base64(crypto_key), salt, 1000, 16) — 
5. encrypted_mac = AES-CBC(aes_key, iv=zeros, hmac, PKCS7) — 

This conclusively rules out the entire crypto stack as the source of
the live AuthenticateMe `dispatcher/fault`. Our DH math, HMAC engine,
PBKDF2 derivation, AES-CBC PKCS7, and crypto_key concatenation are
byte-equal to .NET. The remaining live failure must come from one
of: (a) wire-level ConnectionValidator NBFX shape (DataContract field
names, mustUnderstand attribute, namespace), (b) WCF binary message
header (action+to dict pre-pop), or (c) a subtle XmlSerializer quirk
for live values that the hardcoded fixtures don't exercise (Guid
format edge case, base64 line wrapping, ulong text rendering).

Fixture lives at `crates/mxaccess-asb-nettcp/tests/fixtures/
deterministic-hmac/authenticate-me.kv` (KV format, ASCII hex, lines
trim CRLF/LF transparently). The companion `README.md` documents the
capture procedure and the per-step decomposition. The test consumes
the .NET-supplied canonical XML directly from the fixture's
`xml_utf8_b64` so a Rust XML emitter bug would not mask a Rust
crypto bug — XML byte-equality is verified separately by
`mxaccess-asb::xml_canonical::tests` against the `signed-xml/*.xml`
fixtures.

Workspace: 710 unit tests pass (was 709 + 1 new). Clippy clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 19:12:17 -04:00
..

Deterministic HMAC fixture

Pinned input/output triple for the AsbSystemAuthenticator.Sign crypto path, captured from the .NET reference. Used by the Rust parity test in crates/mxaccess-asb-nettcp/tests/deterministic_hmac.rs to assert byte-equality of crypto_key derivation, canonical XML emission, HMAC-SHA1, PBKDF2-SHA1 AES key derivation, and AES-CBC encryption — independent of session randomness (DH private key, remote public key, and AES IV are all pinned to deterministic values so a single cargo test run can reproduce the .NET output).

Capture procedure

dotnet run --project src\MxAsbClient.Probe -c Release -- --dump-deterministic-hmac > capture.txt

The probe's --dump-deterministic-hmac flag (added 2026-05-05) inlines the per-step decomposition of Sign (AsbSystemAuthenticator .cs:62-82):

  1. shared = remote_pub^private_key mod prime (.NET BigInteger.ModPow)
  2. crypto_key = shared || passphrase_utf8
  3. xml = AuthenticateMe.ToXml() with empty MAC + IV
  4. hmac = HMAC-SHA1(crypto_key, utf8(xml))
  5. aes_key = PBKDF2-SHA1(base64(crypto_key), "ArchestrAService", 1000, 16)
  6. encrypted_mac = AES-CBC(aes_key, iv=zeros, hmac, padding=PKCS7)

Step 6 uses an all-zero IV to make the test reproducible — the real wire path uses a random IV per call, but the Rust test bypasses the random IV path by calling the AES primitive directly with the same zero IV.

File format

Plain-ASCII key=value lines, one per line. Hex values are upper-case (matching .NET's Convert.ToHexString). The xml_utf8_b64 field encodes the canonical XML as base64 of the UTF-8 bytes.

Files

  • authenticate-me.kv — fixture for the AuthenticateMe shape with the [XmlType(Namespace="http://asb.contracts.data/20111111")] ConsumerAuthenticationData wrapper.