Bouncy Castle C# is a great project, making X509 more accessible to programmers working on all kinds of systems. Versatile enough that a compile-time warning about an unused extension would be an annoying false positive for some. Chris asked me to share the code I wrote. So as to increase the number of example usages of Bouncy Castle C# available publicly on the Internet, here you go: a first encounter with Bouncy Castle.
Feel free to copy and improve upon the attached example source code.
using System; using System.IO; using System.Text; using System.Security.Cryptography; using Org.BouncyCastle.Asn1; using Org.BouncyCastle.Asn1.X509; using Org.BouncyCastle.Crypto; using Org.BouncyCastle.Crypto.Parameters; using Org.BouncyCastle.Crypto.Generators; using Org.BouncyCastle.Crypto.Prng; using Org.BouncyCastle.Math; using Org.BouncyCastle.Utilities; using Org.BouncyCastle.Security; using Org.BouncyCastle.X509; using Org.BouncyCastle.OpenSsl; using Org.BouncyCastle.X509.Extension; namespace UnicornCommon { public class Certificate { /// <summary> /// Generate a new certificate. /// </summary> /// <param name="name">The Common Name of the receiver of the certificate. For example *.utm.is.</param> /// <param name="expiryDate">The date when the new certificate is to expire. Either a week from now or decades from now.</param> /// <param name="passwd">The password used to unlock the root certificate.</param> /// <example> /// var cert = new Certificate("*.customer.example", /// DateTime.UtcNow.addWeeks(1), "***"); /// using (var memory = new MemoryStream(1024)) /// cert.SavePrivateKey(memory); /// </example> private static readonly string certificateAuthority = System.Web.Hosting.HostingEnvironment.MapPath("~/App_Data/your-path-to/SHA-256.private.pem"); private static readonly string certificateAuthorityPublic = System.Web.Hosting.HostingEnvironment.MapPath("~/App_Data/your-path-to/SHA-256.cer"); System.Security.Cryptography.X509Certificates.X509Certificate2 keypair; private const string issuerName = "(Your Company Name)"; private const string signatureHashAlgorithm = "SHA512WithRSA"; private const int certificateStrength = 2048; private static readonly log4net.ILog logger = log4net.LogManager.GetLogger (System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); public MemoryStream Stream() { Byte[] bytes = keypair.Export (System.Security.Cryptography.X509Certificates.X509ContentType.Pkcs12, "dummypassword"); return new MemoryStream(bytes); } private class Keyring : IPasswordFinder { char[] password; public Keyring(string passwd) { password = passwd.ToCharArray(); } public char[] GetPassword() { return password; } } private static X509Certificate ReadPubKey(string filename) { var reader = File.OpenText(filename); X509Certificate certificate; try { var pemReader = new PemReader(reader); certificate = (X509Certificate)pemReader.ReadObject(); } catch (PemException e) { //Definitely corruption logger.Error("Public key corrupt."); throw; } return certificate; } private static AsymmetricKeyParameter ReadPrivKey(string filename, string passwd) { var reader = File.OpenText(filename); Keyring keyring = new Keyring(passwd); AsymmetricKeyParameter keyPair; try { var pemReader = new PemReader(reader, keyring); keyPair = (AsymmetricKeyParameter)pemReader.ReadObject(); } catch (PemException e) { //Either corruption or, more likely, an incorrect password if(passwd != null) logger.Warn("Incorrect password to private key (or private key corrupt)."); throw; } return keyPair; } private static AsymmetricCipherKeyPair GenKeyPair(SecureRandom random) { var keyGenerationParameters = new KeyGenerationParameters(random, certificateStrength); var keyGenerator = new RsaKeyPairGenerator(); keyGenerator.Init(keyGenerationParameters); var keyPair = keyGenerator.GenerateKeyPair(); return keyPair; } private static void SetSerialNumber(X509V3CertificateGenerator certGen, SecureRandom random) { var serialNumber = BigIntegers.CreateRandomInRange (BigInteger.One, BigInteger.ValueOf(Int64.MaxValue), random) ; certGen.SetSerialNumber(serialNumber); } private void SetExpiryDate(X509V3CertificateGenerator certGen, DateTime expiryDate) { certGen.SetNotBefore(DateTime.UtcNow.Date); certGen.SetNotAfter(expiryDate); } public Certificate(string clientName, DateTime expiryDate, string passwd) { var certGen = new X509V3CertificateGenerator(); certGen.SetSubjectDN(new X509Name("CN=" + clientName)); certGen.SetIssuerDN(new X509Name("CN=(Your CA Name)")); var random = new SecureRandom(new CryptoApiRandomGenerator()); SetSerialNumber(certGen, random); SetExpiryDate(certGen, expiryDate); var keyPair = GenKeyPair(random); certGen.SetPublicKey(keyPair.Public); certGen.SetSignatureAlgorithm(signatureHashAlgorithm); const int PROVIDER_RSA_AES = 24; CspParameters cspParameters; cspParameters = new CspParameters(PROVIDER_RSA_AES); //cspParameters.Flags = CspProviderFlags.CreateEphemeralKey; cspParameters.KeyNumber = (int)KeyNumber.Exchange; cspParameters.KeyContainerName = "(YourContainerName)"; X509Certificate rootCert = ReadPubKey(certificateAuthorityPublic); var authorityKeyIdentifier = new AuthorityKeyIdentifierStructure(rootCert); certGen.AddExtension( X509Extensions.AuthorityKeyIdentifier.Id, false, authorityKeyIdentifier); AsymmetricKeyParameter rootPriv = ReadPrivKey(certificateAuthority, passwd); X509Certificate x509 = certGen.Generate(rootPriv, random); keypair = new System.Security.Cryptography.X509Certificates.X509Certificate2(DotNetUtilities.ToX509Certificate(x509)); var BCprivKey = keyPair.Private as RsaPrivateCrtKeyParameters ; var DNprivKey = DotNetUtilities.ToRSAParameters(BCprivKey); var rsaProvider = new System.Security.Cryptography.RSACryptoServiceProvider(1024, cspParameters); rsaProvider.ImportParameters(DNprivKey); keypair.PrivateKey = rsaProvider as AsymmetricAlgorithm; } } }