Add opt-in KERB_CERTIFICATE_LOGON SSPI rewrite for smart card logon#171
Closed
Marc-André Moreau (mamoreau-devolutions) wants to merge 1 commit into
Closed
Add opt-in KERB_CERTIFICATE_LOGON SSPI rewrite for smart card logon#171Marc-André Moreau (mamoreau-devolutions) wants to merge 1 commit into
Marc-André Moreau (mamoreau-devolutions) wants to merge 1 commit into
Conversation
Smart card logon via RDP fails when a certificate thumbprint and PIN are both pre-supplied: the CredSSP credential reaching LSASS is not a KERB_CERTIFICATE_LOGON, so tspkg calls CryptAcquireCertificatePrivateKey without CRYPT_ACQUIRE_ALLOW_NCRYPT_KEY_FLAG and fails for CNG-backed keys. Implement the client-side workaround by extending the existing SSPI API hooking instead of patching LSASS. The AcquireCredentialsHandleW detour now, strictly per session and only when KerbCertificateLogon:i:1 is set, rewrites a marshaled smart card certificate credential into the equivalent CredsspCertificateCreds / KERB_CERTIFICATE_LOGON packed buffer before it is handed to LSASS. CREDSSP_CRED_EX wrapping is preserved when present. Per-session isolation reuses the existing instance/session machinery: a new thread-local SSPI session scope is bracketed around CMsRdpClient::raw_Connect() (with a guarded, non-ambiguous global fallback) so the hook can resolve the owning CMsRdpExtendedSettings without affecting other concurrent sessions. Safety: rewrite is opt-in only, never triggered automatically by PasswordContainsSCardPin; unknown credential shapes pass through unchanged; no PINs, passwords, or certificate bytes are logged; PIN-bearing buffers are zeroed before free. Optional secret-free diagnostics are gated behind MSRDPEX_SSPI_SMARTCARD_DEBUG. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Member
|
Superseded by #172, which squashes this work plus the follow-up changes into a single commit: extends the workaround to the in-process RDP ActiveX control (synthesis from the session's marshaled certificate user name + PIN), hardens session correlation and PIN handling, fixes reconnect, keeps it inert for certificate-only logons, and decouples it from PasswordContainsSCardPin. Validated end-to-end against smart card hardware. |
Richard Markiewicz (thenextman)
added a commit
that referenced
this pull request
Jul 3, 2026
## Summary Client-side workaround for smart card certificate logon failures over RDP caused by the `tspkg` regression, implemented entirely by extending MsRdpEx's SSPI API hooking — no LSASS patching and no internal `mstscax` offset hooks. Supersedes #171: same approach, extended so it also works for the **in-process RDP ActiveX control** (not just out-of-process `mstsc.exe`), hardened, and validated end-to-end against smart card hardware. ### Problem When a smart card certificate **and** PIN are both pre-supplied (an unattended logon, no interactive prompt), the CredSSP credential reaching LSASS is not a `KERB_CERTIFICATE_LOGON`. `tspkg` then calls `CryptAcquireCertificatePrivateKey` without `CRYPT_ACQUIRE_ALLOW_NCRYPT_KEY_FLAG`, which fails for CNG/NCrypt-backed keys (the common smart card case) and surfaces as "The Local Security Authority cannot be contacted". The interactive Windows prompt is unaffected because it builds a packed `KERB_CERTIFICATE_LOGON`. Certificate-only (no PIN) logons were never broken. ### Solution When `KerbCertificateLogon:i:1` is set on a session, the `AcquireCredentialsHandleW` detour hands LSASS a `CredsspCertificateCreds` / `KERB_CERTIFICATE_LOGON` packed buffer, obtained either by: - **synthesizing** it from the session's marshaled certificate user name + PIN — for hosts that do not pass the credential in-band (the in-process RDP ActiveX control), or - **rewriting** a marshaled smart card credential already present in the auth data — the out-of-process `mstsc.exe` case (preserving `CREDSSP_CRED_EX` wrapping). ### Key design points - **Session correlation**: an active-session registry with per-thread bindings resolves the owning `CMsRdpExtendedSettings` on the SSPI worker thread — where the asynchronous CredSSP handshake actually runs — and fails closed when ambiguous. The SSPI session scope is idempotent and ends on connect failure, disconnect and teardown. - **PIN handling**: the PIN cannot be read back from the protected secure-string property, so it is captured when set, kept only for the connection, DPAPI-encrypted in memory (`CryptProtectMemory`), decrypted transiently while the credential is built, and zeroed before release. - **Reconnect**: the marshaled certificate user name is snapshotted, because `mstscax` replaces it with the resolved account name after the first logon; without the snapshot a reconnect could not rebuild the credential. - **Opt-in and inert without a PIN**: certificate-only logons are left untouched and use the normal Windows prompt. Independent of `PasswordContainsSCardPin` — the stock RDP setting the caller sets to have a smart card credential delegated to the remote. - **No secret leakage** — no PINs, passwords, or certificate bytes are logged; secret-free diagnostics gated behind `MSRDPEX_SSPI_SMARTCARD_DEBUG=1`. ### Files changed - `dll/Sspi.cpp` — session registry + resolution, diagnostics, `KERB_CERTIFICATE_LOGON` builder, synthesis + rewrite paths, wired-in detour - `dll/RdpSettings.cpp`, `include/MsRdpEx/RdpSettings.h` — `KerbCertificateLogon` opt-in, per-session DPAPI-protected PIN capture, marshaled-username snapshot - `dll/RdpInstance.cpp`, `include/MsRdpEx/RdpInstance.h` — resolve extended settings by core property set - `dll/MsRdpClient.cpp` — SSPI session scope lifetime; discard captured PIN on non-certificate logons - `dll/CMakeLists.txt` — link `crypt32` (DPAPI) - `include/MsRdpEx/Sspi.h` — begin/end session declarations - `README.md` — documents the RDP option, behavior, and debug env var ### Validation - Built `MsRdpEx.dll` Release/x64 — links cleanly, no new warnings. - Validated end-to-end against smart card hardware in Remote Desktop Manager: certificate + PIN logon succeeds in both **embedded (in-process ActiveX)** and **external (`mstsc.exe`)** modes; certificate-only (no PIN) still prompts and connects; **connect and reconnect** both work. - **Not yet done:** x86 / arm64 builds (x64 only so far — CI), and `KERB_SMARTCARD_CSP_INFO` (CspData) for multi-reader / multi-card disambiguation (tracked with a TODO). Co-authored-by: Marc-André Moreau <marcandre.moreau@gmail.com> Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.
Summary
Implements the client-side workaround for smart card certificate logon failures over RDP, entirely by extending MsRdpEx's existing SSPI API hooking — no LSASS patching and no internal
mstscaxoffset hooks.Problem
When a smart card certificate thumbprint and PIN are both pre-supplied (e.g. by a connection manager that stores them and sets
PasswordContainsSCardPin:i:1), the CredSSP credential reaching LSASS is not aKERB_CERTIFICATE_LOGON.tspkgthen callsCryptAcquireCertificatePrivateKeywithoutCRYPT_ACQUIRE_ALLOW_NCRYPT_KEY_FLAG, which fails for CNG/NCrypt-backed keys (the common smart card case). The interactive Windows prompt avoids this because it builds a packedKERB_CERTIFICATE_LOGON.Solution
When
KerbCertificateLogon:i:1is set on a session, theAcquireCredentialsHandleWdetour rewrites a marshaled smart card certificate + PIN CredSSP credential into the equivalentCredsspCertificateCreds/KERB_CERTIFICATE_LOGONpacked buffer before it is handed to LSASS.CREDSSP_CRED_EXwrapping is preserved when present.Key design points
CMsRdpClient::raw_Connect()(with a guarded, non-ambiguous global fallback), so the hook resolves the owningCMsRdpExtendedSettingswithout affecting other concurrent sessions.CredUnPackAuthenticationBufferW→ validate marshaledCertCredential→CredPackAuthenticationBufferW, verifying the first DWORD isKerbCertificateLogon.PasswordContainsSCardPin. Unknown / already-correct credential shapes pass through unchanged.MSRDPEX_SSPI_SMARTCARD_DEBUG=1.Files changed
dll/Sspi.cpp— session-scope infra, diagnostics, KERB_CERTIFICATE_LOGON builder, wired-in rewritedll/MsRdpClient.cpp— SSPI session begin/end aroundraw_Connect()dll/RdpSettings.cpp,include/MsRdpEx/RdpSettings.h—KerbCertificateLogonopt-in +PasswordContainsSCardPinstateinclude/MsRdpEx/Sspi.h— begin/end session declarationsREADME.md— documents the new RDP option, behavior, and debug env varValidation
MsRdpEx.dllRelease/x64 — links cleanly, no new warnings from the changes.MSRDPEX_SSPI_SMARTCARD_DEBUG=1.