Post

How to Create Certificates in C# via Mono.Security

To generate certificates for testing, you probably know the makecert utility from .NET Framework SDK (now Windows SDK). But what if we are going to generate such certificates in C# code?

BouncyCastle is a very famous toolset in this case, if you happen to know it. Roger Lipscombe wrote about how to utilize it with very in-depth details in this blog post. But this library is really big (consider it has so many features), and is not shipped by Mono, I was looking for a more suitable solution on Mono platform, and finally decided to extend Mono.Security.

Of course, you always have samples aside, both this makecert clone and my Jexus Manager.

This post will focus on the later to show you how a self signed certificate is generated by Jexus Manager when it is initially on a new machine.

Certificate Properties

This certificate must match the Common Name of www.jexusmanager.com. Except that it must be a Certificate Authority so we call it a self-signed certificate. The RFC documents also require this certificate to have Subject Key Identifier and Authority Key Identifier (which are equal in this self signed case)

Commented Code

We set the issuer and subject to be the same, and other default properties.

string defaultIssuer = “CN=www.jexusmanager.com”; string defaultSubject = “CN=www.jexusmanager.com”; byte[] sn = Guid.NewGuid().ToByteArray(); string subject = defaultSubject; string issuer = defaultIssuer; DateTime notBefore = DateTime.Now; DateTime notAfter = new DateTime(643445675990000000); // 12/31/2039 23:59:59Z

Then we generate a new public/private key pair as issuer key, RSA issuerKey = new RSACryptoServiceProvider(2048); RSA subjectKey = null;

For extensions, we carefully set Basic Constaints so that this certificate is for Certificate Authority,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
bool selfSigned = true;
string hashName = "SHA1";
CspParameters subjectParams = new CspParameters();
CspParameters issuerParams = new CspParameters();
BasicConstraintsExtension bce = new BasicConstraintsExtension
{
    PathLenConstraint = BasicConstraintsExtension.NoPathLengthConstraint,
    CertificateAuthority = true
};
ExtendedKeyUsageExtension eku = null;
SubjectAltNameExtension alt = null;
string p12file = Path.Combine(path, "temp.pfx");
string p12pwd = "test";
// serial number MUST be positive
if ((sn[0] & 0x80) == 0x80)
    sn[0] -= 0x80;

if (selfSigned)
{
    if (subject != defaultSubject)
    {
        issuer = subject;
        issuerKey = subjectKey;
    }
    else
    {
        subject = issuer;
        subjectKey = issuerKey;
    }
}

if (subject == null)
    throw new Exception("Missing Subject Name");

We finally set two extra extensions (note that the digest was calculated via BouncyCastle at this moment),

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
X509CertificateBuilder cb = new X509CertificateBuilder(3);
cb.SerialNumber = sn;
cb.IssuerName = issuer;
cb.NotBefore = notBefore;
cb.NotAfter = notAfter;
cb.SubjectName = subject;
cb.SubjectPublicKey = subjectKey;
// extensions
if (bce != null)
    cb.Extensions.Add(bce);

if (eku != null)
    cb.Extensions.Add(eku);

if (alt != null)
    cb.Extensions.Add(alt);

IDigest digest = new Sha1Digest();
byte[] resBuf = new byte[digest.GetDigestSize()];
var spki = SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(DotNetUtilities.GetRsaPublicKey(issuerKey));
byte[] bytes = spki.PublicKeyData.GetBytes();
digest.BlockUpdate(bytes, 0, bytes.Length);
digest.DoFinal(resBuf, 0);
cb.Extensions.Add(new SubjectKeyIdentifierExtension { Identifier = resBuf });
cb.Extensions.Add(new AuthorityKeyIdentifierExtension { Identifier = resBuf });
// signature
cb.Hash = hashName;
byte[] rawcert = cb.Sign(issuerKey);

Solely getting the raw bytes are not enough, and we need to put it into a PKCS12 container,

1
2
3
4
5
6
7
8
9
10
11
PKCS12 p12 = new PKCS12();
p12.Password = p12pwd;
ArrayList list = new ArrayList();
// we use a fixed array to avoid endianess issues
// (in case some tools requires the ID to be 1).
list.Add(new byte[4] { 1, 0, 0, 0 });
Hashtable attributes = new Hashtable(1);
attributes.Add(PKCS9.localKeyId, list);
p12.AddCertificate(new Mono.Security.X509.X509Certificate(rawcert), attributes);
p12.AddPkcs8ShroudedKeyBag(subjectKey, attributes);
p12.SaveToFile(p12file);

OK, now we get the certificate.

Interestingly that the stable build of Mono.Security does not allow you to set Subject Key Identifier and Authority Key Identifier extensions as above. That’s because the changes were made by me firstly in my fork of Mono, and later accepted by Mono guys in the master,

https://github.com/mono/mono/pull/1057

As this is my second pull request to them, and the very first accepted, I wrote this blog as a souvenir. Enjoy it.

© Lex Li. All rights reserved. The code included is licensed under CC BY 4.0 unless otherwise noted.