I have been looking everywhere (MSDN, Stackoverflow, blogs, etc) to come up with the following pattern:
A client requests a certificate, compatible with the SMIME standard, to a server. (the AD Certificate Services Template portion will be missing)
A server receives the PKCS10 request and modifies it, or uses an API to ensure that it is enrolled using the correct template
The server generates a response and it is sent to the client
The client accepts the response and completes the enrollment.
Note
Even though I'm using .NET on a Windows Client here, I hope to make this process generic enough so that someone with OSX can request a certificate using the built-in keychain or OpenSSL and request a cert, without having to mention the Certificate Template in the EKU.
My goal is to modify the request on the server and send the result back to the client.
This is what I've attempted so far.
class Program
{
private const int CC_DEFAULTCONFIG = 0;
private const int CC_UIPICKCONFIG = 0x1;
private const int CR_IN_BASE64 = 0x1;
private const int CR_IN_FORMATANY = 0;
private const int CR_IN_PKCS10 = 0x100;
private const int CR_DISP_ISSUED = 0x3;
private const int CR_DISP_UNDER_SUBMISSION = 0x5;
private const int CR_OUT_BASE64 = 0x1;
private const int CR_OUT_CHAIN = 0x100;
private static string requestText = "";
static string SMIMEEncrypt = "1.3.6.1.4.1.311.21.8.4946465.16405226.12930948.10533807.2139545.33.10793632.14573168";
static string SMIMESign = "1.3.6.1.4.1.311.21.8.4946465.16405226.12930948.10533807.2139545.33.5005369.11644649";
enum FullResponsePropertyPropID
{
FR_PROP_NONE =0,
FR_PROP_FULLRESPONSE =1,
FR_PROP_STATUSINFOCOUNT=2,
FR_PROP_BODYPARTSTRING=3,
FR_PROP_STATUS=4,
FR_PROP_STATUSSTRING=5,
FR_PROP_OTHERINFOCHOICE=6,
FR_PROP_FAILINFO=7,
FR_PROP_PENDINFOTOKEN=8,
FR_PROP_PENDINFOTIME=9,
FR_PROP_ISSUEDCERTIFICATEHASH=10,
FR_PROP_ISSUEDCERTIFICATE=11,
FR_PROP_ISSUEDCERTIFICATECHAIN=12,
FR_PROP_ISSUEDCERTIFICATECRLCHAIN=13,
FR_PROP_ENCRYPTEDKEYHAS=14,
FR_PROP_FULLRESPONSENOPKCS7=15,
FR_PROP_CAEXCHANGECERTIFICATEHASH=16,
FR_PROP_CAEXCHANGECERTIFICATE=17,
FR_PROP_CAEXCHANGECERTIFICATECHAIN=18,
FR_PROP_CAEXCHANGECERTIFICATECRLCHAIN=19,
FR_PROP_ATTESTATIONCHALLENGE=20,
FR_PROP_ATTESTATIONPROVIDERNAME=21
}
public static void Main()
{
int PROPTYPE_BINARY = 3;
//// Client
//var objEnroll = new CX509Enrollment();
//string RequestStr = objEnroll.CreateRequest(EncodingType.XCN_CRYPT_STRING_BASE64);
////TEST SERVER
//CCertRequest CertRequest = new CCertRequest ();
// int Disposition = CertRequest.Submit(CR_IN_BASE64, RequestStr, string.Empty, @"caName");
string subjectName = "E= makerofthings7@me.com, CN = makerofthings7@me.com, OU = Technology, L= NYC, S= NY, C=US";
string friendlyName = "makerofthings7 - Encrypt...";
string templateName = SMIMEEncrypt;
// --------------
// --------------
// Client
// --------------
// --------------
// 1. Call any initialization method implemented by the IX509Enrollment object.
CX509Enrollment req1 = new CX509Enrollment();
req1.Initialize(X509CertificateEnrollmentContext.ContextUser);
// 2. Call the CreateRequest method.
string req1a = req1.CreateRequest();
// 3. Store the request for a period of time such as days or weeks.
// --------------
// --------------
// Server
// --------------
// --------------
// 4. Call the Initialize method to create a request object when you are ready to enroll.
var reqServer = new CX509CertificateRequestPkcs10();
reqServer.InitializeFromCertificate(
X509CertificateEnrollmentContext.ContextUser,
req1a // Beginning with Windows 7 and Windows Server 2008 R2, you can specify
// a certificate thumb print or serial number rather than an encoded certificate
);
// Add template (Sign or Encrypt or Both)
CX509ExtensionTemplateName tmplateData2 = new CERTENROLLLib.CX509ExtensionTemplateName();
tmplateData2.InitializeEncode(templateName);
var tmplatex5092 = (IX509Extension)tmplateData2;
reqServer.X509Extensions.Add(new CX509Extension(tmplatex5092));
// -----------------------------------------------------------------------------------
// The following properties can be set before calling the Encode method:
// AlternateSignatureAlgorithm
// ClientId
// HashAlgorithm
// ParentWindow
// RenewalCertificate
// Silent
reqServer.Silent = true;
// SuppressDefaults
// ContextMessage
//
// The following properties must be set, if at all, before calling the Encode method:
// CspInformations
// KeyContainerNamePrefix
// SmimeCapabilities
reqServer.SmimeCapabilities = true;
// Subject
CX500DistinguishedName objName2 = new CX500DistinguishedName();
objName2.Encode(subjectName, X500NameFlags.XCN_CERT_NAME_STR_NONE);
reqServer.Subject = objName2;
// -----------------------------------------------------------------------------------
reqServer.Encode();
//reqServer.get_RawDataToBeSigned();
// InitializeFromRequest() does the following:
// Verifies that the request is a PKCS #10, PKCS #7, or CMC request object.
// Retrieves the template, if any, associated with the request.
// Validates the template.
// Sets the request object on the Request property.
// Retrieves the signature count, issuance policies, and application policies from the template.
// Retrieves the renewal certificate if one exists.
CX509Enrollment enroll2 = new CX509Enrollment();
enroll2.InitializeFromRequest(reqServer);
// Enroll()
// - The method may create a key pair if necessary. Depending on how you initialize the enrollment
// object and on what properties you set, there may be no need to create a key pair. For example,
// if you are renewing a certificate by using an existing key, or if the IX509PrivateKey object
// associated with the certificate request represents an existing key, this method does not create a new key pair.
enroll2.Enroll();
// ALTERNATE IMPLEMENTATION
//CERTCLILib.ICertRequest3 serverReq2 = new CCertRequest();
//var retVAl2 = serverReq2.Submit(CR_IN_ENCODEANY, reqr3.Request.get_RawData(), string.Empty, "a.issue01.bitclear.us\\Secure Issuer 01a-001");
//var serverSignedResponse = serverReq2.GetCertificate(CR_OUT_BASE64HEADER);
//var certwChain2 = serverReq2.GetCertificate(CR_OUT_BASE64HEADER | CR_OUT_CHAIN);
// --------------
// --------------
// Client
// --------------
// --------------
// 5.Populate the request object from your stored request.
CCertRequest CertRequest = new CCertRequest();
var disposition3 = CertRequest.RetrievePending(1, ""); // pretend we checked the server for result status, and it was OK
// If Issued...
if (true)
{
// 6. Call the InstallResponse method.
X509CertificateEnrollmentContext context = X509CertificateEnrollmentContext.ContextUser;
CX509Enrollment enroll3 = new CX509Enrollment();
string serverSignedResponse = CertRequest.GetFullResponseProperty(
(int) FullResponsePropertyPropID.FR_PROP_FULLRESPONSE, 0, PROPTYPE_BINARY, CR_OUT_BASE64).ToString();
enroll3.Initialize(context);
// InstallResponse()
// - Retrieves the dummy certificate from the external store.
// - Retrieves the certificate contained in the response and installs it on the computer.
// - Copies properties from the dummy certificate in the external store onto the newly installed certificate in the personal store.
enroll3.InstallResponse(
InstallResponseRestrictionFlags.AllowNone
| InstallResponseRestrictionFlags.AllowUntrustedRoot //Perform the same action as the AllowUntrustedCertificate flag
// but also installs the certificate even if the certificate chain
// cannot be built because the root is not trusted.
, serverSignedResponse, EncodingType.XCN_CRYPT_STRING_BASE64
, string.Empty // If there is a password, clear it from memory when you have finished using it by calling the SecureZeroMemory function
);
// CreatePFX()
// - Opens the certificate store in memory for the default provider.
// - Adds the installed certificate to the store or builds the certificate chain adds a link to it.
// - Exports the certificate and the private key to a PFX message depending on the export options specified.
// - Encodes the exported message by using DER.
string pfx = enroll3.CreatePFX(
"q", // When you have finished using the password, clear it from memory by calling the SecureZeroMemory function.
PFXExportOptions.PFXExportChainNoRoot);
}