CertificateValidation/Microsoft.AzureStack.PublicCertificateRequest.Internal.psm1
function Set-CertificateSubject { <# .SYNOPSIS Set Distinguished name for certificate request .DESCRIPTION Checks if Common Name is present and honours CN in distinguished name. If common name is not present, it preprends the target RP hostname to the distinguished name as a common name value. #> param ( [System.Security.Cryptography.X509Certificates.X500DistinguishedName]$distinguishedName, [System.Security.Cryptography.X509Certificates.X500DistinguishedNameFlags]$distinguishedNameFlag = 'UseUTF8Encoding', [string]$commonName ) try { $thisFunction = $MyInvocation.MyCommand.Name if ($distinguishedName) { $distinguishedNameExpanded = $distinguishedName.Format($true) $distinguishedNameString = $distinguishedName.Format($false) Write-AzsReadinessLog -Message ("User DN String is: {0}, flag: {1}" -f $distinguishedNameString,$distinguishedNameFlag) -Type Info -Function $thisFunction $commonNamePair = $distinguishedNameExpanded.trim() -split "`n" | Where-Object { $PSITEM -imatch 'cn='} } if ($commonNamePair) { Write-AzsReadinessLog -Message ("User DN String contains common name, no change: {0}" -f $distinguishedNameString) -Type Info -Function $thisFunction } else { $distinguishedNameString = "CN={0}" -f (($commonName,$distinguishedName.Name) -join ',') Write-AzsReadinessLog -Message ("Calculated DN String is: {0}, flag: {1}." -f $distinguishedNameString,$distinguishedNameFlag) -Type Info -Function $thisFunction } return [System.Security.Cryptography.X509Certificates.X500DistinguishedName]::new($distinguishedNameString,[System.Security.Cryptography.X509Certificates.X500DistinguishedNameFlags]::UseUTF8Encoding) } catch { Write-AzsReadinessLog -Message ("Setting Certificate Subject failed with exception: {0}" -f $_.exception.message) -Type Error -Function $thisFunction -toScreen break } } function Get-CSRFileName { param ( [array]$commonName ) $filename = "{0}_certrequest.req" -f $commonName[0].Replace('.', '_').Replace('*', 'wildcard') return $filename } function Write-AzsCertificateRequestFileInternal { <# .SYNOPSIS Internal function to call CSR using the standard Azure Stack request template. .DESCRIPTION Imports the standard Azure Stack certificate request template replaces deployment specific data such as subject and SAN. Detects if commas are used in the subject name and thus requiring a different X500NameFlag .EXAMPLE Write-AzsCertificateRequestFileInternal -subjectAltNames $completeSANs -subject $subject -KeyLength $KeyLength -HashAlgorithm $HashAlgorithm -OutputRequestPath $OutputRequestPath .INPUTS subjectAltNames - string - SubjectAlternativeNames subject - ordereddictionary - hashtable of deployment subject OutputRequestPath - string - path (parent must be valid) to where the CSRs should land. KeyLength - int - Defines the length of the public and private key HashAlgorithm - string - Hash Algorithm to be used for this request. .OUTPUTS Encoded CSR file - This is the file or content that should be presented to a CA to request certificates Clear Text INF file - this is a reference file to help debugging. Certreq log file - the output of the certreq.exe command, later streamed into overall AzsCertificateRequest.log .NOTES #> [cmdletbinding()] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$subject, [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string[]]$subjectAltNames, [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [ValidateScript( {Test-Path (Split-Path $_ -parent) -PathType Container})] $OutputRequestPath, [Parameter(Mandatory = $false)] [ValidateSet(2048, 4096, 8192)] [int]$KeyLength = 2048, [Parameter(Mandatory = $false)] [ValidateSet('SHA256', 'SHA384', 'SHA512')] [string]$HashAlgorithm = 'SHA256', [Parameter(Mandatory = $false)] [string[]]$KeyUsage = @('DigitalSignature','KeyEncipherment'), [Parameter(Mandatory = $false)] $KeyUsageEKUExtension = @('Server Authentication','Client Authentication'), [Parameter(Mandatory = $false)] [System.Security.Cryptography.X509Certificates.X500DistinguishedNameFlags]$DistinguishedNameFlag = $null ) $thisFunction = $MyInvocation.MyCommand.Name try { # translate DistinguishNameFlag $x500flagValue = switch ($DistinguishedNameFlag) { 'DoNotUsePlusSign' {'0x20000000'} 'DoNotUseQuotes' {'0x10000000'} 'ForceUTF8Encoding'{'0x80000'} 'None' {'0'} 'Reversed' {'0x2000000'} 'UseCommas' {'0x4000000'} 'UseNewLines' {'0x8000000'} 'UseSemicolons' {'0x40000000'} 'UseT61Encoding' {'0x20000'} 'UseUTF8Encoding' {'0x40000'} 'Default' {'0'} } # if name flag isn't 'none' write the flag in the inf file, otherwise write nothing. if ($x500flagValue -ne '0') { $X500NameFlags = "X500NameFlags = $x500flagValue`n`r" } # Get Key Usages Strings $keyUsageStrings = Format-KeyUsage -KeyUsage $KeyUsage # Get Enhanced Key Usage strings $EKUInfStrings = Format-EnhancedKeyUsage -EKUExtension $KeyUsageEKUExtension # Get SAN DNS names formatted $sanString = Format-SubjectAlternativeNames -subjectAltNames $subjectAltNames # Get INF Template and replace placeholders with user data $data = Import-PowerShellDataFile $PSScriptRoot\Microsoft.AzureStack.PublicCertificateRequestData.psd1 $infTemplate = $data.requestINF $requestINF = $infTemplate.Replace('[[SubjectString]]', $subject.replace('"','')) $requestINF = $requestINF.Replace('[[KeyUsage]]',$keyUsageStrings) $requestINF = $requestINF.Replace('[[X500]]', $X500NameFlags) $requestINF = $requestINF.Replace('[[SANStrings]]', $sanString) $requestINF = $requestINF.Replace('[[KeyLength]]', $KeyLength) $requestINF = $requestINF.Replace('[[HashAlgorithm]]', $HashAlgorithm) $requestINF = $requestINF.Replace('[[EnhancedKeyStrings]]', $EKUInfStrings.Strings) $requestINF = $requestINF.Replace('[[EnhancedKeyExtension]]', $EKUInfStrings.Extensions) $CertReqFilePaths = Get-CertReqFilePaths -filePath $OutputRequestPath $infFilePath = $CertReqFilePaths.infFilePath $csrFilePath = $CertReqFilePaths.csrFilePath $certReqLogPath = $CertReqFilePaths.certReqLogPath # Create and print Inf template $requestINF | Out-File $infFilePath -Force # Create encoded cert request Write-AzsReadinessLog -Message ("Setting csrFilePath to {0}" -f $csrFilePath) -Type Info -Function $thisFunction $cmd = "-new $infFilePath $csrFilePath" $process = Start-Process -FilePath certreq.exe ` -ArgumentList $cmd ` -WindowStyle Hidden ` -PassThru ` -Wait ` -RedirectStandardOutput $certReqLogPath if ($process.ExitCode -ne 0) { $certReqLogContent = Get-Content $certReqLogPath throw $certReqLogContent } Write-AzsReadinessLog -Message ("CSR generating for following SAN(s): {0}" -f ($subjectAltNames -join ',')) -Type Info -Function $thisFunction -toScreen Write-AzsReadinessLog -Message ("Present this CSR to your Certificate Authority for Certificate Generation: {0}" -f $csrFilePath) -Type Info -Function $thisFunction -toScreen } catch { Write-AzsReadinessLog -Message ("CSR generation failed with: {0}" -f $_.exception) -Type Error -Function $thisFunction throw ("CSR generation failed with: {0}" -f $_.exception) } finally { # Move inf file to child directory called inf $infDest = ("{0}\{1}" -f (Split-Path $infFilePath -parent), 'Inf') if (-not (Test-Path $infDest)) {$null = New-Item -path $infDest -ItemType Directory -Force} Move-Item -Path $infFilePath -Destination $infDest -Force -ErrorAction SilentlyContinue # Move log content to parent log and clean up log file. $certReqLogContent = Get-Content $certReqLogPath -ErrorAction SilentlyContinue | ForEach-Object {if ($_) {$_}} if (-not $certReqLogContent) {$certReqLogContent = '[missing]'} Write-AzsReadinessLog -Message ("Certreq.exe output: {0}" -f ($certReqLogContent -join '. ')) -Type Info -Function $thisFunction -toScreen Remove-item $certReqLogPath -Force } } function Format-KeyUsage { param([string[]]$KeyUsage) $thisFunction = $MyInvocation.MyCommand.Name try { Write-AzsReadinessLog -Message ("Formating key {0} usage(s)." -f $KeyUsage.Length) -Type Info -Function $thisFunction $KeyUsageOutput = for ($i = 0; $i -lt $KeyUsage.Length; $i++) { # convert the value Write-AzsReadinessLog -Message ("Formating Key usage: {0}" -f $KeyUsage[$i]) -Type Info -Function $thisFunction $keyUsageValue = switch ($KeyUsage[$i]) { digitalSignature {'CERT_DIGITAL_SIGNATURE_KEY_USAGE'} nonRepudiation {'CERT_NON_REPUDIATION_KEY_USAGE'} keyEncipherment {'CERT_KEY_ENCIPHERMENT_KEY_USAGE'} dataEncipherment {'CERT_DATA_ENCIPHERMENT_KEY_USAGE'} keyAgreement {'CERT_KEY_AGREEMENT_KEY_USAGE'} keyCertSign {'CERT_KEY_CERT_SIGN_KEY_USAGE'} cRLSign {'CERT_CRL_SIGN_KEY_USAGE'} encipherOnly {'CERT_ENCIPHER_ONLY_KEY_USAGE'} decipherOnly {'CERT_DECIPHER_ONLY_KEY_USAGE'} } "`"$keyUsageValue`"" } $keyUsageString = $KeyUsageOutput -join '|' $keyUsageString } catch { Write-AzsReadinessLog -Message ("Formatting key usage failed with exception: {0}" -f $_.exception) -Type Error -Function $thisFunction break } } function Format-EnhancedKeyUsage { <# .SYNOPSIS Crafts neccessary data for certificate request file .DESCRIPTION Creates strings section and extensions section data e.g. [Strings] szOID_SUBJECT_ALT_NAME2 = "2.5.29.17" szOID_ENHANCED_KEY_USAGE = "2.5.29.37" szOID_PKIX_KP_SERVER_AUTH = "1.3.6.1.5.5.7.3.1" szOID_PKIX_KP_CLIENT_AUTH = "1.3.6.1.5.5.7.3.2" [Extensions] %szOID_SUBJECT_ALT_NAME2% = "{text}[[SANStrings]]" %szOID_ENHANCED_KEY_USAGE% = "{text}%szOID_PKIX_KP_SERVER_AUTH%,%szOID_PKIX_KP_CLIENT_AUTH%" #> param ($EKUExtension) $thisFunction = $MyInvocation.MyCommand.Name try { $dataFile = Import-PowerShellDataFile $PSScriptRoot\Microsoft.AzureStack.CertificateConfig.psd1 $EKUHashtable = $dataFile.CertificateAttributes.EnhancedKeyUsage Write-AzsReadinessLog -Message ("Formating Enhanced Key usages: {0}" -f ($EKUExtension -join ',')) -Type Info -Function $thisFunction if ($null -ne $EKUExtension) { $enhancedStrings = @() $enhancedExtensions = @() $enhancedStrings += "szOID_ENHANCED_KEY_USAGE = `"2.5.29.37`"" foreach ($eku in $EKUExtension) { Write-AzsReadinessLog -Message ("Formating Key usage: {0}" -f $eku) -Type Info -Function $thisFunction $ekuData = $EKUHashtable[$eku] # if the eku isn't present in the config, treat it as user supplied if (-not $ekuData) { $ekuData = @{} $eku.keys | ForEach-Object { $stringName = "XCN_OID_{0}" -f $_.replace(' ','_') $ekuData.Add($stringName, $eku[$_]) } } # Construct string format $ekuString = "{0} = `"{1}`"" -f $ekuData.GetEnumerator().Name,$ekuData[$ekuData.GetEnumerator().Name] Write-AzsReadinessLog -Message ("EKU String: {0}" -f $ekuString) -Type Info -Function $thisFunction # Append string section with EKU and OID $enhancedStrings += $ekuString.replace('XCN_','sz') Write-AzsReadinessLog -Message ("Enhanced String: {0}" -f $enhancedStrings) -Type Info -Function $thisFunction # Append Extension section with EKU and OID $enhancedExtensions += $ekuData.GetEnumerator().Name.replace('XCN_','sz') Write-AzsReadinessLog -Message ("EKU Extension: {0}" -f $enhancedExtensions) -Type Info -Function $thisFunction } $enhancedExtensionString += "%szOID_ENHANCED_KEY_USAGE% = `"{{text}}%{0}%`"" -f ($enhancedExtensions -join '%,%') } @{Strings = ($enhancedStrings -join "`n"); Extensions = $enhancedExtensionString} } Catch { Write-AzsReadinessLog -Message ("Formatting key usage failed with exception: {0}" -f $_.exception) -Type Error -Function $thisFunction break } } function ConvertTo-EnhancedKeyUsage { <# .SYNOPSIS Converts custom input from user to Oid collection of Enhanced Key Usage .DESCRIPTION Converts custom input from user to Oid collection of Enhanced Key Usage .EXAMPLE PS C:\> ConvertTo-EnhancedKeyUsage @('Server Authentication','Client Authentication',@{'My_Custom_Name' = '1.3.6.1.5.15.7.3.2'}) Converts custom input from user to Oid collection of Enhanced Key Usage #> param ($EnhancedKeyUsages) $thisFunction = $MyInvocation.MyCommand.Name try { $EKUCollection = [System.Security.Cryptography.OidCollection]::new() $EKUCollection += foreach ($EnhancedKeyUsage in $EnhancedKeyUsages) { if ($EnhancedKeyUsage -is [HashTable]) { $EnhancedKeyUsage.keys | ForEach-Object { $oid = [System.Security.Cryptography.Oid]::new($EnhancedKeyUsage[$_], $_) [void]$EKUCollection.Add($oid) Write-AzsReadinessLog -Message ('Added OID {0} ({1})' -f $oid.Value,$oid.FriendlyName) -Type Info -Function $thisFunction } } else { $oid = [System.Security.Cryptography.Oid]::new($EnhancedKeyUsage) [void]$EKUCollection.Add($oid) Write-AzsReadinessLog -Message ('Added OID {0} ({1})' -f $oid.Value,$oid.FriendlyName) -Type Info -Function $thisFunction } } $EKUCollection | Foreach-Object { if (!$_.FriendlyName -or !$_.Value) { $errorMsg = ("Invalid Oid data. `nName: '{0}' `nValue: '{1}' `nEnsure input is valid. For example, the following will request Client and Server Auth key usage and the custom usage: `n@('Client Authentication','Server Authentication',@{{'Custom Usage' = '1.3.6.1.5.7.8.2.1'}})" -f $_.friendlyName, $_.Value) Write-AzsReadinessLog -Message $errorMsg -Type Error -Function $thisFunction throw $errorMsg } } } catch { throw $_ } } function Format-SubjectAlternativeNames { <# .SYNOPSIS Formats array of DNS names for SAN .DESCRIPTION Creates neccessary string for SAN in certificate request inf config. %szOID_SUBJECT_ALT_NAME2% = "{text}dns=*.queue.east.azurestack.contoso.com" #> param ([string[]]$subjectAltNames) $thisFunction = $MyInvocation.MyCommand.Name try { $sanStrings = @() foreach ($san in $subjectAltNames) { Write-AzsReadinessLog -Message ("Formatting Subject Alternative DNS Names : {0}" -f $san) -Type Info -Function $thisFunction $sanStrings += "dns={0}" -f $san } $sanStrings -join "&" } catch { Write-AzsReadinessLog -Message ("Formatting Subject Alternative DNS Names failed with exception: {0}" -f $_.exception) -Type Error -Function $thisFunction break } } function Test-DistinguishedName { <# .SYNOPSIS Ensure user specified distinguished name is valid. .DESCRIPTION Ensure the distringuished name has the rght number of rdn when it is formatted .NOTES General notes #> param ( [System.Security.Cryptography.X509Certificates.X500DistinguishedName]$DistinguishedName, [System.Security.Cryptography.X509Certificates.X500DistinguishedNameFlags]$DistinguishedNameFlag ) $thisFunction = $MyInvocation.MyCommand.Name try { $dn = [Security.Cryptography.X509Certificates.X500DistinguishedName]::new($distinguishedName.Name,$DistinguishedNameFlag) $rdnCount = $distinguishedName.Name.ToCharArray() | Group-Object -NoElement | Where-Object Name -eq '=' | Select-Object -ExpandProperty Count $dnFormat = $dn.Format($true) $formatLineCount = $dnFormat | Measure-Object -Line | Select-Object -ExpandProperty Lines # Checking for common issues if ($dnFormat -match ',' -and $DistinguishedNameFlag -ne 'UseSemicolons') { throw ("One or more RDNs appear to contain commas, the correct format for the distinguished name is to seperate RDNs with semi-colons and pass the UseSemiColons DistinguishedNameFlag") } # if the number of rdns doesn't match the number of '=' characters, the format probably isn't what the user intended. if ($rdnCount -ne $formatLineCount) { throw ("Error validating the distinguish name provided: {0}" -f $dnFormat) } Write-AzsReadinessLog ("Validated the distinguished name: {0}" -f $dnFormat) -Type Info -Function $thisFunction return $true } catch { Write-AzsReadinessLog ("Check the input of the distinguished name. Error {0}" -f $_.exception.message) -type Error -Function $thisFunction -toScreen throw $_.exception.message } } function Get-CertReqFilePaths { <# .SYNOPSIS Short description .DESCRIPTION Long description .EXAMPLE PS C:\> $FilePath = Join-Path -Path $OutputRequestPath -ChildPath $subjectAltNames[0].Replace('.', '_').Replace('*', 'wildcard') Get-CertReqFilePaths -filePath $FilePath outputs inf path, req path and log path. #> param ($filePath) $thisFunction = $MyInvocation.MyCommand.Name $directory = Split-Path $filePath -Parent $fileSeed = Split-Path $filePath -Leaf #Create filepaths for inf, req and log as [host|wildcard].region.external.fqdn-CertRequest- $infFilePath = "{0}\{1}_CertRequest_{2}_ClearTextDoNotUse.inf" -f $directory, $fileSeed, (Get-Date -f yyyyMMddHHmmss) Write-AzsReadinessLog -Message ("Setting infFilePath to {0}" -f $infFilePath) -Type Info -Function $thisFunction $csrFilePath = $infFilePath.Replace('_ClearTextDoNotUse', '').Replace('inf', 'req') $certReqLogPath = $csrFilePath.Replace('.req', '.log') @{infFilePath = $infFilePath; csrFilePath = $csrFilePath; certReqLogPath = $certReqLogPath} } # SIG # Begin signature block # MIIjkwYJKoZIhvcNAQcCoIIjhDCCI4ACAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCAGpr7UkJk+Xw7O # Znn4ghutPevmZs8qPnjDK+QZvxonb6CCDYUwggYDMIID66ADAgECAhMzAAABiK9S # 1rmSbej5AAAAAAGIMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p # bmcgUENBIDIwMTEwHhcNMjAwMzA0MTgzOTQ4WhcNMjEwMzAzMTgzOTQ4WjB0MQsw # CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u # ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB # AQCSCNryE+Cewy2m4t/a74wZ7C9YTwv1PyC4BvM/kSWPNs8n0RTe+FvYfU+E9uf0 # t7nYlAzHjK+plif2BhD+NgdhIUQ8sVwWO39tjvQRHjP2//vSvIfmmkRoML1Ihnjs # 9kQiZQzYRDYYRp9xSQYmRwQjk5hl8/U7RgOiQDitVHaU7BT1MI92lfZRuIIDDYBd # vXtbclYJMVOwqZtv0O9zQCret6R+fRSGaDNfEEpcILL+D7RV3M4uaJE4Ta6KAOdv # V+MVaJp1YXFTZPKtpjHO6d9pHQPZiG7NdC6QbnRGmsa48uNQrb6AfmLKDI1Lp31W # MogTaX5tZf+CZT9PSuvjOCLNAgMBAAGjggGCMIIBfjAfBgNVHSUEGDAWBgorBgEE # AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUj9RJL9zNrPcL10RZdMQIXZN7MG8w # VAYDVR0RBE0wS6RJMEcxLTArBgNVBAsTJE1pY3Jvc29mdCBJcmVsYW5kIE9wZXJh # dGlvbnMgTGltaXRlZDEWMBQGA1UEBRMNMjMwMDEyKzQ1ODM4NjAfBgNVHSMEGDAW # gBRIbmTlUAXTgqoXNzcitW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8v # d3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIw # MTEtMDctMDguY3JsMGEGCCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDov # L3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDEx # XzIwMTEtMDctMDguY3J0MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIB # ACnXo8hjp7FeT+H6iQlV3CcGnkSbFvIpKYafgzYCFo3UHY1VHYJVb5jHEO8oG26Q # qBELmak6MTI+ra3WKMTGhE1sEIlowTcp4IAs8a5wpCh6Vf4Z/bAtIppP3p3gXk2X # 8UXTc+WxjQYsDkFiSzo/OBa5hkdW1g4EpO43l9mjToBdqEPtIXsZ7Hi1/6y4gK0P # mMiwG8LMpSn0n/oSHGjrUNBgHJPxgs63Slf58QGBznuXiRaXmfTUDdrvhRocdxIM # i8nXQwWACMiQzJSRzBP5S2wUq7nMAqjaTbeXhJqD2SFVHdUYlKruvtPSwbnqSRWT # GI8s4FEXt+TL3w5JnwVZmZkUFoioQDMMjFyaKurdJ6pnzbr1h6QW0R97fWc8xEIz # LIOiU2rjwWAtlQqFO8KNiykjYGyEf5LyAJKAO+rJd9fsYR+VBauIEQoYmjnUbTXM # SY2Lf5KMluWlDOGVh8q6XjmBccpaT+8tCfxpaVYPi1ncnwTwaPQvVq8RjWDRB7Pa # 8ruHgj2HJFi69+hcq7mWx5nTUtzzFa7RSZfE5a1a5AuBmGNRr7f8cNfa01+tiWjV # Kk1a+gJUBSP0sIxecFbVSXTZ7bqeal45XSDIisZBkWb+83TbXdTGMDSUFKTAdtC+ # r35GfsN8QVy59Hb5ZYzAXczhgRmk7NyE6jD0Ym5TKiW5MIIHejCCBWKgAwIBAgIK # YQ6Q0gAAAAAAAzANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNV # BAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jv # c29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlm # aWNhdGUgQXV0aG9yaXR5IDIwMTEwHhcNMTEwNzA4MjA1OTA5WhcNMjYwNzA4MjEw # OTA5WjB+MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE # BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSgwJgYD # VQQDEx9NaWNyb3NvZnQgQ29kZSBTaWduaW5nIFBDQSAyMDExMIICIjANBgkqhkiG # 9w0BAQEFAAOCAg8AMIICCgKCAgEAq/D6chAcLq3YbqqCEE00uvK2WCGfQhsqa+la # UKq4BjgaBEm6f8MMHt03a8YS2AvwOMKZBrDIOdUBFDFC04kNeWSHfpRgJGyvnkmc # 6Whe0t+bU7IKLMOv2akrrnoJr9eWWcpgGgXpZnboMlImEi/nqwhQz7NEt13YxC4D # dato88tt8zpcoRb0RrrgOGSsbmQ1eKagYw8t00CT+OPeBw3VXHmlSSnnDb6gE3e+ # lD3v++MrWhAfTVYoonpy4BI6t0le2O3tQ5GD2Xuye4Yb2T6xjF3oiU+EGvKhL1nk # kDstrjNYxbc+/jLTswM9sbKvkjh+0p2ALPVOVpEhNSXDOW5kf1O6nA+tGSOEy/S6 # A4aN91/w0FK/jJSHvMAhdCVfGCi2zCcoOCWYOUo2z3yxkq4cI6epZuxhH2rhKEmd # X4jiJV3TIUs+UsS1Vz8kA/DRelsv1SPjcF0PUUZ3s/gA4bysAoJf28AVs70b1FVL # 5zmhD+kjSbwYuER8ReTBw3J64HLnJN+/RpnF78IcV9uDjexNSTCnq47f7Fufr/zd # sGbiwZeBe+3W7UvnSSmnEyimp31ngOaKYnhfsi+E11ecXL93KCjx7W3DKI8sj0A3 # T8HhhUSJxAlMxdSlQy90lfdu+HggWCwTXWCVmj5PM4TasIgX3p5O9JawvEagbJjS # 4NaIjAsCAwEAAaOCAe0wggHpMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBRI # bmTlUAXTgqoXNzcitW2oynUClTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTAL # BgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBRyLToCMZBD # uRQFTuHqp8cx0SOJNDBaBgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsLm1pY3Jv # c29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFf # MDNfMjIuY3JsMF4GCCsGAQUFBwEBBFIwUDBOBggrBgEFBQcwAoZCaHR0cDovL3d3 # dy5taWNyb3NvZnQuY29tL3BraS9jZXJ0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFf # MDNfMjIuY3J0MIGfBgNVHSAEgZcwgZQwgZEGCSsGAQQBgjcuAzCBgzA/BggrBgEF # BQcCARYzaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9kb2NzL3ByaW1h # cnljcHMuaHRtMEAGCCsGAQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAHAAbwBsAGkA # YwB5AF8AcwB0AGEAdABlAG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQBn # 8oalmOBUeRou09h0ZyKbC5YR4WOSmUKWfdJ5DJDBZV8uLD74w3LRbYP+vj/oCso7 # v0epo/Np22O/IjWll11lhJB9i0ZQVdgMknzSGksc8zxCi1LQsP1r4z4HLimb5j0b # pdS1HXeUOeLpZMlEPXh6I/MTfaaQdION9MsmAkYqwooQu6SpBQyb7Wj6aC6VoCo/ # KmtYSWMfCWluWpiW5IP0wI/zRive/DvQvTXvbiWu5a8n7dDd8w6vmSiXmE0OPQvy # CInWH8MyGOLwxS3OW560STkKxgrCxq2u5bLZ2xWIUUVYODJxJxp/sfQn+N4sOiBp # mLJZiWhub6e3dMNABQamASooPoI/E01mC8CzTfXhj38cbxV9Rad25UAqZaPDXVJi # hsMdYzaXht/a8/jyFqGaJ+HNpZfQ7l1jQeNbB5yHPgZ3BtEGsXUfFL5hYbXw3MYb # BL7fQccOKO7eZS/sl/ahXJbYANahRr1Z85elCUtIEJmAH9AAKcWxm6U/RXceNcbS # oqKfenoi+kiVH6v7RyOA9Z74v2u3S5fi63V4GuzqN5l5GEv/1rMjaHXmr/r8i+sL # gOppO6/8MO0ETI7f33VtY5E90Z1WTk+/gFcioXgRMiF670EKsT/7qMykXcGhiJtX # cVZOSEXAQsmbdlsKgEhr/Xmfwb1tbWrJUnMTDXpQzTGCFWQwghVgAgEBMIGVMH4x # CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt # b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01p # Y3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTECEzMAAAGIr1LWuZJt6PkAAAAA # AYgwDQYJYIZIAWUDBAIBBQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQw # HAYKKwYBBAGCNwIBCzEOMAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIPUm # EjvFWMCv2WvzfN5pO8ff7woBow35kVDy1XnzBJl1MEIGCisGAQQBgjcCAQwxNDAy # oBSAEgBNAGkAYwByAG8AcwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5j # b20wDQYJKoZIhvcNAQEBBQAEggEAapWybRieBsEBnC1QnYRFDYi+lGImoTH49Pcd # ERTBA/E0bYCmHsxU906JQXMk+379UWuJ1JWA2Hinhgf/2Wp6IFDLDYqrjvZkpnKP # 18FGNHQYfVNIXv0h2ncMUtrftjDfOklKRtKT/PR07d+QJJnpCH8kj5lZQs4y1YJM # lVV64h276evCCpBhs5tZKoh5SzzCOPIyKz5hJHdHf5A1nHAfTicb8vzXZ+OEI/UE # Jkvw9LWDHJsJE2HqgiLtyvYR+NBKknc2jy8cF6+bPLIy6LEauEiHfS8gaF5wwtX6 # BjcMuCAqtAa+LPJ2lltQYtJEpP+0Cn8EWbzvzNWAydOcwsplnKGCEu4wghLqBgor # BgEEAYI3AwMBMYIS2jCCEtYGCSqGSIb3DQEHAqCCEscwghLDAgEDMQ8wDQYJYIZI # AWUDBAIBBQAwggFVBgsqhkiG9w0BCRABBKCCAUQEggFAMIIBPAIBAQYKKwYBBAGE # WQoDATAxMA0GCWCGSAFlAwQCAQUABCBwBcPaUtWabEmNLf/fvTCf3FysVQJ3px40 # UB096lKm9QIGXtUFW83UGBMyMDIwMDYyMjIxNDM0MC4zMzJaMASAAgH0oIHUpIHR # MIHOMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH # UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSkwJwYDVQQL # EyBNaWNyb3NvZnQgT3BlcmF0aW9ucyBQdWVydG8gUmljbzEmMCQGA1UECxMdVGhh # bGVzIFRTUyBFU046RjdBNi1FMjUxLTE1MEExJTAjBgNVBAMTHE1pY3Jvc29mdCBU # aW1lLVN0YW1wIFNlcnZpY2Wggg5BMIIE9TCCA92gAwIBAgITMwAAASWL3otsciYx # 3QAAAAABJTANBgkqhkiG9w0BAQsFADB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMK # V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0 # IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0Eg # MjAxMDAeFw0xOTEyMTkwMTE0NThaFw0yMTAzMTcwMTE0NThaMIHOMQswCQYDVQQG # EwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwG # A1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSkwJwYDVQQLEyBNaWNyb3NvZnQg # T3BlcmF0aW9ucyBQdWVydG8gUmljbzEmMCQGA1UECxMdVGhhbGVzIFRTUyBFU046 # RjdBNi1FMjUxLTE1MEExJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNl # cnZpY2UwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDQex9jdmBb7OHJ # wSYmMUorZNwAcv8Vy36TlJuzcVx7G+lFqt2zjWOMlSOMkm1XoAuJ8VZ5ShBedADX # DGDKxHNZhLu3EW8x5ot/IOk6izLTlAFtvIXOgzXs/HaOM72XHKykMZHAdL/fpZtA # SM5PalmsXX4Ol8lXkm9jR55K56C7q9+hDU+2tjGHaE1ZWlablNUXBhaZgtCJCd60 # UyZvgI7/uNzcafj0/Vw2bait9nDAVd24yt/XCZnHY3yX7ZsHjIuHpsl+PpDXai1D # we9p0ryCZsl9SOMHextIHe9qlTbtWYJ8WtWLoH9dEMQxVLnmPPDOVmBj7LZhSji3 # 8N9Vpz/FAgMBAAGjggEbMIIBFzAdBgNVHQ4EFgQU86rK5Qcm+QE5NBXGCPIiCBdD # JPgwHwYDVR0jBBgwFoAU1WM6XIoxkPNDe3xGG8UzaFqFbVUwVgYDVR0fBE8wTTBL # oEmgR4ZFaHR0cDovL2NybC5taWNyb3NvZnQuY29tL3BraS9jcmwvcHJvZHVjdHMv # TWljVGltU3RhUENBXzIwMTAtMDctMDEuY3JsMFoGCCsGAQUFBwEBBE4wTDBKBggr # BgEFBQcwAoY+aHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraS9jZXJ0cy9NaWNU # aW1TdGFQQ0FfMjAxMC0wNy0wMS5jcnQwDAYDVR0TAQH/BAIwADATBgNVHSUEDDAK # BggrBgEFBQcDCDANBgkqhkiG9w0BAQsFAAOCAQEAkxxZPGEgIgAhsqZNTZk58V1v # QiJ5ja2xHl5TqGA6Hwj5SioLg3FSLiTmGV+BtFlpYUtkneB4jrZsuNpMtfbTMdG7 # p/xAyIVtwvXnTXqKlCD1T9Lcr94pVedzHGJzL1TYNQyZJBouCfzkgkzccOuFOfeW # PfnMTiI5UBW5OdmoyHPQWDSGHoboW1dTKqXeJtuVDTYbHTKs4zjfCBMFjmylRu52 # Zpiz+9MBeRj4iAeou0F/3xvIzepoIKgUWCZ9mmViWEkVwCtTGbV8eK73KeEE0tfM # U/YY2UmoGPc8YwburDEfelegLW+YHkfrcGAGlftCmqtOdOLeghLoG0Ubx/B7sTCC # BnEwggRZoAMCAQICCmEJgSoAAAAAAAIwDQYJKoZIhvcNAQELBQAwgYgxCzAJBgNV # BAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4w # HAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xMjAwBgNVBAMTKU1pY3Jvc29m # dCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAyMDEwMB4XDTEwMDcwMTIxMzY1 # NVoXDTI1MDcwMTIxNDY1NVowfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hp # bmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jw # b3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAw # ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCpHQ28dxGKOiDs/BOX9fp/ # aZRrdFQQ1aUKAIKF++18aEssX8XD5WHCdrc+Zitb8BVTJwQxH0EbGpUdzgkTjnxh # MFmxMEQP8WCIhFRDDNdNuDgIs0Ldk6zWczBXJoKjRQ3Q6vVHgc2/JGAyWGBG8lhH # hjKEHnRhZ5FfgVSxz5NMksHEpl3RYRNuKMYa+YaAu99h/EbBJx0kZxJyGiGKr0tk # iVBisV39dx898Fd1rL2KQk1AUdEPnAY+Z3/1ZsADlkR+79BL/W7lmsqxqPJ6Kgox # 8NpOBpG2iAg16HgcsOmZzTznL0S6p/TcZL2kAcEgCZN4zfy8wMlEXV4WnAEFTyJN # AgMBAAGjggHmMIIB4jAQBgkrBgEEAYI3FQEEAwIBADAdBgNVHQ4EFgQU1WM6XIox # kPNDe3xGG8UzaFqFbVUwGQYJKwYBBAGCNxQCBAweCgBTAHUAYgBDAEEwCwYDVR0P # BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAU1fZWy4/oolxiaNE9 # lJBb186aGMQwVgYDVR0fBE8wTTBLoEmgR4ZFaHR0cDovL2NybC5taWNyb3NvZnQu # Y29tL3BraS9jcmwvcHJvZHVjdHMvTWljUm9vQ2VyQXV0XzIwMTAtMDYtMjMuY3Js # MFoGCCsGAQUFBwEBBE4wTDBKBggrBgEFBQcwAoY+aHR0cDovL3d3dy5taWNyb3Nv # ZnQuY29tL3BraS9jZXJ0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcnQwgaAG # A1UdIAEB/wSBlTCBkjCBjwYJKwYBBAGCNy4DMIGBMD0GCCsGAQUFBwIBFjFodHRw # Oi8vd3d3Lm1pY3Jvc29mdC5jb20vUEtJL2RvY3MvQ1BTL2RlZmF1bHQuaHRtMEAG # CCsGAQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAFAAbwBsAGkAYwB5AF8AUwB0AGEA # dABlAG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQAH5ohRDeLG4Jg/gXED # PZ2joSFvs+umzPUxvs8F4qn++ldtGTCzwsVmyWrf9efweL3HqJ4l4/m87WtUVwgr # UYJEEvu5U4zM9GASinbMQEBBm9xcF/9c+V4XNZgkVkt070IQyK+/f8Z/8jd9Wj8c # 8pl5SpFSAK84Dxf1L3mBZdmptWvkx872ynoAb0swRCQiPM/tA6WWj1kpvLb9BOFw # nzJKJ/1Vry/+tuWOM7tiX5rbV0Dp8c6ZZpCM/2pif93FSguRJuI57BlKcWOdeyFt # w5yjojz6f32WapB4pm3S4Zz5Hfw42JT0xqUKloakvZ4argRCg7i1gJsiOCC1JeVk # 7Pf0v35jWSUPei45V3aicaoGig+JFrphpxHLmtgOR5qAxdDNp9DvfYPw4TtxCd9d # dJgiCGHasFAeb73x4QDf5zEHpJM692VHeOj4qEir995yfmFrb3epgcunCaw5u+zG # y9iCtHLNHfS4hQEegPsbiSpUObJb2sgNVZl6h3M7COaYLeqN4DMuEin1wC9UJyH3 # yKxO2ii4sanblrKnQqLJzxlBTeCG+SqaoxFmMNO7dDJL32N79ZmKLxvHIa9Zta7c # RDyXUHHXodLFVeNp3lfB0d4wwP3M5k37Db9dT+mdHhk4L7zPWAUu7w2gUDXa7wkn # HNWzfjUeCLraNtvTX4/edIhJEqGCAs8wggI4AgEBMIH8oYHUpIHRMIHOMQswCQYD # VQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEe # MBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSkwJwYDVQQLEyBNaWNyb3Nv # ZnQgT3BlcmF0aW9ucyBQdWVydG8gUmljbzEmMCQGA1UECxMdVGhhbGVzIFRTUyBF # U046RjdBNi1FMjUxLTE1MEExJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1w # IFNlcnZpY2WiIwoBATAHBgUrDgMCGgMVAEXTL+FQbc2G+3MXXvIRKVr2oXCnoIGD # MIGApH4wfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNV # BAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQG # A1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAwDQYJKoZIhvcNAQEF # BQACBQDimzFOMCIYDzIwMjAwNjIyMTczMTU4WhgPMjAyMDA2MjMxNzMxNThaMHQw # OgYKKwYBBAGEWQoEATEsMCowCgIFAOKbMU4CAQAwBwIBAAICJ4AwBwIBAAICEhUw # CgIFAOKcgs4CAQAwNgYKKwYBBAGEWQoEAjEoMCYwDAYKKwYBBAGEWQoDAqAKMAgC # AQACAwehIKEKMAgCAQACAwGGoDANBgkqhkiG9w0BAQUFAAOBgQBzAR3+iKfMoznC # ujJhI6OWQiL/3Gn4PLiDxSVdKD79gYuiaY0dfoXgra+c/q/4k5cgnE7KlmEsNSvS # JQxUdoV1dqsvHs/E8D/YvTQ/+i5IPNB7j7TV2FSv5ZH2ofUVI8dcLzY8v0hX2Ryf # ckehIM7fIrJzA+QXPwxKGEr8/ux43TGCAw0wggMJAgEBMIGTMHwxCzAJBgNVBAYT # AlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYD # VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBU # aW1lLVN0YW1wIFBDQSAyMDEwAhMzAAABJYvei2xyJjHdAAAAAAElMA0GCWCGSAFl # AwQCAQUAoIIBSjAaBgkqhkiG9w0BCQMxDQYLKoZIhvcNAQkQAQQwLwYJKoZIhvcN # AQkEMSIEIAOJBUV8Y04IYpKCqqV2uhy1P7rPL/c0I49ZqUBToPfsMIH6BgsqhkiG # 9w0BCRACLzGB6jCB5zCB5DCBvQQgXd/Gsi5vMF/6iX2CDh+VfmL5RvqaFkFwluiy # je9B9w4wgZgwgYCkfjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3Rv # bjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0 # aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMAITMwAA # ASWL3otsciYx3QAAAAABJTAiBCDgdvR+tpp+ZG60wSvwlQx7tW/67hC7ggOCMftd # b8+MmzANBgkqhkiG9w0BAQsFAASCAQClOp5KggmGXziKHGMuTO9HUEVSHIjFb89j # AVuop31cltN34byhThYJmRdUCBDSUCaLEjWBa6kebwS+DRJveHZYb0jelI7dnbmi # BE5f7MyhwR1b0jQrpk3iUFguP1wj49LyliUDQS48U+6x6eV8u427YFe7SsgpAwnF # yIDaDdrDm+smipq6yUilNHP2gtvrhjgMHt72PiPQUxaJoYxJJs6WtzhBkRA7SZq+ # lob4295ZC1711m0rvOJaocTLSW5J3lbdaTIRbsSrMMvSyRKc3PNhVcDkhsr+YbOk # WmY9QPdEl/vOdVttW0yzRYRhm3/Un/bvLZnWvkZm38AK9IZg7iy2 # SIG # End signature block |