Monday, May 11, 2020

CSR with PowerShell

Following up from a past post, I've decided to put together a PowerShell script for generating a certificate signing request for a certificate authority to sign. Here's the script, but some commentary is in order.



There are several examples of creating certificate signing requests (CSRs) using PowerShell out there, but they tend to use the certreq command line utility, and that's a crying shame, seeing that PowerShell is a perfectly valid .NET library consumer. The System.Security.Cryptography namespace has all the crypto you need.

I explicitly set out to emulate both the certificate request format and the storage methods that Microsoft tools use, so that the generated request would play nicely with the rest of the Microsoft world. Notably, the private key is persisted in the Windows key store (either current user or local machine), and the certificate request is persisted as a self-signed cert-key pair in the certificate store under "Certificate Enrollment Requests".

Since emulating the Microsoft toolset was the goal, some of the cert properties the script creates are extraneous. For example, for Key Usage it's enough to provide Key Encipherment and Digital Signature, but Microsoft also adds Data Encipherment, so my script does, too. In the same vein, for Enhanced Key Usage, Server Auth is sufficient for TLS, but Microsoft adds Client Auth also, and so does my script. To the best of my knowledge, the mainstream HTTPS clients would happily work without those extra usage values.

The crypto algorithms in the script are hard-coded to RSA with 2048-bit key and SHA256. As of this writing (May 2020), this is considered sufficiently secure. The script uses .NET classes for RSA that are backed by the native CNG library (Crypto Next Generation), as opposed to the legacy CryptoAPI.  CNG is supported since Windows Vista.

Following Microsoft's example, the script adds the Application Policies and Friendly Name extensions. Application Policies seems to be a slightly reformatted copy of Enhanced Key Usage. The script hardcodes them both. As for the Friendly Name extension (OID 1.3.6.1.4.1.311.10.11.11), that's a strange extension. Even though the Microsoft Certificate Manager appends it to the cert request as long as you provide a friendly name, it's a second class citizen in Windows. Microsoft's own certificate viewer doesn't know about it (displays just the numeric OID, and the value is displayed as a raw byte array). The System.Security.Cryptography.Oid class doesn't report its display name. The native API function CryptFindOIDInfo, which backs the display name lookup in Oid, returns NULL for it. The said OID is documented in wincrypt.h as szOID_CERT_PROP_ID_PREFIX followed by CERT_FRIENDLY_NAME_PROP_ID. Its content should be a UTF-16 representation of the certificate friendly name parameter, NULL terminated. Even though the certificate details window is not aware of this extension, whatever logic generates the Friendly Name column in the certificate list is.

There's a Windows feedback problem report for that issue. Feel free to click and upvote.

Subject Alternative Name is pretty much a required extension now. Chrome displays the scary security message (you know the one) if SAN is not present in the cert. So the script doesn't even consider the case when alternative name(s) are not given. The script uses the SubjectAlternativeNameBuilder class, which is only available since .NET 4.7.2; there's a legacy version of the script side by side that doesn't depend on that class.

In PowerShell 5+, there's a using namespace command, which would make the script easier to follow by omitting the namespace name. I've decided to go with PowerShell 4 compatibility instead.

No comments:

Post a Comment