Files
Nestori ff0457aa95 v0.9.8
2025-04-18 14:41:13 +03:00

2918 lines
101 KiB
PowerShell
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# This script contains common utility functions used in different functions
# Constants
$const_bom = [byte[]]@(0xEF,0xBB,0xBF)
# Unix epoch time (1.1.1970)
$epoch = Get-Date -Day 1 -Month 1 -Year 1970 -Hour 0 -Minute 0 -Second 0 -Millisecond 0
# Configuration settings
$config = @{}
Function Convert-ByteArrayToB64
{
[cmdletbinding()]
param(
[parameter(Mandatory=$true,ValueFromPipeline)]
[Byte[]]$Bytes,
[Switch]$UrlEncode,
[Switch]$NoPadding
)
$b64 = [convert]::ToBase64String($Bytes);
if($UrlEncode)
{
$b64 = $b64.Replace("/","_").Replace("+","-")
}
if($NoPadding -or $UrlEncode)
{
$b64 = $b64.Replace("=","")
}
return $b64
}
Function Convert-B64ToByteArray
{
[cmdletbinding()]
param(
[parameter(Mandatory=$true,ValueFromPipeline)]
[String]
$B64
)
$B64 = $B64.Replace("_","/").Replace("-","+").TrimEnd(0x00,"=")
# Fill the header with padding for Base 64 decoding
while ($B64.Length % 4)
{
$B64 += "="
}
return [convert]::FromBase64String($B64)
}
Function Convert-B64ToText
{
[cmdletbinding()]
param(
[parameter(Mandatory=$true)]
[String]
$B64
)
return [text.encoding]::UTF8.GetString(([byte[]](Convert-B64ToByteArray -B64 $B64)))
}
Function Convert-TextToB64
{
[cmdletbinding()]
param(
[parameter(Mandatory=$true)]
[String]
$Text
)
return Convert-ByteArrayToB64 -Bytes ([text.encoding]::UTF8.GetBytes($text))
}
Function Convert-ByteArrayToHex
{
[cmdletbinding()]
param(
[parameter(Mandatory=$true,ValueFromPipeline)]
[Byte[]]
$Bytes
)
$HexString = [System.Text.StringBuilder]::new($Bytes.Length * 2)
ForEach($byte in $Bytes){
$HexString.AppendFormat("{0:x2}", $byte) | Out-Null
}
$HexString.ToString()
}
Function Convert-HexToByteArray
{
[cmdletbinding()]
param(
[parameter(Mandatory=$true,ValueFromPipeline)]
[String]
$HexString
)
$Bytes = [byte[]]::new($HexString.Length / 2)
For($i=0; $i -lt $HexString.Length; $i+=2){
$Bytes[$i/2] = [convert]::ToByte($HexString.Substring($i, 2), 16)
}
$Bytes
}
# Converts OID string to bytes
function Convert-OidToBytes
{
[cmdletbinding()]
Param(
[Parameter(Mandatory=$True,ValueFromPipeline)]
[String]$Oid
)
Process
{
$digits = $oid.Split(".")
$bytes = @()
# Encode the first byte
$bytes += ([byte]$digits[0]) * 40 + ([byte]$digits[1])
# Calculate the rest
for($pos = 2; $pos -lt $Digits.Count; $pos++)
{
[int]$digit = $digits[$pos]
if($digit -gt 127) # Multiple bytes needed
{
$mbytes=@()
$mbytes += [byte]($digit -band 0x7f)
while($digit -gt 127)
{
$digit = $digit -shr 7
$mbytes += [byte](($digit -band 0x7f) -bor 0x80)
}
for($a = $mbytes.Count -1 ; $a -ge 0 ; $a--)
{
$bytes += [byte]$mbytes[$a]
}
}
else
{
$bytes += [byte]$digit
}
}
# Return
return [byte[]]$bytes
}
}
# Converts byte array to oid string
function Convert-BytesToOid
{
[cmdletbinding()]
Param(
[Parameter(ParameterSetName = "Bytes",Mandatory=$True,ValueFromPipeline)]
[byte[]]$Bytes,
[Parameter(ParameterSetName = "String",Mandatory=$True)]
[String]$ByteString
)
Process
{
if($ByteString)
{
$Bytes = Convert-HexToByteArray -HexString ($ByteString.Replace("0x","").Replace(",","").Replace(" ",""))
}
$pos = 0
# Check whether we have DER tag. If so, skip the first 2 bytes
if($Bytes[0] -eq 0x06)
{
$pos=2
}
# Calculate the first two digits
$oid="$([byte]($Bytes[$pos]/40)).$([byte]$Bytes[$pos]%40)"
# Calculate the rest
for($pos+=1; $pos -lt $Bytes.Count; $pos++)
{
$digit = 0
$mbyte = @()
while (($Bytes[$pos] -band 0x80) -gt 0)
{
$mByte+=($Bytes[$pos])
$pos++
}
if($mByte.Count -gt 0)
{
$mByte += $Bytes[$pos]
for($a = 1; $a -le $mByte.Count ; $a++)
{
$value = $mByte[$a-1] -band 0x7f # Strip the first byte
$value *= [math]::pow(128, $mByte.Count-$a)
$digit += $value
}
}
else
{
$digit = $Bytes[$pos]
}
$oid += ".$digit"
}
# Return
$oid
}
}
# Loads X509 certificate from .pfx file.
function Load-Certificate
{
<#
.SYNOPSIS
Loads X509 certificate from the given .pfx file
.DESCRIPTION
Loads X509 certificate from the given .pfx file
.Parameter FileName
The full path to .pfx file from where to load the certificate
.Parameter Password
The password of the .pfx file
.Parameter Exportable
Whether the private key should be exportable or not.
.Example
PS C:\>Load-AADIntCertificate -FileName "MyCert.pfx" -Password -Password "mypassword"
#>
[cmdletbinding()]
Param(
[Parameter(Mandatory=$True,ValueFromPipeline)]
[String]$FileName,
[Parameter(Mandatory=$False)]
[String]$Password="",
[Switch]$Exportable
)
Process
{
if(!(Test-Path $FileName))
{
throw "Certificate file $FileName not found!"
}
# Load the certificate
try
{
if($Exportable)
{
$Certificate = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2((Get-Item $FileName).FullName, $Password, [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable) -ErrorAction SilentlyContinue
}
else
{
$Certificate = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2((Get-Item $FileName).FullName, $Password, [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::PersistKeySet) -ErrorAction SilentlyContinue
}
}
catch
{
throw "Error opening certificate: $($_.Exception.InnerException.Message)"""
}
return $Certificate
}
}
# Loads the private key from the given Certificate
function Load-PrivateKey
{
<#
.SYNOPSIS
Loads the private key from the given x509 certificate
.DESCRIPTION
Loads the private key from the given x509 certificate
.Example
$Certificate = Load-Certificate -Filename "mycert.pfx" -Password "myverysecretpassword"
PS C:\>$PrivateKey = Load-AADIntPrivateKey -Certificate $Certificate
#>
[cmdletbinding()]
Param(
[Parameter(Mandatory=$True,ValueFromPipeline)]
[System.Security.Cryptography.X509Certificates.X509Certificate2]$Certificate
)
Process
{
# Store the private key to so that it can be exported
$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
$privateKey = [System.Security.Cryptography.RSACryptoServiceProvider]::new(2048,$cspParameters)
$privateKey.ImportParameters([System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPrivateKey($Certificate).ExportParameters($true))
Write-Verbose "Private Key from $($Certificate.Subject) loaded to the certificate store."
Write-Debug "PK: $( Convert-ByteArrayToB64 -Bytes (([System.Security.Cryptography.RSA]::Create($privateKey.ExportParameters($true))).key.Export([System.Security.Cryptography.CngKeyBlobFormat]::GenericPublicBlob)) )"
return $privateKey
}
}
# Unloads the private key from the store
function Unload-PrivateKey
{
<#
.SYNOPSIS
Unloads the private key from the store
.DESCRIPTION
Unloads the private key from the store
.Example
$Certificate = Load-Certificate -Filename "mycert.pfx" -Password "myverysecretpassword"
PS C:\>$privateKey = Load-AADIntPrivateKey -Certificate $Certificate
PS C:\>Unload-AADIntPrivateKey -PrivateKey $privateKey
#>
[cmdletbinding()]
Param(
[Parameter(Mandatory=$True,ValueFromPipeline)]
[System.Security.Cryptography.RSA]$PrivateKey
)
Process
{
try
{
# Remove the private key from the store
$privateKey.PersistKeyInCsp=$false
$privateKey.Clear()
Write-Verbose "Private Key unloaded from the certificate store."
}
catch
{
Write-Verbose "Could not unload Private Key from the certificate store. That's probably just okay: ""$($_.Exception.InnerException.Message)"""
}
}
}
function Get-CompressedByteArray {
[CmdletBinding()]
Param (
[Parameter(Mandatory,ValueFromPipeline,ValueFromPipelineByPropertyName)]
[byte[]] $byteArray = $(Throw("-byteArray is required"))
)
Process {
Write-Verbose "Get-CompressedByteArray"
[System.IO.MemoryStream] $output = New-Object System.IO.MemoryStream
$gzipStream = New-Object System.IO.Compression.GzipStream $output, ([IO.Compression.CompressionMode]::Compress)
$gzipStream.Write( $byteArray, 0, $byteArray.Length )
$gzipStream.Close()
$output.Close()
return $output.ToArray()
}
}
function Get-DecompressedByteArray {
[CmdletBinding()]
Param (
[Parameter(Mandatory,ValueFromPipeline,ValueFromPipelineByPropertyName)]
[byte[]] $byteArray = $(Throw("-byteArray is required"))
)
Process {
Write-Verbose "Get-DecompressedByteArray"
$input = New-Object System.IO.MemoryStream( , $byteArray )
$output = New-Object System.IO.MemoryStream
$gzipStream = New-Object System.IO.Compression.GzipStream $input, ([IO.Compression.CompressionMode]::Decompress)
$gzipStream.CopyTo( $output )
$gzipStream.Close()
$input.Close()
return $output.ToArray()
}
}
function Get-DeflatedByteArray {
[CmdletBinding()]
Param (
[Parameter(Mandatory,ValueFromPipeline,ValueFromPipelineByPropertyName)]
[byte[]] $byteArray = $(Throw("-byteArray is required"))
)
Process {
Write-Verbose "Get-DecompressedByteArray"
$output = New-Object System.IO.MemoryStream
$defStream = New-Object System.IO.Compression.DeflateStream $output, ([IO.Compression.CompressionMode]::Compress)
$defStream.Write( $byteArray, 0, $byteArray.Length )
$defStream.Close()
$output.Close()
return $output.ToArray()
}
}
function Get-DeDeflatedByteArray {
[CmdletBinding()]
Param (
[Parameter(Mandatory,ValueFromPipeline,ValueFromPipelineByPropertyName)]
[byte[]] $byteArray = $(Throw("-byteArray is required"))
)
Process {
Write-Verbose "Get-DecompressedByteArray"
$input = New-Object System.IO.MemoryStream( , $byteArray )
$output = New-Object System.IO.MemoryStream
$defStream = New-Object System.IO.Compression.DeflateStream $input, ([IO.Compression.CompressionMode]::Decompress)
$defStream.CopyTo( $output )
$defStream.Close()
$input.Close()
return $output.ToArray()
}
}
# Parses the given RSA Key BLOB and returns RSAParameters
Function Parse-KeyBLOB
{
[cmdletbinding()]
param(
[parameter(Mandatory=$false,ValueFromPipeline)]
[Byte[]]$Key
)
process
{
if($key -eq $null)
{
return $null
}
# https://docs.microsoft.com/en-us/windows/win32/api/bcrypt/ns-bcrypt-bcrypt_rsakey_blob
# https://docs.microsoft.com/en-us/windows/win32/seccrypto/base-provider-key-blobs
# Parse the header
$magic = [text.encoding]::ASCII.GetString($Key[0..3])
$bitlen = [bitconverter]::ToUInt32($Key,4)
$publen = [bitconverter]::ToUInt32($Key,8)
$modlen = [bitconverter]::ToUInt32($Key,12)
$pri1len = [bitconverter]::ToUInt32($Key,16)
$pri2len = [bitconverter]::ToUInt32($Key,20)
$headerLen = 6* [System.Runtime.InteropServices.Marshal]::SizeOf([uint32]::new())
# BYTE pubexp[publen]
# BYTE modulus[bitlen/8]
# BYTE prime1[bitlen/16]
# BYTE prime2[bitlen/16]
# BYTE exponent1[bitlen/16]
# BYTE exponent2[bitlen/16]
# BYTE coefficient[bitlen/16]
# BYTE privateExponent[bitlen/8]
# Parse RSA1 (RSAPUBLICBLOB)
$p = $headerLen
$pubexp = $Key[$headerLen..($headerLen + $publen - 1)]; $p += $publen
$modulus = $key[($p)..($p-1 + $modlen)]; $p += $modlen
# Parse RSA2 (RSAPRIVATEBLOB)
if($magic -eq "RSA2" -or $magic -eq "RSA3")
{
$prime1 = $key[($p)..($p-1 + $bitlen/16)] ; $p += $bitlen/16
$prime2 = $key[($p)..($p-1 + $bitlen/16)] ; $p += $bitlen/16
}
# Parse RSA3 (RSAFULLPRIVATEBLOB)
if($magic -eq "RSA3")
{
$exponent1 = $key[($p)..($p-1 + $bitlen/16)] ; $p += $bitlen/16
$exponent2 = $key[($p)..($p-1 + $bitlen/16)] ; $p += $bitlen/16
$coefficient = $key[($p)..($p-1 + $bitlen/16)] ; $p += $bitlen/16
$privateExponent = $key[($p)..($p-1 + $bitlen/8)]
}
$attributes=@{
"D" = $privateExponent
"DP" = $exponent1
"DQ" = $exponent2
"Exponent" = $pubexp
"InverseQ" = $coefficient
"Modulus" = $modulus
"P" = $prime1
"Q"= $prime2
}
[System.Security.Cryptography.RSAParameters]$RSAParameters = New-Object psobject -Property $attributes
return $RSAParameters
}
}
# Converts the given RSAParameters to PEM
# Feb 6th 2022
Function Convert-RSAToPEM
{
[cmdletbinding()]
param(
[parameter(Mandatory=$true,ValueFromPipeline)]
[System.Security.Cryptography.RSAParameters]$RSAParameters
)
process
{
$pemWriter = [Org.BouncyCastle.OpenSsl.PemWriter]::new([System.IO.StringWriter]::new())
$pemWriter.WriteObject([Org.BouncyCastle.Security.DotNetUtilities]::GetRsaKeyPair($RSAParameters).Private)
$PEM = $pemWriter.Writer.ToString()
$pemWriter.Writer.Dispose()
return $PEM
}
}
# Converts the given PEM to RSAParameters
# Feb 6th 2022
Function Convert-PEMToRSA
{
[cmdletbinding()]
param(
[parameter(Mandatory=$true,ValueFromPipeline)]
[String]$PEM
)
process
{
$pemReader = [Org.BouncyCastle.OpenSsl.PemReader]::new([System.IO.StringReader]::new($PEM))
$RSA = $pemReader.ReadObject()
# Certificate
if($RSA.GetType().Name -eq "X509Certificate")
{
$RSAParameters = [Org.BouncyCastle.Security.DotNetUtilities]::ToRSAParameters($RSA.GetPublicKey())
}
# Privatekey
else
{
$RSAParameters = [Org.BouncyCastle.Security.DotNetUtilities]::ToRSAParameters($RSA.Private)
}
$pemReader.Reader.Dispose()
return $RSAParameters
}
}
# Gets the error description from AzureAD
# Aug 2nd 2020
Function Get-Error
{
<#
.SYNOPSIS
Gets a error description for the given error code.
.DESCRIPTION
Gets a error description for the given error code.
.Parameter ErrorCode
Azure AD error code
.Example
Get-AADIntError -ErrorCode AADST700019
700019: Application ID {identifier} cannot be used or is not authorized.
.Example
Get-AADIntError -ErrorCode 700019
700019: Application ID {identifier} cannot be used or is not authorized.
#>
[cmdletbinding()]
param(
[parameter(Mandatory=$true,ValueFromPipeline)]
[String]$ErrorCode
)
Process
{
# Get the error message
$response=Invoke-RestMethod -UseBasicParsing -Method Get -Uri "https://login.microsoftonline.com/error?code=$ErrorCode"
$code = Get-StringBetween -String $response -Start '<td>Error Code</td><td>' -End '</td>'
$message = Get-StringBetween -String $response -Start '<td>Message</td><td>' -End '</td>'
return "$code`: $message"
}
}
# Creates a new random SID
# Feb 12th 2021
function New-RandomSID
{
[cmdletbinding()]
param(
[parameter(Mandatory=$False)]
[ValidateSet(0,1,2,3,4,5,7,9,11,12,15,16,18)]
[int]$IdentifierAuthority=5,
[parameter(Mandatory=$False)]
[ValidateSet(18,21,32,64,80,82,83,90,96)]
[int]$SubAuthority=21
)
Process
{
# Create a random SID
# ref: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-azod/ecc7dfba-77e1-4e03-ab99-114b349c7164
# ref: https://en.wikipedia.org/wiki/Security_Identifier
# Identifier Authorities:
# 0 = Null Authority
# 1 = World Authority
# 2 = Local Authority
# 3 = Creator Authority
# 4 = Non-unique Authority
# 5 = NT Authority NT AUTHORITY\
# 7 = Internet$ Internet$\
# 9 = Resource Manager Authority
# 11 = Microsoft Account Authority MicrosoftAccount\
# 12 = Azure Active Directory AzureAD\
# 15 = Capability SIDS
# 16 = MandatoryLabel\
# 18 = Asserted Identity
# Sub Authorities:
# 18 = LocalSystem
# 21 = Domain
# 32 = Users
# 64 = Authentication
# 80 = NT Service
# 82 = IIS AppPool
# 83 = Virtual Machines
# 90 = Window Manager
# 96 = Font Driver
return "S-1-$IdentifierAuthority-$SubAuthority-$(Get-Random -Minimum 1 -Maximum 0x7FFFFFFF)-$(Get-Random -Minimum 1 -Maximum 0x7FFFFFFF)-$(Get-Random -Minimum 1 -Maximum 0x7FFFFFFF)-$(Get-Random -Minimum 1000 -Maximum 9999)"
}
}
# Returns RCA for given key and data
function Get-RC4{
Param(
[Byte[]]$Key,
[Byte[]]$Data
)
Process
{
$nk = New-Object byte[] 256
$s = New-Object byte[] 256
for ($i = 0; $i -lt 256; $i++)
{
$nk[$i] = $Key[($i % $Key.Length)]
$s[$i] = [byte]$i
}
$j = 0
for ($i = 0; $i -lt 256; $i++)
{
$j = ($j + $s[$i] + $nk[$i]) % 256
$swap = $s[$i]
$s[$i] = $s[$j]
$s[$j] = $swap
}
$output = New-Object byte[] ($Data.Length)
$i = 0
$j = 0
for ($c = 0; $c -lt $data.Length; $c++)
{
$i = ($i + 1) % 256
$j = ($j + $s[$i]) % 256
$swap = $s[$i];
$s[$i] = $s[$j];
$s[$j] = $swap;
$k = $s[(($s[$i] + $s[$j]) % 256)]
$keyed = $data[$c] -bxor $k
$output[$c] = [byte]$keyed
}
return $output
}
}
function Parse-Asn1
{
Param(
[Parameter(Mandatory=$True)]
[byte[]]$Data,
[Parameter(Mandatory=$False)]
[int]$Pos=0,
[Parameter(Mandatory=$False)]
[int]$Level=0
)
Begin
{
}
Process
{
# Must be initialized
[int]$p = $pos
[int]$sBytes = 0
[int]$size = 0
# Get the tag
[int]$tag = $Data[$p]
if(($Data[$p+1] -shr 4) -eq 8) # Get the size
{
# Multibyte
$sBytes = $Data[$p+1] -band 0x0F
for($a = 1 ; $a -le $sBytes; $a++)
{
$size += ($Data[$p+1+$a] * [Math]::Pow(256, $sBytes-$a))
}
$tSize = $size + 2 +$sBytes
}
else
{
$size = $Data[$p+1]
$tSize = $size + 2
}
# Calculate start and end
$start = $p
$end = $p + $tSize - 1
# Move to the start of the data
$p += 2 + $sBytes
if(($tag -shr 4) -eq 0x06) # Application element
{
$appNum = $tag -band 0x0F
$tType = "6{0:X}" -f $appNum
$multiValue = $true
}
elseif(($tag -shr 4) -eq 0x0A) # Sequence element
{
$seqNum = $tag -band 0x0F
$tType = "A{0:X}" -f $seqNum
$multiValue = $true
}
elseif($tag -in 0x10, 0x30)
{
$tType = "SEQUENCE"
$multiValue = $true
}
elseif($tag -in 0x11, 0x31)
{
$tType = "SET"
$multiValue = $true
}
else
{
$multiValue = $false
switch($tag)
{
0x01 {
$tType = "BOOLEAN"
$tData = $Data[$p-1] -ne 0x00
$tValue = $tData
break
}
0x02 {
$tType = "INTEGER"
$tData = 0
for($a = 1 ; $a -le $size; $a++)
{
$tData += $Data[$p-1+$a] * [Math]::Pow(256, $size-$a)
}
$tValue = $tData
break
}
0x03 {
$tType = "BIT STRING"
$tData = $Data[$p..$($p+$size-1)]
$tValue = Convert-ByteArrayToHex -Bytes $tData
break
}
0x04 {
$tType = "OCTET STRING"
Write-Verbose "$((" " * $level ))$tType ($size) $tValue"
$tData = $Data[$p..$($p+$size-1)]
break
}
0x05 {
$tType = "NULL"
$tData = $null
$tValue = $tData
break
}
0x06 {
$tType = "OBJECT IDENTIFIER"
$tData = Convert-BytesToOid -Bytes $Data[$p..$($p+$size-1)]
$tValue = $tData
break
}
0x0A {
$tType = "ENUMERATED"
$tData = 0
for($a = 1 ; $a -le $size; $a++)
{
$tData += $Data[$p-1+$a] * [Math]::Pow(256, $size-$a)
}
$tValue = $tData
break
}
0x13 {
$tType = "PrintableString"
$tData = [text.encoding]::ASCII.GetString($Data[$p..$($p+$size-1)])
$tValue = $tData
break
}
0x16 {
$tType = "IA5String"
$tData = [text.encoding]::ASCII.GetString($Data[$p..$($p+$size-1)])
$tValue = $tData
break
}
0x18 {
$tType = "DATE TIME"
$dStr = [text.encoding]::UTF8.GetString($Data[$p..$($p+$size-1)])
$yyyy = [int]$dStr.Substring(0,4)
$MM = [int]$dStr.Substring(4,2)
$dd = [int]$dStr.Substring(6,2)
$hh = [int]$dStr.Substring(8,2)
$min = [int]$dStr.Substring(10,2)
$ss = [int]$dStr.Substring(12,2)
$tData = [DateTime]"$($yyyy)-$('{0:D2}' -f $MM)-$('{0:D2}' -f $dd)T$('{0:D2}' -f $hh):$('{0:D2}' -f $min):$('{0:D2}' -f $ss)Z"
$tValue = $tData
break
}
0x1B {
$tType = "GENERAL STRING"
$tData = [text.encoding]::UTF8.GetString($Data[$p..$($p+$size-1)])
$tValue = $tData
break
}
0x7B {
$tType = "EncAPRepPart"
Write-Verbose "$((" " * $level ))$tType ($size) $tValue"
try
{
$tData = Parse-Asn1 -Data $Data[$p..$($p+$size-1)] -Level ($Level+1)
}
catch
{
$tData = $Data[$p..$($p+$size-1)]
}
break
}
0x7E {
$tType = "KRB_ERROR"
Write-Verbose "$((" " * $level ))$tType ($size) $tValue"
try
{
$tData = Parse-Asn1 -Data $Data[$p..$($p+$size-1)] -Level ($Level+1)
}
catch
{
$tData = $Data[$p..$($p+$size-1)]
}
break
}
0x80 {
$tType = "APPSPECIFIC"
$tData = $Data[$p..$($p+$size-1)]
break
}
default {
Throw "Unknown TAG 0x$('{0:X}' -f $tag) ($size)"
}
}
}
if($Size -eq 0)
{
$tData = $null
$tValue = $null
}
if(($tag -ne 0x04) -and (($tag -shr 4) -ne 0x07))
{
Write-Verbose "$((" " * $level ))$tType ($size) $tValue"
}
if($multiValue)
{
$tData = @()
While($p -lt $end)
{
$element = Parse-Asn1 -Data $Data -Pos $p -Level ($Level+1)
$p += $element.Size
$tData += $element
}
}
return New-Object psobject -Property @{ "Type" = $tType; "Data" = $tData ; "DataLength" = $size; "Size" = $tSize}
}
}
# Encodes object to Asn1 encoded byte array
# Mar 26th 2021
function Encode-Asn1
{
Param(
[Parameter(Mandatory=$True)]
[psobject]$Data,
[Parameter(Mandatory=$False)]
[int]$Level = 0
)
Begin
{
}
Process
{
$attributes = $Data | get-member | where MemberType -eq "NoteProperty" | select Name
if(!$attributes -or (!"Data","Type" -in $attributes))
{
Throw "Data object doesn't have Data and Type attributes"
}
Write-Verbose "$((" " * $level ))$($Data.Type)"
switch($Data.Type)
{
{$_.startsWith("APP #")}{
$appNum = [byte]$_.Split("#")[1]
$appNum += 0x60
$returnValues = @()
foreach($value in $Data.Data)
{
$returnValues += Encode-Asn1 -Data $value -Level ($Level+1)
}
if($returnValues)
{
return Add-DERTag -Tag $appNum -Data $returnValues
}
break
}
{$_.startsWith("SEQ #")}{
$seqNum = [byte]$_.Split("#")[1]
$seqNum += 0xA0
$returnValues = @()
foreach($value in $Data.Data)
{
$returnValues += Encode-Asn1 -Data $value -Level ($Level+1)
}
if($returnValues)
{
return Add-DERTag -Tag $seqNum -Data $returnValues
}
break
}
"SEQUENCE" {
$returnValues = @()
foreach($value in $Data.Data)
{
$returnValues += Encode-Asn1 -Data $value -Level ($Level+1)
}
if($returnValues)
{
return Add-DERSequence -Data $returnValues
}
break
}
"SET" {
$returnValues = @()
foreach($value in $Data.Data)
{
$returnValues += Encode-Asn1 -Data $value
}
if($returnValues)
{
return Add-DERSet -Data $returnValues
}
break
}
"BOOLEAN" {
return Add-DERBoolean -Value $Data.Data
break
}
"INTEGER" {
return Add-DERInteger -Data ([byte]$Data.Data)
break
}
"ENUMERATED" {
return Add-DERInteger -Data ([byte]$Data.Data)
break
}
"BIT STRING" {
return Add-DERBitString -Data $Data.Data
break
}
"OCTET STRING" {
if($Data.Data -is [System.Array])
{
return Add-DEROctetString -Data $Data.Data
}
else
{
return Add-DEROctetString -Data (Encode-Asn1 -Data $Data.Data -Level ($Level+1))
}
break
}
"NULL" {
return Add-DERNull
break
}
"OBJECT IDENTIFIER" {
return Add-DERObjectIdentifier -ObjectIdentifier $Data.Data
break
}
"GENERAL STRING" {
return Add-DERUtf8String -Text $Data.Data
break
}
"DATE TIME" {
return Add-DERDate -Date $Data.Data
break
}
default {
Throw "Unknown type: $_"
}
}
}
}
# Returns the given number random bytes
function Get-RandomBytes
{
[cmdletbinding()]
Param(
[Parameter(Mandatory=$True)]
[int]$Bytes
)
Process
{
$returnBytes = New-Object byte[] $Bytes
for($c = 0; $c -lt $Bytes ; $c++)
{
$returnBytes[$c] = Get-Random -Minimum 0 -Maximum 0xFF
}
return $returnBytes
}
}
# Computes an SHA1 digest for the given data
function Get-Digest
{
[cmdletbinding()]
Param(
[Parameter(Mandatory=$True)]
[PSObject]$Data
)
Process
{
if($Data -is [String])
{
$binData = [text.encoding]::UTF8.GetBytes([String]$Data)
}
elseif($Data -is [Byte[]])
{
$binData = $Data
}
else
{
Throw "Data must be either String or ByteArray"
}
# Compute SHA1 digest
$SHA1 = [System.Security.Cryptography.SHA1Managed]::Create()
$digest = $SHA1.ComputeHash($binData)
$SHA1.Dispose()
return $digest
}
}
# Creates a new random SID
# May 31st 2021
function New-RandomIPv4
{
[cmdletbinding()]
param(
)
Process
{
return "$(Get-Random -Minimum 0 -Maximum 255).$(Get-Random -Minimum 0 -Maximum 255).$(Get-Random -Minimum 0 -Maximum 255).$(Get-Random -Minimum 0 -Maximum 255)"
}
}
# Create (or use cached) XML dictionary
function Get-XmlDictionary
{
[cmdletbinding()]
Param(
[Parameter(Mandatory=$False)]
[ValidateSet('WCF','Session')]
[String]$Type="WCF"
)
Begin
{
# Create dictionaries array
$dictionaries = @{
"WCF" = New-Object System.Xml.XmlDictionary
"Session" = New-Object System.Xml.XmlDictionary
}
# Dictionary for WCF binary xml
foreach($element in @("mustUnderstand", "Envelope", "http://www.w3.org/2003/05/soap-envelope", "http://www.w3.org/2005/08/addressing", "Header", "Action", "To", "Body", "Algorithm", "RelatesTo", "http://www.w3.org/2005/08/addressing/anonymous", "URI", "Reference", "MessageID", "Id", "Identifier", "http://schemas.xmlsoap.org/ws/2005/02/rm", "Transforms", "Transform", "DigestMethod", "DigestValue", "Address", "ReplyTo", "SequenceAcknowledgement", "AcknowledgementRange", "Upper", "Lower", "BufferRemaining", "http://schemas.microsoft.com/ws/2006/05/rm", "http://schemas.xmlsoap.org/ws/2005/02/rm/SequenceAcknowledgement", "SecurityTokenReference", "Sequence", "MessageNumber", "http://www.w3.org/2000/09/xmldsig#", "http://www.w3.org/2000/09/xmldsig#enveloped-signature", "KeyInfo", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd", "http://www.w3.org/2001/04/xmlenc#", "http://schemas.xmlsoap.org/ws/2005/02/sc", "DerivedKeyToken", "Nonce", "Signature", "SignedInfo", "CanonicalizationMethod", "SignatureMethod", "SignatureValue", "DataReference", "EncryptedData", "EncryptionMethod", "CipherData", "CipherValue", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd", "Security", "Timestamp", "Created", "Expires", "Length", "ReferenceList", "ValueType", "Type", "EncryptedHeader", "http://docs.oasis-open.org/wss/oasis-wss-wssecurity-secext-1.1.xsd", "RequestSecurityTokenResponseCollection", "http://schemas.xmlsoap.org/ws/2005/02/trust", "http://schemas.xmlsoap.org/ws/2005/02/trust#BinarySecret", "http://schemas.microsoft.com/ws/2006/02/transactions", "s", "Fault", "MustUnderstand", "role", "relay", "Code", "Reason", "Text", "Node", "Role", "Detail", "Value", "Subcode", "NotUnderstood", "qname", "", "From", "FaultTo", "EndpointReference", "PortType", "ServiceName", "PortName", "ReferenceProperties", "RelationshipType", "Reply", "a", "http://schemas.xmlsoap.org/ws/2006/02/addressingidentity", "Identity", "Spn", "Upn", "Rsa", "Dns", "X509v3Certificate", "http://www.w3.org/2005/08/addressing/fault", "ReferenceParameters", "IsReferenceParameter", "http://www.w3.org/2005/08/addressing/reply", "http://www.w3.org/2005/08/addressing/none", "Metadata", "http://schemas.xmlsoap.org/ws/2004/08/addressing", "http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous", "http://schemas.xmlsoap.org/ws/2004/08/addressing/fault", "http://schemas.xmlsoap.org/ws/2004/06/addressingex", "RedirectTo", "Via", "http://www.w3.org/2001/10/xml-exc-c14n#", "PrefixList", "InclusiveNamespaces", "ec", "SecurityContextToken", "Generation", "Label", "Offset", "Properties", "Cookie", "wsc", "http://schemas.xmlsoap.org/ws/2004/04/sc", "http://schemas.xmlsoap.org/ws/2004/04/security/sc/dk", "http://schemas.xmlsoap.org/ws/2004/04/security/sc/sct", "http://schemas.xmlsoap.org/ws/2004/04/security/trust/RST/SCT", "http://schemas.xmlsoap.org/ws/2004/04/security/trust/RSTR/SCT", "RenewNeeded", "BadContextToken", "c", "http://schemas.xmlsoap.org/ws/2005/02/sc/dk", "http://schemas.xmlsoap.org/ws/2005/02/sc/sct", "http://schemas.xmlsoap.org/ws/2005/02/trust/RST/SCT", "http://schemas.xmlsoap.org/ws/2005/02/trust/RSTR/SCT", "http://schemas.xmlsoap.org/ws/2005/02/trust/RST/SCT/Renew", "http://schemas.xmlsoap.org/ws/2005/02/trust/RSTR/SCT/Renew", "http://schemas.xmlsoap.org/ws/2005/02/trust/RST/SCT/Cancel", "http://schemas.xmlsoap.org/ws/2005/02/trust/RSTR/SCT/Cancel", "http://www.w3.org/2001/04/xmlenc#aes128-cbc", "http://www.w3.org/2001/04/xmlenc#kw-aes128", "http://www.w3.org/2001/04/xmlenc#aes192-cbc", "http://www.w3.org/2001/04/xmlenc#kw-aes192", "http://www.w3.org/2001/04/xmlenc#aes256-cbc", "http://www.w3.org/2001/04/xmlenc#kw-aes256", "http://www.w3.org/2001/04/xmlenc#des-cbc", "http://www.w3.org/2000/09/xmldsig#dsa-sha1", "http://www.w3.org/2001/10/xml-exc-c14n#WithComments", "http://www.w3.org/2000/09/xmldsig#hmac-sha1", "http://www.w3.org/2001/04/xmldsig-more#hmac-sha256", "http://schemas.xmlsoap.org/ws/2005/02/sc/dk/p_sha1", "http://www.w3.org/2001/04/xmlenc#ripemd160", "http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p", "http://www.w3.org/2000/09/xmldsig#rsa-sha1", "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256", "http://www.w3.org/2001/04/xmlenc#rsa-1_5", "http://www.w3.org/2000/09/xmldsig#sha1", "http://www.w3.org/2001/04/xmlenc#sha256", "http://www.w3.org/2001/04/xmlenc#sha512", "http://www.w3.org/2001/04/xmlenc#tripledes-cbc", "http://www.w3.org/2001/04/xmlenc#kw-tripledes", "http://schemas.xmlsoap.org/2005/02/trust/tlsnego#TLS_Wrap", "http://schemas.xmlsoap.org/2005/02/trust/spnego#GSS_Wrap", "http://schemas.microsoft.com/ws/2006/05/security", "dnse", "o", "Password", "PasswordText", "Username", "UsernameToken", "BinarySecurityToken", "EncodingType", "KeyIdentifier", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#HexBinary", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Text", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509SubjectKeyIdentifier", "http://docs.oasis-open.org/wss/oasis-wss-kerberos-token-profile-1.1#GSS_Kerberosv5_AP_REQ", "http://docs.oasis-open.org/wss/oasis-wss-kerberos-token-profile-1.1#GSS_Kerberosv5_AP_REQ1510", "http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.0#SAMLAssertionID", "Assertion", "urn:oasis:names:tc:SAML:1.0:assertion", "http://docs.oasis-open.org/wss/oasis-wss-rel-token-profile-1.0.pdf#license", "FailedAuthentication", "InvalidSecurityToken", "InvalidSecurity", "k", "SignatureConfirmation", "TokenType", "http://docs.oasis-open.org/wss/oasis-wss-soap-message-security-1.1#ThumbprintSHA1", "http://docs.oasis-open.org/wss/oasis-wss-soap-message-security-1.1#EncryptedKey", "http://docs.oasis-open.org/wss/oasis-wss-soap-message-security-1.1#EncryptedKeySHA1", "http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV1.1", "http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV2.0", "http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLID", "AUTH-HASH", "RequestSecurityTokenResponse", "KeySize", "RequestedTokenReference", "AppliesTo", "Authenticator", "CombinedHash", "BinaryExchange", "Lifetime", "RequestedSecurityToken", "Entropy", "RequestedProofToken", "ComputedKey", "RequestSecurityToken", "RequestType", "Context", "BinarySecret", "http://schemas.xmlsoap.org/ws/2005/02/trust/spnego", " http://schemas.xmlsoap.org/ws/2005/02/trust/tlsnego", "wst", "http://schemas.xmlsoap.org/ws/2004/04/trust", "http://schemas.xmlsoap.org/ws/2004/04/security/trust/RST/Issue", "http://schemas.xmlsoap.org/ws/2004/04/security/trust/RSTR/Issue", "http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue", "http://schemas.xmlsoap.org/ws/2004/04/security/trust/CK/PSHA1", "http://schemas.xmlsoap.org/ws/2004/04/security/trust/SymmetricKey", "http://schemas.xmlsoap.org/ws/2004/04/security/trust/Nonce", "KeyType", "http://schemas.xmlsoap.org/ws/2004/04/trust/SymmetricKey", "http://schemas.xmlsoap.org/ws/2004/04/trust/PublicKey", "Claims", "InvalidRequest", "RequestFailed", "SignWith", "EncryptWith", "EncryptionAlgorithm", "CanonicalizationAlgorithm", "ComputedKeyAlgorithm", "UseKey", "http://schemas.microsoft.com/net/2004/07/secext/WS-SPNego", "http://schemas.microsoft.com/net/2004/07/secext/TLSNego", "t", "http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Issue", "http://schemas.xmlsoap.org/ws/2005/02/trust/RSTR/Issue", "http://schemas.xmlsoap.org/ws/2005/02/trust/Issue", "http://schemas.xmlsoap.org/ws/2005/02/trust/SymmetricKey", "http://schemas.xmlsoap.org/ws/2005/02/trust/CK/PSHA1", "http://schemas.xmlsoap.org/ws/2005/02/trust/Nonce", "RenewTarget", "CancelTarget", "RequestedTokenCancelled", "RequestedAttachedReference", "RequestedUnattachedReference", "IssuedTokens", "http://schemas.xmlsoap.org/ws/2005/02/trust/Renew", "http://schemas.xmlsoap.org/ws/2005/02/trust/Cancel", "http://schemas.xmlsoap.org/ws/2005/02/trust/PublicKey", "Access", "AccessDecision", "Advice", "AssertionID", "AssertionIDReference", "Attribute", "AttributeName", "AttributeNamespace", "AttributeStatement", "AttributeValue", "Audience", "AudienceRestrictionCondition", "AuthenticationInstant", "AuthenticationMethod", "AuthenticationStatement", "AuthorityBinding", "AuthorityKind", "AuthorizationDecisionStatement", "Binding", "Condition", "Conditions", "Decision", "DoNotCacheCondition", "Evidence", "IssueInstant", "Issuer", "Location", "MajorVersion", "MinorVersion", "NameIdentifier", "Format", "NameQualifier", "Namespace", "NotBefore", "NotOnOrAfter", "saml", "Statement", "Subject", "SubjectConfirmation", "SubjectConfirmationData", "ConfirmationMethod", "urn:oasis:names:tc:SAML:1.0:cm:holder-of-key", "urn:oasis:names:tc:SAML:1.0:cm:sender-vouches", "SubjectLocality", "DNSAddress", "IPAddress", "SubjectStatement", "urn:oasis:names:tc:SAML:1.0:am:unspecified", "xmlns", "Resource", "UserName", "urn:oasis:names:tc:SAML:1.1:nameid-format:WindowsDomainQualifiedName", "EmailName", "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", "u", "ChannelInstance", "http://schemas.microsoft.com/ws/2005/02/duplex", "Encoding", "MimeType", "CarriedKeyName", "Recipient", "EncryptedKey", "KeyReference", "e", "http://www.w3.org/2001/04/xmlenc#Element", "http://www.w3.org/2001/04/xmlenc#Content", "KeyName", "MgmtData", "KeyValue", "RSAKeyValue", "Modulus", "Exponent", "X509Data", "X509IssuerSerial", "X509IssuerName", "X509SerialNumber", "X509Certificate", "AckRequested", "http://schemas.xmlsoap.org/ws/2005/02/rm/AckRequested", "AcksTo", "Accept", "CreateSequence", "http://schemas.xmlsoap.org/ws/2005/02/rm/CreateSequence", "CreateSequenceRefused", "CreateSequenceResponse", "http://schemas.xmlsoap.org/ws/2005/02/rm/CreateSequenceResponse", "FaultCode", "InvalidAcknowledgement", "LastMessage", "http://schemas.xmlsoap.org/ws/2005/02/rm/LastMessage", "LastMessageNumberExceeded", "MessageNumberRollover", "Nack", "netrm", "Offer", "r", "SequenceFault", "SequenceTerminated", "TerminateSequence", "http://schemas.xmlsoap.org/ws/2005/02/rm/TerminateSequence", "UnknownSequence", "http://schemas.microsoft.com/ws/2006/02/tx/oletx", "oletx", "OleTxTransaction", "PropagationToken", "http://schemas.xmlsoap.org/ws/2004/10/wscoor", "wscoor", "CreateCoordinationContext", "CreateCoordinationContextResponse", "CoordinationContext", "CurrentContext", "CoordinationType", "RegistrationService", "Register", "RegisterResponse", "ProtocolIdentifier", "CoordinatorProtocolService", "ParticipantProtocolService", "http://schemas.xmlsoap.org/ws/2004/10/wscoor/CreateCoordinationContext", "http://schemas.xmlsoap.org/ws/2004/10/wscoor/CreateCoordinationContextResponse", "http://schemas.xmlsoap.org/ws/2004/10/wscoor/Register", "http://schemas.xmlsoap.org/ws/2004/10/wscoor/RegisterResponse", "http://schemas.xmlsoap.org/ws/2004/10/wscoor/fault", "ActivationCoordinatorPortType", "RegistrationCoordinatorPortType", "InvalidState", "InvalidProtocol", "InvalidParameters", "NoActivity", "ContextRefused", "AlreadyRegistered", "http://schemas.xmlsoap.org/ws/2004/10/wsat", "wsat", "http://schemas.xmlsoap.org/ws/2004/10/wsat/Completion", "http://schemas.xmlsoap.org/ws/2004/10/wsat/Durable2PC", "http://schemas.xmlsoap.org/ws/2004/10/wsat/Volatile2PC", "Prepare", "Prepared", "ReadOnly", "Commit", "Rollback", "Committed", "Aborted", "Replay", "http://schemas.xmlsoap.org/ws/2004/10/wsat/Commit", "http://schemas.xmlsoap.org/ws/2004/10/wsat/Rollback", "http://schemas.xmlsoap.org/ws/2004/10/wsat/Committed", "http://schemas.xmlsoap.org/ws/2004/10/wsat/Aborted", "http://schemas.xmlsoap.org/ws/2004/10/wsat/Prepare", "http://schemas.xmlsoap.org/ws/2004/10/wsat/Prepared", "http://schemas.xmlsoap.org/ws/2004/10/wsat/ReadOnly", "http://schemas.xmlsoap.org/ws/2004/10/wsat/Replay", "http://schemas.xmlsoap.org/ws/2004/10/wsat/fault", "CompletionCoordinatorPortType", "CompletionParticipantPortType", "CoordinatorPortType", "ParticipantPortType", "InconsistentInternalState", "mstx", "Enlistment", "protocol", "LocalTransactionId", "IsolationLevel", "IsolationFlags", "Description", "Loopback", "RegisterInfo", "ContextId", "TokenId", "AccessDenied", "InvalidPolicy", "CoordinatorRegistrationFailed", "TooManyEnlistments", "Disabled", "ActivityId", "http://schemas.microsoft.com/2004/09/ServiceModel/Diagnostics", "http://docs.oasis-open.org/wss/oasis-wss-kerberos-token-profile-1.1#Kerberosv5APREQSHA1", "http://schemas.xmlsoap.org/ws/2002/12/policy", "FloodMessage", "LinkUtility", "Hops", "http://schemas.microsoft.com/net/2006/05/peer/HopCount", "PeerVia", "http://schemas.microsoft.com/net/2006/05/peer", "PeerFlooder", "PeerTo", "http://schemas.microsoft.com/ws/2005/05/routing", "PacketRoutable", "http://schemas.microsoft.com/ws/2005/05/addressing/none", "http://schemas.microsoft.com/ws/2005/05/envelope/none", "http://www.w3.org/2001/XMLSchema-instance", "http://www.w3.org/2001/XMLSchema", "nil", "type", "char", "boolean", "byte", "unsignedByte", "short", "unsignedShort", "int", "unsignedInt", "long", "unsignedLong", "float", "double", "decimal", "dateTime", "string", "base64Binary", "anyType", "duration", "guid", "anyURI", "QName", "time", "date", "hexBinary", "gYearMonth", "gYear", "gMonthDay", "gDay"))
{
$dictionaries["WCF"].Add($element) | Out-Null
}
# Dictionary for Identity Claims Session binary xml
foreach($element in @("Claim","SecurityContextToken","Version","SecureConversationVersion","Issuer","OriginalIssuer","IssuerRef","ClaimCollection","Actor","ClaimProperty","ClaimProperties","Value","ValueType","Label","Type","subjectID","ClaimPropertyName","ClaimPropertyValue","http://www.w3.org/2005/08/addressing/anonymous","http://schemas.xmlsoap.org/ws/2005/05/identity/issuer/self","AuthenticationType","NameClaimType","RoleClaimType","Null", [string]::Empty,"Key","EffectiveTime","ExpiryTime","KeyGeneration","KeyEffectiveTime","KeyExpiryTime","SessionId","Id","ValidFrom","ValidTo","ContextId","SessionToken","SessionTokenCookie","BootStrapToken","Context","ClaimsPrincipal","WindowsPrincipal","WindowIdentity","Identity","Identities","WindowsLogonName","PersistentTrue","SctAuthorizationPolicy","Right","EndpointId","WindowsSidClaim","DenyOnlySidClaim","X500DistinguishedNameClaim","X509ThumbprintClaim","NameClaim","DnsClaim","RsaClaim","MailAddressClaim","SystemClaim","HashClaim","SpnClaim","UpnClaim","UrlClaim","Sid","SessionModeTrue"))
{
$dictionaries["Session"].Add($element) | Out-Null
}
}
Process
{
return $dictionaries[$Type]
}
}
# Converts binary xml to XML
function BinaryToXml
{
[cmdletbinding()]
Param(
[Parameter(Mandatory=$True)]
[byte[]]$xml_bytes,
[Parameter(Mandatory=$True)]
[System.Xml.XmlDictionary]$Dictionary
)
Process
{
$xml_doc = New-Object System.Xml.XmlDocument
[System.Xml.XmlDictionaryReader]$reader = [System.Xml.XmlDictionaryReader]::CreateBinaryReader($xml_bytes,0,$xml_bytes.Length,$Dictionary,[System.Xml.XmlDictionaryReaderQuotas]::Max)
$xml_doc.Load($reader)
return $xml_doc
}
}
# Converts Xml to Binary format
function XmlToBinary
{
[cmdletbinding()]
Param(
[Parameter(Mandatory=$True)]
[xml]$xml_doc,
[Parameter(Mandatory=$True)]
[System.Xml.XmlDictionary]$Dictionary
)
Process
{
$ms = New-Object System.IO.MemoryStream
$writer = [System.Xml.XmlDictionaryWriter]::CreateBinaryWriter($ms,$Dictionary)
$xml_doc.WriteContentTo($writer);
$writer.Flush()
$ms.Position = 0;
$length=$ms.Length
[byte[]]$xml_bytes = New-Object Byte[] $length
$ms.Flush()
$ms.Read($xml_bytes, 0, $length) | Out-Null
$ms.Dispose()
return $xml_bytes
}
}
function Remove-BOM
{
[cmdletbinding()]
Param(
[Parameter(Mandatory=$True)]
[Byte[]]$ByteArray
)
Process
{
if(Compare-Object -ReferenceObject $const_bom -DifferenceObject $ByteArray[0..2] -SyncWindow 0)
{
return $ByteArray
}
else
{
return $ByteArray[3..($ByteArray.length-1)]
}
}
}
# removes the given bytes from the given bytearray
function Remove-Bytes
{
[cmdletbinding()]
Param(
[Parameter(Mandatory=$True)]
[Byte[]]$ByteArray,
[Parameter(Mandatory=$True)]
[Byte[]]$BytesToRemove
)
Process
{
$retVal = @()
for($i = 0; $i -le $ByteArray.Count; $i++)
{
$AddByte=$true
for($b = 0; $b -le $BytesToRemove.Count; $b++)
{
$ByteToRemove = $BytesToRemove[$b]
if($ByteArray[$i] -eq $ByteToRemove)
{
$AddByte=$false
}
}
if($AddByte)
{
$retVal+=$ByteArray[$i]
}
}
$retVal
}
}
# Splits the given string to the given line lenght using the given separator
# Dec 17th 2021
function Split-String
{
[CmdletBinding()]
param(
[Parameter(Mandatory=$true)]
[string]$String,
[Parameter(Mandatory=$false)]
[int]$LineLength = 64,
[Parameter(Mandatory=$false)]
[string]$Separator = "`n"
)
Process
{
$retVal = ""
$p = 0
while($p -lt $String.Length)
{
if($String.Length - $p -lt $LineLength)
{
$retVal += $String.Substring($p)
break
}
else
{
$retVal += $String.Substring($p, $LineLength)
$retVal += $Separator
$p += $LineLength
}
}
return $retVal
}
}
# Creates a new RSA keyBLOB from the given RSAParameters
# Dec 19th 2021
Function New-KeyBLOB
{
[cmdletbinding()]
param(
[parameter(Mandatory=$true,ValueFromPipeline)]
[System.Security.Cryptography.RSAParameters]$Parameters,
[Parameter(Mandatory=$True)]
[ValidateSet('RSA1','RSA2','RSA3')]
[String]$Type
)
process
{
# Set the size information
$bitlen = $Parameters.Modulus.Length * 8
$pubLen = $Parameters.Exponent.Length
$modlen = $Parameters.Modulus.Length
$pri1len = 0
$pri2len = 0
# Calculate the needed blob size for RSA1 (RSAPUBLICBLOB)
$headerLen = 6 * [System.Runtime.InteropServices.Marshal]::SizeOf([uint32]::new())
$blobLen = $headerLen + $pubLen + $modLen
# Check the parameters and choose the type accordingly
if($Type -eq "RSA3" -and (!$Parameters.DP -or !$Parameters.DQ -or !$Parameters.InverseQ -or !$Parameters.D))
{
Write-Warning "No parameters for RSA3, creating RSA2"
$Type = "RSA2"
}
if($Type -eq "RSA2" -and (!$Parameters.P -or !$Parameters.D))
{
Write-Warning "No parameters for RSA2, creating RSA1"
$Type = "RSA1"
}
# If RSA2 or RSA3, set the P & Q lenghts
if($Type -ne "RSA1")
{
$pri1len = $Parameters.P.Length
$pri2len = $Parameters.Q.Length
}
# Adjust the total lenght for RSA2 (RSAPRIVATEBLOB)
if($Type -eq "RSA2")
{
$blobLen += $modLen
}
# Adjust the total lenght for RSA3 (RSAFULLPRIVATEBLOB)
if($Type -eq "RSA3")
{
$blobLen += $modLen + (5 * $modlen/2)
}
# Create the blob
$blob = New-Object byte[] $blobLen
$magic = [text.encoding]::ASCII.GetBytes($Type)
$p = 0
# Set the magic and size information
[Array]::Copy($magic, 0, $blob, $p, 4); $p += 4
[Array]::Copy([bitconverter]::GetBytes([UInt32]$bitLen) , 0, $blob, $p, 4); $p += 4
[Array]::Copy([bitconverter]::GetBytes([UInt32]$pubLen) , 0, $blob, $p, 4); $p += 4
[Array]::Copy([bitconverter]::GetBytes([UInt32]$modLen) , 0, $blob, $p, 4); $p += 4
[Array]::Copy([bitconverter]::GetBytes([UInt32]$pri1len), 0, $blob, $p, 4); $p += 4
[Array]::Copy([bitconverter]::GetBytes([UInt32]$pri2len), 0, $blob, $p, 4); $p += 4
# Set the public exponent and modulus
[Array]::Copy($Parameters.Exponent, 0, $blob, $p, $pubLen) ; $p += $pubLen
[Array]::Copy($Parameters.Modulus , 0, $blob, $p, $modLen) ; $p += $modLen
# Set the private parameters for RSA2 & RSA3
if($Type -eq "RSA2" -or $Type -eq "RSA3")
{
[Array]::Copy($Parameters.P , 0, $blob, $p, $pri1len) ; $p += $pri1len
[Array]::Copy($Parameters.Q , 0, $blob, $p, $pri2len) ; $p += $pri2len
}
# Set the private parameters for RSA3
if($Type -eq "RSA3")
{
[Array]::Copy($Parameters.DP , 0, $blob, $p, $pri1len) ; $p += $pri1len
[Array]::Copy($Parameters.DQ , 0, $blob, $p, $pri2len) ; $p += $pri2len
[Array]::Copy($Parameters.InverseQ , 0, $blob, $p, $pri2len) ; $p += $pri2len
[Array]::Copy($Parameters.D , 0, $blob, $p, $modLen)
}
return $blob
}
}
# Creates a new pfx file from the given certificate and private key (RSAParameters)
# Feb 6th 2022
Function New-PfxFile
{
[cmdletbinding()]
param(
[parameter(Mandatory=$true)]
[System.Security.Cryptography.RSAParameters]$RSAParameters,
[parameter(Mandatory=$true)]
[byte[]]$X509Certificate
)
Begin
{
Add-Type -path "$PSScriptRoot\BouncyCastle.Crypto.dll"
}
Process
{
# Create X509 and private key entries
$x509entry = [Org.BouncyCastle.Pkcs.X509CertificateEntry]::new([Org.BouncyCastle.X509.X509Certificate ]::new($X509Certificate))
$privateKeyEntry = [Org.BouncyCastle.Pkcs.AsymmetricKeyEntry ]::new([Org.BouncyCastle.Security.DotNetUtilities]::GetRsaKeyPair($RSAParameters).Private)
# Create a PKCS12 store and add entries
$pkcsStore = [Org.BouncyCastle.Pkcs.Pkcs12StoreBuilder]::new().Build()
$pkcsStore.SetKeyEntry($null,$privateKeyEntry,$x509entry)
# Export as byte array
$stream = [System.IO.MemoryStream]::new()
$pkcsStore.Save($stream,$null,[Org.BouncyCastle.Security.SecureRandom]::new())
$pfxFile = $stream.ToArray()
$stream.Dispose()
# Return
return $pfxFile
}
}
# Gets a substring from a string between given "tags"
# May 23rd 2022
Function Get-Substring
{
[cmdletbinding()]
param(
[parameter(Mandatory=$true,ValueFromPipeline)]
[string]$String,
[parameter(Mandatory=$true,ValueFromPipeline)]
[string]$Start,
[parameter(Mandatory=$true,ValueFromPipeline)]
[string]$End
)
process
{
$s = $String.IndexOf($Start) + $Start.Length
if($s -lt 0)
{
return
}
$e = $String.IndexOf($End,$s)
if($e -lt 0)
{
return
}
return $String.Substring($s,$e-$s)
}
}
# Checks whether the multi-byte integer has more bytes
function Check-ContinuationBit
{
Param(
[Parameter(Mandatory=$True)]
[byte]$byteVal
)
[byte] $continuationBitmask = 0x80;
return ($continuationBitmask -band $byteval) -ne 0
}
# Encodes integer as multi-byte integer
function Encode-MultiByteInteger
{
param(
[parameter(Mandatory=$true)]
[int]$value
)
Process
{
# If integer is 0, just return that
if($value -eq 0)
{
return 0
}
$byteList = @()
$shiftedValue = $value;
while ($value -gt 0)
{
$addByte = [byte]($value -band 0x7F)
if ($byteList.Count -gt 0)
{
$addByte = $addByte -bor 0x80
}
$newList = @()
$newList += $addByte
$newList += $byteList
$byteList = $newList
$value = $value -shr 7;
}
return $byteList
}
}
# Decodes multi-byte integer from the given byte array
# Sep 29th 2022
function Decode-MultiByteInteger
{
param(
[parameter(Mandatory=$true)]
[byte[]]$Data,
[parameter(Mandatory=$true)]
[ref]$Position,
[parameter(Mandatory=$false)]
[switch]$Reverse,
[parameter(Mandatory=$false)]
[switch]$Google
)
Process
{
$p = $Position.Value
$nBytes = 1
$bytes = New-Object Byte[] 8
if($Google)
{
# Ref: https://developers.google.com/protocol-buffers/docs/encoding#varints
# Strip the continuation bit and add to an array
while((Check-ContinuationBit($Data[$p])) -and $nBytes -lt 8)
{
$bytes[$nBytes-1] = $Data[$p] -band 0x7F
$p++
$nBytes++
}
$bytes[$nBytes-1] = $Data[$p] -band 0x7F
$p++
# Reverse the array
[Array]::Reverse($bytes)
# Shift bits
$n = 7
while($n -gt 8-$nBytes)
{
$shiftedToNext = $bytes[$n-1] -shl $n
$byte = $bytes[$n] -shr 7-$n
$bytes[$n] = $shiftedToNext -bor $byte
$n--
}
$bytes[$n] = $bytes[$n] -shr 7-$n
[Array]::Reverse($bytes)
}
else
{
# Loop until all bytes are handled
while((Check-ContinuationBit($Data[$p])) -and $nBytes -lt 8)
{
# Strip the continuation bit (not really needed as shifting to left)
[byte]$byte = $Data[$p] -band 0x7F
# Shift bits to left 8-$nBytes times
[byte]$shiftedToNext = $byte -shl (8-$nBytes)
# Shift bits to right $nBytes times
$byte = $byte -shr $nBytes
# Add to byte array by binary or as there might be shifted bits
$bytes[$nBytes-1] = $bytes[$nBytes-1] -bor $byte
# Add shifted bits
$bytes[$nBytes] = $shiftedToNext
$nBytes++
$p++
}
# Add to byte array by binary or as there might be shifted bits
$bytes[$nBytes-1] = $bytes[$nBytes-1] -bor $Data[$p]
$p++
}
# Reverse as needed
if($Reverse)
{
$reversedBytes = New-Object Byte[] 8
[Array]::Copy($bytes,0,$reversedBytes,8-$nBytes,$nBytes)
[Array]::Reverse($reversedBytes)
$bytes = $reversedBytes
}
$Position.Value = $p
return [bitconverter]::ToInt64($bytes,0)
}
}
# Gets the content of the given file as byte array
# Sep 30th 2022
function Get-BinaryContent
{
param(
[parameter(Mandatory=$true, ValueFromPipeline, Position=0)]
[string]$Path
)
Process
{
#return [System.IO.File]::ReadAllBytes([System.IO.Path]::GetFullPath($Path))
if($PSVersionTable.PSVersion.Major -ge 6)
{
Get-Content -Path $Path -AsByteStream -Raw -ErrorAction $ErrorActionPreference
}
else
{
Get-Content -Path $Path -Encoding Byte -ErrorAction $ErrorActionPreference
}
}
}
# Sets the content of the given file with given byte array
# Sep 30th 2022
function Set-BinaryContent
{
param(
[parameter(Mandatory=$true, ValueFromPipeline, Position=0)]
[string]$Path,
[parameter(Mandatory=$true, ValueFromPipeline, Position=1)]
[byte[]]$Value
)
Process
{
if($PSVersionTable.PSVersion.Major -ge 6)
{
Set-Content -Path $Path -Value $Value -AsByteStream -ErrorAction $ErrorActionPreference
}
else
{
Set-Content -Path $Path -Value $Value -Encoding Byte -ErrorAction $ErrorActionPreference
}
}
}
# Load the settings from config.json
# May 29th 2023
function Read-Configuration
{
<#
.SYNOPSIS
Loads AADInternals settings
.DESCRIPTION
Loads AADInternals settings from config.json. All changes made after loading AADInternals module will be lost.
.Example
PS C:\>Read-AADIntConfiguration
#>
[cmdletbinding()]
param()
Process
{
# Clear the settings
$Script:config = @{}
# ConvertFrom-Json -AsHashtable not supported in PowerShell 5.1
$configObject = Get-Content -Path "$PSScriptRoot\config.json" | ConvertFrom-Json
foreach($property in $configObject.PSObject.Properties)
{
$Script:config[$property.Name] = $property.Value
}
}
}
# Save the settings to config.json
# May 29th 2023
function Save-Configuration
{
<#
.SYNOPSIS
Saves AADInternals settings
.DESCRIPTION
Saves the current AADInternals settings to config.json. Settings will be loaded when AADInternals module is loaded.
.Example
PS C:\>Save-AADIntConfiguration
#>
[cmdletbinding()]
param()
Process
{
$Script:config | ConvertTo-Json | Set-Content -Path "$PSScriptRoot\config.json"
Write-Host "Settings saved."
}
}
# Shows the configuration
# May 29th 2023
function Get-Configuration
{
<#
.SYNOPSIS
Shows AADInternals settings
.DESCRIPTION
Shows AADInternals settings
.Example
PS C:\>Get-AADIntSettings
Name Value
---- -----
SecurityProtocol Tls12
User-Agent AADInternals
#>
[cmdletbinding()]
param()
Process
{
$Script:config
}
}
# Get AADInternals setting
# May 29th 2023
function Get-Setting
{
[cmdletbinding()]
param(
[parameter(Mandatory=$true, ValueFromPipeline)]
[string]$Setting
)
Process
{
return $Script:config[$Setting]
}
}
# Sets AADInternals setting value
# May 29th 2023
function Set-Setting
{
<#
.SYNOPSIS
Sets the given setting with given value
.DESCRIPTION
Sets the given setting with given value. To persist, use Save-AADIntConfiguration after setting the value.
.Parameter Setting
Name of the setting to be set
.Parameter Value
Value of the setting
.Example
PS C:\>Set-AADIntSetting -Setting "User-Agent" -Value "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36"
.Example
PS C:\>Set-AADIntSetting -Setting "User-Agent" -Value "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36"
PS C:\>Save-AADIntConfiguration
Settings saved.
#>
[cmdletbinding()]
param(
[parameter(Mandatory=$true, ValueFromPipeline, Position=0)]
[string]$Setting,
[parameter(Mandatory=$true, ValueFromPipeline, Position=1)]
[PSObject]$Value
)
Process
{
$Script:config[$Setting] = $value
}
}
# Sets AADInternals User-Agent value
# May 29th 2023
function Set-UserAgent
{
<#
.SYNOPSIS
Sets the User-Agent AADInternals will use in requests.
.DESCRIPTION
Sets a pre configured User-Agent for a specific device that AADInternals will use in requests. Supported devices: 'Windows','MacOS','Linux','iOS','Android','Default'.
To persist, use Save-AADIntConfiguration after setting the User-Agent
.Parameter UserAgent
One of 'Windows','MacOS','Linux','iOS','Android','Default'
.Example
PS C:\>Set-AADIntUserAgent -Device Windows
.Example
PS C:\>Set-AADIntUserAgent -Device Windows
PS C:\>Save-AADIntConfiguration
Settings saved.
#>
[cmdletbinding()]
param(
[parameter(Mandatory=$true)]
[ValidateSet('Windows','MacOS','Linux','iOS','Android','Default')]
[string]$Device
)
Begin
{
$userAgents = @{
"Windows" = "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"
"MacOS" = "Mozilla/5.0 (Macintosh; Intel Mac OS X 13_4)"
"Linux" = "Mozilla/5.0 (X11; Linux x86_64)"
"iOS" = "Mozilla/5.0 (iPhone; CPU iPhone OS 16_5 like Mac OS X)"
"Android" = "Mozilla/5.0 (Linux; Android 10)"
"Default" = "AADInternals"
}
}
Process
{
Set-Setting -Setting "User-Agent" -Value $userAgents[$Device]
}
}
# Gets AADInternals User-Agent value
# Feb 13 2024
function Get-UserAgent
{
[cmdletbinding()]
param()
Process
{
$userAgent = Get-Setting -Setting "User-Agent"
if($userAgent -eq $null)
{
$userAgent = "AADInternals"
}
return $userAgent
}
}
# Return the string between Start and End
# May 29th 2023
function Get-StringBetween
{
[cmdletbinding()]
Param(
[Parameter(Mandatory=$True)]
[String]$String,
[Parameter(Mandatory=$True)]
[String]$Start,
[Parameter(Mandatory=$True)]
[String]$End,
[Parameter(Mandatory=$False)]
[int]$IncludeEndCharacters = 0
)
Process
{
$s = $String.IndexOf($Start)
if($s -gt -1)
{
$e = $String.IndexOf($End,$s + $Start.Length)
if($e -gt $s)
{
$c = $String.Substring($s + $Start.Length,$e-$s-$Start.Length + $IncludeEndCharacters)
}
}
return $c
}
}
# Parses code from the response, either location header or body.
# Jun 9th 2023
Function Parse-CodeFromResponse
{
[cmdletbinding()]
param(
[parameter(Mandatory=$true,ValueFromPipeline)]
[PSObject]$Response
)
process
{
# Parse the code from the Location header
# Location: <redirect_uri>?code=<code>&session_state=<state>
# Try first the location header
$redirect = $Response.Headers["Location"]
if([string]::IsNullOrEmpty($redirect))
{
# Didn't work, so try to parse from the body
Write-Verbose "Location header empty, parsing from body."
# Decode \u0026 to &
$redirect = $response.content.Replace("\u0026","&")
}
if(![string]::IsNullOrEmpty($redirect))
{
# PS versions >= 6 header values are a string array
if($redirect -is [String[]])
{
$redirect = $redirect[0]
}
$authorizationCode = Get-StringBetween -String $redirect -Start 'code=' -End '&'
}
if([string]::IsNullOrEmpty($authorizationCode))
{
Throw "Authorization code not received!"
}
Write-Verbose "Code: $authorizationCode"
return $authorizationCode
}
}
# Prompts for password
# Jun 19th 2023
Function Read-HostPassword
{
[cmdletbinding()]
param(
[parameter(Mandatory=$true,ValueFromPipeline)]
[string]$Prompt
)
process
{
# Use -MaskInput for PowerShell >= 7.1
if( ($PSVersionTable.PSVersion.Major -ge 7) -or
($PSVersionTable.PSVersion.Major -eq 7 -and $PSVersionTable.PSVersion.Minor -ge 1) )
{
$password = Read-Host -Prompt $Prompt -MaskInput
}
else
{
# Use -AsSecureString for PowerShell < 7.1
$securePassword = Read-Host -Prompt $Prompt -AsSecureString
if(!$securePassword)
{
return $null
}
$securePasswordBytes = Convert-HexToByteArray -HexString (ConvertFrom-SecureString $securePassword)
$password = [text.encoding]::Unicode.GetString([Security.Cryptography.ProtectedData]::Unprotect($securePasswordBytes,$null,'CurrentUser'))
}
return $password
}
}
# Reads error stream and returns UTF8 string
# Jun 21st 2023
Function Get-ErrorStreamMessage
{
[cmdletbinding()]
param(
[parameter(Mandatory=$true,ValueFromPipeline)]
[System.IO.MemoryStream]$errorStream
)
process
{
$errorBytes = New-Object byte[] $errorStream.Length
$errorStream.Position = 0
$errorStream.Read($errorBytes,0,$errorStream.Length) | Out-Null
return [text.encoding]::UTF8.GetString($errorBytes)
}
}
# PSVersion aware Invoke-WebRequest
# Jun 27th 2023
Function Invoke-WebRequest2
{
[cmdletbinding()]
param(
[parameter(Mandatory=$true)]
[String]$Uri,
[parameter(Mandatory=$false)]
[String]$Method = "GET",
[parameter(Mandatory=$false)]
[PSObject]$WebSession,
[parameter(Mandatory=$false)]
[PSObject]$Headers,
[parameter(Mandatory=$false)]
[PSObject]$Body,
[parameter(Mandatory=$false)]
[String]$ContentType = "application/x-www-form-urlencoded",
[parameter(Mandatory=$false)]
[int]$MaximumRedirection = 5,
[parameter(Mandatory=$false)]
[String]$SessionVariable,
[parameter(Mandatory=$false)]
[String]$OutFile
)
process
{
$arguments = @{
"UseBasicParsing" = $true
"Uri" = $uri
"Method" = $Method
"MaximumRedirection" = $MaximumRedirection
"ErrorAction" = $ErrorActionPreference
"Headers" = $Headers
"Body" = $body
"ContentType" = $ContentType
"OutFile" = $OutFile
}
if(![string]::IsNullOrEmpty($SessionVariable))
{
$arguments["SessionVariable"] = $SessionVariable
}
elseif($WebSession -ne $null)
{
$arguments["WebSession"] = $WebSession
}
# PSVersions >= 7 set undeclared OutFile as empty string which makes the Invoke-WebRequest fail
if(($PSVersionTable.PSVersion.Major -ge 7) -and ($arguments["OutFile"] -eq ""))
{
$arguments.Remove("OutFile")
}
# PSVersions >= 7 doesn't respect the ErrorAction SilentlyContinue so we need to use SkipHttpErrorCheck
if(($PSVersionTable.PSVersion.Major -ge 7) -and ($ErrorActionPreference -eq "SilentlyContinue"))
{
$arguments["SkipHttpErrorCheck"] = $true
}
Invoke-WebRequest @arguments
}
}
# Aug 23th 2019
# Calculates MD4 hashes from given passwords
function Get-MD4{
PARAM(
[String]$String,
[Byte[]]$bArray,
[Switch]$UpperCase,
[Switch]$AsByteArray # Added by Nestori Syynimaa Aug 23th 2019
)
# Author: Larry.Song@outlook.com
# Reference: https://tools.ietf.org/html/rfc1320
# MD4('abc'):
# a448017aaf21d8525fc10ae87aa6729d UTF-8
# e0fba38268d0ec66ef1cb452d5885e53 Unicode
$Array = [byte[]]@()
if($String)
{
$Array = [System.Text.Encoding]::Unicode.GetBytes($String) # Edited by Nestori Syynimaa Nov 16th 2018
}
if($bArray)
{
$Array = $bArray
}
# padding 100000*** to length 448, last (64 bits / 8) 8 bytes fill with original length
# at least one (512 bits / 8) 64 bytes array
$M = New-Object Byte[] (([math]::Floor($Array.Count/64) + 1) * 64)
# copy original byte array, start from index 0
$Array.CopyTo($M, 0)
# padding bits 1000 0000
$M[$Array.Count] = 0x80
# padding bits 0000 0000 to fill length (448 bits /8) 56 bytes
# Default value is 0 when creating a new byte array, so, no action
# padding message length to the last 64 bits
@([BitConverter]::GetBytes($Array.Count * 8)).CopyTo($M, $M.Count - 8)
# message digest buffer (A,B,C,D)
$A = [Convert]::ToUInt32('0x67452301', 16)
$B = [Convert]::ToUInt32('0xefcdab89', 16)
$C = [Convert]::ToUInt32('0x98badcfe', 16)
$D = [Convert]::ToUInt32('0x10325476', 16)
# There is no unsigned number shift in C#, have to define one.
Add-Type -TypeDefinition @'
public class Shift
{
public static uint Left(uint a, int b)
{
return ((a << b) | (((a >> 1) & 0x7fffffff) >> (32 - b - 1)));
}
}
'@
# define 3 auxiliary functions
function FF([uint32]$X, [uint32]$Y, [uint32]$Z)
{
(($X -band $Y) -bor ((-bnot $X) -band $Z))
}
function GG([uint32]$X, [uint32]$Y, [uint32]$Z)
{
(($X -band $Y) -bor ($X -band $Z) -bor ($Y -band $Z))
}
function HH([uint32]$X, [uint32]$Y, [uint32]$Z){
($X -bxor $Y -bxor $Z)
}
# processing message in one-word blocks
for($i = 0; $i -lt $M.Count; $i += 64)
{
# Save a copy of A/B/C/D
$AA = $A
$BB = $B
$CC = $C
$DD = $D
# Round 1 start
$A = [Shift]::Left(($A + (FF -X $B -Y $C -Z $D) + [BitConverter]::ToUInt32($M[($i + 0)..($i + 3)], 0)) -band [uint32]::MaxValue, 3)
$D = [Shift]::Left(($D + (FF -X $A -Y $B -Z $C) + [BitConverter]::ToUInt32($M[($i + 4)..($i + 7)], 0)) -band [uint32]::MaxValue, 7)
$C = [Shift]::Left(($C + (FF -X $D -Y $A -Z $B) + [BitConverter]::ToUInt32($M[($i + 8)..($i + 11)], 0)) -band [uint32]::MaxValue, 11)
$B = [Shift]::Left(($B + (FF -X $C -Y $D -Z $A) + [BitConverter]::ToUInt32($M[($i + 12)..($i + 15)], 0)) -band [uint32]::MaxValue, 19)
$A = [Shift]::Left(($A + (FF -X $B -Y $C -Z $D) + [BitConverter]::ToUInt32($M[($i + 16)..($i + 19)], 0)) -band [uint32]::MaxValue, 3)
$D = [Shift]::Left(($D + (FF -X $A -Y $B -Z $C) + [BitConverter]::ToUInt32($M[($i + 20)..($i + 23)], 0)) -band [uint32]::MaxValue, 7)
$C = [Shift]::Left(($C + (FF -X $D -Y $A -Z $B) + [BitConverter]::ToUInt32($M[($i + 24)..($i + 27)], 0)) -band [uint32]::MaxValue, 11)
$B = [Shift]::Left(($B + (FF -X $C -Y $D -Z $A) + [BitConverter]::ToUInt32($M[($i + 28)..($i + 31)], 0)) -band [uint32]::MaxValue, 19)
$A = [Shift]::Left(($A + (FF -X $B -Y $C -Z $D) + [BitConverter]::ToUInt32($M[($i + 32)..($i + 35)], 0)) -band [uint32]::MaxValue, 3)
$D = [Shift]::Left(($D + (FF -X $A -Y $B -Z $C) + [BitConverter]::ToUInt32($M[($i + 36)..($i + 39)], 0)) -band [uint32]::MaxValue, 7)
$C = [Shift]::Left(($C + (FF -X $D -Y $A -Z $B) + [BitConverter]::ToUInt32($M[($i + 40)..($i + 43)], 0)) -band [uint32]::MaxValue, 11)
$B = [Shift]::Left(($B + (FF -X $C -Y $D -Z $A) + [BitConverter]::ToUInt32($M[($i + 44)..($i + 47)], 0)) -band [uint32]::MaxValue, 19)
$A = [Shift]::Left(($A + (FF -X $B -Y $C -Z $D) + [BitConverter]::ToUInt32($M[($i + 48)..($i + 51)], 0)) -band [uint32]::MaxValue, 3)
$D = [Shift]::Left(($D + (FF -X $A -Y $B -Z $C) + [BitConverter]::ToUInt32($M[($i + 52)..($i + 55)], 0)) -band [uint32]::MaxValue, 7)
$C = [Shift]::Left(($C + (FF -X $D -Y $A -Z $B) + [BitConverter]::ToUInt32($M[($i + 56)..($i + 59)], 0)) -band [uint32]::MaxValue, 11)
$B = [Shift]::Left(($B + (FF -X $C -Y $D -Z $A) + [BitConverter]::ToUInt32($M[($i + 60)..($i + 63)], 0)) -band [uint32]::MaxValue, 19)
# Round 1 end
# Round 2 start
$A = [Shift]::Left(($A + (GG -X $B -Y $C -Z $D) + [BitConverter]::ToUInt32($M[($i + 0)..($i + 3)], 0) + 0x5A827999) -band [uint32]::MaxValue, 3)
$D = [Shift]::Left(($D + (GG -X $A -Y $B -Z $C) + [BitConverter]::ToUInt32($M[($i + 16)..($i + 19)], 0) + 0x5A827999) -band [uint32]::MaxValue, 5)
$C = [Shift]::Left(($C + (GG -X $D -Y $A -Z $B) + [BitConverter]::ToUInt32($M[($i + 32)..($i + 35)], 0) + 0x5A827999) -band [uint32]::MaxValue, 9)
$B = [Shift]::Left(($B + (GG -X $C -Y $D -Z $A) + [BitConverter]::ToUInt32($M[($i + 48)..($i + 51)], 0) + 0x5A827999) -band [uint32]::MaxValue, 13)
$A = [Shift]::Left(($A + (GG -X $B -Y $C -Z $D) + [BitConverter]::ToUInt32($M[($i + 4)..($i + 7)], 0) + 0x5A827999) -band [uint32]::MaxValue, 3)
$D = [Shift]::Left(($D + (GG -X $A -Y $B -Z $C) + [BitConverter]::ToUInt32($M[($i + 20)..($i + 23)], 0) + 0x5A827999) -band [uint32]::MaxValue, 5)
$C = [Shift]::Left(($C + (GG -X $D -Y $A -Z $B) + [BitConverter]::ToUInt32($M[($i + 36)..($i + 39)], 0) + 0x5A827999) -band [uint32]::MaxValue, 9)
$B = [Shift]::Left(($B + (GG -X $C -Y $D -Z $A) + [BitConverter]::ToUInt32($M[($i + 52)..($i + 55)], 0) + 0x5A827999) -band [uint32]::MaxValue, 13)
$A = [Shift]::Left(($A + (GG -X $B -Y $C -Z $D) + [BitConverter]::ToUInt32($M[($i + 8)..($i + 11)], 0) + 0x5A827999) -band [uint32]::MaxValue, 3)
$D = [Shift]::Left(($D + (GG -X $A -Y $B -Z $C) + [BitConverter]::ToUInt32($M[($i + 24)..($i + 27)], 0) + 0x5A827999) -band [uint32]::MaxValue, 5)
$C = [Shift]::Left(($C + (GG -X $D -Y $A -Z $B) + [BitConverter]::ToUInt32($M[($i + 40)..($i + 43)], 0) + 0x5A827999) -band [uint32]::MaxValue, 9)
$B = [Shift]::Left(($B + (GG -X $C -Y $D -Z $A) + [BitConverter]::ToUInt32($M[($i + 56)..($i + 59)], 0) + 0x5A827999) -band [uint32]::MaxValue, 13)
$A = [Shift]::Left(($A + (GG -X $B -Y $C -Z $D) + [BitConverter]::ToUInt32($M[($i + 12)..($i + 15)], 0) + 0x5A827999) -band [uint32]::MaxValue, 3)
$D = [Shift]::Left(($D + (GG -X $A -Y $B -Z $C) + [BitConverter]::ToUInt32($M[($i + 28)..($i + 31)], 0) + 0x5A827999) -band [uint32]::MaxValue, 5)
$C = [Shift]::Left(($C + (GG -X $D -Y $A -Z $B) + [BitConverter]::ToUInt32($M[($i + 44)..($i + 47)], 0) + 0x5A827999) -band [uint32]::MaxValue, 9)
$B = [Shift]::Left(($B + (GG -X $C -Y $D -Z $A) + [BitConverter]::ToUInt32($M[($i + 60)..($i + 63)], 0) + 0x5A827999) -band [uint32]::MaxValue, 13)
# Round 2 end
# Round 3 start
$A = [Shift]::Left(($A + (HH -X $B -Y $C -Z $D) + [BitConverter]::ToUInt32($M[($i + 0)..($i + 3)], 0) + 0x6ED9EBA1) -band [uint32]::MaxValue, 3)
$D = [Shift]::Left(($D + (HH -X $A -Y $B -Z $C) + [BitConverter]::ToUInt32($M[($i + 32)..($i + 35)], 0) + 0x6ED9EBA1) -band [uint32]::MaxValue, 9)
$C = [Shift]::Left(($C + (HH -X $D -Y $A -Z $B) + [BitConverter]::ToUInt32($M[($i + 16)..($i + 19)], 0) + 0x6ED9EBA1) -band [uint32]::MaxValue, 11)
$B = [Shift]::Left(($B + (HH -X $C -Y $D -Z $A) + [BitConverter]::ToUInt32($M[($i + 48)..($i + 51)], 0) + 0x6ED9EBA1) -band [uint32]::MaxValue, 15)
$A = [Shift]::Left(($A + (HH -X $B -Y $C -Z $D) + [BitConverter]::ToUInt32($M[($i + 8)..($i + 11)], 0) + 0x6ED9EBA1) -band [uint32]::MaxValue, 3)
$D = [Shift]::Left(($D + (HH -X $A -Y $B -Z $C) + [BitConverter]::ToUInt32($M[($i + 40)..($i + 43)], 0) + 0x6ED9EBA1) -band [uint32]::MaxValue, 9)
$C = [Shift]::Left(($C + (HH -X $D -Y $A -Z $B) + [BitConverter]::ToUInt32($M[($i + 24)..($i + 27)], 0) + 0x6ED9EBA1) -band [uint32]::MaxValue, 11)
$B = [Shift]::Left(($B + (HH -X $C -Y $D -Z $A) + [BitConverter]::ToUInt32($M[($i + 56)..($i + 59)], 0) + 0x6ED9EBA1) -band [uint32]::MaxValue, 15)
$A = [Shift]::Left(($A + (HH -X $B -Y $C -Z $D) + [BitConverter]::ToUInt32($M[($i + 4)..($i + 7)], 0) + 0x6ED9EBA1) -band [uint32]::MaxValue, 3)
$D = [Shift]::Left(($D + (HH -X $A -Y $B -Z $C) + [BitConverter]::ToUInt32($M[($i + 36)..($i + 39)], 0) + 0x6ED9EBA1) -band [uint32]::MaxValue, 9)
$C = [Shift]::Left(($C + (HH -X $D -Y $A -Z $B) + [BitConverter]::ToUInt32($M[($i + 20)..($i + 23)], 0) + 0x6ED9EBA1) -band [uint32]::MaxValue, 11)
$B = [Shift]::Left(($B + (HH -X $C -Y $D -Z $A) + [BitConverter]::ToUInt32($M[($i + 52)..($i + 55)], 0) + 0x6ED9EBA1) -band [uint32]::MaxValue, 15)
$A = [Shift]::Left(($A + (HH -X $B -Y $C -Z $D) + [BitConverter]::ToUInt32($M[($i + 12)..($i + 15)], 0) + 0x6ED9EBA1) -band [uint32]::MaxValue, 3)
$D = [Shift]::Left(($D + (HH -X $A -Y $B -Z $C) + [BitConverter]::ToUInt32($M[($i + 44)..($i + 47)], 0) + 0x6ED9EBA1) -band [uint32]::MaxValue, 9)
$C = [Shift]::Left(($C + (HH -X $D -Y $A -Z $B) + [BitConverter]::ToUInt32($M[($i + 28)..($i + 31)], 0) + 0x6ED9EBA1) -band [uint32]::MaxValue, 11)
$B = [Shift]::Left(($B + (HH -X $C -Y $D -Z $A) + [BitConverter]::ToUInt32($M[($i + 60)..($i + 63)], 0) + 0x6ED9EBA1) -band [uint32]::MaxValue, 15)
# Round 3 end
# Increment start
$A = ($A + $AA) -band [uint32]::MaxValue
$B = ($B + $BB) -band [uint32]::MaxValue
$C = ($C + $CC) -band [uint32]::MaxValue
$D = ($D + $DD) -band [uint32]::MaxValue
# Increment end
}
# Output start
$A = ('{0:x8}' -f $A) -ireplace '^(\w{2})(\w{2})(\w{2})(\w{2})$', '$4$3$2$1'
$B = ('{0:x8}' -f $B) -ireplace '^(\w{2})(\w{2})(\w{2})(\w{2})$', '$4$3$2$1'
$C = ('{0:x8}' -f $C) -ireplace '^(\w{2})(\w{2})(\w{2})(\w{2})$', '$4$3$2$1'
$D = ('{0:x8}' -f $D) -ireplace '^(\w{2})(\w{2})(\w{2})(\w{2})$', '$4$3$2$1'
# Output end
if($AsByteArray)
{
return [byte[]]("$A$B$C$D" -replace '..', '0x$&,' -split ',' -ne '')
}
else
{
if($UpperCase)
{
return "$A$B$C$D".ToUpper()
}
else
{
return "$A$B$C$D"
}
}
}
# Sep 25th 2024
# Calculates a thumbprint for the given certificate
function Get-Thumbprint
{
[cmdletbinding()]
Param(
[Parameter(ParameterSetName = "String",Mandatory=$True)]
[String]$Certificate,
[Parameter(ParameterSetName = "Bytes",Mandatory=$True)]
[byte[]]$Bytes
)
if($Bytes -eq $null)
{
if([string]::IsNullOrEmpty($Certificate))
{
return $null
}
else
{
$bytes = [byte[]](Convert-B64ToByteArray -B64 $Certificate)
}
}
return [System.Security.Cryptography.X509Certificates.X509Certificate2]::new($bytes).Thumbprint
}
# Parses the oid values of the given certificate
# Dec 23rd 2021
function Parse-CertificateOIDs
{
[cmdletbinding()]
Param(
[Parameter(Mandatory=$True,ValueFromPipeline)]
[System.Security.Cryptography.X509Certificates.X509Certificate2]$Certificate
)
Process
{
function Get-OidRawValue
{
Param([byte[]]$RawValue)
Process
{
# Quick-and-dirty DER decoder
if($RawValue.Length -gt 3 -and ($RawValue[2] -eq $RawValue.Length-3 ))
{
# 04 81 10 xxx
return $RawValue[3..($RawValue.Length-1)]
}
elseif($RawValue.Length -gt 2 -and ($RawValue[1] -eq $RawValue.Length-2 ))
{
# 04 10 xxx
return $RawValue[2..($RawValue.Length-1)]
}
else
{
return $RawValue
}
}
}
$retVal = New-Object psobject
foreach($ext in $Certificate.Extensions)
{
switch($ext.Oid.Value)
{
#
# Device Certificates
#
"1.2.840.113556.1.5.284.2" {
$retVal | Add-Member -NotePropertyName "DeviceId" -NotePropertyValue ([guid][byte[]](Get-OidRawValue -RawValue $ext.RawData))
break
}
# "The objectGuid of the user object ([MS-ADSC] section 2.268) on the directory server that corresponds to the authenticating user."
# https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-dvrj/850786b9-2525-4047-a5ff-8c3093b46b88
# https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-dvre/76747b5c-06c2-4c73-9207-8ebb6ee891ea
# I.e. the object ID in AAD of the user who joined/registered the device
"1.2.840.113556.1.5.284.3" {
$retVal | Add-Member -NotePropertyName "AuthUserObjectId" -NotePropertyValue ([guid][byte[]](Get-OidRawValue -RawValue $ext.RawData))
break
}
"1.2.840.113556.1.5.284.5" {
$retVal | Add-Member -NotePropertyName "TenantId" -NotePropertyValue ([guid][byte[]](Get-OidRawValue -RawValue $ext.RawData))
break
}
"1.2.840.113556.1.5.284.8" {
# Tenant region
# AF = Africa
# AS = Asia
# AP = Australia/Pasific
# EU = Europe
# ME = Middle East
# NA = North America
# SA = South America
$retVal | Add-Member -NotePropertyName "Region" -NotePropertyValue ([text.encoding]::UTF8.getString([byte[]](Get-OidRawValue -RawValue $ext.RawData)))
break
}
"1.2.840.113556.1.5.284.7" {
# JoinType
# 0 = Registered
# 1 = Joined
$retVal | Add-Member -NotePropertyName "JoinType" -NotePropertyValue ([int]([text.encoding]::UTF8.getString([byte[]](Get-OidRawValue -RawValue $ext.RawData))))
break
}
#
# Web App Proxy certificates
#
"1.3.6.1.4.1.311.82.1"{
$retVal | Add-Member -NotePropertyName "AgentId" -NotePropertyValue ([guid][byte[]](Get-OidRawValue -RawValue $ext.RawData))
break
}
#
# Intune Certificates Ref. https://github.com/ralish/CertUiExts
#
"1.2.840.113556.5.4" {
$retVal | Add-Member -NotePropertyName "IntuneDeviceId" -NotePropertyValue ([guid][byte[]](Get-OidRawValue -RawValue $ext.RawData))
break
}
"1.2.840.113556.5.6" {
$retVal | Add-Member -NotePropertyName "AccountId" -NotePropertyValue ([guid][byte[]](Get-OidRawValue -RawValue $ext.RawData))
break
}
"1.2.840.113556.5.10" {
$retVal | Add-Member -NotePropertyName "AuthUserObjectId" -NotePropertyValue ([guid][byte[]](Get-OidRawValue -RawValue $ext.RawData))
break
}
"1.2.840.113556.5.11" {
$retVal | Add-Member -NotePropertyName "Unknown" -NotePropertyValue ([guid][byte[]](Get-OidRawValue -RawValue $ext.RawData))
break
}
"1.2.840.113556.5.14" {
$retVal | Add-Member -NotePropertyName "TenantId" -NotePropertyValue ([guid][byte[]](Get-OidRawValue -RawValue $ext.RawData))
break
}
"1.2.840.113556.5.24" {
$retVal | Add-Member -NotePropertyName "Hash" -NotePropertyValue (Convert-ByteArrayToHex -bytes ([byte[]](Get-OidRawValue -RawValue $ext.RawData)))
break
}
}
}
return $retVal
}
}
function New-JWT
{
[cmdletbinding()]
Param(
[Parameter(ParameterSetName='PrivateKey', Mandatory=$True)]
[System.Security.Cryptography.RSA]$PrivateKey,
[Parameter(ParameterSetName='Key',Mandatory=$True)]
[Byte[]]$Key,
[Parameter(Mandatory=$True)]
[System.Collections.Specialized.OrderedDictionary]$Header,
[Parameter(Mandatory=$True)]
[System.Collections.Specialized.OrderedDictionary]$Payload
)
Process
{
# Construct the header
$txtHeader = $Header | ConvertTo-Json -Compress
$txtPayload = $Payload | ConvertTo-Json -Compress
# Convert to B64 and strip the padding
$b64Header = Convert-ByteArrayToB64 -Bytes ([text.encoding]::UTF8.getBytes($txtHeader )) -NoPadding
$b64Payload = Convert-ByteArrayToB64 -Bytes ([text.encoding]::UTF8.getBytes($txtPayload)) -NoPadding
# Construct the JWT data to be signed
$binData = [text.encoding]::UTF8.GetBytes(("{0}.{1}" -f $b64Header,$b64Payload))
# Get the signature
$Binsig = Sign-JWT -PrivateKey $PrivateKey -Key $Key -Data $binData
$B64sig = Convert-ByteArrayToB64 -Bytes $Binsig -UrlEncode
# Construct the JWT
$jwt = "{0}.{1}.{2}" -f $b64Header,$b64Payload,$B64sig
# Return
return $jwt
}
}
# Aug 21st 2020
function Sign-JWT
{
[cmdletbinding()]
Param(
[Parameter(Mandatory=$False)]
[System.Security.Cryptography.RSA]$PrivateKey,
[Parameter(Mandatory=$False)]
[Byte[]]$Key,
[Parameter(Mandatory=$True)]
[byte[]]$Data
)
Process
{
if($PrivateKey)
{
# Sign the JWT (RS256)
$signature = $PrivateKey.SignData($Data, [System.Security.Cryptography.HashAlgorithmName]::SHA256, [System.Security.Cryptography.RSASignaturePadding]::Pkcs1)
}
else
{
# Sign the JWT (HS256)
$hmac = New-Object System.Security.Cryptography.HMACSHA256 -ArgumentList @(,$Key)
$signature = $hmac.ComputeHash($Data)
$hmac.Dispose()
}
# Return
return $signature
}
}
# Converts EntraID ObjectID to Security Identifier (SID)
# Dec 3rd 2024
function Convert-ObjectIDtoSID
{
<#
.SYNOPSIS
Converts Entra ID ObjectID to Entra ID SID
.DESCRIPTION
Converts Entra ID ObjectID to Entra ID SID used in Entra ID joined devices and access tokens. The resulting SID always starts with S-1-12-1
.Parameter ObjectID
Entra ID ObjectID
.Example
PS C:\>Convert-AADIntObjectIDtoSID -ObjectID "e06e3596-82c0-4f31-ac5a-439a308e3dc2"
S-1-12-1-3765319062-1328644800-2588105388-3258814000
#>
[cmdletbinding()]
Param(
[Parameter(Mandatory=$True,ValueFromPipeline)]
[GUID]$ObjectID
)
Process
{
# Ref: https://github.com/okieselbach/Intune/blob/master/Convert-AzureAdObjectIdToSid.ps1
$SID = New-Object uint32[] 4
[System.Buffer]::BlockCopy($ObjectID.ToByteArray(), 0, $SID, 0, 16)
return "S-1-12-1-$SID".Replace(' ', '-')
}
}
# Converts EntraID ObjectID to Security Identifier (SID)
# Dec 3rd 2024
function Convert-SIDtoObjectID
{
<#
.SYNOPSIS
Converts Entra ID SID to Entra ID ObjectID
.DESCRIPTION
Converts Entra ID SID used in Entra ID joined devices and access tokens to Entra ID ObjectID. The SID must start with S-1-12-1
.Parameter SID
Entra ID SID
.Example
PS C:\>Convert-AADIntSIDtoObjectID -SID "S-1-12-1-3765319062-1328644800-2588105388-3258814000"
e06e3596-82c0-4f31-ac5a-439a308e3dc2
#>
[cmdletbinding()]
Param(
[Parameter(Mandatory=$True,ValueFromPipeline)]
[string]$SID
)
Process
{
# Ref: https://github.com/okieselbach/Intune/blob/master/Convert-AzureAdSidToObjectId.ps1
$invalidSID = $false
if(-not $SID.StartsWith("S-1-12-1-"))
{
$invalidSID = $true
}
else
{
try
{
$parts = [UInt32[]]$SID.Substring(9).Split('-')
$guid = New-Object Byte[] 16
[System.Buffer]::BlockCopy($parts,0,$guid,0,16)
return [guid]$guid
}
catch
{
$invalidSID = $true
}
}
if($invalidSID)
{
Throw "Invalid SID. SID must start with S-1-12-1"
}
}
}
# Create a new self-signed certificate
# Jan 31st 2021
function New-Certificate
{
<#
.SYNOPSIS
Creates a new self signed certificate.
.DESCRIPTION
Creates a new self signed certificate for the given subject name and returns it as System.Security.Cryptography.X509Certificates.X509Certificate2 or exports directly to .pfx and .cer files.
The certificate is valid for 100 years.
.Parameter SubjectName
The subject name of the certificate, MUST start with CN=
.Parameter Export
Export the certificate (PFX and CER) instead of returning the certificate object. The .pfx file does not have a password.
.Example
PS C:\>$certificate = New-AADIntCertificate -SubjectName "CN=MyCert"
.Example
PS C:\>$certificate = New-AADIntCertificate -SubjectName "CN=MyCert"
PS C:\>$certificate.Export([System.Security.Cryptography.X509Certificates.X509ContentType]::Pfx) | Set-Content MyCert.pfx -Encoding Byte
.Example
PS C:\>$certificate = New-AADIntCertificate -SubjectName "CN=MyCert"
PS C:\>$certificate.Export([System.Security.Cryptography.X509Certificates.X509ContentType]::Cert) | Set-Content MyCert.cer -Encoding Byte
.Example
PS C:\>New-AADIntCertificate -SubjectName "CN=MyCert" -Export
Certificate successfully exported:
CN=MyCert.pfx
CN=MyCert.cer
#>
[cmdletbinding()]
param(
[parameter(Mandatory=$true,ValueFromPipeline)]
[ValidatePattern("[c|C][n|N]=.+")] # Must start with CN=
[String]$SubjectName,
[Switch]$Export
)
Process
{
# Create a private key
$rsa = [System.Security.Cryptography.RSA]::Create(2048)
# Initialize the Certificate Signing Request object
$req = [System.Security.Cryptography.X509Certificates.CertificateRequest]::new($SubjectName, $rsa, [System.Security.Cryptography.HashAlgorithmName]::SHA256,[System.Security.Cryptography.RSASignaturePadding]::Pkcs1)
$req.CertificateExtensions.Add([System.Security.Cryptography.X509Certificates.X509BasicConstraintsExtension]::new($true,$false,0,$true))
$req.CertificateExtensions.Add([System.Security.Cryptography.X509Certificates.X509SubjectKeyIdentifierExtension]::new($req.PublicKey,$false))
# Create a self-signed certificate
$selfSigned = $req.CreateSelfSigned((Get-Date).ToUniversalTime().AddMinutes(-5),(Get-Date).ToUniversalTime().AddYears(100))
# Store the private key to so that it can be exported
$cspParameters = [System.Security.Cryptography.CspParameters]::new()
$cspParameters.ProviderName = "Microsoft Enhanced RSA and AES Cryptographic Provider"
$cspParameters.ProviderType = 24
$cspParameters.KeyContainerName ="AADInternals"
# Set the private key
$privateKey = [System.Security.Cryptography.RSACryptoServiceProvider]::new(2048,$cspParameters)
$privateKey.ImportParameters($rsa.ExportParameters($true))
$selfSigned.PrivateKey = $privateKey
if($Export)
{
Set-BinaryContent -Path "$SubjectName.pfx" -Value $selfSigned.Export([System.Security.Cryptography.X509Certificates.X509ContentType]::Pfx)
Set-BinaryContent -Path "$SubjectName.cer" -Value $selfSigned.Export([System.Security.Cryptography.X509Certificates.X509ContentType]::Cert)
# Print out information
Write-Host "Certificate successfully exported:"
Write-Host " $SubjectName.pfx"
Write-Host " $SubjectName.cer"
}
else
{
return $selfSigned
}
}
}
# Parse access token and return it as PS object
function Read-Accesstoken
{
<#
.SYNOPSIS
Extract details from the given Access Token
.DESCRIPTION
Extract details from the given Access Token and returns them as PS Object
.Parameter AccessToken
The Access Token.
.Example
PS C:\>$token=Get-AADIntReadAccessTokenForAADGraph
PS C:\>Parse-AADIntAccessToken -AccessToken $token
aud : https://graph.windows.net
iss : https://sts.windows.net/f2b2ba53-ed2a-4f4c-a4c3-85c61e548975/
iat : 1589477501
nbf : 1589477501
exp : 1589481401
acr : 1
aio : ASQA2/8PAAAALe232Yyx9l=
amr : {pwd}
appid : 1b730954-1685-4b74-9bfd-dac224a7b894
appidacr : 0
family_name : company
given_name : admin
ipaddr : 107.210.220.129
name : admin company
oid : 1713a7bf-47ba-4826-a2a7-bbda9fabe948
puid : 100354
rh : 0QfALA.
scp : user_impersonation
sub : BGwHjKPU
tenant_region_scope : NA
tid : f2b2ba53-ed2a-4f4c-a4c3-85c61e548975
unique_name : admin@company.onmicrosoft.com
upn : admin@company.onmicrosoft.com
uti : -EWK6jMDrEiAesWsiAA
ver : 1.0
.Example
PS C:\>Parse-AADIntAccessToken -AccessToken $token -Validate
Read-Accesstoken : Access Token is expired
At line:1 char:1
+ Read-Accesstoken -AccessToken $at -Validate -verbose
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException
+ FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,Read-Accesstoken
aud : https://graph.windows.net
iss : https://sts.windows.net/f2b2ba53-ed2a-4f4c-a4c3-85c61e548975/
iat : 1589477501
nbf : 1589477501
exp : 1589481401
acr : 1
aio : ASQA2/8PAAAALe232Yyx9l=
amr : {pwd}
appid : 1b730954-1685-4b74-9bfd-dac224a7b894
appidacr : 0
family_name : company
given_name : admin
ipaddr : 107.210.220.129
name : admin company
oid : 1713a7bf-47ba-4826-a2a7-bbda9fabe948
puid : 100354
rh : 0QfALA.
scp : user_impersonation
sub : BGwHjKPU
tenant_region_scope : NA
tid : f2b2ba53-ed2a-4f4c-a4c3-85c61e548975
unique_name : admin@company.onmicrosoft.com
upn : admin@company.onmicrosoft.com
uti : -EWK6jMDrEiAesWsiAA
ver : 1.0
#>
[cmdletbinding()]
Param(
[Parameter(Mandatory=$True,ValueFromPipeline)]
[String]$AccessToken,
[Parameter()]
[Switch]$ShowDate,
[Parameter()]
[Switch]$Validate
)
Process
{
if([string]::IsNullOrEmpty($AccessToken))
{
Write-Warning "Unable to read access token (null or empty)"
return $null
}
# Token sections
$sections = $AccessToken.Split(".")
# Check if this is JWE
if($sections.Count -eq 5)
{
Write-Warning "JWE token, expected JWS. Unable to parse."
return
}
$header = $sections[0]
$payload = $sections[1]
$signature = $sections[2]
# Convert the token to string and json
$payloadString = Convert-B64ToText -B64 $payload
$payloadObj=$payloadString | ConvertFrom-Json
if($ShowDate)
{
# Show dates
$payloadObj.exp=($epoch.Date.AddSeconds($payloadObj.exp)).toString("yyyy-MM-ddTHH:mm:ssZ").Replace(".",":")
$payloadObj.iat=($epoch.Date.AddSeconds($payloadObj.iat)).toString("yyyy-MM-ddTHH:mm:ssZ").Replace(".",":")
$payloadObj.nbf=($epoch.Date.AddSeconds($payloadObj.nbf)).toString("yyyy-MM-ddTHH:mm:ssZ").Replace(".",":")
}
if($Validate)
{
# Check the signature
if((Is-AccessTokenValid -AccessToken $AccessToken))
{
Write-Verbose "Access Token signature successfully verified"
}
else
{
Write-Error "Access Token signature could not be verified"
}
# Check the timestamp
if((Is-AccessTokenExpired -AccessToken $AccessToken))
{
Write-Error "Access Token is expired"
}
else
{
Write-Verbose "Access Token is not expired"
}
}
# Debug
Write-Debug "PARSED ACCESS TOKEN: $($payloadObj | Out-String)"
# Return
$payloadObj
}
}
# Converts the given B64 encoded RSA cert or private key to PEM
# Jan 29th 2025
Function Convert-B64RSAToPem
{
[cmdletbinding()]
param(
[parameter(Mandatory=$true,ValueFromPipeline)]
[String]$RSA,
[parameter(Mandatory=$false,ValueFromPipeLine)]
[ValidateSet("RSA PRIVATE KEY","CERTIFICATE")]
[String]$Type = "CERTIFICATE"
)
Process
{
$PEM = "-----BEGIN $Type-----`n"
$p = 0
while($p -lt $RSA.Length)
{
$s = $p
$l = 64
$c = $RSA.Length - $p
if($c -lt 64)
{
$l = $c
}
$line = $RSA.Substring($s,$l)
$PEM += "$line`n"
$p += 64
}
$PEM += "-----END $Type-----`n"
return $PEM
}
}
# Gets the thumbprint of the given B64 encoded RSA certificate
# Jan 29th 2025
Function Get-B64RSAThumbprint
{
[cmdletbinding()]
param(
[parameter(Mandatory=$true,ValueFromPipeline)]
[String]$RSA
)
Process
{
$binRSA = [byte[]](Convert-B64ToByteArray -B64 $RSA)
$thumbprint = Get-Digest -Data $binRSA
return $thumbprint
}
}