Last active
March 19, 2025 17:34
-
-
Save jpawlowski/b5c789980f59206b76a4d0f9809a8755 to your computer and use it in GitHub Desktop.
Revisions
-
jpawlowski revised this gist
Mar 19, 2025 . 1 changed file with 4 additions and 4 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -16,18 +16,18 @@ function Get-HmacSignedHeaders { Always retrieve secrets securely from encrypted sources, ensuring they are stored and used as SecureStrings: 1️⃣ Azure Key Vault (Recommended for cloud/hybrid environments) Connect-AzAccount -Identity $sharedSecret = (Get-AzKeyVaultSecret -VaultName "<YourVaultName>" -Name "HmacSharedSecret").SecretValue 2️⃣ Azure Automation Encrypted Variable (inside Automation Account only) $sharedSecret = (Get-AutomationVariable -Name "SharedSecret") | ConvertTo-SecureString -AsPlainText -Force 3️⃣ Windows Credential Manager (if running on Windows, via SecretManagement module) Import-Module Microsoft.PowerShell.SecretManagement $sharedSecret = (Get-StoredCredential -Target "HmacSharedSecret").Password 4️⃣ Encrypted local file (protected by ACLs; not recommended for cloud, acceptable in controlled environments) $sharedSecret = (Get-Content -Path "C:\Secrets\HmacSecret.txt" -Raw) | ConvertTo-SecureString -AsPlainText -Force ❌ For demonstration purposes only (NEVER hardcode secrets in production): -
jpawlowski revised this gist
Mar 19, 2025 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -27,7 +27,7 @@ function Get-HmacSignedHeaders { Import-Module Microsoft.PowerShell.SecretManagement $sharedSecret = (Get-StoredCredential -Target "HmacSharedSecret").Password 4️⃣ Encrypted local file with ACL (only in controlled environments) $sharedSecret = (Get-Content -Path "C:\Secrets\HmacSecret.txt" -Raw) | ConvertTo-SecureString -AsPlainText -Force ❌ For demonstration purposes only (NEVER hardcode secrets in production): -
jpawlowski revised this gist
Mar 19, 2025 . 2 changed files with 49 additions and 58 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -4,15 +4,49 @@ function Test-HmacAuthorization { Verifies HMAC signature of incoming Azure Automation webhook requests. .DESCRIPTION Validates HMAC signature based on signed request headers (timestamp, nonce, content hash) and a shared secret. Supports both HMACSHA256 and HMACSHA512 algorithms. The shared secret is passed securely as a SecureString, converted as late as possible, and cleared from memory immediately after use. Signature verification includes: - Timestamp freshness validation (anti-replay window configurable via AllowedTimeDriftMinutes) - Host - Body content hash integrity check - Nonce inclusion for replay protection (future extension to store/check nonce is left open) .EXAMPLE # ✅ Example 1: Production (Azure Automation) # Retrieve encrypted variable securely $sharedSecret = (Get-AutomationVariable -Name "SharedSecret") | ConvertTo-SecureString -AsPlainText -Force # Verify signature if (-not (Test-HmacAuthorization -SharedSecret $sharedSecret -WebhookData $WebhookData)) { [void]$sharedSecret.Dispose() throw "Unauthorized: Signature verification failed." } # Dispose secret after use [void]$sharedSecret.Dispose() .EXAMPLE # ✅ Example 2: Local Development / Testing # Use test shared secret (DO NOT use hardcoded secrets in production) $sharedSecret = ConvertTo-SecureString -String "MyTestSecretKey" -AsPlainText -Force # Verify signature if (-not (Test-HmacAuthorization -SharedSecret $sharedSecret -WebhookData $WebhookData)) { Write-Output "❌ Signature invalid." } else { Write-Output "✅ Signature valid." } # Dispose secret [void]$sharedSecret.Dispose() .FUNCTIONALITY Security, HMAC Authentication .NOTES Author: Julian Pawlowski @@ -65,6 +99,12 @@ function Test-HmacAuthorization { } } # Host header check if ([string]::IsNullOrEmpty($headers.'Host')) { Write-Error 'Host header required' return $false } # Timestamp freshness check if ([string]::IsNullOrEmpty($headers.'x-ms-date')) { Write-Error 'x-ms-date header required' @@ -138,53 +178,4 @@ function Test-HmacAuthorization { [Text.Encoding]::UTF8.GetBytes($computedHmac), [Text.Encoding]::UTF8.GetBytes($receivedHmac) ) } This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -10,9 +10,9 @@ function Get-HmacSignedHeaders { This function is intended to be used when calling Azure Automation webhooks or other APIs requiring signed requests for enhanced security. ------------------------------------------ 🔐 Secure Shared Secret Retrieval Options: ------------------------------------------ Always retrieve secrets securely from encrypted sources, ensuring they are stored and used as SecureStrings: -
jpawlowski revised this gist
Mar 19, 2025 . 1 changed file with 0 additions and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,4 +1,3 @@ function Get-HmacSignedHeaders { <# .SYNOPSIS -
jpawlowski revised this gist
Mar 19, 2025 . 1 changed file with 47 additions and 67 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,18 +1,59 @@ function Get-HmacSignedHeaders { function Get-HmacSignedHeaders { <# .SYNOPSIS Generates signed headers for HMAC authentication for Azure Automation webhook requests. .DESCRIPTION Generates signed headers for HMAC authentication based on a shared secret (provided as SecureString), webhook URL, and request body content. Includes timestamp, nonce, and content hash, all covered in the HMAC signature. SecureString is converted as late as possible and securely cleared after use. This function is intended to be used when calling Azure Automation webhooks or other APIs requiring signed requests for enhanced security. ---------------------------- 🔐 Secure Shared Secret Retrieval Options: ---------------------------- Always retrieve secrets securely from encrypted sources, ensuring they are stored and used as SecureStrings: 1️⃣ Azure Key Vault (Recommended for cloud environments) Connect-AzAccount -Identity $sharedSecret = (Get-AzKeyVaultSecret -VaultName "<YourVaultName>" -Name "HmacSharedSecret").SecretValue 2️⃣ Azure Automation Encrypted Variable (Automation Account) $sharedSecret = (Get-AutomationVariable -Name "SharedSecret") | ConvertTo-SecureString -AsPlainText -Force 3️⃣ Windows Credential Manager (with SecretManagement module) Import-Module Microsoft.PowerShell.SecretManagement $sharedSecret = (Get-StoredCredential -Target "HmacSharedSecret").Password 4️⃣ Encrypted local file (only in controlled environments) $sharedSecret = (Get-Content -Path "C:\Secrets\HmacSecret.txt" -Raw) | ConvertTo-SecureString -AsPlainText -Force ❌ For demonstration purposes only (NEVER hardcode secrets in production): $sharedSecret = ConvertTo-SecureString -String "MySuperSecretKey" -AsPlainText -Force .EXAMPLE # Example usage: $sharedSecret = (Get-AutomationVariable -Name "SharedSecret") | ConvertTo-SecureString -AsPlainText -Force $webhookUrl = "https://<your-webhook-url>/webhooks" $body = '{"param1":"value1"}' $headers = Get-HmacSignedHeaders -SharedSecret $sharedSecret ` -WebhookUrl $webhookUrl ` -Body $body [void]$sharedSecret.Dispose() Invoke-RestMethod -Method POST ` -Uri $webhookUrl ` -Headers $headers ` -Body $body ` -ContentType 'application/json; charset=utf-8' .FUNCTIONALITY Security, HMAC Authentication .NOTES Author: Julian Pawlowski @@ -100,64 +141,3 @@ function Get-HmacSignedHeaders { return $headers } -
jpawlowski revised this gist
Mar 18, 2025 . 1 changed file with 2 additions and 2 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -28,8 +28,8 @@ function Test-HmacAuthorization { # The full webhook request data object passed by Azure Automation (includes headers and body) [Parameter(Mandatory)][object]$WebhookData, # Allowed time difference in minutes for timestamp validation (default: 5 minutes) [int]$AllowedTimeDriftMinutes = 5, # Expected request path (used in canonical message construction) [string]$ExpectedPath = '/webhooks' -
jpawlowski revised this gist
Mar 18, 2025 . 2 changed files with 3 additions and 13 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -113,13 +113,11 @@ function Test-HmacAuthorization { $canonicalMessage = "$method`n$path`n" + ($headerValues -join ';') $unsecureSecret = $null try { $hmac = New-Object ("System.Security.Cryptography.$algorithm") # SecureString → plaintext $unsecureSecret = [System.Net.NetworkCredential]::New("", $SharedSecret).Password $hmac.Key = [Text.Encoding]::UTF8.GetBytes($unsecureSecret) $computedHmac = [Convert]::ToBase64String($hmac.ComputeHash([Text.Encoding]::UTF8.GetBytes($canonicalMessage))) @@ -133,9 +131,6 @@ function Test-HmacAuthorization { [Array]::Clear($unsecureSecret.ToCharArray(), 0, $unsecureSecret.Length) $unsecureSecret = $null } } # Final comparison (constant-time) This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -68,13 +68,11 @@ function Get-HmacSignedHeaders { $canonicalMessage = "$method`n$path`n$date;$webHost;$contentHash;$Nonce" $unsecureSecret = $null try { $hmac = New-Object ("System.Security.Cryptography.$Algorithm") # SecureString → plaintext $unsecureSecret = [System.Net.NetworkCredential]::New("", $SharedSecret).Password # Set key and compute signature $hmac.Key = [Text.Encoding]::UTF8.GetBytes($unsecureSecret) @@ -89,9 +87,6 @@ function Get-HmacSignedHeaders { [Array]::Clear($unsecureSecret.ToCharArray(), 0, $unsecureSecret.Length) $unsecureSecret = $null } } # Prepare headers (Host excluded intentionally) -
jpawlowski revised this gist
Mar 18, 2025 . 2 changed files with 55 additions and 21 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -4,9 +4,9 @@ function Test-HmacAuthorization { Verifies HMAC signature of incoming Azure Automation webhook requests. .DESCRIPTION Validates HMAC signature including timestamp, nonce, and body hash. Uses SecureString for shared secret, with proper secure cleanup. Supports SHA256 and SHA512. Nonce uniqueness checking is left open for future enhancement. .FUNCTIONALITY Security, HMAC Authentication @@ -20,10 +20,18 @@ function Test-HmacAuthorization { Created: 2025-03-17 Updated: 2025-03-18 #> param( # The shared secret key used for HMAC calculation (SecureString) [Parameter(Mandatory)][securestring]$SharedSecret, # The full webhook request data object passed by Azure Automation (includes headers and body) [Parameter(Mandatory)][object]$WebhookData, # Allowed time difference in minutes for timestamp validation (default: 3 minutes) [int]$AllowedTimeDriftMinutes = 3, # Expected request path (used in canonical message construction) [string]$ExpectedPath = '/webhooks' ) @@ -57,6 +65,7 @@ function Test-HmacAuthorization { } } # Timestamp freshness check if ([string]::IsNullOrEmpty($headers.'x-ms-date')) { Write-Error 'x-ms-date header required' return $false @@ -76,6 +85,7 @@ function Test-HmacAuthorization { } } # Body hash check if ([string]::IsNullOrEmpty($headers.'x-ms-content-sha256')) { Write-Error "x-ms-content-sha256 header required" return $false @@ -89,6 +99,14 @@ function Test-HmacAuthorization { } } # Nonce check if ([string]::IsNullOrEmpty($headers.'x-ms-nonce')) { Write-Error "x-ms-nonce header required" return $false } # Optional future: Implement nonce storage to prevent re-use attacks # Canonical message construction $method = 'POST' $path = $ExpectedPath $headerValues = foreach ($header in $signedHeaders) { $headers.$header } @@ -99,32 +117,28 @@ function Test-HmacAuthorization { try { $hmac = New-Object ("System.Security.Cryptography.$algorithm") # SecureString → plaintext $unsecurePtr = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($SharedSecret) $unsecureSecret = [Runtime.InteropServices.Marshal]::PtrToStringAuto($unsecurePtr) $hmac.Key = [Text.Encoding]::UTF8.GetBytes($unsecureSecret) $computedHmac = [Convert]::ToBase64String($hmac.ComputeHash([Text.Encoding]::UTF8.GetBytes($canonicalMessage))) # Cleanup HMAC key [Array]::Clear($hmac.Key, 0, $hmac.Key.Length) $hmac = $null } finally { if ($unsecureSecret) { [Array]::Clear($unsecureSecret.ToCharArray(), 0, $unsecureSecret.Length) $unsecureSecret = $null } if ($unsecurePtr -ne [IntPtr]::Zero) { [Runtime.InteropServices.Marshal]::ZeroFreeBSTR($unsecurePtr) } } # Final comparison (constant-time) return [System.Security.Cryptography.CryptographicOperations]::FixedTimeEquals( [Text.Encoding]::UTF8.GetBytes($computedHmac), [Text.Encoding]::UTF8.GetBytes($receivedHmac) This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -5,7 +5,8 @@ function Get-HmacSignedHeaders { .DESCRIPTION Generates signed headers for HMAC authentication based on a shared secret (SecureString), URL, and request body. Includes timestamp, nonce, and content hash, all covered in the signature. SecureString is converted as late as possible and securely cleaned up after use. .FUNCTIONALITY Security, HMAC Authentication @@ -19,6 +20,7 @@ function Get-HmacSignedHeaders { Created: 2025-03-17 Updated: 2025-03-18 #> param( # Shared secret used for HMAC signature (SecureString) [Parameter(Mandatory)][securestring]$SharedSecret, @@ -30,55 +32,73 @@ function Get-HmacSignedHeaders { [Parameter(Mandatory)][string]$Body, # Algorithm to use for HMAC signature (default: HMACSHA256) [string]$Algorithm = "HMACSHA256", # Optional: Provide custom nonce (for testing/debugging); defaults to a new random GUID if omitted [string]$Nonce ) if ($Algorithm -notin @('HMACSHA256', 'HMACSHA512')) { throw "Unsupported algorithm: $Algorithm" } # Parse URL components $uri = [System.Uri]$WebhookUrl $method = "POST" $path = $uri.AbsolutePath $webHost = $uri.Host # Timestamp header (RFC1123 format) $date = (Get-Date).ToUniversalTime().ToString("R") # Compute body hash (SHA256) $bodyBytes = [Text.Encoding]::UTF8.GetBytes($Body) $contentHash = [Convert]::ToBase64String(([System.Security.Cryptography.SHA256]::Create()).ComputeHash($bodyBytes)) # Generate nonce if not provided if (-not $Nonce) { $Nonce = [Guid]::NewGuid().ToString() } # Define signed headers and order $signedHeadersList = @('x-ms-date', 'Host', 'x-ms-content-sha256', 'x-ms-nonce') $signedHeaders = ($signedHeadersList -join ';') # Build canonical message $canonicalMessage = "$method`n$path`n$date;$webHost;$contentHash;$Nonce" $unsecureSecret = $null $unsecurePtr = [IntPtr]::Zero try { $hmac = New-Object ("System.Security.Cryptography.$Algorithm") # Convert SecureString to unmanaged pointer $unsecurePtr = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($SharedSecret) $unsecureSecret = [Runtime.InteropServices.Marshal]::PtrToStringAuto($unsecurePtr) # Set key and compute signature $hmac.Key = [Text.Encoding]::UTF8.GetBytes($unsecureSecret) $signature = [Convert]::ToBase64String($hmac.ComputeHash([Text.Encoding]::UTF8.GetBytes($canonicalMessage))) # Cleanup HMAC key [Array]::Clear($hmac.Key, 0, $hmac.Key.Length) $hmac = $null } finally { if ($unsecureSecret) { [Array]::Clear($unsecureSecret.ToCharArray(), 0, $unsecureSecret.Length) $unsecureSecret = $null } if ($unsecurePtr -ne [IntPtr]::Zero) { [Runtime.InteropServices.Marshal]::ZeroFreeBSTR($unsecurePtr) } } # Prepare headers (Host excluded intentionally) $headers = @{ 'x-ms-date' = $date 'x-ms-content-sha256' = $contentHash 'x-ms-nonce' = $Nonce 'x-authorization' = "HMAC-$($Algorithm.Replace('HMAC','')) SignedHeaders=$signedHeaders&Signature=$signature" } -
jpawlowski revised this gist
Mar 18, 2025 . 2 changed files with 2 additions and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -18,6 +18,7 @@ function Test-HmacAuthorization { Author: Julian Pawlowski Company Name: Workoho GmbH Created: 2025-03-17 Updated: 2025-03-18 #> param( [Parameter(Mandatory)][securestring]$SharedSecret, This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -17,6 +17,7 @@ function Get-HmacSignedHeaders { Author: Julian Pawlowski Company Name: Workoho GmbH Created: 2025-03-17 Updated: 2025-03-18 #> param( # Shared secret used for HMAC signature (SecureString) -
jpawlowski revised this gist
Mar 18, 2025 . 2 changed files with 22 additions and 14 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -16,24 +16,17 @@ function Test-HmacAuthorization { .NOTES Author: Julian Pawlowski Company Name: Workoho GmbH Created: 2025-03-17 #> param( [Parameter(Mandatory)][securestring]$SharedSecret, [Parameter(Mandatory)][object]$WebhookData, [int]$AllowedTimeDriftMinutes = 5, [string]$ExpectedPath = '/webhooks' ) $allowedAlgorithms = @('HMACSHA256', 'HMACSHA512') $headers = $WebhookData.RequestHeader $authHeader = $headers.'x-authorization' @@ -101,13 +94,16 @@ function Test-HmacAuthorization { $canonicalMessage = "$method`n$path`n" + ($headerValues -join ';') $unsecureSecret = $null $unsecurePtr = [IntPtr]::Zero try { $hmac = New-Object ("System.Security.Cryptography.$algorithm") # Convert SecureString to unmanaged pointer $unsecurePtr = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($SharedSecret) # Copy unmanaged pointer to managed string $unsecureSecret = [Runtime.InteropServices.Marshal]::PtrToStringAuto($unsecurePtr) # Set HMAC key $hmac.Key = [Text.Encoding]::UTF8.GetBytes($unsecureSecret) $computedHmac = [Convert]::ToBase64String($hmac.ComputeHash([Text.Encoding]::UTF8.GetBytes($canonicalMessage))) @@ -117,10 +113,15 @@ function Test-HmacAuthorization { return $false } finally { # Cleanup plaintext secret if ($unsecureSecret) { [Array]::Clear($unsecureSecret.ToCharArray(), 0, $unsecureSecret.Length) $unsecureSecret = $null } # Cleanup unmanaged memory (VERY IMPORTANT!) if ($unsecurePtr -ne [IntPtr]::Zero) { [Runtime.InteropServices.Marshal]::ZeroFreeBSTR($unsecurePtr) } } return [System.Security.Cryptography.CryptographicOperations]::FixedTimeEquals( This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -15,6 +15,7 @@ function Get-HmacSignedHeaders { .NOTES Author: Julian Pawlowski Company Name: Workoho GmbH Created: 2025-03-17 #> param( @@ -51,21 +52,27 @@ function Get-HmacSignedHeaders { $canonicalMessage = "$method`n$path`n$date;$webHost;$contentHash" $unsecureSecret = $null $unsecurePtr = [IntPtr]::Zero try { $hmac = New-Object ("System.Security.Cryptography.$Algorithm") # Convert SecureString to unmanaged memory $unsecurePtr = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($SharedSecret) $unsecureSecret = [Runtime.InteropServices.Marshal]::PtrToStringAuto($unsecurePtr) $hmac.Key = [Text.Encoding]::UTF8.GetBytes($unsecureSecret) $signature = [Convert]::ToBase64String($hmac.ComputeHash([Text.Encoding]::UTF8.GetBytes($canonicalMessage))) } finally { # Cleanup managed copy if ($unsecureSecret) { [Array]::Clear($unsecureSecret.ToCharArray(), 0, $unsecureSecret.Length) $unsecureSecret = $null } # Cleanup unmanaged memory if ($unsecurePtr -ne [IntPtr]::Zero) { [Runtime.InteropServices.Marshal]::ZeroFreeBSTR($unsecurePtr) } } $headers = @{ -
jpawlowski revised this gist
Mar 18, 2025 . 1 changed file with 0 additions and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -70,7 +70,6 @@ function Get-HmacSignedHeaders { $headers = @{ 'x-ms-date' = $date 'x-ms-content-sha256' = $contentHash 'x-authorization' = "HMAC-$($Algorithm.Replace('HMAC','')) SignedHeaders=$signedHeaders&Signature=$signature" } -
jpawlowski revised this gist
Mar 18, 2025 . 2 changed files with 3 additions and 3 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -144,12 +144,12 @@ $sharedSecret = (Get-AutomationVariable -Name "SharedSecret") | ConvertTo-Secure # Verify HMAC signature if (-not (Test-HmacAuthorization -SharedSecret $sharedSecret -WebhookData $WebhookData)) { [void]$sharedSecret.Dispose() throw "Unauthorized: HMAC signature verification failed." } # Clear secret from memory [void]$sharedSecret.Dispose() # Proceed securely Write-Output "HMAC verification passed." This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -129,7 +129,7 @@ $headers = Get-HmacSignedHeaders -SharedSecret $sharedSecret ` -Body $body # 🚫 Cleanup: SecureString Handling [void]$sharedSecret.Dispose() # Send POST request $null = Invoke-RestMethod -Method POST ` -
jpawlowski revised this gist
Mar 18, 2025 . 2 changed files with 26 additions and 11 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -144,9 +144,13 @@ $sharedSecret = (Get-AutomationVariable -Name "SharedSecret") | ConvertTo-Secure # Verify HMAC signature if (-not (Test-HmacAuthorization -SharedSecret $sharedSecret -WebhookData $WebhookData)) { $sharedSecret.Dispose() throw "Unauthorized: HMAC signature verification failed." } # Clear secret from memory $sharedSecret.Dispose() # Proceed securely Write-Output "HMAC verification passed." This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -78,34 +78,40 @@ function Get-HmacSignedHeaders { return $headers } # ================================ # 🔐 Example Usage: HMAC Header Generation # ================================ # IMPORTANT: # NEVER hardcode secrets in plaintext inside scripts. # Always retrieve secrets securely from encrypted sources, ensuring they are stored and used as SecureStrings: # ---------------------------- # Secure Shared Secret Retrieval Options: # ---------------------------- # 1️⃣ Azure Key Vault (Recommended for cloud/hybrid environments) # Requires: Az module & Managed Identity or Service Principal authentication Connect-AzAccount -Identity $sharedSecret = (Get-AzKeyVaultSecret -VaultName "<YourVaultName>" -Name "HmacSharedSecret").SecretValue # --> Result: SecureString ✅ # 2️⃣ Azure Automation Encrypted Variable (inside Automation Account only) $sharedSecret = (Get-AutomationVariable -Name "SharedSecret") | ConvertTo-SecureString -AsPlainText -Force # --> Result: SecureString ✅ # 3️⃣ Windows Credential Manager (if running on Windows, via SecretManagement module) Import-Module Microsoft.PowerShell.SecretManagement $sharedSecret = (Get-StoredCredential -Target "HmacSharedSecret").Password # --> Result: SecureString ✅ # 4️⃣ Encrypted local file (protected by ACLs; not recommended for cloud, acceptable in controlled environments) $sharedSecret = (Get-Content -Path "C:\Secrets\HmacSecret.txt" -Raw) | ConvertTo-SecureString -AsPlainText -Force # --> Result: SecureString ✅ # ❌ For demonstration purposes only (NEVER do this in production!) $sharedSecret = ConvertTo-SecureString -String "MySuperSecretKey" -AsPlainText -Force # --> Result: SecureString (but insecure in practice!) # ---------------------------- # Webhook Configuration: @@ -122,9 +128,14 @@ $headers = Get-HmacSignedHeaders -SharedSecret $sharedSecret ` -WebhookUrl $webhookUrl ` -Body $body # 🚫 Cleanup: SecureString Handling $sharedSecret.Dispose() # Send POST request $null = Invoke-RestMethod -Method POST ` -Uri $webhookUrl ` -Headers $headers ` -Body $body ` -ContentType 'application/json; charset=utf-8' # ================================ -
jpawlowski revised this gist
Mar 18, 2025 . No changes.There are no files selected for viewing
-
jpawlowski revised this gist
Mar 18, 2025 . 1 changed file with 45 additions and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -128,3 +128,48 @@ function Test-HmacAuthorization { [Text.Encoding]::UTF8.GetBytes($receivedHmac) ) } # ================================ # 🔐 HMAC Authorization Verification Usage # ================================ # ---------------------------------------- # ✅ Example 1: Production (Azure Automation) # ---------------------------------------- # Retrieve encrypted variable and convert immediately to SecureString $sharedSecret = (Get-AutomationVariable -Name "SharedSecret") | ConvertTo-SecureString -AsPlainText -Force # WebhookData is automatically passed by Azure Automation webhook trigger # Verify HMAC signature if (-not (Test-HmacAuthorization -SharedSecret $sharedSecret -WebhookData $WebhookData)) { throw "Unauthorized: HMAC signature verification failed." } # Proceed securely Write-Output "HMAC verification passed." # ---------------------------------------- # ✅ Example 2: Local Development / Testing # ---------------------------------------- # Retrieve secret securely from local source: # - From Credential Manager (example): # Import-Module Microsoft.PowerShell.SecretManagement # $sharedSecret = (Get-StoredCredential -Target "HmacSharedSecret").Password # - From encrypted local file (example): # $sharedSecret = (Get-Content -Path "C:\Secrets\HmacSecret.txt") | ConvertTo-SecureString -AsPlainText -Force # - For testing only (❌ never in production!): $sharedSecret = ConvertTo-SecureString -String "MySuperSecretKey" -AsPlainText -Force # Run verification if (-not (Test-HmacAuthorization -SharedSecret $sharedSecret -WebhookData $WebhookData)) { Write-Output "❌ Local Test: Signature invalid." } else { Write-Output "✅ Local Test: Signature valid!" } # ================================ -
jpawlowski revised this gist
Mar 18, 2025 . 2 changed files with 98 additions and 46 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -4,25 +4,23 @@ function Test-HmacAuthorization { Verifies HMAC signature of incoming Azure Automation webhook requests. .DESCRIPTION Validates the HMAC signature of a webhook request by reconstructing the canonical message and comparing the computed HMAC. Uses SecureString for the shared secret and ensures secure cleanup using finally blocks. Supports SHA256 and SHA512 algorithms. .FUNCTIONALITY Security, HMAC Authentication .EXAMPLE Test-HmacAuthorization -SharedSecret (Get-AutomationVariable -Name 'SharedSecret') -WebhookData $WebhookData .NOTES Author: Julian Pawlowski Created: 2025-03-17 #> param( # The shared secret key used for HMAC calculation (SecureString) [Parameter(Mandatory)][securestring]$SharedSecret, # The full webhook request data object passed by Azure Automation (includes headers and body) [Parameter(Mandatory)][object]$WebhookData, @@ -34,10 +32,8 @@ function Test-HmacAuthorization { [string]$ExpectedPath = '/webhooks' ) $allowedAlgorithms = @('HMACSHA256', 'HMACSHA512') $headers = $WebhookData.RequestHeader $authHeader = $headers.'x-authorization' @@ -46,13 +42,11 @@ function Test-HmacAuthorization { return $false } if ($authHeader -notmatch '^HMAC-(?<Algorithm>[A-Z0-9]+)\s+SignedHeaders=(?<SignedHeaders>[^&]+)&Signature=(?<Signature>.+)$') { Write-Error 'Invalid x-authorization header format' return $false } $algorithm = "HMAC$($matches['Algorithm'])" if ($allowedAlgorithms -notcontains $algorithm) { Write-Error "Algorithm $algorithm not allowed" @@ -62,15 +56,13 @@ function Test-HmacAuthorization { $signedHeaders = $matches['SignedHeaders'].Split(';') $receivedHmac = $matches['Signature'] foreach ($header in $signedHeaders) { if ([string]::IsNullOrEmpty($headers.$header)) { Write-Error "Missing signed header: $header" return $false } } if ([string]::IsNullOrEmpty($headers.'x-ms-date')) { Write-Error 'x-ms-date header required' return $false @@ -90,7 +82,6 @@ function Test-HmacAuthorization { } } if ([string]::IsNullOrEmpty($headers.'x-ms-content-sha256')) { Write-Error "x-ms-content-sha256 header required" return $false @@ -104,27 +95,34 @@ function Test-HmacAuthorization { } } $method = 'POST' $path = $ExpectedPath $headerValues = foreach ($header in $signedHeaders) { $headers.$header } $canonicalMessage = "$method`n$path`n" + ($headerValues -join ';') $unsecureSecret = $null try { $hmac = New-Object ("System.Security.Cryptography.$algorithm") $unsecureSecret = [Runtime.InteropServices.Marshal]::PtrToStringAuto( [Runtime.InteropServices.Marshal]::SecureStringToBSTR($SharedSecret) ) $hmac.Key = [Text.Encoding]::UTF8.GetBytes($unsecureSecret) $computedHmac = [Convert]::ToBase64String($hmac.ComputeHash([Text.Encoding]::UTF8.GetBytes($canonicalMessage))) } catch { Write-Error "Error during HMAC computation" return $false } finally { if ($unsecureSecret) { [Array]::Clear($unsecureSecret.ToCharArray(), 0, $unsecureSecret.Length) $unsecureSecret = $null } } return [System.Security.Cryptography.CryptographicOperations]::FixedTimeEquals( [Text.Encoding]::UTF8.GetBytes($computedHmac), [Text.Encoding]::UTF8.GetBytes($receivedHmac) This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -4,26 +4,22 @@ function Get-HmacSignedHeaders { Generates signed headers for HMAC authentication for Azure Automation webhook requests. .DESCRIPTION Generates signed headers for HMAC authentication based on a shared secret (SecureString), URL, and request body. SecureString is converted as late as possible and cleaned up immediately after use. .FUNCTIONALITY Security, HMAC Authentication .EXAMPLE $headers = Get-HmacSignedHeaders -SharedSecret (ConvertTo-SecureString "MySecretKey" -AsPlainText -Force) -WebhookUrl "https://example.com/webhooks" -Body '{"foo":"bar"}' .NOTES Author: Julian Pawlowski Created: 2025-03-17 #> param( # Shared secret used for HMAC signature (SecureString) [Parameter(Mandatory)][securestring]$SharedSecret, # Full webhook URL to which the request will be sent [Parameter(Mandatory)][string]$WebhookUrl, @@ -35,42 +31,100 @@ function Get-HmacSignedHeaders { [string]$Algorithm = "HMACSHA256" ) if ($Algorithm -notin @('HMACSHA256', 'HMACSHA512')) { throw "Unsupported algorithm: $Algorithm" } $uri = [System.Uri]$WebhookUrl $method = "POST" $path = $uri.AbsolutePath $webHost = $uri.Host $date = (Get-Date).ToUniversalTime().ToString("R") $bodyBytes = [Text.Encoding]::UTF8.GetBytes($Body) $contentHash = [Convert]::ToBase64String(([System.Security.Cryptography.SHA256]::Create()).ComputeHash($bodyBytes)) $signedHeadersList = @('x-ms-date', 'Host', 'x-ms-content-sha256') $signedHeaders = ($signedHeadersList -join ';') $canonicalMessage = "$method`n$path`n$date;$webHost;$contentHash" $unsecureSecret = $null try { $hmac = New-Object ("System.Security.Cryptography.$Algorithm") $unsecureSecret = [Runtime.InteropServices.Marshal]::PtrToStringAuto( [Runtime.InteropServices.Marshal]::SecureStringToBSTR($SharedSecret) ) $hmac.Key = [Text.Encoding]::UTF8.GetBytes($unsecureSecret) $signature = [Convert]::ToBase64String($hmac.ComputeHash([Text.Encoding]::UTF8.GetBytes($canonicalMessage))) } finally { if ($unsecureSecret) { [Array]::Clear($unsecureSecret.ToCharArray(), 0, $unsecureSecret.Length) $unsecureSecret = $null } } $headers = @{ 'x-ms-date' = $date 'Host' = $webHost 'x-ms-content-sha256' = $contentHash 'x-authorization' = "HMAC-$($Algorithm.Replace('HMAC','')) SignedHeaders=$signedHeaders&Signature=$signature" } return $headers } # 🔽 Example Usage: # ---------------------------- # IMPORTANT: # NEVER hardcode secrets in plaintext inside scripts. # Always retrieve secrets securely from encrypted sources like: # ---------------------------- # Secure Shared Secret Retrieval Options: # ---------------------------- # 1️⃣ Azure Key Vault (Recommended for cloud/hybrid environments) # Connect-AzAccount -Identity # $sharedSecret = (Get-AzKeyVaultSecret -VaultName "<YourVaultName>" -Name "HmacSharedSecret").SecretValue # 2️⃣ Azure Automation Encrypted Variable (inside Automation Account only) # $sharedSecret = Get-AutomationVariable -Name "SharedSecret" # 3️⃣ Windows Credential Manager (if running on Windows, via SecretManagement module) # Import-Module Microsoft.PowerShell.SecretManagement # $sharedSecret = (Get-StoredCredential -Target "HmacSharedSecret").Password # 4️⃣ Encrypted local file (protected by ACLs; not recommended for cloud, acceptable in controlled environments) # $secretString = Get-Content -Path "C:\Secrets\HmacSecret.txt" # $sharedSecret = ConvertTo-SecureString -String $secretString -AsPlainText -Force # ❌ For demonstration purposes only (NEVER do this in production!) # $sharedSecret = ConvertTo-SecureString -String "MySuperSecretKey" -AsPlainText -Force # ---------------------------- # Webhook Configuration: # ---------------------------- # Webhook URL (replace with your actual URL) $webhookUrl = "https://<your-webhook-url>/webhooks" # Sample body (can be JSON or plain text) $body = '{"param1":"value1"}' # Generate signed headers $headers = Get-HmacSignedHeaders -SharedSecret $sharedSecret ` -WebhookUrl $webhookUrl ` -Body $body # Send POST request $null = Invoke-RestMethod -Method POST ` -Uri $webhookUrl ` -Headers $headers ` -Body $body ` -ContentType 'application/json; charset=utf-8' -
jpawlowski revised this gist
Mar 17, 2025 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -21,7 +21,7 @@ function Test-HmacAuthorization { Updated: 2025-03-17 #> param( # The shared secret key used for HMAC calculation [Parameter(Mandatory)][string]$SharedSecret, # The full webhook request data object passed by Azure Automation (includes headers and body) -
jpawlowski revised this gist
Mar 17, 2025 . 2 changed files with 0 additions and 0 deletions.There are no files selected for viewing
File renamed without changes.File renamed without changes. -
jpawlowski revised this gist
Mar 17, 2025 . 2 changed files with 0 additions and 0 deletions.There are no files selected for viewing
File renamed without changes.File renamed without changes. -
jpawlowski revised this gist
Mar 17, 2025 . 1 changed file with 76 additions and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,76 @@ function Get-HmacSignedHeaders { <# .SYNOPSIS Generates signed headers for HMAC authentication for Azure Automation webhook requests. .DESCRIPTION This function generates signed headers for HMAC authentication based on the provided shared secret, webhook URL, and request body content. It uses the specified HMAC algorithm (SHA256 or SHA512) and constructs the canonical message according to Azure Communication Services style. See the complementary function Test-HmacAuthorization for signature verification within Azure Automation runbooks. .FUNCTIONALITY Security, HMAC Authentication .EXAMPLE $headers = Get-HmacSignedHeaders -SharedSecret "MySecretKey" -WebhookUrl "https://example.com/webhooks?token=12345" -Body '{"foo":"bar"}' Generates signed headers for HMAC-protected request. .NOTES Author: Julian Pawlowski Created: 2025-03-17 Updated: 2025-03-17 #> param( # Shared secret used for HMAC signature (plain text, matches server config) [Parameter(Mandatory)][string]$SharedSecret, # Full webhook URL to which the request will be sent [Parameter(Mandatory)][string]$WebhookUrl, # Request body content as a string (usually JSON or similar) [Parameter(Mandatory)][string]$Body, # Algorithm to use for HMAC signature (default: HMACSHA256) [string]$Algorithm = "HMACSHA256" ) # Validate allowed algorithm if ($Algorithm -notin @('HMACSHA256', 'HMACSHA512')) { throw "Unsupported algorithm: $Algorithm" } # Parse URL components $uri = [System.Uri]$WebhookUrl $method = "POST" $path = $uri.AbsolutePath # URL path without query string since the webhook token is removed by the Azure Automation service $webHost = $uri.Host # x-ms-date header: RFC1123 timestamp (UTC) $date = (Get-Date).ToUniversalTime().ToString("R") # Compute body hash (x-ms-content-sha256 header, Base64-encoded SHA256) $bodyBytes = [Text.Encoding]::UTF8.GetBytes($Body) $contentHash = [Convert]::ToBase64String(([System.Security.Cryptography.SHA256]::Create()).ComputeHash($bodyBytes)) # Define signed headers and their order (must match server config) $signedHeadersList = @('x-ms-date', 'Host', 'x-ms-content-sha256') $signedHeaders = ($signedHeadersList -join ';') # Canonical message: method, path, signed header values (semicolon-separated) $canonicalMessage = "$method`n$path`n$date;$webHost;$contentHash" # Compute HMAC signature $hmac = New-Object ("System.Security.Cryptography.$Algorithm") $hmac.Key = [Text.Encoding]::UTF8.GetBytes($SharedSecret) $signature = [Convert]::ToBase64String($hmac.ComputeHash([Text.Encoding]::UTF8.GetBytes($canonicalMessage))) # Prepare signed headers $headers = @{ 'x-ms-date' = $date 'x-ms-content-sha256' = $contentHash 'x-authorization' = "HMAC-$($Algorithm.Replace('HMAC','')) SignedHeaders=$signedHeaders&Signature=$signature" } return $headers } -
jpawlowski created this gist
Mar 17, 2025 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,132 @@ function Test-HmacAuthorization { <# .SYNOPSIS Verifies HMAC signature of incoming Azure Automation webhook requests. .DESCRIPTION This function validates the HMAC signature of a webhook request by reconstructing the canonical message and comparing the computed HMAC. It checks timestamp freshness, signed header presence, and request body integrity. It supports both SHA256 and SHA512 algorithms. Use together with Get-HmacSignedHeaders to sign and verify HMAC-authenticated webhook calls. .FUNCTIONALITY Security, HMAC Authentication .EXAMPLE Test-HmacAuthorization -SharedSecret "MySecretKey" -WebhookData $WebhookData .NOTES Author: Julian Pawlowski Created: 2025-03-17 Updated: 2025-03-17 #> param( # The shared secret key used for HMAC calculation (Base64-encoded string) [Parameter(Mandatory)][string]$SharedSecret, # The full webhook request data object passed by Azure Automation (includes headers and body) [Parameter(Mandatory)][object]$WebhookData, # Allowed time difference in minutes for timestamp validation (default: 5 minutes) [int]$AllowedTimeDriftMinutes = 5, # Expected request path (used in canonical message construction) [string]$ExpectedPath = '/webhooks' ) # Allowed HMAC algorithms $allowedAlgorithms = @('HMACSHA256', 'HMACSHA512') # Extract request headers $headers = $WebhookData.RequestHeader $authHeader = $headers.'x-authorization' if (-not $authHeader) { Write-Error 'Missing x-authorization header' return $false } # Parse x-authorization header (expecting format: HMAC-ALGO SignedHeaders=...&Signature=...) if ($authHeader -notmatch '^HMAC-(?<Algorithm>[A-Z0-9]+)\s+SignedHeaders=(?<SignedHeaders>[^&]+)&Signature=(?<Signature>.+)$') { Write-Error 'Invalid x-authorization header format' return $false } # Extract algorithm and signature details $algorithm = "HMAC$($matches['Algorithm'])" if ($allowedAlgorithms -notcontains $algorithm) { Write-Error "Algorithm $algorithm not allowed" return $false } $signedHeaders = $matches['SignedHeaders'].Split(';') $receivedHmac = $matches['Signature'] # Validate that all signed headers are present in the request foreach ($header in $signedHeaders) { if ([string]::IsNullOrEmpty($headers.$header)) { Write-Error "Missing signed header: $header" return $false } } # Validate x-ms-date header freshness (protection against replay attacks) if ([string]::IsNullOrEmpty($headers.'x-ms-date')) { Write-Error 'x-ms-date header required' return $false } else { try { $requestTime = [datetime]::Parse($headers.'x-ms-date').ToUniversalTime() $currentTime = (Get-Date).ToUniversalTime() if ([math]::Abs(($currentTime - $requestTime).TotalMinutes) -gt $AllowedTimeDriftMinutes) { Write-Error 'Request timestamp expired' return $false } } catch { Write-Error 'Invalid timestamp format' return $false } } # Validate x-ms-content-sha256 header (ensures body integrity) if ([string]::IsNullOrEmpty($headers.'x-ms-content-sha256')) { Write-Error "x-ms-content-sha256 header required" return $false } else { $bodyBytes = [Text.Encoding]::UTF8.GetBytes($WebhookData.RequestBody) $computedBodyHash = [Convert]::ToBase64String(([System.Security.Cryptography.SHA256]::Create()).ComputeHash($bodyBytes)) if ($headers.'x-ms-content-sha256' -ne $computedBodyHash) { Write-Error "Content hash mismatch" return $false } } # Construct canonical message (method + path + signed header values) $method = 'POST' $path = $ExpectedPath $headerValues = foreach ($header in $signedHeaders) { $headers.$header } $canonicalMessage = "$method`n$path`n" + ($headerValues -join ';') # Compute HMAC signature try { $hmac = New-Object ("System.Security.Cryptography.$algorithm") $hmac.Key = [Text.Encoding]::UTF8.GetBytes($SharedSecret) } catch { Write-Error "Unsupported algorithm $algorithm" return $false } $computedHmac = [Convert]::ToBase64String($hmac.ComputeHash([Text.Encoding]::UTF8.GetBytes($canonicalMessage))) # Secure, constant-time comparison to prevent timing attacks return [System.Security.Cryptography.CryptographicOperations]::FixedTimeEquals( [Text.Encoding]::UTF8.GetBytes($computedHmac), [Text.Encoding]::UTF8.GetBytes($receivedHmac) ) }