App Proxy Custom Domain Certificate

Hi,

I’m hoping that there’s a powershell wiz who’s able to give me a little bit of assistance on a Post Deployment Script that I’m trying to develop and get working. It’s 90% there, just failing at the last hurdle.

I’m trying to get Certify the Web to upload a certificate to Azure Application Proxy. I’ve got the script to the stage where it adds a password to the pfx file passed to it from Certify, and connects to AzureAD without a problem, and I can even get the ObjectID from Azure which suggests that it’s connected OK. However when I’m running the Set-AzureADApplicationProxyApplicationCustomDomainCertificate module, I’m getting an ‘Object reference not set to an instance of an object’ error. Now this is happening both when I’m running the script and when I’m running through the steps manually as well.

Any help would be appreciated.

Cheers

Jim

param($result)

set-alias ps64 “$env:C:\Windows\System32\WindowsPowerShell\v1.0\Powershell.exe”

ps64 -args $result -command {

$result = $args[0]

#Certificate Location Passed from CertifyTheWeb Application
$pfxpath = $result.ManagedItem.CertificatePath
#Location of the new certificate
$pfxNewPath = “c:\scripts\remotedesktop.pfx”

#Gets the Certificate passed Location Passed above
$mypfx = Get-PfxData -FilePath $pfxpath

#Text String to password protect the cert
$PfxPassword = ConvertTo-SecureString -String [REMOVED] -AsPlainText -Force

#Export the provided certificate to a new file with password protection (needed to be able to upload to Azure Application Proxy)
Export-PfxCertificate -PFXData $mypfx -FilePath $pfxNewPath -Password $PfxPassword

#Connect to AzureAD by obtaining a new refresh token

The refresh token

$refresh_token="[REMOVED]"

Tenant id and account id

$tenant_id = “[REMOVED]”
$account = “[REMOVED]”

1b730954-1685-4b74-9bfd-dac224a7b894 is a public client from Microsoft

$clientId = “1b730954-1685-4b74-9bfd-dac224a7b894”
$uri = “https://login.microsoftonline.com/${tenant_id}/oauth2/token
$body = @{grant_type=‘refresh_token’;resource=‘https://graph.windows.net’;client_id=$clientId;refresh_token=$refresh_token}
$result = Invoke-RestMethod -Method Post -Uri $uri -Body $body
$accessToken = $result.access_token

Connect to AAD using the above details

Connect-AzureAD -TenantId $tenant_id -AadAccessToken $accessToken -AccountId $account

#Gets the AzureADApplication Object ID for the Remote Desktop App
$WebsiteAppName = “Remote Desktop”

$webAppId = (Get-AzureADApplication -SearchString $WebsiteAppName -ea Stop).ObjectId

Set-AzureADApplicationProxyApplicationCustomDomainCertificate -ObjectId $webAppId
-PfxFilePath $pfxNewPath `
-Password $PfxPassword

#Disconnects from Azure AD and cleans up Password Protected PFX file as no longer needed.
Disconnect-AzureAD
Remove-Item $pfxNewPath

}

Hi,

I’m not a powershell expert but things to check:
-https://docs.microsoft.com/en-us/powershell/module/azuread/get-azureadapplication?view=azureadps-2.0 - I noticied you’re using SearchString instead of filter- is $webAppId getting the expected value?

If your print the values to the console then try to just run that line manually does it work?

If could avoid copying the pfx file out with a new password by using the new Pfx password functionality (Settings > UI Settings > Custom PFX/Private key password and setting the password under Certificate > Advanced > Signing and Security)

Why are you using the ps64 wrapper? You maybe don’t need that as powershell is 64-bit by default from v4.x onwards of Certify:

set-alias ps64 “$env:C:\Windows\System32\WindowsPowerShell\v1.0\Powershell.exe”

ps64 -args $result -command {

$result = $args[0]
....
}

Hi Christopher

Thanks for the advice, it’s based on a script I found to update a LE Cert into the 4 Remote Desktop components hence the ps64 bit. I just remove the Remote Desktop bits and started adding the bits I needed piece by piece.

I tried using the pxf password in CTW, however it just kept erroring.

Ye I’ve printed all of the variables out, and they’re the values that I’m expecting, apart from the pxf password which you can’t print it easily so tried by installing the certificate manually into azure, with the password as set in the script and it worked soo it’s looking like it’s all down to the Set-AzureADApplica… cmdlet. Have logged with Microsoft support so will wait to hear back from them.

I have tried manually running it and it comes up with the same error message. :confused:

Cheers

Jim

Note that when using the pfx password option you need to re-request the certificate as your existing stored cert won’t have the pfx password set.

When the script runs its not running as your user account, it’s running as the certify background service which is local system. You can modify this by running the task as a different user under the task parameters tab.

@JimConacher I finally got something to work.

If you’re willing to be a beta tester, I would appreciate any feedback.

I will make some really clear directions on how to set up. If you’re unable to figure it out.

param ($result)

If (($result.IsSuccess -eq "$false") -and ($result.ManagedItem.CertificatePath -eq "null") -and ($result.ManagedItem.CertificatePreviousThumbprintHash -eq "null")) {

    Write-Host "Certify the Web: Failed in some capacity."
    Write-Host "Result are = $($result.IsSuccess)"
    Write-Host "Cert Path is null: $($result.ManagedItem.CertificatePath)"
    Write-Host "Missing previous thumbprint hash $($result.ManagedItem.CertificatePreviousThumbprintHash)"
    Exit
}

Import-Module Microsoft.Graph.Applications
 
Connect-MgGraph -NoWelcome -ClientId "< Your MS ClientID >" -TenantId "< Your MS Tenant ID >" -CertificateThumbprint "< Your Local Certificate Thumbprint >"
 
$applicationId = "< ID >" # You need to use a very specific ID.
$certPfxFilePath = $result.ManagedItem.CertificatePath
$certBytes = [System.IO.File]::ReadAllBytes($certPfxFilePath)
$base64String = [Convert]::ToBase64String($certBytes)
$certPassword = "< Your PFX password >" # Password that protects the pfx file
 
$params = @{
    onPremisesPublishing = @{
        verifiedCustomDomainKeyCredential = @{
            type="X509CertAndPassword";
            value = $base64String
        };

        verifiedCustomDomainPasswordCredential = @{ value = $certPassword };
    }
}
 
Update-MgBetaApplication -ApplicationId $applicationId -BodyParameter $params

@webprofusion Is there any way to pass the PFX password in PowerShell?

Hi, no there is currently no way to pull in the PFX password within the script. As an alternative if you have a mature script we could turn it into a built-in deployment task and build the PFX password set into it. This currently has “MgBeta” so I’m assuming it’s still early days for this.

This is a rough draft documentation on Azure application proxy.

Introduction

This document provides a step-by-step guide to set up and configure the necessary components for authenticating with Microsoft Graph using a self-signed certificate. To achieve this, the Application (Client) ID, Microsoft Tenant ID, and a locally generated self-signed certificate with its thumbprint.

Prerequisites

Before proceeding, have the following prerequisites:

  • PowerShell environment
    • Install-Module Microsoft.Graph -Scope AllUsers
  • Certify the Web service installed
  • Access to Azure portal with permissions to manage applications and certificates

Self-Signed Certificate Generation

To begin, generate a self-signed certificate on the server where Certify the Web is installed. Use the following PowerShell script to generate a certificate with a recommended lifespan of at least 5 years:

$cert = New-SelfSignedCertificate -Subject "MSGraph" -CertStoreLocation "cert:\LocalMachine\My" -NotAfter (Get-Date).AddYears(5)

This certificate will be used for authentication to the Microsoft Graph connection.

Certificate Installation

The generated certificate must be installed on the local machine, allowing Certify the Web service to utilize it. This can be done through the Certificates snap-in or using PowerShell.

Azure Certificate Publishing

To enable Azure to authenticate using the certificate, the certificate needs to be published in Azure. Perform the following steps:

  1. Obtain the thumbprint of the locally generated certificate.

[!danger]
Only upload the public key, not the private key.

  1. Publish the certificate in Azure, associating it with the respective application.
    1. On the App registration on the left hand side: “:old_key:Certificates & secrets”
    2. Click on “Certificates”
    3. Click on “:arrow_up: Upload certificate”
    4. Click on “:file_folder:Upload a certificate (public key) with one of the following file types: .cer, .pem, .crt”
    5. Description: e.g. Server-CTW-01 Server Auth
    6. Click ‘Add’

Azure API Permissions

[!bug]
I’m still beta testing. Trying to figure out the least amount of permission that I need in order to accomplish this goal. Please use discretion on how much permissions is given.

  1. On the left hand side click on: “API Permission”
    1.

Certify the Web Result Handling

The PowerShell script below handles the result obtained from Certify the Web and ensures the success and integrity of the certificate-related operations.

param ($result)

# Check for failure conditions
if (($result.IsSuccess -eq "$false") -and 
    ($result.ManagedItem.CertificatePath -eq "null") -and 
    ($result.ManagedItem.CertificatePreviousThumbprintHash -eq "null") -and 
    ($result.ManagedItem.CertificatePreviousThumbprintHash -eq $result.ManagedItem.CertificateThumbprintHash)) {

    Write-Host "`r`n`#-------------------------------#"
    Write-Host "Certify the Web: Failed in some capacity."
    Write-Host "Result: $($result.IsSuccess)"
    Write-Host "Cert Path is null: $($result.ManagedItem.CertificatePath)"
    Write-Host "Missing previous thumbprint hash $($result.ManagedItem.CertificatePreviousThumbprintHash)"
    Write-Host "Previous and current thumbprint hash match, which cannot be allowed."
    Write-Host "#-------------------------------#`r`n"
    Exit
}

# Display success information
Write-Host "`r`n`#-------------------------------#"
Write-Host "Result Success: $($result.IsSuccess)"
Write-Host "Cert Path: $($result.ManagedItem.CertificatePath)"
Write-Host "Thumbprint verification successful. Previous Thumbprint Hash: $($result.ManagedItem.CertificatePreviousThumbprintHash), Current Thumbprint Hash: $($result.ManagedItem.CertificateThumbprintHash) "
Write-Host "#-------------------------------#`r`n"

Microsoft Graph Connection Configuration

The final part involves configuring the Microsoft Graph connection using the obtained Application (Client) ID, Tenant ID, and the local certificate thumbprint.

$ApplicationID = "<Application (client) ID>"
Import-Module Microsoft.Graph.Applications

# Connect to Microsoft Graph using the provided credentials
Connect-MgGraph -NoWelcome -ClientId $ApplicationID -TenantId "<MS Tenant ID>" -CertificateThumbprint "<Your Local Certificate Thumbprint>"

# Obtain the Application ID from the App Registration
$Id = Get-MgApplication | Where-Object {$_.AppId -eq $ApplicationID} | Select-Object -ExpandProperty Id

# Prepare certificate information for publishing in Azure
$certPfxFilePath = $result.ManagedItem.CertificatePath
$certBytes = [System.IO.File]::ReadAllBytes($certPfxFilePath)
$base64String = [Convert]::ToBase64String($certBytes)
$certPassword = "<PFX password>"

# Define parameters for updating the application with certificate information
$params = @{
    onPremisesPublishing = @{
        verifiedCustomDomainKeyCredential = @{
            type="X509CertAndPassword";
            value = $base64String
        };
        verifiedCustomDomainPasswordCredential = @{ value = $certPassword };
    }
}

# Update the application with the certificate information
Update-MgBetaApplication -ApplicationId $Id -BodyParameter $params

Note: The beta version of the module is used here since the stable version may lack the required parameters.

By following these steps, for a well-configured environment for authenticating with Microsoft Graph using a self-signed certificate. Ensure to keep sensitive information secure and follow best practices for certificate management.

Note: This documentation assumes basic knowledge of PowerShell and Certify the Web.

1 Like

This is my 4th version. Please let me know if you run into any problems.

#-----------------------------------------------#
#------CTW_ApplicationProxy---------------------#
#------Author: Taylor D. Marchetta--------------#
#------https://blog.marchetta.me----------------#
#------Verison 4.0.0----------------------------#
#-----------------------------------------------#

# Hello, I put a lot of work and effort into getting this to work. If you find this useful. Please consider donating to my buymeacoffee.com. https://www.buymeacoffee.com/tdmarchetta
# If you have any other questions. feel free to reach out to me on email: [email protected]

param ($result) # This is the result object from Certify the Web. It contains the path to the certificate and the thumbprint of the certificate. This is used to update the Azure App Registration with the new certificate.

#------Variables------------#
$ApplicationID = "< App ID >" # Application ID
$TenantID = "< Tenant ID >" # Tenant ID
$authCertThumbprint = "< Self sign TLS cert >" # Thumbprint of the certificate that was generated on the server
$certPFXPassword = "< PFX Password >" # Password that protects the pfx file
#------End Variables--------#

If (($result.IsSuccess -eq "$false") -and ($result.ManagedItem.CertificatePath -eq "null") -and ($result.ManagedItem.CertificatePreviousThumbprintHash -eq "null") -and ($result.ManagedItem.CertificatePreviousThumbprintHash -eq $result.ManagedItem.CertificateThumbprintHash)) { 
    Write-Host "`r`n`#-------------------------------#"
    Write-Host DateTime: $(Get-Date -Format "MM-dd-yyyy HH:mm:ss")
    Write-Host "Certify the Web: Failed in some capacity."
    Write-Host "Result are: $($result.IsSuccess)"
    Write-Host "Cert Path is null: $($result.ManagedItem.CertificatePath)"
    Write-Host "Missing previous thumbprint hash: $($result.ManagedItem.CertificatePreviousThumbprintHash) is thumbprint null? "
    Write-Host "The previous thumbprint hash: $($result.ManagedItem.CertificatePreviousThumbprintHash) and current thumbprint hash: $($result.ManagedItem.CertificateThumbprintHash) match which cannot be allowed."
    Write-Host "#-------------------------------#`r`n"
    Exit
}

Write-Host "`r`n`#-------------------------------#"
Write-Host DateTime: $(Get-Date -Format "MM-dd-yyyy HH:mm:ss")

# Get the certificate from the local machine's Personal certificate store
$authCert = Get-Item -Path Cert:\LocalMachine\My\$authCertThumbprint

# Check if the certificate is found
if ($null -ne $authCert) {
    Write-Host "Certificate Expiration Date: $($authCert.NotAfter)"# Display the expiration date of the certificate
} else {
    Write-Host "Certificate with thumbprint $authCertThumbprint not found."
    Exit
}

Write-Host "Result Success: $($result.IsSuccess)"
Write-Host "Cert Path: $($result.ManagedItem.CertificatePath)"
Write-Host "The previous thumbprint hash and current thumbprint hash do not match. This is expected. Previous Thumbprint Hash: $($result.ManagedItem.CertificatePreviousThumbprintHash), Current Thumhprint Hash: $($result.ManagedItem.CertificateThumbprintHash)" 


Import-Module Microsoft.Graph.Applications
Connect-MgGraph -NoWelcome -ClientId $ApplicationID -TenantId $TenantID -CertificateThumbprint $authCertThumbprint # -ClientId: is also "Application (client) ID", Self-gen for 1 year certificate on server itself to auth with Azure

$Id = Get-MgApplication | Where-Object {$_.AppId -eq $ApplicationID} |  Select-Object -ExpandProperty Id # Id in App Registration
$certPfxFilePath = $result.ManagedItem.CertificatePath # Path to the certificate that was generated by Certify the Web
$certBytes = [System.IO.File]::ReadAllBytes($certPfxFilePath) # Read the certificate into a byte array
$base64String = [Convert]::ToBase64String($certBytes) # Convert the byte array to a base64 string
$certPassword = $certPFXPassword # Password that protects the pfx file
 
$params = @{ 
    onPremisesPublishing = @{
        verifiedCustomDomainKeyCredential = @{
            type="X509CertAndPassword"; 
            value = $base64String 
        };

        verifiedCustomDomainPasswordCredential = @{ value = $certPassword };
    }
}
 
Update-MgBetaApplication -ApplicationId $Id -BodyParameter $params # Right now I'm using the beta version of the module because the stable version doesn't have the onPremisesPublishing parameter.

Write-Host "Updated the Azure App Registration with the new certificate."
Write-Host "#-------------------------------#`r`n"
1 Like

@tdmarchetta thanks for sharing this script with the community.

If there are services like this which a lot of people will want to renew a certificates for we can always consider adding a built in task within Certify The Web (we already have Azure certificate deployment such as Key vault and Azure App Service). Generally we just need a show of hands that something would be useful.

1 Like

@webprofusion It would be very cool to have like a GitHub repository of all the add-on scripts. I know that, another thing to manage. But it would be very valuable to have. All the available scripts in one location.

1 Like

Sure thing, I thought we already had this somewhere but here is a new example scripts repo and I’ll link to that from the docs: GitHub - webprofusion/certify-script-examples: Example and user-contributed scripts (PowerShell etc) for custom certificate deployment in Certify The Web

Contributions welcome!