design/followups: rewrite F2/F3/F10/F11 with concrete next-step recipes
Each remaining open followup now lists the precise "Concrete next step" to close it — what to capture, where to write the fixture, which file to edit. Future sessions (or anyone without the project context) can pick up any of these and execute without guessing. F2 (NTLM verify_signature server→client): Status: awaiting wire-fixture capture. M2 wave 3 (callback exporter) is closed under F15, so the path is wired — instrument CallbackExporter to hex-dump inbound StatusReceived bytes during a live subscribe, save under tests/fixtures/m2-status-frame/, port verify_signature mirroring `sign` but using the server-to-client sub-keys per [MS-NLMP] §3.4.4, add `subtle = "2"` for constant-time MAC compare. F3 (cross-domain NTLM Type1/2/3 fixture): Status: permanently out-of-scope on this host (no second AD domain). Documented the lab-environment requirements and the capture procedure for whoever provisions the two-domain harness. F10 (IObjectExporter::ResolveOxid2 opnum 4): Status: awaiting capture or .NET helper. Two paths documented — extend MxNativeClient.Probe with --probe-resolve-oxid2 OR hand-roll the layout from [MS-DCOM] §3.1.2.5.1.4 and validate later. F11 (IRemUnknown::RemAddRef + RemRelease): Status: same shape as F10. Document either probe extension or structural port from [MS-DCOM] §3.1.1.5.6 (REMINTERFACEREF[]). No code changes in this commit — purely sharpening the followup specs so each one's resolution recipe is self-contained. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+19
-16
@@ -167,29 +167,32 @@ Both sides see the same `result_code=32` (= `AsbErrorCode.PublishComplete`, info
|
||||
|
||||
|
||||
### F2 — NTLM verify_signature path + constant-time MAC compare (server-to-client direction)
|
||||
**Severity:** P2
|
||||
**Source:** M2 wave 1, `crates/mxaccess-rpc/src/ntlm.rs`
|
||||
**Why deferred:** The .NET `ManagedNtlmClientContext` only implements client-to-server signing (`cs:30,124`); there is no implementation of server-to-client sign/seal keys or `verify_signature`. Both are needed when the callback exporter receives a signed inbound frame from `NmxSvc.exe`, but no such fixture exists yet.
|
||||
**Resolves when:** M2 wave 3 (callback exporter) captures an `INmxSvcCallback::StatusReceived` frame with an `auth_value` trailer per `design/60-roadmap.md:56` (DoD #3) and a fixture lands under `tests/fixtures/m2-status-frame/`. Add `subtle = "2"` and gate the byte compare behind `ConstantTimeEq` at the same time.
|
||||
**Severity:** P2 — defensive hardening; the inbound auth-value trailer is currently not validated, but in a typical M4 deployment the callback exporter is bound to localhost and only `NmxSvc.exe` writes to it (no MITM surface inside the box).
|
||||
**Status:** Awaiting wire-fixture capture.
|
||||
**Source:** M2 wave 1, `crates/mxaccess-rpc/src/ntlm.rs`. The .NET `ManagedNtlmClientContext` only implements client-to-server signing (`cs:30,124`); there's no implementation of server-to-client sign/seal keys or `verify_signature`. Both are needed when the callback exporter receives a signed inbound frame from `NmxSvc.exe`.
|
||||
**Concrete next step**: with M2 wave 3 (callback exporter) closed under F15, the path to capture is now wired:
|
||||
1. Run `cargo run -p mxaccess --example subscribe` (or any consumer that drives `Session::subscribe`) against a live AVEVA install with a real attribute that ticks (`TestChildObject.TestInt` works).
|
||||
2. Add a temporary `eprintln!` hex dump in `mxaccess-callback::CallbackExporter`'s inbound-frame path to write the raw DCE/RPC bytes to stderr or a file when an `INmxSvcCallback::StatusReceived` frame arrives. The frame should carry an `auth_value` trailer (last `auth_length` bytes of the PDU per the DCE/RPC `[C706]` PDU layout, after the stub data).
|
||||
3. Save the trailing bytes (header + stub + auth-value) under `crates/mxaccess-rpc/tests/fixtures/m2-status-frame/01-localhost.bin`.
|
||||
4. Port `verify_signature` mirroring the existing client-side `ManagedNtlmClientContext::sign` shape but using the **server-to-client** sub-keys (`SealKey_S→C` / `SignKey_S→C`) per `[MS-NLMP]` §3.4.4. Add `subtle = "2"` to the workspace deps and gate the MAC compare via `subtle::ConstantTimeEq`.
|
||||
|
||||
### F3 — Cross-domain NTLM Type1/2/3 fixture
|
||||
**Severity:** P2
|
||||
**Source:** M2 wave 1, `crates/mxaccess-rpc/src/ntlm.rs`
|
||||
**Why deferred:** All current NTLM fixtures are single-domain (the local AVEVA install). Tracked separately in `design/70-risks-and-open-questions.md` R8 (P1 risk) and the open-evidence-gaps table.
|
||||
**Resolves when:** A multi-domain AVEVA test harness lands and a successful cross-domain authenticate round-trip captures Type1/2/3 bytes. Notes: this clears R8.
|
||||
|
||||
**Status:** Permanently out-of-scope on the current dev host (no second AD domain). Resolution requires external infrastructure not available here.
|
||||
**Source:** M2 wave 1, `crates/mxaccess-rpc/src/ntlm.rs`. All current NTLM fixtures are single-domain (the local AVEVA install). Tracked separately in `design/70-risks-and-open-questions.md` R8 (P1 risk) and the open-evidence-gaps table.
|
||||
**Concrete next step:** Provision a two-domain Windows lab (e.g. `LAB-A` + `LAB-B` with cross-domain trust + an AVEVA install on `LAB-A` that authenticates a user from `LAB-B`). Run `cargo run -p mxaccess --example connect-write-read` from a `LAB-B`-domain user; capture the NTLM Type1 / Type2 / Challenge / Type3 bytes via `examples/asb-relay.rs` or a Wireshark NTLM filter. Save under `crates/mxaccess-rpc/tests/fixtures/cross-domain-ntlm/`. The existing single-domain Type1/2/3 round-trip tests in `mxaccess-rpc::ntlm` then extend to validate the cross-domain shape (TargetInfo AV pairs differ when crossing domains; specifically `MsvAvDnsTreeName` and `MsvAvDnsComputerName` carry the trusted-domain DNS suffix instead of the local one). Clears R8 in the risks doc.
|
||||
|
||||
### F10 — `IObjectExporter::ResolveOxid2` (opnum 4) body codec
|
||||
**Severity:** P2
|
||||
**Source:** M2 wave 2, `crates/mxaccess-rpc/src/object_exporter.rs`
|
||||
**Why deferred:** `ObjectExporterMessages.cs` only models opnum 0 (`ResolveOxid`). Opnum 4 (`ResolveOxid2`) has a different response shape — it adds a `COMVERSION` plus an `AuthnHnt[]` array. The .NET reference does not exercise this path, so there's no executable spec to mirror.
|
||||
**Resolves when:** Either a `[MS-DCOM]` §3.1.2.5.1.4-derived layout is verified against a captured `ResolveOxid2` exchange, or the .NET reference grows a `ParseResolveOxid2*` helper.
|
||||
**Severity:** P2 — the ResolveOxid (opnum 0) path is what the .NET reference + our Rust port use; opnum 4 is only needed by callers that want the additional `COMVERSION` + `AuthnHnt[]` data.
|
||||
**Status:** Awaiting wire-fixture capture or .NET helper.
|
||||
**Source:** M2 wave 2, `crates/mxaccess-rpc/src/object_exporter.rs`. `ObjectExporterMessages.cs` only models opnum 0; opnum 4 has a different response shape per `[MS-DCOM]` §3.1.2.5.1.4. No .NET executable spec to mirror.
|
||||
**Concrete next step:** Either (a) extend `MxNativeClient.Probe` with a `--probe-resolve-oxid2` flag that calls `IObjectExporter::ResolveOxid2(oxid, &requested_protseqs)` against `localhost:135` and dumps the response stub to a file, then port the layout into `object_exporter.rs::parse_resolve_oxid2_result` mirroring the existing `parse_resolve_oxid_result` (`object_exporter.rs:226`); or (b) hand-roll the layout from `[MS-DCOM]` §3.1.2.5.1.4 (response = same as ResolveOxid + 8-byte `COMVERSION` + 4-byte `AuthnHnt[]` count + N×4-byte ushort entries + 4-byte status), commit the structural codec, and rely on a future captured frame to validate.
|
||||
|
||||
### F11 — `IRemUnknown::RemAddRef` and `RemRelease` body codecs
|
||||
**Severity:** P2
|
||||
**Source:** M2 wave 2, `crates/mxaccess-rpc/src/rem_unknown.rs`
|
||||
**Why deferred:** `RemUnknownMessages.cs` declares the opnums (`:9-10`) but does not implement encoders/decoders. The Rust port matches that exactly per "port what is already proven."
|
||||
**Resolves when:** The .NET reference adds bodies for opnums 4 / 5 (or a captured frame establishes the on-wire shape). At that point port them into `rem_unknown.rs` alongside the existing `RemQueryInterface` codec.
|
||||
**Severity:** P2 — neither opnum is exercised by the .NET reference's NMX session lifecycle, so the lack of a body codec doesn't block any current consumer.
|
||||
**Status:** Awaiting wire-fixture capture or .NET helper.
|
||||
**Source:** M2 wave 2, `crates/mxaccess-rpc/src/rem_unknown.rs`. `RemUnknownMessages.cs` declares the opnums (`:9-10`) but doesn't implement encoders/decoders. Rust port matches that per "port what is already proven."
|
||||
**Concrete next step:** Either extend `MxNativeClient.Probe` with `--probe-rem-add-ref` / `--probe-rem-release` flags that exercise opnums 4 and 5 against an existing `IRemUnknown` IPID, capture the responses, and port the body layouts into `rem_unknown.rs` alongside the existing `RemQueryInterface` codec; OR derive the layouts from `[MS-DCOM]` §3.1.1.5.6 (`REMINTERFACEREF[]` array of IPID + public/private refs counts) and ship the codecs structurally.
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user