CertificateValidation/PublicCertHelper.psm1
<#############################################################
# # # Copyright (C) Microsoft Corporation. All rights reserved. # # # #############################################################> $ErrorActionPreference = 'Stop' # Explicitly importing it as ERCS VM does not have it by default Import-Module PKI Import-LocalizedData LocalizedData -Filename PublicCertHelper.Strings.psd1 -ErrorAction SilentlyContinue # workaround for https://github.com/PowerShell/JEA/issues/42 # can't use Import-PowerShellDataFile because PS5.1 Import-LocalizedData -FileName Microsoft.AzureStack.CertificateConfig.psd1 -BaseDirectory $PSScriptRoot -BindingVariable CertificateData $certificateDefaults = $CertificateData.CertificateDefaults $myCertPath = 'cert:\localmachine\my\' if ($standalone) { $publicCertRoot = $PSScriptRoot $WarnOnSelfSigned = $true } else { $publicCertRoot = 'C:\CloudDeployment\Setup\Certificates' } $armPublicCertInfo = @{Path="$publicCertRoot\AAD\ARM Public";RecordPrefix=@("management")} $armAdminCertInfo = @{Path="$publicCertRoot\AAD\ARM Admin";RecordPrefix=@("adminmanagement")} $publicPortalCertInfo = @{Path="$publicCertRoot\AAD\Public Portal";RecordPrefix=@("portal")} $adminPortalCertInfo = @{Path="$publicCertRoot\AAD\Admin Portal";RecordPrefix=@("adminportal")} $keyvaultCertInfo = @{Path="$publicCertRoot\AAD\KeyVault";RecordPrefix=@("*.vault")} $keyvaultAdminCertInfo = @{Path="$publicCertRoot\AAD\KeyVaultInternal";RecordPrefix=@("*.adminvault")} $acsTableCertInfo = @{Path="$publicCertRoot\AAD\ACSTable";RecordPrefix=@("*.table")} $acsQueueCertInfo = @{Path="$publicCertRoot\AAD\ACSQueue";RecordPrefix=@("*.queue")} $acsBlobCertInfo = @{Path="$publicCertRoot\AAD\ACSBlob";RecordPrefix=@("*.blob")} $adminHostingCertInfo = @{Path="$publicCertRoot\AAD\Admin Extension Host";RecordPrefix=@("*.adminhosting")} $publicHostingCertInfo = @{Path="$publicCertRoot\AAD\Public Extension Host";RecordPrefix=@("*.hosting")} $acrCertInfo = @{Path="$publicCertRoot\AAD\Container Registry";RecordPrefix=@("*.azsacr")} $adfsCertInfo = @{Path="$publicCertRoot\ADFS\ADFS";RecordPrefix=@("adfs")} $graphCertInfo = @{Path="$publicCertRoot\ADFS\Graph";RecordPrefix=@("graph")} $armADFSPublicCertInfo = @{Path="$publicCertRoot\ADFS\ARM Public";RecordPrefix=@("management")} $armADFSAdminCertInfo = @{Path="$publicCertRoot\ADFS\ARM Admin";RecordPrefix=@("adminmanagement")} $publicADFSPortalCertInfo = @{Path="$publicCertRoot\ADFS\Public Portal";RecordPrefix=@("portal")} $adminADFSPortalCertInfo = @{Path="$publicCertRoot\ADFS\Admin Portal";RecordPrefix=@("adminportal")} $keyvaultADFSCertInfo = @{Path="$publicCertRoot\ADFS\KeyVault";RecordPrefix=@("*.vault")} $keyvaultADFSAdminCertInfo = @{Path="$publicCertRoot\ADFS\KeyVaultInternal";RecordPrefix=@("*.adminvault")} $acsTableADFSCertInfo = @{Path="$publicCertRoot\ADFS\ACSTable";RecordPrefix=@("*.table")} $acsQueueADFSCertInfo = @{Path="$publicCertRoot\ADFS\ACSQueue";RecordPrefix=@("*.queue")} $acsBlobADFSCertInfo = @{Path="$publicCertRoot\ADFS\ACSBlob";RecordPrefix=@("*.blob")} $adminHostingADFSCertInfo = @{Path="$publicCertRoot\ADFS\Admin Extension Host";RecordPrefix=@("*.adminhosting")} $publicHostingADFSCertInfo = @{Path="$publicCertRoot\ADFS\Public Extension Host";RecordPrefix=@("*.hosting")} $acrADFSCertInfo = @{Path="$publicCertRoot\ADFS\Container Registry";RecordPrefix=@("*.azsacr")} # This function appends the region and domainFQDN to the RecordPrefix/es and returns the array for multiple endpoints function New-DNSList { Param ( [Parameter(Mandatory=$true)] [string[]] $RecordPrefixList, [Parameter(Mandatory=$true)] [String] $RegionExternalFQDN ) $dnsListCreated = @() foreach ($RecordPrefix in $RecordPrefixList) { $dnsListCreated += $RecordPrefix + ".$RegionExternalFQDN" } return $dnsListCreated } function New-SelfSignedCertificateWrapper { [CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact='Medium')] Param ( [Parameter(Mandatory=$true)] [string[]] $DnsRecord, [Parameter(Mandatory=$true)] [SecureString] $CertificatePassword, [Parameter(Mandatory=$true)] [string] $OutFilePath, [Parameter(Mandatory=$true)] [string] $RootCertThumbprint ) # Creating if the path does not exist as SecretRotation BVT needs it if(-not (Test-Path $OutFilePath)) { $null = New-Item -Path $OutFilePath -ItemType Directory } if(-not (Get-ChildItem -Path $OutFilePath -Filter '*.pfx')) { $OutFilePath = Join-Path -Path $OutFilePath -ChildPath "SSL.pfx" $rootCert = ( Get-ChildItem -path $RootCertThumbprint ) $certThumbprint = New-SelfSignedCertificate -KeyUsage DigitalSignature, KeyEncipherment -HashAlgorithm SHA256 -KeyUsageProperty All -KeyLength 4096 -Provider "Microsoft Enhanced RSA and AES Cryptographic Provider" -certstorelocation $myCertPath -dnsname $DnsRecord -Signer $rootCert -KeyExportPolicy Exportable -Verbose:$false $certPath = $myCertPath + $certThumbprint.Thumbprint # It can sometimes take a few seconds to populate the store $retryCount = 0; while(-not (Test-Path $certPath)) { Start-Sleep -Seconds 1 if($retryCount++ -eq 10) { throw ($LocalizedData.FailedCreatingCert -f $certPath) } } $null = Export-PfxCertificate -FilePath $OutFilePath -Cert $certPath -Password $CertificatePassword -ChainOption BuildChain -Force -Verbose:$false } } function New-SelfSignedRootCert { [Alias("Create-RootCert")] Param ( [Parameter(Mandatory=$true)] [string] $DnsRecord, [Parameter(Mandatory=$true)] [SecureString] $CertificatePassword ) $baseRootCertThumbprint = New-SelfSignedCertificate -KeyUsage DigitalSignature, KeyEncipherment, CertSign -HashAlgorithm SHA256 -KeyUsageProperty All -KeyLength 4096 -Subject "AzureStackSelfSignedRootCert" -FriendlyName "Azs Self Signed RootCert" -Provider "Microsoft Enhanced RSA and AES Cryptographic Provider" -certstorelocation $myCertPath -dnsname $DnsRecord -TextExtension @("2.5.29.19 ={critical} {text}ca=1&pathlength=3") $intermediateRootCertThumbprint = New-SelfSignedCertificate -KeyUsage DigitalSignature, KeyEncipherment, CertSign -HashAlgorithm SHA256 -KeyUsageProperty All -KeyLength 4096 -Subject "AzureStackSelfSignedIntermediate1Cert" -FriendlyName "Azs Self Signed Intermediate 1 Cert" -Provider "Microsoft Enhanced RSA and AES Cryptographic Provider" -certstorelocation $myCertPath -dnsname $DnsRecord -TextExtension @("2.5.29.19 ={critical} {text}ca=1&pathlength=2") -Signer $baseRootCertThumbprint $secondIntermediateRootCertThumbprint = New-SelfSignedCertificate -KeyUsage DigitalSignature, KeyEncipherment, CertSign -HashAlgorithm SHA256 -KeyUsageProperty All -KeyLength 4096 -Subject "AzureStackSelfSignedIntermediate2Cert" -FriendlyName "Azs Self Signed Intermediate 2 Cert" -Provider "Microsoft Enhanced RSA and AES Cryptographic Provider" -certstorelocation $myCertPath -dnsname $DnsRecord -TextExtension @("2.5.29.19 ={critical} {text}ca=1&pathlength=0") -Signer $intermediateRootCertThumbprint $certPath = $myCertPath + $secondIntermediateRootCertThumbprint.Thumbprint # It can sometimes take a few seconds to populate the store $retryCount = 0; while(-not (Test-Path $certPath)) { Start-Sleep -Seconds 1 if($retryCount++ -eq 10) { throw "Failed to create self signed root certificate in store '$certPath'." } } return $certPath } # Use for one nodes and internal testing only! function New-AzureStackSelfSignedCerts { [CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact='Medium')] Param ( [Parameter(Mandatory=$true)] [string] $RegionExternalFQDN, [Parameter(Mandatory=$true)] [SecureString] $CertificatePassword, [Parameter(Mandatory=$false)] [string] $ExternalCertRoot ) if($ExternalCertRoot) { $publicCertRoot = $ExternalCertRoot $armPublicCertInfo = @{Path="$publicCertRoot\AAD\ARM Public";RecordPrefix=@("management")} $armAdminCertInfo = @{Path="$publicCertRoot\AAD\ARM Admin";RecordPrefix=@("adminmanagement")} $publicPortalCertInfo = @{Path="$publicCertRoot\AAD\Public Portal";RecordPrefix=@("portal")} $adminPortalCertInfo = @{Path="$publicCertRoot\AAD\Admin Portal";RecordPrefix=@("adminportal")} $keyvaultCertInfo = @{Path="$publicCertRoot\AAD\KeyVault";RecordPrefix=@("*.vault")} $keyvaultAdminCertInfo = @{Path="$publicCertRoot\AAD\KeyVaultInternal";RecordPrefix=@("*.adminvault")} $acsTableCertInfo = @{Path="$publicCertRoot\AAD\ACSTable";RecordPrefix=@("*.table")} $acsQueueCertInfo = @{Path="$publicCertRoot\AAD\ACSQueue";RecordPrefix=@("*.queue")} $acsBlobCertInfo = @{Path="$publicCertRoot\AAD\ACSBlob";RecordPrefix=@("*.blob")} $adminHostingCertInfo = @{Path="$publicCertRoot\AAD\Admin Extension Host";RecordPrefix=@("*.adminhosting")} $publicHostingCertInfo = @{Path="$publicCertRoot\AAD\Public Extension Host";RecordPrefix=@("*.hosting")} $acrCertInfo = @{Path="$publicCertRoot\AAD\Container Registry";RecordPrefix=@("*.azsacr")} $adfsCertInfo = @{Path="$publicCertRoot\ADFS\ADFS";RecordPrefix=@("adfs")} $graphCertInfo = @{Path="$publicCertRoot\ADFS\Graph";RecordPrefix=@("graph")} $armADFSPublicCertInfo = @{Path="$publicCertRoot\ADFS\ARM Public";RecordPrefix=@("management")} $armADFSAdminCertInfo = @{Path="$publicCertRoot\ADFS\ARM Admin";RecordPrefix=@("adminmanagement")} $publicADFSPortalCertInfo = @{Path="$publicCertRoot\ADFS\Public Portal";RecordPrefix=@("portal")} $adminADFSPortalCertInfo = @{Path="$publicCertRoot\ADFS\Admin Portal";RecordPrefix=@("adminportal")} $keyvaultADFSCertInfo = @{Path="$publicCertRoot\ADFS\KeyVault";RecordPrefix=@("*.vault")} $keyvaultADFSAdminCertInfo = @{Path="$publicCertRoot\ADFS\KeyVaultInternal";RecordPrefix=@("*.adminvault")} $acsTableADFSCertInfo = @{Path="$publicCertRoot\ADFS\ACSTable";RecordPrefix=@("*.table")} $acsQueueADFSCertInfo = @{Path="$publicCertRoot\ADFS\ACSQueue";RecordPrefix=@("*.queue")} $acsBlobADFSCertInfo = @{Path="$publicCertRoot\ADFS\ACSBlob";RecordPrefix=@("*.blob")} $adminHostingADFSCertInfo = @{Path="$publicCertRoot\ADFS\Admin Extension Host";RecordPrefix=@("*.adminhosting")} $publicHostingADFSCertInfo = @{Path="$publicCertRoot\ADFS\Public Extension Host";RecordPrefix=@("*.hosting")} $acrADFSCertInfo = @{Path="$publicCertRoot\ADFS\Container Registry";RecordPrefix=@("*.azsacr")} } else { Write-VerboseLog $LocalizedData.CreatingExternalSelfSignedCerts } # getting rootCert thumbprint $rootCertThumbprint = New-SelfSignedRootCert -DnsRecord "$RegionExternalFQDN" -CertificatePassword $CertificatePassword # AAD certs New-SelfSignedCertificateWrapper -OutFilePath $armPublicCertInfo.Path -DnsRecord (New-DNSList -RecordPrefixList $armPublicCertInfo.RecordPrefix -RegionExternalFQDN $RegionExternalFQDN) -CertificatePassword $CertificatePassword -RootCertThumbprint $rootCertThumbprint New-SelfSignedCertificateWrapper -OutFilePath $armAdminCertInfo.Path -DnsRecord (New-DNSList -RecordPrefixList $armAdminCertInfo.RecordPrefix -RegionExternalFQDN $RegionExternalFQDN) -CertificatePassword $CertificatePassword -RootCertThumbprint $rootCertThumbprint New-SelfSignedCertificateWrapper -OutFilePath $publicPortalCertInfo.Path -DnsRecord (New-DNSList -RecordPrefixList $publicPortalCertInfo.RecordPrefix -RegionExternalFQDN $RegionExternalFQDN) -CertificatePassword $CertificatePassword -RootCertThumbprint $rootCertThumbprint New-SelfSignedCertificateWrapper -OutFilePath $adminPortalCertInfo.Path -DnsRecord (New-DNSList -RecordPrefixList $adminPortalCertInfo.RecordPrefix -RegionExternalFQDN $RegionExternalFQDN) -CertificatePassword $CertificatePassword -RootCertThumbprint $rootCertThumbprint New-SelfSignedCertificateWrapper -OutFilePath $keyvaultCertInfo.Path -DnsRecord (New-DNSList -RecordPrefixList $keyvaultCertInfo.RecordPrefix -RegionExternalFQDN $RegionExternalFQDN) -CertificatePassword $CertificatePassword -RootCertThumbprint $rootCertThumbprint New-SelfSignedCertificateWrapper -OutFilePath $keyvaultAdminCertInfo.Path -DnsRecord (New-DNSList -RecordPrefixList $keyvaultAdminCertInfo.RecordPrefix -RegionExternalFQDN $RegionExternalFQDN) -CertificatePassword $CertificatePassword -RootCertThumbprint $rootCertThumbprint #Creating three different ACS certs for Blob, Queue and Table: Another option is customers can provide one wilcard cert for all these three New-SelfSignedCertificateWrapper -OutFilePath $acsTableCertInfo.Path -DnsRecord (New-DNSList -RecordPrefixList $acsTableCertInfo.RecordPrefix -RegionExternalFQDN $RegionExternalFQDN) -CertificatePassword $CertificatePassword -RootCertThumbprint $rootCertThumbprint New-SelfSignedCertificateWrapper -OutFilePath $acsQueueCertInfo.Path -DnsRecord (New-DNSList -RecordPrefixList $acsQueueCertInfo.RecordPrefix -RegionExternalFQDN $RegionExternalFQDN) -CertificatePassword $CertificatePassword -RootCertThumbprint $rootCertThumbprint New-SelfSignedCertificateWrapper -OutFilePath $acsBlobCertInfo.Path -DnsRecord (New-DNSList -RecordPrefixList $acsBlobCertInfo.RecordPrefix -RegionExternalFQDN $RegionExternalFQDN) -CertificatePassword $CertificatePassword -RootCertThumbprint $rootCertThumbprint #Creating two additional Extension Host Certs New-SelfSignedCertificateWrapper -OutFilePath $adminHostingCertInfo.Path -DnsRecord (New-DNSList -RecordPrefixList $adminHostingCertInfo.RecordPrefix -RegionExternalFQDN $RegionExternalFQDN) -CertificatePassword $CertificatePassword -RootCertThumbprint $rootCertThumbprint New-SelfSignedCertificateWrapper -OutFilePath $publicHostingCertInfo.Path -DnsRecord (New-DNSList -RecordPrefixList $publicHostingCertInfo.RecordPrefix -RegionExternalFQDN $RegionExternalFQDN) -CertificatePassword $CertificatePassword -RootCertThumbprint $rootCertThumbprint #Creating Container Registry cert, which is optional but required in BCDR pipeline New-SelfSignedCertificateWrapper -OutFilePath $acrCertInfo.Path -DnsRecord (New-DNSList -RecordPrefixList $acrCertInfo.RecordPrefix -RegionExternalFQDN $RegionExternalFQDN) -CertificatePassword $CertificatePassword -RootCertThumbprint $rootCertThumbprint # ADFS certs New-SelfSignedCertificateWrapper -OutFilePath $adfsCertInfo.Path -DnsRecord (New-DNSList -RecordPrefixList $adfsCertInfo.RecordPrefix -RegionExternalFQDN $RegionExternalFQDN) -CertificatePassword $CertificatePassword -RootCertThumbprint $rootCertThumbprint New-SelfSignedCertificateWrapper -OutFilePath $graphCertInfo.Path -DnsRecord (New-DNSList -RecordPrefixList $graphCertInfo.RecordPrefix -RegionExternalFQDN $RegionExternalFQDN) -CertificatePassword $CertificatePassword -RootCertThumbprint $rootCertThumbprint New-SelfSignedCertificateWrapper -OutFilePath $armADFSPublicCertInfo.Path -DnsRecord (New-DNSList -RecordPrefixList $armADFSPublicCertInfo.RecordPrefix -RegionExternalFQDN $RegionExternalFQDN) -CertificatePassword $CertificatePassword -RootCertThumbprint $rootCertThumbprint New-SelfSignedCertificateWrapper -OutFilePath $armADFSAdminCertInfo.Path -DnsRecord (New-DNSList -RecordPrefixList $armADFSAdminCertInfo.RecordPrefix -RegionExternalFQDN $RegionExternalFQDN) -CertificatePassword $CertificatePassword -RootCertThumbprint $rootCertThumbprint New-SelfSignedCertificateWrapper -OutFilePath $publicADFSPortalCertInfo.Path -DnsRecord (New-DNSList -RecordPrefixList $publicADFSPortalCertInfo.RecordPrefix -RegionExternalFQDN $RegionExternalFQDN) -CertificatePassword $CertificatePassword -RootCertThumbprint $rootCertThumbprint New-SelfSignedCertificateWrapper -OutFilePath $adminADFSPortalCertInfo.Path -DnsRecord (New-DNSList -RecordPrefixList $adminADFSPortalCertInfo.RecordPrefix -RegionExternalFQDN $RegionExternalFQDN) -CertificatePassword $CertificatePassword -RootCertThumbprint $rootCertThumbprint New-SelfSignedCertificateWrapper -OutFilePath $keyvaultADFSCertInfo.Path -DnsRecord (New-DNSList -RecordPrefixList $keyvaultADFSCertInfo.RecordPrefix -RegionExternalFQDN $RegionExternalFQDN) -CertificatePassword $CertificatePassword -RootCertThumbprint $rootCertThumbprint New-SelfSignedCertificateWrapper -OutFilePath $keyvaultADFSAdminCertInfo.Path -DnsRecord (New-DNSList -RecordPrefixList $keyvaultADFSAdminCertInfo.RecordPrefix -RegionExternalFQDN $RegionExternalFQDN) -CertificatePassword $CertificatePassword -RootCertThumbprint $rootCertThumbprint #Creating three different ACS certs for Blob, Queue and Table: Another option is customers can provide one wilcard cert for all these three New-SelfSignedCertificateWrapper -OutFilePath $acsTableADFSCertInfo.Path -DnsRecord (New-DNSList -RecordPrefixList $acsTableADFSCertInfo.RecordPrefix -RegionExternalFQDN $RegionExternalFQDN) -CertificatePassword $CertificatePassword -RootCertThumbprint $rootCertThumbprint New-SelfSignedCertificateWrapper -OutFilePath $acsQueueADFSCertInfo.Path -DnsRecord (New-DNSList -RecordPrefixList $acsQueueADFSCertInfo.RecordPrefix -RegionExternalFQDN $RegionExternalFQDN) -CertificatePassword $CertificatePassword -RootCertThumbprint $rootCertThumbprint New-SelfSignedCertificateWrapper -OutFilePath $acsBlobADFSCertInfo.Path -DnsRecord (New-DNSList -RecordPrefixList $acsBlobADFSCertInfo.RecordPrefix -RegionExternalFQDN $RegionExternalFQDN) -CertificatePassword $CertificatePassword -RootCertThumbprint $rootCertThumbprint #Creating two additional Extension Host Certs New-SelfSignedCertificateWrapper -OutFilePath $adminHostingADFSCertInfo.Path -DnsRecord (New-DNSList -RecordPrefixList $adminHostingADFSCertInfo.RecordPrefix -RegionExternalFQDN $RegionExternalFQDN) -CertificatePassword $CertificatePassword -RootCertThumbprint $rootCertThumbprint New-SelfSignedCertificateWrapper -OutFilePath $publicHostingADFSCertInfo.Path -DnsRecord (New-DNSList -RecordPrefixList $publicHostingADFSCertInfo.RecordPrefix -RegionExternalFQDN $RegionExternalFQDN) -CertificatePassword $CertificatePassword -RootCertThumbprint $rootCertThumbprint #Creating Container Registry cert, which is optional but required in BCDR pipeline New-SelfSignedCertificateWrapper -OutFilePath $acrADFSCertInfo.Path -DnsRecord (New-DNSList -RecordPrefixList $acrADFSCertInfo.RecordPrefix -RegionExternalFQDN $RegionExternalFQDN) -CertificatePassword $CertificatePassword -RootCertThumbprint $rootCertThumbprint } function Test-AzureStackCerts { Param ( [Parameter(Mandatory=$true)] [SecureString] $CertificatePassword, [Parameter(Mandatory=$true)] [string] $ExpectedDomainFQDN, [Parameter(Mandatory=$false)] [bool] $UseADFS = $false, [Parameter(Mandatory=$false)] [bool] $isACRInstalled = $false, [Parameter(Mandatory=$false)] [bool] $WarnOnSelfSigned = $true, [Parameter(Mandatory=$false)] [string] $PfxFilesPath, [Parameter(Mandatory=$false)] [Alias('SecretRotation')] [ValidateSet('SameRootOnly','AnyTrustedRoot','')] [string] $RootValidation = '' ) $thisFunction = $MyInvocation.MyCommand.Name $publicCertHelperModuleVersion = $MyInvocation.MyCommand.Module.Version if (!$PfxFilesPath) { Write-Log -Message ($LocalizedData.ValidatingCerts -f $publicCertHelperModuleVersion) -Type Info -Function $thisfunction } else { # we are in external cert rotation, need to use path provided by customer Write-Log -Message ($LocalizedData.ValidatingCerts -f $publicCertHelperModuleVersion) -Type Info -Function $thisfunction $publicCertRoot = $PfxFilesPath $armPublicCertInfo = @{Path="$publicCertRoot\AAD\ARM Public";RecordPrefix=@("management")} $armAdminCertInfo = @{Path="$publicCertRoot\AAD\ARM Admin";RecordPrefix=@("adminmanagement")} $publicPortalCertInfo = @{Path="$publicCertRoot\AAD\Public Portal";RecordPrefix=@("portal")} $adminPortalCertInfo = @{Path="$publicCertRoot\AAD\Admin Portal";RecordPrefix=@("adminportal")} $keyvaultCertInfo = @{Path="$publicCertRoot\AAD\KeyVault";RecordPrefix=@("*.vault")} $keyvaultAdminCertInfo = @{Path="$publicCertRoot\AAD\KeyVaultInternal";RecordPrefix=@("*.adminvault")} $acsTableCertInfo = @{Path="$publicCertRoot\AAD\ACSTable";RecordPrefix=@("*.table")} $acsQueueCertInfo = @{Path="$publicCertRoot\AAD\ACSQueue";RecordPrefix=@("*.queue")} $acsBlobCertInfo = @{Path="$publicCertRoot\AAD\ACSBlob";RecordPrefix=@("*.blob")} $adminHostingCertInfo = @{Path="$publicCertRoot\AAD\Admin Extension Host";RecordPrefix=@("*.adminhosting")} $publicHostingCertInfo = @{Path="$publicCertRoot\AAD\Public Extension Host";RecordPrefix=@("*.hosting")} $acrCertInfo = @{Path="$publicCertRoot\AAD\Container Registry";RecordPrefix=@("*.azsacr")} $adfsCertInfo = @{Path="$publicCertRoot\ADFS\ADFS";RecordPrefix=@("adfs")} $graphCertInfo = @{Path="$publicCertRoot\ADFS\Graph";RecordPrefix=@("graph")} $armADFSPublicCertInfo = @{Path="$publicCertRoot\ADFS\ARM Public";RecordPrefix=@("management")} $armADFSAdminCertInfo = @{Path="$publicCertRoot\ADFS\ARM Admin";RecordPrefix=@("adminmanagement")} $publicADFSPortalCertInfo = @{Path="$publicCertRoot\ADFS\Public Portal";RecordPrefix=@("portal")} $adminADFSPortalCertInfo = @{Path="$publicCertRoot\ADFS\Admin Portal";RecordPrefix=@("adminportal")} $keyvaultADFSCertInfo = @{Path="$publicCertRoot\ADFS\KeyVault";RecordPrefix=@("*.vault")} $keyvaultADFSAdminCertInfo = @{Path="$publicCertRoot\ADFS\KeyVaultInternal";RecordPrefix=@("*.adminvault")} $acsTableADFSCertInfo = @{Path="$publicCertRoot\ADFS\ACSTable";RecordPrefix=@("*.table")} $acsQueueADFSCertInfo = @{Path="$publicCertRoot\ADFS\ACSQueue";RecordPrefix=@("*.queue")} $acsBlobADFSCertInfo = @{Path="$publicCertRoot\ADFS\ACSBlob";RecordPrefix=@("*.blob")} $adminHostingADFSCertInfo = @{Path="$publicCertRoot\ADFS\Admin Extension Host";RecordPrefix=@("*.adminhosting")} $publicHostingADFSCertInfo = @{Path="$publicCertRoot\ADFS\Public Extension Host";RecordPrefix=@("*.hosting")} $acrADFSCertInfo = @{Path="$publicCertRoot\ADFS\Container Registry";RecordPrefix=@("*.azsacr")} } if($UseADFS) { $allCertInfo = @( $adfsCertInfo, $graphCertInfo, $armADFSPublicCertInfo, $armADFSAdminCertInfo, $publicADFSPortalCertInfo, $adminADFSPortalCertInfo, $keyvaultADFSCertInfo, $keyvaultADFSAdminCertInfo, $acsTableADFSCertInfo, $acsQueueADFSCertInfo, $acsBlobADFSCertInfo, $adminHostingADFSCertInfo, $publicHostingADFSCertInfo ) # Validate ACR Certificate only if ACR has been installed on the stamp if($isACRInstalled) { $allCertInfo += $acrADFSCertInfo } } else { $allCertInfo = @( $armPublicCertInfo, $armAdminCertInfo, $publicPortalCertInfo, $adminPortalCertInfo, $keyvaultCertInfo, $keyvaultAdminCertInfo, $acsTableCertInfo, $acsQueueCertInfo, $acsBlobCertInfo, $adminHostingCertInfo, $publicHostingCertInfo ) # Validate ACR Certificate only if ACR has been installed on the stamp if($isACRInstalled) { $allCertInfo += $acrCertInfo } } $allCertResults = @() $allCertResults += $allCertInfo | ForEach-Object { ` Write-Log -Message "Launching Test-Certificate with Path = $($PSITEM.path), ExpectedPrefix = $($PSITEM.RecordPrefix), ExpectedDomainFQDN = $ExpectedDomainFQDN, WarnOnSelfSigned = $WarnOnSelfSigned, RootValidation = $RootValidation" -Type Info -Function $thisfunction $testCertificateParams = @{ CertificatePath = $PSITEM.Path CertificatePassword = $CertificatePassword certConfig = @{DNSName = $PSITEM.RecordPrefix;IncludeTests = 'All';ExcludeTests = 'CNG Key','HTTP CRL';pfxPath = $PSITEM.path} ExpectedDomainFQDN = $ExpectedDomainFQDN WarnOnSelfSigned = $WarnOnSelfSigned RootValidation = $RootValidation } Test-Certificate @testCertificateParams } return $allCertResults } function Test-Certificate { Param ( # Path of folder which contains the cert [Parameter(Mandatory=$true)] [string] $CertificatePath, [Parameter(Mandatory=$true)] [SecureString] $CertificatePassword, [Parameter(Mandatory=$true)] [string] $ExpectedDomainFQDN, [Parameter(Mandatory=$false)] [bool] $WarnOnSelfSigned = $true, [Parameter(Mandatory=$false)] [Alias('SecretRotation')] [ValidateSet('SameRootOnly','AnyTrustedRoot','')] [string] $RootValidation = '', [Parameter(Mandatory=$false)] [Hashtable]$certConfig ) $thisFunction = $MyInvocation.MyCommand.Name $ErrorActionPreference = 'SilentlyContinue' if(-not (Test-Path -Path $CertificatePath)) { # Terminating error for install, need to throw throw ($LocalizedData.IncorrectPath -f $CertificatePath) } $script:pfxFile = Get-ChildItem -Path $CertificatePath -Filter '*.pfx' | ForEach-Object FullName Write-Log -Message "Test PFX Certificate $pfxfile" -Type info -Function $thisFunction if($pfxFile.Count -ne 1) { # Terminating error for install Write-Log -Message ($LocalizedData.MoreThanOneCert -f $CertificatePath) -Type Error -Function $thisFunction } if(-not (Test-Path -Path $pfxFile)) { # Terminating error for install Write-Log ($LocalizedData.MissingCertificate) -Type Error -Function $thisFunction } if ((Get-Command Get-Content).Parameters.AsByteStream) { [byte[]]$pfxBinary = Get-Content -Path $pfxFile -AsByteStream } else { [byte[]]$pfxBinary = Get-Content -Path $pfxFile -Encoding Byte } #$certConfig.pfxPath = $pfxFile $params = @{ CertificateBinary = $pfxBinary CertificatePassword = $CertificatePassword ExpectedDomainFQDN = $ExpectedDomainFQDN WarnOnSelfSigned = $WarnOnSelfSigned RootValidation = $RootValidation certConfig = $certConfig } Test-AzsCertificate @params } function Test-AzsCertificate { Param ( [Parameter(Mandatory=$true)] [ValidateNotNull()] [byte[]] $CertificateBinary, [Parameter(Mandatory=$false)] [ValidateNotNull()] [SecureString] $CertificatePassword, [Parameter(Mandatory=$false)] [string] $ExpectedDomainFQDN, [Parameter(Mandatory=$false)] [bool] $WarnOnSelfSigned = $true, [Parameter(Mandatory=$false)] [Alias('SecretRotation')] [ValidateSet('SameRootOnly','AnyTrustedRoot','')] [string] $RootValidation = '', [Parameter(Mandatory=$false)] [Hashtable]$certConfig ) $thisFunction = $MyInvocation.MyCommand.Name $results = @() $ExpectedPrefix = $certConfig.DnsName # Get Relative Path # Depending on the entry point (deployment, secret rotation, standalone) the path can be in 1 of 2 places if ($pfxFile -or $certConfig.pfxPath) { if ($pfxFile) { $pf = Get-item -Path $pfxFile } else { $pf = Get-item -Path $certConfig.pfxPath } $pathValue = $pf.Directory.Name + '\' + $pf.name Write-Host "Testing: $pathValue" # Check PFX encryption first in case we have difficulty with encryption used and defer printing result until after import attempt for formatting purposes $pfxEncryptCheck = Test-PFXEncryption -pfxfile $pf.fullname -pfxpassword $certificatePassword -certConfig $certConfig $results += $pfxEncryptCheck if ($pfxEncryptCheck.result -eq 'Fail') { Write-Result -in $pfxEncryptCheck Write-Result -in $results.failureDetail throw "Unable to continue until PFX Encryption is TripleDES-SHA1" } } # Begin Checks $ErrorActionPreference = 'Stop' # make sure errors are not suppressed during import. if ($CertificatePassword) { try { $cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($CertificateBinary, $CertificatePassword, [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::DefaultKeySet) $includePFXChecks = $true } catch { # if the exception is bad password and encryption check failed/warned, give additional help about ensuring Triple-DES encryption. if ($_.Exception.ErrorRecord -match 'The specified network password is not correct' -and $pfxEncryptCheck.result -ne 'OK') { Write-Log -Message ($LocalizedData.BadPasswordAndUnknownEncryption -f $_.Exception.Message.Replace("`r`n","")) -Type Error -Function $thisFunction throw ($LocalizedData.BadPasswordAndUnknownEncryption -f $_.Exception.Message.Replace("`r`n","")) } elseif ($_.Exception.ErrorRecord -match 'Cannot find the requested object') { Write-Log -Message ($LocalizedData.ErrorOnX509Import) -Type Error -Function $thisFunction throw $LocalizedData.ErrorOnX509Import } else { Write-Log -Message ("Importing Certificate failed with message: {0}" -f $_.Exception.Message.Replace("`r`n","")) -Type Error -Function $thisFunction throw ("Importing Certificate failed with message: {0}" -f $_.Exception.Message.Replace("`r`n","")) } } } else { $cert.Import($CertificateBinary) $includePFXChecks = $false } Write-Host ("Thumbprint: {0}" -f ($cert | Get-ThumbprintMask)) # if PFXEncryption Check exists write the result to screen. if ($pfxEncryptCheck) { Write-Result -in $pfxEncryptCheck } $ErrorActionPreference = 'SilentlyContinue' #Setting erroraction to continue so tests can complete and user gets full break down of all issues. $expiryCheck = Test-CertificateExpiry -cert $cert -certConfig $certConfig Write-Result -in $expiryCheck $results += $expiryCheck $signatureAlgorithmCheck = Test-SignatureAlgorithm -x509 $cert -certConfig $certConfig Write-Result -in $signatureAlgorithmCheck $results += $signatureAlgorithmCheck $dnsNamesCheck = Test-DnsNames -cert $cert -ExpectedDomainFQDN $ExpectedDomainFQDN -certConfig $certConfig Write-Result -in $dnsNamesCheck $results += $dnsNamesCheck $keyUsageCheck = Test-KeyUsage -cert $cert -certConfig $certConfig Write-Result -in $keyUsageCheck $results += $keyUsageCheck $keySizeCheck = Test-KeySize -cert $cert -certConfig $certConfig Write-Result -in $keySizeCheck $results += $keySizeCheck $httpCDPCheck = Test-HttpCdp -cert $cert -certConfig $certConfig Write-Result -in $httpCDPCheck $results += $httpCDPCheck if ($includePFXChecks) { $pfxParseResult = Test-Pfx -certificateBinary $CertificateBinary -certificatePassword $certificatePassword if ($pfxParseResult.Result -eq 'Fail') { Write-Log -Message ("Unable to Parse PFX. Ensure PFX is correctly formatted. Error: {0}" -f ($pfxParseResult.failureDetail -join ';')) -Type Error -Function $thisFunction throw ("Unable to Parse PFX. Ensure PFX is correctly formatted. Error: {0}" -f ($pfxParseResult.failureDetail -join ';')) } $pfxData = $pfxParseResult.outputObject $pfxParseResult.outputObject = "" # clear the output object Write-Result -in $pfxParseResult $results += $pfxParseResult $privateKeyCheck = Test-PrivateKey -cert $cert -certConfig $certConfig Write-Result -in $privateKeyCheck $results += $privateKeyCheck $selfSignedCheck = Test-CertificateChain -pfxData $pfxData -certConfig $certConfig Write-Result -in $selfSignedCheck $results += $selfSignedCheck $chainOrderCheck = Test-CertificateChainOrder -pfxData $pfxData -certConfig $certConfig Write-Result -in $chainOrderCheck $results += $chainOrderCheck # Only run check for other certificates if cert chain is good and dnsnames are good to avoid noise. if ($selfSignedCheck.result -eq 'OK' -AND $dnsNamesCheck.result -eq 'OK') { $otherCertificateCheck = Test-OtherCertificates -pfxData $pfxData -ExpectedPrefix $ExpectedPrefix -ExpectedDomainFQDN $ExpectedDomainFQDN -certConfig $certConfig } Else { $hash = @{'Test' = 'Other Certificates'; 'Result' = 'Skipped'; 'FailureDetail' = $LocalizedData.TestOtherCertificatesSkipped; 'outputObject' = $null} $otherCertificateCheck = New-Object PSObject -Property $hash } Write-Result -in $otherCertificateCheck $results += $otherCertificateCheck if (-not $standalone -AND $RootValidation -ne '') { if ($RootValidation -eq 'SameRootOnly') { $rootCheck = Test-CertificateRoot -pfx $pfxData -certConfig $certConfig Write-Result -in $rootCheck $results += $rootCheck } else { #Check trust without using pfx as extra store $chainTest = Test-TrustedChain -cert $cert -retryCount 3 -intervalSeconds 5 -certConfig $certConfig if ($chainTest.Result -eq 'Fail') { #create certificate collection object $collection = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2Collection $pfxdata.EndEntityCertificates + $pfxData.OtherCertificates | ForEach-Object {$collection.add($_) | Out-Null} #create new chain $chain = New-Object Security.Cryptography.X509Certificates.X509Chain $chain.ChainPolicy.ExtraStore.AddRange($collection) #Check trust with pfx as extra store $chainTest = Test-TrustedChain -chain $chain -cert $cert -retryCount 3 -intervalSeconds 5 } else { Write-Log -Message ("Chain trust passed with {0} - {1}. No further action." -f $chainTest.Result,($chainTest.Chain.ChainStatus.Status -join ',')) -Type Info -Function $thisFunction } Write-Result -in $chainTest $results += $chainTest } } } # add relative path, thumbprint, certificateID $results | add-member -NotePropertyName CertificateId -NotePropertyValue ([guid]::NewGuid().ToString()) $results | add-member -NotePropertyName Path -NotePropertyValue $pathValue $results | add-member -NotePropertyName Thumbprint -NotePropertyValue $($cert | Get-ThumbprintMask) if ($results.result -match "Fail|Warning") { Write-Result -in $results.failureDetail } return $results } function Test-Pfx { param ([byte[]]$certificateBinary, [securestring]$certificatePassword) $thisFunction = $MyInvocation.MyCommand.Name $test = 'Parse PFX' Write-Log -Message "Parse PFX binary with password" -Type Info -Function $thisFunction $parseResult = Open-PfxData -certificateBinary $certificateBinary -certificatePassword $certificatePassword if ($parseResult.Success) { $tmpParseResult = Open-PfxData -certificateBinary $certificateBinary if ($tmpParseResult.Success) { $result = 'Warning' $failureDetail = ($LocalizedData.UnprotectedPublicCertInfo) Write-Log -Message $LocalizedData.UnprotectedPublicCertInfo -Type Warning -Function $thisFunction } else { $result = 'OK' if ($tmpParseResult.ErrorCode -eq 86) { Write-Log -Message "Parsing PFX binary privacy success" -Type Info -Function $thisFunction } else { Write-Log -Message ("Parsing PFX binary without password with error code 0x{0:x}" -f $($tmpParseResult.ErrorCode)) -Type Warn -Function $thisFunction } } } else { $result = 'Fail' if ($parseResult.ErrorCode -in @(0x80092002, 0x0D)) { $failureDetail = "Pfx data is Invalid" # Terminating error for install Write-Log -Message "Pfx data is Invalid" -Type Error -Function $thisFunction } elseif ($parseResult.ErrorCode -eq 86) { $failureDetail = "Pfx password is Invalid" # Terminating error for install Write-Log -Message $failureDetail -Type Error -Function $thisFunction } else { # Terminating error for install $errorMessage = "Parsing PFX binary with error code 0x{0:x}" -f $($parseResult.ErrorCode) $failureDetail = $errorMessage Write-Log -Message $errorMessage -Type Error -Function $thisFunction } } $hash = @{'Test' = $test; 'Result' = $result; 'FailureDetail' = $failureDetail; 'outputObject' = $parseResult} $object = New-Object PSObject -Property $hash $object } function Test-SignatureAlgorithm { param ([System.Security.Cryptography.X509Certificates.X509Certificate2]$x509,[Hashtable]$certConfig) # Name for log and name for test $thisFunction = $MyInvocation.MyCommand.Name $test = 'Signature Algorithm' # config to run test by if ($certConfig.HashAlgorithm -eq 'default' -or $null -eq $certConfig.HashAlgorithm) { $blockedAlgorithms = 'SHA1RSA' } else { $blockedAlgorithms = $certConfig.HashAlgorithm Write-Log -Message ('Using user-defined Signature Algorithms {0} for test.' -f ($blockedAlgorithms -join ',')) -Type Info -Function $thisFunction } # run test if applicable if (!$certConfig -or (($certConfig.IncludeTests -eq 'All' -or $test -in $certConfig.IncludeTests) -and $test -notin $certConfig.ExcludeTests)) { Write-Log -Message ('Checking Signature Algorithm is not {0}' -f ($blockedAlgorithms -join ',')) -Type Info -Function $thisFunction $signatureAlgorithm = $x509.SignatureAlgorithm.FriendlyName Write-Log -Message ('Signature Algorithm is {0}' -f $signatureAlgorithm) -Type Info -Function $thisFunction if ($signatureAlgorithm -in $blockedAlgorithms) { $result = 'Fail' $failureDetail = ($LocalizedData.SignatureAlgorithmInvalid -f $signatureAlgorithm, ($blockedAlgorithms -join ',')) Write-Log -Message $failureDetail -Type Warn -Function $thisFunction } else { $result = 'OK' Write-Log -Message ('Signature Algorithm is not {0}' -f ($blockedAlgorithms -join ',')) -Type Info -Function $thisFunction } } else { $result = 'SkippedByConfig' $failureDetail = ($LocalizedData.SkippedByConfig -f $test) Write-Log -Message $failureDetail -Type Info -Function $thisFunction } # return output $hash = @{'Test' = $test; 'Result' = $result; 'FailureDetail' = $failureDetail; 'outputObject' = $null} $object = New-Object PSObject -Property $hash $object } function Test-PrivateKey { param ([System.Security.Cryptography.X509Certificates.X509Certificate2]$cert,[Hashtable]$certConfig) $thisFunction = $MyInvocation.MyCommand.Name $test = 'Private Key' if (!$certConfig -or (($certConfig.IncludeTests -eq 'All' -or $test -in $certConfig.IncludeTests) -and $test -notin $certConfig.ExcludeTests)) { $privateKeyFailed = @() $failureDetail = @() try { Write-Log -Message 'Checking Private Key exists' -Type Info -Function $thisFunction if(-not $cert.HasPrivateKey) { $privateKeyFailed += $true $failureDetail += ($LocalizedData.NoPrivateKey) Write-Log -Message ($LocalizedData.NoPrivateKey) -Type Warn -Function $thisFunction } Else { # Check if certificate has been exported from user store. Write-Log -Message 'Private Key Exists, checking Local Machine Key Attribute' -Type Info -Function $thisFunction $key = [System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPrivateKey($cert) Write-Log -Message ('Private Key Provider: {0}' -f $key.Key.Provider) -Type Info -Function $thisFunction if(-not $key.Key.IsMachineKey) { $privateKeyFailed += $true $failureDetail += ($LocalizedData.NotMachineKeyStore) Write-Log -Message ($LocalizedData.NotMachineKeyStore) -Type Warn -Function $thisFunction } else { $privateKeyFailed += $false Write-Log -Message 'Private Key Exists and Local Machine Key Attribute exists' -Type Info -Function $thisFunction } if('CNG key' -notin $certConfig.ExcludeTests) { Write-Log -Message 'Checking PaaS for CNG key...' -Type Info -Function $thisFunction if($key.Key.Provider -eq 'Microsoft Software Key Storage Provider') { $privateKeyFailed += $true $failureDetail += "CNG Certificate detected, support for this certificate type may not currently be available. Please use https://docs.microsoft.com/en-us/azure/azure-stack/azure-stack-get-pki-certs to generate the certificates." Write-Log -Message 'Microsoft Software Key Storage Provider cannot be used for this certificate.' -Type Warn -Function $thisFunction } } } } catch { $privateKeyFailed += $true $failureDetail += $_.exception.message Write-Log -Message $failureDetail -Type Warn -Function $thisFunction } if ($privateKeyFailed -notcontains $true) { $result = 'OK' } else { $result = 'Fail' } } else { $result = 'SkippedByConfig' $failureDetail = ($LocalizedData.SkippedByConfig -f $test) Write-Log -Message $failureDetail -Type Info -Function $thisFunction } $hash = @{'Test' = $test; 'Result' = $result; 'FailureDetail' = $failureDetail; 'outputObject' = $null} $object = New-Object PSObject -Property $hash $object } function Test-CertificateChain { param ([Hashtable]$pfxData,[Hashtable]$certConfig) $thisFunction = $MyInvocation.MyCommand.Name $test = 'Cert Chain' if (!$certConfig -or (($certConfig.IncludeTests -eq 'All' -or $test -in $certConfig.IncludeTests) -and $test -notin $certConfig.ExcludeTests)) { Write-Log -Message 'Checking Certificate chain' -Type Info -Function $thisFunction # Check for chain of trust. Not validating signature algorithm on root or intermediates $otherCertificates = $pfxData.OtherCertificates $cert = $pfxData.EndEntityCertificates if(-not $otherCertificates) { Write-Log -Message 'No other certificates found in pfx' -Type Info -Function $thisFunction if($cert.Issuer -eq $cert.Subject) { $failureDetail = ($LocalizedData.SelfSignedCertificate -f $cert.Subject) Write-Log -Message $failureDetail -Type Warn -Function $thisFunction $result = 'Fail' } else { $result = 'Fail' $failureDetail = ($LocalizedData.NoChainOfTrust) Write-Log -Message $failureDetail -Type Warn -Function $thisFunction } } else { if ($cert.Issuer -in $otherCertificates.subject) { $result = 'OK' Write-Log -Message ('The issuer certificate from {0} is included in the PFX' -f $cert.Issuer) -Type Info -Function $thisFunction } Else { $result = 'Fail' $failureDetail = ($LocalizedData.NoChainOfTrust) Write-Log -Message $failureDetail -Type Warn -Function $thisFunction } } } else { $result = 'SkippedByConfig' $failureDetail = ($LocalizedData.SkippedByConfig -f $test) Write-Log -Message $failureDetail -Type Info -Function $thisFunction } $hash = @{'Test' = $test; 'Result' = $result; 'FailureDetail' = $failureDetail; 'outputObject' = $null} $object = New-Object PSObject -Property $hash $object } function Test-DNSNames { param ([System.Security.Cryptography.X509Certificates.X509Certificate2]$cert,$ExpectedDomainFQDN,[Hashtable]$certConfig) $thisFunction = $MyInvocation.MyCommand.Name $test = 'DNS Names' if (!$certConfig -or (($certConfig.IncludeTests -eq 'All' -or $test -in $certConfig.IncludeTests) -and $test -notin $certConfig.ExcludeTests)) { # Get the records between cn="<records>" $records = @() $records = $cert.DnsNameList.Unicode $recordsString = $records -join ', ' Write-Log -Message ('DNS Names on certificate: {0}' -f $recordsString) -Type Info -Function $thisFunction $dnsNameFailed = @() foreach($prefix in $certConfig.DnsName) { # make prefix and fqdn array and join by '.' because if one doesn't exist it will not leave periods. if ($ExpectedDomainFQDN) { $fullExpectedRecord = ($prefix,$ExpectedDomainFQDN) -join '.' } else { $fullExpectedRecord = $prefix } Write-Log -Message ('Testing for full expected record: {0}' -f $fullExpectedRecord) -Type Info -Function $thisFunction if($records -eq $fullExpectedRecord) { $dnsNameFailed += $false Write-Log -Message ('Records: {0} match: {1}' -f $recordsString,($fullExpectedRecord -join ', ')) -Type Info -Function $thisFunction continue } elseif ($records -eq "*." + $fullExpectedRecord.split('.',2)[1]) { $dnsNameFailed += $false Write-Log -Message ('Records: {0} match wildcard: {1}' -f $recordsString,("\*." + $fullExpectedRecord.split('.',2)[1])) -Type Info -Function $thisFunction } else { $failureDetail += ($LocalizedData.MissingRecord -f @($recordsString, $fullExpectedRecord)) Write-Log -Message $failureDetail -Type Warn -Function $thisFunction $dnsNameFailed += $true } if ($prefix -eq 'adfs') { Write-Log -Message ('Testing ADFS certificate subject {0} for ADFS compatability' -f $cert.subject) -Type Info -Function $thisFunction if ($cert.SubjectName -notlike '*.*') { $dnsNameFailed += $true $failureDetail += ($LocalizedData.DotlessADFSSubject -f $cert.Subject,$fullExpectedRecord) Write-Log -Message ($LocalizedData.DotlessADFSSubject -f $cert.Subject,$fullExpectedRecord) -Type Warn -Function $thisFunction } else { Write-Log -Message ('ADFS certificate subject {0} compatible for ADFS' -f $cert.subject) -Type Info -Function $thisFunction } } } if ($dnsNameFailed -notcontains $true) { $result = 'OK' } else { $result = 'Fail' $failureDetail += ($LocalizedData.CheckDocumentation) } } else { $result = 'SkippedByConfig' $failureDetail = ($LocalizedData.SkippedByConfig -f $test) Write-Log -Message $failureDetail -Type Info -Function $thisFunction } $hash = @{'Test' = $test; 'Result' = $result; 'FailureDetail' = $failureDetail; 'outputObject' = $null} $object = New-Object PSObject -Property $hash $object } function Test-KeyUsage { param ([System.Security.Cryptography.X509Certificates.X509Certificate2]$cert,[hashtable]$certConfig) $thisFunction = $MyInvocation.MyCommand.Name $test = 'Key Usage' if (!$certConfig -or (($certConfig.IncludeTests -eq 'All' -or $test -in $certConfig.IncludeTests) -and $test -notin $certConfig.ExcludeTests)) { # Validating KeyUsage should have Digital Signature, and Key Encipherment. # EnhancedKeyUsage should have Server Authentication and Client Authentication # Data Encipherment no longer required if ($certConfig.KeyUsage -eq 'default' -or $null -eq $certConfig.KeyUsage) { Write-Log -Message ('Testing for default Key Usage') -Type Info -Function $thisFunction $keyUsageArray = $certificateDefaults.KeyUsage.Keys } else { [array]$keyUsageArray = $certConfig.KeyUsage } if ($certConfig.EnhancedKeyUsage -eq 'default' -or $null -eq $certConfig.EnhancedKeyUsage) { Write-Log -Message ('Testing for default Enhanced Key Usage') -Type Info -Function $thisFunction $enhancedKeyUsageArray = $certificateDefaults.EnhancedKeyUsage.Keys | Foreach-Object { $certificateDefaults.EnhancedKeyUsage[$PSITEM].Oid } } else { Write-Log -Message ('Testing for custom Enhanced Key Usage') -Type Info -Function $thisFunction $certConfig.EnhancedKeyUsage = $certConfig.EnhancedKeyUsage | Foreach-Object { ConvertTo-Oid $PSITEM } [array]$enhancedKeyUsageArray = $certConfig.EnhancedKeyUsage | Foreach-Object { $PSITEM.Value } } # Create emtpy arrays for result handling. $keyUsageFailed = @() $failureDetail = @() $keyUsage = $cert.Extensions.KeyUsages $enhancedKeyUsage = $cert.EnhancedKeyUsageList.ObjectId # Check KeyUsage Write-Log -Message ('Testing for expected Key Usage {0}' -f ($keyUsageArray -join ',')) -Type Info -Function $thisFunction Write-Log -Message ('Certificate key usage is {0}' -f ($keyUsage -join ',')) -Type Info -Function $thisFunction foreach ($requiredKeyUsage in $keyUsageArray) { if ($keyUsage -notmatch $requiredKeyUsage) { $keyUsageFailed += $true $failureDetail += ($LocalizedData.IncorrectKeyUsage -f $keyUsage,($requiredKeyUsage -join ',')) Write-Log -Message $failureDetail -Type Warn -Function $thisFunction } else { $keyUsageFailed += $false } } # Check Enhanced KeyUsage Write-Log -Message ('Testing for expected Enhanced Key Usage {0}' -f (($certConfig.EnhancedKeyUsage | Foreach-Object { "{0} ({1})" -f $PSITEM.FriendlyName,$PSITEM.Value }) -join ',')) -Type Info -Function $thisFunction Write-Log -Message ('Certificate Enhanced key usage is {0}' -f ($enhancedKeyUsage -join ',')) -Type Info -Function $thisFunction foreach ($requiredEku in $enhancedKeyUsageArray) { if ($enhancedKeyUsage -notcontains $requiredEku) { $keyUsageFailed += $true if ($enhancedKeyUsage) { $CertFriendlyNames = $cert.EnhancedKeyUsageList.FriendlyName | Foreach-Object {if (!$PSITEM) { 'Custom Oid' }else { $PSITEM }} $RequiredUsageStrings = $certConfig.EnhancedKeyUsage | Foreach-Object { "{0} ({1})" -f $PSITEM.FriendlyName,$PSITEM.Value } $failureDetail += ($LocalizedData.IncorrectEnhancedKeyUsage -f ($CertFriendlyNames -join ','),($RequiredUsageStrings -join ',')) Write-Log -Message $failureDetail -Type Warn -Function $thisFunction } else { $failureDetail += ($LocalizedData.IncorrectEnhancedKeyUsage -f '[Missing]',($enhancedKeyUsageArray -join ',')) Write-Log -Message $failureDetail -Type Warn -Function $thisFunction } } else { $keyUsageFailed += $false } } # Check overall results if ($keyUsageFailed -notcontains $true) { $result = 'OK' Write-Log -Message 'Certificate key usage succeeded' -Type Info -Function $thisFunction } else { $result = 'Fail' Write-Log -Message 'Certificate key usage failed' -Type Warn -Function $thisFunction } } else { $result = 'SkippedByConfig' $failureDetail = ($LocalizedData.SkippedByConfig -f $test) Write-Log -Message $failureDetail -Type Info -Function $thisFunction } $hash = @{'Test' = $test; 'Result' = $result; 'FailureDetail' = ($failureDetail | Sort-Object | Get-Unique); 'outputObject' = $null} $object = New-Object PSObject -Property $hash $object } function Test-CertificateChainOrder { param ([Hashtable]$pfxData,[Hashtable]$certConfig) $thisFunction = $MyInvocation.MyCommand.Name $test = 'Chain Order' if (!$certConfig -or (($certConfig.IncludeTests -eq 'All' -or $test -in $certConfig.IncludeTests) -and $test -notin $certConfig.ExcludeTests)) { Write-Log -Message 'Checking Certificate Chain Order' -Type Info -Function $thisFunction # Validating cert chain order $otherCertificates = $pfxData.OtherCertificates if ($otherCertificates[-1].Issuer -ne $otherCertificates[-1].Subject) { $result = 'Fail' $failureDetail = ($LocalizedData.IncorrectCertChainOrder) Write-Log -Message $failureDetail -Type Warn -Function $thisFunction } Else { $result = 'OK' Write-Log -Message 'Certificate Chain Order succeeded' -Type Info -Function $thisFunction } } else { $result = 'SkippedByConfig' $failureDetail = ($LocalizedData.SkippedByConfig -f $test) Write-Log -Message $failureDetail -Type Info -Function $thisFunction } $hash = @{'Test' = $test; 'Result' = $result; 'FailureDetail' = $failureDetail; 'outputObject' = $null} $object = New-Object PSObject -Property $hash $object } Function Test-OtherCertificates { param ([Hashtable]$pfxData,[string]$ExpectedDomainFQDN,[Hashtable]$certConfig) $test = 'Other Certificates' $thisFunction = $MyInvocation.MyCommand.Name if (!$certConfig -or (($certConfig.IncludeTests -eq 'All' -or $test -in $certConfig.IncludeTests) -and $test -notin $certConfig.ExcludeTests)) { Write-Log -Message "Checking Pfx file for additional certificates" -Type Info -Function $thisFunction $otherCertificates = $pfxData.OtherCertificates $cert = $pfxData.EndEntityCertificates $allcerts = $otherCertificates + @($cert) $otherCertificatesFailed = @() foreach ($prefix in $certConfig.DNSName) { # Apply literal to any wildcard on the expect DNSName. # make prefix and fqdn array and join by '.' because if one doesn't exist it will not leave periods. if ($ExpectedDomainFQDN) { $expectedDNSName = (($prefix,$ExpectedDomainFQDN) -join '.') -replace "\*", "\*" } else { $expectedDNSName = $prefix -replace "\*", "\*" } Write-Log -Message "Checking Pfx file for additional certificate with context of DNS Name: $prefix.$ExpectedDomainFQDN" -Type Info -Function $thisFunction # Find expected certificate according to DNSName literally or by matching wildcard. $targetCert = $allcerts | Where-Object {$_.dnsnamelist.unicode -match $expectedDNSName -OR $_.dnsnamelist.unicode -match "\*." + $expectedDNSName.split('.',2)[1]} # Remove all certs that are the target cert or an issuer of any certificate in the array. $unexpectedCerts = $allcerts | Where-Object {$_.Subject -notin $allcerts.Issuer -AND $_.thumbprint -ne $targetCert.Thumbprint} # Find expected certs for logging. $validCerts = $allcerts | Where-Object {$_.thumbprint -notin $unexpectedCerts.thumbprint} if ($unexpectedCerts) { $otherCertificatesFailed += $true $failureDetail += ($LocalizedData.UnwantedCertificatesInPfx -f ($unexpectedCerts | Get-ThumbprintMask),($validCerts | Get-ThumbprintMask)) Write-Log -Message $failureDetail -Type Warning -Function $thisFunction } Else { $otherCertificatesFailed += $false } } if ($otherCertificatesFailed -notcontains $true) { $result = 'OK' Write-Log -Message ('No additional certificates were detected. Validcert thumbprints {0}' -f ($validCerts | Get-ThumbprintMask)) -Type Info -Function $thisFunction } else { $result = 'Fail' } } else { $result = 'SkippedByConfig' $failureDetail = ($LocalizedData.SkippedByConfig -f $test) Write-Log -Message $failureDetail -Type Info -Function $thisFunction } $hash = @{'Test' = $test; 'Result' = $result; 'FailureDetail' = $failureDetail; 'outputObject' = $null} $object = New-Object PSObject -Property $hash $object } function Test-KeySize { param ([System.Security.Cryptography.X509Certificates.X509Certificate2]$cert,[Hashtable]$certConfig) $test = 'Key Length' $thisFunction = $MyInvocation.MyCommand.Name if ($certConfig.KeyLength -eq 'default' -or $null -eq $certConfig.KeyLength) { $keySizeLowerLimit = 2048 } else { [int]$keySizeLowerLimit = $certConfig.KeyLength } if (!$certConfig -or (($certConfig.IncludeTests -eq 'All' -or $test -in $certConfig.IncludeTests) -and $test -notin $certConfig.ExcludeTests)) { #get the key length of the public key. $keySize = $cert.publickey.key.KeySize Write-Log -Message ("Checking Certificate for Key Length {0}" -f $keySizeLowerLimit) -Type Info -Function $thisFunction Write-Log -Message ("Certificate Key Length {0}" -f $keySize) -Type Info -Function $thisFunction if ($keySize -lt $keySizeLowerLimit) { $result = 'Fail' $failureDetail = ($LocalizedData.WrongKeySize -f $keySize,$keySizeLowerLimit) Write-Log -Message $failureDetail -Type Warn -Function $thisFunction } else { $result = 'OK' Write-Log -Message 'Certificate Key Length succeeded' -Type Info -Function $thisFunction } } else { $result = 'SkippedByConfig' $failureDetail = ($LocalizedData.SkippedByConfig -f $test) Write-Log -Message $failureDetail -Type Info -Function $thisFunction } $hash = @{'Test' = $test; 'Result' = $result; 'FailureDetail' = $failureDetail; 'outputObject' = $null} $object = New-Object PSObject -Property $hash $object } function Test-PFXEncryption { # mandatory check param ($pfxFile, [ValidateNotNullOrEmpty()][SecureString]$pfxpassword,[Hashtable]$certConfig) $test = 'PFX Encryption' $thisFunction = $MyInvocation.MyCommand.Name if (!$certConfig -or (($certConfig.IncludeTests -eq 'All' -or $test -in $certConfig.IncludeTests) -and $test -notin $certConfig.ExcludeTests)) { Write-Log -Message ("Checking PFX Encryption is tripleDES-SHA1.") -Type Info -Function $thisFunction $networkCredential = New-Object System.Net.NetworkCredential -ArgumentList @("", $pfxpassword) $plainCertPassword = $networkCredential.Password try { $cmd = "-p {0} -dumpPFX `"{1}`"" -f $plainCertPassword,$pfxfile $certutilOutputPath = "$ENV:TEMP\AzsRCCertUtil.log" $null = Start-Process -FilePath certutil.exe ` -ArgumentList $cmd ` -WindowStyle Hidden ` -PassThru ` -Wait ` -RedirectStandardOutput $certutilOutputPath $certutilOutput = Get-Content -path $certutilOutputPath $TripleDESMatch = $certutilOutput | Select-String -SimpleMatch "1.2.840.113549.1.12.1.3" if ($TripleDESMatch) { Write-Log -Message ('PFX {0} Encryption is tripleDES-SHA1. CertUtil output {1}' -f $pfxFile,($TripleDESMatch -join ' ::Match:: ')) -Type Info -Function $thisFunction $result = 'OK' } else { if ($certutilOutput -match 'CertUtil: Unknown arg: -dumppfx') { $failureDetail = $LocalizedData.DumpPfxParamFail $result = 'Skipped' } else { $failureDetail = $LocalizedData.IncorrectPFXEncryption -f (($certutilOutput | Select-String -SimpleMatch "1.2.840.113549") -join "` r`n::Match:: ") $result = 'Warning' } Write-Log -Message $failureDetail -Type Warn -Function $thisFunction } } catch { Write-Log -Message ('Unable to determine PFX encryption. Run CertUtil -dumppfx <filename> to check encryption. Checking PFX encryption failed with exception: {0}`n OID dump: {1}' -f $_.exception,(($certutilOutput | Select-String -SimpleMatch "1.2.840.113549") -join "` r`n::Match:: ")) -Type Warn -Function $thisFunction $failureDetail = $LocalizedData.IncorrectPFXEncryption $result = 'Fail' } finally { Remove-item $certutilOutputPath -Force Clear-Variable -Name pfxFile } } else { $result = 'SkippedByConfig' $failureDetail = ($LocalizedData.SkippedByConfig -f $test) Write-Log -Message $failureDetail -Type Info -Function $thisFunction } $hash = @{'Test' = $test; 'Result' = $result; 'FailureDetail' = $failureDetail; 'outputObject' = $null} $object = New-Object PSObject -Property $hash $object } function Get-CDP { param ([System.Security.Cryptography.X509Certificates.X509Certificate2]$cert) $crlext = $cert.Extensions | Where-Object { $_.Oid.FriendlyName -eq 'CRL Distribution Points' } $crldata = $crlext.RawData for ($i = 0 ; $i -lt $crldata.Count ; $i++) { if ($crldata[$i] -eq 0x86) { if ($crldata[$i + 1] -band 0x80) { #long length $start = $i + 3 $end = $crldata[$i + 2] + $start - 1 } else { #short length $start = $i + 2 $end = $crldata[$i + 1] + $start - 1 } [System.Text.Encoding]::ASCII.GetString($crldata[$start..$end]) } } } function Test-HttpCdp { # fail if CDP HTTP is not present # fail if CDP HTTP is not contactable param ([System.Security.Cryptography.X509Certificates.X509Certificate2]$cert,[Hashtable]$certConfig) $thisFunction = $MyInvocation.MyCommand.Name $test = 'HTTP CRL' if (!$certConfig -or (($certConfig.IncludeTests -eq 'All' -or $test -in $certConfig.IncludeTests) -and $test -notin $certConfig.ExcludeTests)) { $cdps = Get-CDP -cert $cert $httpCdp = $cdps | Where-Object {$_ -like 'http://*'} Write-Log -Message 'Checking Http CDP EndPoint' -Type Info -Function $thisFunction $failureDetail = @() if ($httpCdp) { $cdpSuccess = $false foreach ($uri in $httpCdp) { try { $responseStatusCode = (Invoke-WebRequest -Uri $uri -UseBasicParsing).StatusCode if ($responseStatusCode -eq 200) { Write-Log -Message ('Checking {0}. Success.' -f $uri) -Type Info -Function $thisFunction $cdpSuccess = $true break } else { throw "URI: $uri. StatusCode: $responseStatusCode" } } catch { $failureDetail += $LocalizedData.HttpCdpFail -f $uri,$_.Exception.Message } } if ($cdpSuccess) { $result = 'OK' # if result is good, remove any failuredetails as they would have been written to the log $failureDetail = $null } else { $result = 'Fail' Write-Log -Message ($failureDetail -join '. ') -Type Error -Function $thisFunction } } Else { $result = 'Skipped' $failureDetail = ($LocalizedData.NoHttpCdp) Write-Log -Message $failureDetail -Type Warn -Function $thisFunction } } else { $result = 'SkippedByConfig' $failureDetail = ($LocalizedData.SkippedByConfig -f $test) Write-Log -Message $failureDetail -Type Info -Function $thisFunction } $hash = @{'Test' = $test; 'Result' = $result; 'FailureDetail' = $failureDetail; 'outputObject' = $null} $object = New-Object PSObject -Property $hash $object } function Import-AzsCertificate { param ($pfxPath, [securestring]$pfxPassword, [string]$CertStoreLocation = 'cert:\localmachine\trust') $thisFunction = $MyInvocation.MyCommand.Name try { Write-Log -Message ('Importing PFX certificate from {0} to {1}' -f $pfxPath,$CertStoreLocation) -Type Info -Function $thisFunction $certificate = Import-PfxCertificate -Exportable -CertStoreLocation $CertStoreLocation -Password $pfxPassword -FilePath $pfxPath Write-Log -Message 'Import complete' -Type Info -Function $thisFunction } catch { Write-Log -Message ('Import failed: {0}' -f $_.exception) -Type Error -Function $thisFunction } $certificate } function Export-AzsCertificate { param ($filePath, $certPath, [securestring]$pfxPassword) $thisFunction = $MyInvocation.MyCommand.Name try { Write-Log -Message ('Exporting PFX certificate from {0} to {1}' -f $certPath,$filePath) -Type Info -Function $thisFunction $null = Export-PfxCertificate -FilePath $filePath -ChainOption BuildChain -Cert $certPath -Password $pfxPassword -NoProperties -Force -CryptoAlgorithmOption TripleDES_SHA1 Write-Log -Message 'Export complete' -Type Info -Function $thisFunction } catch { if ($_.CategoryInfo.Reason -eq 'ParameterBindingException' -AND $_.Exception.ErrorId -eq 'NamedParameterNotFound' -AND $_.Exception.ParameterName -eq 'CryptoAlgorithmOption') { Write-Log -Message 'Unable to force Crypto Algorithm to TripleDES_SHA1. Retrying with default value.' -Type Warn -Function $thisFunction $null = Export-PfxCertificate -FilePath $filePath -ChainOption BuildChain -Cert $certPath -Password $pfxPassword -NoProperties -Force Write-Log -Message 'Export complete' -Type Info -Function $thisFunction } else { Write-Log -Message ('Export failed: {0}' -f $_.exception) -Type Error -Function $thisFunction } } } function Write-Result { param([psobject]$in) if ($in.test) { Write-Host ("`t{0}: " -f $($in.Test)) -noNewLine if ($in.Result -eq 'OK') { Write-Host 'OK' -foregroundcolor Green } elseif ($in.Result -eq 'WARNING') { Write-Host 'Warning' -foregroundcolor Yellow } elseif ($in.Result -eq 'Skipped') { Write-Host 'Skipped' -foregroundcolor White } elseif ($in.Result -eq 'SkippedByConfig') { Write-Host 'SkippedByConfig' -foregroundcolor DarkGray } else { Write-Host 'Fail' -foregroundcolor Red } } else { Write-Host "`Details:" $in | ForEach-Object {if($_){Write-Host "[-] $_" -foregroundcolor Yellow}} Write-Host ("Additional help URL {0}" -f "https://aka.ms/AzsRemediateCerts") } } function Write-Log { param([string]$Message, [string]$Type = 'verbose', [string]$Function ) # if InstallAzureStackCommon is loaded and ScriptLog global variable exists, # we are in install and will push to install log, otherwise, do a standalone log $pii = $($ENV:USERDNSDOMAIN),$($ENV:COMPUTERNAME),$($ENV:USERNAME),$($ENV:USERDOMAIN) | Foreach-Object { if ($null -ne $PSITEM) { $PSITEM } } $redact = $pii -join '|' $message = [regex]::replace($Message,$redact,"[*redacted*]") if ((-not $standalone) -AND (Get-Module InstallAzureStackCommon) -AND (Get-Variable -Name ScriptLog -Scope Global -ea SilentlyContinue)) { if ($type -match 'verbose|info') { Write-VerboseLog $message } elseif ($type -eq 'warn') { Write-WarningLog $message } elseif ($type -eq 'error') { Write-TerminatingErrorLog $message } } Else { $outfile = "$PSScriptRoot\CertChecker.log" $entry = "[{0}] [{1}] [{2}] {3}" -f ([datetime]::now).tostring(), $type, $function, $Message $entry | Out-File -FilePath $outfile -Append -Force } } function Get-ThumbprintMask { [cmdletbinding()] [OutputType([string])] Param ([Parameter(ValueFromPipelinebyPropertyName=$True)]$thumbprint) Begin { $thumbprintMasks = @() } Process { $thumbprintMasks += foreach ($thumb in $thumbprint) { try { if (($thumb.length - 12) -gt 0) { $firstSix = $thumb.Substring(0,6) $lastSix = $thumb.Substring(($thumb.length - 6),6) $middleN = '*' * ($thumb.length - 12) $thumbprintMask = '{0}{1}{2}' -f $firstSix,$middleN, $lastSix } else { throw ("Error applying thumbprint mask from thumbprint starting with {0} and length of {1}" -f $thumbprint.Substring(0,10),$thumbprint.Length) } } catch { $_.exception } $thumbprintMask } } End { $thumbprintMasks -join ',' } } function Open-PfxData { param ([byte[]]$certificateBinary, [securestring]$certificatePassword) $thisFunction = $MyInvocation.MyCommand.Name $Source = @' using System; using System.Runtime.InteropServices; namespace AzureStack.PartnerToolkit { [StructLayout(LayoutKind.Sequential)] public struct CRYPT_DATA_BLOB { public int cbData; public IntPtr pbData; } public class Crypto { [DllImport("Crypt32.dll", SetLastError = true)] public static extern IntPtr PFXImportCertStore( ref CRYPT_DATA_BLOB pPfx, [MarshalAs(UnmanagedType.LPWStr)] String szPassword, uint dwFlags); [DllImport("Crypt32.DLL", SetLastError = true)] public static extern IntPtr CertEnumCertificatesInStore( IntPtr storeProvider, IntPtr prevCertContext ); [DllImport("Crypt32.dll", SetLastError = true)] public static extern Boolean CertCloseStore( IntPtr hCertStore, Int32 dwFlags ); [DllImport("CRYPT32.DLL", EntryPoint = "CertGetCertificateContextProperty", CharSet = CharSet.Unicode, SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] public static extern Boolean CertGetCertificateContextProperty( [In] IntPtr pCertContext, [In] Int32 dwPropId, [Out] IntPtr pvData, [In, Out] ref Int32 pcbData); } } '@ Add-Type -TypeDefinition $Source -Language CSharp Write-Log -Message "Marshalling PFX binary..." -Type Verbose -Function $thisFunction $pPfxBinary = New-Object AzureStack.PartnerToolkit.CRYPT_DATA_BLOB $pPfxBinary.cbData = $certificateBinary.Length $pPfxBinary.pbData = [System.Runtime.InteropServices.Marshal]::AllocHGlobal($certificateBinary.Length) [System.Runtime.InteropServices.Marshal]::Copy($certificateBinary, 0, $pPfxBinary.pbData, $certificateBinary.Length) Write-Log -Message "Convert cert password to string..." -Type Verbose -Function $thisFunction if ($certificatePassword) { $networkCredential = New-Object System.Net.NetworkCredential -ArgumentList @("", $certificatePassword) $plainCertPassword = $networkCredential.Password } else { $plainCertPassword = "" } try { # PKCS12_OBJECT_LOCATOR_ALL_IMPORT_FLAGS (0x8250) | CRYPT_EXPORTABLE (0x00000001) # PKCS12_OBJECT_LOCATOR_ALL_IMPORT_FLAGS = # PKCS12_ALWAYS_CNG_KSP (0x00000200) | # PKCS12_NO_PERSIST_KEY (0x00008000) | # PKCS12_IMPORT_SILENT (0x00000040) | # PKCS12_INCLUDE_EXTENDED_PROPERTIES (0x0010) $importFlag = 0x8251 Write-Log -Message "Parsing PFX binary with flag $importFlag ..." -Type Verbose -Function $thisFunction $hCertStore = [AzureStack.PartnerToolkit.Crypto]::PFXImportCertStore([ref]$pPfxBinary, $plainCertPassword, $importFlag) if ($hCertStore -eq [System.IntPtr]::Zero) { $ErrorCode = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error() if (-not $plainCertPassword) { # PKCS12_ONLY_NOT_ENCRYPTED_CERTIFICATES (0x0800) | PKCS12_INCLUDE_EXTENDED_PROPERTIES (0x00000010) $importFlag = 0x0810 Write-Log -Message "Parsing PFX binary with error. Try to parse without password with flag $importFlag again..." -Type Verbose -Function $thisFunction $hCertStore = [AzureStack.PartnerToolkit.Crypto]::PFXImportCertStore([ref]$pPfxBinary, $plainCertPassword, $importFlag) } if ($hCertStore -eq [System.IntPtr]::Zero) { return @{ Success = $false ErrorCode = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error() } } } Write-Log -Message "Retrieving the certs from the temp store..." -Type Verbose -Function $thisFunction $ret = @{ EndEntityCertificates = @() OtherCertificates = @() } $currentCertContext = 0 while ($true) { $currentCertContext = [System.IntPtr]([AzureStack.PartnerToolkit.Crypto]::CertEnumCertificatesInStore($hCertStore, $currentCertContext)) if ($currentCertContext -ne [System.IntPtr]::Zero) { $newCert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2 -ArgumentList @($currentCertContext) if (Get-EndEntityCertificate $currentCertContext) { $ret.EndEntityCertificates += @($newCert); } else { $ret.OtherCertificates += @($newCert); } continue } break } $ret.Success = $true return $ret } finally { if ($hCertStore -ne [System.IntPtr]::Zero) { if (-not ([AzureStack.PartnerToolkit.Crypto]::CertCloseStore($hCertStore, 0))) { $ErrorCode = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error() Write-Log -Message "Close cert store with error code $ErrorCode" -Type Warning -Function $thisFunction } } } } function Get-EndEntityCertificate { param ([System.IntPtr] $pCertificate) $privateKeyPropIds = @( 5, # CERT_KEY_CONTEXT_PROP_ID 2 # CERT_KEY_PROV_INFO_PROP_ID ) foreach ($propId in $privateKeyPropIds) { $cbData = 0 if ([AzureStack.PartnerToolkit.Crypto]::CertGetCertificateContextProperty( $pCertificate, $propId, [System.IntPtr]::Zero, [ref]$cbData)) { return $true } } return $false } function New-CertificateCollection { param ([hashtable]$pfxdata) $thisFunction = $MyInvocation.MyCommand.Name #create array of all certificates from pfx package $otherCertificates = $pfxData.OtherCertificates $cert = $pfxData.EndEntityCertificates $allcerts = $otherCertificates + @($cert) Write-Log -Message ('Building certificate collection.') -Type Info -Function $thisFunction # create collection of all certificates $collection = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2Collection $allcerts | ForEach-Object {$collection.add($PSITEM) | Out-Null} Write-Log -Message ('Building certificate collection complete.') -Type Info -Function $thisFunction #return collection return $collection } function Test-TrustedChain { #Function to test certificate chain, will retry (configurable) 3 times with (configurable) 5 seconds intervals param ([Security.Cryptography.X509Certificates.X509Chain]$chain,[System.Security.Cryptography.X509Certificates.X509Certificate2]$cert,[int]$retryCount = 3,[int]$intervalSeconds = 5,[Hashtable]$certConfig ) $thisFunction = $MyInvocation.MyCommand.Name $test = 'Trusted Chain' if (!$certConfig -or (($certConfig.IncludeTests -eq 'All' -or $test -in $certConfig.IncludeTests) -and $test -notin $certConfig.ExcludeTests)) { Write-Log -Message ('Testing Chain Trust for {0}' -f ($cert | Get-ThumbprintMask)) -Type Info -Function $thisFunction $failureDetail = @() try { #create empty chain is one was not passed to the function if(-not $chain) { $chain = New-Object Security.Cryptography.X509Certificates.X509Chain } if ($chain.ChainPolicy.extrastore) { Write-Log -Message $localizedData.ChainCheckExtraStore -Type Info -Function $thisfunction } else { Write-Log -Message $localizedData.ChainCheckNoStore -Type Info -Function $thisfunction } # If no CDP info exists on the certificate disable the revocation check. $cdpInfo = $cert.Extensions | Where-Object {$_.oid.Value -eq '2.5.29.31'} if (-not $cdpInfo) { $chain.ChainPolicy.RevocationMode = 'NoCheck' Write-Log -Message $localizedData.RevocationModeNoCheck -Type Warn -Function $thisfunction } else { Write-Log -Message $localizedData.RevocationModeDefault -Type Info -Function $thisfunction } # Attempt to build the chain do { $chainResult = $chain.build($cert) $chainFailureReasons = $chain.ChainStatus.status $retry++ #sleep if unsuccessful and none CRL error present if (-not $chainResult -AND $retry -lt $retryCount -AND $chainFailureReasons) { Write-Log -Message ($localizedData.ChainCheckRetry -f ($chainFailureReasons -join ','),$retry) -Type Warn -Function $thisfunction start-Sleep -Seconds $intervalSeconds } } while (-not $chainResult -AND $retry -le $retryCount) #Interpret result if ($chainResult) { Write-Log -Message $localizedData.ChainCheckSuccess -Type Info -Function $thisfunction $result = 'OK' } else { Write-Log -Message ($localizedData.ChainCheckFailed -f ($chainFailureReasons -join ',')) -Type Warn -Function $thisfunction $failureDetail = $chain.ChainStatus.StatusInformation -join '' $result = 'Fail' } # Downgrade result and add failure detail if revocation was disabled if ($chain.ChainPolicy.RevocationMode -eq 'NoCheck') { switch ( $result ) { 'OK' {$result = 'Warning'} 'Fail' {$result = 'Fail'} } $failureDetail += $localizedData.RevocationModeNoCheck } } catch { $result = 'Fail' $failureDetail = ($localizedData.TestException -f $test, $_.exception) Write-Log -Message $failureDetail -Type Warn -Function $thisFunction } finally { if ($chain) { $chain.Dispose() } } } else { $result = 'SkippedByConfig' $failureDetail = ($LocalizedData.SkippedByConfig -f $test) Write-Log -Message $failureDetail -Type Info -Function $thisFunction } $hash = @{'Result' = $result; 'test' = $test; 'failuredetail' = $failureDetail; 'outputObject' = $chain} $object = New-Object PSObject -Property $hash $object } function Test-CertificateRoot { #test if root is the same as stored on ercs machine param ($pfx, [ValidateScript({Test-Path -Path $_ -PathType Container})] $rootpath = "$ENV:SystemDrive\ExternalCerts\Root", [Hashtable]$certConfig ) $thisFunction = $MyInvocation.MyCommand.Name $test = 'Match Root' if (!$certConfig -or (($certConfig.IncludeTests -eq 'All' -or $test -in $certConfig.IncludeTests) -and $test -notin $certConfig.ExcludeTests)) { try { $rootCerts = Get-ChildItem -Path $rootpath -Recurse -Filter *.cer Write-Log -Message ($LocalizedData.RootCertificateFoundOnStamp -f $rootCerts.count, $rootpath) -Type Info -Function $thisFunction if (-not $rootCerts) { $result = 'Fail' $failureDetail = ($LocalizedData.RootCertNotOnDisk -f $rootpath) } else { foreach ($rootCert in $rootCerts) { Write-Log -Message ("Loading {0} for same root comparison" -f $rootCert.fullname) -Type Info -Function $thisFunction $cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($rootCert.fullname) $rootThumbprint = $cert.Thumbprint $pfxIssuer = $pfx.OtherCertificates | Where-Object subject -eq $pfx.EndEntityCertificates.Issuer if ($pfxIssuer.thumbprint -eq $rootThumbprint) { Write-Log -Message ($LocalizedData.RootCertificateMatch -f ($pfxIssuer | Get-ThumbprintMask),($cert | Get-ThumbprintMask)) -Type Info -Function $thisFunction $result = 'OK' break } else { $failureDetail = $LocalizedData.RootCertificateNotMatch -f ($pfxIssuer | Get-ThumbprintMask),($cert | Get-ThumbprintMask) Write-Log -Message $failureDetail -Type Warn -Function $thisFunction $result = 'Fail' } } } } catch { $result = 'Fail' $failureDetail = ($localizedData.TestException -f $test, $_.exception.message) Write-Log -Message $failureDetail -Type Warn -Function $thisFunction } } else { $result = 'SkippedByConfig' $failureDetail = ($LocalizedData.SkippedByConfig -f $test) Write-Log -Message $failureDetail -Type Info -Function $thisFunction } $hash = @{'Result' = $result; 'test' = $test; 'failuredetail' = $failureDetail; 'outputObject' = $null} $object = New-Object PSObject -Property $hash $object } function Test-CertificateExpiry { # block if certificate expiry <= threshold (default 7 days) param ([System.Security.Cryptography.X509Certificates.X509Certificate2]$cert,[int]$expiryThresholdDays = 7,[Hashtable]$certConfig) $thisFunction = $MyInvocation.MyCommand.Name $test = 'Expiry Date' if (!$certConfig -or (($certConfig.IncludeTests -eq 'All' -or $test -in $certConfig.IncludeTests) -and $test -notin $certConfig.ExcludeTests)) { try { $thresholdDateTime = [System.DateTime]::Now.AddDays($expiryThresholdDays) $certExpiry = $cert.NotAfter if ($certExpiry -le $thresholdDateTime) { $result = 'Fail' Write-Log -Message ($localizedData.ExpiryFailure -f $certExpiry,($cert | Get-ThumbprintMask),$thresholdDateTime ) -Type Warn -Function $thisFunction if ($certExpiry -le [System.DateTime]::Now) { $failureDetail = $localizedData.ExpiredFailureDetail } else { $failureDetail = $localizedData.ExpiryFailureDetail -f $certExpiry, $expiryThresholdDays } } else { $result = 'OK' Write-Log -Message ($localizedData.ExpirySuccess -f $certExpiry,($cert | Get-ThumbprintMask),$thresholdDateTime ) -Type Info -Function $thisFunction } } catch { $result = 'Fail' $failureDetail = ($localizedData.TestException -f $test, $_.exception.message) Write-Log -Message $failureDetail -Type Warn -Function $thisFunction } } else { $result = 'SkippedByConfig' $failureDetail = ($LocalizedData.SkippedByConfig -f $test) Write-Log -Message $failureDetail -Type Info -Function $thisFunction } $hash = @{'Result' = $result; 'test' = $test; 'failuredetail' = $failureDetail; 'outputObject' = $null} $object = New-Object PSObject -Property $hash $object } function ConvertTo-Oid { param ($in) $thisFunction = $MyInvocation.MyCommand.Name try { # user may have passed in Write-Log -Message $in -Type Info -Function $thisFunction if ($in -is [string]) { $oid = [System.Security.Cryptography.Oid]::new($in) if (!$oid.FriendlyName -or !$oid.Value) { throw ("Unable to convert {0} into Oid" -f $in) } } elseif ($in -is [Hashtable]) { $oid = $in.Keys | ForEach-Object { [System.Security.Cryptography.Oid]::new($in[$PSITEM],$PSITEM) } } else { throw ("Unable to convert {0} into Oid" -f $in) } return $oid } catch { throw ("Unable to convert {0} into Oid. Make sure the input has a valid name and Oid. Error {1}" -f $in,$_.exception.message) } } # SIG # Begin signature block # MIInygYJKoZIhvcNAQcCoIInuzCCJ7cCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCAwl3HmUlOW0KCK # 7yTHhVIRj0/qKD3Uec8gkfD8JSCGX6CCDYEwggX/MIID56ADAgECAhMzAAACzI61 # lqa90clOAAAAAALMMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p # bmcgUENBIDIwMTEwHhcNMjIwNTEyMjA0NjAxWhcNMjMwNTExMjA0NjAxWjB0MQsw # CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u # ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB # AQCiTbHs68bADvNud97NzcdP0zh0mRr4VpDv68KobjQFybVAuVgiINf9aG2zQtWK # No6+2X2Ix65KGcBXuZyEi0oBUAAGnIe5O5q/Y0Ij0WwDyMWaVad2Te4r1Eic3HWH # UfiiNjF0ETHKg3qa7DCyUqwsR9q5SaXuHlYCwM+m59Nl3jKnYnKLLfzhl13wImV9 # DF8N76ANkRyK6BYoc9I6hHF2MCTQYWbQ4fXgzKhgzj4zeabWgfu+ZJCiFLkogvc0 # RVb0x3DtyxMbl/3e45Eu+sn/x6EVwbJZVvtQYcmdGF1yAYht+JnNmWwAxL8MgHMz # xEcoY1Q1JtstiY3+u3ulGMvhAgMBAAGjggF+MIIBejAfBgNVHSUEGDAWBgorBgEE # AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUiLhHjTKWzIqVIp+sM2rOHH11rfQw # UAYDVR0RBEkwR6RFMEMxKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1 # ZXJ0byBSaWNvMRYwFAYDVQQFEw0yMzAwMTIrNDcwNTI5MB8GA1UdIwQYMBaAFEhu # ZOVQBdOCqhc3NyK1bajKdQKVMFQGA1UdHwRNMEswSaBHoEWGQ2h0dHA6Ly93d3cu # bWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY0NvZFNpZ1BDQTIwMTFfMjAxMS0w # Ny0wOC5jcmwwYQYIKwYBBQUHAQEEVTBTMFEGCCsGAQUFBzAChkVodHRwOi8vd3d3 # Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY0NvZFNpZ1BDQTIwMTFfMjAx # MS0wNy0wOC5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAgEAeA8D # sOAHS53MTIHYu8bbXrO6yQtRD6JfyMWeXaLu3Nc8PDnFc1efYq/F3MGx/aiwNbcs # J2MU7BKNWTP5JQVBA2GNIeR3mScXqnOsv1XqXPvZeISDVWLaBQzceItdIwgo6B13 # vxlkkSYMvB0Dr3Yw7/W9U4Wk5K/RDOnIGvmKqKi3AwyxlV1mpefy729FKaWT7edB # d3I4+hldMY8sdfDPjWRtJzjMjXZs41OUOwtHccPazjjC7KndzvZHx/0VWL8n0NT/ # 404vftnXKifMZkS4p2sB3oK+6kCcsyWsgS/3eYGw1Fe4MOnin1RhgrW1rHPODJTG # AUOmW4wc3Q6KKr2zve7sMDZe9tfylonPwhk971rX8qGw6LkrGFv31IJeJSe/aUbG # dUDPkbrABbVvPElgoj5eP3REqx5jdfkQw7tOdWkhn0jDUh2uQen9Atj3RkJyHuR0 # GUsJVMWFJdkIO/gFwzoOGlHNsmxvpANV86/1qgb1oZXdrURpzJp53MsDaBY/pxOc # J0Cvg6uWs3kQWgKk5aBzvsX95BzdItHTpVMtVPW4q41XEvbFmUP1n6oL5rdNdrTM # j/HXMRk1KCksax1Vxo3qv+13cCsZAaQNaIAvt5LvkshZkDZIP//0Hnq7NnWeYR3z # 4oFiw9N2n3bb9baQWuWPswG0Dq9YT9kb+Cs4qIIwggd6MIIFYqADAgECAgphDpDS # AAAAAAADMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMK # V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0 # IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0 # ZSBBdXRob3JpdHkgMjAxMTAeFw0xMTA3MDgyMDU5MDlaFw0yNjA3MDgyMTA5MDla # MH4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS # ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMT # H01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTEwggIiMA0GCSqGSIb3DQEB # AQUAA4ICDwAwggIKAoICAQCr8PpyEBwurdhuqoIQTTS68rZYIZ9CGypr6VpQqrgG # OBoESbp/wwwe3TdrxhLYC/A4wpkGsMg51QEUMULTiQ15ZId+lGAkbK+eSZzpaF7S # 35tTsgosw6/ZqSuuegmv15ZZymAaBelmdugyUiYSL+erCFDPs0S3XdjELgN1q2jz # y23zOlyhFvRGuuA4ZKxuZDV4pqBjDy3TQJP4494HDdVceaVJKecNvqATd76UPe/7 # 4ytaEB9NViiienLgEjq3SV7Y7e1DkYPZe7J7hhvZPrGMXeiJT4Qa8qEvWeSQOy2u # M1jFtz7+MtOzAz2xsq+SOH7SnYAs9U5WkSE1JcM5bmR/U7qcD60ZI4TL9LoDho33 # X/DQUr+MlIe8wCF0JV8YKLbMJyg4JZg5SjbPfLGSrhwjp6lm7GEfauEoSZ1fiOIl # XdMhSz5SxLVXPyQD8NF6Wy/VI+NwXQ9RRnez+ADhvKwCgl/bwBWzvRvUVUvnOaEP # 6SNJvBi4RHxF5MHDcnrgcuck379GmcXvwhxX24ON7E1JMKerjt/sW5+v/N2wZuLB # l4F77dbtS+dJKacTKKanfWeA5opieF+yL4TXV5xcv3coKPHtbcMojyyPQDdPweGF # RInECUzF1KVDL3SV9274eCBYLBNdYJWaPk8zhNqwiBfenk70lrC8RqBsmNLg1oiM # CwIDAQABo4IB7TCCAekwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFEhuZOVQ # BdOCqhc3NyK1bajKdQKVMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1Ud # DwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFHItOgIxkEO5FAVO # 4eqnxzHRI4k0MFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6Ly9jcmwubWljcm9zb2Z0 # LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y # Mi5jcmwwXgYIKwYBBQUHAQEEUjBQME4GCCsGAQUFBzAChkJodHRwOi8vd3d3Lm1p # Y3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y # Mi5jcnQwgZ8GA1UdIASBlzCBlDCBkQYJKwYBBAGCNy4DMIGDMD8GCCsGAQUFBwIB # FjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2RvY3MvcHJpbWFyeWNw # cy5odG0wQAYIKwYBBQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AcABvAGwAaQBjAHkA # XwBzAHQAYQB0AGUAbQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAGfyhqWY # 4FR5Gi7T2HRnIpsLlhHhY5KZQpZ90nkMkMFlXy4sPvjDctFtg/6+P+gKyju/R6mj # 82nbY78iNaWXXWWEkH2LRlBV2AySfNIaSxzzPEKLUtCw/WvjPgcuKZvmPRul1LUd # d5Q54ulkyUQ9eHoj8xN9ppB0g430yyYCRirCihC7pKkFDJvtaPpoLpWgKj8qa1hJ # Yx8JaW5amJbkg/TAj/NGK978O9C9Ne9uJa7lryft0N3zDq+ZKJeYTQ49C/IIidYf # wzIY4vDFLc5bnrRJOQrGCsLGra7lstnbFYhRRVg4MnEnGn+x9Cf43iw6IGmYslmJ # aG5vp7d0w0AFBqYBKig+gj8TTWYLwLNN9eGPfxxvFX1Fp3blQCplo8NdUmKGwx1j # NpeG39rz+PIWoZon4c2ll9DuXWNB41sHnIc+BncG0QaxdR8UvmFhtfDcxhsEvt9B # xw4o7t5lL+yX9qFcltgA1qFGvVnzl6UJS0gQmYAf0AApxbGbpT9Fdx41xtKiop96 # eiL6SJUfq/tHI4D1nvi/a7dLl+LrdXga7Oo3mXkYS//WsyNodeav+vyL6wuA6mk7 # r/ww7QRMjt/fdW1jkT3RnVZOT7+AVyKheBEyIXrvQQqxP/uozKRdwaGIm1dxVk5I # RcBCyZt2WwqASGv9eZ/BvW1taslScxMNelDNMYIZnzCCGZsCAQEwgZUwfjELMAkG # A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx # HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9z # b2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAAAsyOtZamvdHJTgAAAAACzDAN # BglghkgBZQMEAgEFAKCBrjAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgor # BgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQgLGQrmS7i # TbnqhThkODckrK6uXTOu9b/nMmw0xW/y9tAwQgYKKwYBBAGCNwIBDDE0MDKgFIAS # AE0AaQBjAHIAbwBzAG8AZgB0oRqAGGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbTAN # BgkqhkiG9w0BAQEFAASCAQCQ5brwjvYNlp6RrgeDmFrZMYbTxrgNPa/YdQ5SZPl9 # oUPW+XIGZMiaIlKkEeXVSFDGj5LbH5AeiiFJMuY7T1BDjrRdAwoxcJ67khWkoeLm # bmXkG5Kk7usG17W0V2812U7/k4FbHlTwzRWdjxJU6zej1gzlzNa/4xAUfptLiTWe # DPEd4+izl7yD8QrrbieN2mwrdCeRX7cqEyN3PTyIDJ+Ex0ZK+p6WfPTBQaT1ZhsW # 4z7xggynYBGfRRreR7/B6JHurSS/9J9lskb94BOsTQcR908653a01qH0pF+WLYWi # QVFhBG2O6T60bO5Ue4AYsmQ1LhsNq1y4jbBhAFPnL+0uoYIXKTCCFyUGCisGAQQB # gjcDAwExghcVMIIXEQYJKoZIhvcNAQcCoIIXAjCCFv4CAQMxDzANBglghkgBZQME # AgEFADCCAVkGCyqGSIb3DQEJEAEEoIIBSASCAUQwggFAAgEBBgorBgEEAYRZCgMB # MDEwDQYJYIZIAWUDBAIBBQAEILGVczFGt3ifKE2Gt7ubPNqkSmyS3V+wQRwC/Z/5 # nMu+AgZjYtcG6MgYEzIwMjIxMTExMjE1MzMwLjg2NlowBIACAfSggdikgdUwgdIx # CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt # b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xLTArBgNVBAsTJE1p # Y3Jvc29mdCBJcmVsYW5kIE9wZXJhdGlvbnMgTGltaXRlZDEmMCQGA1UECxMdVGhh # bGVzIFRTUyBFU046MTc5RS00QkIwLTgyNDYxJTAjBgNVBAMTHE1pY3Jvc29mdCBU # aW1lLVN0YW1wIFNlcnZpY2WgghF4MIIHJzCCBQ+gAwIBAgITMwAAAbWtGt/XhXBt # EwABAAABtTANBgkqhkiG9w0BAQsFADB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMK # V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0 # IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0Eg # MjAxMDAeFw0yMjA5MjAyMDIyMTFaFw0yMzEyMTQyMDIyMTFaMIHSMQswCQYDVQQG # EwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwG # A1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMS0wKwYDVQQLEyRNaWNyb3NvZnQg # SXJlbGFuZCBPcGVyYXRpb25zIExpbWl0ZWQxJjAkBgNVBAsTHVRoYWxlcyBUU1Mg # RVNOOjE3OUUtNEJCMC04MjQ2MSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFt # cCBTZXJ2aWNlMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAlwsKuGVe # gsKNiYXFwU+CSHnt2a7PfWw2yPwiW+YRlEJsH3ibFIiPfk/yblMp8JGantu+7Di/ # +3e5wWN/nbJUIMUjEWJnc8JMjoPmHCWsMtJOuR/1Ru4aa1RrxQtIelq098TBl4k7 # NsEE87l7qKFmy8iwGNQjkwr0bMu4BJwy7BUXiXHegOSU992rfQ4xNZoxznv42TLQ # sc9NmcBq5WslkqVATcc8PSfgBLEpdG1Dp2wqNw4JrJFwJNA1bfzTScYABc5smRZB # gsP4JiK/8CVrlocheEyQonjm3rFttrojAreSUnixALu9pDrsBI4DUPGG34oIbieI # 1oqFl/xk7A+7uM8k4o8ifMVWNTaczbPldDYtn6hBre7r25RED4uecCxP8Dxy34YP # UElWllPP3LAXp5cMwRjx+EWzjEtILEKXuAcfxrXCTwyYhm5XNzCCZYh4/gF2U2y/ # bYfekKpaoFYwkoZeT6ZxoQbX5Kftgj+tZkFV21UvZIkJ6b34a/44dtrsK6diTmVn # NTM9J6P6Ehlk2sfcUwbHIGL8mYqdKOiyd4RxOCmSvcFNkZEgrk548mHCbDbTyO9x # SzN1EkWxbp8n/LHVnZ9fp5hILGntkMzaD5aXRCQyHSIhsPtR7Q/rKoHyjFqgtGO9 # ftnxYvxzNrbKeMCzwmcqwMrX6Hcxe0SeKZ8CAwEAAaOCAUkwggFFMB0GA1UdDgQW # BBRsUIbZgoZVXVXVWQX0Ok1VO2bHUzAfBgNVHSMEGDAWgBSfpxVdAF5iXYP05dJl # pxtTNRnpcjBfBgNVHR8EWDBWMFSgUqBQhk5odHRwOi8vd3d3Lm1pY3Jvc29mdC5j # b20vcGtpb3BzL2NybC9NaWNyb3NvZnQlMjBUaW1lLVN0YW1wJTIwUENBJTIwMjAx # MCgxKS5jcmwwbAYIKwYBBQUHAQEEYDBeMFwGCCsGAQUFBzAChlBodHRwOi8vd3d3 # Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY3Jvc29mdCUyMFRpbWUtU3Rh # bXAlMjBQQ0ElMjAyMDEwKDEpLmNydDAMBgNVHRMBAf8EAjAAMBYGA1UdJQEB/wQM # MAoGCCsGAQUFBwMIMA4GA1UdDwEB/wQEAwIHgDANBgkqhkiG9w0BAQsFAAOCAgEA # kFGOpyjKV2s2sA+wTqDwDdhp0mFrPtiU4rN3OonTWqb85M6WH19c/P517xujLCih # /HllP5xKWmXnAIRV1/NQDkJBLSdLTb/NQtcT1FWGQ7CMTnrn9tLZxqIFtKVylvQN # yh31C/qkC8QmNpyzakO0G38uOGgOkJ9Eq4nA+7QwVfobDlggWuEpzdFnRdyXL32g # OqSvrLjFKpv4KEVqaBTiaxCWZDlIhG3YgUza7cnG5Z2SA/feMq/IiV06AzUadZw6 # XgcTrqXmEmE0tMmdl44MMFC3wGU9AVeFCWKdD9WOnYA2zHg+XF2LQVto0VYtFLd6 # c6DQFcmB38GvPCKVYSn8r10EoXuRN+gQ7hLcim12esOnW4F4bHCmHWTVWeAGgPiS # ItHHRfGKLEUZmotVOdFPR8wiuADT/fHSXBkkdpL12tvgEGELeTznzFulZ16b/Nv6 # dtbgSRZreesJBNKpTjdYju/GqnlAkpflL6J0wxk957/UVYnmjjRY61jX90QGQmBz # m9vs/+2bj02Xx/bXXy8vq57jmNXQ2ufOaJm3nAcD2qOaSyXEOj9mqhMt4tdvMjHh # iNPldfj0Q7Kq1HgdRBrKWkzCQNi4ts8HRJBipNaVpWfU7BcRn8BeYzdLoIzwRLDt # atz6aBho3oD/bXHrZagxprM5MsMB/rVfb5Xn1YS7/uEwggdxMIIFWaADAgECAhMz # AAAAFcXna54Cm0mZAAAAAAAVMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJV # UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE # ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9v # dCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgMjAxMDAeFw0yMTA5MzAxODIyMjVaFw0z # MDA5MzAxODMyMjVaMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9u # MRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRp # b24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMIICIjAN # BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA5OGmTOe0ciELeaLL1yR5vQ7VgtP9 # 7pwHB9KpbE51yMo1V/YBf2xK4OK9uT4XYDP/XE/HZveVU3Fa4n5KWv64NmeFRiMM # tY0Tz3cywBAY6GB9alKDRLemjkZrBxTzxXb1hlDcwUTIcVxRMTegCjhuje3XD9gm # U3w5YQJ6xKr9cmmvHaus9ja+NSZk2pg7uhp7M62AW36MEBydUv626GIl3GoPz130 # /o5Tz9bshVZN7928jaTjkY+yOSxRnOlwaQ3KNi1wjjHINSi947SHJMPgyY9+tVSP # 3PoFVZhtaDuaRr3tpK56KTesy+uDRedGbsoy1cCGMFxPLOJiss254o2I5JasAUq7 # vnGpF1tnYN74kpEeHT39IM9zfUGaRnXNxF803RKJ1v2lIH1+/NmeRd+2ci/bfV+A # utuqfjbsNkz2K26oElHovwUDo9Fzpk03dJQcNIIP8BDyt0cY7afomXw/TNuvXsLz # 1dhzPUNOwTM5TI4CvEJoLhDqhFFG4tG9ahhaYQFzymeiXtcodgLiMxhy16cg8ML6 # EgrXY28MyTZki1ugpoMhXV8wdJGUlNi5UPkLiWHzNgY1GIRH29wb0f2y1BzFa/Zc # UlFdEtsluq9QBXpsxREdcu+N+VLEhReTwDwV2xo3xwgVGD94q0W29R6HXtqPnhZy # acaue7e3PmriLq0CAwEAAaOCAd0wggHZMBIGCSsGAQQBgjcVAQQFAgMBAAEwIwYJ # KwYBBAGCNxUCBBYEFCqnUv5kxJq+gpE8RjUpzxD/LwTuMB0GA1UdDgQWBBSfpxVd # AF5iXYP05dJlpxtTNRnpcjBcBgNVHSAEVTBTMFEGDCsGAQQBgjdMg30BATBBMD8G # CCsGAQUFBwIBFjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL0RvY3Mv # UmVwb3NpdG9yeS5odG0wEwYDVR0lBAwwCgYIKwYBBQUHAwgwGQYJKwYBBAGCNxQC # BAweCgBTAHUAYgBDAEEwCwYDVR0PBAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHwYD # VR0jBBgwFoAU1fZWy4/oolxiaNE9lJBb186aGMQwVgYDVR0fBE8wTTBLoEmgR4ZF # aHR0cDovL2NybC5taWNyb3NvZnQuY29tL3BraS9jcmwvcHJvZHVjdHMvTWljUm9v # Q2VyQXV0XzIwMTAtMDYtMjMuY3JsMFoGCCsGAQUFBwEBBE4wTDBKBggrBgEFBQcw # AoY+aHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraS9jZXJ0cy9NaWNSb29DZXJB # dXRfMjAxMC0wNi0yMy5jcnQwDQYJKoZIhvcNAQELBQADggIBAJ1VffwqreEsH2cB # MSRb4Z5yS/ypb+pcFLY+TkdkeLEGk5c9MTO1OdfCcTY/2mRsfNB1OW27DzHkwo/7 # bNGhlBgi7ulmZzpTTd2YurYeeNg2LpypglYAA7AFvonoaeC6Ce5732pvvinLbtg/ # SHUB2RjebYIM9W0jVOR4U3UkV7ndn/OOPcbzaN9l9qRWqveVtihVJ9AkvUCgvxm2 # EhIRXT0n4ECWOKz3+SmJw7wXsFSFQrP8DJ6LGYnn8AtqgcKBGUIZUnWKNsIdw2Fz # Lixre24/LAl4FOmRsqlb30mjdAy87JGA0j3mSj5mO0+7hvoyGtmW9I/2kQH2zsZ0 # /fZMcm8Qq3UwxTSwethQ/gpY3UA8x1RtnWN0SCyxTkctwRQEcb9k+SS+c23Kjgm9 # swFXSVRk2XPXfx5bRAGOWhmRaw2fpCjcZxkoJLo4S5pu+yFUa2pFEUep8beuyOiJ # Xk+d0tBMdrVXVAmxaQFEfnyhYWxz/gq77EFmPWn9y8FBSX5+k77L+DvktxW/tM4+ # pTFRhLy/AsGConsXHRWJjXD+57XQKBqJC4822rpM+Zv/Cuk0+CQ1ZyvgDbjmjJnW # 4SLq8CdCPSWU5nR0W2rRnj7tfqAxM328y+l7vzhwRNGQ8cirOoo6CGJ/2XBjU02N # 7oJtpQUQwXEGahC0HVUzWLOhcGbyoYIC1DCCAj0CAQEwggEAoYHYpIHVMIHSMQsw # CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u # ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMS0wKwYDVQQLEyRNaWNy # b3NvZnQgSXJlbGFuZCBPcGVyYXRpb25zIExpbWl0ZWQxJjAkBgNVBAsTHVRoYWxl # cyBUU1MgRVNOOjE3OUUtNEJCMC04MjQ2MSUwIwYDVQQDExxNaWNyb3NvZnQgVGlt # ZS1TdGFtcCBTZXJ2aWNloiMKAQEwBwYFKw4DAhoDFQCNMJ9r11RZj0PWu3uk+aQH # F3IsVaCBgzCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9u # MRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRp # b24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMA0GCSqG # SIb3DQEBBQUAAgUA5xkykTAiGA8yMDIyMTExMjA0NDQwMVoYDzIwMjIxMTEzMDQ0 # NDAxWjB0MDoGCisGAQQBhFkKBAExLDAqMAoCBQDnGTKRAgEAMAcCAQACAg/LMAcC # AQACAhF5MAoCBQDnGoQRAgEAMDYGCisGAQQBhFkKBAIxKDAmMAwGCisGAQQBhFkK # AwKgCjAIAgEAAgMHoSChCjAIAgEAAgMBhqAwDQYJKoZIhvcNAQEFBQADgYEAjziN # 2m8d2VLj0OFhVeQk5c6zDc7j5T+lJHhf1mxbO5zH+MvISAjpQU8piZlWXWgoCgF6 # FOfqONSDgZCj7BGNPACnpJbPc9QRfH4HipahiY2MpKVjOW2fWMBJ1Jxto8TZnCsP # h6l8NN7CKC5hWuLnIHIhp+n1mpmGQh8zXVH+gy4xggQNMIIECQIBATCBkzB8MQsw # CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u # ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNy # b3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMAITMwAAAbWtGt/XhXBtEwABAAABtTAN # BglghkgBZQMEAgEFAKCCAUowGgYJKoZIhvcNAQkDMQ0GCyqGSIb3DQEJEAEEMC8G # CSqGSIb3DQEJBDEiBCDLgpLzD+dL3mHj59fXyDL0Vuz3EKpJDFWi+AZRTPWKjDCB # +gYLKoZIhvcNAQkQAi8xgeowgecwgeQwgb0EICfKDTUtaGcWifYc3OVnIpp7Ykn0 # S8JclVzrlAgF8ciDMIGYMIGApH4wfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldh # c2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBD # b3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIw # MTACEzMAAAG1rRrf14VwbRMAAQAAAbUwIgQg01BSamR72g5YL1pgLFZt98ymG11S # yJ/c9zWIy12IT58wDQYJKoZIhvcNAQELBQAEggIALlDQpljkTivfTENakPjeyjY8 # RWR0K488JDFDtg0/SXTyaU4LIrY7GL+ihPpDmVe1ZYiST0JuUsZG7sWlv0Fq7M1n # Tkwq+naR+U0qsF9kdEl9FXzfPKvD8Rv0CovekkeJAjsc8mIIFGa7DLKysTkwytUp # z5voEHAzTGh7HXNJoBRZDFRq4vhLrku1YwC1icZu6yscNchbZ5tncac7lWAjXMe7 # b7c15TYR1p6s9mqEp0ncx0WHAyyL28Yw083rhZqmvpE4qkCOEeJ2p9veU5aSQMg7 # sV1welnERa4sIrhxY9MT45OLM5cSs5ctkFMjyYeRGv9qug/J8V1dHUVg01vkJg/v # t76/dq7ozliIi23gcDMf+2xsNZgt3CEG3+2h0/e5Cr3ZpyDW+nXhKfH4OjWOrqES # WAKDiGBxo8BzWX7i3oQm7QWcncHMKRh0FzZAarx7RB/7RUyPbNKdhxgoakniC3x6 # uyCcAwQhvb7ExPdGvD36qRFniFt0GD96z6G3KC8RXTtc+P8aqon3pUl/4/b9l7Gv # PIRXaAM4M2RWS3TkvdqHLT25qE4JYcPTeJe3XXuRZpMU8ZwSvt99Vj4KyC1VLuNj # J7++OeYs3Vt5x9Ph+bXVlnXeGQyOgK6TpCNK+hn5FkXrUJqRAY9JwyqwD1YL/OeD # P9iGo7dx9TCD3CRjPEE= # SIG # End signature block |