Adding certificates extensions via a Certificate Services Policy Module

Hi everyone,

My name is Carlos and my expertise is in Cryptography and Certificates APIs/interfaces.  My blog posts will mostly relate to PKI questions or problems that I see customers encountering and I will talk about how to resolve them.

For my first blog post, I'll write about how to add extensions to a certificate via a policy module and will provide a code example.  This topic is for those already experienced with certificate APIs.  For more information please read the documentation on writing custom modules -
https://msdn.microsoft.com/en-us/library/windows/desktop/aa388216(v=vs.85).aspx

 

What is a Policy Module?

A policy module is a DLL that is loaded by Microsoft Certificate Services to process certificate requests as they come in.  A module has the ability to accept or reject a certificate request as well as modify the certificate attributes/extensions before it's issued.

A policy module is a COM based component that must implement an ICertPolicy and ICertManageModule interface.  The most important being the ICertPolicy interface -https://msdn.microsoft.com/en-us/library/windows/desktop/aa385033(v=vs.85).aspx. The ICertPolicy::VerifyRequest method is where most of the work is done.

How do I get started?

Writing a custom policy module is complex if you want to start from scratch.  Luckily, Microsoft provides Win2K8 and Win2k8R2 policy module samples in the Windows SDK for Windows 7.  You can download and review these samples to get a better understanding about how policy modules work.

How do I add an extension to the incoming certificate request?

As I stated earlier, most of the work is performed in the ICertPolicy::VerifyRequest method.  This method must acquire the ICertServerPolicy interface and call ICertServerPolicy::SetCertificateExtension to add the custom extension that you want the issued certificate to have.  The extensions must be encoded before calling ICertServerPolicy::SetCertificateExtension.

The samples already contain example extension methods that can be called from VerifyRequest.  Here's a list of some of them:
CCertPolicySample::_AddRevocationExtension
CCertPolicySample::_AddAuthorityKeyId
CCertPolicySample::_AddDefaultKeyUsageExtension
CCertPolicySample::_AddEnhancedKeyUsageExtension
CCertPolicySample::_AddDefaultBasicConstraintsExtension

You can add additional extensions recognized by Windows.  You encode them using CryptEncodeObject.  The extensions and constants that can be encoded can be found here -https://msdn.microsoft.com/en-us/library/windows/desktop/aa378145(v=vs.85).aspx. Or you can encode your own custom extension and pass that to SetCertificateExtension.

I recently wrote a method called _AddCertPoliciesExtension that adds a Certificate Policies extension to the certificate.  I've included it below as a code example.  The method itself uses a lot of the helper functions included with the sample.

That's it for now, until my next blog post. 

 

HRESULT CCertPolicySample::_AddCertPoliciesExtension(
        IN ICertServerPolicy *pServer) {
    HRESULT hr;
    CERT_NAME_VALUE NameValue;
    LPSTR strQualifier = "https://www.test.org/PKI/AssuranceLevel";
    BYTE *pbEncodedQualifier = NULL;
    DWORD cbEncodedQualifier = 0;
    CERT_POLICIES_INFO cpi;
    BYTE *pbPolicies = NULL;
    DWORD cbPolicies;
    CERT_POLICY_INFO *pcpi = NULL;
    CERT_POLICY_QUALIFIER_INFO *pcpqi = NULL;   
    VARIANT varExtension;

    ZeroMemory(&cpi, sizeof(cpi));
    VariantInit(&varExtension);

    NameValue.dwValueType = CERT_RDN_IA5_STRING;
    NameValue.Value.pbData = (LPBYTE) strQualifier;
    NameValue.Value.cbData = lstrlenA(strQualifier);

    if (!ceEncodeObject(
            X509_ASN_ENCODING,
            X509_ANY_STRING ,
            &NameValue,
            0,
            FALSE,
            &pbEncodedQualifier,
            &cbEncodedQualifier))
    {
        hr = ceHLastError();
        _JumpError(hr, error, "Policy:ceEncodeObject");
    }

    cpi.cPolicyInfo = 1;
    cpi.rgPolicyInfo = (CERT_POLICY_INFO *) LocalAlloc(
                LMEM_FIXED | LMEM_ZEROINIT,
                cpi.cPolicyInfo * sizeof(cpi.rgPolicyInfo[0]));
    if (NULL == cpi.rgPolicyInfo)
    {
        hr = E_OUTOFMEMORY;
        _JumpError(hr, error, "Policy:LocalAlloc");
    }

    pcpi = cpi.rgPolicyInfo;
    pcpi[0].cPolicyQualifier = 1;
    pcpi[0].pszPolicyIdentifier = "2.17.845.1.11293.1.12.1.1";

    cpi.rgPolicyInfo[0].rgPolicyQualifier = (CERT_POLICY_QUALIFIER_INFO*)  LocalAlloc(
                LMEM_FIXED | LMEM_ZEROINIT,
                cpi.rgPolicyInfo[0].cPolicyQualifier * sizeof(cpi.rgPolicyInfo[0].rgPolicyQualifier[0]));
    if (NULL == cpi.rgPolicyInfo[0].rgPolicyQualifier)
    {
        hr = E_OUTOFMEMORY;
        _JumpError(hr, error, "Policy:LocalAlloc");
    }

    pcpqi = cpi.rgPolicyInfo[0].rgPolicyQualifier;

    pcpqi[0].pszPolicyQualifierId = szOID_PKIX_POLICY_QUALIFIER_CPS;
    pcpqi[0].Qualifier.pbData = pbEncodedQualifier;
    pcpqi[0].Qualifier.cbData = cbEncodedQualifier;

    if (!ceEncodeObject(
            X509_ASN_ENCODING,
            X509_CERT_POLICIES,
            &cpi,
            0,
            FALSE,
            &pbPolicies,
            &cbPolicies))
    {
        hr = ceHLastError();
        _JumpError(hr, error, "Policy:ceEncodeObject");
    }

    varExtension.bstrVal = NULL;

    if (!ceConvertWszToBstr(
            &varExtension.bstrVal,
            (WCHAR const *) pbPolicies,
            cbPolicies))
    {
        hr = E_OUTOFMEMORY;
        _JumpError(hr, error, "Policy:ceConvertWszToBstr");
    }

    varExtension.vt = VT_BSTR;

    hr = polSetCertificateExtension(
                pServer,
                TEXT(szOID_CERT_POLICIES),
                PROPTYPE_BINARY,
                0,
                &varExtension);
    _JumpIfError(hr, error, "Policy:polSetCertificateExtension");

error:
    VariantClear(&varExtension);
    if (NULL != pcpi) LocalFree(pcpi);  
    if (NULL != pcpqi) LocalFree(pcpqi);
    if (NULL != pbPolicies) LocalFree(pbPolicies);
    if (NULL != pbEncodedQualifier) LocalFree(pbEncodedQualifier);

    return(hr);
}