Files
mxaccess/rust/Cargo.toml
T
Joseph Doherty 9496322712
rust / build / test / clippy / fmt (push) Has been cancelled
[F27] mxaccess-asb-nettcp: constant-time DH mod_exp via crypto-bigint::DynResidue
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>
2026-05-06 03:16:33 -04:00

84 lines
2.8 KiB
TOML

[workspace]
resolver = "3"
members = [
"crates/mxaccess-codec",
"crates/mxaccess-galaxy",
"crates/mxaccess-rpc",
"crates/mxaccess-callback",
"crates/mxaccess-nmx",
"crates/mxaccess-asb-nettcp",
"crates/mxaccess-asb",
"crates/mxaccess",
"crates/mxaccess-compat",
]
[workspace.package]
version = "0.0.0"
edition = "2024"
license = "MIT"
repository = "https://github.com/<org>/mxaccess"
rust-version = "1.85"
authors = ["Joseph Doherty <dohejw01@gmail.com>"]
# Workspace-level dependency pins. Crates opt in via `dep = { workspace = true }`.
# M0 stubs use minimal deps; the full pinned set per design/30-crate-topology.md
# will be uncommented as M1+ implementation lands.
[workspace.dependencies]
thiserror = "2"
tracing = "0.1"
async-trait = "0.1"
futures-util = "0.3"
bytes = "1"
byteorder = "1"
tokio = { version = "1", features = ["net", "io-util", "rt-multi-thread", "sync", "time", "macros"] }
# M5 ASB transport (F19). Crypto crates target the digest 0.10 / cipher 0.4
# generation (the line that hmac 0.12, md-5 0.10, sha1 0.10, sha2 0.10,
# aes 0.8, cbc 0.1, pbkdf2 0.12 all share). mxaccess-rpc is already on this
# generation (crates/mxaccess-rpc/Cargo.toml:13-18); M5 sticks with it for
# resolved-graph coherence. The design doc at design/30-crate-topology.md:251-289
# prescribed the 0.11/0.5 generation but the rpc crate landed earlier on the
# 0.10/0.4 line — when those two diverge, the implementation is canonical.
hmac = "0.12"
md-5 = "0.10"
sha1 = "0.10"
sha2 = "0.10"
aes = "0.8"
cbc = { version = "0.1", features = ["std"] }
pbkdf2 = { version = "0.12", default-features = false, features = ["hmac"] }
flate2 = "1"
rand = "0.8"
# 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"] }
[workspace.lints.rust]
unsafe_op_in_unsafe_fn = "warn"
[workspace.lints.clippy]
unwrap_used = "deny"
expect_used = "deny"
panic = "deny"
todo = "warn" # warn during M0 stubs; will tighten to deny post-M1
unreachable = "deny"
indexing_slicing = "deny"
[profile.release]
opt-level = 3
lto = "thin"
codegen-units = 1
[profile.dev]
opt-level = 0