Search code examples
c#opensslcryptographyrsax509

How to generate an RSA public key using a CSR file in C#?


I have 2 lines of openssl commands that work and generate the public key that would work with an api that requires it to function:

openssl req -new -utf8 -nameopt multiline,utf8 -config fa.cnf -newkey rsa:2048 -nodes -keyout fa.key -out fa.csr
openssl req -in fa.csr -noout -pubkey -out publickey.pem

As far as I understand the command, it first try to generate a .csr file using fa.cnf config file and an RSA private key and save them in fa.csr and fa.csr files relatively. then, it generates the public key by specifying the .csr file. Here is the configuration file:

[req]
prompt = no
distinguished_name = dn

[dn]
CN=MyCompany [Stamp]
serialNumber = 12345678910
O = Non-Governmental
C = US

I've tried to implement the exact procedure in C#, and this is as far as I could get:

var subjectName = "CN=MyCompany [Stamp],O=Non-Governmental,serialNumber=12345678910,C=US";

// Create new Object for Issuer and Subject
var subject = new X509Name(subjectName);

// Generate the key Value Pair, which in our case is a public Key
var randomGenerator = new CryptoApiRandomGenerator();
var random = new SecureRandom(randomGenerator);
const int strength = 2048;
var keyGenerationParameters = new KeyGenerationParameters(random, strength);

var keyPairGenerator = new RsaKeyPairGenerator();
keyPairGenerator.Init(keyGenerationParameters);
var subjectKeyPair = keyPairGenerator.GenerateKeyPair();

using (var fw = File.CreateText("fa.key"))
{
    var writer = new PemWriter(fw);
    writer.WriteObject(subjectKeyPair.Private);
}

//PKCS #10 Certificate Signing Request
var csr = new Pkcs10CertificationRequest("SHA1WITHRSA", subject, subjectKeyPair.Public, null, subjectKeyPair.Private);

//Convert BouncyCastle CSR to .PEM file.
var CSRPem = new StringBuilder();
var CSRPemWriter = new PemWriter(new StringWriter(CSRPem));
CSRPemWriter.WriteObject(csr);
CSRPemWriter.Writer.Flush();

//get CSR text
var CSRtext = CSRPem.ToString();

// Write content into a Txt file
using (StreamWriter f = new StreamWriter(@"fa.csr"))
{
    f.Write(CSRtext);
}

var publicKey = csr.GetPublicKey();

using (var fw = File.CreateText("publickey.pem"))
{
    var writer = new PemWriter(fw);
    writer.WriteObject(publicKey);
}

But the generated public key file is not valid. and the api doesn't work. I don't understand why it doesn't work or which part I've messed up. The only difference between the files generated by these 2 methods is the header of the private key file fa.key which writes this:

-----BEGIN RSA PRIVATE KEY-----

Instead of this:

-----BEGIN PRIVATE KEY-----

I appreciate anyone who could lead me the right direction or introduce me a library that could make it easy to generate the files.


Solution

  • This is fairly well built in for .NET 7. For older .NETs the PEM-exporting methods don't exist, but converting the binary data to PEM is fairly easy (with the PemEncoding class (.NET 5+), or just a helper function).

    using (RSA key = RSA.Create(2048))
    {
        CertificateRequest request = new CertificateRequest(
            "CN=MyCompany [Stamp], O=Non-Governmental, serialNumber=12345678910, C=US",
            key,
            HashAlgorithmName.SHA256,
            RSASignaturePadding.Pkcs1);
    
        // other stuff could have modified the request here, but you aren't
        // using any of the extra fancy options
    
        File.WriteAllText("fa.key", key.ExportPkcs8PrivateKeyPem());
        File.WriteAllText("fa.csr", request.CreateSigningRequestPem());
        File.WriteAllText("publickey.pem", key.ExportSubjectPublicKeyInfoPem());
    }
    

    I've gone ahead and upgraded this to SHA-256, which is probably what the OpenSSL command is already doing. (Also because CertificateRequest doesn't support SHA-1, as it was already a broken algorithm when CertificateRequest was built)