[F27] mxaccess-asb-nettcp: constant-time DH mod_exp via crypto-bigint::DynResidue
rust / build / test / clippy / fmt (push) Has been cancelled
rust / build / test / clippy / fmt (push) Has been cancelled
Closes F27 per option (b) of its resolve criterion: fixed-width
U2048 DH backend using crypto-bigint's Montgomery-form residue
arithmetic.
New auth.rs::constant_time_mod_exp(base, exp, modulus) wrapper
preserves the BigUint-in-BigUint-out API of the existing byte
helpers; the actual square-and-multiply chain runs in Montgomery
form. Both DH call sites swap to the wrapper:
- AsbAuthenticator::new line 179 (public-key generation)
- crypto_key line 354 (shared-secret derivation)
DH private exponent timing-leak resistance is the goal: the .NET
reference's BigInteger.ModPow is also non-constant-time, so we
were at parity but not at the long-term Rust target. With this
fix the production path no longer leaks the bit-pattern of the
long-lived DH private key through power/timing side channels.
DynResidueParams::new requires an odd modulus (Montgomery form's
only restriction). Production DH primes are always odd
(`MX_ASB_DH_PRIME = 1552...7919` on this host's registry).
CryptoParameters::DEFAULT_PRIME_TEXT — the test-fixture default
inherited from AsbRegistry.cs:66 — ends in 4 (even), which is
mathematically unsound for DH but kept for parity with the .NET
default. For that case the wrapper falls back to BigUint::modpow,
preserving the wire bytes (modular exp is a pure function of
inputs).
Wire-byte parity verified two ways:
1. Unit fixture test
`auth.rs::deterministic_hmac_matches_dotnet_fixture` — byte-equal
to captured .NET output for the full DH → PBKDF2 → AES-CBC chain.
Continues to pass.
2. Live: Connect handshake against the local AVEVA install
completes with apollo:V2 lifetime, proving MxDataProvider
accepts the constant-time-derived public key and the
shared-secret-based AuthenticateMe.
Workspace deps:
- crypto-bigint = "0.5" added to [workspace.dependencies] and
mxaccess-asb-nettcp/Cargo.toml.
- num-bigint retained for decimal-string parsing + .NET-LE byte
conversion (crypto-bigint has neither).
Closes the "review.md MAJOR finding" originally flagged at
design/30-crate-topology.md:269-274.
design/followups.md: F27 moved to Resolved.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+9
-5
@@ -47,14 +47,18 @@ cbc = { version = "0.1", features = ["std"] }
|
||||
pbkdf2 = { version = "0.12", default-features = false, features = ["hmac"] }
|
||||
flate2 = "1"
|
||||
rand = "0.8"
|
||||
# DH bigint. NOTE: num-bigint::modpow is not constant-time. The DH private
|
||||
# exponent is long-lived (AsbSystemAuthenticator.cs:153-166); .NET BigInteger
|
||||
# also isn't constant-time, so we are at parity with the reference. Tracked
|
||||
# as F27 to swap to crypto-bigint::BoxedUint once that crate exposes a stable
|
||||
# pow_mod over heap-allocated values — design/30-crate-topology.md:269-274.
|
||||
# DH bigint. F27 (closed): constant-time `mod_exp` lives in
|
||||
# `crypto-bigint::DynResidue`; we keep `num-bigint` for decimal parsing
|
||||
# + .NET-LE byte conversion (crypto-bigint has no decimal-string parser
|
||||
# and no built-in .NET `BigInteger.ToByteArray()` ordering). The
|
||||
# `auth.rs::constant_time_mod_exp` wrapper bridges both: parse via
|
||||
# num-bigint, compute via crypto-bigint Uint<32> + DynResidue, return
|
||||
# back through num-bigint for downstream byte slicing. Wire bytes
|
||||
# stay identical so existing fixtures pin parity.
|
||||
num-bigint = "0.4"
|
||||
num-traits = "0.2"
|
||||
num-integer = "0.1"
|
||||
crypto-bigint = "0.5"
|
||||
quick-xml = "0.36"
|
||||
tokio-util = { version = "0.7", features = ["codec"] }
|
||||
zeroize = { version = "1", features = ["zeroize_derive"] }
|
||||
|
||||
Reference in New Issue
Block a user