Home > Software engineering >  C# - Generate X509 Certificate based on a given issuer certificate in byte[]
C# - Generate X509 Certificate based on a given issuer certificate in byte[]

Time:09-22

I want to create iothub device certificates from C# code. The root CA is stored in keyvault as a .pfx, fetched as a string, and then converted from base 64 in order to obtain the certificate bytes as it is required for a certificate stored in keyvault: Azure Key Vault Certificates does not have the Private Key when retrieved via IKeyVaultClient.GetCertificateAsync

I want to write a function that will take these bytes, along with a subject name (for the leaf certificate) and will create a x509 certificate (with both public and private keys) that would have the issuer as the root.

Here is what I have sketched so far:

public static X509Certificate2 GenerateCertificateBasedOnIssuer(string subjectName, byte[] issuerByteCert)
        {
            var issuerCertificate = new X509Certificate2(issuerByteCert);

            RSA keyProvider = issuerCertificate.GetRSAPrivateKey();

            CertificateRequest certificateRequest = new CertificateRequest($"CN={subjectName}", keyProvider, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);

            CryptoApiRandomGenerator randomGenerator = new CryptoApiRandomGenerator();
            SecureRandom random = new SecureRandom(randomGenerator);
            BigInteger serialNumber = BigIntegers.CreateRandomInRange(BigInteger.One, BigInteger.ValueOf(Int64.MaxValue), random);

            var publicOnlyDeviceCertificate = certificateRequest.Create(issuerCertificate, issuerCertificate.NotBefore, issuerCertificate.NotAfter, serialNumber.ToByteArray());

            return publicOnlyDeviceCertificate; // oh no ! :(

        }

The issue I am having with this solution is that the created certificate only contains a public key.

I found another solution that appears to solve my problem on another Stack Overflow question using BouncyCastle's X509V3CertificateGenerator: Generate a self-signed certificate on the fly

The issue I have with this solution is that I cannot convert my rootCA certificate's private key to an AsymmetricKeyParameter (first parameter of the X509V3CertificateGenerator.Generate method). I tried converting the issuer's key to AsymmetricKeyParameter using this solution: convert PEM encoded RSA public key to AsymmetricKeyParameter, but I got an invalid operation exception.

I was wondering if I was on the right path (as far as understanding goes) and if there is a way to generate a certificate with a private (and public key) based on the code I currently have in place.

UPDATE: I have been able to convert a private key to an AsymmetricKeyParameter by hardcoding the key as follows:

 string testKey = @"-----BEGIN PRIVATE KEY-----
<THE KEY>
-----END PRIVATE KEY-----
";
 var stringReader = new StringReader(testKey);
 var pemReader = new PemReader(stringReader);
 var pemObject = pemReader.ReadObject(); 
 var keyParam = ((AsymmetricKeyParameter)pemObject);

Azure keyvault stores certificate in a pfx format. I am thinking of storing the private key as a secret string. I will keep testing with an hardcoded key for now until I get to a working solution.

I am now testing with BouncyCastle and will come back with a working solution if it works!

CodePudding user response:

The key you pass to CertificateRequest is used as the public key in the cert... so you want to pass a new key, not the issuer's key.

Then, once you now have the subject key, you use CopyWithPrivateKey at the end to glue them back together.

public static X509Certificate2 GenerateCertificateBasedOnIssuer(string subjectName, byte[] issuerByteCert)
{
    using (var issuerCertificate = new X509Certificate2(issuerByteCert))
    using (RSA subjectKey = RSA.Create(2048))
    {
        CertificateRequest certificateRequest = new CertificateRequest($"CN={subjectName}", subjectKey, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);

        CryptoApiRandomGenerator randomGenerator = new CryptoApiRandomGenerator();
        SecureRandom random = new SecureRandom(randomGenerator);
        BigInteger serialNumber = BigIntegers.CreateRandomInRange(BigInteger.One, BigInteger.ValueOf(Int64.MaxValue), random);

        var publicOnlyDeviceCertificate = certificateRequest.Create(issuerCertificate, issuerCertificate.NotBefore, issuerCertificate.NotAfter, serialNumber.ToByteArray());

        using (publicOnlyDeviceCertificate)
        {
            return publicOnlyDeviceCertificate.CopyWithPrivateKey(subjectKey);
        }
    }
  • Related