Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,15 @@ tests/fixtures/*.wsf -text
tests/fixtures/generated-unsigned/** -text
tests/fixtures/generated-signed/** -text
tests/fixtures/rdp/** -text -diff
# Signed Artifact Signing fixtures (scripts included): byte-exact.
tests/fixtures/artifact-signing/** -text

# Binary Authenticode fixtures.
tests/fixtures/**/*.appx binary
tests/fixtures/**/*.appxbundle binary
tests/fixtures/**/*.cab binary
tests/fixtures/**/*.cat binary
tests/fixtures/**/*.crl binary
tests/fixtures/**/*.crt binary
tests/fixtures/**/*.der binary
tests/fixtures/**/*.dll binary
Expand Down
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ On Linux/macOS, match **`ci-unix`** by running **`bash scripts/linux-portable-va
| **`docs/linux-signing-pipelines.md`** | Linux / portable verify, REST hash-sign, and hybrid embed workflows. |
| **`docs/migration-azuresigntool.md`** | Migration notes for AzureSignTool-style Key Vault signing. |
| **`docs/migration-artifact-signing.md`** | Migration notes for Azure Artifact Signing / Trusted Signing. |
| **`docs/artifact-signing-validation.md`** | Artifact Signing verification: publisher ID (certificate profile EKU) encoding, pinned Microsoft root CAs, validation algorithm. |
| **`docs/roadmap-authenticode-linux.md`** | Unix/portable subset and **`psign-tool portable`**. |
| **`docs/authenticode-trust-stack.md`** | Portable trust crate split (picky vs digest vs CMS). |
| **`docs/authroot-linux-verify.md`** | Anchor dir + AuthRoot CAB usage on Linux. |
Expand Down
3 changes: 3 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions PowerShell/Devolutions.Psign/Devolutions.Psign.psd1
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@
NestedModules = @('lib/net8.0/Devolutions.Psign.PowerShell.dll')
FormatsToProcess = @('Devolutions.Psign.Format.ps1xml')
CmdletsToExport = @(
'Get-PsignArtifactSigningPublisherId',
'Get-PsignSignature',
'New-PsignFileCatalog',
'Set-PsignSignature',
'Test-PsignArtifactSigning',
'Test-PsignFileCatalog',
'Test-PsignModule',
'Protect-PsignModule',
Expand Down
2 changes: 1 addition & 1 deletion PowerShell/Devolutions.Psign/Devolutions.Psign.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ Register-ArgumentCompleter -CommandName Set-PsignSignature, Protect-PsignModule
}
}

Register-ArgumentCompleter -CommandName Get-PsignSignature -ParameterName RevocationMode -ScriptBlock {
Register-ArgumentCompleter -CommandName Get-PsignSignature, Test-PsignArtifactSigning -ParameterName RevocationMode -ScriptBlock {
param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)
@('Off', 'BestEffort', 'Require') | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object {
[System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,20 @@ LONG DESCRIPTION
Sign files with a local certificate, PFX, portable cert store
thumbprint, Azure Key Vault, or Azure Trusted Signing.

AZURE ARTIFACT SIGNING CMDLETS

Get-PsignArtifactSigningPublisherId
Read the Azure Artifact Signing publisher ID (certificate profile
EKU 1.3.6.1.4.1.311.97.*) from a signed file or a certificate.
Decoding only; no trust decision.

Test-PsignArtifactSigning
Validate an Azure Artifact Signing signature: identity EKU, signer
and timestamp chains pinned to Microsoft Identity Verification Root
Certificate Authority 2020, validity at the trusted timestamp, and
optionally one of the expected -PublisherId values. Get-PsignSignature
accepts the same policy through -ArtifactSigning* parameters.

MODULE MANAGEMENT CMDLETS

Test-PsignModule
Expand Down Expand Up @@ -113,6 +127,8 @@ EXAMPLES

SEE ALSO
Get-PsignSignature
Get-PsignArtifactSigningPublisherId
Test-PsignArtifactSigning
Set-PsignSignature
Test-PsignModule
Protect-PsignModule
Expand Down
14 changes: 14 additions & 0 deletions PowerShell/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ Import-Module ./PowerShell/Devolutions.Psign/Devolutions.Psign.psd1
| Cmdlet | Description |
|--------|-------------|
| `Get-PsignSignature` | Inspect Authenticode signatures (PE, scripts, packages) |
| `Get-PsignArtifactSigningPublisherId` | Read the Azure Artifact Signing publisher ID from a signed file or certificate |
| `Test-PsignArtifactSigning` | Validate Azure Artifact Signing signatures with pinned Microsoft roots and publisher ID pinning |
| `Set-PsignSignature` | Sign files using local keys, PFX, cert store, Azure KV, or Trusted Signing |
| `Test-PsignModule` | Validate a module against AllSigned/RemoteSigned execution policy |
| `Protect-PsignModule` | Batch-sign all policy-checked files in a module |
Expand Down Expand Up @@ -107,6 +109,18 @@ Get-PsignSignature ./app.exe -AnchorDirectory ./trusted-roots/
Get-PsignSignature ./app.exe -AuthRootCab ./authroot.cab
```

Azure Artifact Signing validation pins its own Microsoft root instead of using AuthRoot or explicit anchors:

```powershell
# Publisher ID (certificate profile EKU suffix) of the signer; no trust decision:
Get-PsignArtifactSigningPublisherId ./tool.exe
Get-PsignArtifactSigningPublisherId -Certificate $signerCertificate

# Azure Artifact Signing validation pinned to Microsoft's root and to one certificate profile:
Test-PsignArtifactSigning ./tool.exe -PublisherId 285218949.549339218.252840042.443064405
Get-PsignSignature ./tool.exe -ArtifactSigningPublisherId 285218949.549339218.252840042.443064405
```

## Signing Sources

| Source | Parameters |
Expand Down
20 changes: 20 additions & 0 deletions PowerShell/tests/PortableSignature.PackageNative.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,23 @@ Describe 'Portable PowerShell package-native coverage' {
$inspected = Get-PsignSignature -LiteralPath $work
$inspected.Format | Should -Be 'NuGet'
$inspected.Status | Should -Be 'Valid'

# NuGet clients require the signature content embedded in the CMS (not detached).
Add-Type -AssemblyName System.IO.Compression.FileSystem
$zip = [System.IO.Compression.ZipFile]::OpenRead($work)
try {
$stream = $zip.GetEntry('.signature.p7s').Open()
$buffer = [System.IO.MemoryStream]::new()
$stream.CopyTo($buffer)
$stream.Dispose()
}
finally {
$zip.Dispose()
}
$cms = [System.Security.Cryptography.Pkcs.SignedCms]::new()
$cms.Decode($buffer.ToArray())
$cms.Detached | Should -BeFalse
[System.Text.Encoding]::UTF8.GetString($cms.ContentInfo.Content) | Should -BeLike 'Version:1*-Hash:*'
}

It 'signs and inspects symbol NuGet packages' {
Expand All @@ -209,6 +226,9 @@ Describe 'Portable PowerShell package-native coverage' {
$inspected = Get-PsignSignature -LiteralPath $work
$inspected.Format | Should -Be 'Vsix'
$inspected.Status | Should -Be 'Valid'
# Standard OPC XMLDSig, not the legacy psign layout.
$inspected.StatusMessage | Should -BeLike 'VSIX OPC XMLDSig signature*'
$inspected.SignerCertificate | Should -Not -BeNullOrEmpty
}

It 'signs and inspects ClickOnce XML manifests for .manifest, .application, and .vsto' -TestCases @(
Expand Down
180 changes: 180 additions & 0 deletions PowerShell/tests/PsignArtifactSigning.Cmdlets.Tests.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
Set-StrictMode -Version Latest

$env:PSIGN_NO_AUTO_TRUST = '1'

<#
.SYNOPSIS
Tests for Get-PsignArtifactSigningPublisherId, Test-PsignArtifactSigning, and the
-ArtifactSigning* parameters of Get-PsignSignature.

A self-signed certificate carrying forged Azure Artifact Signing EKUs proves that the
publisher ID is decoded from any certificate but only validates when the chain ends at the
pinned Microsoft root. Set PSIGN_ARTIFACT_SIGNING_SAMPLE to a real Artifact Signing PE (and
optionally PSIGN_ARTIFACT_SIGNING_SAMPLE_PUBLISHER_ID) to also run the positive cases.
#>

BeforeDiscovery {
$script:HasSample = -not [string]::IsNullOrWhiteSpace($env:PSIGN_ARTIFACT_SIGNING_SAMPLE)
}

BeforeAll {
$repoRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot)
$modulePath = Join-Path $repoRoot 'PowerShell\Devolutions.Psign\Devolutions.Psign.psd1'
Import-Module $modulePath -Force

$script:FixtureDir = Join-Path $repoRoot 'tests\fixtures\artifact-signing'
$script:RealPublisherId = '285218949.549339218.252840042.443064405'
$script:ForgedPublisherId = '111.222.333.444'

$script:TestDir = Join-Path ([System.IO.Path]::GetTempPath()) "psign-aas-$([System.Guid]::NewGuid().ToString('N').Substring(0,8))"
New-Item -ItemType Directory -Path $script:TestDir -Force | Out-Null

$rsa = [System.Security.Cryptography.RSA]::Create(2048)
$request = [System.Security.Cryptography.X509Certificates.CertificateRequest]::new(
'CN=Forged Artifact Signing',
$rsa,
[System.Security.Cryptography.HashAlgorithmName]::SHA256,
[System.Security.Cryptography.RSASignaturePadding]::Pkcs1)
$request.CertificateExtensions.Add(
[System.Security.Cryptography.X509Certificates.X509KeyUsageExtension]::new(
[System.Security.Cryptography.X509Certificates.X509KeyUsageFlags]::DigitalSignature, $true))
$ekuOids = [System.Security.Cryptography.OidCollection]::new()
foreach ($oid in @('1.3.6.1.4.1.311.97.1.0', "1.3.6.1.4.1.311.97.$script:ForgedPublisherId", '1.3.6.1.5.5.7.3.3')) {
$null = $ekuOids.Add([System.Security.Cryptography.Oid]::new($oid))
}
$request.CertificateExtensions.Add(
[System.Security.Cryptography.X509Certificates.X509EnhancedKeyUsageExtension]::new($ekuOids, $false))
$script:ForgedCert = $request.CreateSelfSigned(
[System.DateTimeOffset]::UtcNow.AddDays(-1),
[System.DateTimeOffset]::UtcNow.AddDays(30))

$certPath = Join-Path $script:TestDir 'forged.cer'
$keyPath = Join-Path $script:TestDir 'forged.key'
[System.IO.File]::WriteAllBytes($certPath, $script:ForgedCert.Export(
[System.Security.Cryptography.X509Certificates.X509ContentType]::Cert))
[System.IO.File]::WriteAllText($keyPath,
[System.Security.Cryptography.PemEncoding]::WriteString('PRIVATE KEY', $rsa.ExportPkcs8PrivateKey()))

$script:ForgedScript = Join-Path $script:TestDir 'forged.ps1'
Set-Content -LiteralPath $script:ForgedScript -Value "Write-Output 'forged'" -Encoding UTF8
Set-PsignSignature -FilePath $script:ForgedScript -CertificatePath $certPath -PrivateKeyPath $keyPath | Out-Null
}

AfterAll {
if ($script:TestDir -and (Test-Path $script:TestDir)) {
Remove-Item -LiteralPath $script:TestDir -Recurse -Force
}
}

Describe 'Get-PsignArtifactSigningPublisherId' {
It 'decodes the publisher ID from a certificate' {
$cert = [System.Security.Cryptography.X509Certificates.X509Certificate2]::new(
(Join-Path $script:FixtureDir 'devolutions-signer.pem'))
$id = Get-PsignArtifactSigningPublisherId -Certificate $cert
$id.PublisherId | Should -Be $script:RealPublisherId
$id.Kind | Should -Be 'public_trust'
$id.HasPublicTrustMarker | Should -BeTrue
$id.IdentityEku | Should -Be "1.3.6.1.4.1.311.97.$script:RealPublisherId"
}

It 'decodes the publisher ID from a signed file without validating it' {
$id = Get-PsignArtifactSigningPublisherId -LiteralPath $script:ForgedScript
$id.PublisherId | Should -Be $script:ForgedPublisherId
$id.Path | Should -Be $script:ForgedScript
}

It 'writes an error for a certificate without an Artifact Signing identity' {
$plain = [System.Security.Cryptography.X509Certificates.X509Certificate2]::new(
(Join-Path $repoRoot 'tests\fixtures\devolutions-authenticode\authenticode-test-ca.crt'))
$result = Get-PsignArtifactSigningPublisherId -Certificate $plain -ErrorAction SilentlyContinue -ErrorVariable err
$result | Should -BeNullOrEmpty
$err[0].FullyQualifiedErrorId | Should -BeLike 'NotArtifactSigningCertificate*'
}
}

Describe 'Test-PsignArtifactSigning' {
It 'rejects forged Artifact Signing EKUs that do not chain to the pinned Microsoft root' {
Test-PsignArtifactSigning -LiteralPath $script:ForgedScript | Should -BeFalse
Test-PsignArtifactSigning -LiteralPath $script:ForgedScript -PublisherId $script:ForgedPublisherId | Should -BeFalse
}

It 'returns the signature object with -Detailed' {
$detail = Test-PsignArtifactSigning -LiteralPath $script:ForgedScript -Detailed
$detail.Status | Should -Be 'NotTrusted'
$detail.ArtifactSigningVerification | Should -BeNullOrEmpty
$detail.ArtifactSigningPublisherId | Should -Be $script:ForgedPublisherId
}

It 'validates a real Artifact Signing sample with publisher pinning' -Skip:(-not $script:HasSample) {
$sample = $env:PSIGN_ARTIFACT_SIGNING_SAMPLE
$expected = if ($env:PSIGN_ARTIFACT_SIGNING_SAMPLE_PUBLISHER_ID) { $env:PSIGN_ARTIFACT_SIGNING_SAMPLE_PUBLISHER_ID } else { $script:RealPublisherId }
Test-PsignArtifactSigning -LiteralPath $sample | Should -BeTrue
Test-PsignArtifactSigning -LiteralPath $sample -PublisherId $expected | Should -BeTrue
Test-PsignArtifactSigning -LiteralPath $sample -PublisherId '1.2.3.4' | Should -BeFalse

$detail = Test-PsignArtifactSigning -LiteralPath $sample -PublisherId $expected -Detailed
$detail.ArtifactSigningVerification.RootSha1 | Should -Be 'f40042e2e5f7e8ef8189fed15519aece42c3bfa2'
$detail.ArtifactSigningVerification.Timestamped | Should -BeTrue
}
}

Describe 'Get-PsignSignature Artifact Signing parameters' {
It 'exposes the decoded publisher ID without validation' {
(Get-PsignSignature -LiteralPath $script:ForgedScript -SkipTrust).ArtifactSigningPublisherId |
Should -Be $script:ForgedPublisherId
}

It 'reports NotTrusted for forged EKUs under -ArtifactSigningPublisherId' {
$signature = Get-PsignSignature -LiteralPath $script:ForgedScript -ArtifactSigningPublisherId $script:ForgedPublisherId
$signature.Status | Should -Be 'NotTrusted'
$signature.TrustStatus | Should -Be 'NotTrusted'
}

It 'rejects generic trust anchors combined with Artifact Signing' {
$null = Get-PsignSignature -LiteralPath $script:ForgedScript -ArtifactSigning `
-TrustedCertificate $script:ForgedCert -ErrorAction SilentlyContinue -ErrorVariable err
$err[0].Exception.Message | Should -BeLike '*cannot be combined with Artifact Signing*'
}
}

Describe 'Artifact Signing across script, MSIX, NuGet, and VSIX formats' {
BeforeAll {
$script:PrivateTrustDir = Join-Path $script:FixtureDir 'private-trust'
$script:PrivateTrustRoot = Join-Path $script:PrivateTrustDir 'root.cer'
$script:PrivateTrustId = '11.22.33.44.55.66.77.88'
}

It 'decodes the publisher ID from <File>' -TestCases @(
@{ File = 'sample.js' }, @{ File = 'sample.msix' }, @{ File = 'sample.nupkg' }, @{ File = 'sample.vsix' }
) {
$id = Get-PsignArtifactSigningPublisherId -LiteralPath (Join-Path $script:PrivateTrustDir $File)
$id.PublisherId | Should -Be $script:PrivateTrustId
$id.Kind | Should -Be 'private_trust'
}

It 'validates <File> against the private trust root with publisher pinning' -TestCases @(
@{ File = 'sample.js' }, @{ File = 'sample.msix' }, @{ File = 'sample.nupkg' }, @{ File = 'sample.vsix' }
) {
$path = Join-Path $script:PrivateTrustDir $File
$common = @{ LiteralPath = $path; PrivateTrustRoot = $script:PrivateTrustRoot; AllowMissingTimestamp = $true }
Test-PsignArtifactSigning @common -PublisherId $script:PrivateTrustId | Should -BeTrue
Test-PsignArtifactSigning @common -PublisherId '1.2.3.4' | Should -BeFalse
Test-PsignArtifactSigning -LiteralPath $path -AllowMissingTimestamp | Should -BeFalse

$detail = Test-PsignArtifactSigning @common -PublisherId $script:PrivateTrustId -Detailed
$detail.ArtifactSigningVerification.Identity.Kind | Should -Be 'private_trust'
}

It 'trust-verifies test-CA signed <File> with explicit anchors' -TestCases @(
@{ File = 'generated-signed\scripts\wsh\js\utf8-crlf.js' },
@{ File = 'generated-signed\msix\sample.msix' },
@{ File = 'package-signing\signed\sample.signed.nupkg' },
@{ File = 'package-signing\signed\sample.signed.vsix' }
) {
$signature = Get-PsignSignature -LiteralPath (Join-Path $repoRoot "tests\fixtures\$File") `
-TrustedCertificatePath (Join-Path $repoRoot 'tests\fixtures\devolutions-authenticode\authenticode-test-ca.crt') `
-AsOf ([datetime]'2026-06-01')
$signature.Status | Should -Be 'Valid'
$signature.TrustStatus | Should -Be 'Valid'
}
}
3 changes: 3 additions & 0 deletions crates/psign-authenticode-trust/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ sha2 = "0.10"
rsa = { version = "0.9.10", features = ["sha2"] }
authenticode = { version = "0.5.0", features = ["std", "object"] }
psign-sip-digest = { path = "../psign-sip-digest" }
psign-opc-sign = { path = "../psign-opc-sign" }
quick-xml = "0.38"
zip = { version = "0.6.6", default-features = false, features = ["deflate"] }
picky = { version = "7.0.0-rc.23", features = ["pkcs7", "time_conversion"] }
picky-asn1-x509 = "0.15.4"
time = { version = "0.3", features = ["formatting", "parsing"] }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
-----BEGIN CERTIFICATE-----
MIIFzDCCA7SgAwIBAgIQVJjS0dRbGZVIE3nIEcCHmTANBgkqhkiG9w0BAQwFADB3
MQswCQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMUgw
RgYDVQQDEz9NaWNyb3NvZnQgSWRlbnRpdHkgVmVyaWZpY2F0aW9uIFJvb3QgQ2Vy
dGlmaWNhdGUgQXV0aG9yaXR5IDIwMjAwHhcNMjAwNDE2MTgzNjE2WhcNNDUwNDE2
MTg0NDQwWjB3MQswCQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBv
cmF0aW9uMUgwRgYDVQQDEz9NaWNyb3NvZnQgSWRlbnRpdHkgVmVyaWZpY2F0aW9u
IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMjAwggIiMA0GCSqGSIb3DQEB
AQUAA4ICDwAwggIKAoICAQCzkSoHgwZn/Z6d4MfAt6TmQgR/D6bbX/vVWtdFoPt3
C/CA86ZtWk15U9ighoRXRSDHolT7x6K/isduNfOiFcQvTuNKhZZJDf++mdgU9rwn
B+5Cmyv1C5IG5P1pE2WokXLymITrgz0O5NdxEkghyw3t9kdJt5v5yccXtoRP/7is
mtdzZ0mF44a9N0DQJYbU3rXCbWJq1al4vC1vSfnlbBQU/RTH02UWN97LbrxeKY39
YpsVLNYF5rmJMjOjYsfX1lJnCMQu9FYrnguHzOyntKaq6wXNGVelOgsEJxyRZ54t
Yi0vHr7awCDLBBnKM/uJvpjicqByNb554ZyDb+RtF2+Q8z0AhnU4jtDgSZq729P4
MMrVV4hoTXLTv21/cdj9vQ2ukmRIt1tveSa1zZuVIYTR7w8yPXtXjPNFB0x84F4Y
DjV2i22eyzZ0qwX44HNdMlaUZ5clCsY1PZSX58FEi4D9wfj0dBnlMPYG+yFXPgYc
i2sVhidJe4KTylnodUfoPzj0x1N5oLa04lxR771fOMET5ngMlVouxUBZKMwPJMDs
ugl3I5k4prYc2se6ILbXN9h/N68I4ztx225zG32ZcrDkhjNZdLUWAHtQbcaGE9r9
xDmCPSQAmmDaupTABVEsNKxQmROHu7MFgLJNMAJcuCaDXbRjc++uI5VPYCi+N9Vb
pQIDAQABo1QwUjAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV
HQ4EFgQUyH7SaoUqG8oZmAQHJ89QEE9oqKIwEAYJKwYBBAGCNxUBBAMCAQAwDQYJ
KoZIhvcNAQEMBQADggIBAK9q3eYZ5y2UQxlOy+lQlWSlA5ECi+I2gDsVolLCFhm2
alpddEMw9Jv/YHQJsSEekBZtxSSPXGaIY/RPzH3yEkxAEIsBn9qpyK7ylRvPnQXr
ST50oGhb5VYsZRyCflPaVtlGF3mSRcQQNghSKRfLL6byftRpJIoej7BzDcwcSquy
qu2nkWMBZCKoMrh+MiizZ3MtkbTcMQEL90cKpvHXSu1WYMQsCKN7QLC8dCdSh9a+
iN03ioluZ4gd9cldoP62qzqA1xqXPBc2IkEerE3Vg+Y8OL1PMOlUqdO2BMMydmG7
sBjFKxizwIDVt5WwXlFNIvzsWKro2JS0pS7tkt7nGHwhV91VY/e/bc0f0qZ3KHDH
4ls6WwjSW07IAJaz4YM2r4YKZVx09ursemp0oPBL7u+Uo6xQ8oft1zowg8n7fVe+
5eP4QcrlZK6zo+xY7IWazO+56vNWGLlcc5qvxXcXg1nbNxoYclSlQdK2I3WjQ5rl
d3yWebdBjb/s3ICgn9F3dVhfNRPgJRpnC33OJfoHCuRhIdjUHOUHxjaZ9JbQxhX+
Ts3Xroud2xb9BMaSvdSI5qmjqrv3ZDg7X8wM0DW+dBkDpsWqTKJhNoI+HfMrvJdd
20t4Oy31O+9gI+j17AsjNpWvmGa/U9N7uGlKKpZmacSUxvRfbqyYeIiABlyisu2i
-----END CERTIFICATE-----
Loading
Loading