Device_utils.ps1

# This file contains utility functions for local AAD Joined devices

# Exports the transport key of the local device
# Dec 17th 2021
function Get-LocalDeviceTransportKeys
{
    [CmdletBinding()]
    param(
        [Parameter(Mandatory=$True)]
        [ValidateSet('Joined','Registered')]
        [String]$JoinType,
        [Parameter(Mandatory=$True)]
        [String]$IdpDomain,
        [Parameter(Mandatory=$True)]
        [String]$TenantId,
        [Parameter(Mandatory=$True)]
        [String]$UserEmail
    )
    Begin
    {
        $sha256 = [System.Security.Cryptography.SHA256]::Create()
    }
    Process
    {
        # Calculate registry key parts
        $idp    = Convert-ByteArrayToHex -Bytes ($sha256.ComputeHash([text.encoding]::Unicode.GetBytes($IdpDomain)))
        $tenant = Convert-ByteArrayToHex -Bytes ($sha256.ComputeHash([text.encoding]::Unicode.GetBytes($TenantId)))
        $email  = Convert-ByteArrayToHex -Bytes ($sha256.ComputeHash([text.encoding]::Unicode.GetBytes($UserEmail)))
        $sid    = Convert-ByteArrayToHex -Bytes ($sha256.ComputeHash([text.encoding]::Unicode.GetBytes(([System.Security.Principal.WindowsIdentity]::GetCurrent()).User.Value)))
        

        if($JoinType -eq "Joined")
        {
            $registryPath = "HKLM:\SYSTEM\CurrentControlSet\Control\Cryptography\Ngc\KeyTransportKey\PerDeviceKeyTransportKey\$Idp\$tenant"
        }
        else
        {
            $registryPath = "HKLM:\SYSTEM\CurrentControlSet\Control\Cryptography\Ngc\KeyTransportKey\$sid\$idp\$($tenant)_$($email)"
        }

        if((Test-Path -Path $registryPath) -eq $false)
        {
            Throw "The device seems not to be Azure AD joined or registered. Registry key not found: $registryPath"
        }

        # Get the Transport Key name from registry
        try
        {
            $transPortKeyName = Get-ItemPropertyValue -Path "$registryPath" -Name "SoftwareKeyTransportKeyName"
        }
        catch
        {
            # This machine probably has a TPM, so the value name would be "TpmKeyTransportKeyName"
            Throw "Unable to get SoftwareTransportKeyName from $registryPath"
        }

        Write-Verbose "TransportKey name: $transportKeyName`n"

        $transPortKey = Find-PrivateKey -KeyName $transportKeyName -Paths "$env:ALLUSERSPROFILE\Microsoft\Crypto\SystemKeys" -Elevate

        return $transPortKey
    }
    End
    {
        $sha256.Dispose()
    }
}

# 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
    }
}

# Gets service account names for all services
# Aug 29th 2022
function Get-ServiceAccountNames
{
    [cmdletbinding()]

    Param()
    Process
    {
        foreach($service in Get-ChildItem -Path "HKLM:\SYSTEM\CurrentControlSet\Services\")
        {
            $svcName    = $service.PSChildName
            $svcAccount = $service.GetValue("ObjectName")

            if(![string]::IsNullOrEmpty($svcAccount))
            {
                Write-Debug "Service: '$svcName', AccountName: '$svcAccount'"

                New-Object psobject -Property ([ordered]@{"Service" = $svcName; "AccountName" = $svcAccount})
            }
        }
    }
}