Private/SecretProtection.ps1

$script:WsaDpapiWebhookMarker = 'wsa-dpapi-lm-v1:'
$script:WsaDpapiWebhookContext = 'WindowsSecurityAudit/ScheduledScan/Webhook/v1'

function ConvertFrom-WsaDpapiProtectedText {
    [CmdletBinding()]
    [OutputType([PSCustomObject])]
    param(
        [Parameter(Mandatory = $true)]
        [AllowEmptyString()]
        [string]$ProtectedText
    )

    if (-not $ProtectedText.StartsWith($script:WsaDpapiWebhookMarker, [System.StringComparison]::Ordinal)) {
        throw [System.ArgumentException]::new("Unsupported DPAPI protection marker. Expected '$script:WsaDpapiWebhookMarker'.")
    }

    $encodedCipherText = $ProtectedText.Substring($script:WsaDpapiWebhookMarker.Length)
    if ([string]::IsNullOrWhiteSpace($encodedCipherText)) {
        throw [System.FormatException]::new('The DPAPI ciphertext is empty.')
    }

    try {
        $cipherBytes = [System.Convert]::FromBase64String($encodedCipherText)
    }
    catch {
        throw [System.FormatException]::new('The DPAPI ciphertext is not valid Base64.')
    }

    if ($cipherBytes.Length -eq 0) {
        throw [System.FormatException]::new('The DPAPI ciphertext is empty.')
    }

    return [PSCustomObject]@{
        Marker = $script:WsaDpapiWebhookMarker
        Scope = 'LocalMachine'
        Version = 1
        Context = $script:WsaDpapiWebhookContext
        CipherBytes = $cipherBytes
    }
}

function Get-WsaDpapiProtectedDataType {
    [CmdletBinding()]
    [OutputType([type])]
    param()

    $assemblyName = if ($PSVersionTable.PSEdition -eq 'Desktop') {
        'System.Security'
    }
    else {
        'System.Security.Cryptography.ProtectedData'
    }
    $qualifiedName = "System.Security.Cryptography.ProtectedData, $assemblyName"
    $protectedDataType = [type]::GetType($qualifiedName, $false)

    if ($null -eq $protectedDataType) {
        try {
            $null = [System.Reflection.Assembly]::Load($assemblyName)
        }
        catch {
            throw [System.PlatformNotSupportedException]::new(
                "Windows DPAPI ProtectedData is unavailable because assembly '$assemblyName' could not be loaded.",
                $_.Exception)
        }
        $protectedDataType = [type]::GetType($qualifiedName, $false)
    }

    if ($null -eq $protectedDataType) {
        throw [System.PlatformNotSupportedException]::new(
            "Windows DPAPI ProtectedData is unavailable in assembly '$assemblyName'.")
    }

    return $protectedDataType
}

function Protect-WsaDpapiData {
    [CmdletBinding()]
    [OutputType([string])]
    param(
        [Parameter(Mandatory = $true)]
        [AllowEmptyString()]
        [string]$PlainText
    )

    if ([System.Environment]::OSVersion.Platform -ne [System.PlatformID]::Win32NT) {
        throw [System.PlatformNotSupportedException]::new('Windows DPAPI LocalMachine protection is only available on Windows.')
    }

    $protectedDataType = Get-WsaDpapiProtectedDataType
    $scopeType = $protectedDataType.Assembly.GetType('System.Security.Cryptography.DataProtectionScope', $true)
    $scope = [System.Enum]::Parse($scopeType, 'LocalMachine')
    $plainBytes = $null
    $entropyBytes = $null
    $cipherBytes = $null

    try {
        $entropyBytes = [System.Text.Encoding]::UTF8.GetBytes($script:WsaDpapiWebhookContext)
        $plainBytes = [System.Text.Encoding]::UTF8.GetBytes($PlainText)
        $cipherBytes = $protectedDataType::Protect($plainBytes, $entropyBytes, $scope)
        return $script:WsaDpapiWebhookMarker + [System.Convert]::ToBase64String($cipherBytes)
    }
    finally {
        if ($null -ne $plainBytes) {
            [System.Array]::Clear($plainBytes, 0, $plainBytes.Length)
        }
        if ($null -ne $entropyBytes) {
            [System.Array]::Clear($entropyBytes, 0, $entropyBytes.Length)
        }
        if ($null -ne $cipherBytes) {
            [System.Array]::Clear($cipherBytes, 0, $cipherBytes.Length)
        }
    }
}

function Unprotect-WsaDpapiData {
    [CmdletBinding()]
    [OutputType([string])]
    param(
        [Parameter(Mandatory = $true)]
        [AllowEmptyString()]
        [string]$ProtectedText
    )

    $parsedProtectedText = $null
    $plainBytes = $null
    $entropyBytes = $null
    $cipherBytes = $null

    try {
        $parsedProtectedText = ConvertFrom-WsaDpapiProtectedText -ProtectedText $ProtectedText
        $cipherBytes = $parsedProtectedText.CipherBytes

        if ([System.Environment]::OSVersion.Platform -ne [System.PlatformID]::Win32NT) {
            throw [System.PlatformNotSupportedException]::new('Windows DPAPI LocalMachine unprotection is only available on Windows.')
        }

        $protectedDataType = Get-WsaDpapiProtectedDataType
        $scopeType = $protectedDataType.Assembly.GetType('System.Security.Cryptography.DataProtectionScope', $true)
        $scope = [System.Enum]::Parse($scopeType, 'LocalMachine')
        $entropyBytes = [System.Text.Encoding]::UTF8.GetBytes($script:WsaDpapiWebhookContext)
        $plainBytes = $protectedDataType::Unprotect($cipherBytes, $entropyBytes, $scope)
        return [System.Text.Encoding]::UTF8.GetString($plainBytes)
    }
    finally {
        if ($null -ne $plainBytes) {
            [System.Array]::Clear($plainBytes, 0, $plainBytes.Length)
        }
        if ($null -ne $entropyBytes) {
            [System.Array]::Clear($entropyBytes, 0, $entropyBytes.Length)
        }
        if ($null -ne $cipherBytes) {
            [System.Array]::Clear($cipherBytes, 0, $cipherBytes.Length)
        }
    }
}