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;
}
}
}