Hello crypto world! One my colleague asked me about how to get certificate purposes property. Here is a little intro.

Certificate purposes are (mainly) limited by Enhanced Key Usages extension:

image

That is true. In certain cases it is reasonable to limit certificate purposes to a subset of purposes that are allowed in EKU extension. For example, in many and many CAs are allowed for any purpose (All Application Policies) and you can limit it's purposes to a limited set:

image

Note: these properties are available only when certificate is installed in the certificate store or PKCS#7/Serialized Store.

Almost all CA certificates in Trusted Root CAs container are limited in purposes by setting appropriate check-boxes in certificate properties. Since this is certificate property, you won't be able to find appropriate field in X509Certificate2 object.

Fortunately there is Windows PowerShell and CryptoAPI which can help here. We will use unmanaged CryptoAPI function called CertGetCertificateContextProperty in conjunction with property ID (PropID): CERT_ENHKEY_USAGE_PROP_ID (36).

As per description:

Returns an array of bytes that contain an ASN.1-encoded CERT_ENHKEY_USAGE structure. This structure contains an array of Enhanced Key Usage object identifiers (OIDs), each of which specifies a valid use of the certificate.

the function returns ASN.1 encoded byte array which contains EKU object identifiers. If the function returns False, then no properties are set (equals to Enable all purposes for this certificate option). If the function returns True and empty ASN.1 sequence, then it equals to Disable all purposes for this certificate option. If ASN.1 sequence is not empty, then certificate purposes are constrained. And here is a PowerShell script example:

function Get-CertificateEKUProperty {
[CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [Security.Cryptography.X509Certificates.X509Certificate2]$Cert
    )
$signature = @"
[DllImport("Crypt32.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern bool CertGetCertificateContextProperty(
    IntPtr pCertContext,
    uint dwPropId,
    Byte[] pvData,
    ref uint pcbData
);
"@
    Add-Type -MemberDefinition $signature -Namespace PKI -Name Crypt32
    # check if X509Certificate2 object is not empty
    if (!$Cert.Equals([IntPtr]::Zero)) {
        $pcbData = 0
        # if the function returns True, then certificate purposes are either disabled or constrained
        # otherwise (False) valid purposes are determined by certificate's EKU
        if ([PKI.Crypt32]::CertGetCertificateContextProperty($Cert.Handle,0x9,$null,[ref]$pcbData)) {
            # create a buffer for ASN.1 encoded byte array
            $pvData = New-Object byte[] -ArgumentList $pcbData
            # call the function again to write actual data
            [void][PKI.Crypt32]::CertGetCertificateContextProperty($Cert.Handle,0x9,$pvData,[ref]$pcbData)
            # instantiate AsnEncodedData object with ASN.1 encoded byte array
            $asn = New-Object Security.Cryptography.AsnEncodedData (,$pvData)
            # instaintiate X509EnhancedKeyUsageExtension to retrieve OIDs in a readable form
            $eku = New-Object Security.Cryptography.X509Certificates.X509EnhancedKeyUsageExtension $asn, $false
            # if none OIDs are defined, then all certificate purposes are explicitly disabled in the properties
            if ($eku.EnhancedKeyUsages.Count -eq 0) {
                Write-Warning "All purposes for this certificate are explicitly disabled."
            } else {
                # return constrained OIDs
                $eku.EnhancedKeyUsages
            }
        } else {Write-Warning "Valid purposes are determined by the certificate EKU extension."}
    } else {
        Write-Error -Category InvalidArgument -ErrorId "InvalidArgumentException" `

        -Message "An attempt was made to access an uninitialized object."
    }
}

The function accepts X509Certificate2 objects and returns OidCollection or appropriate warning messages. Here is an example:

PS C:\> Get-CertificateEKUProperty (dir cert:\CurrentUser\Root\02FA*)

Value                                                       FriendlyName
-----                                                       ------------
1.3.6.1.5.5.7.3.1                                           Server Authentication
1.3.6.1.5.5.7.3.2                                           Client Authentication
1.3.6.1.5.5.7.3.4                                           Secure Email
1.3.6.1.5.5.7.3.3                                           Code Signing
1.3.6.1.5.5.7.3.8                                           Time Stamping
1.3.6.1.4.1.311.10.3.4                                      Encrypting File System
1.3.6.1.5.5.7.3.6                                           IP security tunnel termination
1.3.6.1.5.5.7.3.7                                           IP security user


PS C:\> Get-CertificateEKUProperty (dir cert:\CurrentUser\Root\1941A*)
WARNING: Valid purposes are determined by the certificate EKU extension.
PS C:\> Get-CertificateEKUProperty (dir cert:\CurrentUser\My\D48*)
WARNING: All purposes for this certificate are explicitly disabled.
PS C:\>

HTH.


Share this article:

Comments:

Ashley Brener

This article has been most useful. I have ported your code to C# in an MVC website and am attempting to disable all purposes for the certificate . Thawte has upgraded their "thawte Primary Root CA" to 2048bit causeing some web browsers to display SSL certs as unverified. I have the following code for removing all EnhancedKeyUsages if found: [DllImport("Crypt32.dll", SetLastError = true, CharSet = CharSet.Auto)] public static extern bool CertSetEnhancedKeyUsage( IntPtr pCertContext, IntPtr pUsage ); ... void disableAllPurposes(IntPtr certHandle) { IntPtr pUsage = Marshal.AllocHGlobal(0); CertSetEnhancedKeyUsage(certHandle, pUsage); } This seems to work in a console application, albeit purposes are still visible for the thawte Primary Root CA in certmgr. The changes also do not persist in a web application. Given my limited C++ knowledge, I know that there is a problem with the creation of pUsage pointer, can anyone enlighten me here? Thanks in advance.

Vadims Podans

I thnink, you have to pass an empty CTL_USAGE structure insted of blank pointer: http://msdn.microsoft.com/en-us/library/aa381493(v=vs.85).aspx and replace 'IntPtr pUsage' with 'CTL_USAGE pUsage'. Also make sure that you have store context for your certificate. If certificate is retrieved without a store, you will modify only current certificate context and certificate store won't be notified about the change.

Hugh K.

Vadims,  thank you,  this is extremely helpful.  There are many other sites/posts online where authors confuse the certificate's EKU with the EKU configured by the user/store.  

This is the only code sample I could find describing the latter.

David Homer

Hello,

That's awesome!

Do you know where the purposes are stored? Nothing is shown in Process Monitor when the change is made so I assume it's in a secured part of the registry?

When you say "Since this is certificate property" do you actually mean a store property? Changing the purposes certainly doesn't change the information that is stored in the certificate Blob in the registry.

We are trying to document all of these settings for our server documentation tool so this could be a finishing piece, that we can get if we're using PowerShell remoting to collect the data.
https://www.centrel-solutions.com/xiaconfiguration/network-documentation-tool.aspx

 

Thanks,

 

Dave

 

Vadims Podāns

> Do you know where the purposes are stored?

they are stored in a BLOB stored in SystemCertificates registry key along with the certificate. The blob consists of properties and raw certificate.

> When you say "Since this is certificate property" do you actually mean a store property?

yes, it is store-attached property, not a part of raw certificate data.

> Changing the purposes certainly doesn't change the information that is stored in the certificate Blob in the registry.

it does change the blob. You should check it twice, because properties are stored within a blob.

David Homer

Hello!

Yes sorry - you're right the Blob does change! I'm not sure why Process Monitor didn't see it.

Is there a way to read the "Purposes" if you read the Blob from a remote machine registry as just a byte[] array and construct the X509Certificate from the Blob?

 

Thanks,

Dave

Vadims Podāns

> Is there a way to read the "Purposes" if you read the Blob from a remote machine registry as just a byte[] array and construct the X509Certificate from the Blob?

It is simply serialized cert, so you can read it with X509Certificate2 class

David Homer

Yes, that worked using only the binary data. Great, thank you very much :)

david

    [array]$Users=Get-ADUser  -Server $domaine -filter 'name -like "*toto*"' -Properties DistinguishedName, certificates
 foreach ($user in $Users) {
    $i=0

    foreach ($c in $user.Certificates) {
        $i+=1
        $cert=$null
        $cert=[System.Security.Cryptography.X509Certificates.X509Certificate2]$c
        $base=(($c.Subject -split ", ")[0] -replace "CN\=","").split(" ")
        $nomdufichier=$base[1]+"-"+$base[0]+"-"+$base[2]
        $name="{0}_{1}" -f $nomdufichier,$i
        $m=Export-Certificate -Type CERT -FilePath "C:\temp\$name.cer" -Cert $cert
        $p=Import-Certificate -FilePath "C:\temp\$name.cer" -CertStoreLocation 'Cert:\CurrentUser\My'
        [string]$role=($p.Extensions | Where-Object { $_.oid.FriendlyName -match "Utilisation de la clé" }).KeyUsages
        if ($role -ne "KeyEncipherment"){Remove-Item -Path "C:\temp\$name.cer" }
        $echu=$p.NotAfter
        Write-host "C:\temp\$name.cer échoua à la date $echu"
        $p|Remove-Item

       
    }
}


Post your comment:

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