Continuing my 2-post series about Certificate Policies certificate extension. In the first part we covered theoretical questions and common design scenarios. In this post I will show how you can add Certificate Policies extension in CA and end-entity certificates.

Certificate Policies extension in CA certificates

When installing Windows CA, either, via Server Manager UI, or PowerShell, there is no way to provide additional information to include in the CA certificate. To include this information, Windows supports a special CA configuration file named “CAPolicy.inf” which must be saved in the %systemroot% directory (usually, it is C:\Windows). CA installation code reads this file in two cases:

  1. During new CA installation;
  2. During existing CA certificate renewal.

CAPolicy.inf syntax is another story, so we will focus on our subject only. Main section in the INF file is [PolicyStatementExtension] and has the following syntax:

[PolicyStatementExtension]
Policies = Policy1, Policy2, ... PolicyN

[Policy1]
OID = 1.3.6.1.4.1.{PENnumber}.1.1
URL = http//www.company.com/rpa

[Policy2]
OID = 1.3.6.1.4.1.{PENnumber}.1.2
URL = https://www.company.com/policies/smartcardpol.aspx

<...>
[PolicyN]
OID = 1.3.6.1.4.1.{PENnumber}.1.3
URL = http://www.company.com/sec/pol/cps.aspx

remember, once policy is explicitly defined at 2nd level, all certificates below shall contain only valid policies. In other words, child CAs may define all or subset of policies defined in the 2nd level CA certificate. Certificates below cannot extend this list.

A single CA certificate may host multiple policies in the certificate policies extension. In the “Policies” key you specify a comma-separated list of policy names. These names are used by CA installation code and are not inserted in the certificate. So you can choose an arbitrary name for each policy. Policy name must not contain spaces or any non-alphanumerical value.

After that, you create INF sections where each section name matches policy identifier specified in the “Policies” key. Each policy section must consist of at least two keys:

  • OID – specifies the policy identifier. Must be specified only once per policy;
  • One or more combination of “URL” and/or “Notice” qualifiers, where:
    • URL key is CPS Pointer, or an url to a resource that contains particular policy description (CPS). You can specify multiple URLs where policy’s CPS is located;
    • Notice key is User Notice and its use is not recommended, so you should avoid it whenever possible.

“URL” qualifier is CPS Pointer, or an url to a resource that contains particular policy description (CPS). You can specify multiple URLs for single policy.

Certificate Policies extension in end-entity certificates

Depending on enrollment practice, there are three main approaches how to include certificate policies in end entity certificates.

Certreq.exe with INF syntax

When using INF-templated certreq.exe to create offline requests, you can use the same syntax as in the CAPolicy.inf file.

PowerShell with CertEnroll syntax

When using PowerShell and CertEnroll COM interfaces to create either, offline or online certificate request, the following code may be used:

# prepare policyID=policyQualifier
# instantiate X509ExtensionCertificatePolicies COM object:
$CertPoliciesExt = New-Object -ComObject X509Enrollment.CX509ExtensionCertificatePolicies
# prepare a policy collection
$Policies = New-Object -ComObject X509Enrollment.CCertificatePolicies
# the code below may be used in a loop when adding multiple policies
# prepare first policy object
$Policy = New-Object -ComObject X509Enrollment.CCertificatePolicy
# create policy identifier
$oid = New-Object -ComObject X509Enrollment.CObjectId
$oid.InitializeFromValue("1.3.6.1.4.1.99999.1.1")
# initialize policy from policy identifier
$Policy.Initialize($oid)
# instantiate policy qualifier
$Qualifier = New-Object -ComObject X509Enrollment.CPolicyQualifier
$Qualifier.InitializeEncode("http//www.company.com/rpa",1)
# and add this qualifier to certificate policy object
$Policy.PolicyQualifiers.Add($Qualifier)
# add policy to a policy collection
$Policies.Add($Policy)
# end of possible loop.
# initialize Certificate Policies extension from a collection of policies
$CertPoliciesExt.InitializeEncode($Policies)
# add certificate policies extension to your request object
$RequestObject.X509Extensions.Add($CertPoliciesExt)

The code looks a bit large for inline scripting, but it should be automated by using reusable functions and loop operators.

Certificate Templates GUI

In Active Directory environments and Enterprise CAs you can configure certificate policies on a certificate template basis. And all certificates issued based on that template will have a certificate policies extension. To configure certificate template to include certificate policies, the following steps should be used:

  1. Log on to computer where ADCS RSAT is installed with Enterprise Admins or delegated permissions;
  2. Open Certificate Templates MMC snap-in (certtmpl.msc);
  3. In the Certificate Templates snap-in, select desired certificate template and select its properties;
  4. In the certificate template editor dialog, switch to Extensions tab and select Issuance Policies extension;
  5. Press Add button to add policies to include;

Add Issuance Policies

At this point you will see certificate policy management dialog, where you can select one or more policies from a list. If there is no such policy, you can create a new one:

Create Issuance Policy

never use auto-generated policy identifier in production environments, because they are generated under Microsoft’s arc where you do not have permissions.

Phinal. HTH


Share this article:

Comments:

AndrePKI

So you can create a policy from the Templates console like you show. (Or you could use 'certutil -oid "displayname"  <locale id> 2' (which clears the displayName BTW)).

But can you then actually issue certificates based on that template, when the issuing CA does not have this policy in its CA-certificate - I think not. You will get "invalid policy" errors in the CA's eventlog and the failed requests list. Either you have to renew the CA's certificate to include the new policy or set the flag to ignore invalid policies (the latter I do not recommend).

Vadims Podāns

> But can you then actually issue certificates based on that template, when the issuing CA does not have this policy in its CA-certificate - I think not

correct, you cannot. Once policy identifier disappears from CA certificate it becomes invalid at n + 1 level.

> or set the flag to ignore invalid policies (the latter I do not recommend).

yes, this flag makes very little sense. Although, CA will be able to issue the certificate, but any policy validation by client will fail in any way.

PKIcurious

Hi Vadims

 

Long time fan.   but anyhoo.   I have a two-tier PKI setup in my lab I have a IANA assigned PEN for my business and have it added to my two tier PKI at the issuing CA level via the capolicy.inf as stated.   Everything is working great with it, I can see the statement button light up, it is available to add on all the templates, and certificates are issuing fine and working great.    I noticed that everytime I use certutil -verify <pathtoexportedcertificate.cer>  I receive the message: 

Cannot find object or property. 0x80092004 (-2146885628 CRYPT_E_NOT_FOUND)
------------------------------------
CertUtil: -verify command FAILED: 0x80092004 (-2146885628 CRYPT_E_NOT_FOUND)
CertUtil: Cannot find object or property.

If I strip the issuance policy from the certificate it verifies fine.    

Is this because my root is allowing all issuance policies?   

thanks in advance.  

 

PKIcurious

Update on my above post in case other folks are working through this.  

I am doing a two tier PKI,  the first run with the root allowing all issuance policies, and the issuing CAs with the appropriate OID mapped to issuance policies, I couldn't get certutil -verify to successfully verify either user or computer certs when issued with the issuance policy (on the template).   The next run (rebuild) I included all the OID's that I will be using linked to the different Issuance policies in my domain from the root on down the chain to the different issuing CAs.      Added the Issuance policies to the domain, and issued certs with appropriate issuance policies stamped on cert.   This was successful in verifying the chain.   It seems that with Server 2016 if you are doing a two tier PKI without policy CA's, then it may be wise to determine the different level of assurances and then create the issuance policies ahead of time, and then after adding to domain, certs are validating appropriately.  

Vadims Podāns

> from the root on down the chain to the different issuing CAs

that's wrong. An inability to validate policies has nothing to do with root CA. This indicate that you made mistake somewhere in your design or implementation. The article is still correct for Windows Server 2016.

Rafal

By accident I have changed policy extention name, certutil -oid [number] [policy name]...I can't back to old name, please any advice?

No I see at View Obkect Identifiers:

Policy name        Object Identifier   Policy Type    

"bad name"          "number"            Application

Vadims Podāns

Try to delete it:

certutil -oid <OidDisplayName> delete

 

consult with help: certutil -oid -?

Ashish Sen Jaswal

Hi Vadims,

I have been going through your blog as well as sent you a request to connect via LinkedIn. I am facing issues in creating "New" Application and Issuance Policies and "Add" them to Certificate Templates via PowerShell just like we do via GUI.

I tried the first approach but I do not see the CAPolicy.inf file. Then I tried the second approach as-is and it fails at the last line. Not sure what I am doing wrong here.

Will you be able to help?

Thanks in advance.

Vadims Podāns

> I am facing issues in creating "New" Application and Issuance Policies and "Add" them to Certificate Templates via PowerShell

what kind of issues? Note that PS example in this post is only about to adding Certificate Policies extension to CSR and nothing else.

> I tried the first approach but I do not see the CAPolicy.inf file.

CAPolicy.inf is used only by ADCS Certification Authority installation process. You have to manually create it 

Ashish Sen Jaswal

Like the way we create Application and Issuance Policies as shown in the screenshot above, is there a way to do the same using PowerShell?

Vadims Podāns

With my PSPKI module, you can register custom application/issuance policy OID using Register-ObjectIdentifier command. Though, you have to manually add this policy to certificate template.

Ashish Sen Jaswal

Thank you very much!!

Ashish Sen Jaswal

Hi Vadmins,

I tried to run Register-ObjectIdentifer with one of your examples in the documentation but it is giving me the error below. Anything wrong or any suggestions?

PS C:\Users\LabAdmin> Register-ObjectIdentifier -FriendlyName "Contoso Pharmaceuticals smart card policy" -Value "1.3.6.1.4.1.311.999.2" -OidGroup IssuancePolicy -CPSLocation "http://www.contoso.com/cps/documents/scpolicy.pdf" -UseActiveDirectory

Confirm
Are you sure you want to perform this action?
Performing the operation "Register object identifier with name: 'Contoso Pharmaceuticals smart card policy' and value:
'1.3.6.1.4.1.311.999.2'" on target "DC1".
[Y] Yes  [A] Yes to All  [N] No  [L] No to All  [S] Suspend  [?] Help (default is "Y"): A
Exception calling "Register" with "6" argument(s): "Unknown error (0x80005000)"
At C:\Program Files\WindowsPowerShell\Modules\PSPKI\3.5\Client\Register-ObjectIdentifier.ps1:32 char:3
+         [Security.Cryptography.Oid2]::Register($Value,$FriendlyName,$ ...
+         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [], MethodInvocationException
    + FullyQualifiedErrorId : COMException

Vadims Podāns

It seems, there is a regression bug: https://github.com/PKISolutions/PSPKI/issues/110

the fix will be available next Monday.

Ashish Sen Jaswal

Hi Vadims,

Any updates to the fix above?

Ashish Sen Jaswal

It seems to be working fine now. Thanks!! I will let you know if something does not work.

Vadims Podāns

You are welcome!

Ashish Sen Jaswal

I see Vadims that you have extended the base Microsoft's OID .Net Class. Is it possible to achieve the same using the same base Microsoft's OID .Net Class for both Application and Issuance Policies using PowerShell?

Vadims Podāns

What do you mean? If you have concerns on PSPKI module, I would suggest to post your questions on GitHub: https://github.com/PKISolutions/PSPKI

Ashish Sen Jaswal

No not a concern at all. No problem will ask in the link provided if required.

Ashish Sen Jaswal

Hi Vadims,

Just a quick question. I am running the below script to create a dummy issuance policy. It does create the issuance policy with Name and OID but does not show me the CPS Location. Can you help?

$Server = (Get-ADDomainController -Discover -ForceDiscover -Writable).HostName[0]
    Import-Module ActiveDirectory -Verbose:$false
    $ConfigNC = $((Get-ADRootDSE -Server $Server).configurationNamingContext)
    
    $OID = New-TemplateOID -Server $Server -ConfigNC $ConfigNC
    $TemplateOIDPath = "CN=OID,CN=Public Key Services,CN=Services,$ConfigNC"
    $oa = @{
        'DisplayName' = 'Policy6'
        'flags' = [System.Int32]'2'
        'msPKI-OID-CPS' = 'https://www.company.com/policies/smartcardpol.aspx'
        'msPKI-Cert-Template-OID' = '1.3.6.1.4.1.1.1.6'
    }
    New-ADObject -Path $TemplateOIDPath -OtherAttributes $oa -Name $OID.TemplateName -Type 'msPKI-Enterprise-Oid' -Server $Server

Ashish Sen Jaswal

Anything that you can share that is wrong over here in the above script Vadims?

Kostya

Is it possible (by any method) to insert user notice text into EE certs via ePKI. Does it have to be present in the CA of tier above? 

PKI_Q

Hello,

Is it OK to start with the Intermediate/Issuing CA OID only instead of defining all other OIDs at renewal or installation?

[PolicyStatementExtension]
Policies = Policy1

[Policy1]
OID = 1.3.6.1.4.1.{PENnumber}.1.1
URL = http//www.company.com/rpa

In the future, can we specify other OIDs for certificates (templates)?

 

Ian

Hi Podans;

Sorry for the Necropost - I spent a c***pload of time trying to work out how to put a CPS statement on a CSR created by a third-party company.  I'd never done this, and trying to work with the INF files resulted in failure (they adjust the CSR by the looks - I got errors relating to the absence of the private key - which I wouldn't have in this case).  (Digicert SaaS-based issuer CSR, asking for me to sign it with my On-Premise Standalone Offline Root - which needed to be Windows for reasons that aren't important to this message).

You mentioned on another page that you didn't know what format to put a string into to allow it to be imported with "Certutil -setextension"  On a hunch, I took this code (above) and added two paragraphs to create a hex-byte-string-file that is, lo-and-behold, compatible with "certutil -setextension nn 1.2.3.4.5 y @FILENAME".  I'm posting here because it took me days to find.  I hope you find it useful - perhaps its an extra option to the ones you spell out above!

I've probably hashed it up or reencoded something I didn't need to, but here you are.

# Input/Output Variables
$oidString      = "1.3.6.1.4.1.123456.1.1.1"
$cpsURLString   = "http://blahablahds.co.nz/pki/cps.txt"
$outputFileName = "EncodedCPSExt.txt"

# prepare policyID=policyQualifier
# instantiate X509ExtensionCertificatePolicies COM object:
$CertPoliciesExt = New-Object -ComObject X509Enrollment.CX509ExtensionCertificatePolicies

# prepare a policy collection
$Policies = New-Object -ComObject X509Enrollment.CCertificatePolicies
# the code below may be used in a loop when adding multiple policies

# prepare first policy object
$Policy = New-Object -ComObject X509Enrollment.CCertificatePolicy
# create policy identifier
$oid = New-Object -ComObject X509Enrollment.CObjectId
$oid.InitializeFromValue($oidString)
# initialize policy from policy identifier
$Policy.Initialize($oid)
# instantiate policy qualifier
$Qualifier = New-Object -ComObject X509Enrollment.CPolicyQualifier
$Qualifier.InitializeEncode($cpsURLString,1)
# and add this qualifier to certificate policy object
$Policy.PolicyQualifiers.Add($Qualifier)
# add policy to a policy collection
$Policies.Add($Policy)
# end of possible loop.
# initialize Certificate Policies extension from a collection of policies
$CertPoliciesExt.InitializeEncode($Policies)
# add certificate policies extension to your request object
#$RequestObject.X509Extensions.Add($CertPoliciesExt)

$RawData = [Convert]::FromBase64String($CertPoliciesExt.RawData(1))
$NativeExtension = New-Object Security.Cryptography.X509Certificates.X509Extension "2.5.29.32", $RawData, $false

#Display the request Content
Write-Output "EXTENSION REQUESTED:"
write-Output $NativeExtension.Format(1)
Write-Output "WILL BE OUTPUT TO $($outputFileName)"

# Obtain byte array of the data
$Rawobject = $NativeExtension.RawData
# Convert byte array to hyphen-separated hex string
$hex = [System.BitConverter]::ToString($RawObject) 
# Replace hyphens with space (required for text format)
$hexString = $hex.Replace('-',' ')
# Save the output as a file
$hexstring | Out-File $outputFileName
 


Post your comment:

Please, solve this little equation and enter result below. Captcha