Diffie Hellman Key Exchange using ECDSA x509 certificates - c#

I am trying to perform a Diffie-Hellman key exchange using 2 ECDSA x509 certificates.
Here is the method where I extract the keys from the certificates for computation of the derived key.
private byte[] GetDerivedKey(X509Certificate2 publicCertificate, X509Certificate2 privateCertificate)
{
byte[] derivedKey;
using (var privateKey = privateCertificate.GetECDsaPrivateKey())
using (var publicKey = publicCertificate.GetECDsaPublicKey())
{
var privateParams = privateKey.ExportParameters(true); //This line is failing
var publicParams = publicKey.ExportParameters(false);
using (var privateCng = ECDiffieHellmanCng.Create(privateParams))
using (var publicCng = ECDiffieHellmanCng.Create(publicParams))
{
derivedKey = privateCng.DeriveKeyMaterial(publicCng.PublicKey);
}
}
return derivedKey;
}
I've commented on the line that is failing privateKey.ExportParameters(true) with the error:
System.Security.Cryptography.CryptographicException : The requested operation is not supported.
at System.Security.Cryptography.NCryptNative.ExportKey(SafeNCryptKeyHandle key, String format)
at System.Security.Cryptography.CngKey.Export(CngKeyBlobFormat format)
at System.Security.Cryptography.ECCng.ExportParameters(CngKey key, Boolean includePrivateParameters, ECParameters& ecparams)
at System.Security.Cryptography.ECDsaCng.ExportParameters(Boolean includePrivateParameters)
Because this is a self signed certificate that I am generating, I assume I am doing something wrong.
I first create a root CA certificate and pass in the private key to sign my certificate.
private X509Certificate2 CreateECSDACertificate(string certificateName,
string issuerCertificateName,
TimeSpan lifetime,
AsymmetricKeyParameter issuerPrivateKey,
string certificateFriendlyName = null)
{
// Generating Random Numbers
var randomGenerator = new CryptoApiRandomGenerator();
var random = new SecureRandom(randomGenerator);
var signatureFactory = new Asn1SignatureFactory("SHA256WithECDSA", issuerPrivateKey, random);
// The Certificate Generator
var certificateGenerator = new X509V3CertificateGenerator();
// Serial Number
var serialNumber = BigIntegers.CreateRandomInRange(BigInteger.One, BigInteger.ValueOf(Int64.MaxValue), random);
certificateGenerator.SetSerialNumber(serialNumber);
// Issuer and Subject Name
var subjectDistinguishedName = new X509Name($"CN={certificateName}");
var issuerDistinguishedName = new X509Name($"CN={issuerCertificateName}");
certificateGenerator.SetSubjectDN(subjectDistinguishedName);
certificateGenerator.SetIssuerDN(issuerDistinguishedName);
// Valid For
var notBefore = DateTime.UtcNow.Date;
var notAfter = notBefore.Add(lifetime);
certificateGenerator.SetNotBefore(notBefore);
certificateGenerator.SetNotAfter(notAfter);
//key generation
var keyGenerationParameters = new KeyGenerationParameters(random, _keyStrength);
var keyPairGenerator = new ECKeyPairGenerator();
keyPairGenerator.Init(keyGenerationParameters);
var subjectKeyPair = keyPairGenerator.GenerateKeyPair();
certificateGenerator.SetPublicKey(subjectKeyPair.Public);
var certificate = certificateGenerator.Generate(signatureFactory);
var store = new Pkcs12Store();
var certificateEntry = new X509CertificateEntry(certificate);
store.SetCertificateEntry(certificateName, certificateEntry);
store.SetKeyEntry(certificateName, new AsymmetricKeyEntry(subjectKeyPair.Private), new[] { certificateEntry });
X509Certificate2 x509;
using (var pfxStream = new MemoryStream())
{
store.Save(pfxStream, null, new SecureRandom());
pfxStream.Seek(0, SeekOrigin.Begin);
x509 = new X509Certificate2(pfxStream.ToArray());
}
x509.FriendlyName = certificateFriendlyName;
return x509;
}
The .HasPrivateKey() method returns true, which I've read can return a false positive.
When I add my certificates to the store, I can verify the cert chain.
[Test]
public void CreateSelfSignedCertificate_AfterAddingToStore_CanBuildChain()
{
var result = _target.CreateSelfSignedCertificate(_subject, _issuer, TimeSpan.FromDays(356), _certificateFriendlyName, _issuerFriendlyName);
_store.TryAddCertificateToStore(result.CertificateAuthority, _caStoreName, _location);
_store.TryAddCertificateToStore(result.Certificate, _certStoreName, _location);
var chain = new X509Chain
{
ChainPolicy =
{
RevocationMode = X509RevocationMode.NoCheck
}
};
var chainBuilt = chain.Build(result.Certificate);
if (!chainBuilt)
{
foreach (var status in chain.ChainStatus)
{
Assert.Warn(string.Format("Chain error: {0} {1}", status.Status, status.StatusInformation));
}
}
Assert.IsTrue(chainBuilt, "Chain");
}
I thought at first that maybe the private cert had to come from the cert store, so I imported it and then pulled it back out, but I get the same error, which is another reason I believe I'm not doing something quite right.
EDIT:
I have another class generating RSA x509's using the same code for putting the private key into the certificate. It allows me to export the RSA private key.
The variable _keyStrength is 384 and my signature factory is using "SHA256withECDSA". I have also tried using "SHA384withECDSA" but I get the same error.

OK. It's a blind shot but after looking at your code I noticed two things:
When you create PFX you set null password. But when you load the PFX into X509Certificate2 class you are using wrong constructor. You should use one with a password parameter and give a null into it
When you load PFX into X509Certificate2 class you do not specify, if the private key should be exportable. I think that this is the reason why privateKey.ExportParameters(true) gives you an exception. You should use this constructor and specify null as password
Made it working
I thought it was a bug. It's possible that it is. We clearly stated in X509Constructor that the private key should be exportable. I used X509KeyStorageFlags.EphemeralKeySet | X509KeyStorageFlags.Exportable flags too. But when I looked at the CngKey it had ExportPolicy set to AllowExport but not AllowPlaintextExport.
It was exportable in some way. privateKey.Key.Export(CngKeyBlobFormat.OpaqueTransportBlob) worked. But privateKey.ExportParameters(true) did not.
I've searched for a solution how to change ExportPolicy of CngKey. I found this SO question that helped me to change it. After that the ExportParameters worked.
The fixed version of your GetDerivedKey method is
private byte[] GetDerivedKey(X509Certificate2 publicCertificate, X509Certificate2 privateCertificate)
{
byte[] derivedKey;
using (var privateKey = privateCertificate.GetECDsaPrivateKey())
using (var publicKey = privateCertificate.GetECDsaPublicKey())
{
var myPrivateKeyToMessWith = privateKey as ECDsaCng;
// start - taken from https://stackoverflow.com/q/48542233/3245057
// make private key exportable:
byte[] bytes = BitConverter.GetBytes((int)(CngExportPolicies.AllowExport | CngExportPolicies.AllowPlaintextExport));
CngProperty pty = new CngProperty(NCryptExportPolicyProperty, bytes, CngPropertyOptions.Persist);
myPrivateKeyToMessWith.Key.SetProperty(pty);
// end - taken from https://stackoverflow.com/q/48542233/3245057
var privateParams = myPrivateKeyToMessWith.ExportParameters(true); //This line is NOT failing anymore
var publicParams = publicKey.ExportParameters(false);
using (var privateCng = ECDiffieHellmanCng.Create(privateParams))
using (var publicCng = ECDiffieHellmanCng.Create(publicParams))
{
derivedKey = privateCng.DeriveKeyMaterial(publicCng.PublicKey);
}
}
return derivedKey;
}

I started using the solution #pepo posted which lead me to discover 'GetECDsaPrivateKey' does not return an ECDsa object but an ECDsaCng. I simplified the key derivation to this.
byte[] derivedKey;
using (var privateKey = (ECDsaCng)certificate.GetECDsaPrivateKey())
using (var publicKey = (ECDsaCng)certificate.GetECDsaPublicKey())
{
var publicParams = publicKey.ExportParameters(false);
using (var publicCng = ECDiffieHellmanCng.Create(publicParams))
using (var diffieHellman = new ECDiffieHellmanCng(privateKey.Key))
{
derivedKey = diffieHellman.DeriveKeyMaterial(publicCng.PublicKey);
}
}
return derivedKey;

Related

Creating Netsuite Certificate X509 using C#

I am trying to generate a certificate using Bouncy Castle. When I load it into Netsuite it says certificate parsing error.
The certificate must meet the following requirements:
The public part of the certificate must be in x.509 format with a file extension of .cer, .pem, or .crt.
The length of the RSA key must be 3072 bits, or 4096 bits. The length of EC key must 256 bits, 384 bits, or 521 bits.
The maximum time period that a certificate can be valid is two years. If the certificate is valid for a longer time period, the system automatically shortens the time period to two years.
One certificate can only be used for one combination of integration record, role, and entity. If you want to use the same integration record for multiple entities or roles, you must use a different certificate for each unique combination.
The following example shows how to create a valid certificate using OpenSSL:
openssl req -x509 -newkey rsa:4096 -sha256 -keyout auth-key.pem -out auth-cert.pem -nodes -days 730.
Using the code below
public async Task GenerateX509Certificate()
{
int TenantId = (int)_session.TenantId;
var IntegrationData = await _appIntegrationRepository.FirstOrDefaultAsync(c => c.TenantId == TenantId);
RsaKeyPairGenerator g = new RsaKeyPairGenerator();
g.Init(new KeyGenerationParameters(new SecureRandom(), 3072));
AsymmetricCipherKeyPair pair = g.GenerateKeyPair();
PrivateKeyInfo privateKeyInfo = PrivateKeyInfoFactory.CreatePrivateKeyInfo(pair.Private);
byte[] serializedPrivateBytes = privateKeyInfo.ToAsn1Object().GetDerEncoded();
string serializedPrivate = Convert.ToBase64String(serializedPrivateBytes);
SubjectPublicKeyInfo publicKeyInfo = SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(pair.Public);
byte[] serializedPublicBytes = publicKeyInfo.ToAsn1Object().GetDerEncoded();
string serializedPublic = Convert.ToBase64String(serializedPublicBytes);
// Distribute PEMs.
IntegrationData.PublicKey = serializedPublic;
IntegrationData.PrivateKey = serializedPrivate;
var key = GenerateX509Certificate("company","Los Angeles");
var outputkey = Convert.ToBase64String(key.GetRSAPrivateKey().ExportRSAPrivateKey());
TextWriter textWriter = new StringWriter();
PemWriter pemWriter = new PemWriter(textWriter);
pemWriter.WriteObject(key);
pemWriter.Writer.Flush();
string privateKey = textWriter.ToString();
await _appIntegrationRepository.UpdateAsync(IntegrationData);
}
public static X509Certificate2 GenerateX509Certificate(string commonName, string locality)
{
var keyPair = GetAsymmetricCipherKeyPair();
var certificateGenerator = GetX509V3CertificateGenerator(keyPair, commonName, locality);
var bouncyCastleCertificate = GenerateBouncyCastleCertificate(
keyPair, certificateGenerator);
return GenerateX509CertificateWithPrivateKey(
keyPair, bouncyCastleCertificate);
}
private static AsymmetricCipherKeyPair GetAsymmetricCipherKeyPair()
{
var keyPairGen = new RsaKeyPairGenerator();
var keyParams = new KeyGenerationParameters(
new SecureRandom(new CryptoApiRandomGenerator()), 4096);
keyPairGen.Init(keyParams);
var keyPair = keyPairGen.GenerateKeyPair();
return keyPair;
}
private static X509V3CertificateGenerator GetX509V3CertificateGenerator(
AsymmetricCipherKeyPair keyPair, string commonName, string locality)
{
IDictionary attrs = new Hashtable();
attrs[X509Name.CN] = commonName;
attrs[X509Name.L] = locality;
IList ord = new ArrayList();
ord.Add(X509Name.CN);
ord.Add(X509Name.L);
var gen = new X509V3CertificateGenerator();
gen.SetSerialNumber(BigInteger.ProbablePrime(120, new Random()));
gen.SetNotAfter(DateTime.Now.AddDays(1));
gen.SetNotBefore(DateTime.Now.AddDays(-1));
gen.SetPublicKey(keyPair.Public);
gen.AddExtension(X509Extensions.BasicConstraints, true, new BasicConstraints(false));
gen.SetSubjectDN(new X509Name(ord, attrs));
gen.SetIssuerDN(new X509Name(ord, attrs));
var ski = new SubjectKeyIdentifier(
SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(keyPair.Public));
gen.AddExtension(X509Extensions.SubjectKeyIdentifier, false, ski);
var keyUsage = new KeyUsage(KeyUsage.DigitalSignature | KeyUsage.KeyCertSign);
gen.AddExtension(X509Extensions.KeyUsage, true, keyUsage);
return gen;
}
private static X509Certificate GenerateBouncyCastleCertificate(
AsymmetricCipherKeyPair keyPair, X509V3CertificateGenerator gen)
{
ISignatureFactory sigFact = new Asn1SignatureFactory(
"SHA256WithRSA", keyPair.Private);
var bcCert = gen.Generate(sigFact);
return bcCert;
}
private static X509Certificate2 GenerateX509CertificateWithPrivateKey(
AsymmetricCipherKeyPair keyPair,
X509Certificate bcCert)
{
var privateKeyInfo = PrivateKeyInfoFactory.CreatePrivateKeyInfo(keyPair.Private);
var asn1Seq = (Asn1Sequence)Asn1Object.FromByteArray(
privateKeyInfo.ParsePrivateKey().GetDerEncoded());
var rsaPrivateKeyStruct = RsaPrivateKeyStructure.GetInstance(asn1Seq);
var rsa = DotNetUtilities.ToRSA(rsaPrivateKeyStruct);
var x509Cert = new X509Certificate2(bcCert.GetEncoded());
return x509Cert.CopyWithPrivateKey(rsa);
}

Bouncycastle - Generate PublicKey from PrivateKey

I have a simple task but I don't know how to do it. I want to generate a public key from a private key with Bouncycastle in C#.
using (var reader = new StringReader(privateKey))
{
var pemReader = new PemReader(reader);
// ...
}
I found a lot of examples that used this:
AsymmetricCipherKeyPair keyPair = (AsymmetricCipherKeyPair)pemReader.ReadObject();
var publicKey = keyPair.Public;
But it throws an exception:
Unable to cast object of type 'Org.BouncyCastle.Crypto.Parameters.RsaPrivateCrtKeyParameters' to type 'Org.BouncyCastle.Crypto.AsymmetricCipherKeyPair
What do I need to change to get it working?
Edit:
I used the example from #James K Polk and got it working.
using (var reader = new StringReader(privateKey))
{
var pemReader = new PemReader(reader);
var pemObject = pemReader.ReadObject();
var rsaPrivateCrtKeyParameters = (RsaPrivateCrtKeyParameters)pemObject;
var rsaKeyParameters = new RsaKeyParameters(false, rsaPrivateCrtKeyParameters.Modulus, rsaPrivateCrtKeyParameters.PublicExponent);
}
As stated in the comment by #dlatikay it is not necessarily true that a public key can be derived from a private key, so there is no GetPublic method or property available on an abstract private key. However, most private key classes contain enough information to easily derive the public key. Here is an example for Bouncycastle RSA private keys:
var rand = new SecureRandom();
var keyGenParams = new RsaKeyGenerationParameters( new BigInteger("65537"), rand, 1024, 64 );
var rsaKeyGen = new RsaKeyPairGenerator();
rsaKeyGen.Init(keyGenParams);
var rsaKeyPair = rsaKeyGen.GenerateKeyPair();
var rsaPriv = (RsaPrivateCrtKeyParameters) rsaKeyPair.Private;
// Make a public from the private
var rsaPub = new RsaKeyParameters(false, rsaPriv.Modulus, rsaPriv.PublicExponent);
Also, note that RsaPrivateCrtKeyParameters is a subclass of RsaKeyParameters so, depending on how you use it, it may be possible to use the RsaPrivateCrtKeyParameters instance directly where a public key is expected.

Get public key from private key in Bouncy Castle C#

So I have an encrypted private key PEM. I can read it and get the private key with the following:
AsymmetricKeyParameter key;
using (var sr = new StringReader(pem))
using (var pf = new PassowrdFinder { Password = password })
{
var reader = new PemReader(sr, pf);
key = (AsymmetricKeyParameter)reader.ReadObject();
}
I also need the public key, to create the SPKI later on. I tried
var keyPair = new AsymmetricCipherKeyPair(key, key);
Which fails with System.ArgumentException: Expected a public key Parameter name: publicParameter.
My question is: How to get the public key from a private key?
Thanks to help from James K Polk, here is what I came up with
AsymmetricCipherKeyPair GetKeyPairFromPrivateKey(AsymmetricKeyParameter privateKey)
{
AsymmetricCipherKeyPair keyPair = null;
if (privateKey is RsaPrivateCrtKeyParameters rsa)
{
var pub = new RsaKeyParameters(false, rsa.Modulus, rsa.PublicExponent);
keyPair = new AsymmetricCipherKeyPair(pub, privateKey);
}
else if (privateKey is Ed25519PrivateKeyParameters ed)
{
var pub = ed.GeneratePublicKey();
keyPair = new AsymmetricCipherKeyPair(pub, privateKey);
}
else if (privateKey is ECPrivateKeyParameters ec)
{
var q = ec.Parameters.G.Multiply(ec.D);
var pub = new ECPublicKeyParameters(ec.AlgorithmName, q, ec.PublicKeyParamSet);
keyPair = new AsymmetricCipherKeyPair(pub, ec);
}
if (keyPair == null)
throw new NotSupportedException($"The key type {privateKey.GetType().Name} is not supported.");
return keyPair;
}
It should be very simple:
AsymmetricCipherKeyPair KeyPair = (AsymmetricCipherKeyPair)reader.ReadObject();
Then:
var pubKey = KeyPair.public;
I'm a little clumsy with the Bouncycastle C# library but I think the way to do this is to explicitly make a new key object using the appropriate components of the private key. Example
// Make an rsa keypair for testing
var rand = new SecureRandom();
var keyGenParams = new RsaKeyGenerationParameters(
new BigInteger("65537"), rand, 1024, 64
);
var rsaKeyGen = new RsaKeyPairGenerator();
rsaKeyGen.Init(keyGenParams);
var rsaKeyPair = rsaKeyGen.GenerateKeyPair();
var rsaPriv = (RsaPrivateCrtKeyParameters)rsaKeyPair.Private;
// Make a public from the private
var rsaPub = new RsaKeyParameters(false, rsaPriv.Modulus, rsaPriv.PublicExponent);
// Try it out
var rsaKeyPair2 = new AsymmetricCipherKeyPair(rsaPub, rsaPriv);
The downside of this approach is that it requires a concrete instance of a specific kind of asymmetric key; it does not work with the abstract asymmetric key classes.

Exporting or Saving CX509PrivateKey

Is it possible somehow to save or export CX509PrivateKey. The idea is that I create a CSR sent to CA get a Certificate and then... somehow I have to get the private key but no idea how, unfortunately nothing found on google.
My piece of code:
var objPrivateKey = new CX509PrivateKey();
objPrivateKey.Length = int.Parse(ConfigurationManager.AppSettings["objPrivateKeyLength"]);
objPrivateKey.KeySpec = X509KeySpec.XCN_AT_SIGNATURE;
objPrivateKey.KeyUsage = X509PrivateKeyUsageFlags.XCN_NCRYPT_ALLOW_ALL_USAGES;
objPrivateKey.MachineContext = false;
objPrivateKey.ExportPolicy = X509PrivateKeyExportFlags.XCN_NCRYPT_ALLOW_EXPORT_FLAG;
objPrivateKey.CspInformations = objCSPs;
objPrivateKey.Create();
Actually I have solved this task in this way. (Here is a bit more code, so you can understand what I was trying to do).
Here all the job is done.
/*
using CERTADMINLib;
using CERTCLILib;
using CertSevAPI.Core.Models;
using CertSrvAPI.Core;
using CertSrvAPI.Core.Models;
using Org.BouncyCastle.Asn1;
using Org.BouncyCastle.Asn1.Pkcs;
using Org.BouncyCastle.Asn1.X509;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Generators;
using Org.BouncyCastle.Crypto.Prng;
using Org.BouncyCastle.Pkcs;
using Org.BouncyCastle.Security;
using System;
*/
var caService = new CAService();
// Create a certificate request.
// The private key is here.
var caRequest = caService.CertRequest(subjectDN);
// Submit the certificate request and get the response.
var caResponse = caService.SendCertRequest(caRequest.Request);
// If certificated is not issued return null.
if (!caService.IsIssued(caResponse.Disposition))
{
return null;
}
// Download the P7B file from CA.
var p7b = new WebClient().DownloadData(
_appSettings.CERT_SRV + "/CertSrv/CertNew.p7b?ReqID=" + caResponse.CertRequest.GetRequestId() + "&Enc=bin");
try
{
var certCollection = new X509Certificate2Collection();
// Import the downloaded file.
certCollection.Import(p7b);
// Create a PKCS store.
var pfx = new Pkcs12Store();
// Insert root CA certificate into the PKCS store.
pfx.SetCertificateEntry("rootCert",
new X509CertificateEntry(DotNetUtilities.FromX509Certificate(certCollection[0])));
// Get the second certificate from the downloaded file.
// That one is the generated certificate for our request.
var certificateEntry = new X509CertificateEntry[1];
certificateEntry[0] = new X509CertificateEntry(DotNetUtilities.FromX509Certificate(certCollection[1]));
// Insert our certificate with the private key
// under the same alias so then we know that this private key
// is for our certificate.
pfx.SetKeyEntry("taxkey", new AsymmetricKeyEntry(caRequest.PrivateKey), certificateEntry);
var memoryStream = new MemoryStream();
// Stream PFX store using the desired password
// for our file.
pfx.Save(memoryStream, password.ToCharArray(), new SecureRandom());
var pfxBytes = memoryStream.GetBuffer();
pfxBytes = Pkcs12Utilities.ConvertToDefiniteLength(pfxBytes, password.ToCharArray());
// Here you can save the pfxBytes to a file, if you want.
// Actually I needed it to give as a response in MVC application.
return File(pfxBytes, System.Net.Mime.MediaTypeNames.Application.Octet, "NewCert.pfx");
}
catch (Exception ex)
{
// If there is an error remove private key from
// the memory.
caRequest.PrivateKey = null;
caRequest.Request = null;
ErrorSignal.FromCurrentContext().Raise(ex);
if (showError != null && showError.ToLower() == "true")
{
throw ex;
}
return null;
}
The private key is in the CARequest.
/*
using Org.BouncyCastle.Crypto;
*/
public class CARequestModel
{
public AsymmetricKeyParameter PrivateKey { get; set; }
public string Request { get; set; }
}
The private key is saved in the memory until the moment we need it to save to the PFX file, and it is generated at the moment we create the certificate request. So here is the certificate request generating method.
public CARequestModel CertRequest(string subjectDN)
{
var name = new X509Name(subjectDN);
var rsaKeyPairGenerator = new RsaKeyPairGenerator();
rsaKeyPairGenerator.Init(
new KeyGenerationParameters(new SecureRandom(
new CryptoApiRandomGenerator()), _appSettings.PRIVATE_KEY_LENGHT));
// Generate key pair.
var keyPair = rsaKeyPairGenerator.GenerateKeyPair();
// Get the private key.
var privateKey = keyPair.Private;
// Get the public key.
var publicKey = keyPair.Public;
// Set the key usage scope.
var keyUsage = new KeyUsage(KeyUsage.DigitalSignature);
var extensionsGenerator = new X509ExtensionsGenerator();
extensionsGenerator.AddExtension(X509Extensions.KeyUsage, true, keyUsage);
var attribute = new AttributeX509(
PkcsObjectIdentifiers.Pkcs9AtExtensionRequest, new DerSet(extensionsGenerator.Generate()));
// Create the certificate request
var csr = new Pkcs10CertificationRequest("SHA256withRSA", name, publicKey, new DerSet(attribute), privateKey);
// Get it as DER, because then I have to submit it to the MS CA server.
var csrBytes = csr.GetDerEncoded();
// Return the Request and private key
return
new CARequestModel
{
Request = Convert.ToBase64String(csrBytes),
PrivateKey = privateKey
};
}

How do I use BouncyCastle to generate a Root Certificate and then a site certificate that is signed by that Root certificate?

I'm trying to build a self hosted service that uses WebAPI with SSL and I need to be able to self-generate SSL certificates to use. I want to be able to do this all from C#. I've been playing with BouncyCastle.
I need to generate 2 certificates, a root and a site certificate. Then I need to install them in Windows in their correct places.
I can't figure out how to make my second certificate reference my root ca. Everything I've tried just gets me an untrusted certificate error. Any help would be appreciated.
This is what I do (I'm using DSA, but if you are using RSA, just change the key generation).
public void IssueClientFromCA()
{
// get CA
string caCn = "MyCA CommonName";
Stream caCertFile = File.OpenRead(string.Format(#"{0}\{1}", _certificatesDir, "MyCAFile.pfx"));
char[] caPass = "passwordForThePfx".ToCharArray();
Pkcs12Store store = new Pkcs12StoreBuilder().Build();
store.Load(caCertFile, caPass);
var caCert = store.GetCertificate(caCn).Certificate;
var caPrivKey = store.GetKey(caCn).Key;
var clientCert = CertIssuer.GenerateDsaCertificateAsPkcs12(
"My Client FriendlyName",
"My Client SubjectName",
"GT",
new DateTime(2011,9,19),
new DateTime(2014,9,18),
"PFXPASS",
caCert,
caPrivKey);
var saveAS = string.Format(#"{0}\{1}", _certificatesDir, "clientCertFile.pfx");
File.WriteAllBytes(saveAS, clientCert);
}
public static byte[] GenerateDsaCertificateAsPkcs12(
string friendlyName,
string subjectName,
string country,
DateTime validStartDate,
DateTime validEndDate,
string password,
Org.BouncyCastle.X509.X509Certificate caCert,
AsymmetricKeyParameter caPrivateKey)
{
var keys = GenerateDsaKeys();
#region build certificate
var certGen = new X509V3CertificateGenerator();
// build name attributes
var nameOids = new ArrayList();
nameOids.Add(Org.BouncyCastle.Asn1.X509.X509Name.CN);
nameOids.Add(X509Name.O);
nameOids.Add(X509Name.C);
var nameValues = new ArrayList();
nameValues.Add(friendlyName);
nameValues.Add(subjectName);
nameValues.Add(country);
var subjectDN = new X509Name(nameOids, nameValues);
// certificate fields
certGen.SetSerialNumber(BigInteger.ValueOf(1));
certGen.SetIssuerDN(caCert.SubjectDN);
certGen.SetNotBefore(validStartDate);
certGen.SetNotAfter(validEndDate);
certGen.SetSubjectDN(subjectDN);
certGen.SetPublicKey(keys.Public);
certGen.SetSignatureAlgorithm("SHA1withDSA");
// extended information
certGen.AddExtension(X509Extensions.AuthorityKeyIdentifier, false, new AuthorityKeyIdentifierStructure(caCert.GetPublicKey()));
certGen.AddExtension(X509Extensions.SubjectKeyIdentifier, false, new SubjectKeyIdentifierStructure(keys.Public));
#endregion
// generate x509 certificate
var cert = certGen.Generate(caPrivateKey);
//ert.Verify(caCert.GetPublicKey());
var chain = new Dictionary<string, Org.BouncyCastle.X509.X509Certificate>();
//chain.Add("CertiFirmas CA", caCert);
var caCn = caCert.SubjectDN.GetValues(X509Name.CN)[0].ToString();
chain.Add(caCn, caCert);
// store the file
return GeneratePkcs12(keys, cert, friendlyName, password, chain);
}
private static byte[] GeneratePkcs12(AsymmetricCipherKeyPair keys, Org.BouncyCastle.X509.X509Certificate cert, string friendlyName, string password,
Dictionary<string, Org.BouncyCastle.X509.X509Certificate> chain)
{
var chainCerts = new List<X509CertificateEntry>();
// Create the PKCS12 store
Pkcs12Store store = new Pkcs12StoreBuilder().Build();
// Add a Certificate entry
X509CertificateEntry certEntry = new X509CertificateEntry(cert);
store.SetCertificateEntry(friendlyName, certEntry); // use DN as the Alias.
//chainCerts.Add(certEntry);
// Add chain entries
var additionalCertsAsBytes = new List<byte[]>();
if (chain != null && chain.Count > 0)
{
foreach (var additionalCert in chain)
{
additionalCertsAsBytes.Add(additionalCert.Value.GetEncoded());
}
}
if (chain != null && chain.Count > 0)
{
var addicionalCertsAsX09Chain = BuildCertificateChainBC(cert.GetEncoded(), additionalCertsAsBytes);
foreach (var addCertAsX09 in addicionalCertsAsX09Chain)
{
chainCerts.Add(new X509CertificateEntry(addCertAsX09));
}
}
// Add a key entry
AsymmetricKeyEntry keyEntry = new AsymmetricKeyEntry(keys.Private);
// no chain
store.SetKeyEntry(friendlyName, keyEntry, new X509CertificateEntry[] { certEntry });
using (var memoryStream = new MemoryStream())
{
store.Save(memoryStream, password.ToCharArray(), new SecureRandom());
return memoryStream.ToArray();
}
}
Some missing methods:
static IEnumerable<Org.BouncyCastle.X509.X509Certificate> BuildCertificateChainBC(byte[] primary, IEnumerable<byte[]> additional)
{
X509CertificateParser parser = new X509CertificateParser();
PkixCertPathBuilder builder = new PkixCertPathBuilder();
// Separate root from itermediate
var intermediateCerts = new List<Org.BouncyCastle.X509.X509Certificate>();
HashSet rootCerts = new HashSet();
foreach (byte[] cert in additional)
{
var x509Cert = parser.ReadCertificate(cert);
// Separate root and subordinate certificates
if (x509Cert.IssuerDN.Equivalent(x509Cert.SubjectDN))
rootCerts.Add(new TrustAnchor(x509Cert, null));
else
intermediateCerts.Add(x509Cert);
}
// Create chain for this certificate
X509CertStoreSelector holder = new X509CertStoreSelector();
holder.Certificate = parser.ReadCertificate(primary);
// WITHOUT THIS LINE BUILDER CANNOT BEGIN BUILDING THE CHAIN
intermediateCerts.Add(holder.Certificate);
PkixBuilderParameters builderParams = new PkixBuilderParameters(rootCerts, holder);
builderParams.IsRevocationEnabled = false;
X509CollectionStoreParameters intermediateStoreParameters =
new X509CollectionStoreParameters(intermediateCerts);
builderParams.AddStore(X509StoreFactory.Create(
"Certificate/Collection", intermediateStoreParameters));
PkixCertPathBuilderResult result = builder.Build(builderParams);
return result.CertPath.Certificates.Cast<Org.BouncyCastle.X509.X509Certificate>();
}
private static AsymmetricCipherKeyPair GenerateDsaKeys()
{
DSACryptoServiceProvider DSA = new DSACryptoServiceProvider();
var dsaParams = DSA.ExportParameters(true);
AsymmetricCipherKeyPair keys = DotNetUtilities.GetDsaKeyPair(dsaParams);
return keys;
}
Also: you have to install you CA certificate into the Trusted CAs store in the client machine, as well as the client certificate (it could be in the Personal or ThirdParty store).

Categories

Resources