powershellpkicertificate-authoritycertificate-store

Setting a CA Certificate, with specific Enabled Purposes, using PowerShell


How do I programmatically change the Enabled Purposes of a Certificate Authority, in the relevant Windows Certificate Store, using PowerShell?

This is possible to do in the Certificates MMC snap-in

Is this only possible using P/Invoke with CertSetCertificateContextProperty as per StackOverflow: How to set certificate purposes? {C#}

Ideally, I want to import a custom Trusted Root Certificate Authority and only enable it for the purpose of Client Authentication.


Solution

  • A PowerShell Cmdlet that uses CertSetCertificateContextProperty at it's core. Thank you to Crypt32 and their answer on another post for guidance.

    Example Usage:

    Set-CertificateEku -StoreLocation 'CurrentUser' -StoreName 'Root' -CertificateThumbprint 'ffffffffffffffffffffffffffffffffffffffff' -Oids @("1.3.6.1.5.5.7.3.2") # Client Authentication
    
    Function Set-CertificateEku {
        [CmdletBinding()]
        Param(
            [Parameter(Mandatory)]
            [ValidateSet('CurrentUser', 'LocalMachine')]
            $StoreLocation,
            
            [Parameter(Mandatory)]
            $StoreName,
    
            [Parameter(Mandatory)]
            $CertificateThumbprint,
    
            [Parameter(Mandatory)]
            $Oids
        )
        $StoreLocation = switch($StoreLocation) {
            'CurrentUser' {
                [System.Security.Cryptography.X509Certificates.StoreLocation]::CurrentUser
            }
            'LocalMachine' {
                [System.Security.Cryptography.X509Certificates.StoreLocation]::LocalMachine
            }
        }
        try {
            $CertificateStore = [System.Security.Cryptography.X509Certificates.X509Store]::new($StoreName, $StoreLocation)
            $CertificateStore.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadWrite -bor [System.Security.Cryptography.X509Certificates.OpenFlags]::OpenExistingOnly)
        } catch {
            Write-Error "Could not Open Certificate Store $StoreName in $StoreLocation"
            return $false
        }
        $Certificates = $CertificateStore.Certificates.Find(
            [System.Security.Cryptography.X509Certificates.X509FindType]::FindByThumbprint,
            $CertificateThumbprint,
            $false
        )
        if($Certificates.Count -eq 0) {
            Write-Error "Could not find Certificate $CertificateThumbprint in $StoreName in $StoreLocation"
            return $false
        }
        $Certificate = $Certificates[0]
        
    
        $PKICrypt32 = @"
        [DllImport("Crypt32.dll", SetLastError = true, CharSet = CharSet.Auto)]
        public static extern bool CertSetCertificateContextProperty(
            IntPtr pCertContext,
            uint dwPropId,
            uint dwFlags,
            IntPtr pvData
        );
        [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)]
        public struct CRYPTOAPI_BLOB {
            public uint cbData;
            public IntPtr pbData;
        }
    "@
        Add-Type -MemberDefinition $PKICrypt32 -Namespace 'PKI' -Name 'Crypt32'
    
        $OIDs = [Security.Cryptography.OidCollection]::new()
        foreach($Oid in $Oids) {
            [void]$OIDs.Add([Security.Cryptography.Oid]::new($Oid))
        }
        $EKU = [Security.Cryptography.X509Certificates.X509EnhancedKeyUsageExtension]::new($OIDs, $false)
        $pbData = [Runtime.InteropServices.Marshal]::AllocHGlobal($EKU.RawData.Length)
        [Runtime.InteropServices.Marshal]::Copy($EKU.RawData, 0, $pbData, $EKU.RawData.Length)
    
        $Blob = New-Object PKI.Crypt32+CRYPTOAPI_BLOB -Property @{
            cbData = $EKU.RawData.Length;
            pbData = $pbData;
        }
        $pvData = [Runtime.InteropServices.Marshal]::AllocHGlobal([Runtime.InteropServices.Marshal]::SizeOf([type][PKI.Crypt32+CRYPTOAPI_BLOB]))
        [Runtime.InteropServices.Marshal]::StructureToPtr($Blob, $pvData, $false)
    
        $Result = [PKI.Crypt32]::CertSetCertificateContextProperty($Certificate.Handle, 9, 0, $pvData)
        [Runtime.InteropServices.Marshal]::FreeHGlobal($pvData)
        [Runtime.InteropServices.Marshal]::FreeHGlobal($pbData)
        $CertificateStore.Close()
        return $Result
    }