Public/Secrets/New-IDBridgeSecretCertificate.ps1

<#
.SYNOPSIS
Create the self-signed Document Encryption certificate used by the Cms secrets provider.

.DESCRIPTION
Creates the certificate Protect-CmsMessage/Unprotect-CmsMessage need (Document Encryption
EKU 1.3.6.1.4.1.311.80.1, KeyEncipherment + DataEncipherment key usage, RSA 3072,
non-exportable private key) using the in-box PKI module — no external dependency.

By default the certificate goes in the computer store (Cert:\LocalMachine\My), which
requires an elevated session; only SYSTEM and Administrators can decrypt until -GrantRead
adds a private-key read ACL for another principal (e.g. the gMSA that runs scheduled
IDBridge jobs). Use -StoreLocation CurrentUser for a per-user certificate on a dev machine
(no elevation needed).

Put the returned thumbprint in Secrets.Cms.Thumbprint in IDBridgeConfig.psd1. Requires an
initialized session (Initialize-IDBridge) because it logs via Write-Log.

.PARAMETER Subject
Certificate subject. Defaults to 'CN=IDBridge Secrets', which Set-IDBridgeSecret finds
automatically when no thumbprint is configured.

.PARAMETER StoreLocation
'LocalMachine' (default; requires elevation) or 'CurrentUser'.

.PARAMETER ValidityYears
Certificate lifetime in years (default 10). Protect-CmsMessage refuses an expired
certificate; decryption of existing secrets keeps working.

.PARAMETER GrantRead
Account to grant private-key read access, e.g. 'DOMAIN\gMSA-IDBridge$'. Machine store only.

.EXAMPLE
New-IDBridgeSecretCertificate

.EXAMPLE
New-IDBridgeSecretCertificate -GrantRead 'SDOM\gMSA-IDBridge$'

.EXAMPLE
New-IDBridgeSecretCertificate -StoreLocation CurrentUser # dev machine, no elevation

.NOTES
   Created by: Sam Cattanach
   Modified: 2026-07-01
#>

function New-IDBridgeSecretCertificate {
    [CmdletBinding()]
    [OutputType([System.Security.Cryptography.X509Certificates.X509Certificate2])]
    param (
        [Parameter()]
        [string]$Subject = 'CN=IDBridge Secrets',

        [Parameter()]
        [ValidateSet('LocalMachine', 'CurrentUser')]
        [string]$StoreLocation = 'LocalMachine',

        [Parameter()]
        [int]$ValidityYears = 10,

        [Parameter()]
        [string]$GrantRead
    )

    $certParams = @{
        Type              = 'DocumentEncryptionCert'
        Subject           = $Subject
        CertStoreLocation = "Cert:\$StoreLocation\My"
        KeyExportPolicy   = 'NonExportable'
        KeyAlgorithm      = 'RSA'
        KeyLength         = 3072
        KeyUsage          = 'KeyEncipherment', 'DataEncipherment'
        NotAfter          = (Get-Date).AddYears($ValidityYears)
        ErrorAction       = 'Stop'
    }
    try { $cert = New-SelfSignedCertificate @certParams }
    catch { Throw "Error creating the CMS certificate in Cert:\$StoreLocation\My (LocalMachine requires an elevated session): $($_)" }

    Write-Log -Message "Secret: Created CMS certificate '$Subject' in Cert:\$StoreLocation\My (thumbprint $($cert.Thumbprint), expires $($cert.NotAfter.ToString('yyyy-MM-dd')))." -Level Info

    if ($GrantRead) {
        try { Grant-IDBridgeCertificatePrivateKeyAccess -Thumbprint $cert.Thumbprint -Identity $GrantRead }
        catch { Throw "Certificate created (thumbprint $($cert.Thumbprint)) but granting private-key read to '$GrantRead' failed: $($_)" }
    }

    Write-Host "Certificate created. Set Secrets.Cms.Thumbprint = '$($cert.Thumbprint)' in IDBridgeConfig.psd1" -ForegroundColor Green
    return $cert
}