Search code examples
c#securityx509certificate2

Generate and sign certificate in different machines C#


I need to generate certificates to be used in secure communication between agents. Each agent generates a certificate and must send it to the system CA, in another machine, to be signed (and trusted by the other agents). I am doing it using C# with the following code for the agent:

 //generate certificate
        ECDsa elipticCurveNistP256Key = ECDsa.Create(ECCurve.CreateFromValue("1.2.840.10045.3.1.7")); // nistP256 curve
        CertificateRequest certificateRequest = new CertificateRequest("CN=" + agentId, elipticCurveNistP256Key, HashAlgorithmName.SHA256);
        certificateRequest.CertificateExtensions.Add(
            new X509BasicConstraintsExtension(false, false, 0, false));
        certificateRequest.CertificateExtensions.Add(
            new X509KeyUsageExtension(
                X509KeyUsageFlags.DigitalSignature | X509KeyUsageFlags.NonRepudiation,
                false));

        // Add the SubjectAlternativeName extension
        var sanBuilder = new SubjectAlternativeNameBuilder();
        sanBuilder.AddIpAddress(IPAddress.Parse(agentIpAddress));
        certificateRequest.CertificateExtensions.Add(sanBuilder.Build());

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

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

And the following code for the CA system:

  X509Certificate2 signedCertificate = certificateRequest.Create(
            caCertificatePFX,
            DateTimeOffset.UtcNow.AddDays(-1),
            DateTimeOffset.UtcNow.AddDays(30),
            new byte[] {1, 2, 3, 4});

Of course, I use also code for communication between the machines that I do not show here. But I have at least two problems:

  1. I would like to have a complete separation between certificate generation and signing but even with lots of tries this was the only code that I could manage to get to work. If I am not mistaken this code has the certificate creation at the CA system which is not the ideal scenario (CA has access to agent private key) but if I didn't find a better one it's something I can accept.

  2. The second problem is that even if I accept the first problem I still need to send the CertificateRequest object from one machine to another and CertificateRequest is not serializable. I have found the method CreateSigningRequest() that "Creates an ASN.1 DER-encoded PKCS#10 CertificationRequest value representing the state of the current object." however I have not found a way to then make it be a CertificateRequest object again so that I can run the CA system code.

Does anyone know how I can do this? Hopefully to completely separate certificate generation and certificate signing, but if that is not possible at least to create CertificateRequest object back.

I am running .Net Framework 4.7.2 that I need to maintain in order to use previously developed Windows Forms.

Thanks


Solution

  • As you noted, there's not a way to read back the PKCS#10 request. That's largely because too many of the things are missing to be an "OK" Certificate Authority, so having a reader would just make for a lot of "bad" Certificate Authorities. (Since your CA doesn't support revocation it's also a "bad" CA, but you're mitigating that with short lifetime certificates.)

    The PKCS#10 request contains:

    • A data format version
    • A name (presumably the one that the requester wants)
    • A public key
    • Attributes
      • The requested extensions comes here (EKUs, Subject Alternative Name, etc)
    • A signature, to prove that the requester has the private key.

    The data format version is irrelevant if you're not using the data format, and the signature is not really important for "closed" issuers (CAs that only issue certificates to directly-known parties). So you just need to transport the public key and whatever other data you need for the request (looking at your current code, the agent ID and IP address).

    The only tricky part is sending the public key... but with .NET Core 3.0+ you can normalize all of the keys to their SubjectPublicKeyInfo format:

    byte[] spki = elipticCurveNistP256Key.ExportSubjectPublicKeyInfo();
    

    While it would have been exceedingly clever for the PublicKey type to have an ImportSubjectPublicKeyInfo method, that hasn't happened yet. For generic parsing you'd want to try all the major key types, but since you're a closed CA on the other side you can know a priori that it's ECDSA:

    using (ECDsa clientPub = ECDsa.Create())
    {
        clientPub.ImportSubjectPublicKeyInfo(transmittedSpki, out _);
    
        // the rest of your code goes here.
    }