Rotating Every Cipher: SChannel and the Twenty-Year Algorithm-Agility Story of Windows TLS
How one Windows DLL rotated every TLS primitive from RC4 to ML-KEM without breaking IIS, RDP, SQL Server, or .NET SslStream -- and why Vista's 2007 CNG was the inflection point.
Permalink1. Two PowerShell Outputs, Twelve Years Apart
Run Get-TlsCipherSuite on a freshly installed Windows Server 2025 and the output is unrecognisable to a 2012 administrator [7]. RC4 is gone. 3DES is gone. The list is led by TLS_AES_256_GCM_SHA384 and TLS_AES_128_GCM_SHA256 -- TLS 1.3 cipher suites that did not exist when schannel.dll was first written. Yet IIS, SQL Server, RDP via CredSSP, LDAPS, WinHTTP, and every .NET SslStream consumer on the planet still compiles against the same Win32 SSPI surface they did in 2007 [1]. How does one DLL rotate every cryptographic primitive in its lineup without breaking the world above it?
That question is this article's organising prompt. The answer, held back deliberately until Section 4, is algorithm agility -- the architectural property Microsoft made first-class when it shipped Cryptography API: Next Generation alongside Windows Vista in early 2007 [2].
The Win32 abstraction that lets an application acquire credentials, build a security context, and exchange authentication tokens without knowing which protocol (Kerberos, NTLM, Negotiate, or Schannel) is doing the work underneath. SChannel is the SSP that implements SSL, TLS, and DTLS on Windows; its module is schannel.dll and its public surface is AcquireCredentialsHandle / InitializeSecurityContext / AcceptSecurityContext [1].
Which Windows endpoints SChannel actually owns
SChannel is not the only TLS stack that runs on Windows. The honest scope of this article is the set of Windows TLS endpoints Microsoft itself owns. SChannel is the SSP behind:
- IIS TLS termination for HTTP/1.1 and HTTP/2 (HTTP/3 over QUIC terminates in
msquic.dll, which uses SChannel for the TLS 1.3 handshake key derivation and then performs the per-packet AEAD outsideschannel.dllper RFC 9001 §5 [8][9]). - RDP Network Level Authentication via CredSSP -- the CredSSP SSP wraps SChannel to deliver the TLS-protected credential prompt before the RDP session opens.
- LDAPS for Active Directory client and server bindings.
- RPC over HTTPS as used by Outlook Anywhere and historical Exchange topologies.
- SQL Server TDS-over-TLS encryption on Windows.
- WinHTTP and WinINet -- the Win32 HTTP clients behind
BITS,WebClient, and many enterprise agents. - .NET
SslStreamwhen running on Windows. On Linux .NET delegates to OpenSSL; on macOS it uses Apple's Network framework.
The endpoints SChannel does not own on a typical Windows box are equally important to name. Chromium and (via Chromium) Microsoft Edge ship BoringSSL -- legacy EdgeHTML used Windows native crypto, but it has been end-of-life since Edge's January 15, 2020 Chromium-based re-launch. Firefox ships NSS. Containerised .NET workloads on Linux ship with OpenSSL. SQL Server on Linux uses OpenSSL too [10][11]. The Windows TLS story is genuinely a Windows-platform story, not a "what speaks TLS on a Windows machine" story. On Linux, .NET's SslStream does not use SChannel at all -- it delegates to OpenSSL [11]. The Win32 SChannel story really is a Windows-platform story, not a story about everything TLS-shaped that happens on a Windows machine. MsQuic uses SChannel only for the TLS 1.3 handshake key derivation -- the per-packet AEAD that protects QUIC payloads runs outside schannel.dll, in MsQuic itself, per RFC 9001 §5 packet protection [8][9]. The MsQuic project documents the TLS abstraction layer (CxPlatTlsProcessData) and notes explicitly that "the TLS record layer is not included" and that "TLS exposes the encryption key material to QUIC to secure its own packets" [8].
The artifact comparison
The cleanest way to see the substrate's twenty-year track record is to compare what Get-TlsCipherSuite returns on two Windows generations [7][12]. The TLS 1.3 cipher suites listed on the Windows Server 2022 / 2025 page (TLS_AES_128_GCM_SHA256, TLS_AES_256_GCM_SHA384, TLS_CHACHA20_POLY1305_SHA256) [13] simply are not on the Windows 7 / Server 2008 R2 page [14]; conversely, the Windows 7 page enumerates TLS_RSA_WITH_RC4_128_SHA, TLS_RSA_WITH_3DES_EDE_CBC_SHA, and TLS_RSA_WITH_AES_128_CBC_SHA as enabled by default -- suites that newer Windows builds have either removed or moved off-by-default [15].
// Approximation of the SChannel cipher-suite roster on two Windows generations.
const server2012R2 = [
'TLS_RSA_WITH_RC4_128_SHA',
'TLS_RSA_WITH_3DES_EDE_CBC_SHA',
'TLS_RSA_WITH_AES_128_CBC_SHA',
'TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA',
'TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256',
];
const server2025 = [
'TLS_AES_256_GCM_SHA384',
'TLS_AES_128_GCM_SHA256',
'TLS_CHACHA20_POLY1305_SHA256',
'TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384',
'TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256',
];
const rotatedOut = server2012R2.filter(s => !server2025.includes(s));
const rotatedIn = server2025.filter(s => !server2012R2.includes(s));
console.log('Rotated out (2012R2 default -> 2025 absent):');
rotatedOut.forEach(s => console.log(' - ' + s));
console.log('Rotated in (2025 default -> 2012R2 unavailable):');
rotatedIn.forEach(s => console.log(' + ' + s)); Press Run to execute.
The whole journey, on one timeline:
Diagram source
gantt
dateFormat YYYY
axisFormat %Y
title SChannel substrate eras and primitive rotations
section Substrate
CryptoAPI 1.0 (CSPs) :crit, capi, 1996, 2007
CNG (BCrypt + NCrypt) :active, cng, 2007, 2026
SymCrypt unified engine :sym, 2017, 2026
section Protocol versions
SSL 2.0 / 3.0 / TLS 1.0 :p10, 1996, 2014
TLS 1.1 / 1.2 :p12, 2008, 2026
TLS 1.3 default-on :p13, 2021, 2026
section Cipher generations
ECDHE plus AES-GCM debut :g1, 2009, 2026
RC4 deprecation :g2, 2013, 2016
3DES retirement :g3, 2019, 2026
SHA-1 sunset :g4, 2016, 2022
TLS 1.0 / 1.1 off-default :g5, 2020, 2025
X25519MLKEM768 hybrid :g6, 2025, 2026 CNG did not exist for the first eleven years of SChannel's life. To see why CNG had to be invented, the next section walks the rigidity that almost broke the Windows TLS stack before AES could even be standardised.
2. Before CNG: PCT 1.0, SSL, and the Tyranny of ALG_ID
PCT lost. By the time schannel.dll shipped in Windows NT 4.0 (commonly placed in 1996 per contemporary release histories), the new SChannel SSP had to negotiate three incompatible handshakes on the same wire: SSL 2.0, SSL 3.0, and PCT 1.0 [1]. By Vista, PCT was gone; SSL 2.0 was on its way to formal IETF prohibition [17]; SSL 3.0 had a few years left before POODLE would kill it off in 2014 [18]. The protocol-level story is well-trodden. The substrate underneath -- the engine SChannel called into to compute each primitive -- is what made the next decade much harder than it had to be.
CryptoAPI 1.0 and the CSP cage
A loadable DLL that implements a fixed catalog of cryptographic operations under CryptoAPI 1.0. Each CSP advertises a provider type (e.g. PROV_RSA_FULL, PROV_RSA_SCHANNEL) and exposes its primitives through opaque ALG_ID constants such as CALG_RC4, CALG_3DES, and CALG_SHA1. Adding a new primitive meant shipping a new CSP DLL, registering it under HKLM\Software\Microsoft\Cryptography\Defaults\Provider, and threading a fresh BLOB type through every consumer that called CryptAcquireContext.
The CryptoAPI 1.0 model had a single fatal property: the primitive was the API. To compute SHA-256, code had to ask CAPI for an ALG_ID whose numeric value was CALG_SHA_256 -- and that constant only existed once Microsoft shipped a CSP that defined it, in the same OS release that introduced the algorithm [19]. Elliptic-curve cryptography never arrived in CAPI in any usable form; the ALG_ID + key BLOB shape simply could not express the named curves, parameter sets, point-compression flags, or per-curve coordinate sizes that ECC required.
So in the early 2000s SChannel's cipher-suite list was less a menu of cryptography and more a snapshot of what CSPs had shipped. FIPS 197 (the AES standard) was published in November 2001. Windows XP shipped without AES in its default SChannel cipher list and only got it broadly via Service Pack 3 and Server 2003. The four-year AES gap was not Microsoft dragging its feet -- it was the thickness of a CSP-rev cycle. RC4 dominance, 3DES persistence, 1024-bit RSA inertia, no ECC: these were the substrate's fingerprints, not the vendor's preferences.
Diagram source
flowchart LR
A[Application -- IIS / IE / RPC] --> B[SChannel SSP]
B --> C[CryptoAPI 1.0 / CryptAcquireContext]
C --> D["RSA SChannel CSP -- ALG_ID lookup"]
C --> E["Base / Enhanced CSP -- ALG_ID lookup"]
C --> F[Smart Card CSP]
D -. "Adding ECC requires a new CSP, new ALG_ID, new BLOB type, new IANA codepoint" .-> G((Friction))
E -. "Adding SHA-256 requires CSP rev + OS release" .-> G The PCT failure as a positive lesson
PCT's loss is, in retrospect, the strongest early case for algorithm agility. SChannel had shipped PCT-the-protocol in 1996; by 2007 PCT was a footnote and SChannel was speaking TLS 1.0, TLS 1.1, SSL 3.0, and (with the right service pack) early TLS 1.2 drafts. The application surface above SChannel did not flinch. Microsoft had bet on PCT, lost, rotated to TLS, and shipped the rotation through the protocol abstraction that the SSP boundary provided.
What the SSP boundary did not shield was the primitive layer. Algorithm rotation had to happen one CSP rev at a time. By the mid-2000s Microsoft's engineering leadership had a clear diagnosis: the protocol abstraction worked; the primitive abstraction did not. The next CSP rev would not save them, because there were not enough CSP revs in the future to keep up with what cryptography was about to do -- ECC was already standardised, AEAD constructions were being designed, and the post-quantum research had been live for a decade.
The early-2000s lag in AES adoption, the persistence of RC4 and 3DES, and the absence of ECC in Windows were not vendor laziness. CryptoAPI 1.0's
ALG_ID+ provider-type model was structurally incapable of representing ECC's named curves and parameter sets. The right question was never "why is Microsoft slow?" -- it was "what would a Windows cryptographic substrate that was not slow look like?" The Vista CNG redesign is what that question's answer looks like.
RC4 dominance, 3DES persistence, 1024-bit RSA inertia, no ECC -- these were not laziness, they were the substrate. A fix to TLS 1.0 was easy; a fix to the way Windows let an application reach a primitive was a rewrite.
3. Configuration Agility Without Substrate Agility: XP and Server 2003
Consider a single registry path: HKLM\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.0\Client\DisabledByDefault. That value was introduced for SChannel's TLS 1.0 support in the XP / Server 2003 era. It is exactly the same registry sub-tree path an operator uses in 2026 to disable TLS 1.0 itself [15]. The configuration surface from 1999 has outlasted three generations of TLS.
This is not an accident. By the time TLS 1.0 landed in Windows (RFC 2246, Tim Dierks and Christopher Allen at Certicom, January 1999 [20]) and TLS 1.1 followed (RFC 4346, Dierks and Eric Rescorla, April 2006 [21]), SChannel had developed an emerging design pattern: every protocol version became a sub-key, every cipher suite became a registry-driven enable/disable, and the SSL Cipher Suite Order Group Policy gave administrators a single rope to pull when an algorithm fell from grace.
That model has aged well. Microsoft's current tls-registry-settings page is essentially the same structural document it would have been twenty years ago, with new sub-keys for each new protocol version (SSL 2.0, SSL 3.0, TLS 1.0, TLS 1.1, TLS 1.2, TLS 1.3, DTLS 1.0, DTLS 1.2) and new values for the policy levers Microsoft has added along the way [15]. The same SCHANNEL\Protocols\<ver>\<role>\Enabled pattern handles SSL 2, SSL 3, TLS 1.0, TLS 1.1, TLS 1.2, TLS 1.3, DTLS 1.0, and DTLS 1.2. A single sub-key per protocol; new versions slot in without reorganising the hive.
The four sub-keys an XP / Server 2003 box exposed
The shape of the SCHANNEL\ hive on a representative Server 2003 R2 box, reconstructed from Microsoft Knowledge Base article KB245030 ("How to restrict the use of certain cryptographic algorithms and protocols in Schannel.dll") and the modern Microsoft Learn tls-registry-settings page that preserves the same structural document [15], is shown below. Microsoft Knowledge Base article KB245030 ("How to restrict the use of certain cryptographic algorithms and protocols in Schannel.dll") is the origin document for the four-sub-key SCHANNEL\ registry pattern this section dumps. The original support.microsoft.com URL now returns HTTP 404; the same content lives at Microsoft Learn's tls-registry-settings page [15]. The four sub-keys (Protocols, Ciphers, Hashes, KeyExchangeAlgorithms) have been stable since Windows 2000. The DWORD convention is itself the agility affordance: 0xffffffff means "enabled," 0 means "disabled," and the Server versus Client role split lets an admin disable SSL 2.0 server-side without breaking outbound HTTPS client-side during the transition.
HKLM\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\
Protocols\
SSL 2.0\Server\Enabled = 0xffffffff ; ON by default on 2003 R2
SSL 3.0\Server\Enabled = 0xffffffff
TLS 1.0\Server\Enabled = 0xffffffff
; TLS 1.1 / 1.2 sub-keys absent -- those protocols do not exist on 2003 R2
Ciphers\
RC4 128/128\Enabled = 0xffffffff
RC4 56/128\Enabled = 0xffffffff ; export-grade, still present
RC4 40/128\Enabled = 0xffffffff ; export-grade
Triple DES 168\Enabled = 0xffffffff
DES 56/56\Enabled = 0xffffffff
RC2 40/128\Enabled = 0xffffffff ; export-grade
NULL\Enabled = 0
Hashes\
MD5\Enabled = 0xffffffff
SHA\Enabled = 0xffffffff
KeyExchangeAlgorithms\
Diffie-Hellman\Enabled = 0xffffffff
PKCS\Enabled = 0xffffffff ; RSA key transport
Notice what is not there. No TLS 1.1, no TLS 1.2, no AES sub-key (AES ALG_ID constants arrived in rsaenh.dll via XP SP3 and Server 2003 SP2 but SChannel had to learn the suite-name strings separately). No ECC primitive at all -- CryptoAPI 1.0 could not express named curve parameters in the ALG_ID + key BLOB shape, so no amount of registry editing could unlock an ECDHE cipher suite on a 2003-era box. The four-sub-key layout (Protocols, Ciphers, Hashes, KeyExchangeAlgorithms) is the configuration surface; what the surface can offer is bounded by the substrate underneath it.
The CSP layer underneath: PROV_RSA_SCHANNEL and CALG_TLS1PRF
On the dispatch side of that same XP / 2003 box, SChannel relied on two CryptoAPI 1.0 Cryptographic Service Providers in particular. The Microsoft Learn "Cryptographic Provider Types" page enumerates the provider types Microsoft shipped [22]:
PROV_RSA_SCHANNEL(provider type12) -- the SChannel-private CSP. It carried the TLS-specific primitives: theCALG_TLS1PRFpseudorandom function (algorithm identifier0x0000800a), theCALG_SCHANNEL_MASTER_HASHandCALG_SCHANNEL_MAC_KEYandCALG_SCHANNEL_ENC_KEYkey-derivation handles, and (because the substrate had to negotiate three handshake protocols) theCALG_SSL2_MASTERandCALG_PCT1_MASTERconstants documented on the Microsoft LearnALG_IDpage [19].PROV_RSA_FULL/PROV_RSA_AES(rsaenh.dll) -- the general-purpose enhanced CSP, which carried the bulk symmetric primitives the cipher list named (CALG_RC4,CALG_DES,CALG_3DES, eventuallyCALG_AES_128,CALG_AES_256).
Both CSPs were loaded by CryptAcquireContext against the HKLM\SOFTWARE\Microsoft\Cryptography\Defaults\Provider registry hierarchy. Neither was extensible without an rsaenh.dll (or analogous) revision and a CSP-rev ship cycle. The registry hive let an operator turn primitives off; it could not let an operator turn a new primitive on, because the CSP catalog itself was the menu. Adding ECC to that menu was not a configuration problem -- it required a different substrate.
The SSL Cipher Suite Order GPO -- and why it is a Vista-era artifact, not a 2003-era one
Cipher-suite ordering (as opposed to enablement) was not exposed as an administrative tunable until Windows Vista and Server 2008 added the Computer Configuration > Administrative Templates > Network > SSL Configuration Settings > SSL Cipher Suite Order Group Policy. The current Microsoft Learn "Manage Transport Layer Security (TLS)" page documents the format verbatim: "a strict comma delimited format. Each cipher suite string ends with a comma to the right side of it... the list of cipher suites is limited to 1,023 characters." [23] A representative XP-era ordering string -- if the GPO had existed for the operator to set -- would have read something like TLS_RSA_WITH_RC4_128_SHA,TLS_RSA_WITH_3DES_EDE_CBC_SHA,TLS_RSA_WITH_DES_CBC_SHA,..., walking the actual Server 2003 default lineup that the CSP catalog could deliver. The fact that this lever did not exist on 2003 -- the operator was limited to flipping Ciphers\<name>\Enabled DWORDs in the per-cipher sub-tree -- is itself evidence of how the operator-facing SChannel surface matured one Windows release at a time.
No enumeration tool on Server 2003
There is no Get-TlsCipherSuite cmdlet on Windows Server 2003. Windows PowerShell itself only shipped (as KB968930) in 2009, and the TLS PowerShell module first appeared in Windows 8 and Server 2012 [7]. On a 2003-era box the empirical answer to "what does this server actually negotiate?" was either a reg query "HKLM\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.0\Server" /v Enabled against the registry sub-tree above, or -- for what the client actually picked -- an outbound Internet Explorer 6 trace, or -- for what the server actually accepted -- a TCP-connect dump against port 443 with a TLS scanner of the era (typically openssl s_client -connect host:443 -cipher ALL running on a separately-administered Linux box). The operator-visible inventory tool an admin reaches for in 2026 is itself a CNG-era artifact.
The agility split: configuration vs. substrate
Here is the structural problem that XP-era SChannel revealed. The configuration surface was getting more agile -- an operator could turn cipher suites on and off, prefer one over another, disable an entire protocol version -- but the engine underneath was not. New primitives still required a CSP rev. New named curves were unrepresentable. SHA-256 in TLS handshake signatures was a several-year project.
A useful metaphor: configuration agility without substrate agility is a treadmill. You can disable bad cipher suites at will. You cannot add a new family of primitives without rebuilding the engine. By the mid-2000s Microsoft had two options. Patch CAPI in place forever -- absorb every new algorithm as a new ALG_ID constant, a new CSP DLL, a new BLOB type, a new round of partner re-certification. Or ship a successor.
They chose the second. The next section is the eureka moment the rest of the article hangs on.
4. CNG: Where Vista Made Algorithm Agility First-Class (January 2007)
Vista is where the article's clock starts. In January 2007 Microsoft did not patch CryptoAPI 1.0; it shipped a parallel substrate alongside it: Cryptography API: Next Generation. The Microsoft Learn portal still describes it in one sentence that doubles as the article's thesis: "CNG is the long-term replacement for the CryptoAPI. CNG is designed to be extensible at many levels and cryptography agnostic in behavior." [2]
"CNG is the long-term replacement for the CryptoAPI. CNG is designed to be extensible at many levels and cryptography agnostic in behavior." -- Microsoft Learn, Cryptography API: Next Generation portal [2]
The two splits that look prosaic in the documentation -- BCrypt for primitives, NCrypt for key custodians -- are, in fact, the single architectural decision that makes the rest of this article's twenty-year story possible.
The post-Vista replacement for CryptoAPI 1.0. CNG splits cryptography into two API surfaces. BCrypt (bcrypt.dll) handles primitive operations (hashes, ciphers, key-agreement, signing) and addresses algorithms by string identifier through BCryptOpenAlgorithmProvider. NCrypt (ncrypt.dll) handles key storage and custody through pluggable Key Storage Providers (KSPs). CNG is the substrate Microsoft built so that every later cryptographic primitive could be added as a provider update rather than an API rewrite [2].
BCrypt: algorithms become strings
The shape of the BCrypt API is the eureka moment.
BCRYPT_ALG_HANDLE hAlg;
NTSTATUS status = BCryptOpenAlgorithmProvider(
&hAlg,
BCRYPT_AES_ALGORITHM, // string identifier
NULL, // default provider
0);
BCRYPT_AES_ALGORITHM is the literal string "AES". The handle returned by BCryptOpenAlgorithmProvider does not encode which DLL implements AES; it encodes the contract that the resulting handle satisfies (block cipher, configurable mode, configurable key length). The same shape later admits BCRYPT_ECDH_P256_ALGORITHM, BCRYPT_SHA384_ALGORITHM, BCRYPT_CHACHA20_POLY1305_ALG_HANDLE, and -- in 2024-2026 -- BCRYPT_MLKEM_ALG_HANDLE with parameter-set selectors such as BCRYPT_MLKEM_PARAMETER_SET_768 [6].
BCRYPT_MLKEM_ALG_HANDLE resolves through the exact same hash-table lookup as BCRYPT_AES_ALGORITHM did in 2007. The substrate did not need an architectural change to absorb a brand-new algorithm family seventeen years later -- the dispatch was already built for it.
NCrypt: key custodians become pluggable
BCrypt is the CNG API for primitives -- arithmetic that takes plaintext and a key and returns ciphertext (or hash, or signature). NCrypt is the CNG API for key custodians -- objects that own a private key and expose only signing, decryption, and key-derivation operations. The split lets a TLS server hold a private key whose material it never sees: SChannel calls NCryptSignHash against an NCRYPT_KEY_HANDLE, and the handle's owning KSP (software, smart card, or TPM) performs the operation in its own trust boundary.
A CNG-loadable module that owns the lifecycle and operations of a private key. Microsoft ships three out of the box: the Microsoft Software KSP (keys at rest in the user or machine profile, protected by DPAPI), the Microsoft Smart Card KSP (keys on a PIV / CCID device), and the Microsoft Platform Crypto Provider (keys non-exportable from the TPM 2.0). Third parties ship KSPs for HSMs and cloud KMS systems. SChannel sees only the NCRYPT_KEY_HANDLE; the custodian is opaque to the SSP.
How SChannel uses CNG
After Vista, SChannel's internals look very different. The cipher-suite registry resolves to BCrypt algorithm identifiers rather than ALG_ID constants. The credentials handle that an IIS worker process receives from AcquireCredentialsHandle holds an NCRYPT_KEY_HANDLE for the server certificate's private key; signing operations during the handshake (CertificateVerify) dispatch through NCryptSignHash to whichever KSP owns the key.
Diagram source
flowchart TD
A["IIS / SQL Server / SslStream / WinHTTP"] --> B[SChannel SSP -- schannel.dll]
B --> C["BCrypt -- bcrypt.dll"]
B --> D["NCrypt -- ncrypt.dll"]
C --> E["SymCrypt primitive engine"]
C --> F["Third-party BCrypt providers"]
D --> G[Microsoft Software KSP]
D --> H[Smart Card KSP]
D --> I["Microsoft Platform Crypto Provider -- TPM 2.0"]
D --> J["HSM / cloud KMS KSPs"]
E --> K[(Algorithm dispatch by string identifier)]
G --> L[(Key operations by opaque handle)] The agility property, stated forward
From 2007 onward, adding a primitive to Windows TLS is a CNG-provider-update problem, not an SChannel-rewrite problem. The application surface stays put. IIS does not get rebuilt. SslStream does not change. The cipher suite negotiated on the wire is whatever the SChannel cipher-suite registry currently exposes; the cipher-suite registry resolves to whatever BCrypt providers are loaded.
Algorithm agility is not a property of TLS-the-protocol. The cipher-suite codepoint is the minor half of the work; the major half is having a substrate that resolves a new algorithm identifier without rebuilding every consumer. CNG's BCrypt dispatch is what that substrate looks like in Windows. The protocol's cipher-suite registry is enumerated; the substrate's algorithm registry is open. That asymmetry is the entire game.
SymCrypt as the parallel track
Microsoft's unified, FIPS 140-validated cryptographic primitive engine. Niels Ferguson began the project in late 2006 with the first sources committed in February 2007 [3] -- nearly a decade before Heartbleed. SymCrypt became the primary library for symmetric algorithms starting with Windows 8 and the primary library for all algorithms across Windows since the Windows 10 1703 release in March 2017. Microsoft open-sourced SymCrypt under the MIT license in July 2019 [3]. Its release-by-release primitive timeline lives in the public CHANGELOG [24].
This timing matters because the original framing many readers carry around -- "Microsoft rewrote its crypto engine after Heartbleed" -- is historically wrong on every axis. SymCrypt predates Heartbleed by seven years [3]. Heartbleed was an OpenSSL heartbeat-extension bug and did not affect SChannel because SChannel does not implement that code path [25]. The article's Section 6 treats this conflation in detail. For now, the honest framing is: SymCrypt was the long, quiet maturation of CNG's primitive layer over a decade, designed by a working Microsoft cryptographer for a substrate already built to accept it. Niels Ferguson's publicly visible work -- including his co-authorship of Cryptography Engineering with Bruce Schneier and Tadayoshi Kohno [26] -- is the closest the public has to a primitive-design rationale for what eventually became SymCrypt.
CNG was Microsoft betting that Windows could keep its Win32 API contract stable while every cryptographic primitive underneath it rotated. The next four sections are the receipts on that bet -- five complete cipher-suite rotations, a parsing-path RCE that almost broke trust in the substrate, and a present-day post-quantum pivot that is the cleanest agility receipt of all.
5. Five Generations of Cipher-Suite Rotation, 2009 to 2025
Between Windows 7 in 2009 [14] and the rolling Windows Server 2022 / 2025 default lists [13], Microsoft rotated every primitive in SChannel's default cipher list at least five times. Not once did IIS, SQL Server, or SslStream get a source-code change because of it. Those five rotations are the agility receipts.
The rough shape of the rotations:
| Generation | Window | New primitive(s) | Old primitive(s) retired | Disablement mechanism | Cryptographic indictment |
|---|---|---|---|---|---|
| G1 | Win7 / 2008 R2, Oct 2009 | ECDHE key exchange + AES-GCM AEAD | Static RSA key transport + AES-CBC + HMAC | New cipher-suite registrations [14] | Lucky13 (2013), BEAST (2011) [27] |
| G2 | KB2868725, Nov 12 2013 -> off-default 2016 | (none added) | RC4 stream cipher | SCH_USE_STRONG_CRYPTO registry value [28] | RC4 NOMORE (75-hour cookie recovery) [29] |
| G3 | Win10 v1903, May 2019 Update | (none added) | 3DES (64-bit block) | Cipher-suite default-off in cipher list [12] | SWEET32 (785 GB / less than 48 h) [30] |
| G4 | 2016-2022 | SHA-256 / SHA-384 handshake signatures | SHA-1 handshake signatures and SHA-1 trust-store roots | Microsoft Trusted Root Program distrust events; chain-engine policy | SHAttered (Feb 2017) [31] |
| G5 | 2020-2025 | TLS 1.3 (default-on Win11 / Server 2022) | TLS 1.0 and TLS 1.1 | SCHANNEL\Protocols\TLS 1.0\<role>\DisabledByDefault [15] | Decade of attack research (BEAST, POODLE, FREAK, Logjam) [32] |
Each row below adds the engineering detail that the table compresses.
G1 -- ECDHE plus AES-GCM (Windows 7 / Server 2008 R2, October 2009)
A key-agreement protocol where both parties generate a fresh elliptic-curve key pair for every handshake and exchange public points; the shared secret is never derived from the long-term server certificate's private key. ECDHE provides forward secrecy: compromising the server's RSA or ECDSA private key tomorrow does not let an adversary decrypt connections recorded today. ECDHE cipher suites first appear in SChannel on Windows 7 / Server 2008 R2 in 2009 [14].
A construction that encrypts plaintext and produces an authentication tag in a single operation, with neither output usable in isolation. AEAD ends an entire class of "mac-then-encrypt vs. encrypt-then-mac" padding-oracle bugs (Lucky13, POODLE-style attacks on CBC) by removing the separable padding step entirely. The AEAD framework is introduced in RFC 5246 §6.2.3.3 [33]; AES-GCM is the canonical instantiation on Windows.
The Windows 7 cipher-suite roster enumerates ECDHE-based suites like TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 and the AES-GCM AEAD suites TLS_RSA_WITH_AES_256_GCM_SHA384 and TLS_RSA_WITH_AES_128_GCM_SHA256 [14]. These suites simply did not exist in the SChannel cipher-suite list of XP / Server 2003. Microsoft was able to add them because the CNG substrate, two years old by Windows 7's RTM, dispatched algorithms by string; the cipher-suite registry just gained new rows that resolved to BCRYPT_ECDH_P256_ALGORITHM and BCRYPT_AES_ALGORITHM with the GCM chaining mode set.
G2 -- RC4 deprecation (KB2868725, November 12 2013, default-off 2016)
In November 2013 Microsoft published Security Advisory 2868725, "Update for Disabling RC4," which introduced the SCH_USE_STRONG_CRYPTO flag in the SCHANNEL_CRED structure and the matching registry mechanism [28]. The press around the advisory was driven by the BEAST attack (2011) -- whose practical mitigation had been to prefer RC4 over CBC suites to dodge the CBC implicit-IV bug -- and by mounting attacks against RC4 itself by AlFardan et al. and others.
The full cryptographic indictment landed at USENIX Security in August 2015: Mathy Vanhoef and Frank Piessens published "All Your Biases Belong to Us: Breaking RC4 in WPA-TKIP and TLS," demonstrating a 75-hour HTTPS cookie recovery against RC4-secured TLS [29]. Six months earlier, Andrei Popov of Microsoft Corp. had authored RFC 7465, "Prohibiting RC4 Cipher Suites" [34]. Edge and IE 11 disabled RC4 by default in late 2016; SChannel's RC4 suites moved to off-by-default on the same trajectory [12]. RFC 7465 ("Prohibiting RC4 Cipher Suites in TLS") was authored by A. Popov of Microsoft Corp. [34] -- the same engineer whose name is on the current Microsoft Learn SChannel SSP overview page [1]. Microsoft's anti-RC4 push was Microsoft-led at the IETF, not just internally.
G3 -- 3DES retirement (Windows 10 v1903, May 2019 Update)
The cryptographic indictment for 3DES is a textbook example of the block-cipher birthday bound. A 64-bit block cipher reaches a 50% probability of an internal collision after roughly encrypted blocks under a single key -- around 32 GB. The SWEET32 paper (Bhargavan and Leurent, ACM CCS 2016) translated that bound into a practical TLS cookie-recovery attack: with 785 GB of induced HTTP traffic over a long-lived 3DES-encrypted connection, an adversary could recover an HTTPS cookie in less than two days [30].
Microsoft moved 3DES cipher suites to off-by-default starting with Windows 10 version 1903 (May 2019 Update). The exact pivot is visible in the per-OS Microsoft Learn cipher-suite tables: TLS_RSA_WITH_3DES_EDE_CBC_SHA appears in the default-enabled list for Windows 10 v1709 and was removed from the default-enabled list for v1903 onward [12][13]. The registry-toggle mechanism is the same SCHANNEL\Ciphers\<algorithm>\Enabled shape that has been in place since the XP era [15]. Crucially, no application changed -- IIS, SQL Server, and SslStream simply stopped negotiating 3DES because the cipher list no longer offered it.
G4 -- SHA-1 sunset (2016 to 2022)
SHA-1 deprecation in SChannel was not a single registry flip; it was a coordinated rotation across the certificate trust pipeline (covered in Section 7), the handshake signature suite, and the trust-store membership of root CAs that issued SHA-1 leaves. Two cryptographic indictments did the load-bearing work. The first was protocol-level: SLOTH (Bhargavan and Leurent, NDSS 2016 [35][36]) showed that an attacker able to compute MD5 or SHA-1 transcript-hash collisions could impersonate one party to the other inside TLS 1.2's client-authenticated handshake by forging matching CertificateVerify signatures -- a direct attack on authentication, not on the primitive's collision resistance in the abstract. The second was primitive-level: SHAttered (Stevens, Bursztein, Karpman, Albertini, Markov; February 2017 [31]) supplied the concrete colliding PDF pair that closed the public debate about SHA-1's safety margin, published as IACR ePrint 2017/190. Two indictments together -- protocol-level via SLOTH, primitive-level via SHAttered -- is the empirically-correct framing for the SHA-1 retirement timeline.
For SChannel specifically the rotation was: SHA-256 / SHA-384 handshake signatures for new connections; chain-engine policy stops accepting SHA-1 leaves for id-kp-serverAuth; and the Microsoft Trusted Root Program distrust events that retired SHA-1 code-signing and TLS certificates from authrootstl.cab over 2016 to 2022. Section 7 walks the trust pipeline in detail; for the agility argument what matters is that none of these required an SslStream change.
G5 -- TLS 1.0 / 1.1 disablement (2020 to 2025)
Microsoft's rollout used the registry pattern from Section 3. Per-application disablement first -- IE 11, Edge legacy, .NET via ServicePointManager.SecurityProtocol, individual server roles -- then OS-level defaults in 2024 and 2025 [15]. The TLS 1.0 / 1.1 lifecycle is the article's clearest data point on the difference between "the substrate can rotate" and "the world will move" (Section 11 returns to this).
The TLS 1.3 side of G5 -- the positive half of the protocol-version rotation -- shipped default-on in Windows Server 2022 (GA August 2021) and Windows 11 (GA October 5, 2021). Windows 10 and Server 2019 SChannel remain TLS 1.2 only [13]. The three TLS 1.3 AEAD suites (TLS_AES_128_GCM_SHA256, TLS_AES_256_GCM_SHA384, TLS_CHACHA20_POLY1305_SHA256) [37] became the default lineup -- another row of new entries in the cipher-suite registry, with the BCrypt providers behind them already shipping.
Five rotations, zero application source changes. But one episode from the same era did break the calm -- a parsing-path remote code execution in SChannel itself, published on Patch Tuesday in November 2014, that the press still routinely confuses with Heartbleed [4]. The agility substrate did not protect Windows from that one.
6. MS14-066 / WinShock: What Happened, What It Was Not
If you searched "SChannel 2014 vulnerability" in late 2014 you got two stories blended together: Heartbleed (April) and the November SChannel RCE everyone called WinShock. They are not the same story. They are not the same vulnerability. They are not even the same vendor. The blending is the single most-misremembered SChannel event, and this article exists in part to set the record straight.
What MS14-066 actually was
On Patch Tuesday, November 11, 2014, Microsoft published Security Bulletin MS14-066 -- "Vulnerability in Schannel Could Allow Remote Code Execution (2992611)" [4]. The vulnerability identifier was CVE-2014-6321. The bulletin's first sentence reads, verbatim:
"This security update resolves a privately reported vulnerability in the Microsoft Secure Channel (Schannel) security package in Windows. The vulnerability could allow remote code execution if an attacker sends specially crafted packets to a Windows server." -- Microsoft Security Bulletin MS14-066, November 11, 2014 [4]
The technical character of the bug was a pre-authentication remote code execution in SChannel's TLS message-parsing path. The NVD record summarises it as "Schannel in Microsoft Windows Server 2003 SP2, Windows Vista SP2, Windows Server 2008 SP2 and R2 SP1, Windows 7 SP1, Windows 8, Windows 8.1, Windows Server 2012 Gold and R2, and Windows RT Gold and 8.1 allows remote attackers to execute arbitrary code via crafted packets" [38]. US-CERT issued Alert TA14-318A confirming the severity and noting the wide platform coverage [39]; CERT/CC published vulnerability note VU#505120 with the same substance [40]. The bulletin was disclosed under coordinated vulnerability disclosure on the standard Patch Tuesday cadence; IBM X-Force researcher Robert Freeman is publicly credited as the discoverer. The "privately reported" phrasing in MS14-066 [4] is Microsoft's standard nomenclature for coordinated-disclosure intake, not a claim that the discovery was internal to Microsoft.
What MS14-066 was not
It was not Heartbleed. Heartbleed (CVE-2014-0160), disclosed April 7, 2014, was a flaw in OpenSSL's TLS Heartbeat extension code path [25]. The bug let an attacker over-read OpenSSL process memory by sending a Heartbeat request whose declared payload length exceeded the actual payload. SChannel does not implement the OpenSSL Heartbeat extension; that code simply did not exist in schannel.dll. Microsoft's MSRC publicly noted in April 2014 that Microsoft Services were not affected by the Heartbleed vulnerability -- the substance held because the affected codebase was OpenSSL's, not Microsoft's [25]. The original April 2014 MSRC blog post stating SChannel was unaffected by Heartbleed has migrated and renders only its page chrome today. The substance is independently anchored by the NVD record for CVE-2014-0160, which explicitly scopes the vulnerability to OpenSSL 1.0.1 through 1.0.1f.
It was not "silently patched," at least not at the headline level. CVE-2014-6321 had a public Patch Tuesday bulletin, contemporary Krebs and NVD coverage, US-CERT and CERT/CC alerts, and proof-of-concept walkthroughs from BeyondTrust and Security Sift within months [40][39]. The "silently patched" framing in the press is the residue of a real but narrower fact: the same KB shipped additional Schannel hardening fixes that were not separately bulletined. The "silently patched" framing of MS14-066 is itself the residue of a real fact -- the November 11, 2014 KB included Schannel hardening fixes that were not separately bulletined. The headline CVE itself was very much public, and the discovery is publicly credited to IBM X-Force researcher Robert Freeman under coordinated vulnerability disclosure. This article does not assign specific CVE IDs to the bundled hardening extras, in line with the project's premise-audit discipline.
What it occasioned
Three lasting effects of MS14-066 are worth naming.
First, the cipher-suite expansion in the same KB. The patch bundled new TLS 1.2 cipher suites (the ECDHE-RSA suites that Windows 7 and Server 2008 R2 had partially supported, broadened across the entire then-supported family). Some operators were caught off guard by the new lineup; the registry-toggle pattern from Section 3 was what got them out of the bind.
Second, a measurable uptick in external SChannel fuzzing. After 2014, the public TLS-stack-testing community treated SChannel as a first-class target, not as a closed-source black box no one could meaningfully probe. The most visible artifact is Hubert Kario's TLS-Fuzzer at Red Hat -- a test suite that, in the project's own framing, "doesn't check only that the system under test didn't crash, it checks that it returned correct error messages" [41]. Section 11 returns to TLS-Fuzzer as the closest public substitute for a behavioural specification of SChannel.
Third, the lesson the substrate could not absorb: algorithm-agility does not extend to the parsing path. The wire-format state machine has to be correct because no provider model can fix a bug in schannel.dll itself. CNG could rotate primitives without rewriting SChannel; CNG could not rotate SChannel's TLS message parser. That asymmetry is structural and remains true today.
What it was not, part two: not the trigger for SymCrypt
Some narratives connect MS14-066 to a "SChannel rewrite" or a "FIPS rewrite" project that followed. The dates do not support either framing. SymCrypt was started by Niels Ferguson in late 2006, with the first sources committed in February 2007 [3] -- seven years before Heartbleed and eight years before MS14-066. SymCrypt became the primary library for symmetric algorithms with Windows 8 (October 2012, before MS14-066) and the primary library for all algorithms across Windows starting with the Windows 10 1703 release in March 2017. Open-sourcing followed under the MIT license in July 2019 [3]. The honest story is that SymCrypt was the maturation of CNG's primitive layer over a decade; it had no causal relationship to either 2014 disclosure.
MS14-066 taught Microsoft that the substrate's algorithm-agility property does not extend to the parsing path -- the wire-format state machine has to be correct because no provider model can fix a bug in schannel.dll itself. The next section turns to the other load-bearing path: not the bytes on the wire, but the certificate the server presents to authenticate.
7. The Certificate-Validation Pipeline: CertGetCertificateChain, OCSP, and the Microsoft Trusted Root Program
The other half of any TLS handshake is trust. Bytes can be encrypted with the strongest AEAD in the SymCrypt CHANGELOG and the handshake can use a quantum-resistant key exchange -- and the whole exchange still means nothing if the certificate the server presents traces back to an attacker-controlled CA. On Windows, that whole question routes through one API: CertGetCertificateChain.
The chain engine
CertGetCertificateChain walks from leaf to trusted root using Authority Key Identifier / Subject matching, fetches any missing intermediates via the certificate's Authority Information Access (AIA) caIssuers URL, and resolves against the local Microsoft Trusted Root Store. The store itself is kept current through the crypt32.dll auto-update mechanism, which downloads a signed authrootstl.cab periodically and updates the trust list in place.
Per-certificate checks follow the X.509 PKI profile (RFC 5280, May 2008) [42]:
- Signature verification -- each cert is signed by the next-up cert's private key.
- Validity --
notBefore/notAfterwithin the current time. - Key Usage and Extended Key Usage -- the leaf must include
id-kp-serverAuth(1.3.6.1.5.5.7.3.1) for a TLS server presentation, and the chain's intermediates must permitserverAuthin their EKU constraints if they declare any. - Basic Constraints -- non-leaf certs must have
cA=TRUE. - Name Constraints -- per RFC 5280 §4.2.1.10, intermediates may declare
permittedSubtreesandexcludedSubtreesover DNS names, IP ranges, and other name forms; the chain engine enforces these against the leaf's SAN. - Revocation -- per-cert, against the chosen revocation source.
CertVerifyCertificateChainPolicy then layers protocol-specific overlays on top of that purely structural validation. The most important for TLS is CERT_CHAIN_POLICY_SSL, which adds the SNI / SAN hostname match and TLS-specific server-auth constraints.
Diagram source
flowchart TD
A["Leaf certificate from TLS handshake"] --> B["Chain engine -- CertGetCertificateChain"]
B --> C{"Path build via AKI / SKI matching, AIA caIssuers fetch"}
C --> D["Per-cert structural checks (RFC 5280)"]
D --> E{"Revocation source"}
E --> F[CRL distribution point]
E --> G[OCSP responder]
E --> H[OCSP stapled response]
D --> I["CertVerifyCertificateChainPolicy"]
I --> J{"CERT_CHAIN_POLICY_SSL -- SNI / SAN match, serverAuth EKU"}
J --> K[Chain valid for TLS server]
F --> I
G --> I
H --> I Revocation: CRL, OCSP, OCSP stapling
The Online Certificate Status Protocol (RFC 6960) lets a client ask the issuing CA's OCSP responder whether a specific certificate is revoked, by serial number [43]. Plain OCSP is a separate request to the CA on every connection, which leaks visited hostnames to the CA and adds latency. OCSP stapling lets the server fetch a fresh signed OCSP response on a schedule and "staple" it into the TLS handshake via the status_request extension -- the client gets the same revocation proof without the side channel. SChannel consumes stapled OCSP responses through the status_request extension (RFC 6066 §8 for TLS 1.2, RFC 8446 §4.4.2.1 for TLS 1.3 [37]) and feeds the result into the chain engine.
A practical SChannel deployment combines CRL fetching, OCSP, and OCSP stapling: stapling preferred when present, OCSP fallback when not, CRL as the long-tail safety net. IIS's stapling support is on by default in modern releases; turning it off is the wrong default for any internet-facing endpoint.
The Microsoft Trusted Root Program and the CCADB
SChannel's trust posture inherits the Microsoft Trusted Root Program's membership decisions. Microsoft does not run the trust program in isolation. It participates in the Common CA Database (CCADB) alongside Mozilla, Google, and Apple, sharing root inclusion / removal / audit data across the major root stores [44]. The CCADB Resources page lists the public extractions (Microsoft's TLS roots, Mozilla's TLS roots, code-signing roots, S/MIME roots) and the program-specific report URLs.
The governance flow is documented end-to-end on the Microsoft Trusted Root Program program-requirements page [45]. Membership requires annual WebTrust or ETSI EN 319 411 audits, full CCADB disclosure of the PKI hierarchy, and adherence to the technical requirements (minimum key sizes, signature-algorithm policy, extension constraints, name-form profiles). Distrust decisions can be triggered by (a) CCADB-coordinated cross-vendor consensus where Microsoft acts alongside Mozilla, Apple, and Google; (b) unilateral Microsoft action when the program judges a CA below the bar; or (c) audit-failure findings that fail to remediate inside an agreed window.
Propagation to Windows clients goes through two signed trust lists distributed via the Automatic Root Update mechanism: authrootstl.cab carries the currently-trusted roots together with per-EKU enablement bits, and disallowedcertstl.cab is the explicit untrust list. Both are fetched by crypt32.dll from http://ctldl.windowsupdate.com/... on a periodic schedule and consumed by the chain engine on its next chain build. The SChannel SSP itself does not maintain a separate trust list; it inherits whatever CertGetCertificateChain resolves against the auto-updated stores.
Diagram source
flowchart TB
A[CA submits audit, CCADB disclosure, technical compliance] --> B[Microsoft Trusted Root Program review]
B --> C{"Decision -- include, distrust, NotBefore-date schedule"}
C --> D[CCADB cross-vendor coordination -- Mozilla, Apple, Google]
C --> E[authrootstl.cab updates]
C --> F[disallowedcertstl.cab updates]
E --> G[ctldl.windowsupdate.com distribution]
F --> G
G --> H[crypt32.dll Automatic Root Update on client]
H --> I[CertGetCertificateChain consults updated stores]
I --> J[SChannel SSP handshake trust decision] Two worked examples: DigiNotar (2011) and Symantec (2018)
The MTRP governance flow looks abstract until two real distrust events make it concrete.
DigiNotar -- August / September 2011 -- panic-mode revocation. Microsoft Security Advisory 2607712 ("Fraudulent Digital Certificates Could Allow Spoofing") was published on August 29, 2011 and updated through September 19 to version 5.0 [46]. The Dutch CA DigiNotar's signing infrastructure had been breached by an attacker who issued fraudulent certificates for *.google.com and other high-value names. Microsoft, Mozilla, Apple, and Google removed DigiNotar's roots from their trust stores within days. The Microsoft-side propagation pushed the DigiNotar Root CA out of authrootstl.cab and added the relevant entries to disallowedcertstl.cab; clients on the Automatic Root Update pipeline picked up the change within the next refresh cycle. SChannel's chain engine then refused to validate any leaf signed under the DigiNotar hierarchy -- not because SChannel changed, but because the trust store it consults changed underneath it.
Symantec deprecation -- October 2018 -- planned per-NotBefore-date schedule. The Symantec distrust is the cleanest published example of how a CCADB-coordinated planned deprecation differs from a panic-mode revocation. Microsoft's October 4, 2018 Security Blog post documents the four-vendor (Microsoft, Mozilla, Apple, Google) coordinated schedule, keyed on the certificate's NotBefore date rather than on the root itself: per the per-root table in the blog post, the relevant cut-overs were September 30, 2018; January 31, 2019; and January 1, 2020 [47]. Certificates issued before the per-root NotBefore date stayed trusted to their natural expiration; certificates issued after were rejected. The mechanism on the SChannel side is unchanged from DigiNotar -- the chain engine reads the updated trust posture from authrootstl.cab / disallowedcertstl.cab and applies it on the next chain build -- but the operational character is completely different: a years-long planned phase-out instead of a week-long emergency cleanup.
Enterprise observability: CAPI2/Operational event IDs
The governance flow ends at the operator's host -- but only if the operator can see it land. The Microsoft Learn troubleshooting article on the May 24, 2022 removal of the U.S. Federal Common Policy CA "G1" root carries the canonical observability recipe [48]. On any Windows host you can enable the per-event tracing channel with wevtutil sl Microsoft-Windows-CAPI2/Operational /e:true and then watch the Event Viewer under Applications and Services Logs > Microsoft > Windows > CAPI2 > Operational for the chain-engine events that the FCPCA-removal article enumerates verbatim [48]: Event ID 90 logs every certificate consulted during chain building, Event ID 11 records chain-build failures, Event ID 30 records SSL or NTAuth policy-layer failures, Events 40-43 show stored CRLs and AIA paths, and Events 50-53 show network CRL accesses. The same article documents the empirical post-distrust propagation window in plain language: "Applications and operations that depend on the 'G1' root certificate will fail one to seven days after they receive the root certificate update." That one-to-seven-day window is the realistic latency budget between an MTRP distrust event landing in authrootstl.cab and a given Windows host actually applying it -- a fingerprint operators can validate per host, not just per the rollout calendar.
The PowerShell complement is brief and worth keeping in the muscle memory: Get-ChildItem Cert:\LocalMachine\AuthRoot enumerates the currently-trusted roots; Get-ChildItem Cert:\LocalMachine\Disallowed enumerates the disallowed store; both reflect whatever the last crypt32.dll Automatic Root Update cycle left in place.
A cautionary tale: CVE-2020-0601, "Curveball"
In January 2020 the NSA disclosed a chain-engine spoofing vulnerability in crypt32.dll's ECC certificate validation [49][50]. The bug let an attacker craft a fraudulent ECC certificate that the Windows chain engine would treat as having been signed by a trusted root, by failing to fully verify the curve parameters against the cached trusted root's curve. Curveball is strictly a crypt32.dll bug, not a SChannel SSP bug -- but it shaped the SChannel posture in two ways. First, it demonstrated that the chain engine and the SSP are equally load-bearing for "is this TLS connection trustworthy?" Second, it was the most prominent example of the NSA disclosing a Windows vulnerability via the regular MSRC channel rather than hoarding it. Microsoft's January 2020 Patch Tuesday cycle addressed CVE-2020-0601 ahead of any public proof-of-concept.
Certificate validation is the other axis on which SChannel has had to evolve. With the substrate (Sections 4 through 6) and the trust pipeline (this section) both stabilised, the article now turns to what "modern SChannel" actually looks like in the field -- TLS 1.3 on by default, TPM-backed server keys available for compliance scenarios, and the LSA-protection moat that makes session-key extraction harder than it used to be.
8. Modern SChannel: TLS 1.3, CredSSP for RDP, TPM-Backed Keys, and the LSASS Moat
By mid-2026 a default Windows 11 or Server 2022 / 2025 box is doing things its 2019 equivalent could not. TLS 1.3 is on. CredSSP wraps the RDP credential-delegation path inside a SChannel-protected TLS tunnel [51]. The TPM is available as a key custodian. LSASS is a Protected Process; on most newer Windows 11 builds, Credential Guard is on by default. These are not four independent stories; they are four layers of the same defence-in-depth posture for the modern SChannel-served TLS endpoint.
TLS 1.3 in SChannel
RFC 8446 (Eric Rescorla, Mozilla, August 2018) [37] is the protocol generation that SChannel finally ships default-on in Windows Server 2022 (GA August 2021) and Windows 11 (GA October 5, 2021). Windows 10 and Windows Server 2019 SChannel remain TLS 1.2 only -- a fact worth naming because it is the most common cause of confusion in mixed-version Windows fleets [13].
What changed at the wire-format level matters less for SChannel than how cleanly the changes mapped through CNG. TLS 1.3 shrank the cipher-suite menu to three AEAD suites: TLS_AES_128_GCM_SHA256, TLS_AES_256_GCM_SHA384, and TLS_CHACHA20_POLY1305_SHA256 [37]. The key-share namespace separated from the cipher-suite namespace -- supported_groups (X25519, secp256r1, secp384r1, and now X25519MLKEM768) is an independent extension from cipher_suites. The handshake collapsed to one round trip. The 0-RTT (early data) feature of TLS 1.3 trades a round trip for replay-resistance complexity. SChannel's posture on 0-RTT is conservative: clients can request it, servers default to off unless explicitly opted in, and the documentation flags the replay-protection trade-offs.
The downgrade-resistance sentinel in ServerHello.random (RFC 8446 §4.1.3) is worth a beat. A TLS 1.3 server that, for whatever reason, is negotiated down to TLS 1.2 or below by middlebox interference fills the last eight bytes of its ServerHello.random with one of two well-known sentinels (44 4F 57 4E 47 52 44 01 for "downgraded from 1.3 to 1.2"; 44 4F 57 4E 47 52 44 00 for "downgraded from 1.3 to 1.1 or earlier"). A genuinely TLS 1.3-capable client checks for the sentinel after the handshake and aborts on mismatch. This puts the active-downgrade-attack envelope inside TLS 1.3 at a much narrower place than it was in TLS 1.2.
Diagram source
sequenceDiagram
participant App as Application
participant SC as SChannel SSP
participant CNG as BCrypt / NCrypt
participant Peer as Remote endpoint
App->>SC: AcquireCredentialsHandle (server cert, key handle)
App->>SC: InitializeSecurityContext (first call)
SC->>CNG: BCrypt ECDH or MLKEM key share
SC->>Peer: ClientHello (cipher_suites, supported_groups, key_share)
Peer-->>SC: ServerHello, EncryptedExtensions, Certificate, CertVerify, Finished
SC->>CNG: NCryptSignHash or NCrypt key derive
SC->>App: SECBUFFER tokens, then SEC_E_OK
App->>SC: EncryptMessage and DecryptMessage on every record CredSSP and the Remote Desktop NLA path
The SSPI provider in credssp.dll that securely delegates Windows credentials from a client to a target server inside a TLS-protected tunnel. CredSSP is the SSP that backs Remote Desktop Network Level Authentication (RDP NLA): it wraps a SChannel TLS handshake, tunnels an SPNEGO / Kerberos / NTLM authentication inside that tunnel, performs a channel-binding hash exchange, and finally transmits the user's credential material to the destination encrypted under the SSPI session key [51][52].
The Microsoft Open Specifications page for the Credential Security Support Provider Protocol ([MS-CSSP], version 21.0, April 2024) [51] defines the protocol that backs Remote Desktop NLA. CredSSP is not a TLS protocol of its own; it is an SSP that uses SChannel as its transport. The relationship is structural -- CredSSP is one of the most consequential consumers of SChannel inside Windows, and almost every RDP session opened against a modern Windows host runs the CredSSP-over-SChannel sequence before the RDP video stream even starts.
The five-step CredSSP-over-TLS sequence per the open-spec "Processing Events and Sequencing Rules" page [53]:
- TLS handshake. The CredSSP client and CredSSP server complete the SChannel TLS handshake; only the server presents a certificate, so the TLS-layer client is anonymous. After this step, all subsequent CredSSP messages are encrypted by the TLS channel. The MS-CSSP spec is explicit that "the CredSSP Protocol does not extend the TLS wire protocol" and that "TLS session resumption is not supported" [53].
- SPNEGO / Kerberos / NTLM tunnelled inside TLS. Authentication tokens are carried in the
negoTokensfield of the protocol'sTSRequestASN.1 structure. The negotiation is performed by the SSPI Negotiate provider, which usually selects Kerberos when the client is domain-joined and falls back to NTLM otherwise. - Public-key (channel-binding) hash exchange. This is the post-CVE-2018-0886 mechanism. The client computes a SHA-256 hash over a fixed magic string concatenated with a nonce and the server's
SubjectPublicKey, encrypts that hash under the SSPI session key established in step 2, and sends it in thepubKeyAuthfield ofTSRequest. The earlier (v2 / v3 / v4) "encrypt the public key + 1" scheme that was broken by CVE-2018-0886 has been replaced by this channel-binding hash for protocol versions 5 and 6 of CredSSP. - Server-side hash response with the server magic. The server computes its own version of the hash (using a different fixed magic string for the server-to-client direction), encrypts it under the session key, and returns it in its own
pubKeyAuth. Both sides have now proven they hold the same session key bound to the same server public key, which closes a class of man-in-the-middle attacks against the inner authentication. - Encrypted credential transfer in
authInfo. The credentials themselves -- aTSPasswordCreds,TSSmartCardCreds, orTSRemoteGuardCredsstructure depending on the chosen logon style -- are encrypted under the SSPI session key and transmitted in theauthInfofield. The destination decrypts them insidelsass.exe(a PPL-protected process when RunAsPPL is enabled, see below), and the operating system then uses them to log the user on.
Diagram source
sequenceDiagram
participant Client as RDP client
participant Server as RDP server
Note over Client,Server: Step 1 -- SChannel TLS handshake, server cert only, client anonymous at TLS layer
Client->>Server: ClientHello
Server-->>Client: ServerHello, Certificate, ServerHelloDone (TLS 1.2) or one-RTT TLS 1.3 equivalent
Client->>Server: Finished -- TLS tunnel up
Note over Client,Server: Step 2 -- SPNEGO Kerberos or NTLM tokens inside TSRequest.negoTokens, all inside TLS
Client->>Server: TSRequest with negoTokens (Kerberos AP-REQ or NTLM Type 1)
Server-->>Client: TSRequest with negoTokens (Kerberos AP-REP or NTLM Type 2 then 3)
Note over Client,Server: Step 3 -- channel-binding hash, client side (replaces broken pre-CVE-2018-0886 scheme)
Client->>Server: TSRequest.pubKeyAuth -- E(sessionKey, SHA256(client-magic, nonce, server SubjectPublicKey))
Note over Client,Server: Step 4 -- server-side hash response with server-magic
Server-->>Client: TSRequest.pubKeyAuth -- E(sessionKey, SHA256(server-magic, nonce, server SubjectPublicKey))
Note over Client,Server: Step 5 -- encrypted credentials in TSRequest.authInfo
Client->>Server: TSRequest.authInfo -- E(sessionKey, TSPasswordCreds or TSSmartCardCreds or TSRemoteGuardCreds)
Note over Server: lsass.exe decrypts, logs the user on The NLA threat-model framing per the archived Server 2008 R2 TechNet content is worth quoting because it captures what NLA actually buys [54]. NLA forces user authentication before RDP session resources are allocated: "It requires fewer remote computer resources initially. The remote computer uses a limited number of resources before authenticating the user, rather than starting a full remote desktop connection as in previous versions. It can help provide better security by reducing the risk of denial-of-service attacks." The two concrete payoffs are pre-auth DoS resistance and pre-auth RDP-codepath RCE mitigation. BlueKeep (CVE-2019-0708) and DejaBlue (CVE-2019-1181 / 1182) would each have been substantially harder to exploit on NLA-enabled hosts because the vulnerable RDP code paths sit behind the NLA gate. NLA has been on by default for RDP Session Hosts since Windows Server 2012 R2.
TPM-backed server keys via the Microsoft Platform Crypto Provider
The Microsoft Platform Crypto Provider (PCP) is a KSP that stores private keys non-exportable inside TPM 2.0. For an IIS or SslStream server, switching to a PCP-backed certificate means the certificate's private key never resides in software memory; CertificateVerify signing during the handshake dispatches through NCryptSignHash to PCP to TPM2_Sign.
Two caveats need stating plainly. First, PCP-backed key operations are slower than software-backed key operations -- TPM 2.0 ECDSA / RSA signing latency is in the tens of milliseconds, which is a hard cap on handshake throughput. A high-volume edge IIS workload cannot meet its handshake-rate SLA with TPM-backed keys. Second, production prevalence of PCP-backed server keys remains low outside specific compliance scenarios. The capability is shipping; the typical pattern is software-backed keys at the edge and TPM-backed keys for long-lived service-identity certificates where the latency does not dominate. TPM 2.0 signing latency is the ceiling for TPM-backed TLS handshake throughput. A high-volume IIS edge cannot meet handshake-rate SLAs with TPM-backed keys; that is why the typical pattern is software-backed keys at the edge and TPM-backed keys for service identity at lower call rates.
LSA Protection (RunAsPPL) and Credential Guard
A Windows process-protection lattice where a "protected" process can be opened for certain rights only by callers whose protection level is greater than or equal to the target's. When LSASS runs as a PPL (via HKLM\SYSTEM\CurrentControlSet\Control\Lsa\RunAsPPL), a non-PPL caller's OpenProcess(LSASS, PROCESS_VM_READ, ...) returns ERROR_ACCESS_DENIED. PPL is a same-privilege gate: it operates entirely inside Virtual Trust Level 0 (VTL0), the normal kernel/user world [55][56].
LSASS holds the cleartext session keys SChannel derives for each active TLS connection. Historically Mimikatz's sekurlsa::schannel command read those keys directly out of LSASS memory after a debug-privilege OpenProcess. Once RunAsPPL is enforced, the read fails: a non-PPL Mimikatz cannot open LSASS for memory read [55].
Clément Labro's RunAsPPL analysis (itm4n) is the canonical practitioner's text on the gotchas [56]. The single most important framing point Labro makes is the disambiguation between PPL and Credential Guard:
"When it comes to protecting against credentials theft on Windows, enabling LSA Protection (a.k.a. RunAsPPL) on LSASS may be considered as the very first recommendation to implement... Credential Guard and LSA Protection are actually complementary." -- Clément Labro, Do You Really Know About LSA Protection (RunAsPPL)? [56]
The disambiguation matters because the two mechanisms operate at different layers. PPL is a same-privilege gate inside VTL0. Credential Guard moves credential material into the LSAIso trustlet at VTL1, behind the VBS / Hyper-V boundary -- a cross-privilege isolation that PPL cannot provide [57]. The misconception that Credential Guard alone defeats mimikatz sekurlsa::schannel is one of the most common operator errors in this space. They stack. They are not substitutes.
Diagram source
flowchart TB
subgraph VTL0["VTL0 -- Normal World"]
subgraph User["User mode"]
App["Mimikatz / arbitrary code -- non-PPL"]
end
subgraph Kern["Kernel mode (NT kernel)"]
LSASS["LSASS -- PPL when RunAsPPL=1"]
SCh[schannel.dll loaded in LSASS]
end
end
subgraph VTL1["VTL1 -- Isolated User Mode (VBS)"]
LSAIso["LSAIso trustlet -- Credential Guard"]
end
App -. "OpenProcess(LSASS, VM_READ) -- denied when PPL on" .-> LSASS
LSASS -. "RPC to LSAIso for credential ops" .-> LSAIso
SCh --> LSASS The last open question on RunAsPPL is whether the protection itself is bypassable. The honest answer is "less so than it used to be." Labro's follow-up "The End of PPLdump" walks through how a 2021-era SymLink + KnownDlls trick that defeated PPL was patched, and how the post-patch PPL invariant holds for current Windows servicing branches [58]. Combined with HVCI and VBS-on-by-default in newer Windows 11 builds, the modern SChannel session key is genuinely harder to lift than it was in 2019.
Modern SChannel is the substrate plus the trust pipeline plus the CredSSP RDP wrapper plus the LSASS moat. The one piece still in flight as of mid-2026 is the cryptographic primitive nobody had in 2009 -- the post-quantum hybrid key exchange.
9. The Post-Quantum Pivot: ML-KEM, SymCrypt, and Hybrid TLS 1.3
On August 13, 2024, NIST published FIPS 203 -- the standard for ML-KEM, the first quantum-resistant key-encapsulation mechanism the United States government endorses for production use [5]. The standard defines three parameter sets (ML-KEM-512, ML-KEM-768, ML-KEM-1024) with security grounded in the Module Learning With Errors problem. The SymCrypt CHANGELOG entry for v103.5.0 reads, verbatim: "Add ML-KEM per final FIPS 203" [24]. That single line is what the receipts on Microsoft's twenty-year algorithm-agility bet look like in the present tense.
A KEM is a public-key construction that, given a recipient's public key, produces a (ciphertext, shared-secret) pair such that the recipient can recover the shared secret from the ciphertext using its private key. ML-KEM is the NIST-standardised KEM derived from the CRYSTALS-Kyber proposal; ML-KEM-768 generates a 1184-byte public key and a 1088-byte ciphertext and produces a 32-byte shared secret. FIPS 203 [5] is the final standard; SymCrypt v103.5.0 is the first SymCrypt release shipping ML-KEM per that standard [24].
What is shipping
The PQC primitives Microsoft has rolled into SymCrypt are publicly tracked in the project CHANGELOG [24]:
- v103.5.0 -- ML-KEM (FIPS 203) [5].
- v103.6.0 -- LMS (NIST SP 800-208 stateful hash-based signature) and AES-KW(P).
- v103.7.0 -- ML-DSA (FIPS 204) [59].
- v103.11.0 -- Composite ML-KEM (hybrid ML-KEM with a classical KEM).
- v103.12.0 -- Composite ML-DSA (hybrid ML-DSA with a classical signature scheme).
- v103.12.1 -- AVX-512 AES-GCM (up to ~35% throughput improvement).
CNG exposes the matching BCRYPT_MLKEM_ALG_HANDLE with parameter-set selectors -- BCRYPT_MLKEM_PARAMETER_SET_768, BCRYPT_MLKEM_PARAMETER_SET_1024, and so on [6]. The Microsoft Learn page for the CNG ML-KEM API surface carries an explicit "prerelease product / Windows Insider Preview" banner. The article therefore frames SChannel's PQC support as preview / Insider-channel as of mid-2026, with broader GA rollout in flight; the Microsoft Tech Community PQC announcement (December 2024) is the narrative anchor and the Insider-Preview banner on the API doc is the technical hedge [60][61].
The hash-based and stateless-hash-based signature side of PQC (SLH-DSA, FIPS 205 [62]) is shipping in SymCrypt and CNG along the same trajectory. Section 11 returns to why the signature-side PQC transition is harder than the KEM-side transition.
Hybrid TLS 1.3 key exchange: X25519MLKEM768
The IETF-converged named group for the most-deployed hybrid is X25519MLKEM768, defined in draft-ietf-tls-ecdhe-mlkem (Kris Kwiatkowski / PQShield, Panos Kampanakis / AWS, Bas Westerbaan / Cloudflare, Douglas Stebila / University of Waterloo; currently -05 as of May 26, 2026) [63]. The draft also defines SecP256r1MLKEM768 and SecP384r1MLKEM1024 for deployments that prefer NIST curves over X25519.
The handshake mechanics are clean. The client sends mlkem_pk || x25519_pk (1184 + 32 = 1216 bytes) in its key_share; the server responds with mlkem_ct || x25519_pk (1088 + 32 = 1120 bytes); both sides compute shared_secret = mlkem_ss || x25519_ss (32 + 32 = 64 bytes) and feed that into TLS 1.3's HKDF-Extract as IKM.
Diagram source
sequenceDiagram
participant Client
participant Server
Note over Client: Generate X25519 keypair and ML-KEM-768 keypair
Client->>Server: ClientHello with key_share (mlkem_pk concatenated with x25519_pk -- 1216 B)
Note over Server: Generate X25519 keypair, ML-KEM encapsulate to client mlkem_pk
Server->>Client: ServerHello with key_share (mlkem_ct concatenated with x25519_pk -- 1120 B)
Note over Client: X25519 DH, ML-KEM decapsulate
Note over Client,Server: shared_secret -- mlkem_ss concatenated with x25519_ss (64 B)
Note over Client,Server: HKDF-Extract over shared_secret continues TLS 1.3 key schedule The construction is defence-in-depth against either a classical-only break or a quantum-only break: an adversary must defeat both X25519 and ML-KEM-768 to recover the session key, and the hybrid analysis (Bindel et al., PQCrypto 2019) shows the construction is at least as secure as the stronger of the two components. The minor cost is the inflated ClientHello and ServerHello (about 1.2 KB extra) and a couple of milliseconds of ML-KEM operations.
// Pseudocode for the X25519MLKEM768 shared-secret concatenation.
// In real SChannel: BCryptSecretAgreement(BCRYPT_ECDH_P256_ALGORITHM_HANDLE / X25519, ...)
// + BCryptDecapsulate(BCRYPT_MLKEM_ALG_HANDLE, parameter_set = 768, ...)
function x25519_dh(privA, pubB) { return new Uint8Array(32).fill(0xAA); } // 32 B
function mlkem768_decaps(privA, ct) { return new Uint8Array(32).fill(0xBB); } // 32 B
const x25519_ss = x25519_dh('clientX25519Priv', 'serverX25519Pub');
const mlkem_ss = mlkem768_decaps('clientMLKEMPriv', 'serverMLKEMCt');
const hybrid_secret = new Uint8Array(64);
hybrid_secret.set(mlkem_ss, 0);
hybrid_secret.set(x25519_ss, 32);
console.log('IKM length for HKDF-Extract:', hybrid_secret.length, 'bytes');
console.log('First byte: 0x' + hybrid_secret[0].toString(16),
'(from ML-KEM half, defends against quantum break)');
console.log('Byte 32: 0x' + hybrid_secret[32].toString(16),
'(from X25519 half, defends against classical break)'); The agility payoff
This rotation is the cleanest demonstration of Section 4's thesis. Adding X25519MLKEM768 to SChannel required: (a) a SymCrypt primitive (v103.5.0+ for ML-KEM, with X25519 long present per RFC 7748 [64]); (b) a new BCrypt provider registration (BCRYPT_MLKEM_ALG_HANDLE and the hybrid named-group plumbing); (c) a new SChannel named-group entry. No IIS source change. No SQL Server source change. No SslStream source change. Eighteen years after Vista shipped CNG, the substrate is producing receipts for a brand-new algorithm family.
The deployment side is moving faster than most ten-year forecasts in cryptography ever predicted. Cloudflare's measurements (March 2024) put PQC-secured TLS 1.3 connections at "nearly two percent" of inbound, with the team forecasting double-digit percentages by end of 2024 [65]. Cloudflare's origin-side PQC rollout has been live since September 2023 [66]. Chrome / BoringSSL, Edge (via BoringSSL), and Firefox / NSS ship X25519MLKEM768 client-side. OpenSSL 3.5 ships ML-KEM. Server-side SChannel adoption is rolling through the Insider channel and the official Tech Community posts as of mid-2026 [6][61]. Cloudflare's measurements (March 2024) put PQC-secured TLS 1.3 connections at "nearly two percent" of their inbound; by the end of 2024 they expected double-digit percentages [65]. The transition is moving faster than most ten-year forecasts in cryptography ever predicted.
If the PQC rotation is the agility payoff, the next question is the obvious one: how does SChannel's answer compare to the other TLS stacks shipping on the same calendar? OpenSSL, BoringSSL, NSS, and Apple's Network framework have all had to solve the same algorithm-agility problem -- and they have all made different trade-offs.
10. Competing Approaches: How Other TLS Stacks Solve Algorithm Agility
Algorithm agility is not a property of TLS-the-protocol. It is a property of the substrate underneath the protocol. Every major TLS implementation has had to answer the same question -- "how do we add a new primitive without breaking our consumers?" -- and the answers are surprisingly different.
| Stack | Substrate model | Stability commitment | PQC integration as of mid-2026 |
|---|---|---|---|
| SChannel / CNG | BCrypt providers + NCrypt KSPs; Win32 API-stable [2] | Strong: Win32 SSPI surface frozen | ML-KEM in SymCrypt v103.5.0 [24]; X25519MLKEM768 Insider Preview [6] |
| OpenSSL 3.x | OSSL_PROVIDER modules via OSSL_DISPATCH arrays [@openssl-provider7-3.0] | Strong-by-major-version | OQS-Provider for early PQC; ML-KEM in OpenSSL 3.5 |
| BoringSSL | Single source tree; "rolling release"; no provider model [10] | Explicitly none ("no guarantees of API or ABI stability") | X25519MLKEM768 shipping; consumer vendoring required |
| NSS | OASIS PKCS #11 v3.1 modules via CK_FUNCTION_LIST [@nss-3.111-release-notes][@oasis-pkcs11-v3.1] | Strong (Firefox compatibility) | ML-KEM via PKCS #11 v3.1 C_Encapsulate / C_Decapsulate; X25519MLKEM768 in Firefox 132 |
| Apple Network framework / Secure Transport | Framework-version pinning per OS release [67][68] | Strong per OS version | Hybrid KEM shipping in newer Network framework releases |
.NET SslStream cross-platform | Delegates to host OS stack [11] | Strong per .NET version | Inherits underlying stack's PQC support |
OpenSSL 3.x: OSSL_PROVIDER, explicit contexts, three in-tree providers
OpenSSL 3.0 replaced the older ENGINE model with the OSSL_PROVIDER system, described in the provider(7) manpage as "a unit of code that provides one or more implementations for various operations for diverse algorithms" [@openssl-provider7-3.0]. A provider exposes its operations through an OSSL_DISPATCH array of {function-id, function-pointer} pairs. The loader's entry point is a single exported function with this exact signature [@openssl-provider7-3.0]:
int OSSL_provider_init(const OSSL_CORE_HANDLE *handle,
const OSSL_DISPATCH *in,
const OSSL_DISPATCH **out,
void **provctx);
The in array gives the provider the callbacks the OpenSSL library is willing to provide to the provider (logging, error reporting, library-context queries, parameter accessors) [@openssl-provider-base7-3.0]; the out array is filled in by the provider with the operations it implements. The provctx is the provider's own per-instance state.
OpenSSL 3.0 ships three in-tree providers: default (the modern algorithm set), legacy (RC4, MD4, IDEA, and other backward-compatibility primitives), and FIPS (the FIPS 140-3-validated subset). Out-of-tree, the OQS-Provider plugs PQC primitives into OpenSSL without recompiling the OpenSSL build itself. The substantive contrast with CNG: OpenSSL makes the provider context an explicit parameter via OSSL_LIB_CTX *, which means multiple isolated provider sets can coexist inside one process (a FIPS-validated workload and a legacy workload in the same binary). CNG keeps provider dispatch global per-process. Both models are functionally agile; OpenSSL's is more compositional at runtime, while CNG's is more governed through the Windows servicing branch.
BoringSSL's anti-agility position
BoringSSL is Google's TLS stack used by Chromium and (via Chromium) Microsoft Edge. The project README says, verbatim:
"Although BoringSSL is an open source project, it is not intended for general use, as OpenSSL is. We don't recommend that third parties depend upon it. Doing so is likely to be frustrating because there are no guarantees of API or ABI stability." -- BoringSSL README [10]
BoringSSL achieves agility by refusing to absorb it as a public API. Consumers vendor BoringSSL into their own build tree and accept the lift of tracking head. Chromium does this; Edge inherits it; cURL ships configurations that link against BoringSSL when the consumer asks for it. The model is the inverse of CNG's: maximum velocity for the maintainer, maximum churn for the consumer. For a vendor whose chief constraint is API stability for the Win32 / .NET universe, BoringSSL's model is structurally incompatible. For a vendor whose chief constraint is shipping the modern internet's TLS posture into a browser monthly, BoringSSL's model is the right answer.
NSS and PKCS #11
Mozilla's NSS predates almost every other stack here and uses the OASIS PKCS #11 (Cryptoki) module standard as its agility hinge [@oasis-pkcs11-v3.1]. A PKCS #11 module exposes a single entry point, C_GetFunctionList(CK_FUNCTION_LIST_PTR_PTR ppFunctionList), which returns a table of roughly seventy function pointers. Those functions are organised around a three-level hierarchy: a slot is a place where a token sits; a token holds objects (keys, certificates, data); cryptographic operations are invoked against an object referenced by CK_OBJECT_HANDLE and parametrised by a CK_MECHANISM (e.g. CKM_AES_GCM, CKM_ECDH1_DERIVE).
NSS itself ships two PKCS #11 modules out of the box: the NSS softoken (softokn3, in-process software primitives) and the NSS FIPS softoken (the FIPS-validated variant). Hardware PKCS #11 modules for HSMs and smart cards load through the same SECMOD_LoadUserModule API. PQC arrived in NSS via PKCS #11 v3.1's KEM operations: C_Encapsulate and C_Decapsulate are standardised verbs that ML-KEM-768 implementations can expose without needing the historic CKM_VENDOR_DEFINED mechanism-ID reservation pattern. NSS 3.111 (released April 28, 2025) is the release marker for full PKCS #11 v3.1 adoption in NSS [@nss-3.111-release-notes]; Firefox shipped X25519MLKEM768 client-side in Firefox 132 (October 2024). The high-order contrast with CNG: PKCS #11 is a cross-vendor industry standard, so the same NSS / Firefox runtime can talk to a Mozilla softoken, an HSM module, and a smart-card module through a single interface; CNG is single-vendor (Microsoft) but exposes a fully Microsoft-curated provider universe through a stable Win32 API.
Apple Network framework and CryptoKit
Apple's TLS-stack history splits into the deprecated Secure Transport API [68] and the modern Network framework introduced in macOS 10.14 and iOS 12 [67]. Secure Transport's design was C-API and typed-enum: SSLProtocol selected the TLS version; SSLCipherSuite integers were the IANA cipher-suite codepoints; the developer worked with SSLContextRef handles much as a Windows developer works with CtxtHandle. The agility model was named-enum-per-OS-release: every TLS version and cipher was a compile-time constant, and the SDK version the application was built against determined what was selectable.
Network framework moved the API to a Swift-first surface (NWProtocolTLS.Options, sec_protocol_options_set_min_tls_protocol_version) and started Apple's deprecation glide for Secure Transport. On top of the network-layer primitives, CryptoKit (iOS 13 / macOS 10.15) provides the Swift-idiomatic high-level crypto API for symmetric AEAD, ECDH, ECDSA, and (via subsequent OS releases) the post-quantum primitives [69]. The cadence is bound to Apple's annual OS release: a new algorithm becomes available when the OS that ships it becomes available, and applications that need it bump their minimum-deployment target.
The structural contrast with CNG: Apple's model gives the platform vendor very tight control over what is selectable and a clean deprecation path (you simply drop a constant from a future SDK), but the cost is that the application's algorithm options track the OS version the application is built for. CNG decouples those -- a Windows 11 application built against a 2015 Win32 SDK still sees new BCrypt algorithm strings as the OS ships them, because the dispatch is by string at runtime.
.NET SslStream -- one API, three host backends
.NET's System.Net.Security.SslStream is identical on every host. The implementation, however, delegates to the host operating system's TLS stack. On Windows it calls into SChannel through SSPI; on Linux it calls into OpenSSL via System.Security.Cryptography.Native.OpenSsl; on macOS it calls into Apple's Network framework via System.Security.Cryptography.Native.Apple [11]. There is no "pick a backend" knob in SslStream; the runtime picks whichever backend the host OS provides.
The agility consequence for PQC is direct. A .NET 10 application running on a Windows Insider build whose SChannel has X25519MLKEM768 enabled by default will negotiate hybrid PQC automatically. The same application running on macOS gets classical X25519 until Apple ships hybrid in Network framework. The same application running on Linux against OpenSSL 3.5 gets ML-KEM via OpenSSL's in-tree implementation. The application source code never changes; the wire-level cryptography is whatever the host's TLS stack negotiates. This is the agility property in cross-platform clothing -- and it works because each host's substrate is itself agile.
Five substrates, five answers
The cross-stack comparison surfaces the meta-point. Five substrates: CNG, OpenSSL OSSL_PROVIDER, BoringSSL's vendored tree, PKCS #11, Apple's named-enum SDK. Five answers, all functional, all optimising for different deployment models. SChannel / CNG is the most registry-driven and single-vendor-extensible; OpenSSL is the most context-explicit; PKCS #11 is the most cross-vendor-standardised; BoringSSL is the most aggressive-by-refusing-stability; Apple is the most named-enum-SDK-bound. None of these is "the right" answer -- each is the answer that fits its vendor's deployment shape. The agility property is of the substrate, and the right substrate depends on what you ship and to whom.
Agility is the capacity for rotation. Whether the rotation actually happens is a separate problem -- one that the empirical evidence of TLS 1.0 / 1.1's 25-year tail tells a sobering story about.
11. Limits and Open Problems
Algorithm agility is necessary. It is not sufficient. TLS 1.0 was published in 1999 [20]; default-off in stable Windows did not arrive until 2024-2025 [15]. Twenty-five years. The substrate could have rotated TLS 1.0 out a decade earlier; the world would not move.
Agility lets the substrate add a new primitive and lets operators disable an old one. It cannot force operators of TLS-1.2-only or TLS-1.0-only endpoints to upgrade. The substrate solved the rotation problem; the world is the bottleneck. Trust-store distrust events, OS-level deprecation defaults, browser warnings, and eventual code-path removal are the levers that close the gap -- but each operates on the scale of years, not weeks.
This re-organises the reader's understanding of why Microsoft's posture on PQC is "ship and hedge" rather than "ship and declare victory." The substrate is ahead of the protocol; the protocol is ahead of the deployments; the deployments are years behind.
The downgrade-attack envelope
RFC 7568 (June 2015) formally prohibits SSL 3.0 [70]; RFC 6176 (March 2011) formally prohibits SSL 2.0 [17]. The TLS Fallback SCSV cipher suite (RFC 7507) bounded downgrade attacks within TLS 1.0 / 1.1 / 1.2. TLS 1.3's ServerHello.random downgrade-resistance sentinel (RFC 8446 §4.1.3 [37]) closes the downgrade attack surface within TLS 1.3. The remaining downgrade exposure lives at the boundary with TLS-1.2-only counterparties -- a boundary that shrinks every year but does not yet close.
Signature-side PQC and the chain-size problem
The PQC hybrid KEM transition is the easy half of the post-quantum migration. The signature side is harder. ML-DSA-65 produces ~3.3 KB signatures with ~2 KB public keys [59]; SLH-DSA at 128-bit security produces signatures in the 7 to 17 KB range [62]; Falcon (FN-DSA in the NIST nomenclature) produces ~1 KB signatures but is harder to implement correctly because of its floating-point Gaussian sampling.
Why this matters for SChannel: TLS server certificate chains are sent in the Certificate handshake message. A chain that fits inside TCP's initial congestion window (typically 10 segments, or about 14.6 KB) ships in one round trip; a chain that overflows the IW takes another RTT. Adding a 3.3 KB ML-DSA signature plus a 2 KB ML-DSA public key to every cert in a chain rapidly blows past 14.6 KB for a typical leaf-intermediate-root structure. The community working hypothesis is that the hybrid-signature transition in TLS will lag the hybrid-KEM transition by years; SymCrypt's Composite ML-DSA support (v103.12.0) [24] is the substrate-side preparation for that transition, but the IETF TLS WG signature-side drafts are still in flight.
Composite-identifier namespace sprawl in CNG
Every hybrid construction adds at least one new CNG algorithm identifier. X25519MLKEM768, SecP256r1MLKEM768, SecP384r1MLKEM1024 already exist. Composite ML-DSA + ECDSA is in flight. If pure ML-KEM-1024 and pure SLH-DSA are eventually default-on, the algorithm namespace doubles per hybrid family. The substrate is capable of absorbing the sprawl; whether the cipher-suite registry remains legible to administrators is a separate user-interface problem.
The opaque-engine bargain
SymCrypt is open since July 2019 and externally auditable [3]. The SChannel SSP binary itself remains closed-source. External behavioural verification -- Hubert Kario's tlsfuzzer -- is the closest the public has to a formal specification of schannel.dll's wire-level behaviour [41]. The project's framing is precise: it "doesn't check only that the system under test didn't crash, it checks that it returned correct error messages" [41]. That is the closest practitioners get to a behavioural spec without source.
The asymmetry has a name in the article's argument: open-source substrate, closed-source SSP. The agility receipts of Sections 5 and 9 are auditable at the primitive layer. The parsing-path correctness of Section 6 is not -- the coordinated-disclosure intake of MS14-066 (with IBM X-Force researcher Robert Freeman credited as the discoverer per the bulletin's acknowledgments [4]) is the kind of receipt the binary-only delivery model can produce. Modern external fuzzing has narrowed the gap but does not close it.
Legacy protocol removal versus disablement
Disablement by default is universal in Windows 11 / Server 2022+. Removing the negotiation code paths is a separate, slower trajectory. SSL 3.0's code paths are largely gone from current SChannel; TLS 1.0 / 1.1 code paths remain reachable behind registry flags because some long-tail enterprise scenarios still require them. The protocol surface of SChannel is wider than its default-enabled surface; an audit posture must account for the difference.
Dead ends and the diseases they failed to cure
The five agility receipts of Section 5 are the primitive-rotation story. But not every TLS failure is a primitive failure, and the substrate could not save Windows from the four most-instructive engineering dead ends the IETF eventually had to legislate out of the protocol itself. CRIME / TLS-level DEFLATE compression (Rizzo and Duong, ekoparty 2012) was a compression-then-encryption side-channel that no primitive substitution could fix; TLS 1.3 removed compression from the protocol entirely (RFC 8446 §4.1.2 [37]) and SChannel never shipped TLS-level compression in the first place. Insecure renegotiation (CVE-2009-3555) -- Ray and Dispensa, November 2009 -- let an MITM splice attacker-prefix application data into a victim's authenticated session because the mid-session re-handshake had no transcript binding to the prior session; RFC 5746 (Eric Rescorla et al., February 2010 [71]) added the renegotiation_info extension, Microsoft shipped the fix in SChannel via KB977377 / KB980436 in early 2010, and TLS 1.3 removed renegotiation outright in favour of the narrowly-scoped KeyUpdate message and post-handshake CertificateRequest. Anonymous Diffie-Hellman cipher suites (TLS 1.0 through 1.2 specified TLS_DH_anon_* and TLS_ECDH_anon_* with no server certificate at all -- forward secrecy without authentication) were off-by-default in every major stack including SChannel and removed from the TLS 1.3 cipher-suite namespace entirely. Export-grade RSA / FREAK (Beurdouche, Bhargavan and colleagues, IEEE S&P 2015 [72]) used roughly $100 of EC2 compute to break 512-bit RSA in hours and force-downgrade non-export-aware servers; Microsoft pruned the export-RSA suites from SChannel's default set via MS15-031 / KB3046049 in March 2015 [72].
If the theoretical limits are humbling, the practical day-to-day -- "what should I actually do with my SChannel-served TLS endpoints this Monday morning?" -- has a much cleaner set of answers.
12. Practical Guide: Nine Things to Do on a Windows-Served TLS Endpoint
A working operator's reference distilled to the essentials -- the nine things that, if you do nothing else this quarter, materially improve the security posture of a Windows-served TLS endpoint.
1. Inventory
# Cipher suites enabled on this host, in negotiation order
Get-TlsCipherSuite | Select-Object -Property Name, Cipher, CipherLength, KeyType, Exchange
# ECC named groups (TLS 1.3 key shares; X25519, secp256r1, secp384r1; PQC hybrids on newer builds)
Get-TlsEccCurve
Pair this with a registry walk of HKLM\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\ -- Protocols\<ver>\<role>\Enabled and DisabledByDefault for protocol versions, Ciphers\<algorithm>\Enabled for primitive disables, and Hashes\<algorithm>\Enabled for handshake-hash disables [15].
2. Disable the legacy protocol versions
Set SCHANNEL\Protocols\SSL 3.0\<role>\Enabled = 0 and DisabledByDefault = 1 for both Client and Server sub-keys. Repeat for TLS 1.0 and TLS 1.1. The asymmetry between Client and Server hives bites: an outbound WinHTTP call from your IIS worker is governed by the Client sub-key even though the server itself is gated by Server [15].
3. Disable RC4 and 3DES at the cipher level
RC4: KB2868725 [28] introduced the mechanism. Set Ciphers\RC4 40/128\Enabled = 0, Ciphers\RC4 56/128\Enabled = 0, Ciphers\RC4 64/128\Enabled = 0, Ciphers\RC4 128/128\Enabled = 0. 3DES: Ciphers\Triple DES 168\Enabled = 0. Then verify with Get-TlsCipherSuite that no *RC4* or *3DES* suites are still listed.
4. Cipher-suite ordering for TLS 1.2
The SSL Cipher Suite Order GPO is the lever. Put ECDHE + AES-GCM suites at the top; keep CHACHA20-POLY1305 as a fallback for clients without AES-NI; pull legacy AES-CBC suites to the bottom. The Microsoft Learn "Manage TLS" page walks through the GPO interaction [23].
5. Enable OCSP stapling on IIS, and enable CAPI2/Operational logging for distrust observability
OCSP stapling is on by default in modern IIS. Verify that your front door is sending stapled responses (via openssl s_client -status -connect host:443 < /dev/null | grep -i ocsp from a test client). If your CA does not support OCSP for the issued cert, the stapling fails silently and you lose the revocation channel; pick a CA that does.
For trust-store observability, enable the per-host CAPI2/Operational tracing channel with wevtutil sl Microsoft-Windows-CAPI2/Operational /e:true and watch for the chain-engine events the Microsoft Learn FCPCA-removal article enumerates: Event ID 11 (chain-build failures), Event ID 30 (SSL or NTAuth policy failures), Event ID 90 (every certificate consulted during chain build) [48]. The FCPCA article also documents the empirical "one to seven days" propagation latency between an MTRP distrust landing in authrootstl.cab and a given client actually applying it -- the same window applies to any future CCADB-coordinated removal (cross-reference Section 7).
6. Enforce RunAsPPL and Credential Guard
These are complementary, not alternatives [56]. Set HKLM\SYSTEM\CurrentControlSet\Control\Lsa\RunAsPPL = 1 and reboot; verify LSASS comes back as a Protected Process with Get-Process lsass | Select Name, Protect* [55]. Then enable Credential Guard via Group Policy or MDM; on most newer Windows 11 builds it is on by default [57]. Auditing-only mode (AuditLevel) is the right step before enforcement to identify any legacy LSA plug-ins that fail to load as PPL.
7. Lock down CredSSP / RDP NLA on Remote Desktop Session Hosts
Confirm Network Level Authentication is enabled on any RDP Session Host (it has been default-on since Windows Server 2012 R2) [54]. Confirm the host is running CredSSP version 5 or higher, so the channel-binding hash mechanism that replaced the broken pre-CVE-2018-0886 "encrypt the public key + 1" scheme is in force [53]. For any administrative jump-host scenario where the destination's plaintext-credential exposure must be zero, use Remote Credential Guard (TSRemoteGuardCreds) -- the destination receives only a service ticket usable for the session, not a reusable password or hash. Pair NLA enforcement with the certificate-validation knobs in item 5: the SChannel server certificate the CredSSP TLS handshake validates is the same one a TLS-only audit covers, so the trust pipeline reuse is exact.
8. FIPS-mode toggle: what FipsAlgorithmPolicy = 1 actually means in 2026
The Local Security Policy setting "System cryptography: Use FIPS compliant algorithms for encryption, hashing, and signing" (registry: HKLM\SYSTEM\CurrentControlSet\Control\Lsa\FipsAlgorithmPolicy\Enabled = 1) is the operator-side policy lever that pins SChannel, EFS, BitLocker, and RDP encryption to the FIPS 140-validated subset of CNG's catalog [73]. The "what it disables" question has changed since the legacy "TLS_RSA_WITH_3DES_EDE_CBC_SHA only" framing on the policy reference page itself [73]. The modern Microsoft Learn "TLS Cipher Suites in Windows 11" page is explicit that "FIPS-compliance has become more complex with the addition of elliptic curves making the FIPS mode enabled column in previous versions of this table misleading," and points readers to NIST SP 800-52 Rev. 2 section 3.3.1 for the authoritative FIPS-approved TLS 1.2 / 1.3 cipher-suite list [74][75].
In practice on a Windows 11 / Server 2022 box with FipsAlgorithmPolicy = 1: SChannel will negotiate TLS 1.3's TLS_AES_128_GCM_SHA256 and TLS_AES_256_GCM_SHA384 (the third TLS 1.3 mandatory suite, TLS_CHACHA20_POLY1305_SHA256, is not FIPS-approved because ChaCha20-Poly1305 is not on the FIPS algorithm list); for TLS 1.2 it will negotiate the ECDHE-with-AES-GCM and ECDHE-with-AES-CBC-SHA2 variants over the NIST curves P-256, P-384, and P-521 only; the X25519 named group is not FIPS-approved as of the May 2026 Windows servicing snapshot; and the X25519MLKEM768 hybrid in Insider channels is not FIPS-approved either, because of the X25519 component.
Two-sided framing: SymCrypt's FIPS 140-3 validation is the engine-side receipt; FipsAlgorithmPolicy = 1 is the consumer-side policy lever that pins consumers to the validated subset. Both are required for the system to be "operating in FIPS mode" in the CMVP sense [76]. At the BCrypt layer, FIPS enforcement is opt-in via a CNG flag that callers pass to BCryptOpenAlgorithmProvider; SChannel honours the system policy directly, but legacy applications loading deprecated CryptoAPI 1.0 CSPs (PROV_RSA_FULL, rsaenh.dll, etc.) bypass the toggle entirely [22].
9. Pilot the PQC hybrid where you can
Where Windows builds support X25519MLKEM768 -- presently Insider Preview channels per the CNG ML-KEM page's banner [6] -- pilot the hybrid against an internal client. Validate via Wireshark (looking for the X25519MLKEM768 named-group selector in ClientHello / ServerHello key_share extensions) and a curl build with ML-KEM support. Measure connection-establishment latency; for a typical handshake the additional ~5-10 ms is in the noise (see the §9 PQC handshake budget Callout for the TPM-bound exception).
Wireshark tip: filter for the hybrid named group
The X25519MLKEM768 named group has IANA codepoint 0x11ec. A Wireshark display filter of tls.handshake.extensions_key_share_group == 0x11ec flags handshakes that negotiated the hybrid. Combined with tls.handshake.version == 0x0304, you can quickly spot whether a peer actually used the PQC hybrid or fell back to plain X25519.
Common pitfalls
ClientvsServerasymmetry. Two sub-keys, two hives, four registry edits per protocol version. Tooling likeIISCryptoautomates the matrix; doing it by hand is the most common source of "we thought we disabled TLS 1.0 but our outbound WinHTTP still negotiates it" tickets.SCH_USE_STRONG_CRYPTO-- the SCHANNEL_CRED flag is per-call, not per-machine. .NET sets it by default on modern targets but historically didn't on .NET Framework 4.5.x. If you maintain old .NET Framework workloads, audit them.SSLKEYLOGFILE-- SChannel does not export keys toSSLKEYLOGFILE. Wireshark cannot decrypt SChannel-served TLS traffic without separate key extraction (etw-based, or a TLS-terminating proxy). Plan your packet-capture strategy accordingly.
If the practical guide is the "what to do," the FAQ that follows is the "what to stop believing."
13. Frequently Asked Questions
Frequently asked questions about SChannel and Windows TLS
Was MS14-066 the same thing as Heartbleed?
No. They were different bugs, different stacks, different vendors. Heartbleed (CVE-2014-0160), April 7, 2014, was a flaw in OpenSSL's TLS Heartbeat extension code path [25]. MS14-066 / CVE-2014-6321 ("WinShock"), November 11, 2014, was a pre-authentication remote code execution in SChannel's TLS message-parsing path [4][38], disclosed under coordinated vulnerability disclosure and credited by IBM X-Force to researcher Robert Freeman. SChannel does not implement OpenSSL's Heartbeat code and was not affected by Heartbleed; Microsoft confirmed this publicly in April 2014. The two events have been blended in many secondary accounts since.
Was the 2014 SChannel patch silent?
No. CVE-2014-6321 had a public Patch Tuesday bulletin (MS14-066, November 11, 2014) [4], a US-CERT alert (TA14-318A, November 18, 2014) [39], and a CERT/CC vulnerability note (VU#505120) [40]. The "silently patched" framing in some accounts refers to the additional SChannel hardening fixes Microsoft bundled into the same KB without separate bulletins, not to the headline CVE itself. This article does not assign specific CVE IDs to those bundled extras.
Is ZeroLogon (CVE-2020-1472) an SChannel TLS bug?
No. ZeroLogon affected the Netlogon Remote Protocol (MS-NRPC), implemented in netlogon.dll. The "Netlogon secure channel" and the "SChannel SSP" (schannel.dll, the TLS provider this article is about) share a name root but are different protocols, different DLLs, different code paths, and different bug classes. Confusing the two is one of the most common Windows-security naming traps.
If I enable RunAsPPL, do I still need Credential Guard?
Yes -- they are complementary, not alternatives [56]. PPL is a same-privilege gate inside Virtual Trust Level 0 (VTL0): it stops a non-PPL process from opening LSASS for memory read [55]. Credential Guard moves credential material into the LSAIso trustlet at VTL1, behind the VBS / Hyper-V boundary [57]. They protect against different threats and stack rather than substitute.
Is ML-KEM in CNG generally available right now?
No. The Microsoft Learn page for the CNG ML-KEM API carries an explicit "prerelease product / Windows Insider Preview" banner as of mid-2026 [6]. The primitive ships in SymCrypt v103.5.0 and later [24]; the CNG and SChannel surfaces are rolling through the Insider channel. Track the Microsoft Tech Community PQC posts for OS-channel GA announcements [60][61].
Does .NET SslStream use SChannel?
On Windows, yes. On Linux .NET delegates SslStream to OpenSSL; on macOS it uses Apple's Network framework. PQC support follows the underlying stack, so the same .NET binary's TLS posture differs by host OS in mid-2026.
Why is the title 'twenty-year' when SChannel itself is roughly thirty years old?
Because the agility property the article is about is anchored to CNG, which shipped in Vista in January 2007 [2] -- about nineteen years to mid-2026. Pre-CNG SChannel was not algorithm-agile in any meaningful sense: primitives were baked into CryptoAPI 1.0 CSP DLLs, ECC could not be expressed in the ALG_ID + key BLOB model at all, and adding a new algorithm required a CSP rev plus an OS release. CNG is when "rotate every cipher" stopped being a slogan and started being a property the substrate could deliver. The thirty-year framing would be arithmetically accurate but argumentatively wrong.
The Microsoft TLS stack has spent twenty years proving that one architectural decision -- decouple algorithms from DLLs, addressed by string identifier through a stable provider model -- can carry a vendor through every primitive rotation cryptography throws at it. The receipts now include a post-quantum hybrid key exchange that runs through the same dispatch path Vista shipped in 2007. The next test, the signature-side PQC transition, is already in flight inside SymCrypt. Whatever the world chooses to do with those primitives, the substrate is ready.
Study guide
Key terms
- SChannel SSP
- The Microsoft Security Support Provider (schannel.dll) implementing SSL, TLS, and DTLS on Windows.
- CNG (Cryptography API: Next Generation)
- Vista-era substrate replacing CryptoAPI 1.0; splits into BCrypt (primitives) and NCrypt (key custodians).
- BCrypt
- CNG API for primitive cryptographic operations addressed by string algorithm identifier.
- NCrypt
- CNG API for key custodians via pluggable Key Storage Providers (software, smart card, TPM, HSM).
- KSP (Key Storage Provider)
- Pluggable NCrypt module that owns a private key's lifecycle and operations.
- SymCrypt
- Microsoft's unified FIPS-validated primitive engine started by Niels Ferguson in late 2006; open-sourced under MIT in July 2019.
- AEAD
- Authenticated Encryption with Associated Data; single-pass encrypt-and-authenticate construction that eliminates mac-then-encrypt padding-oracle bugs.
- ECDHE
- Ephemeral Elliptic-Curve Diffie-Hellman; provides forward secrecy by deriving each session key from a fresh elliptic-curve key pair.
- ML-KEM
- FIPS 203 module-lattice-based Key Encapsulation Mechanism; the NIST-standardised PQC KEM that X25519MLKEM768 wraps.
- PPL (Protected Process Light)
- Same-privilege process-protection gate inside VTL0; blocks non-PPL callers from opening LSASS for memory read when RunAsPPL is enabled.
- CertGetCertificateChain
- The Windows chain-building API that walks from leaf to trusted root with revocation, name-constraint, and EKU enforcement.
- MS14-066 / WinShock
- Pre-authentication SChannel parsing-path RCE (CVE-2014-6321), patched November 11, 2014; not Heartbleed.
Comprehension questions
Name the structural property of CryptoAPI 1.0 that prevented ECC from being expressed.
The ALG_ID + key BLOB model could not represent named curves, parameter sets, or point compression -- ECC's identity is per-curve, not per-algorithm-type.
What two API splits did CNG introduce, and what does each abstract?
BCrypt abstracts primitives (algorithms by string identifier); NCrypt abstracts key custodians (via Key Storage Providers).
Why is the 'twenty-year algorithm-agility' frame anchored to 2007 rather than 1996?
CNG (2007) is when algorithm agility became a first-class substrate property. Pre-CNG SChannel was protocol-agile (it could rotate SSL/TLS versions) but not primitive-agile -- adding a new primitive required a CSP DLL rev.
What is the SChannel-side disambiguation between MS14-066 and Heartbleed?
MS14-066 was a pre-auth RCE in SChannel's TLS parsing path (Microsoft, November 2014). Heartbleed was an OpenSSL Heartbeat over-read (April 2014). SChannel does not implement the OpenSSL Heartbeat code path.
What concrete handshake-time data does the X25519MLKEM768 named group concatenate?
ClientHello key_share is mlkem_pk || x25519_pk (1216 B); ServerHello key_share is mlkem_ct || x25519_pk (1120 B); both sides derive shared_secret = mlkem_ss || x25519_ss (64 B) and feed it to TLS 1.3's HKDF-Extract.
Why are PPL (RunAsPPL) and Credential Guard described as complementary rather than substitutes?
PPL is a same-privilege gate inside VTL0 (it blocks OpenProcess(LSASS, VM_READ) from non-PPL callers). Credential Guard moves credential material into the LSAIso trustlet at VTL1 behind the VBS / Hyper-V boundary. They protect against different attack classes.
References
- (2025). TLS/SSL overview (Schannel SSP). https://learn.microsoft.com/en-us/windows-server/security/tls/tls-ssl-schannel-ssp-overview ↩
- (2025). Cryptography API: Next Generation. https://learn.microsoft.com/en-us/windows/win32/seccng/cng-portal ↩
- (2026). microsoft/SymCrypt -- core cryptographic function library used by Windows. https://github.com/microsoft/SymCrypt ↩
- (2014). Microsoft Security Bulletin MS14-066 -- Vulnerability in Schannel Could Allow Remote Code Execution (2992611). https://learn.microsoft.com/en-us/security-updates/securitybulletins/2014/ms14-066 ↩
- (2024). FIPS 203 -- Module-Lattice-Based Key-Encapsulation Mechanism Standard (ML-KEM). https://csrc.nist.gov/pubs/fips/203/final ↩
- (2025). CNG ML-KEM examples. https://learn.microsoft.com/en-us/windows/win32/seccng/cng-mlkem-examples ↩
- (2025). Get-TlsCipherSuite (TLS PowerShell module). https://learn.microsoft.com/en-us/powershell/module/tls/get-tlsciphersuite ↩
- (2025). MsQuic TLS Abstraction Layer (docs/TLS.md). https://github.com/microsoft/msquic/blob/main/docs/TLS.md ↩
- (2021). RFC 9001 -- Using TLS to Secure QUIC. https://datatracker.ietf.org/doc/html/rfc9001 ↩
- (2026). BoringSSL -- project README. https://boringssl.googlesource.com/boringssl/ ↩
- (2026). Cross-platform cryptography in .NET. https://learn.microsoft.com/en-us/dotnet/standard/security/cross-platform-cryptography ↩
- (2024). Cipher Suites in Schannel. https://learn.microsoft.com/en-us/windows/win32/secauthn/cipher-suites-in-schannel ↩
- (2025). TLS Cipher Suites in Windows Server 2022. https://learn.microsoft.com/en-us/windows/win32/secauthn/tls-cipher-suites-in-windows-server-2022 ↩
- (2024). TLS Cipher Suites in Windows 7. https://learn.microsoft.com/en-us/windows/win32/secauthn/tls-cipher-suites-in-windows-7 ↩
- (2025). Transport Layer Security (TLS) registry settings. https://learn.microsoft.com/en-us/windows-server/security/tls/tls-registry-settings ↩
- (1995). The Private Communication Technology Protocol (PCT) version 1, draft-benaloh-pct-00. https://datatracker.ietf.org/doc/html/draft-benaloh-pct-00 ↩
- (2011). RFC 6176 -- Prohibiting Secure Sockets Layer (SSL) Version 2.0. https://datatracker.ietf.org/doc/html/rfc6176 ↩
- (2014). This POODLE Bites: Exploiting The SSL 3.0 Fallback. https://www.openssl.org/~bodo/ssl-poodle.pdf ↩
- (2025). ALG_ID (Wincrypt.h) -- Win32 apps. https://learn.microsoft.com/en-us/windows/win32/seccrypto/alg-id ↩
- (1999). RFC 2246 -- The TLS Protocol Version 1.0. https://datatracker.ietf.org/doc/html/rfc2246 ↩
- (2006). RFC 4346 -- The Transport Layer Security (TLS) Protocol Version 1.1. https://datatracker.ietf.org/doc/html/rfc4346 ↩
- (2025). Cryptographic Provider Types -- Win32 apps. https://learn.microsoft.com/en-us/windows/win32/seccrypto/cryptographic-provider-types ↩
- (2025). Manage Transport Layer Security (TLS). https://learn.microsoft.com/en-us/windows-server/security/tls/manage-tls ↩
- (2026). microsoft/SymCrypt CHANGELOG.md. https://github.com/microsoft/SymCrypt/blob/main/CHANGELOG.md ↩
- (2014). CVE-2014-0160 -- OpenSSL Heartbleed. https://nvd.nist.gov/vuln/detail/CVE-2014-0160 ↩
- (2010). Cryptography Engineering: Design Principles and Practical Applications. https://www.schneier.com/books/cryptography-engineering/ ↩
- (2011). Here Come The XOR Ninjas (BEAST attack on TLS 1.0 CBC). https://bug665814.bmoattachments.org/attachment.cgi?id=540839 ↩
- (2013). Microsoft Security Advisory 2868725 -- Update for Disabling RC4. https://learn.microsoft.com/en-us/security-updates/securityadvisories/2013/2868725 ↩
- (2015). All Your Biases Belong to Us: Breaking RC4 in WPA-TKIP and TLS. https://www.usenix.org/conference/usenixsecurity15/technical-sessions/presentation/vanhoef ↩
- (2016). SWEET32: Birthday attacks on 64-bit block ciphers in TLS and OpenVPN. https://sweet32.info/ ↩
- (2017). The first collision for full SHA-1 (IACR ePrint 2017/190). https://eprint.iacr.org/2017/190.pdf ↩
- (2015). Logjam: Imperfect Forward Secrecy -- How Diffie-Hellman Fails in Practice. https://weakdh.org/ ↩
- (2008). RFC 5246 -- The Transport Layer Security (TLS) Protocol Version 1.2. https://datatracker.ietf.org/doc/html/rfc5246 ↩
- (2015). RFC 7465 -- Prohibiting RC4 Cipher Suites. https://datatracker.ietf.org/doc/html/rfc7465 ↩
- (2016). Transcript Collision Attacks: Breaking Authentication in TLS, IKE, and SSH (SLOTH). https://www.mitls.org/downloads/transcript-collisions.pdf ↩
- (2015). Transcript Collision Attacks: Breaking Authentication in TLS, IKE, and SSH (IACR ePrint 2015/1170). https://eprint.iacr.org/2015/1170 ↩
- (2018). RFC 8446 -- The Transport Layer Security (TLS) Protocol Version 1.3. https://datatracker.ietf.org/doc/html/rfc8446 ↩
- (2014). CVE-2014-6321 -- Microsoft Schannel Remote Code Execution Vulnerability. https://nvd.nist.gov/vuln/detail/CVE-2014-6321 ↩
- (2014). TA14-318A -- Microsoft Windows Secure Channel (Schannel) Vulnerability. https://www.us-cert.gov/ncas/alerts/TA14-318A ↩
- (2014). VU#505120 -- Microsoft Schannel vulnerable to RCE via specially crafted packets. https://www.kb.cert.org/vuls/id/505120 ↩
- (2026). tlsfuzzer/tlsfuzzer -- SSL and TLS protocol test suite. https://github.com/tlsfuzzer/tlsfuzzer ↩
- (2008). RFC 5280 -- Internet X.509 Public Key Infrastructure Certificate and CRL Profile. https://datatracker.ietf.org/doc/html/rfc5280 ↩
- (2013). RFC 6960 -- X.509 Internet Public Key Infrastructure Online Certificate Status Protocol (OCSP). https://datatracker.ietf.org/doc/html/rfc6960 ↩
- (2026). CCADB -- Resources. https://ccadb.org/resources ↩
- (2026). Microsoft Trusted Root Program -- Program Requirements. https://learn.microsoft.com/en-us/security/trusted-root/program-requirements ↩
- (2011). Microsoft Security Advisory 2607712 -- Fraudulent Digital Certificates Could Allow Spoofing (DigiNotar). https://learn.microsoft.com/en-us/security-updates/securityadvisories/2011/2607712 ↩
- (2018). Microsoft partners with DigiCert to begin deprecating Symantec TLS certificates. https://www.microsoft.com/en-us/security/blog/2018/10/04/microsoft-partners-with-digicert-to-begin-deprecating-symantec-tls-certificates/ ↩
- (2026). Removal of the U.S. Federal Common Policy CA certificate from the Microsoft trusted root. https://learn.microsoft.com/en-us/troubleshoot/windows-server/certificates-and-public-key-infrastructure-pki/microsoft-trusted-root-store-removal-of-us-federal-common-policy ↩
- (2020). CVE-2020-0601 -- Windows CryptoAPI Spoofing Vulnerability (Curveball). https://nvd.nist.gov/vuln/detail/CVE-2020-0601 ↩
- (2020). Patch Critical Cryptographic Vulnerability in Microsoft Windows Clients and Servers (CVE-2020-0601). https://media.defense.gov/2020/Jan/14/2002234275/-1/-1/0/CSA-WINDOWS-10-CRYPT-LIB-20190114.pdf ↩
- (2024). [MS-CSSP]: Credential Security Support Provider (CredSSP) Protocol. https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-cssp/85f57821-40bb-46aa-bfcb-ba9590b8fc30 ↩
- (2024). [MS-CSSP]: Glossary. https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-cssp/97e4a826-1112-4ab4-8662-cfa58418b4c1 ↩
- (2024). [MS-CSSP] 3.1.5 Processing Events and Sequencing Rules. https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-cssp/385a7489-d46b-464c-b224-f7340e308a5c ↩
- (2014). Configure Network Level Authentication for Remote Desktop Services Connections (archived TechNet Wiki). https://learn.microsoft.com/en-us/archive/technet-wiki/5490.configure-network-level-authentication-for-remote-desktop-services-connections ↩
- (2026). Configure added LSA protection. https://learn.microsoft.com/en-us/windows-server/security/credentials-protection-and-management/configuring-additional-lsa-protection ↩
- (2021). Do You Really Know About LSA Protection (RunAsPPL)?. https://itm4n.github.io/lsass-runasppl/ ↩
- (2026). Credential Guard overview. https://learn.microsoft.com/en-us/windows/security/identity-protection/credential-guard/ ↩
- (2022). The End of PPLdump. https://itm4n.github.io/the-end-of-ppldump/ ↩
- (2024). FIPS 204 -- Module-Lattice-Based Digital Signature Standard (ML-DSA). https://csrc.nist.gov/pubs/fips/204/final ↩
- (2024). Microsoft's quantum-resistant cryptography is here. https://techcommunity.microsoft.com/blog/microsoft-security-blog/microsofts-quantum-resistant-cryptography-is-here/4238780 ↩
- (2025). Companion guide: transitioning to post-quantum cryptography. https://techcommunity.microsoft.com/discussions/windows-security/companion-guide-transitioning-to-post-quantum-cryptography/4504853 ↩
- (2024). FIPS 205 -- Stateless Hash-Based Digital Signature Standard (SLH-DSA). https://csrc.nist.gov/pubs/fips/205/final ↩
- (2026). Post-quantum hybrid ECDHE-MLKEM Key Agreement for TLSv1.3 (draft-ietf-tls-ecdhe-mlkem-05). https://datatracker.ietf.org/doc/draft-ietf-tls-ecdhe-mlkem/ ↩
- (2016). RFC 7748 -- Elliptic Curves for Security. https://datatracker.ietf.org/doc/html/rfc7748 ↩
- (2024). The state of the post-quantum Internet in 2024. https://blog.cloudflare.com/pq-2024/ ↩
- (2023). Post-quantum cryptography to origins. https://blog.cloudflare.com/post-quantum-to-origins/ ↩
- (2025). Network framework -- developer documentation. https://developer.apple.com/documentation/network ↩
- (2025). Secure Transport (deprecated) -- developer documentation. https://developer.apple.com/documentation/security/secure_transport ↩
- (2025). CryptoKit -- developer documentation. https://developer.apple.com/documentation/cryptokit ↩
- (2015). RFC 7568 -- Deprecating Secure Sockets Layer Version 3.0. https://datatracker.ietf.org/doc/html/rfc7568 ↩
- (2010). RFC 5746 -- Transport Layer Security (TLS) Renegotiation Indication Extension. https://datatracker.ietf.org/doc/html/rfc5746 ↩
- (2015). A Messy State of the Union: Taming the Composite State Machines of TLS (SMACK / FREAK project site). https://www.smacktls.com/ ↩
- (2026). System cryptography: Use FIPS compliant algorithms for encryption, hashing, and signing (Windows 10 / Windows 11 Security Policy Settings). https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-10/security/threat-protection/security-policy-settings/system-cryptography-use-fips-compliant-algorithms-for-encryption-hashing-and-signing ↩
- (2025). TLS Cipher Suites in Windows 11. https://learn.microsoft.com/en-us/windows/win32/secauthn/tls-cipher-suites-in-windows-11 ↩
- (2019). NIST SP 800-52 Rev. 2 -- Guidelines for the Selection, Configuration, and Use of Transport Layer Security (TLS) Implementations. https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-52r2.pdf ↩
- (2026). Windows FIPS 140 validation. https://learn.microsoft.com/en-us/windows/security/security-foundations/certification/fips-140-validation ↩
- (2024). OpenSSL 3.0 provider(7) manpage. https://docs.openssl.org/3.0/man7/provider/ ↩
- (2024). OpenSSL 3.0 provider-base(7) manpage. https://docs.openssl.org/3.0/man7/provider-base/ ↩
- (2025). NSS 3.111 release notes. https://firefox-source-docs.mozilla.org/security/nss/releases/nss_3_111.html ↩
- (2024). PKCS #11 Cryptographic Token Interface Base Specification Version 3.1. https://docs.oasis-open.org/pkcs11/pkcs11-spec/v3.1/os/pkcs11-spec-v3.1-os.html ↩
- (2025). BCryptOpenAlgorithmProvider function (bcrypt.h) -- Win32 apps. https://learn.microsoft.com/en-us/windows/win32/api/bcrypt/nf-bcrypt-bcryptopenalgorithmprovider ↩