Skip to content

Instantly share code, notes, and snippets.

@dreadsend
Last active March 29, 2024 21:48
Show Gist options
  • Save dreadsend/b257928af29f8827259f435104986a01 to your computer and use it in GitHub Desktop.
Save dreadsend/b257928af29f8827259f435104986a01 to your computer and use it in GitHub Desktop.
PowerShell Implementation of Prooftokens for Entra ID Application Certificate Rotation
# Documentation: https://learn.microsoft.com/en-us/graph/application-rollkey-prooftoken?tabs=powershell
# https://learn.microsoft.com/en-us/graph/api/application-addkey?view=graph-rest-1.0&tabs=http
function New-Prooftoken {
param (
[Parameter(Mandatory = $true)]
[string]$clientId,
[Parameter(Mandatory = $true)]
[System.Security.Cryptography.X509Certificates.X509Certificate2]$cert
)
# Very far removed, but still based on https://adamtheautomator.com/powershell-graph-api/
# Get base64 hash of certificate in Web Encoding
$CertificateBase64Hash = [System.Convert]::ToBase64String( $cert.GetCertHash() ) -replace '\+', '-' -replace '/', '_' -replace '='
$StartDate = (Get-Date "1970-01-01T00:00:00Z").ToUniversalTime()
$now = (Get-Date).ToUniversalTime()
# Create JWT timestamp for expiration - 5 Minute Lifetime here
$JWTExpirationTimeSpan = ( New-TimeSpan -Start $StartDate -End $now.AddMinutes(5) ).TotalSeconds
$JWTExpiration = [math]::Round($JWTExpirationTimeSpan, 0)
# Create JWT validity start timestamp
$NotBeforeExpirationTimeSpan = ( New-TimeSpan -Start $StartDate -End $now ).TotalSeconds
$NotBefore = [math]::Round($NotBeforeExpirationTimeSpan, 0)
# Create JWT header
$JWTHeader = @{
alg = "RS256"
typ = "JWT"
x5t = $CertificateBase64Hash
kid = $cert.Thumbprint
}
# Create JWT payload
$JWTPayLoad = @{
aud = "00000002-0000-0000-c000-000000000000"
exp = $JWTExpiration
iss = $clientID
nbf = $NotBefore
iat = $NotBefore
}
# Convert header and payload to base64
$JWTHeaderToByte = [System.Text.Encoding]::UTF8.GetBytes(($JWTHeader | ConvertTo-Json))
$EncodedHeader = [System.Convert]::ToBase64String($JWTHeaderToByte) -replace '='
$JWTPayLoadToByte = [System.Text.Encoding]::UTF8.GetBytes(($JWTPayload | ConvertTo-Json))
$EncodedPayload = [System.Convert]::ToBase64String($JWTPayLoadToByte) -replace '='
$JWT = $EncodedHeader + "." + $EncodedPayload
# Define RSA signature and hashing algorithm
$RSAPadding = [Security.Cryptography.RSASignaturePadding]::Pkcs1
$HashAlgorithm = [Security.Cryptography.HashAlgorithmName]::SHA256
# Sign the JWT
$rsaCert = [System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPrivateKey($cert)
$Signature = [Convert]::ToBase64String(
$rsaCert.SignData([System.Text.Encoding]::UTF8.GetBytes($JWT), $HashAlgorithm, $RSAPadding)
) -replace '\+', '-' -replace '/', '_' -replace '='
# Add Signature to JWT
$JWT = $JWT + "." + $Signature
return ConvertTo-SecureString $JWT -AsPlainText -Force
end {
# Clear Senstive Values
$sensitiveVars = @("Signature","JWT")
Remove-Variable $sensitiveVars
[gc]::collect()
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment