MSEndpointMgr

Unpacking the Microsoft Intune MDM Certificate

In this post we take a small deep-dive into the Intune MDM certificate and talk about OID’s and how they can be leveraged to elevate your trust in the device identity.

In this blog, we take a tiny deep dive into what we uncovered while researching the Microsoft Intune MDM (Mobile Device Management) certificate.

This Intune MDM certificate plays a crucial role in securing devices enrolled in Intune, but hidden within its properties is some seriously useful information. When properly decoded, it can reveal key insights about both the device and the associated tenant.

We will pay particular attention to the certificate’s Object Identifiers (OIDs) and show you how to extract reliable data that helps identify the device and give you far more confidence in the associated Intune tenant – unlike relying on system registry values, which, let’s face it, are about as trustworthy as processed cheese

The MDM Certificate: A Key to Device and Tenant Identity

The Microsoft Intune MDM certificate is issued to each device enrolled in Intune and helps ensure secure communication between the device and the Intune service. This certificate contains fields and extensions that aren’t immediately human-readable. But when decoded, it reveals important details, including the MDM Device ID and the Entra ID Tenant ID, both of which are very interesting for high device identity confidence.

We found that two specific OIDs within the MDM certificate hold critical information:

  1. OID 1.2.840.113556.5.4 – This OID contains the MDM Device ID
  2. OID 1.2.840.113556.5.14 – This OID contains the Entra ID Tenant ID

The values associated with these OIDs are stored as byte arrays, which need to be converted into readable GUIDs to understand what they represent. However, this isn’t as simple as reading the bytes in sequence. Both identifiers are stored in a format comprised of both little-endian, and big endian encoding, meaning the order of the byte segments needs to be reversed in a specific order before the GUID is formed and recognisable.

OID 1.2.840.113556.5.4
OID 1.2.840.113556.5.14
1.2.840.113556.5.6

Rearranging the OID Byte Arrays to Reveal Device and Tenant IDs

Honestly, this was trial and error to work out the re-ordering of the OID’s. Nonetheless, it should be valuable for the pink matter in our skulls and for the history books.

Intune MDM Device ID (OID 1.2.840.113556.5.4)

The MDM Device ID is a unique identifier assigned to each device by Intune. The specific reordering pattern follows this structure:

  • The first 4 bytes are reversed.
  • The next 2 bytes are reversed.
  • The subsequent 2 bytes are reversed.
  • The third segment of 2 bytes is reversed.
  • The last 6 bytes remain in the original order.

Lets take some “hairball data” to visualise this re-ordering:-

12 34 56 78 9A BC DE F0 12 34 56 78 90 AB CD EF
  1. The first 4 bytes are reversed:
    • Original: 12 34 56 78
    • Reversed: 78 56 34 12
  2. The next 2 bytes are reversed:
    • Original: 9A BC
    • Reversed: BC 9A
  3. The subsequent 2 bytes are reversed:
    • Original: DE F0
    • Reversed: F0 DE
  4. The third segment of 2 bytes are reversed.
    • Original: 12 34
    • Reversed: 12 34
  5. The last 6 bytes stay in the original order:
    • 56 78 90 AB CD EF

Now that we’ve rearranged the bytes, we can form the GUID:-

78 56 34 12-BC 9A-F0 DE-12 34-56 78 90 AB CD EF

Giving us a Device ID of 78563412-BC9A-F0DE-1234-567890ABCDEF

Entra ID Tenant ID (OID 1.2.840.113556.5.14)

The process to convert the Tenant ID into a readable GUID is similar to that of the Device ID. Take not that we have 8 bytes in the last byte segment here though:

  • The first 4 bytes are reversed.
  • The next 2 bytes are reversed.
  • The subsequent 2 bytes are reversed.
  • The third segment of 2 bytes stay in the original order.
  • The remaining 8 bytes stay in the original order.

After this reordering, the byte array is transformed into a valid GUID that identifies the tenant to which the device belongs.

Lets take some more “hairball data” to visualise this re-ordering:-

A1 B2 C3 D4 E5 F6 12 34 56 78 9A BC DE F0 01 23 A1

Following the reordering pattern from your script:

  1. First 4 bytes are reversed (ignore first 2 bytes because we are doing little endian remember):
    • Original: A1 B2 C3 D4 E5 F6
    • Reversed: F6 E5 D4 C3
  2. Next 2 bytes are reversed:
    • Original: E5 F6
    • Reversed: F6 E5
  3. Next 2 bytes are reversed:
    • Original: 12 34
    • Reversed: 34 12
  4. Next 2 bytes stay in the original order:
    • 56 78
  5. Final 6 bytes stay in the original order:
    • 9A BC DE F0 01 23 A1

Now, let’s construct the GUID by combining the reordered segments:-

F6 E5 D4 C3-F6 E5-34 12-56 78-9A BC DE F0 01 23 A1

Giving us a Tenant ID of F6E5D4C3-F6E5-3412-5678-9ABCDEF00123A1

Who cares about OID’s?

Knowing this information won’t change your life. If it does, let us know. But it might open the door to new ideas about how you identify a device and tenant Id on a device.

Why should you care about these OIDs, and more importantly, why should you trust the data they contain over something simpler, like registry values? Trusting a registry value is like trusting a slice of processed cheese. Sure, it gets the job done, but you’re never quite sure what’s in it. On the other hand, trusting information from a certificate is like savoring a well-aged block of Parmigiano-Reggiano. It’s authentic, carefully crafted, and easily recognizable by true cheese connoisseurs.

The key difference is trust. Certificates, issued by trusted authorities like Microsoft, provide a cryptographic chain of trust that ensures the data is reliable and secure. Registry values, on the other hand, can be easily modified, meaning they lack the security and authenticity that certificates provide. Just as a true cheese lover would opt for Parmigiano-Reggiano over processed cheese, an IT professional might prefer certificates over more vulnerable registry data when it comes to identifying a device and tenant id.

Show us the script already

In trusted MSEndpointMgr fashion, we created a script that will do some cool stuff.

Intune/Certificates/Get-MDMInformation.ps1 at master · MSEndpointMgr/Intune (github.com)

Cool Script

The script will do the following:-

  • Opens the local machine’s certificate store to search for certificates issued by the Microsoft Intune MDM Device CA.
  • Validates the certificate chain to ensure both the intermediate and root issuers are trusted and match expected values.
  • Checks if the private key is present and determines if it is exportable.
  • Extracts specific OIDs (Intune MDM Device ID and Entra Tenant ID) from the certificate extensions.
  • Converts the OID byte arrays into GUIDs by reordering the bytes to match the correct format.
  • Retrieves Key Storage Provider (KSP) information using certutil for detailed private key management insights.
  • Outputs detailed certificate information such as name, thumbprint, issuer, chain trust status, private key presence, private key exportability, and reassembled GUIDs from the OIDs.
  • Returns the results as a PowerShell object with the certificate details.

Code below for you screen grabbing hooligans:-

<#
.Synopsis
Check information about the Intune MDM certificate and the associated OIDs.

Created on:   2024-09-16
Created by:   Ben Whitmore @MSEndpointMgr
Filename:     Get-MDMInformation.ps1

.Description
This script checks the local machine certificate store for certificates issued by the Microsoft Intune MDM Device CA.
It validates the certificate chain and checks if the private key is present and exportable.
The script also looks for specific OIDs in the certificate extensions and converts the byte arrays to GUIDs.
The output includes the certificate name, thumbprint, issuer, chain trust status, private key presence, private key exportability status and the reassembled GUIDs from the byte arrays.

---------------------------------------------------------------------------------
LEGAL DISCLAIMER

The PowerShell script provided is shared with the community as-is
The author and co-author(s) make no warranties or guarantees regarding its functionality, reliability, or suitability for any specific purpose
Please note that the script may need to be modified or adapted to fit your specific environment or requirements
It is recommended to thoroughly test the script in a non-production environment before using it in a live or critical system
The author and co-author(s) cannot be held responsible for any damages, losses, or adverse effects that may arise from the use of this script
You assume all risks and responsibilities associated with its usage
---------------------------------------------------------------------------------

.PARAMETER mdmIntermediateIssuer
The issuer of the mdm certificate. Default is 'CN=Microsoft Intune MDM Device CA'.

.PARAMETER mdmRootIssuer
The issuer of the mdm root certificate. Default is 'CN=Microsoft Intune Root Certification Authority'.

.EXAMPLE
.\Get-MDMInformation.ps1

#>
param (
    [string]$mdmIntermediateIssuer = 'CN=Microsoft Intune MDM Device CA',
    [string]$mdmRootIssuer = 'CN=Microsoft Intune Root Certification Authority'
)

# Function to validate if the certificate chain is valid and from the correct issuer
function Test-CertificateIssuer {
    param (
        [System.Security.Cryptography.X509Certificates.X509Certificate2]$cert,
        [string]$expectedIntermediateIssuer,
        [string]$expectedRootIssuer
    )

    # Create a new instance of the x509Chain class to build and validate the certificate chain
    $chain = New-Object System.Security.Cryptography.X509Certificates.X509Chain
    $chain.ChainPolicy.RevocationMode = [System.Security.Cryptography.X509Certificates.X509RevocationMode]::NoCheck # Disable revocation check

    if ($chain.Build($cert)) {
    
        # Store variables for the intermediate and root certificate validation
        $intermediateIssuerValid = $false
        $rootIssuerValid = $false

        # Validate each certificate in the chain
        foreach ($element in $chain.ChainElements) {
            $subject = $element.Certificate.Subject
            $issuer = $element.Certificate.Issuer

            # Check if the certificate is the root certificate (self-signed)
            if ($subject -eq $issuer) {

                # Validate the root certificate issuer
                if ($issuer -eq $expectedRootIssuer) {
                    $rootIssuerValid = $true
                }
            }
            elseif ($issuer -eq $expectedIntermediateIssuer) {

                # Validate the intermediate issuer
                $intermediateIssuerValid = $true
            }
        }

        # Return true only if both the intermediate and root issuers are valid
        return $intermediateIssuerValid -and $rootIssuerValid
    }
    else {
        return $false
    }
}

# Function to check if the current user is an administrator
function Test-IsAdmin {
    $currentUser = [Security.Principal.WindowsIdentity]::GetCurrent()
    $principal = New-Object Security.Principal.WindowsPrincipal($currentUser)
    return $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
}

# Combined function to check private key exportability and get KSP, including TPM detection
function Get-PrivateKeyInfo {
    param (
        [System.Security.Cryptography.X509Certificates.X509Certificate2]$cert
    )

    if (Test-IsAdmin) {
        # Test if the private key is exportable
        try {
            $certBytes = $cert.Export([System.Security.Cryptography.X509Certificates.X509ContentType]::Pkcs12, $null)
            $isExportable = $true
        }
        catch {
            $isExportable = $false
        }

        # Use Start-Process to run certutil and capture only the provider information. Certutil is more reliable than the .NET classes for this information.
        try {
            $certUtilOutput = & certutil.exe -store my $cert.Thumbprint | Select-String -Pattern 'Provider'

            # Extract the provider value from the output and clean it
            $provider = $certUtilOutput -replace 'Provider\s*=\s*', '' -replace '^\s+', ''

            # Store provider in variable
            $ksp = $provider
        }
        catch {
            $ksp = ("Error retrieving KSP: {0}" -f $_)
        }
    }
    else {
        $isExportable = 'Insufficient privileges to test (Requires Admin)'
        $ksp = 'Insufficient privileges to test (Requires Admin)'
    }

    return [PSCustomObject]@{
        Exportable = $isExportable
        KspName    = $ksp
    }
}

function Convert-BitStringToGuid {
    param (
        [byte[]]$bitstring,
        [string]$oid
    )
    
    # Convert the byte array to a hexadecimal string
    $hexString = [System.BitConverter]::ToString($bitstring)

    # Split the string into individual hex pairs
    $hexArray = $hexString.Split('-')

    # Reorder the array based on the OID
    if ($oid -eq '1.2.840.113556.5.4') {

        # Intune MDM Device ID (4-byte little-endian, 2-byte little-endian, 2-byte little-endian, 2-byte little-endian, rest big-endian)
        $guidArray = @(
            $hexArray[3], $hexArray[2], $hexArray[1], $hexArray[0], '-'
            $hexArray[5], $hexArray[4], '-'
            $hexArray[7], $hexArray[6], '-'
            $hexArray[8], $hexArray[9], '-'
            $hexArray[10..15]
        )
    }
    elseif ($oid -eq '1.2.840.113556.5.14') {

        # Entra ID Tenant ID (4-byte little-endian, 2-byte little-endian, 2-byte little-endian, 2-byte big-endian, rest big-endian)
        $guidArray = @(
            $hexArray[5], $hexArray[4], $hexArray[3], $hexArray[2], '-'
            $hexArray[7], $hexArray[6], '-'
            $hexArray[9], $hexArray[8], '-'
            $hexArray[10], $hexArray[11], '-'
            $hexArray[12..17]
        )
    }

    # Join the array and return the formatted GUID
    return $guidArray -join ''
}

# Convert byte array to hexadecimal string
function ConvertToHexString($byteArray) {
    return ($byteArray | ForEach-Object { $_.ToString('x2') }) -join ' '
}

# Open the LocalMachine certificate store (or User store - sometimes the MDM certificate is located here depending on the enrollment method)
$x509Store = New-Object System.Security.Cryptography.X509Certificates.X509Store([System.Security.Cryptography.X509Certificates.StoreName]::My, [System.Security.Cryptography.X509Certificates.StoreLocation]::LocalMachine)
$x509Store.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadOnly)

# Initialize array to hold the results
$certResults = @()

# Loop through certificates and check the criteria
foreach ($cert in $x509Store.Certificates) {

    # Only process if the certificate issuer is correct
    if ($cert.Issuer -eq $mdmIntermediateIssuer) {

        # Is the private key present?
        $hasPrivateKey = $cert.HasPrivateKey

        # Retrieve private key exportability and KSP information
        $privateKeyInfo = Get-PrivateKeyInfo -cert $cert

        # Validate the chain and check the issuer
        $chainTrusted = Test-CertificateIssuer -cert $cert -expectedIntermediateIssuer $mdmIntermediateIssuer -expectedRootIssuer $mdmRootIssuer

        # Variables to hold OID data
        $mdmDeviceIdBitString = $null
        $entraTenantIdBitString = $null
        $mdmDeviceIdGuid = $null
        $entraTenantIdGuid = $null
        $mdmCertOidBitString = $null
        $mdmCertOidHex = $null

        # Loop through extensions to find the specific OIDs
        foreach ($extension in $cert.Extensions) {
            if ($extension.Oid.Value -eq '1.2.840.113556.5.4') {

                # OID for Intune MDM Device ID
                $mdmDeviceIdBitString = $extension.RawData
                $mdmDeviceIdGuid = Convert-BitStringToGuid -bitstring $mdmDeviceIdBitString -oid $extension.Oid.Value
            }
            elseif ($extension.Oid.Value -eq '1.2.840.113556.5.14') {

                # OID for Entra Tenant ID
                $entraTenantIdBitString = $extension.RawData
                $entraTenantIdGuid = Convert-BitStringToGuid -bitstring $entraTenantIdBitString -oid $extension.Oid.Value
            }
            elseif ($extension.Oid.Value -eq '1.2.840.113556.5.6') {

                # OID for MDM certificate
                $mdmCertOidBitString = $extension.RawData
                $mdmCertOid = 'This is an MDM certificate'
                $mdmCertOidFound = $true
            }
        }

        # Create an object for the certificate details
        $certObject = [PSCustomObject]@{
            CertificateName                              = $cert.Subject
            CertificateThumbprint                        = $cert.Thumbprint
            CertificateIssuer                            = $cert.Issuer
            CertificateChainTrusted                      = $chainTrusted
            PrivateKeyPresent                            = $hasPrivateKey
            PrivateKeyExportable                         = $privateKeyInfo.Exportable
            KeyStorageProvider                           = $privateKeyInfo.KspName
            IntuneMDMDeviceIDOID_1_2_840_113556_5_4      = if ($mdmDeviceIdBitString) { ConvertToHexString $mdmDeviceIdBitString } else { 'Not Found' }
            IntuneMDMDeviceIDReassembled                 = if ($mdmDeviceIdGuid) { $mdmDeviceIdGuid } else { 'Not Found' }
            EntraTenantIDOID_1_2_840_113556_5_14         = if ($entraTenantIdBitString) { ConvertToHexString $entraTenantIdBitString } else { 'Not Found' }
            EntraTenantIDReassembled                     = if ($entraTenantIdGuid) { $entraTenantIdGuid } else { 'Not Found' }
            MDMCertOID_1_2_840_113556_5_6                = if ($mdmCertOidFound) { $mdmCertOid } else { 'This is not a valid MDM certificate' }
        }

        # Add object to results array
        $certResults += $certObject
    }
}

# Output the array of certificate details
return $certResults

# Close the store when done
$x509Store.Close()

You will need to run this thing as Admin to check the private key information.

Run as admin

You should get a nice summary detailing information about the MDM certificate.

Certificate information

Final Thoughts

One interesting finding on older devices is that the private key is not protected by TPM. You can tell by checking the Key Storage Provider (KSP). If the key is protected by TPM, the KSP should display something like “Microsoft Platform Crypto Provider”. On older devices, it might show “Microsoft Software Key Storage Provider”, indicating the key is not hardware-protected and is more vulnerable. This is a key distinction when auditing device security.

There are indications in the Intune Management Extension in the ClientCertPicker class that Microsoft are looking for these certificates too – to do what with we wonder? 😉

Ben Whitmore

Microsoft MVP - Enterprise Mobility, Microsoft Certified Trainer and Microsoft 365 Certified: Enterprise Administrator Expert. Community driven and passionate Customer Engineer Lead at Patch My PC with over 2 decades of experience in driving adoption and technology change within the Enterprise.

Add comment

Sponsors

Categories

MSEndpointMgr.com use cookies to ensure that we give you the best experience on our website.