We currently have a solution to generate CA certificates using Microsoft Active Directory Certificate Services.
We have an application which you can login to and you can send a request for a CA certificate.
Behind the scenes this loads an iframe (pointing to a form on the MS CA certificate service) copies a load of data using jQuery and submits the form.
The form submission creates a CSR using ActiveX and CertEnroll and POSTs it to another ASP page. (The whole MS AD certificate service is ASP pages)
The issue is that this only works in IE (obviously)
My proposal was to move the CSR generation server side so that this wouldn't rely on an IE and could be used from any browser.
I have a solution that works, it sends the data to an api and within the api it generates a CSR and forwards it over to the MS CA cert server and this generates valid certificates.
The problem
The issue is that once I install the certificates, the information which states "You have a private key that corresponds to this certificate" is missing. On the existing system this message is displayed as below:
My assumption is that this is because the KeyProtection has not been set for the Private Key. Unfortunately, the flag that it used in the existing VBScript implementation requires user input: XCN_NCRYPT_UI_PROTECT_KEY_FLAG. Each time a certificate is requested the user is prompted to secure the key
For my .Net Framework implementation I have attempted two different CSR generators, one using Bouncy Castle and one using an interop for the CERTENROLLLib.
The code I'll display below will be the CERTENROLLLib implementation, but if anyone has ideas for either solution I would be happy to hear them
The CERTENROLLLib CSR generation
var values = new Dictionary<string, string>
{
{ "E", emailAddress },
{ "CN", commonName },
{ "OU", organizationalUnit },
{ "L", locality }
};
var privateKey = new CX509PrivateKeyClass
{
Length = 1024,
MachineContext = false,
CspInformations = GetCryptographicProvider(),
// This can't be set as it requires UI feedback
//KeyProtection = X509PrivateKeyProtection.XCN_NCRYPT_UI_PROTECT_KEY_FLAG,
ExportPolicy = X509PrivateKeyExportFlags.XCN_NCRYPT_ALLOW_EXPORT_NONE,
};
// Create the actual key pair
privateKey.Create();
// Initialize the PKCS#10 certificate request object based on the private key.
// Using the context, indicate that this is a user certificate request and don't
// provide a template name
var certificateRequest = new CX509CertificateRequestPkcs10Class();
certificateRequest.InitializeFromPrivateKey(
X509CertificateEnrollmentContext.ContextUser,
privateKey,
string.Empty
);
var extension = new CX509ExtensionsClass
{
GetExtensionKeyUsage(),
GetExtensionEnhancedKeyUsage()
};
certificateRequest.X509Extensions.AddRange(extension);
// Encode the name in using the Distinguished Name object
var valueCollection = values.Select(v => $"{v.Key}={v.Value}").Reverse();
var subject = new CX500DistinguishedNameClass();
subject.Encode(
string.Join(";", valueCollection), X500NameFlags.XCN_CERT_NAME_STR_ENABLE_PUNYCODE_FLAG
);
// Assigning the subject name by using the Distinguished Name object initialized above
certificateRequest.Subject = subject;
// Create enrollment request
var enrollmentRequest = new CX509EnrollmentClass();
enrollmentRequest.InitializeFromRequest(certificateRequest);
var csr = enrollmentRequest.CreateRequest(EncodingType.XCN_CRYPT_STRING_BASE64REQUESTHEADER);
So my question is how can I add key protection to the private key as shown in the screenshot?
As this is server side I can't expect any user feedback, so is there another approach.
Any guidance will be welcome, as is probably painfully clear, I am quite new to CSR generation.
Eventually this will be rewritten and we'll probably be generating our own certificates, but I was simply trying to improve the current situation.
Many thanks