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.
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:
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:
“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.
Depending on enrollment practice, there are three main approaches how to include certificate policies in end entity certificates.
When using INF-templated certreq.exe to create offline requests, you can use the same syntax as in the CAPolicy.inf file.
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.
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:
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:
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
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).
> 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.
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.
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.
> 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.
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
Try to delete it:
certutil -oid <OidDisplayName> delete
consult with help: certutil -oid -?
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.
> 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
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?
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.
Thank you very much!!
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
It seems, there is a regression bug: https://github.com/PKISolutions/PSPKI/issues/110
the fix will be available next Monday.
Hi Vadims,
Any updates to the fix above?
Yes, PSPKI is released with fix: https://www.powershellgallery.com/packages/PSPKI/3.7
It seems to be working fine now. Thanks!! I will let you know if something does not work.
You are welcome!
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?
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
No not a concern at all. No problem will ask in the link provided if required.
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
Anything that you can share that is wrong over here in the above script Vadims?
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?
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)?
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:
Comments: