PTA.ps1

# This script contains utility functions for Cloud Web Application Proxy

# Registers an app to Cloud WAP
# Nov 10th 2019
function Register-PTAAgent
{
<#
    .SYNOPSIS
    Registers the PTA agent to Azure AD and creates a client certificate
 
    .DESCRIPTION
    Registers the PTA agent to Azure AD with given machine name and creates a client certificate
 
    .Example
    Register-AADIntPTAAgent -MachineName "server1.company.com"
 
    PTA agent registered as server1.company.com
    Certificate saved to PTA_client_certificate.pfx
 
    .Example
    $pt=Get-AADIntAccessTokenForPTA
    PS C:\>Register-AADIntPTAAgent -AccessToken $pt -MachineName "server1.company.com" -FileName server1.pfx
 
    PTA agent registered as server1.company.com
    Certificate saved to server1.pfx
    
#>

    [cmdletbinding()]
    Param(
        [Parameter(Mandatory=$True)]
        [String]$AccessToken,
        [Parameter(Mandatory=$True)]
        [String]$MachineName,
        [Parameter(Mandatory=$False)]
        [String]$FileName="PTA_client_certificate.pfx"
    )
    Process
    {
        # Set some variables
        $tenantId = Get-TenantID -AccessToken $AccessToken
        $OSLanguage="1033"
        $OSLocale="0409"
        $OSSku="8"
        $OSVersion="10.0.17763"
        
        # Create a private key and do something with it to get it stored
        $rsa=[System.Security.Cryptography.RSA]::Create(2048)
                
        # Initialize the Certificate Signing Request object
        $CN="" # The name doesn't matter
        $req = [System.Security.Cryptography.X509Certificates.CertificateRequest]::new($CN, $rsa, [System.Security.Cryptography.HashAlgorithmName]::SHA256,[System.Security.Cryptography.RSASignaturePadding]::Pkcs1)
        
        # Key usage
        $req.CertificateExtensions.Add([System.Security.Cryptography.X509Certificates.X509KeyUsageExtension]::new([System.Security.Cryptography.X509Certificates.X509KeyUsageFlags]::DigitalSignature -bor [System.Security.Cryptography.X509Certificates.X509KeyUsageFlags]::NonRepudiation -bor [System.Security.Cryptography.X509Certificates.X509KeyUsageFlags]::KeyEncipherment -bor [System.Security.Cryptography.X509Certificates.X509KeyUsageFlags]::DataEncipherment, $false))
        # TLS Web client authentication
        $oidCollection = [System.Security.Cryptography.OidCollection]::new()
        $oidCollection.Add([System.Security.Cryptography.Oid]::new("1.3.6.1.5.5.7.3.2")) | Out-Null
        $req.CertificateExtensions.Add([System.Security.Cryptography.X509Certificates.X509EnhancedKeyUsageExtension]::new($oidCollection, $true))

        # Add the public Key to the request
        $req.CertificateExtensions.Add([System.Security.Cryptography.X509Certificates.X509SubjectKeyIdentifierExtension]::new($req.PublicKey,$false))

        # Create the signing request and convert to Base 64
        $csr=$req.CreateSigningRequest()
        $b64Csr=[convert]::ToBase64String($csr)

        # Create the request body
        $body=@"
        <RegistrationRequest xmlns="http://schemas.datacontract.org/2004/07/Microsoft.ApplicationProxy.Common.Registration" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
            <Base64Csr>$b64Csr
</Base64Csr>
            <AuthenticationToken>$AccessToken</AuthenticationToken>
            <Base64Pkcs10Csr i:nil="true"/>
            <Feature>PassthroughAuthentication</Feature>
            <RegistrationRequestSettings>
                <SystemSettingsInformation i:type="a:SystemSettings" xmlns="http://schemas.datacontract.org/2004/07/Microsoft.ApplicationProxy.Common.RegistrationCommons" xmlns:a="http://schemas.datacontract.org/2004/07/Microsoft.ApplicationProxy.Common.Utilities.SystemSettings">
                    <a:MachineName>$machineName</a:MachineName>
                    <a:OsLanguage>$OSLanguage</a:OsLanguage>
                    <a:OsLocale>$OSLocale</a:OsLocale>
                    <a:OsSku>$OSSku</a:OsSku>
                    <a:OsVersion>$OSVersion</a:OsVersion>
                </SystemSettingsInformation>
                <PSModuleVersion>1.5.643.0</PSModuleVersion>
                <SystemSettings i:type="a:SystemSettings" xmlns:a="http://schemas.datacontract.org/2004/07/Microsoft.ApplicationProxy.Common.Utilities.SystemSettings">
                    <a:MachineName>$machineName</a:MachineName>
                    <a:OsLanguage>$OSLanguage</a:OsLanguage>
                    <a:OsLocale>$OSLocale</a:OsLocale>
                    <a:OsSku>$OSSku</a:OsSku>
                    <a:OsVersion>$OSVersion</a:OsVersion>
                </SystemSettings>
            </RegistrationRequestSettings>
            <TenantId>$tenantId</TenantId>
            <UserAgent>PassthroughAuthenticationConnector/1.5.643.0</UserAgent>
        </RegistrationRequest>
"@

        
        # Register the app and get the certificate
        $response = Invoke-RestMethod -Uri "https://$tenantId.registration.msappproxy.net/register/RegisterConnector" -Method Post -Body $body -Headers @{"Content-Type"="application/xml; charset=utf-8"}
        if($response.RegistrationResult.IsSuccessful -eq "true")
        {
            # Get the certificate and convert to byte array
            $b64Cert = $response.RegistrationResult.Certificate
            $binCert = [convert]::FromBase64String($b64Cert)
            
            # Create a new x509certificate
            $cert = [System.Security.Cryptography.X509Certificates.X509Certificate2]::new($binCert,"",[System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::UserKeySet -band [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable)

            # Store the private key so that it can be exported
            $cspParameters = [System.Security.Cryptography.CspParameters]::new()
            $cspParameters.ProviderName = "Microsoft Enhanced RSA and AES Cryptographic Provider"
            $cspParameters.ProviderType = 24
            $cspParameters.KeyContainerName ="AADInternals"
            
            # Set the private key
            $privateKey = [System.Security.Cryptography.RSACryptoServiceProvider]::new(2048,$cspParameters)
            $privateKey.ImportParameters($rsa.ExportParameters($true))
            $cert.PrivateKey = $privateKey

            # Export the certificate to pfx
            $binCert = $cert.Export([System.Security.Cryptography.X509Certificates.X509ContentType]::Pfx)
            $binCert | Set-Content $fileName -Encoding Byte

            # Remove the private key from the store
            $privateKey.PersistKeyInCsp=$false
            $privateKey.Clear()

            Write-Host "PTA agent registered as $machineName"
            Write-Host "Certificate saved to $fileName"
        }
        else
        {
            # Something went wrong
            Write-Error $response.RegistrationResult.ErrorMessage
        }
    }
}

function Get-BootstrapConfiguration
{
<#
    .SYNOPSIS
    Registers an app to Cloud WAP
 
    .DESCRIPTION
    Registers an app to Cloud Web Application Proxy
 
    .Example
    Get-AADIntLoginInformation -Domain outlook.com
 
     
 
    .Example
    Get-AADIntLoginInformation -UserName someone@company.com
 
              :
 
    
#>

    [cmdletbinding()]
    Param(
        [Parameter(ParameterSetName="AccessToken", Mandatory=$True)]
        [String]$AccessToken,
        [Parameter(ParameterSetName="TenantId", Mandatory=$True)]
        [String]$TenantId,
        [Parameter(Mandatory=$True)]
        [String]$MachineName,
        [Parameter(Mandatory=$False)]
        [String]$fileName="PTA_client_certificate.pfx"
    )
    Process
    {
        if(![string]::IsNullOrEmpty($AccessToken))
        {
            $TenantId = Get-TenantID -AccessToken $AccessToken
        }

        $fullPath = (Get-Item $fileName).FullName

        $OSLanguage="1033"
        $OSLocale="0409"
        $OSSku="8"
        $OSVersion="10.0.17763"
      
        $body=@"
        <BootstrapRequest xmlns="http://schemas.datacontract.org/2004/07/Microsoft.ApplicationProxy.Common.SignalerDataModel" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
            <BootstrapDataModelVersion>1.5.644.0</BootstrapDataModelVersion>
            <ConnectorId>12161898-a592-413a-b018-9756e89b71ce</ConnectorId>
            <ConnectorVersion>1.5.644.0</ConnectorVersion>
            <ConsecutiveFailures>118</ConsecutiveFailures>
            <CurrentProxyPortResponseMode>Primary</CurrentProxyPortResponseMode>
            <FailedRequestMetrics xmlns:a="http://schemas.datacontract.org/2004/07/Microsoft.ApplicationProxy.Common.BootstrapDataModel"/>
            <InitialBootstrap>true</InitialBootstrap>
            <IsProxyPortResponseFallbackDisabledFromRegistry>true</IsProxyPortResponseFallbackDisabledFromRegistry>
            <LatestDotNetVersionInstalled>461814</LatestDotNetVersionInstalled>
            <MachineName>$machineName</MachineName>
            <OperatingSystemLanguage>$OSLanguage</OperatingSystemLanguage>
            <OperatingSystemLocale>$OSLocale</OperatingSystemLocale>
            <OperatingSystemSKU>$OSSku</OperatingSystemSKU>
            <OperatingSystemVersion>$OSVersion</OperatingSystemVersion>
            <PerformanceMetrics xmlns:a="http://schemas.datacontract.org/2004/07/Microsoft.ApplicationProxy.Common.BootstrapDataModel"/>
            <ProxyDataModelVersion>1.5.644.0</ProxyDataModelVersion>
            <RequestId>$((New-Guid).ToString())</RequestId>
            <SubscriptionId>$TenantId</SubscriptionId>
            <SuccessRequestMetrics xmlns:a="http://schemas.datacontract.org/2004/07/Microsoft.ApplicationProxy.Common.BootstrapDataModel"/>
            <TriggerErrors/>
            <UpdaterStatus>Running</UpdaterStatus>
            <UseServiceBusTcpConnectivityMode>false</UseServiceBusTcpConnectivityMode>
            <UseSpnegoAuthentication>false</UseSpnegoAuthentication>
        </BootstrapRequest>
"@

        $cert = [System.Security.Cryptography.X509Certificates.X509Certificate2]::CreateFromCertFile($fullPath)

        $url="https://$TenantId.bootstrap.msappproxy.net/ConnectorBootstrap"
        # The cert must be "linked" to this web page by IE + it needs to be installed on the personal etc. store.
        $response = Invoke-WebRequest -Uri $url -Method Post -Certificate $cert -Body $body -ContentType "application/xml; charset=utf-8"
        
        [xml]$xmlResponse = $response.Content

        return $xmlResponse.BootstrapResponse.SignalingListenerEndpoints.SignalingListenerEndpointSettings

        
    }
}


function Get-SASToken
{

    [cmdletbinding()]
    Param(
        [Parameter(Mandatory=$True)]
        [String]$Url,
        [Parameter(Mandatory=$True)]
        [String]$Key,
        [Parameter(Mandatory=$True)]
        [String]$KeyName
    )
    Process
    {
        # Create the HMAC object
        $keyBytes=[Text.Encoding]::UTF8.GetBytes($Key)
        $hmac = [System.Security.Cryptography.HMACSHA256]::new($keyBytes)

        # Get the current time
        $expires=([DateTimeOffset]::Now.ToUnixTimeSeconds())

        # Form the string to sign (urlencoded uri + \n + expires)
        $namespace = $url.split("/")[2]
        $urlToSign = [System.Web.HttpUtility]::UrlEncode("https://$namespace/") + "`n" + [string]$expires
        $byteUrl=[Text.Encoding]::UTF8.GetBytes($encUrl)

        # Calculate the signature
        $byteHash = $hmac.ComputeHash($byteUrl)
        $signature = [System.Convert]::ToBase64String($byteHash)

        # Form the token
        $SASToken = "SharedAccessSignature sr=" + [System.Web.HttpUtility]::UrlEncode($Url) + "&sig=" + [System.Web.HttpUtility]::UrlEncode($signature) + "&se=" + $expires + "&skn=" + $KeyName

        return $SASToken
    }
}