mirror of
https://github.com/Gerenios/AADInternals
synced 2026-06-08 11:09:38 +00:00
469 lines
17 KiB
PowerShell
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
|
|
}
|
|
|
|
} |