Fix REG_DWORD handling in ConfigService.GetRegistryValue#6
Merged
Conversation
There was a problem hiding this comment.
Pull request overview
Fixes ConfigService.GetRegistryValue so enterprise policy registry values stored as REG_DWORD/REG_QWORD are no longer silently ignored, enabling CSP-deployed boolean/integer policies to work as documented.
Changes:
- Refactors
GetRegistryValueto read raw registry values and convert supported types via a newConvertToConfigStringhelper. - Updates the
TempRegistryKeytest fixture to reuse production conversion logic and adds helpers to support QWORD + rawRegistryKey.GetValue()testing. - Adds
RegistryValueConversionTestswith unit + integration + end-to-end coverage for DWORD/QWORD conversions and affected getters.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| src/CryptEscrow/Services/ConfigService.cs | Adds ConvertToConfigString and routes both registry paths through shared raw-value conversion logic. |
| tests/CryptEscrow.Tests/Fixtures/TempRegistryKey.cs | Aligns fixture registry reads with production conversion and adds QWORD + direct key access for integration tests. |
| tests/CryptEscrow.Tests/Services/RegistryValueConversionTests.cs | Adds regression coverage for conversion and end-to-end DWORD policy parsing. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
rodchristiansen
added a commit
that referenced
this pull request
Apr 11, 2026
1. Rename Serilog property placeholder {Path} to {PolicySource} in
ReadValueFromPath. Elsewhere in this codebase {Path} is used for
actual filesystem/registry paths (e.g. 'mTLS enabled via PEM
files: {Path}' in CryptServerClient.cs); reusing the same name
with 'GP'/'MDM' labels would make structured log queries
misleading.
2. Reword GetRegistryValue XML doc summary. Previously said 'Reads a
string value' which was accurate before the DWORD/QWORD fix but is
now misleading — the method accepts multiple registry types and
returns their string representation. Updated to 'Reads an
enterprise policy value from the registry ... and returns its
string representation.'
No behavior change; docs + log template only. 61 tests still passing.
Closes #4. GetRegistryValue was reading raw values with `key?.GetValue(name) as string`, which returns null for any non-string registry type. REG_DWORD values come back from RegistryKey.GetValue as boxed int — so every CSP-deployed boolean or integer policy that used the standard DWORD wire format was silently dropped. This affected GetUseMtls, GetAutoRotate, GetSkipCertCheck, GetCleanupOldProtectors, GetKeyEscrowIntervalHours, and GetValidateKey — all documented as String/DWORD in the README but only actually working for strings. The "Handle registry DWORD values" fall-through in GetRegistryBool (ConfigService.cs:115) was unreachable. ## Fix Factor out ConvertToConfigString as an internal pure helper that converts a raw registry value to its string form, and route both production paths (GP and MDM) through it: - string / REG_SZ / REG_EXPAND_SZ → returned as-is (null for empty) - int / REG_DWORD → invariant-culture int.ToString - long / REG_QWORD → invariant-culture long.ToString - anything else (REG_BINARY, REG_MULTI_SZ, …) → null (caller falls through) GetRegistryBool and GetRegistryInt now receive the string form of DWORDs and the existing int.TryParse branches light up. ## Tests (22 new, 61 total passing in ~1s) RegistryValueConversionTests: - 8 pure unit tests on ConvertToConfigString covering null, empty/whitespace strings, positive/negative/zero/MaxValue/MinValue DWORDs, QWORD max, byte arrays and string arrays (both null). - 3 integration tests that open a real HKCU subkey via TempRegistryKey, call GetValue on REG_SZ/REG_DWORD/REG_QWORD entries, and assert ConvertToConfigString returns the expected string. These prove the boxed int/long round trip works against actual registry data — the part of the production chain that the old `as string` cast dropped. - 4 end-to-end tests (GetSkipCertCheck, GetKeyEscrowIntervalHours, GetAutoRotate) exercising the full GetRegistryBool/Int path with DWORD values. ## Test seam changes TempRegistryKey.ReadValue now delegates to ConfigService.ConvertToConfigString instead of having its own private switch, so production and test conversion logic can never drift apart. New TempRegistryKey.OpenKey() returns the underlying HKCU key so tests can exercise the raw GetValue chain directly. TempRegistryKey.SetQword added for QWORD coverage. ## Joins GlobalState collection RegistryValueConversionTests uses TempRegistryKey and TempConfigFile (both mutate ConfigService static state), so the class joins the existing GlobalStateCollection to opt out of cross-class parallelism.
1. Rename Serilog property placeholder {Path} to {PolicySource} in
ReadValueFromPath. Elsewhere in this codebase {Path} is used for
actual filesystem/registry paths (e.g. 'mTLS enabled via PEM
files: {Path}' in CryptServerClient.cs); reusing the same name
with 'GP'/'MDM' labels would make structured log queries
misleading.
2. Reword GetRegistryValue XML doc summary. Previously said 'Reads a
string value' which was accurate before the DWORD/QWORD fix but is
now misleading — the method accepts multiple registry types and
returns their string representation. Updated to 'Reads an
enterprise policy value from the registry ... and returns its
string representation.'
No behavior change; docs + log template only. 61 tests still passing.
185f971 to
d5b1f0c
Compare
rodchristiansen
added a commit
that referenced
this pull request
Apr 12, 2026
Consolidates the four PRs merged today into a single semver entry for humans, and triggers a new CI release whose body will link back to the individual timestamp-tagged releases (2308, 2314, 2344, 2347). The entry credits @aysiu explicitly for the PEM client cert contribution in PR #2, which was the original spark for the mTLS feature. PFX + Credential Manager and the strategy-priority refactor were layered on as maintainer tweaks. Also documents: - xUnit test suite + dotnet test CI integration (PR #3) - Node.js 24 opt-in for JavaScript Actions (PR #5) - REG_DWORD/QWORD handling fix in GetRegistryValue, closing #4 (PR #6)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #4.
Summary
`GetRegistryValue` was reading raw values with `key?.GetValue(name) as string`, which returns null for any non-string registry type. `REG_DWORD` values come back from `RegistryKey.GetValue` as boxed `int`, so every CSP-deployed boolean or integer policy that used the standard DWORD wire format was silently dropped.
Affected helpers (all documented as `String/DWORD` in the README, but only actually working for string-typed deployments):
The "Handle registry DWORD values" fall-through at `ConfigService.cs:115` in `GetRegistryBool` was unreachable.
Fix
Factor out `ConvertToConfigString` as an internal pure helper and route both production paths (`SOFTWARE\Policies...` and `SOFTWARE\Microsoft\PolicyManager...`) through it:
`GetRegistryBool` and `GetRegistryInt` now receive the string form of DWORDs and their existing `int.TryParse` branches light up correctly.
Tests (+22, 61 total, ~1s runtime)
New `RegistryValueConversionTests` class (joins `GlobalStateCollection` since it mutates `ConfigService` static state):
Test seam changes
Test plan