Monday, November 7, 2022

Full circle with a gap at 11 o'clock

Some time ago I've claimed that I've run a full circle on a TLS certificate lifecycle. Well, there was one more kink in that circle that I've failed to acknowledge at the time.

The kink had to do with the unremarkable step of "installing a certificate" on the machine where previously a CSR for that certificate was generated. On the Windows UI level, it's pretty much a two click operation - double click on a signed cert in Explorer, click OK to confirm. Internally, the only parameter of note is where to place the cert (machine vs user and the cert store name).

In PowerShell, there is a ready made cmdlet for that - Import-Certificate. However, the whole theme of this series was decomposing certificate operations into .NET crypto primitives, and this one doesn't admit, it seems, such a decomposition. At least not easily.

I have even checked the decompiled MSIL of the DLL behind Import-Certificate (Microsoft.CertificateServices.PKIClient.Cmdlets.dll, available in the .NET Global Assembly Cache) - its managed implementation does a couple of sanity checks, then calls into native code.

I have a strong suspicion that this is yet another case of a crypto operation that is possible is native CryptoAPI but isn't quite exposed in .NET. Normally, I'd expect the import procedure to go like this:
  1. Load the newly signed cert
  2. Find the saved CSR in the Requests cert store by matching the public key
  3. Retrieve the private key reference from the saved CSR
  4. Attach the private key reference to the newly signed cert
  5. Add the cert to the a cert store
  6. Delete the saved CSR from the Requests store
Step 3 seems to be either impossible or rather involved. You load the CSR, you get an X509Certificate2 object where the HasPrivateKey property evaluates to True, but the PrivateKey property is empty.

No comments:

Post a Comment