Generate and Sign Certificate Request using pure .net Framework
Asked Answered
S

2

55

I am trying to use pure .net code to create a certificate request and create a certificate from the certificate request against an existing CA certificate I have available (either in the Windows Certificate store or as a separate file).

I know that I have the classes X509Certificate and X509Certificate2 available to load certificates and get access to their information, but I don't see any classes or functionality within the System.Security.Cryptography namespace that could be used to create a certificate request or to sign such a certificate request to create a new signed certificate.

And that although the documentation on the System.Security.Cryptography.Pkcs namespace says:

The System.Security.Cryptography.Pkcs namespace provides programming elements for Public Key Cryptography Standards (PKCS), including methods for signing data, exchanging keys, requesting certificates, public key encryption and decryption, and other security functions.

So, how can I create a certificate request and fulfill that request to create a new X509 certificate using only pure .net classes from System.Security.Cryptography?


Note:

  • I don't want to use an external executable like openssl or MakeCert
  • I don't want to use BouncyCastle
  • I don't want to use Windows Certificate Enrollment API
  • I don't want to use the native Win32 API functions
Swen answered 10/1, 2018 at 21:27 Comment(2)
Looking at this and looking at the source code I don't think you'll be able to do this without one of the options you dismissedKill
@MaartenBodewes The question is not whether I want to use a library or not. I can do what I want to do using the Certificate Enrollment API. My question is: Do I have to use an additional library (like the Certificate Enrollment Library) or is the functionality maybe already available in the default .net Framework and I am just so blind that I don't see it?Swen
S
90

Short answer: You can starting in .NET Framework 4.7.2.

This functionality was originally added to .NET Core 2.0 in the form of the CertificateRequest class, which can build a PKCS#10 certification signing request or an X.509 (self-signed or chained) public key certificate.

The classes for that feature were made available in .NET Framework 4.7.2.

using (RSA parent = RSA.Create(4096))
using (RSA rsa = RSA.Create(2048))
{
    CertificateRequest parentReq = new CertificateRequest(
        "CN=Experimental Issuing Authority",
        parent,
        HashAlgorithmName.SHA256,
        RSASignaturePadding.Pkcs1);

    parentReq.CertificateExtensions.Add(
        new X509BasicConstraintsExtension(true, false, 0, true));

    parentReq.CertificateExtensions.Add(
        new X509SubjectKeyIdentifierExtension(parentReq.PublicKey, false));

    using (X509Certificate2 parentCert = parentReq.CreateSelfSigned(
        DateTimeOffset.UtcNow.AddDays(-45),
        DateTimeOffset.UtcNow.AddDays(365)))
    {
        CertificateRequest req = new CertificateRequest(
            "CN=Valid-Looking Timestamp Authority",
            rsa,
            HashAlgorithmName.SHA256,
            RSASignaturePadding.Pkcs1);

        req.CertificateExtensions.Add(
            new X509BasicConstraintsExtension(false, false, 0, false));

        req.CertificateExtensions.Add(
            new X509KeyUsageExtension(
                X509KeyUsageFlags.DigitalSignature | X509KeyUsageFlags.NonRepudiation,
                false));

        req.CertificateExtensions.Add(
            new X509EnhancedKeyUsageExtension(
                new OidCollection
                {
                    new Oid("1.3.6.1.5.5.7.3.8")
                },
                true));

        req.CertificateExtensions.Add(
            new X509SubjectKeyIdentifierExtension(req.PublicKey, false));

        using (X509Certificate2 cert = req.Create(
            parentCert,
            DateTimeOffset.UtcNow.AddDays(-1),
            DateTimeOffset.UtcNow.AddDays(90),
            new byte[] { 1, 2, 3, 4 }))
        {
            // Do something with these certs, like export them to PFX,
            // or add them to an X509Store, or whatever.
        }
    }
}

Longer answer if you're stuck on older versions: To accomplish your goal without adding any new P/Invokes, you would need to read and understand the following documents:

  • ITU-T X.680-201508, the ASN.1 language
  • IETF RFC 5280 or ITU-T X.509, the documents that explain the fields in X.509 certificates.
  • IETF RFC 2986, explains the PKCS#10 certification signing request
  • ITU-T X.690, explains the BER encoding family for ASN.1 (including DER) which tells you how to read and write bytes to achieve the semantic meaning from X.509 / PKCS#10.

And then you could write a DER writer/reader, and just emit the bytes for what you want.

Shirline answered 11/1, 2018 at 15:29 Comment(13)
CertificateRequest is available in the .NET Framework 4.7.2 early access build (blogs.msdn.microsoft.com/dotnet/2018/02/05/…). When it releases fully hopefully I (or someone else) remembers to edit the answer.Shirline
Hi @bartonjs, What should the serialNumber be? how can we get/generate it?Sapsago
@Sapsago CA/Browser Forum's Baseline Requirements (v1.7.2) only says "CAs SHALL generate non-sequential Certificate serial numbers greater than zero (0) containing at least 64 bits of output from a CSPRNG.", which is the guidance for public CAs. If you're a private CA: Pure random, incrementing integer, high 4 bytes are an integer, low 8 are random, whatever you like (just don't be more than 20 bytes long).Shirline
@Shirline Getting following error when a client connection request is received and certificate used is as mentioned above. _____ Error - The server mode SSL must use a certificate with the associated private key. ____ Any clue?Charland
@JackSparrow Sounds like you need a X509Certificate2 certWithKey = cert.CopyWithPrivateKey(key); in the "do something with these certs" block.Shirline
Thanks @Shirline CopyWithPrivateKey + github.com/dotnet/runtime/issues/23749, cleared initial errors, and could able to assign a certificate at service start with kestral, its working all good on Firefox and Microsoft edge, getting following error in google chrome, Failed to load resource: net::ERR_CERT_AUTHORITY_INVALID.., will appreciate any help?Charland
@JackSparrow That’s a browser specific problem, please ask a new question.Shirline
@Shirline Why do we need to specify a serial number if this CSR is meant to be submitted to an Active Directory PKI CA? Doesn't the CA generate the serial number for me?Immigration
@Shirline How would I use this code to get a cert from my on prem AD PKI CA? I have code that works using CERTENROLLLib but I would like to port it over to dotnet core.Immigration
@Immigration The CreateSigningRequest doesn't need a serial number, since (as you suggest) it doesn't make sense. As for replacing some CERTENROLLLib usage, you should ask a new question saying what you have, and what you've tried so far to replace it.Shirline
@Shirline The question was how to create a signed CSR ("how can I create a certificate request and sign that request"). The output of this code is a certificate, so this doesn't actually answer the question.Aplacental
@KeithRobertson The question clarifies it as "... and sign that request to create a new X509 certificate", which is why the answer creates a certificate. You can make the signed request with CreateSigningRequest instead of CreateShirline
can you convert this to vb.net ?Donaghue
N
7

I cant comment on the answer above, so this serves as a comment. @Keith

if the issue is the private key used for the server certificate is missing the private key i hope this explains whats going on.

To combine the public and private keys in the answer above,

cert = RSACertificateExtensions.CopyWithPrivateKey(cert, rsa);

This will bundle the private key with the certificate for exporting to PFX with File.WriteAllBytes("filename", cert.Export(X509ContentType.Pfx, "passphrase for export"));

for the example provided above. The method CreateSelfSigned returns a X509Certificate2 object with the public and private key attached. Where as when signing against a root, or subordinate The Create method will only create the public key component in the X509Certificate2 object.

I think this is because the usual certificate methods would use the CSR to sign against and return the public key for acceptance by the client which would never expose the private key to the signing server.

Natator answered 26/10, 2021 at 3:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.