Public/Get-PSBuildCertificate.ps1

function Get-PSBuildCertificate {
    <#
    .SYNOPSIS
        Resolves a code-signing X509Certificate2 from one of several common sources.
    .DESCRIPTION
        Resolves a code-signing certificate suitable for use with Set-AuthenticodeSignature.
        Supports five certificate sources to accommodate local development, CI/CD pipelines,
        and custom signing infrastructure:

          Auto - Checks the CertificateEnvVar environment variable first. If it is
                      populated, uses EnvVar mode; otherwise falls back to Store mode.
                      This is the recommended default for projects that run both locally
                      and in automated pipelines.

          Store - Selects the first valid, unexpired code-signing certificate that has
                      a private key from the Windows certificate store at CertStoreLocation.
                      Suitable for developer workstations where a certificate is installed.

          Thumbprint - Like Store, but matches a specific certificate by its thumbprint.
                      Recommended when multiple code-signing certificates are installed and
                      you need a deterministic selection.

          EnvVar - Decodes a Base64-encoded PFX from an environment variable and
                      optionally decrypts it with a password from a second variable.
                      The most common approach for GitHub Actions, Azure DevOps Pipelines,
                      and GitLab CI where secrets are stored as masked variables.

          PfxFile - Loads a PFX/P12 file from disk with an optional SecureString password.
                      Useful for local scripts, containers, and environments where a
                      certificate file is mounted or distributed via a secrets manager.

        Note: Authenticode signing is a Windows-only capability. This function will fail
        on non-Windows platforms when using Store or Thumbprint sources.
    .PARAMETER CertificateSource
        The source from which to resolve the code-signing certificate.
        Valid values: Auto, Store, Thumbprint, EnvVar, PfxFile. Default: Auto.
    .PARAMETER CertStoreLocation
        Windows certificate store path to search when CertificateSource is Store or Thumbprint.
        Default: Cert:\CurrentUser\My.
    .PARAMETER Thumbprint
        The exact certificate thumbprint to look up. Required when CertificateSource is Thumbprint.
    .PARAMETER CertificateEnvVar
        Name of the environment variable holding the Base64-encoded PFX certificate.
        Used by the EnvVar source and by Auto as the presence-detection key.
        Default: SIGNCERTIFICATE.
    .PARAMETER CertificatePasswordEnvVar
        Name of the environment variable holding the PFX password. Used by EnvVar source.
        Default: CERTIFICATEPASSWORD.
    .PARAMETER PfxFilePath
        File system path to a PFX/P12 certificate file. Required when CertificateSource is PfxFile.
    .PARAMETER PfxFilePassword
        Password for the PFX file as a SecureString. Used by PfxFile source.
    .PARAMETER SkipValidation
        Skip validation checks (private key presence, expiration, Code Signing EKU) for certificates
        loaded from EnvVar or PfxFile sources. Use with caution; invalid certificates will fail during
        actual signing operations with less descriptive errors.
    .OUTPUTS
        System.Security.Cryptography.X509Certificates.X509Certificate2
        Returns the resolved certificate, or $null if none was found (Store/Thumbprint sources).
    .EXAMPLE
        PS> $cert = Get-PSBuildCertificate

        Resolve automatically: use the SIGNCERTIFICATE env var when present, otherwise search
        the current user's certificate store.
    .EXAMPLE
        PS> $cert = Get-PSBuildCertificate -CertificateSource Store

        Explicitly load the first valid code-signing certificate from the current user's store.
    .EXAMPLE
        PS> $cert = Get-PSBuildCertificate -CertificateSource Thumbprint -Thumbprint 'AB12CD34EF56...'

        Load a specific certificate from the certificate store by its thumbprint.
    .EXAMPLE
        PS> $cert = Get-PSBuildCertificate -CertificateSource EnvVar `
                        -CertificateEnvVar 'MY_PFX' -CertificatePasswordEnvVar 'MY_PFX_PASS'

        Decode a PFX certificate stored in a CI/CD secret environment variable.
    .EXAMPLE
        PS> $pass = Read-Host -Prompt 'Certificate password' -AsSecureString
        PS> $cert = Get-PSBuildCertificate -CertificateSource PfxFile -PfxFilePath './codesign.pfx' -PfxFilePassword $pass

        Load a code-signing certificate from a PFX file on disk.
    #>

    [CmdletBinding()]
    [OutputType([System.Security.Cryptography.X509Certificates.X509Certificate2])]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute(
        'PSAvoidUsingPlainTextForPassword',
        'CertificatePasswordEnvVar',
        Justification = 'This is not a password in plain text. It is the name of an environment variable that contains the password, which is a common pattern for CI/CD pipelines and secrets management.'
    )]
    param(
        [ValidateSet('Auto', 'Store', 'Thumbprint', 'EnvVar', 'PfxFile')]
        [string]$CertificateSource = 'Auto',

        [string]$CertStoreLocation = 'Cert:\CurrentUser\My',

        [string]$Thumbprint,

        [string]$CertificateEnvVar = 'SIGNCERTIFICATE',

        [string]$CertificatePasswordEnvVar = 'CERTIFICATEPASSWORD',

        [string]$PfxFilePath,

        [securestring]$PfxFilePassword,

        [switch]$SkipValidation
    )

    # Resolve 'Auto' to the actual source based on environment variable presence
    $resolvedSource = $CertificateSource
    if ($resolvedSource -eq 'Auto') {
        $resolvedSource = if (-not [string]::IsNullOrEmpty([System.Environment]::GetEnvironmentVariable($CertificateEnvVar))) {
            'EnvVar'
        } else {
            'Store'
        }
        Write-Verbose ($LocalizedData.CertificateSourceAutoResolved -f $resolvedSource)
    }

    $cert = $null

    switch ($resolvedSource) {
        'Store' {
            # Throw if running on a non-Windows platform since the certificate store is not supported
            if (-not $IsWindows) {
                throw $LocalizedData.CertificateSourceStoreNotSupported
            }
            $cert = Get-ChildItem -Path $CertStoreLocation -CodeSigningCert |
                Where-Object { $_.HasPrivateKey -and $_.NotAfter -gt (Get-Date) } |
                Select-Object -First 1
            if ($cert) {
                Write-Verbose ($LocalizedData.CertificateResolvedFromStore -f $CertStoreLocation, $cert.Subject)
            }
        }
        'Thumbprint' {
            if ([string]::IsNullOrWhiteSpace($Thumbprint)) {
                throw "CertificateSource 'Thumbprint' requires a non-empty Thumbprint value."
            }

            # Normalize thumbprint input by removing whitespace for robust matching
            $normalizedThumbprint = ($Thumbprint -replace '\s', '')

            $cert = Get-ChildItem -Path $CertStoreLocation -CodeSigningCert |
                Where-Object {
                    ($_.Thumbprint -replace '\s', '') -ieq $normalizedThumbprint -and
                    $_.HasPrivateKey -and
                    $_.NotAfter -gt (Get-Date)
                } |
                Select-Object -First 1
            if ($cert) {
                Write-Verbose ($LocalizedData.CertificateResolvedFromThumbprint -f $Thumbprint, $cert.Subject)
            }
        }
        'EnvVar' {
            $b64Value = [System.Environment]::GetEnvironmentVariable($CertificateEnvVar)
            if ([string]::IsNullOrWhiteSpace($b64Value)) {
                throw "Environment variable '$CertificateEnvVar' is not set or is empty. When using CertificateSource='EnvVar', you must provide a Base64-encoded PFX in this variable."
            }

            try {
                $buffer = [System.Convert]::FromBase64String($b64Value)
            } catch [System.FormatException] {
                throw "Environment variable '$CertificateEnvVar' does not contain a valid Base64-encoded PFX value."
            }
            $password = [System.Environment]::GetEnvironmentVariable($CertificatePasswordEnvVar)
            $cert = [System.Security.Cryptography.X509Certificates.X509Certificate2]::new($buffer, $password)
            Write-Verbose ($LocalizedData.CertificateResolvedFromEnvVar -f $CertificateEnvVar)
        }
        'PfxFile' {
            $cert = [System.Security.Cryptography.X509Certificates.X509Certificate2]::new($PfxFilePath, $PfxFilePassword)
            Write-Verbose ($LocalizedData.CertificateResolvedFromPfxFile -f $PfxFilePath)
        }
    }

    # Validate certificates loaded from EnvVar or PfxFile sources unless -SkipValidation is specified
    if ($cert -and -not $SkipValidation -and ($resolvedSource -eq 'EnvVar' -or $resolvedSource -eq 'PfxFile')) {
        # Check for private key
        if (-not $cert.HasPrivateKey) {
            throw ($LocalizedData.CertificateMissingPrivateKey -f $cert.Subject)
        }

        # Check expiration
        if ($cert.NotAfter -le (Get-Date)) {
            throw ($LocalizedData.CertificateExpired -f $cert.NotAfter, $cert.Subject)
        }

        # Check for Code Signing EKU (1.3.6.1.5.5.7.3.3)
        $codeSigningOid = '1.3.6.1.5.5.7.3.3'
        $hasCodeSigningEku = $cert.EnhancedKeyUsageList | Where-Object { $_.ObjectId -eq $codeSigningOid }
        if (-not $hasCodeSigningEku) {
            throw ($LocalizedData.CertificateMissingCodeSigningEku -f $cert.Subject)
        }

        Write-Verbose "Certificate validation passed: HasPrivateKey=$($cert.HasPrivateKey), NotAfter=$($cert.NotAfter), CodeSigningEKU=Present"
    }

    Write-Verbose ('Certificate resolution complete: ' + ($cert ? $cert.Subject : 'No certificate found'))
    $cert
}