Skip to content

Instantly share code, notes, and snippets.

@jborean93
Created March 2, 2021 04:42
Show Gist options
  • Save jborean93/9758823e0546abf561b07d380bc60c53 to your computer and use it in GitHub Desktop.
Save jborean93/9758823e0546abf561b07d380bc60c53 to your computer and use it in GitHub Desktop.
Opens an X509 store for an NT Service account
# Copyright: (c) 2021, Jordan Borean (@jborean93) <jborean93@gmail.com>
# MIT License (see LICENSE or https://opensource.org/licenses/MIT)
Function Get-ServiceCertStore {
<#
.SYNOPSIS
Open an X509 store to a service account.
.DESCRIPTION
Opens an X509 store to the NT SERVICE account specified. The X509 store can be used to then add/remove/enumerate
certificate to and from that store.
.PARAMETER ServiceName
The name of the service to open the store for. This should be the service name and not the display name.
.PARAMETER Name
The store name to open, e.g. 'My', 'Root'. Can be a value of [Security.Cryptography.X509Certificates.StoreName] or
just any string for the store name. If the store does not exist this will create the new store unless OpenFlags
has 'OpenExistingOnly' set.
.PARAMETER OpenFlags
Customise the open behaviour for the store. Can be set to
IncludeArchive: Include archived certificates
MaxAllowed: Opens with the highest access allowed for the current user
OpenExistingOnly: Opens existing store names, will fail if the store does not exist
ReadOnly: Open with read only access.
ReadWrite: Open with read write access.
.EXAMPLE Opens the NTDS\My store
$store = Get-ServiceCertStore NTDS
$store.Certificates
.EXAMPLE Opens the root store with read only access
$store = Get-Service WinRM | Get-ServiceCertStore -Name Root -OpenFlags ReadOnly
#>
[CmdletBinding()]
param (
[Parameter(Mandatory=$true, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)]
[string]
$ServiceName,
[Parameter()]
[string]
$Name = 'My',
[Security.Cryptography.X509Certificates.OpenFlags]
$OpenFlags = [Security.Cryptography.X509Certificates.OpenFlags]::MaxAllowed
)
begin {
$typeParams = @{
TypeDefinition = @'
using Microsoft.Win32.SafeHandles;
using System;
using System.Runtime.InteropServices;
namespace X509
{
public class NativeMethods
{
[DllImport("Crypt32.dll")]
public static extern bool CertCloseStore(
IntPtr hCertStore,
uint dwFlags);
[DllImport("Crypt32.dll", CharSet=CharSet.Unicode, SetLastError=true)]
public static extern SafeX509Store CertOpenStore(
IntPtr lpszStoreProvider,
uint dwEncodingType,
IntPtr hCryptProv,
uint dwFlags,
string pvPara);
}
public class SafeX509Store : SafeHandleZeroOrMinusOneIsInvalid
{
public SafeX509Store() : base(true) { }
protected override bool ReleaseHandle()
{
return NativeMethods.CertCloseStore(handle, 0);
}
}
}
'@
}
Add-Type @typeParams
$provider = [IntPtr]::new(10) # CERT_STORE_PROV_SYSTEM_W
$flags = (0x00050000 -bor 0x00000004) # CERT_SYSTEM_STORE_SERVICES | CERT_STORE_DEFER_CLOSE_UNTIL_LAST_FREE_FLAG
$flagType = [Security.Cryptography.X509Certificates.OpenFlags]
$openMode = [int]$OpenFlags -band 3
$accessFlags = switch ($openMode) {
0 { 0x00008000 } # CERT_STORE_READONLY_FLAG
2 { 0x00001000 } # CERT_STORE_MAXIMUM_ALLOWED_FLAG
default { 0 }
}
$flags = $flags -bor $accessFlags
if ($OpenFlags.HasFlag($flagType::OpenExistingOnly)) {
$flags = $flags -bor 0x00004000 # CERT_STORE_OPEN_EXISTING_FLAG
}
if ($OpenFlags.HasFlag($flagType::IncludeArchived)) {
$flags = $flags -bor 0x00000200 # CERT_STORE_ENUM_ARCHIVED_FLAG
}
}
process {
$handle = [X509.NativeMethods]::CertOpenStore(
$provider,
0, # encoding type is only valid for PKCS7 or filename providers,
[IntPtr]::Zero,
$flags,
"$ServiceName\$Name"
); $err = [Runtime.InteropServices.Marshal]::GetLastWin32Error()
if ($handle.IsInvalid) {
$exp = [ComponentModel.Win32Exception]$err
Write-Error -Message "Failed to open '$ServiceName\$Name': $($exp.Message)" -Exception $exp
return
}
try {
[Security.Cryptography.X509Certificates.X509Store]::new($handle.DangerousGetHandle())
}
finally {
$handle.Dispose()
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment