From 5f7fd98db88e43341dd4e61dd8a7251fd7363d6d Mon Sep 17 00:00:00 2001 From: Max Allen <125095090+AzureToTheMax@users.noreply.github.com> Date: Wed, 10 May 2023 16:08:35 -0400 Subject: [PATCH 01/29] Update README.md NOT COMPLETE - still needs more info but I want to make a coding change. --- README.md | 35 +++++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c45ad0e..0cfd982 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,9 @@ This module performs the device trust validation and can be embedded in most Fun # How the trusted device validation works -Every Azure AD joined or hybrid Azure AD joined device has a computer certificate that was enrolled when registering the device to Azure AD. This device specific computer certificate's public and private keys are available locally on the device, while the public key is known to Azure AD. The device trust validation functionality occurs in the following scenarios: +Every Azure AD joined or hybrid Azure AD joined device has a computer certificate that was generated when registering the device to Azure AD. This device specific computer certificate's public and private keys are available locally on the device. When registering the device, a special field called the ["alternativeSecurityIds"](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-dvrj/f900e812-8f1c-4345-9ab0-b91111068651) is added to the Device's Azure record which contains a "key" field with a value that is a Base64 encoded representation of that SAME key pairs SHA1 Thumbprint, as well as the entire public keys SHA1 hash. + +The device trust validation functionality occurs in the following scenarios: - Client-side data gathering - Function App data validation @@ -14,9 +16,38 @@ Every Azure AD joined or hybrid Azure AD joined device has a computer certificat On the client-side, a signature hash using the private key of the computer certificate is calculated and sent encoded as a Base64 string to the Function App including the Azure AD device identifier (the common name of the computer certificate), the public key as a byte array encoded as a Base64 string together with the computer certificate thumbprint. These data strings are sent all together as parameter input when calling the Function App API +On the client side, a table of information is built which will both serve to carry the data needed to authenticate to our Function App, as well as any other payload required for your specific needs. By default, this table contains... + +* The devices name. +* The devices Azure AD ID which was pulled from the devices registry. +* The thumbprint of the certificate used when registering to Azure AD. This was also pulled from the devices registry. +* A copy of the computers public certificate which has been turned into a byte array then encoded as a Base64 string for ease of transport. +* And last but not least, a signature generated from the SHA256 hash of the devices Azure ID made using the devices private certificate. The private certificate was located by looking for the correspondiing thumbprint in the computers certificate store. + +...And again, all of that data will be passed to the Function App along with any other data you add. Details on how to add more in the use section. + +Note: The signature is *not* an encrypted form of the SHA256 hash of the devices Azure ID, nor does it contian the hash at all. It is meerely a method to validate and authenticate a SHA256 hash, and thus the chunk of data it represents, when combined with the public certificate. + + ## Function App -To be added... +When the Function App receieves a request, it will start by pulling the various information sent by the client out of the body of the request. The Function App will then use it's Graph permissions **(Device.Read.All is required)** to pull the full Azure AD record for the Azure AD Device ID provided in the request. + +1. As mentioned, this record contains a "alternativeSecurityIds" field with a key value that has a base64 represenation of the devices public ceritificates SHA1 thumbprint and, a SHA1 hash of the full X.509 public certificate. The authentication then starts by confirming the SAHA1 thumbprint provided in our request matches the SHA1 thumbprint stored in the alternativeSecurityIds/keys field. Technically, we didn't extract the hash from the key, but rather we provided it as a seperate field. Still, this confirms we at least know the correct thumnbprint. + +2. Next, we confirm that the full SHA1 hash of the X.509 public cert that was provided matches the SHA1 hash of the devices public cert that was stored again in the alternativeSecurityIds/keys field. At this point, we know the public certificate provided is not just related to the same private certificate, but is indeed the exact same public certificate originally made when the device was registered. + +3. Now that we know our public key is legitimate, we are going to test the signature against the SHA256 hash of the devices Azure ID using that public key. In order to do this, we must take our Base64 encoded Public Key, turn it back into a byte array, and convert that back into a function RSA key. We then pull the devices Azure AD ID, this time from Azure itself, and again calculate it’s SHA256 hash. We can then use our public key to validate that signature against that hash. + +4. Lastly, we do a simple check to ensure that the Azure AD Device ID is enabled. + +With all this confirmed, we know that... + +* The request contained a valid Azure Device ID +* The request contained a valid thumbprint of that Azure Device's original registering certificate +* The request contained a +* That Azure Device ID is Enabled + # How to use AADDeviceTrust.Client module in a client-side script Ensure the AADDeviceTrust.Client module is installed on the device prior to running the sample code below. Use the `Test-AzureADDeviceRegistration` function to ensure the device where the code is running on fulfills the device registration requirements. Then use the `New-AADDeviceTrustBody` function to automatically generate a hash-table object containing the gathered data required for the body of the request. Finally, use built-in `Invoke-RestMethod` cmdlet to invoke the request against the Function App, passing the gathered data to be validated by the Function App, if the request comes from a trusted device. From e8d922576dd92d0d4889bb6d5c1ded75602802f6 Mon Sep 17 00:00:00 2001 From: Max Allen <125095090+AzureToTheMax@users.noreply.github.com> Date: Wed, 10 May 2023 16:44:49 -0400 Subject: [PATCH 02/29] Update Get-AzureADDeviceID.ps1 --- .../Private/Get-AzureADDeviceID.ps1 | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/AADDeviceTrust.Client/Private/Get-AzureADDeviceID.ps1 b/AADDeviceTrust.Client/Private/Get-AzureADDeviceID.ps1 index 5c9c233..f3b7e4c 100644 --- a/AADDeviceTrust.Client/Private/Get-AzureADDeviceID.ps1 +++ b/AADDeviceTrust.Client/Private/Get-AzureADDeviceID.ps1 @@ -14,6 +14,7 @@ function Get-AzureADDeviceID { Version history: 1.0.0 - (2021-05-26) Function created + 1.0.1 - (2022-10-20) @AzureToTheMax - Fixed issue pertaining to Cloud PCs (Windows 365) devices ability to locate their AzureADDeviceID. #> Process { # Define Cloud Domain Join information registry path @@ -30,7 +31,25 @@ function Get-AzureADDeviceID { # Handle return value return $AzureADDeviceID + + } else { + + #If no certificate was found, locate it by Common Name instead of Thumbprint. This is likely a CPC or similar. + $AzureADJoinCertificate = Get-ChildItem -Path "Cert:\LocalMachine\My" -Recurse | Where-Object { $PSItem.Subject -like "CN=($AzureADJoinInfoThumbprint)" } + + if ($AzureADJoinCertificate -ne $null){ + # Cert is now found, extract Device ID from Common Name + $AzureADDeviceID = ($AzureADJoinCertificate | Select-Object -ExpandProperty "Subject") -replace "CN=", "" + # Handle return value + return $AzureADDeviceID + + } else { + # Last ditch effort, try and use the ThumbPrint itself. + $AzureADDeviceID=$AzureADJoinInfoThumbprint + return $AzureADDeviceID + + } } } } -} \ No newline at end of file +} From 50b279d9bd55d5a1180b1e7ce3e53fe03d616c55 Mon Sep 17 00:00:00 2001 From: Max Allen <125095090+AzureToTheMax@users.noreply.github.com> Date: Wed, 10 May 2023 17:54:43 -0400 Subject: [PATCH 03/29] Update Get-AzureADRegistrationCertificateThumbprint.ps1 Updating for Windows 365 / Cloud PCs which have their JoinInfo child key as their Azure ID rather than the thumbprint --- ...ureADRegistrationCertificateThumbprint.ps1 | 26 ++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/AADDeviceTrust.Client/Private/Get-AzureADRegistrationCertificateThumbprint.ps1 b/AADDeviceTrust.Client/Private/Get-AzureADRegistrationCertificateThumbprint.ps1 index 197347a..8da2695 100644 --- a/AADDeviceTrust.Client/Private/Get-AzureADRegistrationCertificateThumbprint.ps1 +++ b/AADDeviceTrust.Client/Private/Get-AzureADRegistrationCertificateThumbprint.ps1 @@ -14,6 +14,7 @@ function Get-AzureADRegistrationCertificateThumbprint { Version history: 1.0.0 - (2021-06-03) Function created + 1.0.1 - (2023-05-10) @AzureToTheMax Updated for Cloud PCs which don't have their thumbprint as their JoinInfo key name. #> Process { # Define Cloud Domain Join information registry path @@ -21,8 +22,27 @@ function Get-AzureADRegistrationCertificateThumbprint { # Retrieve the child key name that is the thumbprint of the machine certificate containing the device identifier guid $AzureADJoinInfoThumbprint = Get-ChildItem -Path $AzureADJoinInfoRegistryKeyPath | Select-Object -ExpandProperty "PSChildName" + # Check for a cert matching that thumbprint + $AzureADJoinCertificate = Get-ChildItem -Path "Cert:\LocalMachine\My" -Recurse | Where-Object { $PSItem.Thumbprint -eq $AzureADJoinInfoThumbprint } + + if($AzureADJoinCertificate -ne $null){ + # if a matching cert was found tied to that reg key (thumbprint) value, then that is the thumbprint and it can be returned. + $AzureADThumbprint = $AzureADJoinInfoThumbprint + + # Handle return value + return $AzureADThumbprint + + } else { + + # If a cert was not found, that reg key was not the thumbprint but can be used to locate the cert as it is likely the Azure ID which is in the certs common name. + $AzureADJoinCertificate = Get-ChildItem -Path "Cert:\LocalMachine\My" -Recurse | Where-Object { $PSItem.Subject -like "CN=$($AzureADJoinInfoThumbprint)" } + + #Pull thumbprint from cert + $AzureADThumbprint = $AzureADJoinCertificate.Thumbprint + + # Handle return value + return $AzureADThumbprint + } - # Handle return value - return $AzureADJoinInfoThumbprint } -} \ No newline at end of file +} From 9b8993c281d37b66c91c7fdea3605894402d8f57 Mon Sep 17 00:00:00 2001 From: Max Allen <125095090+AzureToTheMax@users.noreply.github.com> Date: Wed, 10 May 2023 21:14:56 -0400 Subject: [PATCH 04/29] Update README.md still not done --- README.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 0cfd982..96bff63 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@ # Overview -When building a Function App API in Azure that accepts incoming HTTP requests from an Azure AD joined devices, being able to validate such a request is only coming from a trusted device in a given Azure AD tenant adds extensive security to the API. By default, the Function App can be configured to only accept incoming requests with a valid client certificate, which is a good security practice. Although, there's also another option to enhance the security of a Function App in terms of validating the incoming request, using the certificate enrolled to the device when it first registered itself with Azure AD. +When building a Function App API in Azure that accepts incoming HTTP requests from Azure AD joined devices, being able to validate such a request is only coming from a trusted device in a given Azure AD tenant adds extensive security to the API. By default, the Function App can be configured to only accept incoming requests with a valid client certificate, which is a good security practice. Although, there's also another option to enhance the security of a Function App in terms of validating the incoming request, using the certificate enrolled to the device when it first registered itself with Azure AD. This module performs the device trust validation and can be embedded in most Function Apps where enhanced request validation is required. # How the trusted device validation works -Every Azure AD joined or hybrid Azure AD joined device has a computer certificate that was generated when registering the device to Azure AD. This device specific computer certificate's public and private keys are available locally on the device. When registering the device, a special field called the ["alternativeSecurityIds"](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-dvrj/f900e812-8f1c-4345-9ab0-b91111068651) is added to the Device's Azure record which contains a "key" field with a value that is a Base64 encoded representation of that SAME key pairs SHA1 Thumbprint, as well as the entire public keys SHA1 hash. +Every Azure AD joined or hybrid Azure AD joined device has a computer certificate that was generated when registering the device to Azure AD. This device specific computer certificate's public and private keys are available locally on the device. When registering the device, a special field called the ["alternativeSecurityIds"](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-dvrj/f900e812-8f1c-4345-9ab0-b91111068651) is added to the Device's Azure record which contains a "key" field with a value that is a Base64 encoded representation of that same private/public key pairs SHA1 Thumbprint, as well as the entire public keys SHA1 hash. The device trust validation functionality occurs in the following scenarios: @@ -14,26 +14,26 @@ The device trust validation functionality occurs in the following scenarios: ## Client-side -On the client-side, a signature hash using the private key of the computer certificate is calculated and sent encoded as a Base64 string to the Function App including the Azure AD device identifier (the common name of the computer certificate), the public key as a byte array encoded as a Base64 string together with the computer certificate thumbprint. These data strings are sent all together as parameter input when calling the Function App API - On the client side, a table of information is built which will both serve to carry the data needed to authenticate to our Function App, as well as any other payload required for your specific needs. By default, this table contains... * The devices name. -* The devices Azure AD ID which was pulled from the devices registry. -* The thumbprint of the certificate used when registering to Azure AD. This was also pulled from the devices registry. +* The devices Azure AD ID. +* The thumbprint of the certificate used when registering to Azure AD. * A copy of the computers public certificate which has been turned into a byte array then encoded as a Base64 string for ease of transport. -* And last but not least, a signature generated from the SHA256 hash of the devices Azure ID made using the devices private certificate. The private certificate was located by looking for the correspondiing thumbprint in the computers certificate store. +* And last but not least, a signature generated from the SHA256 hash of the devices Azure ID wihch was signed using the devices private certificate. -...And again, all of that data will be passed to the Function App along with any other data you add. Details on how to add more in the use section. +...And again, all of that data will be passed to the Function App along with any other data you add. Details on how to add more fields can be found in the use section. -Note: The signature is *not* an encrypted form of the SHA256 hash of the devices Azure ID, nor does it contian the hash at all. It is meerely a method to validate and authenticate a SHA256 hash, and thus the chunk of data it represents, when combined with the public certificate. +Note: The signature is *not* an encrypted form of the SHA256 hash of the devices Azure ID, nor does it contian the hash of the Azure ID at all. It also does not contain the Private key. It is meerely a method to validate and authenticate a SHA256 hash, and thus the chunk of data it represents, when combined with the public certificate. How this comes into play is explained in the next section. ## Function App -When the Function App receieves a request, it will start by pulling the various information sent by the client out of the body of the request. The Function App will then use it's Graph permissions **(Device.Read.All is required)** to pull the full Azure AD record for the Azure AD Device ID provided in the request. +When the Function App receieves a request, it will start by pulling the various information sent by the client out of the body of the request. The Function App will then use it's Graph permissions* to pull the full Azure AD record for the Azure AD Device ID provided in the request. As mentioned, this record contains a "alternativeSecurityIds" field with a key value that has a base64 represenation of the SHA1 thumbprint and SHA1 hash of the full X.509 public certificate used when the machine originally registered. + +***Function App needs Device.Read.All permissions** -1. As mentioned, this record contains a "alternativeSecurityIds" field with a key value that has a base64 represenation of the devices public ceritificates SHA1 thumbprint and, a SHA1 hash of the full X.509 public certificate. The authentication then starts by confirming the SAHA1 thumbprint provided in our request matches the SHA1 thumbprint stored in the alternativeSecurityIds/keys field. Technically, we didn't extract the hash from the key, but rather we provided it as a seperate field. Still, this confirms we at least know the correct thumnbprint. +1. The authentication then starts by confirming the SHA1 thumbprint provided in our request matches the SHA1 thumbprint stored in the alternativeSecurityIds/keys field. Technically, we didn't extract the hash from the key, but rather we provided it as a seperate field. Still, this confirms we at least know the correct thumnbprint. 2. Next, we confirm that the full SHA1 hash of the X.509 public cert that was provided matches the SHA1 hash of the devices public cert that was stored again in the alternativeSecurityIds/keys field. At this point, we know the public certificate provided is not just related to the same private certificate, but is indeed the exact same public certificate originally made when the device was registered. From e1d1e536e28f88492ccb804a17a75d7dd6f5f6a2 Mon Sep 17 00:00:00 2001 From: Max Allen <125095090+AzureToTheMax@users.noreply.github.com> Date: Wed, 10 May 2023 21:15:43 -0400 Subject: [PATCH 05/29] Update Get-AzureADDeviceID.ps1 Fixed issue pertaining to Cloud PCs (Windows 365) devices ability to locate their AzureADDeviceID. --- AADDeviceTrust.Client/Private/Get-AzureADDeviceID.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AADDeviceTrust.Client/Private/Get-AzureADDeviceID.ps1 b/AADDeviceTrust.Client/Private/Get-AzureADDeviceID.ps1 index f3b7e4c..e7c5f0e 100644 --- a/AADDeviceTrust.Client/Private/Get-AzureADDeviceID.ps1 +++ b/AADDeviceTrust.Client/Private/Get-AzureADDeviceID.ps1 @@ -44,7 +44,7 @@ function Get-AzureADDeviceID { return $AzureADDeviceID } else { - # Last ditch effort, try and use the ThumbPrint itself. + # Last ditch effort, try and use the ThumbPrint (reg key) itself. $AzureADDeviceID=$AzureADJoinInfoThumbprint return $AzureADDeviceID From 140269b7170a0817d29695f6b4329ff591edf560 Mon Sep 17 00:00:00 2001 From: Max Allen <125095090+AzureToTheMax@users.noreply.github.com> Date: Wed, 10 May 2023 21:16:10 -0400 Subject: [PATCH 06/29] Update Get-AzureADRegistrationCertificateThumbprint.ps1 @AzureToTheMax Updated for Cloud PCs which don't have their thumbprint as their JoinInfo key name. From 1ee591e1309d4103ea72b83c167445719abe4d2d Mon Sep 17 00:00:00 2001 From: Max Allen <125095090+AzureToTheMax@users.noreply.github.com> Date: Wed, 10 May 2023 21:16:50 -0400 Subject: [PATCH 07/29] Update Get-PublicKeyBytesEncodedString.ps1 @AzureToTheMax - Updated to use X509 for the full public key with extended properties in the PEM format --- .../Private/Get-PublicKeyBytesEncodedString.ps1 | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/AADDeviceTrust.Client/Private/Get-PublicKeyBytesEncodedString.ps1 b/AADDeviceTrust.Client/Private/Get-PublicKeyBytesEncodedString.ps1 index 8e9c1d7..c237d1d 100644 --- a/AADDeviceTrust.Client/Private/Get-PublicKeyBytesEncodedString.ps1 +++ b/AADDeviceTrust.Client/Private/Get-PublicKeyBytesEncodedString.ps1 @@ -14,10 +14,11 @@ function Get-PublicKeyBytesEncodedString { Author: Nickolaj Andersen / Thomas Kurth Contact: @NickolajA Created: 2021-06-07 - Updated: 2021-06-07 + Updated: 2023-05-10 Version history: 1.0.0 - (2021-06-07) Function created + 1.0.1 - (2023-05-10) @AzureToTheMax - Updated to use X509 for the full public key with extended properties in the PEM format Credits to Thomas Kurth for sharing his original C# code. #> @@ -27,14 +28,19 @@ function Get-PublicKeyBytesEncodedString { [string]$Thumbprint ) Process { + # Determine the certificate based on thumbprint input $Certificate = Get-ChildItem -Path "Cert:\LocalMachine\My" -Recurse | Where-Object { $PSItem.Thumbprint -eq $Thumbprint } if ($Certificate -ne $null) { - # Get the public key bytes - [byte[]]$PublicKeyBytes = $Certificate.GetPublicKey() + # Bring the cert into a X509 object + $X509 = [System.Security.Cryptography.X509Certificates.X509Certificate2]::New($Certificate) + #Set the type of export to perform + $type = [System.Security.Cryptography.X509Certificates.X509ContentType]::Cert + #Export the public cert + $PublicKeyBytes = $X509.Export($type, "") - # Handle return value + # Handle return value - convert to Base64 return [System.Convert]::ToBase64String($PublicKeyBytes) } } -} \ No newline at end of file +} From ab262f6bfa8d8d1c24cf5efc821e8f47a7fa7227 Mon Sep 17 00:00:00 2001 From: Max Allen <125095090+AzureToTheMax@users.noreply.github.com> Date: Wed, 10 May 2023 21:17:52 -0400 Subject: [PATCH 08/29] Update New-AADDeviceTrustBody.ps1 @AzureToTheMax - Updated to no longer use Thumbprint field, now redundant. --- AADDeviceTrust.Client/Public/New-AADDeviceTrustBody.ps1 | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/AADDeviceTrust.Client/Public/New-AADDeviceTrustBody.ps1 b/AADDeviceTrust.Client/Public/New-AADDeviceTrustBody.ps1 index f5e7fe6..4ba88f1 100644 --- a/AADDeviceTrust.Client/Public/New-AADDeviceTrustBody.ps1 +++ b/AADDeviceTrust.Client/Public/New-AADDeviceTrustBody.ps1 @@ -13,10 +13,11 @@ function New-AADDeviceTrustBody { Author: Nickolaj Andersen Contact: @NickolajA Created: 2022-03-14 - Updated: 2022-03-14 + Updated: 2023-05-10 Version history: 1.0.0 - (2022-03-14) Script created + 1.0.1 - (2023-05-10) @AzureToTheMax - Updated to no longer use Thumbprint field, now redundant. #> [CmdletBinding(SupportsShouldProcess = $true)] param() @@ -32,11 +33,11 @@ function New-AADDeviceTrustBody { DeviceName = $env:COMPUTERNAME DeviceID = $AzureADDeviceID Signature = $Signature - Thumbprint = $CertificateThumbprint + #Thumbprint = $CertificateThumbprint PublicKey = $PublicKeyBytesEncoded } # Handle return value return $BodyTable } -} \ No newline at end of file +} From f17419a18b488288a323f5b1ef836c021adc437d Mon Sep 17 00:00:00 2001 From: Max Allen <125095090+AzureToTheMax@users.noreply.github.com> Date: Wed, 10 May 2023 21:18:43 -0400 Subject: [PATCH 09/29] Update New-HashString.ps1 Just adding a note for future someone. AzureToTheMax was here - this function does not appear to be used anywhere? If it is, it may need to be updated to accept a full PEM and use the X502 class like the others. --- AADDeviceTrust.FunctionApp/Public/New-HashString.ps1 | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/AADDeviceTrust.FunctionApp/Public/New-HashString.ps1 b/AADDeviceTrust.FunctionApp/Public/New-HashString.ps1 index c73d2df..39d4077 100644 --- a/AADDeviceTrust.FunctionApp/Public/New-HashString.ps1 +++ b/AADDeviceTrust.FunctionApp/Public/New-HashString.ps1 @@ -17,6 +17,9 @@ function New-HashString { Version history: 1.0.0 - (2021-08-23) Function created + + #AzureToTheMax was here - this function does not appear to be used anywhere? If it is, it may need to be updated to accept a full PEM and use the X502 class like the others. + #> param( [parameter(Mandatory = $true, HelpMessage = "Specify a Base64 encoded value for which a hash will be computed.")] @@ -39,4 +42,4 @@ function New-HashString { # Handle return value return $ComputedHashString } -} \ No newline at end of file +} From a681de111d27cbd10b6d1cd3bc822ae97f77fabd Mon Sep 17 00:00:00 2001 From: Max Allen <125095090+AzureToTheMax@users.noreply.github.com> Date: Wed, 10 May 2023 21:20:56 -0400 Subject: [PATCH 10/29] 1.0.1 1. Updated Thumbprint compare to use actual PEM cert via X502 class rather than simply a passed and separate thumbprint value. 2. Updated Hash compare to use full PEM cert via the X502 class, pull out just the public key data, and compare from that like before. --- ...st-AzureADDeviceAlternativeSecurityIds.ps1 | 25 ++++++++++++++++--- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/AADDeviceTrust.FunctionApp/Public/Test-AzureADDeviceAlternativeSecurityIds.ps1 b/AADDeviceTrust.FunctionApp/Public/Test-AzureADDeviceAlternativeSecurityIds.ps1 index 99630bc..0200d10 100644 --- a/AADDeviceTrust.FunctionApp/Public/Test-AzureADDeviceAlternativeSecurityIds.ps1 +++ b/AADDeviceTrust.FunctionApp/Public/Test-AzureADDeviceAlternativeSecurityIds.ps1 @@ -19,10 +19,14 @@ function Test-AzureADDeviceAlternativeSecurityIds { Author: Nickolaj Andersen Contact: @NickolajA Created: 2021-06-07 - Updated: 2021-06-07 + Updated: 2023-02-10 Version history: 1.0.0 - (2021-06-07) Function created + 1.0.1 - (2023-02-10) @AzureToTheMax + 1. Updated Thumbprint compare to use actual PEM cert via X502 class rather than simply a passed and separate thumbprint value. + 2. Updated Hash compare to use full PEM cert via the X502 class, pull out just the public key data, and compare from that like before. + #> param( [parameter(Mandatory = $true, HelpMessage = "Specify the alternativeSecurityIds.Key property from an Azure AD device record.")] @@ -44,8 +48,13 @@ function Test-AzureADDeviceAlternativeSecurityIds { switch ($Type) { "Thumbprint" { + Write-Output "Using new X502 Thumbprint compare" + + # Convert Value (cert) passed back to X502 Object + $X502 = [System.Security.Cryptography.X509Certificates.X509Certificate2]::New([System.Convert]::FromBase64String($Value)) + # Validate match - if ($Value -match $AzureADDeviceAlternativeSecurityIds.Thumbprint) { + if ($X502.thumbprint -match $AzureADDeviceAlternativeSecurityIds.Thumbprint) { return $true } else { @@ -53,8 +62,16 @@ function Test-AzureADDeviceAlternativeSecurityIds { } } "Hash" { + Write-Output "Using new X502 hash compare" + + # Convert Value (cert) passed back to X502 Object + $X502 = [System.Security.Cryptography.X509Certificates.X509Certificate2]::New([System.Convert]::FromBase64String($Value)) + + # Pull out just the public key, removing extended values + $X502Pub = [System.Convert]::ToBase64String($X502.PublicKey.EncodedKeyValue.rawData) + # Convert from Base64 string to byte array - $DecodedBytes = [System.Convert]::FromBase64String($Value) + $DecodedBytes = [System.Convert]::FromBase64String($X502Pub) # Construct a new SHA256Managed object to be used when computing the hash $SHA256Managed = New-Object -TypeName "System.Security.Cryptography.SHA256Managed" @@ -75,4 +92,4 @@ function Test-AzureADDeviceAlternativeSecurityIds { } } } -} \ No newline at end of file +} From b15363666fcfe0a7916d751ca61d449af6791fa9 Mon Sep 17 00:00:00 2001 From: Max Allen <125095090+AzureToTheMax@users.noreply.github.com> Date: Wed, 10 May 2023 21:21:24 -0400 Subject: [PATCH 11/29] 1.0.1 @AzureToTheMax - Updated to use full PEM cert via X502, extract the public key, and perform test like before using that. --- .../Public/Test-Encryption.ps1 | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/AADDeviceTrust.FunctionApp/Public/Test-Encryption.ps1 b/AADDeviceTrust.FunctionApp/Public/Test-Encryption.ps1 index 881a808..1427160 100644 --- a/AADDeviceTrust.FunctionApp/Public/Test-Encryption.ps1 +++ b/AADDeviceTrust.FunctionApp/Public/Test-Encryption.ps1 @@ -19,10 +19,11 @@ function Test-Encryption { Author: Nickolaj Andersen / Thomas Kurth Contact: @NickolajA Created: 2021-06-07 - Updated: 2021-06-07 + Updated: 2023-05-10 Version history: 1.0.0 - (2021-06-07) Function created + 1.0.1 - (2023-05-10) @AzureToTheMax - Updated to use full PEM cert via X502, extract the public key, and perform test like before using that. Credits to Thomas Kurth for sharing his original C# code. #> @@ -40,8 +41,16 @@ function Test-Encryption { [string]$Content ) Process { - # Convert from Base64 string to byte array - $PublicKeyBytes = [System.Convert]::FromBase64String($PublicKeyEncoded) + + Write-Output "Using new X502 encryption test" + # Convert Value (cert) passed back to X502 Object + $X502 = [System.Security.Cryptography.X509Certificates.X509Certificate2]::New([System.Convert]::FromBase64String($PublicKeyEncoded)) + + # Pull out just the public key, removing extended values + $X502Pub = [System.Convert]::ToBase64String($X502.PublicKey.EncodedKeyValue.rawData) + + # Convert encoded public key from Base64 string to byte array + $PublicKeyBytes = [System.Convert]::FromBase64String($X502Pub) # Convert signature from Base64 string [byte[]]$Signature = [System.Convert]::FromBase64String($Signature) @@ -74,4 +83,4 @@ function Test-Encryption { # Verify the signature with the computed hash of the content using the public key $PublicKey.VerifyHash($ComputedHash, $Signature, [System.Security.Cryptography.HashAlgorithmName]::SHA256, [System.Security.Cryptography.RSASignaturePadding]::Pkcs1) } -} \ No newline at end of file +} From fd89dba925ae814ccd2431a47ec789cf3728bd32 Mon Sep 17 00:00:00 2001 From: Max Allen <125095090+AzureToTheMax@users.noreply.github.com> Date: Wed, 10 May 2023 21:23:11 -0400 Subject: [PATCH 12/29] 1.0.1 @AzureToTheMax - Fixed issue pertaining to Cloud PCs (Windows 365) devices ability to locate their AzureADDeviceID. From 0931ccf967e12cf406d1d59658888abe4162a6b1 Mon Sep 17 00:00:00 2001 From: Max Allen <125095090+AzureToTheMax@users.noreply.github.com> Date: Wed, 10 May 2023 21:23:50 -0400 Subject: [PATCH 13/29] 1.0.1 @AzureToTheMax - Fixed issue pertaining to Cloud PCs (Windows 365) devices ability to locate their AzureADDeviceID. From e4d7c79d32b7bec09bab4c14232032bd7df8573e Mon Sep 17 00:00:00 2001 From: Max Allen <125095090+AzureToTheMax@users.noreply.github.com> Date: Wed, 10 May 2023 21:38:32 -0400 Subject: [PATCH 14/29] 1.0.2 A mostly complete readme with all the new x509 class wording. It's late and I will need to re-read it again tomorrow. --- README.md | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 96bff63..a50be6e 100644 --- a/README.md +++ b/README.md @@ -18,35 +18,37 @@ On the client side, a table of information is built which will both serve to car * The devices name. * The devices Azure AD ID. -* The thumbprint of the certificate used when registering to Azure AD. * A copy of the computers public certificate which has been turned into a byte array then encoded as a Base64 string for ease of transport. -* And last but not least, a signature generated from the SHA256 hash of the devices Azure ID wihch was signed using the devices private certificate. +* And last but not least, a signature generated from the SHA256 hash of the devices Azure ID which was signed using the devices private certificate. ...And again, all of that data will be passed to the Function App along with any other data you add. Details on how to add more fields can be found in the use section. -Note: The signature is *not* an encrypted form of the SHA256 hash of the devices Azure ID, nor does it contian the hash of the Azure ID at all. It also does not contain the Private key. It is meerely a method to validate and authenticate a SHA256 hash, and thus the chunk of data it represents, when combined with the public certificate. How this comes into play is explained in the next section. +Note: The signature is *not* an encrypted form of the SHA256 hash of the devices Azure ID, nor does it contain the hash of the Azure ID at all. It also does not contain the Private key. It is merely a method to validate and authenticate a SHA256 hash, and thus the chunk of data it represents, when combined with the public certificate. How this comes into play is explained in the next section. ## Function App -When the Function App receieves a request, it will start by pulling the various information sent by the client out of the body of the request. The Function App will then use it's Graph permissions* to pull the full Azure AD record for the Azure AD Device ID provided in the request. As mentioned, this record contains a "alternativeSecurityIds" field with a key value that has a base64 represenation of the SHA1 thumbprint and SHA1 hash of the full X.509 public certificate used when the machine originally registered. +When the Function App receives a request, it will start by pulling the various information sent by the client out of the body of the request. The Function App will then use its Graph permissions* to pull the full Azure AD record for the Azure AD Device ID provided in the request. As mentioned, this record contains a "alternativeSecurityIds" field with a key value that has a base64 represenation of the SHA1 thumbprint and SHA1 hash of the full X.509 public certificate used when the machine originally registered. ***Function App needs Device.Read.All permissions** -1. The authentication then starts by confirming the SHA1 thumbprint provided in our request matches the SHA1 thumbprint stored in the alternativeSecurityIds/keys field. Technically, we didn't extract the hash from the key, but rather we provided it as a seperate field. Still, this confirms we at least know the correct thumnbprint. +1. The authentication then starts by taking the full PEM X.509 public cert provided in our request and pulling out the SHA1 thumbprint of the certificate. It then confirms that thumbprint matches the SHA1 thumbprint stored in the alternativeSecurityIds/keys field. With this, we can confirm that the public key we have provided in our request is at least related to the public key (or more so private key) originally used when the device registered with Azure. -2. Next, we confirm that the full SHA1 hash of the X.509 public cert that was provided matches the SHA1 hash of the devices public cert that was stored again in the alternativeSecurityIds/keys field. At this point, we know the public certificate provided is not just related to the same private certificate, but is indeed the exact same public certificate originally made when the device was registered. +2. Next, we confirm that the SHA1 hash of the entire public cert that was provided matches the SHA1 hash of the devices public cert that was stored in the alternativeSecurityIds/keys field. At this point, we know the public certificate provided is not just related to the same private certificate but is indeed the exact same public certificate originally made when the device was registered. -3. Now that we know our public key is legitimate, we are going to test the signature against the SHA256 hash of the devices Azure ID using that public key. In order to do this, we must take our Base64 encoded Public Key, turn it back into a byte array, and convert that back into a function RSA key. We then pull the devices Azure AD ID, this time from Azure itself, and again calculate it’s SHA256 hash. We can then use our public key to validate that signature against that hash. +3. Now that we know our public key is legitimate and not just some random key, we are going to test the signature against the SHA256 hash of the devices Azure ID using that public key. In order to do this, we must take our Base64 encoded Public Key, turn it back into a byte array, and convert that back into a functional RSA key. We then pull the devices Azure AD ID, this time from Azure itself, and again calculate it’s SHA256 hash. We can then use our public key to validate that signature against that hash proving that we also have the matching private key. 4. Lastly, we do a simple check to ensure that the Azure AD Device ID is enabled. With all this confirmed, we know that... * The request contained a valid Azure Device ID -* The request contained a valid thumbprint of that Azure Device's original registering certificate -* The request contained a -* That Azure Device ID is Enabled +* The request contained a public certificate with a thumbprint that matches the thumbprint stored in Azure. Now we know this public cert is at least related to the same private cert. +* The request contained a public certificate with a hash that matches the hash of the original public certificate stored in Azure. Now we know that this is the same public cert originally used to register the device. +* The signature file provided is indeed a signed copy of the devices Azure AD ID which, since we know this is the original public key, we can infer/know the original private key was used to sign it. +* That Azure Device ID in question is Enabled + +At this point, the device is authenticated and the remainder of the request (your custom code) can begin to process. # How to use AADDeviceTrust.Client module in a client-side script @@ -58,7 +60,7 @@ if (Test-AzureADDeviceRegistration -eq $true) { $BodyTable = New-AADDeviceTrustBody # Extend body table with custom data to be processed by Function App - # ... + $BodyTable.Add("Key", "Value") #Example Only # Send log data to Function App $URI = "https://.azurewebsites.net/api/?code=" @@ -79,7 +81,8 @@ Enable the module to be installed as a managed dependency by editing your requir 'AADDeviceTrust.FunctionApp' = '1.*' } ``` +You will also need to grant your Function App Device.Read.All Graph permissions using it's managed identity. This is done such that the Function App can pull the devices Azure AD records. -Another option would also be to clone this module from GitHub and include it in the modules folder of your Function App, to embedd it directly and not have a dependency to PSGallery. +Another option would also be to clone this module from GitHub and include it in the modules folder of your Function App, to embed it directly and not have a dependency to PSGallery. For a full sample Function App function, explore the code in \Samples\FunctionApp-MSI.ps1 in this repo. From 840251162ab6091461cfaf647ce4383c9dfd88be Mon Sep 17 00:00:00 2001 From: Max Allen <125095090+AzureToTheMax@users.noreply.github.com> Date: Tue, 16 May 2023 16:17:56 -0400 Subject: [PATCH 15/29] 1.0.2 --- README.md | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index a50be6e..3e88194 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ This module performs the device trust validation and can be embedded in most Fun Every Azure AD joined or hybrid Azure AD joined device has a computer certificate that was generated when registering the device to Azure AD. This device specific computer certificate's public and private keys are available locally on the device. When registering the device, a special field called the ["alternativeSecurityIds"](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-dvrj/f900e812-8f1c-4345-9ab0-b91111068651) is added to the Device's Azure record which contains a "key" field with a value that is a Base64 encoded representation of that same private/public key pairs SHA1 Thumbprint, as well as the entire public keys SHA1 hash. -The device trust validation functionality occurs in the following scenarios: +The device trust validation functionality occurs in the two parts: - Client-side data gathering - Function App data validation @@ -17,32 +17,33 @@ The device trust validation functionality occurs in the following scenarios: On the client side, a table of information is built which will both serve to carry the data needed to authenticate to our Function App, as well as any other payload required for your specific needs. By default, this table contains... * The devices name. -* The devices Azure AD ID. -* A copy of the computers public certificate which has been turned into a byte array then encoded as a Base64 string for ease of transport. +* A copy of the computers public certificate in PEM format which has been encoded as a Base64 string for ease of transport. * And last but not least, a signature generated from the SHA256 hash of the devices Azure ID which was signed using the devices private certificate. ...And again, all of that data will be passed to the Function App along with any other data you add. Details on how to add more fields can be found in the use section. -Note: The signature is *not* an encrypted form of the SHA256 hash of the devices Azure ID, nor does it contain the hash of the Azure ID at all. It also does not contain the Private key. It is merely a method to validate and authenticate a SHA256 hash, and thus the chunk of data it represents, when combined with the public certificate. How this comes into play is explained in the next section. +Note: The signature is *not* an encrypted form of the SHA256 hash of the devices Azure ID, nor does it contain the hash of the Azure ID at all. It also does not contain the Private key. It is merely a method to validate and authenticate a SHA256 hash, and thus the chunk of data it represents (the Azure ID), when combined with the public certificate. How this comes into play is explained in the next section. ## Function App -When the Function App receives a request, it will start by pulling the various information sent by the client out of the body of the request. The Function App will then use its Graph permissions* to pull the full Azure AD record for the Azure AD Device ID provided in the request. As mentioned, this record contains a "alternativeSecurityIds" field with a key value that has a base64 represenation of the SHA1 thumbprint and SHA1 hash of the full X.509 public certificate used when the machine originally registered. +When the Function App receives a request, it will start by pulling the various information sent by the client out of the body of the request. + +The first thing The Function App does is pull the devices Azure AD ID from the certificate provided. It will then use its Graph permissions* to pull the full Azure AD record for that Azure AD Device ID. As mentioned, this record contains a "alternativeSecurityIds" field with a key value that has a base64 representation of the SHA1 thumbprint and SHA1 hash of the full X.509 public certificate used when the machine originally registered. ***Function App needs Device.Read.All permissions** 1. The authentication then starts by taking the full PEM X.509 public cert provided in our request and pulling out the SHA1 thumbprint of the certificate. It then confirms that thumbprint matches the SHA1 thumbprint stored in the alternativeSecurityIds/keys field. With this, we can confirm that the public key we have provided in our request is at least related to the public key (or more so private key) originally used when the device registered with Azure. -2. Next, we confirm that the SHA1 hash of the entire public cert that was provided matches the SHA1 hash of the devices public cert that was stored in the alternativeSecurityIds/keys field. At this point, we know the public certificate provided is not just related to the same private certificate but is indeed the exact same public certificate originally made when the device was registered. +2. Next, we confirm that the SHA1 hash of the entire public key that was provided matches the SHA1 hash of the devices public key that was stored in the alternativeSecurityIds/keys field. At this point, we know the public certificate provided is not just related to the same private certificate but is indeed the exact same public certificate originally made when the device was registered. -3. Now that we know our public key is legitimate and not just some random key, we are going to test the signature against the SHA256 hash of the devices Azure ID using that public key. In order to do this, we must take our Base64 encoded Public Key, turn it back into a byte array, and convert that back into a functional RSA key. We then pull the devices Azure AD ID, this time from Azure itself, and again calculate it’s SHA256 hash. We can then use our public key to validate that signature against that hash proving that we also have the matching private key. +3. Now that we know our public key is legitimate and not just some random key, we are going to test the signature against the SHA256 hash of the devices Azure ID using that public key. In order to do this, we must take our Base64 encoded Public Key, turn it back into a byte array, and convert that back into a functional RSA key. We then pull the devices Azure AD ID, this time from Azure itself, and again calculate it’s SHA256 hash. We can then use our public key to validate that signature against that hash (the hash of the Azure ID) proving that we must also have the matching private key. 4. Lastly, we do a simple check to ensure that the Azure AD Device ID is enabled. With all this confirmed, we know that... -* The request contained a valid Azure Device ID +* The request contained a public certificate issued to a valid Azure Device ID * The request contained a public certificate with a thumbprint that matches the thumbprint stored in Azure. Now we know this public cert is at least related to the same private cert. * The request contained a public certificate with a hash that matches the hash of the original public certificate stored in Azure. Now we know that this is the same public cert originally used to register the device. * The signature file provided is indeed a signed copy of the devices Azure AD ID which, since we know this is the original public key, we can infer/know the original private key was used to sign it. @@ -73,6 +74,7 @@ else { For a full sample of the client-side script, explore the code in \Samples\ClientSide.ps1 in this repo. + # How to use AADDeviceTrust.FunctionApp module in a Function App Enable the module to be installed as a managed dependency by editing your requirements.psd1 file of the Function App, e.g. as shown below: @@ -86,3 +88,9 @@ You will also need to grant your Function App Device.Read.All Graph permissions Another option would also be to clone this module from GitHub and include it in the modules folder of your Function App, to embed it directly and not have a dependency to PSGallery. For a full sample Function App function, explore the code in \Samples\FunctionApp-MSI.ps1 in this repo. + + +# What certificate is sent to the Function App? +If you are curious to know and see the Certificate sent to the Function App in a friendly format, you can use the \Samples\Cert-Exporter-Sample.ps1 to generate a .CER file. This is done by taking the same Base64 content and slightly re-arranging it into the PEM format. The Base64 itself simply needs to be broken at every 64th character, then the appropriete cert start and end headers are added to the top and bottom. + +This will allow you to visually see the cert, visually confirm the private key is not attached, and confirm the cert is issues to your devices Azure AD ID. The file is generated in the run location of the script and will be named Cert.cer. From 2a3b03b7bb1cd7aa45ad69403389fb3b7f5906ed Mon Sep 17 00:00:00 2001 From: Max Allen <125095090+AzureToTheMax@users.noreply.github.com> Date: Tue, 16 May 2023 17:33:51 -0400 Subject: [PATCH 16/29] 1.0.2 1.0.1 - 2023-05-11 Updated to use X509 class 1.0.2 - 2023-05-14 Updated to pull Azure AD Device ID from the cert --- Samples/FunctionApp-MSI.ps1 | 74 +++++++++++++++++++++---------------- 1 file changed, 43 insertions(+), 31 deletions(-) diff --git a/Samples/FunctionApp-MSI.ps1 b/Samples/FunctionApp-MSI.ps1 index e2534e3..c8db3f3 100644 --- a/Samples/FunctionApp-MSI.ps1 +++ b/Samples/FunctionApp-MSI.ps1 @@ -1,3 +1,21 @@ + <# + .SYNOPSIS + HTTP Function App sample + + .NOTES + Author: Nickolaj Andersen, Maxton Allen + Contact: @NickolajA, @AzureToTheMax + Created: 2022-01-25 + Updated: 2023-05-14 + + Version history: + 1.0.0 - 2022-01-25 created + 1.0.1 - 2023-05-11 Updated to use X509 class + 1.0.2 - 2023-05-14 Updated to pull Azure AD Device ID from the cert + + + #> + using namespace System.Net # Input bindings are passed in via param block. @@ -10,49 +28,42 @@ param( ) # Functions -function Get-AuthToken { + +function Get-SelfGraphAuthToken { <# .SYNOPSIS - Retrieve an access token for the Managed System Identity. - - .DESCRIPTION - Retrieve an access token for the Managed System Identity. + Use the permissions granted to the Function App itself to obtain a Graph token for running Graph queries. + Returns a formated header for use with the original code. .NOTES - Author: Nickolaj Andersen - Contact: @NickolajA + Author: Nickolaj Andersen, Maxton Allen + Contact: @NickolajA, @AzureToTheMax Created: 2021-06-07 - Updated: 2021-06-07 + Updated: 2023-02-17 Version history: - 1.0.0 - (2021-06-07) Function created + 1.0.0 - 2021-06-07 Function created + 1.0.1 - 2023-02-17 @AzureToTheMax - Updated to API Version 2019-08-01 from 2017-09-01 #> Process { - # Get Managed Service Identity details from the Azure Functions application settings - $MSIEndpoint = $env:MSI_ENDPOINT - $MSISecret = $env:MSI_SECRET - # Define the required URI and token request params - $APIVersion = "2017-09-01" - $ResourceURI = "https://graph.microsoft.com" - $AuthURI = $MSIEndpoint + "?resource=$($ResourceURI)&api-version=$($APIVersion)" + $resourceURI = "https://graph.microsoft.com" + $tokenAuthURI = $env:IDENTITY_ENDPOINT + "?resource=$resourceURI&api-version=2019-08-01" + $tokenResponse = Invoke-RestMethod -Method Get -Headers @{"X-IDENTITY-HEADER"="$env:IDENTITY_HEADER"} -Uri $tokenAuthURI - # Call resource URI to retrieve access token as Managed Service Identity - $Response = Invoke-RestMethod -Uri $AuthURI -Method "Get" -Headers @{ "Secret" = "$($MSISecret)" } - - # Construct authentication header to be returned from function + $AuthenticationHeader = @{ - "Authorization" = "Bearer $($Response.access_token)" - "ExpiresOn" = $Response.expires_on + "Authorization" = "Bearer $($tokenResponse.access_token)" + "ExpiresOn" = $tokenResponse.expires_on } - - # Handle return value return $AuthenticationHeader } -} +}#end function + + # Retrieve authentication token -$AuthToken = Get-AuthToken +$AuthToken = Get-SelfGraphAuthToken # Initate variables $StatusCode = [HttpStatusCode]::OK @@ -60,13 +71,14 @@ $Body = [string]::Empty # Assign incoming request properties to variables $DeviceName = $Request.Body.DeviceName -$DeviceID = $Request.Body.DeviceID $Signature = $Request.Body.Signature -$Thumbprint = $Request.Body.Thumbprint $PublicKey = $Request.Body.PublicKey +#Get Device ID from the cert +$DeviceID = Get-AzureDeviceIDFromCertificate -Value $PublicKey + # Initiate request handling -Write-Output -InputObject "Initiating request handling for device named as '$($DeviceName)' with identifier: $($DeviceID)" +Write-Output -InputObject "Initiating request handling for device named as '$($DeviceName)' with cert containing Azure AD identifier: $($DeviceID)" # Retrieve Azure AD device record based on DeviceID property from incoming request body $AzureADDeviceRecord = Get-AzureADDeviceRecord -DeviceID $DeviceID -AuthToken $AuthToken @@ -74,7 +86,7 @@ if ($AzureADDeviceRecord -ne $null) { Write-Output -InputObject "Found trusted Azure AD device record with object identifier: $($AzureADDeviceRecord.id)" # Validate thumbprint from input request with Azure AD device record's alternativeSecurityIds details - if (Test-AzureADDeviceAlternativeSecurityIds -AlternativeSecurityIdKey $AzureADDeviceRecord.alternativeSecurityIds.key -Type "Thumbprint" -Value $Thumbprint) { + if (Test-AzureADDeviceAlternativeSecurityIds -AlternativeSecurityIdKey $AzureADDeviceRecord.alternativeSecurityIds.key -Type "Thumbprint" -Value $PublicKey) { Write-Output -InputObject "Successfully validated certificate thumbprint from inbound request" # Validate public key hash from input request with Azure AD device record's alternativeSecurityIds details @@ -129,4 +141,4 @@ else { Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ StatusCode = $StatusCode Body = $Body -}) \ No newline at end of file +}) From d33fa8d1ca4f9f5fdaad0beefe47ece4cdc14437 Mon Sep 17 00:00:00 2001 From: Max Allen <125095090+AzureToTheMax@users.noreply.github.com> Date: Tue, 16 May 2023 17:34:51 -0400 Subject: [PATCH 17/29] 1.0.0 --- Samples/Cert-Exporter-Sample.ps1 | 103 +++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 Samples/Cert-Exporter-Sample.ps1 diff --git a/Samples/Cert-Exporter-Sample.ps1 b/Samples/Cert-Exporter-Sample.ps1 new file mode 100644 index 0000000..b227e33 --- /dev/null +++ b/Samples/Cert-Exporter-Sample.ps1 @@ -0,0 +1,103 @@ + <# + .SYNOPSIS + Export the CERT to be uploaded in .cer/PEM format to the running directory. + + .DESCRIPTION + Export the CERT to be uploaded in .cer/PEM format to the running directory. Used for testing and validaiton. + Provides visual confirmation that the private key is not part of the cert. + Provides visual confirmation that the CN is the Azure AD Device ID. + + .NOTES + Author: Maxton Allen + Contact: @AzureToTheMax + Created: 2023-05-14 + Updated: 2023-05-14 + + Version history: + 1.0.0 - (2023-05-14) created + #> + + +function Get-AzureADRegistrationCertificateThumbprint { + <# + .SYNOPSIS + Get the thumbprint of the certificate used for Azure AD device registration. + + .DESCRIPTION + Get the thumbprint of the certificate used for Azure AD device registration. + + .NOTES + Author: Nickolaj Andersen + Contact: @NickolajA + Created: 2021-06-03 + Updated: 2021-06-03 + + Version history: + 1.0.0 - (2021-06-03) Function created + 1.0.1 - (2023-05-10) @AzureToTheMax Updated for Cloud PCs which don't have their thumbprint as their JoinInfo key name. + #> + Process { + # Define Cloud Domain Join information registry path + $AzureADJoinInfoRegistryKeyPath = "HKLM:\SYSTEM\CurrentControlSet\Control\CloudDomainJoin\JoinInfo" + + # Retrieve the child key name that is the thumbprint of the machine certificate containing the device identifier guid + $AzureADJoinInfoThumbprint = Get-ChildItem -Path $AzureADJoinInfoRegistryKeyPath | Select-Object -ExpandProperty "PSChildName" + # Check for a cert matching that thumbprint + $AzureADJoinCertificate = Get-ChildItem -Path "Cert:\LocalMachine\My" -Recurse | Where-Object { $PSItem.Thumbprint -eq $AzureADJoinInfoThumbprint } + + if($AzureADJoinCertificate -ne $null){ + # if a matching cert was found tied to that reg key (thumbprint) value, then that is the thumbprint and it can be returned. + $AzureADThumbprint = $AzureADJoinInfoThumbprint + + # Handle return value + return $AzureADThumbprint + + } else { + + # If a cert was not found, that reg key was not the thumbprint but can be used to locate the cert as it is likely the Azure ID which is in the certs common name. + $AzureADJoinCertificate = Get-ChildItem -Path "Cert:\LocalMachine\My" -Recurse | Where-Object { $PSItem.Subject -like "CN=$($AzureADJoinInfoThumbprint)" } + + #Pull thumbprint from cert + $AzureADThumbprint = $AzureADJoinCertificate.Thumbprint + + # Handle return value + return $AzureADThumbprint + } + + } +} + +$thumbprint = Get-AzureADRegistrationCertificateThumbprint +#Get the cert as base64 + $Certificate = Get-ChildItem -Path "Cert:\LocalMachine\My" -Recurse | Where-Object { $PSItem.Thumbprint -eq $Thumbprint } + if ($Certificate -ne $null) { + # Bring the cert into a X509 object + $X509 = [System.Security.Cryptography.X509Certificates.X509Certificate2]::New($Certificate) + #Set the type of export to perform + $type = [System.Security.Cryptography.X509Certificates.X509ContentType]::Cert + #Export the public cert + $PublicKeyBytes = $X509.Export($type, "") + + # Handle return value - convert to Base64 + $PublicKeyEncoded = [System.Convert]::ToBase64String($PublicKeyBytes) + } + +#Alter formatting + #Break it every 64 characters to make it into the CER format + $PublicKeyBroken = $PublicKeyEncoded | + ForEach-Object { + $line = $_ + + for ($i = 0; $i -lt $line.Length; $i += 64) + { + $length = [Math]::Min(64, $line.Length - $i) + $line.SubString($i, $length) + } + } + +#Set cer content to current working directory +Set-Content ".\cert.cer" "-----BEGIN CERTIFICATE----- +$PublicKeyBroken +-----END CERTIFICATE-----" + +#Your cert should now be in the working directory as cert.cer and can be opened natively by Windows for inspection. \ No newline at end of file From 4c0fbb3c3eed48718807374f6c91e515d42b06d9 Mon Sep 17 00:00:00 2001 From: Max Allen <125095090+AzureToTheMax@users.noreply.github.com> Date: Tue, 16 May 2023 17:36:14 -0400 Subject: [PATCH 18/29] 1.0.1 Updating to include Get-AzureADDeviceRecord --- AADDeviceTrust.FunctionApp/AADDeviceTrust.FunctionApp.psd1 | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/AADDeviceTrust.FunctionApp/AADDeviceTrust.FunctionApp.psd1 b/AADDeviceTrust.FunctionApp/AADDeviceTrust.FunctionApp.psd1 index 9f49e63..e280c0d 100644 --- a/AADDeviceTrust.FunctionApp/AADDeviceTrust.FunctionApp.psd1 +++ b/AADDeviceTrust.FunctionApp/AADDeviceTrust.FunctionApp.psd1 @@ -42,7 +42,8 @@ "Get-AzureADDeviceRecord", "New-HashString", "Test-AzureADDeviceAlternativeSecurityIds", - "Test-Encryption" + "Test-Encryption", + "Get-AzureADDeviceRecord" ) # Variables to export from this module @@ -75,4 +76,4 @@ } - \ No newline at end of file + From 98a6f53d09d4d2297e77093ff4af69ff08916925 Mon Sep 17 00:00:00 2001 From: Max Allen <125095090+AzureToTheMax@users.noreply.github.com> Date: Tue, 16 May 2023 17:37:40 -0400 Subject: [PATCH 19/29] 1.0.0 --- .../Get-AzureADDeviceIDFromCertificate.ps1 | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 AADDeviceTrust.FunctionApp/Public/Get-AzureADDeviceIDFromCertificate.ps1 diff --git a/AADDeviceTrust.FunctionApp/Public/Get-AzureADDeviceIDFromCertificate.ps1 b/AADDeviceTrust.FunctionApp/Public/Get-AzureADDeviceIDFromCertificate.ps1 new file mode 100644 index 0000000..00d6a1d --- /dev/null +++ b/AADDeviceTrust.FunctionApp/Public/Get-AzureADDeviceIDFromCertificate.ps1 @@ -0,0 +1,36 @@ +function Get-AzureDeviceIDFromCertificate { + <# + .SYNOPSIS + Used to pull the Azure Device ID from the provided Base64 certificate. + + .DESCRIPTION + Used by the function app to pull the Azure Device ID from the provided Base64 certificate. + + .NOTES + Author: Maxton Allen + Contact: @AzureToTheMax + Created: 2023-05-14 + Updated: 2023-05-14 + + Version history: + 1.0.0 - (2023-05-14) created + #> + param( + [parameter(Mandatory = $true, HelpMessage = "Specify a Base64 encoded value for which an Azure Device ID will be extracted.")] + [ValidateNotNullOrEmpty()] + [string]$Value + ) + Process { + # Convert Value (cert) passed back to X502 Object + $X502 = [System.Security.Cryptography.X509Certificates.X509Certificate2]::New([System.Convert]::FromBase64String($Value)) + + # Get the Subject (issued to) + $Subject = $X502.Subject + + # Remove the leading "CN=" + $SubjectTrimed = $Subject.TrimStart("CN=") + + # Handle return + Return $SubjectTrimed + } +} \ No newline at end of file From f62fa6d1d352bb50499c6476b629ce8d479fec0d Mon Sep 17 00:00:00 2001 From: Max Allen <125095090+AzureToTheMax@users.noreply.github.com> Date: Tue, 16 May 2023 17:38:30 -0400 Subject: [PATCH 20/29] 1.0.1 --- AADDeviceTrust.FunctionApp/AADDeviceTrust.FunctionApp.psd1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AADDeviceTrust.FunctionApp/AADDeviceTrust.FunctionApp.psd1 b/AADDeviceTrust.FunctionApp/AADDeviceTrust.FunctionApp.psd1 index e280c0d..59ccf21 100644 --- a/AADDeviceTrust.FunctionApp/AADDeviceTrust.FunctionApp.psd1 +++ b/AADDeviceTrust.FunctionApp/AADDeviceTrust.FunctionApp.psd1 @@ -43,7 +43,7 @@ "New-HashString", "Test-AzureADDeviceAlternativeSecurityIds", "Test-Encryption", - "Get-AzureADDeviceRecord" + "Get-AzureADDeviceIDFromCertificate" ) # Variables to export from this module From 35113dc2ac7b4c38a1a39e6aa6295296b0adbc15 Mon Sep 17 00:00:00 2001 From: Max Allen <125095090+AzureToTheMax@users.noreply.github.com> Date: Tue, 16 May 2023 17:38:52 -0400 Subject: [PATCH 21/29] 1.0.0 --- .../Public/Get-AzureADDeviceIDFromCertificate.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/AADDeviceTrust.FunctionApp/Public/Get-AzureADDeviceIDFromCertificate.ps1 b/AADDeviceTrust.FunctionApp/Public/Get-AzureADDeviceIDFromCertificate.ps1 index 00d6a1d..72eacee 100644 --- a/AADDeviceTrust.FunctionApp/Public/Get-AzureADDeviceIDFromCertificate.ps1 +++ b/AADDeviceTrust.FunctionApp/Public/Get-AzureADDeviceIDFromCertificate.ps1 @@ -1,4 +1,4 @@ -function Get-AzureDeviceIDFromCertificate { +function Get-AzureADDeviceIDFromCertificate { <# .SYNOPSIS Used to pull the Azure Device ID from the provided Base64 certificate. @@ -33,4 +33,4 @@ function Get-AzureDeviceIDFromCertificate { # Handle return Return $SubjectTrimed } -} \ No newline at end of file +} From 387d51f104dbdf10496fc951e181ea892fe0665f Mon Sep 17 00:00:00 2001 From: Max Allen <125095090+AzureToTheMax@users.noreply.github.com> Date: Tue, 16 May 2023 17:39:37 -0400 Subject: [PATCH 22/29] 1.0.1 Fixing Get-AzureADDeviceIDFromCertificate name --- Samples/FunctionApp-MSI.ps1 | 204 ++++++++++++++++++------------------ 1 file changed, 102 insertions(+), 102 deletions(-) diff --git a/Samples/FunctionApp-MSI.ps1 b/Samples/FunctionApp-MSI.ps1 index c8db3f3..67b38b7 100644 --- a/Samples/FunctionApp-MSI.ps1 +++ b/Samples/FunctionApp-MSI.ps1 @@ -16,129 +16,129 @@ #> -using namespace System.Net + using namespace System.Net -# Input bindings are passed in via param block. -param( - [Parameter(Mandatory = $true)] - $Request, - - [Parameter(Mandatory = $false)] - $TriggerMetadata -) - -# Functions - -function Get-SelfGraphAuthToken { - <# - .SYNOPSIS - Use the permissions granted to the Function App itself to obtain a Graph token for running Graph queries. - Returns a formated header for use with the original code. + # Input bindings are passed in via param block. + param( + [Parameter(Mandatory = $true)] + $Request, - .NOTES - Author: Nickolaj Andersen, Maxton Allen - Contact: @NickolajA, @AzureToTheMax - Created: 2021-06-07 - Updated: 2023-02-17 + [Parameter(Mandatory = $false)] + $TriggerMetadata + ) - Version history: - 1.0.0 - 2021-06-07 Function created - 1.0.1 - 2023-02-17 @AzureToTheMax - Updated to API Version 2019-08-01 from 2017-09-01 - #> - Process { - - $resourceURI = "https://graph.microsoft.com" - $tokenAuthURI = $env:IDENTITY_ENDPOINT + "?resource=$resourceURI&api-version=2019-08-01" - $tokenResponse = Invoke-RestMethod -Method Get -Headers @{"X-IDENTITY-HEADER"="$env:IDENTITY_HEADER"} -Uri $tokenAuthURI - + # Functions + + function Get-SelfGraphAuthToken { + <# + .SYNOPSIS + Use the permissions granted to the Function App itself to obtain a Graph token for running Graph queries. + Returns a formated header for use with the original code. + + .NOTES + Author: Nickolaj Andersen, Maxton Allen + Contact: @NickolajA, @AzureToTheMax + Created: 2021-06-07 + Updated: 2023-02-17 - $AuthenticationHeader = @{ - "Authorization" = "Bearer $($tokenResponse.access_token)" - "ExpiresOn" = $tokenResponse.expires_on + Version history: + 1.0.0 - 2021-06-07 Function created + 1.0.1 - 2023-02-17 @AzureToTheMax - Updated to API Version 2019-08-01 from 2017-09-01 + #> + Process { + + $resourceURI = "https://graph.microsoft.com" + $tokenAuthURI = $env:IDENTITY_ENDPOINT + "?resource=$resourceURI&api-version=2019-08-01" + $tokenResponse = Invoke-RestMethod -Method Get -Headers @{"X-IDENTITY-HEADER"="$env:IDENTITY_HEADER"} -Uri $tokenAuthURI + + + $AuthenticationHeader = @{ + "Authorization" = "Bearer $($tokenResponse.access_token)" + "ExpiresOn" = $tokenResponse.expires_on + } + return $AuthenticationHeader } - return $AuthenticationHeader - } -}#end function - - - -# Retrieve authentication token -$AuthToken = Get-SelfGraphAuthToken - -# Initate variables -$StatusCode = [HttpStatusCode]::OK -$Body = [string]::Empty - -# Assign incoming request properties to variables -$DeviceName = $Request.Body.DeviceName -$Signature = $Request.Body.Signature -$PublicKey = $Request.Body.PublicKey - -#Get Device ID from the cert -$DeviceID = Get-AzureDeviceIDFromCertificate -Value $PublicKey - -# Initiate request handling -Write-Output -InputObject "Initiating request handling for device named as '$($DeviceName)' with cert containing Azure AD identifier: $($DeviceID)" - -# Retrieve Azure AD device record based on DeviceID property from incoming request body -$AzureADDeviceRecord = Get-AzureADDeviceRecord -DeviceID $DeviceID -AuthToken $AuthToken -if ($AzureADDeviceRecord -ne $null) { - Write-Output -InputObject "Found trusted Azure AD device record with object identifier: $($AzureADDeviceRecord.id)" - - # Validate thumbprint from input request with Azure AD device record's alternativeSecurityIds details - if (Test-AzureADDeviceAlternativeSecurityIds -AlternativeSecurityIdKey $AzureADDeviceRecord.alternativeSecurityIds.key -Type "Thumbprint" -Value $PublicKey) { - Write-Output -InputObject "Successfully validated certificate thumbprint from inbound request" - - # Validate public key hash from input request with Azure AD device record's alternativeSecurityIds details - if (Test-AzureADDeviceAlternativeSecurityIds -AlternativeSecurityIdKey $AzureADDeviceRecord.alternativeSecurityIds.key -Type "Hash" -Value $PublicKey) { - Write-Output -InputObject "Successfully validated certificate SHA256 hash value from inbound request" - - $EncryptionVerification = Test-Encryption -PublicKeyEncoded $PublicKey -Signature $Signature -Content $AzureADDeviceRecord.deviceId - if ($EncryptionVerification -eq $true) { - Write-Output -InputObject "Successfully validated inbound request came from a trusted Azure AD device record" - - # Validate that the inbound request came from a trusted device that's not disabled - if ($AzureADDeviceRecord.accountEnabled -eq $true) { - Write-Output -InputObject "Azure AD device record was validated as enabled" - - # - # - # Place your code here, at this stage incoming request has been validated as trusted - # - # + }#end function + + + + # Retrieve authentication token + $AuthToken = Get-SelfGraphAuthToken + + # Initate variables + $StatusCode = [HttpStatusCode]::OK + $Body = [string]::Empty + + # Assign incoming request properties to variables + $DeviceName = $Request.Body.DeviceName + $Signature = $Request.Body.Signature + $PublicKey = $Request.Body.PublicKey + + #Get Device ID from the cert + $DeviceID = Get-AzureADDeviceIDFromCertificate -Value $PublicKey + + # Initiate request handling + Write-Output -InputObject "Initiating request handling for device named as '$($DeviceName)' with cert containing Azure AD identifier: $($DeviceID)" + + # Retrieve Azure AD device record based on DeviceID property from incoming request body + $AzureADDeviceRecord = Get-AzureADDeviceRecord -DeviceID $DeviceID -AuthToken $AuthToken + if ($AzureADDeviceRecord -ne $null) { + Write-Output -InputObject "Found trusted Azure AD device record with object identifier: $($AzureADDeviceRecord.id)" + + # Validate thumbprint from input request with Azure AD device record's alternativeSecurityIds details + if (Test-AzureADDeviceAlternativeSecurityIds -AlternativeSecurityIdKey $AzureADDeviceRecord.alternativeSecurityIds.key -Type "Thumbprint" -Value $PublicKey) { + Write-Output -InputObject "Successfully validated certificate thumbprint from inbound request" + + # Validate public key hash from input request with Azure AD device record's alternativeSecurityIds details + if (Test-AzureADDeviceAlternativeSecurityIds -AlternativeSecurityIdKey $AzureADDeviceRecord.alternativeSecurityIds.key -Type "Hash" -Value $PublicKey) { + Write-Output -InputObject "Successfully validated certificate SHA256 hash value from inbound request" + + $EncryptionVerification = Test-Encryption -PublicKeyEncoded $PublicKey -Signature $Signature -Content $AzureADDeviceRecord.deviceId + if ($EncryptionVerification -eq $true) { + Write-Output -InputObject "Successfully validated inbound request came from a trusted Azure AD device record" + + # Validate that the inbound request came from a trusted device that's not disabled + if ($AzureADDeviceRecord.accountEnabled -eq $true) { + Write-Output -InputObject "Azure AD device record was validated as enabled" + + # + # + # Place your code here, at this stage incoming request has been validated as trusted + # + # + } + else { + Write-Output -InputObject "Trusted Azure AD device record validation for inbound request failed, record with deviceId '$($DeviceID)' is disabled" + $StatusCode = [HttpStatusCode]::Forbidden + $Body = "Disabled device record" + } } else { - Write-Output -InputObject "Trusted Azure AD device record validation for inbound request failed, record with deviceId '$($DeviceID)' is disabled" + Write-Warning -Message "Trusted Azure AD device record validation for inbound request failed, could not validate signed content from client" $StatusCode = [HttpStatusCode]::Forbidden - $Body = "Disabled device record" + $Body = "Untrusted request" } } else { - Write-Warning -Message "Trusted Azure AD device record validation for inbound request failed, could not validate signed content from client" + Write-Warning -Message "Trusted Azure AD device record validation for inbound request failed, could not validate certificate SHA256 hash value" $StatusCode = [HttpStatusCode]::Forbidden $Body = "Untrusted request" } } else { - Write-Warning -Message "Trusted Azure AD device record validation for inbound request failed, could not validate certificate SHA256 hash value" + Write-Warning -Message "Trusted Azure AD device record validation for inbound request failed, could not validate certificate thumbprint" $StatusCode = [HttpStatusCode]::Forbidden $Body = "Untrusted request" } } else { - Write-Warning -Message "Trusted Azure AD device record validation for inbound request failed, could not validate certificate thumbprint" + Write-Warning -Message "Trusted Azure AD device record validation for inbound request failed, could not find device with deviceId: $($DeviceID)" $StatusCode = [HttpStatusCode]::Forbidden $Body = "Untrusted request" } -} -else { - Write-Warning -Message "Trusted Azure AD device record validation for inbound request failed, could not find device with deviceId: $($DeviceID)" - $StatusCode = [HttpStatusCode]::Forbidden - $Body = "Untrusted request" -} - -# Associate values to output bindings by calling 'Push-OutputBinding'. -Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ - StatusCode = $StatusCode - Body = $Body -}) + + # Associate values to output bindings by calling 'Push-OutputBinding'. + Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ + StatusCode = $StatusCode + Body = $Body + }) From 8a182d4e4ab0b7fa7cb890e968254e612458f08e Mon Sep 17 00:00:00 2001 From: Max Allen <125095090+AzureToTheMax@users.noreply.github.com> Date: Tue, 16 May 2023 17:40:39 -0400 Subject: [PATCH 23/29] 1.0.2 --- .../Public/New-AADDeviceTrustBody.ps1 | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/AADDeviceTrust.Client/Public/New-AADDeviceTrustBody.ps1 b/AADDeviceTrust.Client/Public/New-AADDeviceTrustBody.ps1 index 4ba88f1..d58504c 100644 --- a/AADDeviceTrust.Client/Public/New-AADDeviceTrustBody.ps1 +++ b/AADDeviceTrust.Client/Public/New-AADDeviceTrustBody.ps1 @@ -13,17 +13,18 @@ function New-AADDeviceTrustBody { Author: Nickolaj Andersen Contact: @NickolajA Created: 2022-03-14 - Updated: 2023-05-10 + Updated: 2023-05-14 Version history: 1.0.0 - (2022-03-14) Script created - 1.0.1 - (2023-05-10) @AzureToTheMax - Updated to no longer use Thumbprint field, now redundant. + 1.0.1 - (2023-05-10) @AzureToTheMax - Updated to no longer use Thumbprint field, no redundant. + 1.0.2 - (2023-05-14) @AzureToTheMax - Updating to pull the Azure AD Device ID from the certificate itself. #> [CmdletBinding(SupportsShouldProcess = $true)] param() Process { # Retrieve required data for building the request body - $AzureADDeviceID = Get-AzureADDeviceID + $AzureADDeviceID = Get-AzureADDeviceID # Still needed to form the signature. $CertificateThumbprint = Get-AzureADRegistrationCertificateThumbprint $Signature = New-RSACertificateSignature -Content $AzureADDeviceID -Thumbprint $CertificateThumbprint $PublicKeyBytesEncoded = Get-PublicKeyBytesEncodedString -Thumbprint $CertificateThumbprint @@ -31,9 +32,9 @@ function New-AADDeviceTrustBody { # Construct client-side request header $BodyTable = [ordered]@{ DeviceName = $env:COMPUTERNAME - DeviceID = $AzureADDeviceID + #DeviceID = $AzureADDeviceID - Will be pulled from the key. Signature = $Signature - #Thumbprint = $CertificateThumbprint + #Thumbprint = $CertificateThumbprint - Will be pulled from the key. PublicKey = $PublicKeyBytesEncoded } From 1232f1bf77d88c21c111dd422fe91908347e39b3 Mon Sep 17 00:00:00 2001 From: Max Allen <125095090+AzureToTheMax@users.noreply.github.com> Date: Tue, 16 May 2023 18:22:15 -0400 Subject: [PATCH 24/29] 1.0.2 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3e88194..1cf9e9c 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ Note: The signature is *not* an encrypted form of the SHA256 hash of the devices When the Function App receives a request, it will start by pulling the various information sent by the client out of the body of the request. -The first thing The Function App does is pull the devices Azure AD ID from the certificate provided. It will then use its Graph permissions* to pull the full Azure AD record for that Azure AD Device ID. As mentioned, this record contains a "alternativeSecurityIds" field with a key value that has a base64 representation of the SHA1 thumbprint and SHA1 hash of the full X.509 public certificate used when the machine originally registered. +The first thing the Function App does is pull the devices Azure AD ID from the certificate provided. It will then use its Graph permissions* to pull the full Azure AD record for that Azure AD Device ID. As mentioned, this record contains a "alternativeSecurityIds" field with a key value that has a base64 representation of the SHA1 thumbprint and SHA1 hash of the full X.509 public certificate used when the machine originally registered. ***Function App needs Device.Read.All permissions** From de6e617b4ae09983622777309d489004948ba1c2 Mon Sep 17 00:00:00 2001 From: Max Allen <125095090+AzureToTheMax@users.noreply.github.com> Date: Mon, 22 May 2023 15:59:25 -0400 Subject: [PATCH 25/29] 1.0.2 Corrected Date --- .../Public/Test-AzureADDeviceAlternativeSecurityIds.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/AADDeviceTrust.FunctionApp/Public/Test-AzureADDeviceAlternativeSecurityIds.ps1 b/AADDeviceTrust.FunctionApp/Public/Test-AzureADDeviceAlternativeSecurityIds.ps1 index 0200d10..ebc0154 100644 --- a/AADDeviceTrust.FunctionApp/Public/Test-AzureADDeviceAlternativeSecurityIds.ps1 +++ b/AADDeviceTrust.FunctionApp/Public/Test-AzureADDeviceAlternativeSecurityIds.ps1 @@ -19,11 +19,11 @@ function Test-AzureADDeviceAlternativeSecurityIds { Author: Nickolaj Andersen Contact: @NickolajA Created: 2021-06-07 - Updated: 2023-02-10 + Updated: 2023-05-10 Version history: 1.0.0 - (2021-06-07) Function created - 1.0.1 - (2023-02-10) @AzureToTheMax + 1.0.1 - (2023-05-10) @AzureToTheMax 1. Updated Thumbprint compare to use actual PEM cert via X502 class rather than simply a passed and separate thumbprint value. 2. Updated Hash compare to use full PEM cert via the X502 class, pull out just the public key data, and compare from that like before. From 1a66a3fde1591d3aeb86e2ba619496045e0bc7c7 Mon Sep 17 00:00:00 2001 From: Max Allen <125095090+AzureToTheMax@users.noreply.github.com> Date: Wed, 7 Jun 2023 21:52:27 -0400 Subject: [PATCH 26/29] 2.1 Updating for PSObject example --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 1cf9e9c..ae1d380 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,8 @@ if (Test-AzureADDeviceRegistration -eq $true) { $BodyTable = New-AADDeviceTrustBody # Extend body table with custom data to be processed by Function App - $BodyTable.Add("Key", "Value") #Example Only + $BodyTable.Add("Key", "Value") # Example only. This format works for most situations. + $BodyTable.Add("EmbededPSObject", $EmbededPSObject) # Other situations such as embedding a PSObject/JSON may need alternate formatting such as this. # Send log data to Function App $URI = "https://.azurewebsites.net/api/?code=" From f9b667d93857dc10c99ab0495830bf82c467b8e6 Mon Sep 17 00:00:00 2001 From: Max Allen <125095090+AzureToTheMax@users.noreply.github.com> Date: Wed, 7 Jun 2023 21:54:05 -0400 Subject: [PATCH 27/29] 2.2 Updating for better examples in adding other values --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index ae1d380..d695368 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,7 @@ if (Test-AzureADDeviceRegistration -eq $true) { # Extend body table with custom data to be processed by Function App $BodyTable.Add("Key", "Value") # Example only. This format works for most situations. + $BodyTable.Add("Key2", "$($Value)") # Example only. This format works for most situations involving simple variables. $BodyTable.Add("EmbededPSObject", $EmbededPSObject) # Other situations such as embedding a PSObject/JSON may need alternate formatting such as this. # Send log data to Function App From 2ac5c36979b860cf2cf8017cbc40629a9bee9a80 Mon Sep 17 00:00:00 2001 From: Max Allen <125095090+AzureToTheMax@users.noreply.github.com> Date: Tue, 20 Jun 2023 19:46:47 -0400 Subject: [PATCH 28/29] 1.0.2 Fixing capital letter issue --- AADDeviceTrust.Client/Private/Get-AzureADDeviceID.ps1 | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/AADDeviceTrust.Client/Private/Get-AzureADDeviceID.ps1 b/AADDeviceTrust.Client/Private/Get-AzureADDeviceID.ps1 index e7c5f0e..9a5e071 100644 --- a/AADDeviceTrust.Client/Private/Get-AzureADDeviceID.ps1 +++ b/AADDeviceTrust.Client/Private/Get-AzureADDeviceID.ps1 @@ -10,11 +10,13 @@ function Get-AzureADDeviceID { Author: Nickolaj Andersen Contact: @NickolajA Created: 2021-05-26 - Updated: 2021-05-26 + Updated: 2023-06-20 Version history: 1.0.0 - (2021-05-26) Function created 1.0.1 - (2022-10-20) @AzureToTheMax - Fixed issue pertaining to Cloud PCs (Windows 365) devices ability to locate their AzureADDeviceID. + 1.0.2 - (2023-06-20) @AzureToTheMax - Fixed issue pertaining to Cloud PCs (Windows 365) devices where the reported AzureADDeviceID was in all capitals, breaking signature creation. + #> Process { # Define Cloud Domain Join information registry path @@ -28,6 +30,8 @@ function Get-AzureADDeviceID { if ($AzureADJoinCertificate -ne $null) { # Determine the device identifier from the subject name $AzureADDeviceID = ($AzureADJoinCertificate | Select-Object -ExpandProperty "Subject") -replace "CN=", "" + # Convert upper to lowercase. + $AzureADDeviceID = "$($AzureADDeviceID)".ToLower() # Handle return value return $AzureADDeviceID @@ -40,12 +44,16 @@ function Get-AzureADDeviceID { if ($AzureADJoinCertificate -ne $null){ # Cert is now found, extract Device ID from Common Name $AzureADDeviceID = ($AzureADJoinCertificate | Select-Object -ExpandProperty "Subject") -replace "CN=", "" + # Convert upper to lowercase. + $AzureADDeviceID = "$($AzureADDeviceID)".ToLower() # Handle return value return $AzureADDeviceID } else { # Last ditch effort, try and use the ThumbPrint (reg key) itself. $AzureADDeviceID=$AzureADJoinInfoThumbprint + # Convert upper to lowercase. + $AzureADDeviceID = "$($AzureADDeviceID)".ToLower() return $AzureADDeviceID } From 5ead527009ab1e01797ca8529e0b37a6bb4e7bf3 Mon Sep 17 00:00:00 2001 From: Max Allen <125095090+AzureToTheMax@users.noreply.github.com> Date: Tue, 5 Sep 2023 17:23:09 -0400 Subject: [PATCH 29/29] 1.1 Fixing spelling error in Synopsis / Description --- AADDeviceTrust.Client/Public/New-AADDeviceTrustBody.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/AADDeviceTrust.Client/Public/New-AADDeviceTrustBody.ps1 b/AADDeviceTrust.Client/Public/New-AADDeviceTrustBody.ps1 index d58504c..aecfe5e 100644 --- a/AADDeviceTrust.Client/Public/New-AADDeviceTrustBody.ps1 +++ b/AADDeviceTrust.Client/Public/New-AADDeviceTrustBody.ps1 @@ -1,10 +1,10 @@ function New-AADDeviceTrustBody { <# .SYNOPSIS - Construct the body with the elements for a sucessful device trust validation required by a Function App that's leveraging the AADDeviceTrust.FunctionApp module. + Construct the body with the elements for a successful device trust validation required by a Function App that's leveraging the AADDeviceTrust.FunctionApp module. .DESCRIPTION - Construct the body with the elements for a sucessful device trust validation required by a Function App that's leveraging the AADDeviceTrust.FunctionApp module. + Construct the body with the elements for a successful device trust validation required by a Function App that's leveraging the AADDeviceTrust.FunctionApp module. .EXAMPLE .\New-AADDeviceTrustBody.ps1