Skip to content

M4-11 Device key for Windows

No Date Ruling Reason
D-M4-11-0 2026-10-15 Base int/m1-wave23 at d2d32fa, branch feat/M4-11-device-key-windows in its own worktree; this build neither merges locally nor pushes, the PR lane lands it. Every rule the brief cites (R-CL-04, R-CL-05, R-CL-12, R-ID-11, D-L10) is on the base, so nothing is ported from main. The base names the chapters docs/spec/15-client-architektur.md and 08-identitaet.md; text added there is English (as D-M4-10-0). Names that differ from the brief: the core has, besides key_provider.h, cng_key_provider.cpp, device_key_store.cpp and .h, the headers cng_key_provider.h and marker_store.h with credential_marker_store.cpp (the marker of note 4 behind its own interface), and the tests have fake_key_provider.h, test_support.h, main.cpp and device_key_store_test.cpp; no der.cpp, because NCryptSignHash returns R \|\| S for ECDSA. codegen-check already hashes every file under the member, so the Makefile only gains a comment that the C++ outputs are covered. packages/device_key/analysis_options.yaml gains analyzer: exclude: [windows/**], which the pinned Flutter's flutter pub get writes itself once a plugin has a windows/ folder ("Upgrading analysis_options.yaml to exclude build and platform directories"); the folder holds no Dart code. The launcher's instructions take precedence over the brief's landing step; merged layouts win over the brief's names (D-W-100, D-W-114).
D-M4-11-1 2026-10-15 Provider choice per key at creation, never migrated: a new dpop key goes to MS_PLATFORM_CRYPTO_PROVIDER when it opens and NCryptIsAlgSupported(BCRYPT_ECDSA_P256_ALGORITHM) succeeds, otherwise to MS_KEY_STORAGE_PROVIDER with hardwareBound = false. A marker (generic credential in the user's Credential Manager, CRED_PERSIST_LOCAL_MACHINE, target name = key name, blob platform or software) records the provider; a key is opened only there. Without a marker (a stop between NCryptFinalizeKey and writing it) an existing key is looked up in the platform provider, then the software provider, used where it is and the marker written. A marker without an openable key (NTE_BAD_KEYSET, NTE_NO_KEY, NTE_NOT_FOUND) is key_invalidated and nothing is recreated; a marker whose provider no longer opens is not_available. Keys are per user (no NCRYPT_MACHINE_KEY_FLAG), ECDSA P-256, export policy 0 set before NCryptFinalizeKey. A key moved between providers would change the JWK and need a new device (08 5.4); the marker is how the plugin tells "not created yet" from "lost" (brief note 4). Which status a real TPM clear produces is not measured.
D-M4-11-2 2026-10-15 No step-up key on Windows: Ensure(step_up) fails with not_available, and the plugin never offers a key whose confirmation only the plugin enforces. The DPoP key signs with NCRYPT_SILENT_FLAG. Sources, retrieved 2026-10-15: strong key protection (NCRYPT_UI_FORCE_HIGH_PROTECTION_FLAG) prompts for a password whose "exact behavior is specified by the KSP" (learn.microsoft.com, X509PrivateKeyProtection); the Microsoft KSPs accept that password from the process through NCRYPT_PIN_PROPERTY and "cache this value so that the user is only prompted once per process" (Key Storage Property Identifiers); a Microsoft answer on per-use reauthentication for Platform Crypto Provider keys names only a PIN or a custom KSP (Microsoft Q&A 1076208); Windows Hello keys (KeyCredentialManager) are RSA and sign with SHA-256 and PKCS #1 (Windows Hello for apps). Not checked on a Windows host: no Windows machine was reachable. Recommendation (08 6.1): a Windows Hello RS256 step-up key needs RS256 or PS256 in 03 device_keys.alg and in the JOSE profile of ADR 0067, a core contract change; until then the step-up of a Windows client uses the other methods of 08 6. 08 6.1: the confirmation must come from the person, not from code in the process; a password the process can set or that is cached per process does not meet that, and ES256 is the only algorithm of the contract (R-CL-04).
D-M4-11-3 2026-10-15 The CTest suite uses a minimal harness windows/core/tests/check.h (TEST, CHECK, REQUIRE, SKIP) without a third-party framework and without exceptions: one executable runs the test named by its argument, CTest registers one test per name, exit code 77 is a skip (SKIP_RETURN_CODE). The core library is built with _HAS_EXCEPTIONS=0 like Flutter's apply_standard_settings for the plugin, and every operation returns a Status instead of throwing. No new dependency for a small suite; the same exception setting on both sides of the plugin boundary.
D-M4-11-4 2026-10-15 tpm_path_when_available skips with platform crypto provider not available on this host when MS_PLATFORM_CRYPTO_PROVIDER does not open or lacks P-256, and the platform half of keys_are_not_exportable prints the same reason; the fallback runs always, through FakeKeyProvider (TPM missing, or a TPM without P-256 simulated on a software provider handle) and against the real software provider. Every test key and marker starts with device-key-test-; main.cpp deletes leftovers in both providers and the Credential Manager before and after each test. Brief note 5; CI runners have no TPM, so the TPM path counts as proven only on a host with one.
D-M4-11-5 2026-10-17 The ticket was ported from the integration squash of wave 27 (built against a base that already held M5-01) together with the integration fix INT-W27/2, which keeps the Windows plugin overriding Sign after M5-01 added the prompt texts to the Pigeon method; the English chapters of main take the spec changes (08 6.1 and K-ID-4, 15 9). Port onto main (PR lane); M5-01 is merged before this ticket.