Files
unknown caf93cbbb3 v0.9.5
2024-11-14 13:20:28 -06:00

469 lines
17 KiB
PowerShell

# Decrypt ADFS RefreshToken
# Oct 28th 2021
function Unprotect-ADFSRefreshToken
{
<#
.SYNOPSIS
Decrypts and verifies the given AD FS generated Refresh Token with the given certificates.
.DESCRIPTION
Decrypts and verifies the given AD FS generated Refresh Token with the given certificates.
.PARAMETER RefreshToken
AD FS generated RefreshToken.
.PARAMETER PfxFileName_encryption
Name of the PFX file of token encryption certificate.
.PARAMETER PfxPassword_encryption
Password of the token encryption PFX file. Optional.
.PARAMETER PfxFileName_signing
Name of the PFX file of token signing certificate. Optional. If not provided, refresh token is not verified.
.PARAMETER PfxPassword_signing
Password of the token signing PFX file. Optional.
.Example
PS C:\>Unprotect-ADFSRefreshToken -RefreshToken $token -PfxFileName_encryption .\ADFS_encryption.pfx -PfxFileName_signing .\ADFS_signing.pfx
ClientID : 5846ec9c-1cd7-4040-8630-6ae82d6cdfd3
RedirectUri :
Resource : urn:microsoft:userinfo
Issuer : http://sts.company.com/adfs/services/trust
NotBefore : 1635414030
ExpiresOn : 1635442830
SingleSignOnToken : {"TokenType":0,"StringToken":"vVV[redacted]W/gE=","Version":1}
DeviceFlowDeviceId :
IsDeviceFlow : False
SessionKeyString :
SSOToken : <SessionToken>[redacted]</SessionToken>
#>
[cmdletbinding()]
Param(
[Parameter(Mandatory=$True,ValueFromPipeLine)]
[String]$RefreshToken,
[Parameter(Mandatory=$True)]
[string]$PfxFileName_encryption,
[Parameter(Mandatory=$False)]
[string]$PfxPassword_encryption,
[Parameter(Mandatory=$False)]
[string]$PfxFileName_signing,
[Parameter(Mandatory=$False)]
[string]$PfxPassword_signing
)
Begin
{
$certificate_encryption = Load-Certificate -FileName $PfxFileName_encryption -Password $PfxPassword_encryption -Exportable
[System.Security.Cryptography.RSACryptoServiceProvider]$privateKey_encryption = Load-PrivateKey -Certificate $certificate_encryption
if($PfxFileName_signing)
{
$certificate_signing = Load-Certificate -FileName $PfxFileName_signing -Password $PfxPassword_signing -Exportable
}
}
Process
{
# Separate token and signature
$tokenParts = $RefreshToken.Split(".")
$enc_refresh_token = (Convert-B64ToByteArray -B64 $tokenParts[0])
$signature = (Convert-B64ToByteArray -B64 $tokenParts[1])
# Verify the signature if the signing certificate provided
if($certificate_signing)
{
$valid = $certificate_signing.PublicKey.Key.VerifyData($enc_refresh_token,"SHA256",$signature)
if(!$valid)
{
Write-Warning "Invalid signature or signing certificate!"
}
Write-Verbose "Refresh token signature validated."
}
# Get the refresh token components
$p = 0
$hash = $enc_refresh_token[$p..($p+32-1)] ; $p+=32
$enc_Key_IV_len = [bitconverter]::ToUInt32($enc_refresh_token[$p..($p+3)],0); $p+=4
$enc_Key_IV = $enc_refresh_token[($p)..($p + $enc_Key_IV_len -1)]; $p+= $enc_Key_IV_len
$enc_token_len = [bitconverter]::ToUInt32($enc_refresh_token[$p..($p+3)],0); $p+=4
$enc_token = $enc_refresh_token[($p)..($p + $enc_token_len -1)]
# Compare the hash
$sha256 = [System.Security.Cryptography.SHA256]::Create()
$comp_hash = $sha256.ComputeHash([text.encoding]::UTF8.GetBytes($privateKey_encryption.ToXmlString($false)))
if(Compare-Object -ReferenceObject $hash -DifferenceObject $comp_hash -SyncWindow 0)
{
Write-Error "Invalid decryption certificate (hash doesn't match)."
return
}
Write-Verbose "Decryption key hash validated."
# Decrypt Key and IV
$dec_Key_IV = $privateKey_encryption.Decrypt($enc_Key_IV, $True)
$dec_Key = $dec_Key_IV[ 0..31]
$dec_IV = $dec_Key_IV[32..48]
# Decrypt the refresh token
$Crypto = [System.Security.Cryptography.RijndaelManaged]::Create()
$Crypto.Mode = "CBC"
$Crypto.Padding = "PKCS7"
$Crypto.Key = $dec_Key
$Crypto.IV = $dec_IV
$decryptor = $Crypto.CreateDecryptor()
$ms = New-Object System.IO.MemoryStream
$cs = New-Object System.Security.Cryptography.CryptoStream($ms,$decryptor,[System.Security.Cryptography.CryptoStreamMode]::Write)
$cs.Write($enc_token,0,$enc_token.Count)
$cs.Close()
$cs.Dispose()
$dec_refresh_token = [text.encoding]::UTF8.GetString($ms.ToArray())
$ms.Close()
$ms.Dispose()
# Convert from json
$refresh_token = $dec_refresh_token | ConvertFrom-Json
# Get the deflated SSOToken
[byte[]]$def_SSOToken = Convert-B64ToByteArray(($refresh_token.SingleSignOnToken | ConvertFrom-Json).StringToken)
# Get the binary xml SSOToken
$bxml_SSOToken = Get-DeDeflatedByteArray -byteArray $def_SSOToken
# Get the xml SSOTOken
$xml_SSOToken = BinaryToXml -xml_bytes $bxml_SSOToken -Dictionary (Get-XmlDictionary -type Session)
# Set the SSOToken and return
$refresh_token | Add-Member -NotePropertyName "SSOToken" -NotePropertyValue $xml_SSOToken.outerxml
$refresh_token
}
End
{
Unload-PrivateKey -PrivateKey $privateKey_encryption
}
}
# Create a new ADFS RefreshToken
# Oct 28th 2021
function New-ADFSRefreshToken
{
<#
.SYNOPSIS
Creates a new AD FS Refresh Token with the given certificate.
.DESCRIPTION
Creates a new AD FS Refresh Token with the given certificate.
.PARAMETER NotBefore
The time after the refresh token is valid. Defaults to current time.
.PARAMETER ExpiresOn
The time when the refresh token is invalidated. Defaults to 8 hours from the current time.
.PARAMETER UserPrincipalName
UserPrincipalName of the user.
.PARAMETER Name
DisplayName of the user. Optional.
.PARAMETER ClientID
GUID of the client id. The client MUST be configured in the target AD FS server.
.PARAMETER Resource
The resource (uri) of the refresh token.
.PARAMETER Issuer
The uri of the issuing party
.PARAMETER RedirectUri
The redirect uri. Optional.
.PARAMETER PfxFileName_encryption
Name of the PFX file of token encryption certificate.
.PARAMETER PfxPassword_encryption
Password of the token encryption PFX file. Optional.
.PARAMETER PfxFileName_signing
Name of the PFX file of token signing certificate.
.PARAMETER PfxPassword_signing
Password of the token signing PFX file. Optional.
.Example
$refresh_token = New-AADIntADFSRefreshToken -UserPrincipalName "user@company.com" -Resource "urn:microsoft:userinfo" -Issuer "http://sts.company.com/adfs/services/trust" -PfxFileName_encryption .\ADFS_encryption.pfx -PfxFileName_signing .\ADFS_signing.pfx -ClientID "5846ec9c-1cd7-4040-8630-6ae82d6cdfd3"
$body=@{
"client_id" = "5846ec9c-1cd7-4040-8630-6ae82d6cdfd3"
"refresh_token" = $refresh_token
"grant_type" = "refresh_token"
}
$response = Invoke-RestMethod -UseBasicParsing -Uri "https://sts.company.com/adfs/services/trust/adfs/oauth2/token" -Method Post -Body $body
$access_token = $response.access_token
#>
[cmdletbinding()]
Param(
[Parameter(Mandatory=$False)]
[DateTime]$NotBefore = (Get-Date),
[Parameter(Mandatory=$False)]
[DateTime]$ExpiresOn = ((Get-Date).AddHours(8)),
[Parameter(Mandatory=$True)]
[String]$UserPrincipalName,
[Parameter(Mandatory=$False)]
[String]$Name,
[Parameter(Mandatory=$True)]
[guid]$ClientID,
[Parameter(Mandatory=$True)]
[String]$Resource,
[Parameter(Mandatory=$True)]
[String]$Issuer,
[Parameter(Mandatory=$False)]
[String]$RedirectUri,
[Parameter(Mandatory=$True)]
[String]$PfxFileName_encryption,
[Parameter(Mandatory=$False)]
[String]$PfxPassword_encryption,
[Parameter(Mandatory=$True)]
[String]$PfxFileName_signing,
[Parameter(Mandatory=$False)]
[String]$PfxPassword_signing
)
Begin
{
$certificate_encryption = Load-Certificate -FileName $PfxFileName_encryption -Password $PfxPassword_encryption
$certificate_signing = Load-Certificate -FileName $PfxFileName_signing -Password $PfxPassword_signing -Exportable
$privateKey_signing = Load-PrivateKey -Certificate $certificate_signing
}
Process
{
# Generate Session Token
$Key = Get-RandomBytes -Bytes 16
[xml]$xml_SessionToken =@"
<SessionToken>
<Version>1</Version>
<SecureConversationVersion>http://docs.oasis-open.org/ws-sx/ws-secureconversation/200512</SecureConversationVersion>
<Id>_$((New-Guid).ToString())-$(Convert-ByteArrayToHex -Bytes (Get-RandomBytes -Bytes 16))</Id>
<ContextId>urn:uuid:$((New-Guid).ToString())</ContextId>
<Key>$(Convert-ByteArrayToB64 -Bytes $Key)</Key>
<KeyGeneration>urn:uuid:$((New-Guid).ToString())</KeyGeneration>
<EffectiveTime>$($NotBefore.Ticks)</EffectiveTime>
<ExpiryTime>$($ExpiresOn.Ticks)</ExpiryTime>
<KeyEffectiveTime>$($NotBefore.Ticks)</KeyEffectiveTime>
<KeyExpiryTime>$($ExpiresOn.Ticks)</KeyExpiryTime>
<ClaimsPrincipal>
<Identities>
<Identity NameClaimType="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name" RoleClaimType="http://schemas.microsoft.com/ws/2008/06/identity/claims/role">
<ClaimCollection>
<Claim Issuer="AD AUTHORITY" OriginalIssuer="AD AUTHORITY" Type="http://schemas.microsoft.com/ws/2014/01/identity/claims/anchorclaimtype" Value="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn" ValueType="http://www.w3.org/2001/XMLSchema#string"/>
<Claim Issuer="AD AUTHORITY" OriginalIssuer="AD AUTHORITY" Type="http://schemas.microsoft.com/ws/2008/06/identity/claims/authenticationinstant" Value="$($NotBefore.ToUniversalTime().ToString("s", [cultureinfo]::InvariantCulture)+".000Z")" ValueType="http://www.w3.org/2001/XMLSchema#dateTime"/>
<Claim Issuer="LOCAL AUTHORITY" OriginalIssuer="LOCAL AUTHORITY" Type="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn" Value="$UserPrincipalName" ValueType="http://www.w3.org/2001/XMLSchema#string"/>
<Claim Issuer="LOCAL AUTHORITY" OriginalIssuer="LOCAL AUTHORITY" Type="http://schemas.microsoft.com/claims/authnmethodsreferences" Value="http://schemas.microsoft.com/claims/multipleauthn" ValueType="http://www.w3.org/2001/XMLSchema#string"/>
</ClaimCollection>
</Identity>
</Identities>
</ClaimsPrincipal>
<EndpointId/>
</SessionToken>
"@
$sessionToken = Get-DeflatedByteArray -byteArray (XmlToBinary -xml_doc $xml_SessionToken -Dictionary (Get-XmlDictionary -Type Session))
# Construct the refresh token
$refresh_token = [ordered]@{
"ClientID" = $ClientID.ToString()
"RedirectUri" = $RedirectUri
"Resource" = $Resource
"Issuer" = $Issuer
"NotBefore" = [int]($NotBefore-$epoch).TotalSeconds
"ExpiresOn" = [int]($ExpiresOn-$epoch).TotalSeconds
"SingleSignOnToken" = @{
"TokenType" = 0
"StringToken" = Convert-ByteArrayToB64 -Bytes $sessionToken
"Version" = 1
} | ConvertTo-Json -Compress
"DeviceFlowDeviceId" = $null
"IsDeviceFlow" = $false
"SessionKeyString" = $null
} | ConvertTo-Json -Compress
$dec_token = [text.encoding]::UTF8.GetBytes($refresh_token)
# Create IV and key
$dec_IV = Get-RandomBytes -Bytes 16
$dec_Key = Get-RandomBytes -Bytes 32
# Encrypt the refresh token
$Crypto = [System.Security.Cryptography.RijndaelManaged]::Create()
$Crypto.Mode = "CBC"
$Crypto.Padding = "PKCS7"
$Crypto.Key = $dec_Key
$Crypto.IV = $dec_IV
$decryptor = $Crypto.CreateEncryptor()
$ms = New-Object System.IO.MemoryStream
$cs = New-Object System.Security.Cryptography.CryptoStream($ms,$decryptor,[System.Security.Cryptography.CryptoStreamMode]::Write)
$cs.Write($dec_token,0,$dec_token.Count)
$cs.Close()
$cs.Dispose()
$enc_token = $ms.ToArray()
$ms.Close()
$ms.Dispose()
# Encrypt Key Iv block
$enc_Key_IV = New-Object Byte[] 48
[Array]::Copy($dec_Key,$enc_Key_IV,32)
[Array]::Copy($dec_IV,0,$enc_Key_IV,32,16)
$dec_Key_IV=$certificate_encryption.PublicKey.Key.Encrypt($enc_Key_IV, $True)
# Get the encryption key hash
$sha256 = [System.Security.Cryptography.SHA256]::Create()
$hash = $sha256.ComputeHash([text.encoding]::UTF8.GetBytes($certificate_encryption.PublicKey.Key.ToXmlString($false)))
# Create the block
$buffer = New-Object System.IO.MemoryStream
$buffer.Write($hash,0,$hash.Length)
$buffer.Write([bitconverter]::GetBytes([uint32]$dec_Key_IV.Length),0,4)
$buffer.Write($dec_Key_IV,0,$dec_Key_IV.Length)
$buffer.Write([bitconverter]::GetBytes([uint32]$enc_token.Length),0,4)
$buffer.Write($enc_token,0,$enc_token.Length)
$buffer.Flush()
$enc_refresh_token = $buffer.ToArray()
$buffer.Dispose()
# Sign the token
# Store the public key
$cspParameters = [System.Security.Cryptography.CspParameters]::new()
$cspParameters.ProviderName = "Microsoft Enhanced RSA and AES Cryptographic Provider"
$cspParameters.ProviderType = 24
$cspParameters.KeyContainerName ="AADInternals"
# Get the private key from the certificate
$publicKey = [System.Security.Cryptography.RSACryptoServiceProvider]::new(2048,$cspParameters)
$publicKey.ImportParameters($certificate_signing.PublicKey.Key.ExportParameters($False))
$signature = $privateKey_signing.SignData($enc_refresh_token,"SHA256")
# Return
return "$(Convert-ByteArrayToB64 -Bytes $enc_refresh_token -UrlEncode).$(Convert-ByteArrayToB64 -Bytes $signature -UrlEncode)"
}
End
{
Unload-PrivateKey -PrivateKey $privateKey_signing
}
}
# Create a new ADFS Access Token
# Nov 1st 2021
function New-ADFSAccessToken
{
<#
.SYNOPSIS
Creates a new AccessToken and signs it with the given certificates.
.DESCRIPTION
Creates a new AccessToken and signs it with the given certificates.
#>
[cmdletbinding()]
Param(
[Parameter(Mandatory=$False)]
[DateTime]$NotBefore = (Get-Date),
[Parameter(Mandatory=$False)]
[DateTime]$ExpiresOn = ((Get-Date).AddHours(8)),
[Parameter(Mandatory=$False)]
[String]$UserPrincipalName,
[Parameter(Mandatory=$False)]
[String]$Name,
[Parameter(Mandatory=$True)]
[guid]$ClientID,
[Parameter(Mandatory=$True)]
[String]$Resource,
[Parameter(Mandatory=$False)]
[String]$Scope="openid",
[Parameter(Mandatory=$True)]
[String]$Issuer,
[Parameter(Mandatory=$True)]
[String]$PfxFileName_signing,
[Parameter(Mandatory=$False)]
[String]$PfxPassword_signing
)
Begin
{
$certificate_signing = Load-Certificate -FileName $PfxFileName_signing -Password $PfxPassword_signing -Exportable
$privateKey_signing = Load-PrivateKey -Certificate $certificate_signing
}
Process
{
# Construct the refresh token
$payLoad = [ordered]@{
"aud" = $Resource
"iss" = $Issuer
"iat" = [int]($NotBefore-$epoch).TotalSeconds
"nbf" = [int]($NotBefore-$epoch).TotalSeconds
"exp" = [int]($ExpiresOn-$epoch).TotalSeconds
"sub" = $Name
"apptype" = "Public"
"appid" = $ClientID
"authmethod" = "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport"
"auth_time" = "$($NotBefore.ToUniversalTime().ToString("s", [cultureinfo]::InvariantCulture)+".000Z")"
"ver" = "1.0"
"scp" = $Scope
}
$certHash = Convert-ByteArrayToB64 -bytes $certificate_signing.GetCertHash() -UrlEncode -NoPadding
$header = [ordered]@{
"typ" = "JWT"
"alg" = "RS256"
"x5t" = $certHash
"kid" = $certHash
}
$jwt = New-JWT -PrivateKey $privateKey_signing -Header $header -Payload $payLoad
return $jwt
}
End
{
Unload-PrivateKey -PrivateKey $privateKey_signing
}
}