Crypto modes and call binding
graph TD
CALL["Method call, for example CreateSelfSigned"]
FW[".NET method, unchanged"]
EXT["Library extension"]
DEF["PostQuantumConfig.DefaultMode"]
C["Classic: result of the .NET method"]
H["Hybrid: classical plus ML-DSA"]
P["PostQuantum: ML-DSA or ML-KEM only"]
CALL -- "without a CryptoMode argument" --> FW
CALL -- "with a CryptoMode argument" --> EXT
EXT -- "value null" --> DEF
EXT --> C
EXT --> H
EXT --> P
DEF --> C
DEF --> H
DEF --> P
Figure: The compiler alone decides whether a call reaches the library, based on the CryptoMode
argument. Only inside the library does the argument's value select the mode.
The library has three modes. You choose the mode at the call site. There's no global switch that silently changes the behavior of existing code.
Key features
- Three modes in the
CryptoModeenumeration:Classic,Hybrid, andPostQuantum. - Binding by the compiler: A call without
CryptoModealways reaches the .NET method. - A default for the value
null:PostQuantumConfig.DefaultModedetermines what a call withmode: nullcreates. - No silent fallback: A requested PQ mode delivers PQ or fails.
PostQuantumonly after an opt-in: Under the BSI TR-02102-1 defaults, PQ algorithms on their own are allowed only after your app permits them.
Core concepts
The three modes
| Mode | Result | Who can verify it |
|---|---|---|
Classic | exactly the result of the .NET method | any software |
Hybrid | the classical result (RSA or ECDSA) plus an ML-DSA or ML-KEM part | any software verifies the classical part; software that uses this library verifies both parts |
PostQuantum | ML-DSA or ML-KEM only | only software that supports ML-DSA or ML-KEM |
Hybrid is the mode for the transition. Peers that don't know about PQ keep working unchanged, and the PQ
part protects the data once the classical algorithms can be broken. BSI TR-02102-1 recommends PQ algorithms
only in this combination with a classical algorithm.
PostQuantum requires every party to handle ML-DSA or ML-KEM. Because the policy doesn't recommend a PQ
algorithm on its own, every operation in this mode throws a CryptographicException until your app sets
PostQuantumExemptions.PurePostQuantum in PostQuantumConfig.Exemptions. That applies to verification,
too. For details, see Post-quantum algorithm policy.
Binding by the compiler
Each extension has the same name and parameters as the .NET method and appends an optional parameter,
CryptoMode? mode. When both an instance method and an extension method fit, the C# compiler always
prefers the instance method. As a result:
// Binds to CertificateRequest.CreateSelfSigned in .NET. using var classic = request.CreateSelfSigned(notBefore, notAfter); // Binds to the library's extension. using var hybrid = request.CreateSelfSigned(notBefore, notAfter, CryptoMode.Hybrid);
A call without the argument therefore behaves exactly as before, including its errors. For example, for a
request with an ML-DSA key, CreateSelfSigned without the argument throws the .NET
InvalidOperationException, because .NET doesn't know that key. Only the call with
CryptoMode.PostQuantum issues the certificate.
The value null and DefaultMode
When you pass mode: null, the extension applies PostQuantumConfig.DefaultMode. The default is
Classic. PostQuantumConfig.EnablePostQuantum(mode) loads OpenSSL and sets DefaultMode to mode only
after that succeeds. This lets you set the mode in one place in your app, while every call site still shows
through its argument that it uses the library:
PostQuantumConfig.EnablePostQuantum(CryptoMode.Hybrid); // DefaultMode is now Hybrid, so this call creates a hybrid certificate. using var certificate = request.CreateSelfSigned(notBefore, notAfter, mode: null);
No silent fallback
If a call requests Hybrid or PostQuantum and the library can't create the PQ part, the call fails with
a CryptographicException. That happens when OpenSSL is missing or doesn't provide the algorithm, when a
required ML-DSA key isn't in the process, and when the key doesn't match the mode. Such a call never
returns a result that carries only the classical part.
EnablePostQuantum follows the same rule. If OpenSSL fails to load or doesn't provide ML-DSA-65 or
ML-KEM-768, the method throws, and DefaultMode keeps its previous value.
EnablePostQuantum(CryptoMode.PostQuantum) throws without the PurePostQuantum exemption before it even
loads OpenSSL.
Limitations
- No argument, no PQ.
DefaultModeonly affects calls that pass theCryptoModeargument. Existing code without that argument doesn't gain a PQ part fromEnablePostQuantum. - Pass
nullby name only. A positionalnullcan bind to a different .NET overload. For example,request.CreateSigningRequest(null)calls the .NET overload that takes anX509SignatureGenerator. Writemode: nullinstead. - The mode and the key must match.
PostQuantumneeds an ML-DSA or ML-KEM key, andHybridneeds an RSA or ECDSA key. The library generates the accompanying ML-DSA key or takes it from the caller. - Process-wide defaults.
DefaultModeandOpenSslPathapply to the whole process. Set both once when your app starts, before the first cryptographic operation runs.