Wednesday, December 19, 2012

How to create a X509Certificate2 programmatically?

X509 compatible certificates are commonly used in various scenarios. Creating a new certificate usually involves using the makecert.exe or a specialized application (I prefer Portecle, it’s easy to use and free).

Creating certificates programmatically is also a common requirement. Although the base class library supports X509 infrastructure (System.Security.Cryptography.X509Certificates namespace), there is no easy way to create certificates with sole BCL. Fortunately, there is Bouncy Castle which contains all required APIs. Bouncy Castle is available on NuGet, adding it to your solution could just not be easier.

The code below is based on a Stack Overflow entry followed by a comment from someone.

/// <summary>
/// Based on:
/// http://stackoverflow.com/questions/3770233/is-it-possible-to-programmatically-generate-an-x509-certificate-using-only-c
/// http://web.archive.org/web/20100504192226/http://www.fkollmann.de/v2/post/Creating-certificates-using-BouncyCastle.aspx
/// </summary>
public class CertificateGenerator
{
    public static X509Certificate 
        GenerateCertificate( string subjectName, out AsymmetricCipherKeyPair kp )
    {
        var kpgen = new RsaKeyPairGenerator();
 
        // certificate strength 1024 bits
        kpgen.Init( new KeyGenerationParameters( 
              new SecureRandom( new CryptoApiRandomGenerator() ), 1024 ) ); 
 
        kp = kpgen.GenerateKeyPair();
 
        var gen = new X509V3CertificateGenerator();
 
        var certName = new X509Name( "CN=" + subjectName );
        var serialNo = BigInteger.ProbablePrime( 120, new Random() );
 
        gen.SetSerialNumber( serialNo );
        gen.SetSubjectDN( certName );
        gen.SetIssuerDN( certName );
        gen.SetNotAfter( DateTime.Now.AddYears( 100 ) );
        gen.SetNotBefore( DateTime.Now.Subtract( new TimeSpan( 7, 0, 0, 0 ) ) );
        gen.SetSignatureAlgorithm( "SHA1withRSA" );
        gen.SetPublicKey( kp.Public );
 
        gen.AddExtension(
            X509Extensions.AuthorityKeyIdentifier.Id,
            false,
            new AuthorityKeyIdentifier(
                SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo( kp.Public ),
                new GeneralNames( new GeneralName( certName ) ),
                serialNo ) );
 
        /* 
         1.3.6.1.5.5.7.3.1 - id_kp_serverAuth 
         1.3.6.1.5.5.7.3.2 - id_kp_clientAuth 
         1.3.6.1.5.5.7.3.3 - id_kp_codeSigning 
         1.3.6.1.5.5.7.3.4 - id_kp_emailProtection 
         1.3.6.1.5.5.7.3.5 - id-kp-ipsecEndSystem 
         1.3.6.1.5.5.7.3.6 - id-kp-ipsecTunnel 
         1.3.6.1.5.5.7.3.7 - id-kp-ipsecUser 
         1.3.6.1.5.5.7.3.8 - id_kp_timeStamping 
         1.3.6.1.5.5.7.3.9 - OCSPSigning
         */
        gen.AddExtension(
            X509Extensions.ExtendedKeyUsage.Id,
            false,
            new ExtendedKeyUsage( new [] { KeyPurposeID.IdKPServerAuth } ) );
 
        var newCert = gen.Generate( kp.Private );
        
        return newCert;
    }
 
    public static void SaveToFile( 
        X509Certificate newCert, 
        AsymmetricCipherKeyPair kp, 
        string FilePath, 
        string CertAlias,
        string Password )
    {
        var newStore = new Pkcs12Store();
 
        var certEntry = new X509CertificateEntry( newCert );
 
        newStore.SetCertificateEntry(
            CertAlias,
            certEntry
            );
 
        newStore.SetKeyEntry(
            CertAlias,
            new AsymmetricKeyEntry( kp.Private ),
            new[] { certEntry }
            );
 
        using ( var certFile = File.Create( FilePath ) )
        {
            newStore.Save(
                certFile,
                Password.ToCharArray(),
                new SecureRandom( new CryptoApiRandomGenerator() )
                );
        }
    }
}

I can now link this with the BCL as the saved certificate is compatible with the System.Security.Cryptography.X509Certificates.X509Certificate2:

// create bouncy castle cert and save it
AsymmetricCipherKeyPair kp;
var x509  = CertificateGenerator.GenerateCertificate( "Subject", out kp );
 
string FilePath = "cert.pfx";
string Alias    = "foo";
string Pwd      = "bar";
 
CertificateGenerator.SaveToFile( x509, kp, FilePath, Alias, Pwd );
 
// open the store as X509Certificate2
var x5092 = new System.Security.Cryptography.X509Certificates.X509Certificate2( FilePath, Pwd ); 
 
Console.WriteLine( x5092.SubjectName );
Console.WriteLine( x5092.Thumbprint );
Console.WriteLine( x5092.PrivateKey.SignatureAlgorithm );

6 comments:

Anonymous said...

Ok, I'll bite. What's BCL?

Wiktor Zychla said...

http://en.wikipedia.org/wiki/Base_Class_Library

Anonymous said...

Oh, haha. Duh, I get it now.

Matt said...

Thank you so much - I had had enough of PInvoke

Anonymous said...

How to generate root certificate (CA)? Thanks

Anonymous said...

Nice Article, this what I want.
Thanks Wiktor !!!