Vjz
Vjz

Reputation: 121

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.

Upvotes: 0

Views: 1230

Answers (2)

schatten
schatten

Reputation: 1

Vjz's answer is almost completely perfect, but he has an error in his/her code that prevents it from running properly.

The line: $OIDs = [Security.Cryptography.OidCollection]::new() is unintentionally clearing out the input parameter (also named OIDs)

Function Set-CertificateEku {
[CmdletBinding()]
Param(
    [Parameter(Mandatory)]
    [ValidateSet('CurrentUser', 'LocalMachine')]
    $StoreLocation,
    
    [Parameter(Mandatory)]
    $StoreName,

    [Parameter(Mandatory)]
    $CertificateThumbprint,

    [Parameter(Mandatory)]
    $OidArray
)
$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()


write-host $CertificateThumbprint



foreach($Oid in $OidArray) {

    [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
}

Set-CertificateEku -StoreLocation 'LocalMachine' -StoreName 'My' -CertificateThumbprint 'ffffffffffffffffff' -OidArray @("1.3.6.1.5.5.7.3.1") # Server Authentication

Upvotes: 0

Vjz
Vjz

Reputation: 121

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
}

Upvotes: 2

Related Questions