How to create a self-sign certificate to sign a MimeKit Message? - c#

How to create a self-signed certificate for development suitable to sign MimeKit Messages?
MimeKit has its own CmsSigner. When i try to load the certificate into MimeKit CmsSigner:
X509Certificate2 cert = new X509Certificate2(#"cert.pfx", "xpto", X509KeyStorageFlags.Exportable);
var signer = new MimeKit.Cryptography.CmsSigner(cert);
it throws:
'The certificate cannot be used for signing.'

The problem is that the default algorithm used by CmsSign has to be the same algorithm used to create the certificate key, in my case, SHA1.
Here how was loaded for an S/MIME certificate:
X509Certificate2 cert = new X509Certificate2(#"ca.p12", "xpto", X509KeyStorageFlags.Exportable);
var signer = new CmsSigner(cert);
signer.DigestAlgorithm = DigestAlgorithm.Sha1;
MultipartSigned.Create( signer, mimeMessage.Body);

var message = new MimeMessage() { ... };
// Load your x509 certificate
x509certificate2 cert = new x509certificate2("d:\\mycer.pfx", "123456789", x509keystorageflags.exportable);
// CmsSigner = CMS = Cryptographic Message Syntax = a standard syntax for storing signed and/or encrypted data
var signer = new cmssigner(cert);
signer.digestalgorithm = digestalgorithm.sha256;
// This will sign the message body using our certificate which includes our organisation name
// Needs this package to run: https://www.nuget.org/packages/System.Data.SQLite/
message.body = multipartsigned.create(signer, message.body);
// Getting the private key from the pfx file
// https://www.asptricks.net/2016/09/how-to-export-private-key-from.html
RSACryptoServiceProvider rsa = (RSACryptoServiceProvider)cert.PrivateKey;
AsymmetricCipherKeyPair keyPair = DotNetUtilities.GetRsaKeyPair(rsa);
var myCAprivateKey = keyPair.Private;
// Now sign the message with the private key only to authenticate DKIM
DkimSigner Signer = new DkimSigner(
myCAprivateKey,
"mydomain.com", // your domain name
"myDKIM") // The dkim selector on your domain's DNS (txt record)
{
HeaderCanonicalizationAlgorithm = DkimCanonicalizationAlgorithm.Relaxed,
BodyCanonicalizationAlgorithm = DkimCanonicalizationAlgorithm.Relaxed,
AgentOrUserIdentifier = "#mydomain.com", // your domain name
QueryMethod = "dns/txt",
SignatureAlgorithm = DkimSignatureAlgorithm.RsaSha1
};
Signer.Sign(message, headers);
// do your sending logic

Related

C# Generate client Certificate from Root Certificate using CertEnroll

I would like to write a function that generates client certificates signed by my root certificate using CertEnroll.dll,
I do have Root PFX file at specific location and by reading that creating CSignerCertificate object.
Here is my code for the same.
I am getting error for below code 'CertEnroll::CSignerCertificate::Initialize: Cannot find object or property. 0x80092004 (-2146885628 CRYPT_E_NOT_FOUND)'
public static X509Certificate2 CertCreateNew(string subjectName)
{
// create DN for subject and issuer
var dn = new CX500DistinguishedName();
dn.Encode("CN=" + subjectName, 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 = false;
privateKey.Length = 2048;
privateKey.KeySpec = X509KeySpec.XCN_AT_SIGNATURE; // use is not limited
privateKey.ExportPolicy = X509PrivateKeyExportFlags.XCN_NCRYPT_ALLOW_PLAINTEXT_EXPORT_FLAG;
privateKey.Create();
var hashobj = new CObjectId();
hashobj.InitializeFromAlgorithmName(ObjectIdGroupId.XCN_CRYPT_HASH_ALG_OID_GROUP_ID,
ObjectIdPublicKeyFlags.XCN_CRYPT_OID_INFO_PUBKEY_ANY,
AlgorithmFlags.AlgorithmFlagsNone, "SHA256");
// add extended key usage if you want - look at MSDN for a list of possible OIDs
var oid = new CObjectId();
oid.InitializeFromValue("1.3.6.1.5.5.7.3.1"); // SSL server
var oidlist = new CObjectIds();
oidlist.Add(oid);
var eku = new CX509ExtensionEnhancedKeyUsage();
eku.InitializeEncode(oidlist);
// Create the self signing request
var cert = new CX509CertificateRequestCertificate();
X509Certificate2 signercertificate = CertOpen("My Personal CA");
X509Certificate2 signer = new X509Certificate2(System.IO.File.ReadAllBytes(#"d:\\PKICertificates\\Root Certificates\\MyPersonalCA.pfx"), "password");
if (signer == null)
{
throw new CryptographicException("Signer not found");
}
ISignerCertificate signerCertificate = new CSignerCertificate();
signerCertificate.Initialize(false, X509PrivateKeyVerify.VerifySilent, EncodingType.XCN_CRYPT_STRING_BASE64, Convert.ToBase64String(signer.RawData));
cert.Issuer.Encode(signer.Subject, X500NameFlags.XCN_CERT_NAME_STR_NONE);
cert.InitializeFromPrivateKey(X509CertificateEnrollmentContext.ContextUser, privateKey, "");
cert.SignerCertificate = (CSignerCertificate)signerCertificate;
cert.Subject = dn;
cert.NotBefore = DateTime.Now;
// this cert expires immediately. Change to whatever makes sense for you
cert.NotAfter = DateTime.Now.AddYears(10);
cert.X509Extensions.Add((CX509Extension)eku); // add the EKU
cert.HashAlgorithm = hashobj; // Specify the hashing algorithm
cert.Encode(); // encode the certificate
// Do the final enrollment process
var enroll = new CX509Enrollment();
enroll.InitializeFromRequest(cert); // load the certificate
enroll.CertificateFriendlyName = subjectName; // Optional: add a friendly name
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("", // no password, this is for internal consumption
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 (this is usually what you want to do)
System.Security.Cryptography.X509Certificates.X509KeyStorageFlags.Exportable
);
}
Does anyone know how I can generate a client CX509CertificateRequest signed by my root?
Any help or advice would be greatly appreciated.
I found my answer by doing trial and error and finally got success. Posting code here may be it will helpful to others who are getting same error.
Note: No need to assign Issuer property it will set automatically, if IssuerCertificate property is assigned before Encode
public static X509Certificate2 CertCreateNew(string subjectName)
{
X509Certificate2 signer = new X509Certificate2(System.IO.File.ReadAllBytes(#"d:\\PKICertificates\\Root Certificates\\MyPersonalCA.pfx"), "password");
// create DN for subject and issuer
var dn = new CX500DistinguishedName();
dn.Encode("CN=" + subjectName, 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 = false;
privateKey.Length = 2048;
privateKey.KeySpec = X509KeySpec.XCN_AT_SIGNATURE; // use is not limited
privateKey.ExportPolicy = X509PrivateKeyExportFlags.XCN_NCRYPT_ALLOW_PLAINTEXT_EXPORT_FLAG;
privateKey.Create();
var hashobj = new CObjectId();
hashobj.InitializeFromAlgorithmName(ObjectIdGroupId.XCN_CRYPT_HASH_ALG_OID_GROUP_ID,
ObjectIdPublicKeyFlags.XCN_CRYPT_OID_INFO_PUBKEY_ANY,
AlgorithmFlags.AlgorithmFlagsNone, "SHA256");
// add extended key usage if you want - look at MSDN for a list of possible OIDs
var oid = new CObjectId();
oid.InitializeFromValue("1.3.6.1.5.5.7.3.2"); // SSL server
var oidlist = new CObjectIds();
oidlist.Add(oid);
var eku = new CX509ExtensionEnhancedKeyUsage();
eku.InitializeEncode(oidlist);
// Create the self signing request
var cert = new CX509CertificateRequestCertificate();
if (signer == null)
throw new CryptographicException("Signer not found");
ISignerCertificate signerCertificate = new CSignerCertificate();
signerCertificate.Initialize(false, X509PrivateKeyVerify.VerifySilent, EncodingType.XCN_CRYPT_STRING_BASE64, Convert.ToBase64String(signer.RawData));
cert.InitializeFromPrivateKey(X509CertificateEnrollmentContext.ContextUser, privateKey, "");
cert.SignerCertificate = (CSignerCertificate)signerCertificate;
cert.Subject = dn;
cert.NotBefore = DateTime.Now.Date;
cert.NotAfter = cert.NotBefore + new TimeSpan(3650, 0, 0, 0);
cert.X509Extensions.Add((CX509Extension)eku); // add the EKU
cert.HashAlgorithm = hashobj; // Specify the hashing algorithm
cert.Encode(); // encode the certificate
// Do the final enrollment process
var enroll = new CX509Enrollment();
enroll.InitializeFromRequest(cert); // load the certificate
enroll.CertificateFriendlyName = subjectName; // Optional: add a friendly name
string csr = enroll.CreateRequest();
enroll.InstallResponse(InstallResponseRestrictionFlags.AllowUntrustedCertificate,
csr, EncodingType.XCN_CRYPT_STRING_BASE64, "");
var base64encoded = enroll.CreatePFX("", // no password, this is for internal consumption
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 (this is usually what you want to do)
System.Security.Cryptography.X509Certificates.X509KeyStorageFlags.Exportable
);
}

Upgrading from makecert.exe to CertEnroll - issues with certificate trust

I have an app that up until now used makecert.exe to generate self certificates. However as makecert does't have the ability to add a SubjectAltName field, I am needing to migrate the code to certenroll.dll
This is the original makecert code:
public static X509Certificate2 MakeCert(string subjectName)
{
X509Certificate2 cert;
string certFile = Path.Combine(Path.GetTempPath(), subjectName + ".cer");
var process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "makecert.exe",
Arguments = " -pe -ss my -n \"CN=" + subjectName + ", O=myCert, OU=Created by me\" -sky exchange -in MyCustomRoot -is my -eku 1.3.6.1.5.5.7.3.1 -cy end -a sha1 -m 132 -b 10/08/2018 " + certFile,
UseShellExecute = false,
RedirectStandardOutput = true,
CreateNoWindow = true
}
};
process.Start();
string str = "";
while (!process.StandardOutput.EndOfStream)
{
var line = process.StandardOutput.ReadLine();
str += line;
//Console.WriteLine(line);
}
process.WaitForExit();
cert = new X509Certificate2(certFile);
// Install Cert
try
{
var store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
store.Open(OpenFlags.ReadWrite);
try
{
var contentType = X509Certificate2.GetCertContentType(certFile);
var pfx = cert.Export(contentType);
cert = new X509Certificate2(pfx, (string)null, X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.MachineKeySet);
store.Add(cert);
}
finally
{
store.Close();
}
}
catch (Exception ex)
{
Console.WriteLine(String.Format("Could not create the certificate from file from {0}", certFile), ex);
}
return cert;
}
And this is the certenroll.dll code:
public static X509Certificate2 CertOpen(string subjectName)
{
try
{
X509Store store = new X509Store("My", StoreLocation.CurrentUser);
store.Open(OpenFlags.ReadOnly);
try
{
var cer = store.Certificates.Find(
X509FindType.FindBySubjectName,
subjectName,
false);
if (cer.Count > 0)
{
return cer[0];
}
else
{
return null;
}
}
finally
{
store.Close();
}
}
catch
{
return null;
}
}
public static X509Certificate2 CertCreateNew(string subjectName)
{
// create DN for subject and issuer
var dn = new CX500DistinguishedName();
dn.Encode("CN=" + subjectName, 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 = false;
privateKey.Length = 2048;
privateKey.KeySpec = X509KeySpec.XCN_AT_SIGNATURE; // use is not limited
privateKey.ExportPolicy = X509PrivateKeyExportFlags.XCN_NCRYPT_ALLOW_PLAINTEXT_EXPORT_FLAG;
privateKey.Create();
var hashobj = new CObjectId();
hashobj.InitializeFromAlgorithmName(ObjectIdGroupId.XCN_CRYPT_HASH_ALG_OID_GROUP_ID,
ObjectIdPublicKeyFlags.XCN_CRYPT_OID_INFO_PUBKEY_ANY,
AlgorithmFlags.AlgorithmFlagsNone, "SHA256");
// add extended key usage if you want - look at MSDN for a list of possible OIDs
var oid = new CObjectId();
oid.InitializeFromValue("1.3.6.1.5.5.7.3.1"); // SSL server
var oidlist = new CObjectIds();
oidlist.Add(oid);
var eku = new CX509ExtensionEnhancedKeyUsage();
eku.InitializeEncode(oidlist);
// Create the self signing request
var cert = new CX509CertificateRequestCertificate();
cert.InitializeFromPrivateKey(X509CertificateEnrollmentContext.ContextUser, privateKey, "");
X509Certificate2 signer = CertOpen("MyCustomRoot");
if (signer == null)
{
throw new CryptographicException("Signer not found");
}
String base64str = Convert.ToBase64String(signer.RawData);
ISignerCertificate signerCertificate = new CSignerCertificate();
signerCertificate.Initialize(false, X509PrivateKeyVerify.VerifySilent, EncodingType.XCN_CRYPT_STRING_BASE64, base64str);
// this line MUST be called AFTER IX509CertificateRequestCertificate.InitializeFromPrivateKey call,
// otherwise you will get OLE_E_BLANK uninitialized object error.
cert.SignerCertificate = (CSignerCertificate)signerCertificate;
cert.Subject = dn;
cert.Issuer.Encode(signer.Subject, X500NameFlags.XCN_CERT_NAME_STR_NONE); ; // the issuer and the subject are the same
cert.NotBefore = DateTime.Now;
// this cert expires immediately. Change to whatever makes sense for you
cert.NotAfter = DateTime.Now.AddYears(10);
cert.X509Extensions.Add((CX509Extension)eku); // add the EKU
cert.HashAlgorithm = hashobj; // Specify the hashing algorithm
cert.Encode(); // encode the certificate
// Do the final enrollment process
var enroll = new CX509Enrollment();
enroll.InitializeFromRequest(cert); // load the certificate
enroll.CertificateFriendlyName = subjectName; // Optional: add a friendly name
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("", // no password, this is for internal consumption
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 (this is usually what you want to do)
System.Security.Cryptography.X509Certificates.X509KeyStorageFlags.Exportable
);
}
Further to the assistance from Crypt32 I am now having issues with the signerCertificate.Initialize line. I can't seem to get it to use my self cert. root certificate. I assume that I'm trying to feed it in the wrong format as I am getting the following error:
The certificate does not have the property that references a private
key. 0x8009200a (CRYPT_E_UNEXPECTED_MSG_TYPE)
You have to specify a signer certificate in SignerCertificate property of IX509CertificateRequestCertificate object (cert variable in your code). Signer certificate must be supplied in a form of ISignerCertificate instance. More information: ISignerCertificate interface
Update 1 (13.12.2019)
sorry to tell, but almost every piece in ISignerCertificate call is incorrect.
If you specify X509PrivateKeyVerify.VerifyNone, then private key existence is not checked. You need to use X509PrivateKeyVerify.VerifySilent flag.
You are using Base64 formatting with PEM header and footer to format certificate as a string. You are using EncodingType.XCN_CRYPT_STRING_BASE64 which expects raw Base64 string without PEM envelope. PEM-formatted certificate uses EncodingType.XCN_CRYPT_STRING_BASE64HEADER encoding type. In your case I would do:
X509Certificate signer = CertOpen("MyCustomRoot");
if (signer == null) {
throw new CryptographicException("Signer not found");
}
String base64str = Convert.ToBase64String(signer.RawData);
signerCertificate.Initialize(false, X509PrivateKeyVerify.VerifySilent, EncodingType.XCN_CRYPT_STRING_BASE64, base64str);
<...>
// put issuer directly from issuer cert:
issuer.Encode(signer.Subject, X500NameFlags.XCN_CERT_NAME_STR_NONE);
<...>
cert.InitializeFromPrivateKey(X509CertificateEnrollmentContext.ContextUser, privateKey, "");
// this line MUST be called AFTER IX509CertificateRequestCertificate.InitializeFromPrivateKey call,
// otherwise you will get OLE_E_BLANK uninitialized object error.
cert.SignerCertificate = signerCertificate;
Also, some minor improvements:
In CertOpen method you do not close the store.
if (cer != null && cer.Count >0) -- IIRC, X509Certificate2Collection.Find never returns null, so just check if returned collection is non-empty.
You are assigning ISignerCertificate object to request before initializing it. See my comments above.
Bear in mind, that SHA512 is not enabled by default in all cryptographic modules. SHA512 is disabled in Windows when you use TLS 1.2
Update 2 (14.12.2019)
I reproed the code with my modifications I provided yesterday, the code works. What CRYPT_E_UNEXPECTED_MSG_TYPE error suggests is that signer certificate doesn't have a private key in certificate store.

Self signed certificate using CERTENROLLLib, certificate has expired status

I am using code found in How to create a self-signed certificate using C#? to generate self signed certificate in C#. Below is the code. Using this I am able to generate and add the certificate to My and Root store, however the certificate status says "The certificate has expired or is not yet valid", how to resolve this?
public X509Certificate2 CreateSelfSignedCertificate(string subjectName)
{
var dn = new CX500DistinguishedName();
dn.Encode("CN=" + subjectName, X500NameFlags.XCN_CERT_NAME_STR_NONE);
// create a new private key for the certificate
CX509PrivateKey privateKey = new CX509PrivateKey();
privateKey.ProviderName = "Microsoft Strong Cryptographic Provider";
privateKey.Length = 1024;
privateKey.KeySpec = X509KeySpec.XCN_AT_KEYEXCHANGE;
privateKey.KeyUsage = X509PrivateKeyUsageFlags.XCN_NCRYPT_ALLOW_DECRYPT_FLAG | X509PrivateKeyUsageFlags.XCN_NCRYPT_ALLOW_KEY_AGREEMENT_FLAG;
privateKey.MachineContext = true;
privateKey.ExportPolicy = X509PrivateKeyExportFlags.XCN_NCRYPT_ALLOW_EXPORT_FLAG;
privateKey.Create();
// Use the stronger SHA512 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, "SHA1");
// add extended key usage if you want - look at MSDN for a list of possible OIDs
var oid = new CObjectId();
oid.InitializeFromValue("1.3.6.1.5.5.7.3.1"); // SSL server
var oidlist = new CObjectIds();
oidlist.Add(oid);
var eku = new CX509ExtensionEnhancedKeyUsage();
eku.InitializeEncode(oidlist);
CObjectId objOID = new CObjectId();
CAlternativeName objAlternativeName1 = new CAlternativeName();
CAlternativeNames objAlternativeNames = new CAlternativeNames();
CX509ExtensionAlternativeNames objExtensionAlternativeNames = new CX509ExtensionAlternativeNames();
string fqdn = GetFQDN();
// Create the Issuer Alternative Name as if it were a Subject Alternative Name
objAlternativeName1.InitializeFromString(AlternativeNameType.XCN_CERT_ALT_NAME_DNS_NAME, fqdn);
objAlternativeNames.Add(objAlternativeName1);
objExtensionAlternativeNames.InitializeEncode(objAlternativeNames);
// Create the self signing request
var cert = new CX509CertificateRequestCertificate();
cert.InitializeFromPrivateKey(X509CertificateEnrollmentContext.ContextMachine, privateKey, "");
cert.Subject = dn;
cert.Issuer = dn; // the issuer and the subject are the same
cert.NotBefore = DateTime.Now;
// this cert expires immediately. Change to whatever makes sense for you
//DateTime endtime = new DateTime(DateTime.Now.Year + 20, DateTime.Now.Month, DateTime.Now.Day);
cert.NotAfter = cert.NotBefore.AddYears(20);
cert.X509Extensions.Add((CX509Extension)objExtensionAlternativeNames);
cert.X509Extensions.Add((CX509Extension)eku); // add the EKU
cert.HashAlgorithm = hashobj; // Specify the hashing algorithm
cert.Encode(); // encode the certificate
// Do the final enrollment process
var enroll = new CX509Enrollment();
enroll.InitializeFromRequest(cert); // load the certificate
enroll.CertificateFriendlyName = subjectName; // Optional: add a friendly name
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("", // no password, this is for internal consumption
PFXExportOptions.PFXExportChainWithRoot);
X509Certificate2 certificate = new X509Certificate2(System.Convert.FromBase64String(base64encoded), "", X509KeyStorageFlags.Exportable);
X509Store store = new X509Store(StoreName.Root, StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadWrite);
store.Add(certificate);
store.Close();
// instantiate the target class with the PKCS#12 data (and the empty password)
return certificate;
}
You must provide UTC date time values:
cert.NotBefore = DateTime.UtcNow;

Enroll an X509Certificate2 without any trace of it being left on the machine

Currently creating Certificate Authorities and Issued Certificates. The Generation of the request, enrollment and validation are all functional, but when I checked my certificate store, I realized, it was placing them in my personal certificate directory. For memory, security and legal reasons, I can't have that.
The certificates are stored in a secure remote database. The certificates may be randomly accessed or generated on a random machine from a collection. If they generate certificates, it will store them on whichever machine created the certificate. Is there a way to generate a certificate enrollment (CX509Enrollment) without any trace of the certificate being left on the machine afterwards?
The portion that controls enrollment is relatively small and straight forward. It can only be ran as an administrator. I assume that's because it's adding certificates to the store.
I'm currently running a separate project file to attempt to debug this issue.
Both my certificates are constructed and kept in memory.
static void Main(string[] args)
{
X509Certificate2 rootCert = CreateSelfSignedCertificate("testRoot");
X509Certificate2 signedChild = CreateSignedCertificate("testyMcTesterson", rootCert);
X509Chain chain = new X509Chain();
chain.ChainPolicy = new X509ChainPolicy()
{
RevocationMode = X509RevocationMode.NoCheck,
VerificationFlags = X509VerificationFlags.AllFlags,
UrlRetrievalTimeout = new TimeSpan(0, 1, 0)
};
chain.ChainPolicy.ExtraStore.Add(rootCert);
bool isValid = chain.Build(signedChild); //Is True :D
}
The certificates end up in my personal certificate store
My enrollment occurs in this method. It takes a fully contructed and encoded certificate request.
public static CX509Enrollment EnrollCertificateRequest(CX509CertificateRequestCertificate certRequest)
{
var enroll = new CX509Enrollment();
enroll.InitializeFromRequest(certRequest);
string csr = enroll.CreateRequest();
enroll.InstallResponse(InstallResponseRestrictionFlags.AllowUntrustedCertificate,
csr, EncodingType.XCN_CRYPT_STRING_BASE64, "");
return enroll;
}
EDIT
I'm currently limited to .NET 4.5.x.
Another problem I'm running into, is that trying to sign a certificate with a root will throw a CRYPT_E_NOT_FOUND exception.
There's probably not a way to do it with CX509Enroll. But you can possibly accomplish your goals with .NET Framework 4.7.2 and the CertificateRequest class.
using (RSA rsa = RSA.Create(2048))
{
CertificateRequest request = new CertificateRequest(
"CN=Your Name Here",
rsa,
HashAlgorithmName.SHA256,
RSASignaturePadding.Pkcs1);
SubjectAlternativeNameBuilder builder = new SubjectAlternativeNameBuilder();
builder.AddDnsName("your.name.here");
builder.AddDnsName("your.other.name.here");
request.CertificateExtensions.Add(builder.Build());
// Any other extensions you're supposed to request, like not being a CA.
request.CertificateExtensions.Add(
new X509BasicConstraintsExtension(false, false, 0, false));
// TLS Server?
request.CertificateExtensions.Add(
new X509EnhancedKeyUsageExtension(
new OidCollection
{
new Oid("1.3.6.1.5.5.7.3.1")
},
false));
byte[] derEncodedRequest = request.CreateSigningRequest();
X509Certificate2 responseWithPrivateKey;
using (X509Certificate2 response = SendRequestToServerAndGetResponse(derEncodedRequest))
{
responseWithPrivateKey = response.CopyWithPrivateKey(rsa);
}
// Use it, save it to a PFX, whatever.
// At this point, nothing has touched the hard drive.
}
I figured it out without using enrollment and sticking to .NET 4.5.x.
I first create a RSACrytpoServiceProvider using CspParameters.
I then construct a private and public key from the RSACryptoServiceProvider. I create a certificate request and initiate from the private key. The certificate request is encoded and converted to raw data, then to bytes. The bytes are then used to create an X509Certificate2.
public static X509Certificate2 GenerateCertificate(string subjectName)
{
var dn = new CX500DistinguishedName();
dn.Encode("CN=" + subjectName, X500NameFlags.XCN_CERT_NAME_STR_COMMA_FLAG);
//Create crytpo provider to generate an assymetric key
int KeyType = (int)X509ProviderType.XCN_PROV_RSA_SCHANNEL;
CspParameters cspParams = new CspParameters(KeyType);
cspParams.Flags = CspProviderFlags.UseMachineKeyStore;
cspParams.KeyContainerName = Guid.NewGuid().ToString();
var rsa = new RSACryptoServiceProvider(2048, cspParams);
var CryptoProvider = rsa.CspKeyContainerInfo.ProviderName;
var keyContainerName = rsa.CspKeyContainerInfo.KeyContainerName;
CX509PrivateKey privateKey = new CX509PrivateKey();
privateKey.MachineContext = true;
privateKey.ProviderName = CryptoProvider;
privateKey.ContainerName = keyContainerName;
privateKey.KeyUsage = X509PrivateKeyUsageFlags.XCN_NCRYPT_ALLOW_ALL_USAGES;
privateKey.Open();
keyContainerName = privateKey.ContainerName;
CX509PublicKey publicKey = privateKey.ExportPublicKey();
var oid = new CObjectId();
oid.InitializeFromValue("1.3.6.1.5.5.7.3.1"); // SSL server
var oidlist = new CObjectIds();
oidlist.Add(oid);
var eku = new CX509ExtensionEnhancedKeyUsage();
eku.InitializeEncode(oidlist);
var hashobj = new CObjectId();
hashobj.InitializeFromAlgorithmName(ObjectIdGroupId.XCN_CRYPT_HASH_ALG_OID_GROUP_ID,
ObjectIdPublicKeyFlags.XCN_CRYPT_OID_INFO_PUBKEY_ANY,
AlgorithmFlags.AlgorithmFlagsNone, "SHA256");
CX509CertificateRequestCertificate certRequest = new CX509CertificateRequestCertificate();
certRequest.InitializeFromPrivateKey(
X509CertificateEnrollmentContext.ContextMachine,
privateKey,
"");
certRequest.Subject = dn;
certRequest.NotBefore = DateTime.Now;
certRequest.NotAfter = DateTime.Now.AddYears(1);
certRequest.HashAlgorithm = hashobj;
certRequest.X509Extensions.Add((CX509Extension)eku);
certRequest.Encode();
return new X509Certificate2(
Convert.FromBase64String(certRequest.RawData), "",
X509KeyStorageFlags.Exportable)
{
PrivateKey = rsa,
FriendlyName = subjectName
};
}
To issue a cert. The same process is followed a CSignerCertificate is initiated and attached. But before that happens, I save the root certificate to the My, Local Machine Certificate Store. I then create a signed certificate using the root that was just added to the store. I then remove the certificate from the store.
Signing a Certificate Request
var dnSigner = new CX500DistinguishedName();
dnSigner.Encode("CN=" + signer.FriendlyName, X500NameFlags.XCN_CERT_NAME_STR_COMMA_FLAG);
string base64Root = Convert.ToBase64String(signer.RawData);
CSignerCertificate certSigner = new CSignerCertificate();
bool useMachineStore = ((ICspAsymmetricAlgorithm)signer.PrivateKey).CspKeyContainerInfo.MachineKeyStore;
certSigner.Initialize(useMachineStore, X509PrivateKeyVerify.VerifyNone, EncodingType.XCN_CRYPT_STRING_BASE64, base64Root);
certRequest.SignerCertificate = certSigner;
certRequest.Issuer = dnSigner;
static void Main(string[] args)
{
X509Certificate2 rootCert = GenerateCertificate("TEST_ROOT");
X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadWrite);
store.Add(rootCert);
X509Certificate2 signedChild = GenerateUserCertificate("Testy McTesterson", rootCert);
store.Remove(rootCert);
store.Close();
}
A few important things to note:
This will only work with certain key usage and key spec flags
An X509Chain will still build and validate but it will recognize that the root is untrusted (Easy to bypass).

C# Non self signed client certs with CERTENROLL.dll are unable to be verified

This is a followup to C# Generate a non self signed client CX509Certificate Request without a CA using the certenroll.dll
I am trying to create a client cert signed by my self signed CA, as in the above question. I can create the desired certs without issue using makecert.exe (they show as trusted). I attempting to do the same in C#. The certs are created and placed in the machine's My store. The self signed CA is also installed in the machine's Trusted store. When I open them, they say:
Windows does not have enough information to verify this certificate.
Help?
Code:
// create DN
var dn = new CX500DistinguishedName();
dn.Encode("CN=Demo Cert", X500NameFlags.XCN_CERT_NAME_STR_NONE);
// create prvate key
var pk = new CX509PrivateKey();
pk.MachineContext = true;
pk.Length = 1024;
pk.KeySpec = X509KeySpec.XCN_AT_KEYEXCHANGE;
pk.ExportPolicy = X509PrivateKeyExportFlags.XCN_NCRYPT_ALLOW_EXPORT_FLAG;
pk.Create();
// use SHA512
var hash = new CObjectId();
hash.InitializeFromAlgorithmName(ObjectIdGroupId.XCN_CRYPT_HASH_ALG_OID_GROUP_ID, ObjectIdPublicKeyFlags.XCN_CRYPT_OID_INFO_PUBKEY_ANY, AlgorithmFlags.AlgorithmFlagsNone, "SHA512");
// EKU
var oid = new CObjectId();
oid.InitializeFromValue("1.3.6.1.5.5.7.3.2");
var oidlist = new CObjectIds();
oidlist.Add(oid);
var eku = new CX509ExtensionEnhancedKeyUsage();
eku.InitializeEncode(oidlist);
// Initialize the signer
var TheCA = GimmeTheCA(); // method returns my self signed CA as X509Certificate2
ISignerCertificate signer = new CSignerCertificate();
signer.Initialize(true, X509PrivateKeyVerify.VerifyNone, EncodingType.XCN_CRYPT_STRING_HEX, TheCA.GetRawCertDataString());
// Root Dn
var Rootdn = new CX500DistinguishedName();
Rootdn.Encode(TheCA.Subject, X500NameFlags.XCN_CERT_NAME_STR_NONE);
// Cert Request
var cert = new CX509CertificateRequestCertificate();
cert.InitializeFromPrivateKey(X509CertificateEnrollmentContext.ContextMachine, pk, "");
cert.Subject = dn;
cert.Issuer = Rootdn;
cert.SignerCertificate = (CSignerCertificate)signer;
cert.NotBefore = DateTime.Now;
cert.NotAfter = new DateTime(2020, 1, 1);
cert.X509Extensions.Add((CX509Extension)eku);
cert.HashAlgorithm = hash;
cert.Encode();
// Enrollment
var enroll = new CX509Enrollment();
enroll.InitializeFromRequest(cert);
enroll.CertificateFriendlyName = "Intel IPT";
string csr = enroll.CreateRequest();
// Install
enroll.InstallResponse(InstallResponseRestrictionFlags.AllowUntrustedRoot, csr, EncodingType.XCN_CRYPT_STRING_BASE64, "");
You may try :
pk.KeySpec = X509KeySpec.XCN_AT_SIGNATURE;
pk.KeyUsage = X509PrivateKeyUsageFlags.XCN_NCRYPT_ALLOW_ALL_USAGES;
Run the application with Elevated Privileges (run it as an Administrator).
Put a Copy of the CA certificate int the "Computer\Personal" ("MY") Store (but keep the original certificate in the "Computer\Trusted Root Cert. Auth." ("Root") Store)

Categories