Creating a CSR with subject alternate names using BouncyCastle(.Net)

Written by Troy on August 4, 2016 Categories: Uncategorized

I’ll firstly proclaim that I am out of my depth and don’t know what I’m doing. I’m not familiar with the structure of certificates or CSRs, but internet searches did not reveal much information for what I was trying to do. So here is the results of my hacking, if they prove useful to you :)

To create a simple CSR with SANs (subject alternate names), do the following:

private string GenerateRequest(string subjectDn, ICollection sans)
{
    X509Name name = new X509Name(subjectDn);

    RsaKeyPairGenerator kpg = new RsaKeyPairGenerator();
    kpg.Init(new KeyGenerationParameters(new SecureRandom(), 2048));
    AsymmetricCipherKeyPair kp = kpg.GenerateKeyPair();

    ISignatureFactory sigFactory = new Asn1SignatureFactory("SHA256WITHRSA", kp.Private);

    Asn1Set attributes = null;
    if (sans.Count > 0)
    {
        GeneralNames names = new GeneralNames(
            sans.Select(n => new GeneralName(GeneralName.DnsName, n)).ToArray()
            );

        Asn1Sequence sanSequence = new DerSequence(X509Extensions.SubjectAlternativeName, new DerOctetString(names));
        Asn1Sequence container = new DerSequence(sanSequence);
        Asn1Set extensionSet = new DerSet(container);
        Asn1Sequence extensionRequest = new DerSequence(PkcsObjectIdentifiers.Pkcs9AtExtensionRequest, extensionSet);
        attributes = new DerSet(extensionRequest);
    }

    Pkcs10CertificationRequest csr = new Pkcs10CertificationRequest(sigFactory, name, kp.Public, attributes, kp.Private);

    StringBuilder pemString = new StringBuilder();
    PemWriter pemWriter = new PemWriter(new StringWriter(pemString));
    pemWriter.WriteObject(csr);
    pemWriter.Writer.Flush();

    return pemString.ToString();
}

Now, I can’t account for the structure. If you know how the internal structure of a CSR should look, well I guess you’re not reading this :) Anyway, I used the following code to dump out a CSR that I created with openssl, and built up my sets and sequences based on that dump. Good luck!

private string GetIndent(int indentLevel)
{
    return new string(' ', indentLevel * 2);
}

private void WriteIndent(int indentLevel, string format, params object[] args)
{
    string detail = string.Format(format, args);
    Console.WriteLine("{0}- {1}", GetIndent(indentLevel), detail);
}

private void DumpItem(Pkcs10CertificationRequest certificate)
{
    Console.WriteLine("algorithm: {0}", certificate.SignatureAlgorithm.Algorithm);
    CertificationRequestInfo requestInfo = certificate.GetCertificationRequestInfo();
    Console.WriteLine("subject: {0}", requestInfo.Subject);
    DumpItem(0, requestInfo.Attributes);
}

private void DumpItem(int indentLevel, Asn1Object item)
{
    if (item == null)
    {
        return;
    }
    DerObjectIdentifier identifier = item as DerObjectIdentifier;
    if (identifier != null)
    {
        DumpItem(indentLevel, identifier);
        return;
    }
    DerSequence sequence = item as DerSequence;
    if (sequence != null)
    {
        DumpItem(indentLevel, sequence);
        return;
    }
    DerSet set = item as DerSet;
    if (set != null)
    {
        DumpItem(indentLevel, set);
        return;
    }
    DerOctetString octet = item as DerOctetString;
    if (octet != null)
    {
        DumpItem(indentLevel, octet);
        return;
    }
    Assert.Fail("Can't yet handle a '{0}'", item.GetType()); // Yeah I'm using NUnit
}

private void DumpItem(int indentLevel, DerObjectIdentifier identifier)
{
    WriteIndent(indentLevel, "identifier {0}", identifier.Id);
}

private void DumpItem(int indentLevel, DerOctetString octet)
{
    StringBuilder asciiString = new StringBuilder();
    foreach (byte b in octet.GetOctets())
    {
        if (b < ' ' || b > 126)
        {
            asciiString.Append('_');
        }
        else
        {
            asciiString.Append((char) b);
        }
    }
    WriteIndent(indentLevel, "octet string: {0}", octet);
    WriteIndent(indentLevel, "\"{0}\"", asciiString.ToString());
}

private void DumpItem(int indentLevel, DerSet set)
{
    WriteIndent(indentLevel, "set {0} with {1} items", set.GetType(), set.Count);
    foreach (Asn1Object entry in set)
    {
        DumpItem(indentLevel + 1, entry);
    }
}

private void DumpItem(int indentLevel, DerSequence sequence)
{
    WriteIndent(indentLevel, "sequence {0}, with {1} items", sequence.GetType(), sequence.Count);
    foreach (Asn1Object entry in sequence)
    {
        DumpItem(indentLevel + 1, entry);
    }
}

private Pkcs10CertificationRequest ParseString(string request)
{
    using (StringReader stringReader = new StringReader(request))
    {
        PemReader reader = new PemReader(stringReader);
        Pkcs10CertificationRequest result = (Pkcs10CertificationRequest) reader.ReadObject();
        Assert.IsNotNull(result);
        return result;
    }
}

public void Inspect_Cert()
{
    string csr = @"-----BEGIN CERTIFICATE REQUEST----- blah blha...";
    Pkcs10CertificationRequest inspectThis = ParseString(csr);
    DumpItem(inspectThis);
}

/*
The output will look something like:
algorithm: 1.2.840.113549.1.1.5
subject: C=...
- set Org.BouncyCastle.Asn1.DerSet with 1 items
  - sequence Org.BouncyCastle.Asn1.DerSequence, with 2 items
    - identifier 1.2.840.113549.1.9.14
    - set Org.BouncyCastle.Asn1.DerSet with 1 items
      - sequence Org.BouncyCastle.Asn1.DerSequence, with 1 items
        - sequence Org.BouncyCastle.Asn1.DerSequence, with 2 items
          - identifier 2.5.29.17
          - octet string: #30368...
          - "0___foo-na.com__foo-eu.com..."

*/
No Comments

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>