The ML-DSA private key is missing
A call with CryptoMode.Hybrid or CryptoMode.PostQuantum throws a CryptographicException with one of
these messages:
The ML-DSA private key of issuer 'CN=Contoso Hybrid CA' is not available. No ML-DSA private key is available for signer 'CN=Contoso Accounting'; Hybrid signing requires one. A PQ export was requested, but no ML-DSA private key is available for any certificate. No ML-DSA key pair is available for 'CN=www.example.com'; a PQ store operation needs one.
Or GetAlternativePrivateMLDsaKey or GetPrivateMLDsaKey returns null.
Environment
All targets. Operations that sign with a certificate's private ML-DSA key are affected: issuing certificates and CRLs, signing CMS messages, exporting PFX files with a PQ key, and storing key files.
Cause
The .NET X509Certificate2 class doesn't carry an ML-DSA key. The library keeps the key in a process table,
where the certificate finds it through its thumbprint. The entry is missing in these cases:
- The certificate was issued in a different process and loaded from a PEM or DER file. Such files don't contain a private key.
- The certificate was imported with the .NET
new X509Certificate2(pfx, password)constructor. That path skips the PQ key in the PFX file. - A pure ML-DSA certificate was issued from a request with
Create. At first, it only knows the public key. - The process restarted. The table lives only as long as the process.
Resolution
Associate the key before you call the operation. How you do that depends on where the key is:
| The key is | Associate it with |
|---|---|
in a PFX file from ExportPfx | X509Certificate2ExportExtensions.ImportPfx instead of the .NET constructor |
| in a key file next to the certificate store | X509Store.Find(findType, findValue, validOnly, restorePqKeys: true) or X509StoreExtensions.RestorePqKeys |
an MLDsaKey in the process, for a hybrid certificate | certificate.AttachAlternativePrivateMLDsaKey(key) |
an MLDsaKey in the process, for a pure ML-DSA certificate | certificate.AttachPrivateMLDsaKey(key) |
Then check with GetAlternativePrivateMLDsaKey or GetPrivateMLDsaKey that the key is present. The .NET
HasPrivateKey property only reflects the classical key. For details, see
Keys and key storage.