I appreciate any advice I can on this issue it has been driving me crazy and I cannot find any documentaion. I am currently trying to generate a self-signed root certificate in C# using the mono security api that meets certain requirements (RSA 2048, SHA256, Etc). I have been successfully able to generate a certificate that meets all of these requirements but one.
I am trying to add a CertificatePoliciesExtension these extensions are usually formatted as follows in X509 certs
[4]: ObjectId: 2.5.29.32 Criticality=false
CertificatePolicies [
[CertificatePolicyId: [some policy oid]
[PolicyQualifierInfo:
[qualifierID: some qualifier oid
qualifier: some ascii encoded byte stream]]]]
My Code Is Below:
private void GenerateRootCertMono(RSACryptoServiceProvider rsa, byte[] serialNumber, string password)
{
// default values
string subject = "CN= company name";
string issuer = "CN=company name";
DateTime notBefore = DateTime.Now;
DateTime notAfter = new DateTime(643445675990000000); // 12/31/2039 23:59:59Z
X509CertificateBuilder cb = new X509CertificateBuilder(3);
cb.SerialNumber = serialNumber;
cb.IssuerName = issuer;
cb.NotBefore = notBefore;
cb.NotAfter = notAfter;
cb.SubjectName = subject;
cb.SubjectPublicKey = rsa;
// extensions
BasicConstraintsExtension bce = new BasicConstraintsExtension();
bce.CertificateAuthority = true;
cb.Extensions.Add(bce);
KeyUsageExtension kue = new KeyUsageExtension();
kue.KeyUsage = KeyUsages.digitalSignature ;
KeyUsageExtension kue1 = new KeyUsageExtension();
kue1.KeyUsage = KeyUsages.keyCertSign;
//my failed attempt to generate a simple Certificate Policy Extension
**ASN1 a = new ASN1 ();
a.Add(ASN1Convert.FromOid("2.5.29.32"));
a.Add(ASN1Convert.FromOid("some oid"));
a.Add(new ASN1 (System.Text.Encoding.ASCII.GetBytes("some text")));
CertificatePoliciesExtension pce = new CertificatePoliciesExtension(a);
cb.Extensions.Add(pce);
cb.Extensions.Add(kue);
cb.Extensions.Add(kue1);
// signature
cb.Hash = "SHA256";
byte[] rawcert = cb.Sign(rsa);
X509Certificate2 pfx = new X509Certificate2(rawcert);
pfx.PrivateKey = rsa;
this.PFX = pfx.Export(X509ContentType.Pkcs12, password);
return;
}
I know generating these extensions is possible because I have seen them in other certificates. Does anyone have any experience or advice? I am also flexible with the API if someone knows how to generate theses extensions using MSCAPI in C# for example that would also be an acceptable solution. Thank you in advance for any help.
After scouring multiple sources I was able to solve this problem by switching from Mono to Microsoft's certenroll.dll. My code for generating a self signed certificate with Policy Extensions is below. Hope this helps anyone having similar problems.
Code based off of the following reference:
http://technet.microsoft.com/en-us/library/ff182332(v=ws.10).aspx.
public X509Certificate2 CreateSelfSignedCert(string subject,string password,DateTime expDate)
{
// create DN for subject and issuer
var dn = new CX500DistinguishedName();
// dn.Encode("CN=" + subject, X500NameFlags.XCN_CERT_NAME_STR_NONE);
dn.Encode(subject, X500NameFlags.XCN_CERT_NAME_STR_NONE);
// create a new private key for the certificate
CX509PrivateKey privateKey = new CX509PrivateKey();
privateKey.ProviderName = "Microsoft Base Cryptographic Provider v1.0";
privateKey.MachineContext = true;
privateKey.Length = 2048;
privateKey.KeySpec = X509KeySpec.XCN_AT_SIGNATURE; // use is not limited
privateKey.ExportPolicy
= X509PrivateKeyExportFlags.XCN_NCRYPT_ALLOW_PLAINTEXT_EXPORT_FLAG;
privateKey.Create();
// Use Sha256 hashing algorithm
var hashobj = new CObjectId();
hashobj.InitializeFromAlgorithmName(
ObjectIdGroupId.XCN_CRYPT_HASH_ALG_OID_GROUP_ID,
ObjectIdPublicKeyFlags.XCN_CRYPT_OID_INFO_PUBKEY_ANY,
AlgorithmFlags.AlgorithmFlagsNone,
"SHA256");
// Create the self signing request
var cert = new CX509CertificateRequestCertificate();
cert.InitializeFromPrivateKey(
X509CertificateEnrollmentContext.ContextMachine,
privateKey,
string.Empty);
cert.Subject = dn;
cert.Issuer = dn; // the issuer and the subject are the same
cert.NotBefore = DateTime.Now;
cert.NotAfter = expDate;
cert.HashAlgorithm = hashobj;
// extensions
CX509ExtensionKeyUsage ku = new CX509ExtensionKeyUsage();
ku.InitializeEncode(CERTENROLLLib.X509KeyUsageFlags.XCN_CERT_KEY_CERT_SIGN_KEY_USAGE | CERTENROLLLib.X509KeyUsageFlags.XCN_CERT_DIGITAL_SIGNATURE_KEY_USAGE);
ku.Critical = true;
cert.X509Extensions.Add((CX509Extension)ku);
CX509ExtensionBasicConstraints bc = new CX509ExtensionBasicConstraints();
bc.InitializeEncode(true,0);
bc.Critical = false;
cert.X509Extensions.Add((CX509Extension)bc);
// Add the certificate policy.
CObjectId cpOid = new CObjectId();
cpOid.InitializeFromValue("some oid");
CCertificatePolicy cp = new CCertificatePolicy();
CPolicyQualifier Qualifier = new CPolicyQualifier();
Qualifier.InitializeEncode("Policy Notice", PolicyQualifierType.PolicyQualifierTypeUserNotice);
cp.Initialize(cpOid);
cp.PolicyQualifiers.Add(Qualifier);
CCertificatePolicies cps = new CCertificatePolicies();
cps.Add(cp);
CX509ExtensionCertificatePolicies cpExt = new CX509ExtensionCertificatePolicies();
cpExt.InitializeEncode(cps);
cert.X509Extensions.Add((CX509Extension)cpExt);
// Do the final enrollment process
var enroll = new CX509Enrollment();
enroll.InitializeFromRequest(cert); // load the certificate
string csr = enroll.CreateRequest(); // Output the request in base64
// and install it back as the response
enroll.InstallResponse(InstallResponseRestrictionFlags.AllowUntrustedCertificate,
csr, EncodingType.XCN_CRYPT_STRING_BASE64, ""); // no password
// output a base64 encoded PKCS#12 so we can import it back to the .Net security classes
var base64encoded = enroll.CreatePFX( "", PFXExportOptions.PFXExportChainWithRoot);
// instantiate the target class with the PKCS#12 data (and the empty password)
return new System.Security.Cryptography.X509Certificates.X509Certificate2(
System.Convert.FromBase64String(base64encoded), "",
// mark the private key as exportable
System.Security.Cryptography.X509Certificates.X509KeyStorageFlags.Exportable
);
}
Related
I need to generate the RSA certificate(self signed certificate) with help of C# code. I have used the below code to create the certificate.
public bool CreateRSACertificate()
{
RSA rsaKey = RSA.Create();
CertificateRequest certRequest = new CertificateRequest("cn=MyApplication", rsaKey, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
certRequest.CertificateExtensions.Add(new X509BasicConstraintsExtension(true, false, 0, true));
certRequest.CertificateExtensions.Add(new X509SubjectKeyIdentifierExtension(certRequest.PublicKey, false));
X509Certificate2 certificate = certRequest.CreateSelfSigned(DateTimeOffset.Now, DateTimeOffset.Now.AddYears(5));
byte[] certData = certificate.Export(X509ContentType.Pfx, "TestPassword");
X509Certificate2 cert = new X509Certificate2(certData, "TestPassword", X509KeyStorageFlags.Exportable);
File.WriteAllBytes("MyCertificate.pfx", cert.Export(X509ContentType.Pfx, "TestPassword"));
return true;
}
And, after that I try to encrypt the data using the the certificate file which I created using the below code.
public bool EncryptAndDecryptFile()
{
string data = "{data: 'mydate123#gmail.com'}";
X509Certificate2 certificate = new X509Certificate2("MyCertificate.pfx", "TestPassword", X509KeyStorageFlags.Exportable);
if (certificate.HasPrivateKey) {
Console.WriteLine("Private key available "); // It's says like the private key was available
}
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
rsa.FromXmlString(certificate.PublicKey.Key.ToXmlString(false));
byte[] bytes = Encoding.ASCII.GetBytes(data);
var encryptedData = rsa.Encrypt(bytes, false); //It seems the data encrypted. I'm not sure.But, I can able to see some encrypted data.
using (certificate.GetRSAPrivateKey()) {
RSACryptoServiceProvider drsa = new RSACryptoServiceProvider();
drsa.FromXmlString(certificate.PrivateKey.ToXmlString(false));
var decdata = drsa.Decrypt(encryptedData, false); // Here, I see some exception.
someString = Encoding.ASCII.GetString(decdata);
}
Console.WriteLine("someString someString ::: " + someString);
return true;
}
While, running the above code I see the below error.
Unhandled exception. Internal.Cryptography.CryptoThrowHelper+WindowsCryptographicException: Key not valid for use in specified state.
at Internal.NativeCrypto.CapiHelper.ExportKeyBlob(Boolean includePrivateParameters, SafeKeyHandle safeKeyHandle)
at System.Security.Cryptography.RSACryptoServiceProvider.ExportParameters(Boolean includePrivateParameters)
at ConfigUtility.X509Certificate.ReadRSACertificate()
To verify the certificate I have the below command,
certutil -dump MyCertificate.pfx
So, the above utility gave me the below output,
================ Certificate 0 ================
================ Begin Nesting Level 1 ================
Element 0:
Serial Number: 054834637a713ecf
Issuer: CN=MyApplication
NotBefore: 29-05-2020 13:49
NotAfter: 29-05-2025 13:49
Subject: CN=MyApplication
Signature matches Public Key
Root Certificate: Subject matches Issuer
Cert Hash(sha1): 16e83e8a92a38b948adad03a86768e27115851d4
---------------- End Nesting Level 1 ----------------
Provider = Microsoft Software Key Storage Provider
Private key is NOT plain text exportable
Encryption test passed
CertUtil: -dump command completed successfully.
In fact, you get exception in this line:
drsa.FromXmlString(certificate.PrivateKey.ToXmlString(false));
the whole encryption and decryption code pieces are incorrect, you are messing things with old an deprecated RSACryptoServiceProvider and doing unnecessary operations. Here is how the method should look like:
public bool EncryptAndDecryptFile() {
string data = "{data: 'mydate123#gmail.com'}";
X509Certificate2 certificate = new X509Certificate2("MyCertificate.pfx", "TestPassword", X509KeyStorageFlags.Exportable);
if (certificate.HasPrivateKey) {
Console.WriteLine("Private key available "); // It's says like the private key was available
}
Byte[] encryptedData = new Byte[0];
using (RSA pubKey = certificate.GetRSAPublicKey()) {
byte[] bytes = Encoding.ASCII.GetBytes(data);
encryptedData = rsa.Encrypt(bytes, RSAEncryptionPadding.OaepSHA256);
}
// assuming, encryptedData is not null
String someString = String.Empty;
using (RSA prKey = certificate.GetRSAPrivateKey()) {
Byte[] decdata = prKey.Decrypt(encryptedData, RSAEncryptionPadding.OaepSHA256);
someString = Encoding.ASCII.GetString(decdata);
}
return data.Equals(someString);
}
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;
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
};
}
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).
I am using the method below to sign Xml Documents:
public static XmlDocument SignDocument(XmlDocument doc)
{
string signatureCanonicalizationMethod = "http://www.w3.org/2001/10/xml-exc-c14n#";
string signatureMethod = #"http://www.w3.org/2001/04/xmldsig-more#rsa-sha256";
string digestMethod = #"http://www.w3.org/2001/04/xmlenc#sha256";
string signatureReferenceURI = "#_73e63a41-156d-4fda-a26c-8d79dcade713";
CryptoConfig.AddAlgorithm(typeof(RSAPKCS1SHA256SignatureDescription), signatureMethod);
var signingCertificate = GetCertificate();
SignedXml signer = new SignedXml(doc);
signer.SigningKey = signingCertificate.PrivateKey;
signer.KeyInfo = new KeyInfo();
signer.KeyInfo.AddClause(new KeyInfoX509Data(signingCertificate));
signer.SignedInfo.CanonicalizationMethod = signatureCanonicalizationMethod;
signer.SignedInfo.SignatureMethod = signatureMethod;
XmlDsigEnvelopedSignatureTransform envelopeTransform = new XmlDsigEnvelopedSignatureTransform();
XmlDsigExcC14NTransform cn14Transform = new XmlDsigExcC14NTransform();
Reference signatureReference = new Reference();
signatureReference.Uri = signatureReferenceURI;
signatureReference.AddTransform(envelopeTransform);
signatureReference.AddTransform(cn14Transform);
signatureReference.DigestMethod = digestMethod;
signer.AddReference(signatureReference);
signer.ComputeSignature();
XmlElement signatureElement = signer.GetXml();
doc.DocumentElement.AppendChild(signer.GetXml());
return doc;
}
private static X509Certificate2 GetCertificate()
{
X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadOnly);
X509Certificate2 card = null;
foreach (X509Certificate2 cert in store.Certificates)
{
if (!cert.HasPrivateKey) continue;
if (cert.Thumbprint.Equals("a_certain_thumb_print", StringComparison.OrdinalIgnoreCase))
{
card = cert;
break;
}
}
store.Close();
return card;
}
An exception of type System.Security.Cryptography.CryptographicException is thrown when trying to compute the signature with the error message Invalid algorithm specified. Any ideas?
Machine: Windows Server 2008 R2
.Net Framework: 4.0.
IDE: Visual Studio 2010.
Thanks so much for this blog. It actually solved my problem.
By the way, if certificate is loaded from file, it should be exportable:
X509Certificate2 x509Key = new X509Certificate2("xxxxx.pfx", "123", X509KeyStorageFlags.Exportable);
string signatureMethod = #"http://www.w3.org/2001/04/xmldsig-more#rsa-sha256";
This signature method is not implemented in .NET according to https://msdn.microsoft.com/en-us/library/system.security.cryptography.xml.signedinfo.signaturemethod(v=vs.110).aspx
The reply from #minhj mentions about some blog, but the link is not there.
However, adding the class mentioned here and registering it fixed the problem. Seems it should be registered only once per app domain.