The November 2020 Cumulative Update Preview for Windows 10 version 2004 and below, as well as the January 2021 Security and Quality Rollup release for .NET Framework 4.8 released a change to improve the clean-up process for X509Certificate2 certificates. Below, we provide additional clarifications on a private key lifetime on Windows.
Since .NET Framework 2.0 there have been two different ways to load a certificate and its associated private key from a PKCS#12 PFX file: as one certificate object via members on the X509Certificate2 class or as all of the certificates present in the PFX via the X509Certificate2Collection.Import methods.
The default behavior is that the private key gets loaded into a persisted (named) key via one of the system cryptography libraries, which indirectly writes a file to disk. Most callers loading a PFX only use the certificate object temporarily, so when .NET is releasing the native resources associated with the certificate it also deletes the private key. The main exceptions to this behavior are
-
The X509KeyStorageFlags.PersistKeySet flag, which results in the file being written but not deleted,
-
The X509KeyStorageFlags.EphemeralKetSet flag, which results in the private key being loaded to memory with no backing file,
-
Abnormal process termination, which can occur when key deletion is pending.
Counterintuitively, the two different ways to load certificates have always used different mechanisms for tracking when it is an appropriate time to delete the private key. Loading a single certificate via the X509Certificate2 class uses a marker that is only visible to the .NET runtime, and only applies to that one object reference. Loading a PFX via X509Certificate2Collection.Import uses a marker on the native certificate object, which results in “deletion accountability” being shared across any managed objects that represent the same native certificate. This means that new X509Certificate2(otherCert.Handle).Dispose() will cause the private key file to be erased during the call to Dispose() if otherCert was loaded from a PFX via X509Certificate2Collection.Import, but not if it was loaded from a PFX via any X509Certificate2 class members. Some portions of .NET internally create new managed X509Certificate2 objects from native certificate handles, such as the X509Chain class and the X509Certificate2Collection.Find method. These portions of the framework, and any portions that directly or indirectly use them, may result in premature key erasure as the objects they create get garbage collected.
A bug was introduced in the initial release of .NET Framework 4.8 which resulted in X509Certificate2Collection.Import to not apply the deletion marker even if neither PersistKeySet nor EphemeralKeySet were specified. .NET released a fix for this bug in the January 2021 Security and Quality Rollup to prevent the unintentional accumulation of abandoned private key files. Callers impacted by this fix can specify the PersistKeySet flag to return to the (unintentionally different) .NET Framework 4.8 RTM behavior, though doing so will result in file accumulation that needs to be addressed with custom cleanup logic.
X509Certificate2Collection.Import on .NET Core for Windows and .NET 5+ for Windows behaves in the same way as .NET Framework 2.0-4.7.2 (and .NET Framework 4.8 with all updates applied). The .NET team is considering moving to an improved design in .NET 6 (or future versions), but this design update will not apply to .NET Framework, .NET Core, or .NET 5.