Reputation: 1402
I am trying to use only .Net code to create a certificate request and submit the request to our on premise Active Directory PKI certificate authority, and get a certificate back. I have a solution that has been working for a few years, but it uses CERTCLILib and CERTENROLLLib, and I would like to shed these dependencies and port this code over to .Net 5.
These certificates are then imported onto a Yubikey device. We generate the key pair on the Yubikey and then use the public key with the CSR.
This question here Generate and Sign Certificate Request using pure .net Framework has been very helpful in getting a DER encoded CSR, but I still have a few questions that I haven't been able to figure out.
CertificateRequest
object?RSAParameters
object. How can I get that into an RSA
object to use with the CertificateRequst
constructor?System.Security.Cryptography.X509Certificates
namespace that accomplishes that.Here is my current code that is working that I want to port to .NET 5. Note that DeviceDetails
contains properties about the Yubikey device and the CA and template. This code is part of a larger app that provisions Yubikey devices.
public static class CertificateUtilities
{
//private const int CC_UIPICKCONFIG = 0x1;
private const int CR_IN_BASE64 = 0x1;
private const int CR_IN_FORMATANY = 0;
private const int CR_DISP_ISSUED = 0x3;
private const int CR_DISP_UNDER_SUBMISSION = 0x5;
private const int CR_OUT_BASE64 = 0x1;
public static string GenerateRequest(DeviceDetails deviceDetails)
{
// Create all the objects that will be required
var objPkcs10 = new CX509CertificateRequestPkcs10();
var objDN = new CX500DistinguishedName();
var objObjectIds = new CObjectIds();
var objObjectId = new CObjectId();
var objExtensionKeyUsage = new CX509ExtensionKeyUsage();
var objX509ExtensionEnhancedKeyUsage = new CX509ExtensionEnhancedKeyUsage();
var objPublicKey = new CX509PublicKey();
try
{
var publicKey = Utilities.ExportPublicKeyToPEMFormat(deviceDetails.PublicKey);
publicKey = string.Join("", publicKey.Split(new[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries).Where(s => !s.StartsWith("--")));
objPublicKey.InitializeFromEncodedPublicKeyInfo(publicKey, EncodingType.XCN_CRYPT_STRING_BASE64);
var sha512 = new CObjectId();
sha512.InitializeFromValue("2.16.840.1.101.3.4.2.3");
// Initialize the PKCS#10 certificate request object based on the public key.
objPkcs10.InitializeFromPublicKey(X509CertificateEnrollmentContext.ContextUser, objPublicKey, "");
objPkcs10.HashAlgorithm = sha512;
//Key Usage Extension
objExtensionKeyUsage.InitializeEncode(
X509KeyUsageFlags.XCN_CERT_DIGITAL_SIGNATURE_KEY_USAGE |
X509KeyUsageFlags.XCN_CERT_NON_REPUDIATION_KEY_USAGE |
X509KeyUsageFlags.XCN_CERT_KEY_ENCIPHERMENT_KEY_USAGE |
X509KeyUsageFlags.XCN_CERT_DATA_ENCIPHERMENT_KEY_USAGE
);
objPkcs10.X509Extensions.Add((CX509Extension)objExtensionKeyUsage);
// Enhanced Key Usage Extension
objObjectId.InitializeFromValue("1.3.6.1.5.5.7.3.2"); // OID for Client Authentication usage
objObjectIds.Add(objObjectId);
objX509ExtensionEnhancedKeyUsage.InitializeEncode(objObjectIds);
objPkcs10.X509Extensions.Add((CX509Extension)objX509ExtensionEnhancedKeyUsage);
// Encode the name in using the Distinguished Name object
objDN.Encode(
deviceDetails.CertificateDetails.Subject,
X500NameFlags.XCN_CERT_NAME_STR_NONE
);
// Adding the subject name by using the Distinguished Name object initialized above
objPkcs10.Subject = objDN;
// Adding the Subject Alternate Names
var strRfc822Name = deviceDetails.UserDetails.OUN + "@corp.com";
var strUpn = deviceDetails.UserDetails.OUN + "@corp.com";
var objRfc822Name = new CAlternativeName();
var objUserPrincipalName = new CAlternativeName();
var objAlternativeNames = new CAlternativeNames();
var objExtensionAlternativeNames = new CX509ExtensionAlternativeNames();
// Set Alternative RFC822 Name
objRfc822Name.InitializeFromString(AlternativeNameType.XCN_CERT_ALT_NAME_RFC822_NAME, strRfc822Name);
// Set Alternative UPN
objUserPrincipalName.InitializeFromString(AlternativeNameType.XCN_CERT_ALT_NAME_USER_PRINCIPLE_NAME, strUpn);
// Set Alternative Names
objAlternativeNames.Add(objRfc822Name);
objAlternativeNames.Add(objUserPrincipalName);
objExtensionAlternativeNames.InitializeEncode(objAlternativeNames);
objPkcs10.X509Extensions.Add((CX509Extension)objExtensionAlternativeNames);
// Create a CMC outer request and initialize
var cmcReq = new CX509CertificateRequestCmc();
cmcReq.InitializeFromInnerRequestTemplateName(objPkcs10, deviceDetails.CertificateDetails.TemplateName);
// encode the request
cmcReq.Encode();
var strRequest = cmcReq.RawData[EncodingType.XCN_CRYPT_STRING_BASE64];
return strRequest;
}
catch (Exception ex)
{
throw new Exception(ex.Message);
}
}
// Submit request to CA and get response
public static X509Certificate2 SendRequest(DeviceDetails deviceDetails, string request, out string error)
{
error = "";
// Create all the objects that will be required
//var objCertConfig = new CCertConfig();
var objCertRequest = new CCertRequest();
try
{
// Submit the request
var iDisposition = objCertRequest.Submit(
CR_IN_BASE64 | CR_IN_FORMATANY,
request,
null,
deviceDetails.CertificateDetails.IssuingCa
);
// Check the submission status
if (CR_DISP_ISSUED != iDisposition) // Not enrolled
{
var strDisposition = objCertRequest.GetDispositionMessage();
if (CR_DISP_UNDER_SUBMISSION == iDisposition) // Pending
{
error = "The submission is pending: " + strDisposition;
return null;
}
else // Failed
{
error = $"The submission failed: {strDisposition}. Last status: {objCertRequest.GetLastStatus()}";
return null;
}
}
// Get the certificate
var strCert = objCertRequest.GetCertificate(CR_OUT_BASE64);
var rawCert = Convert.FromBase64String(strCert);
return new X509Certificate2(rawCert);
}
catch (Exception ex)
{
throw new Exception($"Error sending the request. {ex.Message}");
}
}
}
Upvotes: 2
Views: 3197
Reputation: 33108
Multipart questions are hard, since they require multipart answers. Here are the parts I can answer:
How do I specify the CA and the template to use in the CertificateRequest object?
You can't, but that's OK, because you don't in the CertEnroll code, either. The CertificateRequest object is equivalent to your objPkcs10
, the CA and template are for what you do with the CreateSigningRequest
output.
I have a public key that is a RSAParameters object. How can I get that into an RSA object to use with the CertificateRequst constructor?
using (RSA key = RSA.Create())
{
key.ImportParameters(rsaParameters);
...
}
Once I have the DER encoded CSR, how do I submit that to the CA? I can't find any classes or methods in the System.Security.Cryptography.X509Certificates namespace that accomplishes that.
There's nothing directly in the box for this. Based on the CX509CertificateRequest
class name, it seems to be using Certificate Management over CMS (CMC), but then there's still the authentication and request submission parts to solve.
Upvotes: 1