param( [Parameter(Mandatory=$true)][string] $RootCertificate, [Parameter(Mandatory=$true)][string] $PublisherCertificate ) Set-StrictMode -Version 2.0 $ErrorActionPreference = 'Stop' $ExpectedRootSha256 = '07EF52D210A7AF3013231A1F678778E8F0C055B3CD1FFE5E076661DD63939836' $ExpectedPublisherSha256 = '50FCB6C85FD87040D158009DEC5E846E10A35C5803ED00922D0C259DE4889C03' function Get-CertificateSha256 { param([Security.Cryptography.X509Certificates.X509Certificate2] $Certificate) $Sha256 = [Security.Cryptography.SHA256]::Create() try { return ([BitConverter]::ToString($Sha256.ComputeHash($Certificate.RawData))).Replace('-', '') } finally { $Sha256.Dispose() } } function Resolve-PublicCertificatePath { param([string] $Path) if (-not (Test-Path -LiteralPath $Path -PathType Leaf)) { throw "Certificate does not exist: $Path" } $Resolved = Resolve-Path -LiteralPath $Path if ($Resolved.Provider.Name -ne 'FileSystem') { throw "Certificate is not a filesystem path: $Path" } return $Resolved.ProviderPath } function Add-CurrentUserCertificate { param( [string] $StoreName, [string] $CertificatePath, [Security.Cryptography.X509Certificates.X509Certificate2] $Certificate ) $Store = New-Object Security.Cryptography.X509Certificates.X509Store( $StoreName, [Security.Cryptography.X509Certificates.StoreLocation]::CurrentUser) try { $Store.Open([Security.Cryptography.X509Certificates.OpenFlags]::ReadWrite) $Store.Add($Certificate) return } catch { $StoreError = $_.Exception.Message } finally { $Store.Close() } $Certutil = Join-Path $env:SystemRoot 'System32\certutil.exe' $Output = & $Certutil -user -f -addstore $StoreName $CertificatePath 2>&1 if ($LASTEXITCODE -ne 0) { throw "Could not add the certificate to CurrentUser\$StoreName. X509Store: $StoreError CertUtil: $($Output -join ' ')" } } $RootCertificate = Resolve-PublicCertificatePath $RootCertificate $PublisherCertificate = Resolve-PublicCertificatePath $PublisherCertificate $Root = New-Object Security.Cryptography.X509Certificates.X509Certificate2($RootCertificate) $Publisher = New-Object Security.Cryptography.X509Certificates.X509Certificate2($PublisherCertificate) try { if ((Get-CertificateSha256 $Root) -ne $ExpectedRootSha256) { throw 'The Neomilkshake root certificate fingerprint is not trusted.' } if ((Get-CertificateSha256 $Publisher) -ne $ExpectedPublisherSha256) { throw 'The Neomilkshake publisher certificate fingerprint is not trusted.' } if ($Root.GetNameInfo( [Security.Cryptography.X509Certificates.X509NameType]::SimpleName, $false) -ne 'Neomilkshake Private Root CA') { throw "Unexpected root subject: $($Root.Subject)" } if ($Publisher.GetNameInfo( [Security.Cryptography.X509Certificates.X509NameType]::SimpleName, $false) -ne 'Neomilkshake') { throw "Unexpected publisher subject: $($Publisher.Subject)" } $RootConstraints = $Root.Extensions | Where-Object { $_.Oid.Value -eq '2.5.29.19' } $PublisherConstraints = $Publisher.Extensions | Where-Object { $_.Oid.Value -eq '2.5.29.19' } if (-not $RootConstraints.CertificateAuthority) { throw 'The Neomilkshake root certificate is not a certificate authority.' } if ($PublisherConstraints.CertificateAuthority) { throw 'The Neomilkshake publisher certificate must not be a certificate authority.' } $CodeSigning = $false foreach ($Extension in $Publisher.Extensions) { if ($Extension.Oid.Value -ne '2.5.29.37') { continue } foreach ($Usage in $Extension.EnhancedKeyUsages) { if ($Usage.Value -eq '1.3.6.1.5.5.7.3.3') { $CodeSigning = $true } } } if (-not $CodeSigning) { throw 'The Neomilkshake publisher certificate has no code-signing EKU.' } if ($Publisher.Issuer -ne $Root.Subject) { throw 'The Neomilkshake publisher was not issued by the supplied root.' } Add-CurrentUserCertificate Root $RootCertificate $Root Add-CurrentUserCertificate TrustedPublisher $PublisherCertificate $Publisher Write-Host ('Trusted Neomilkshake root: ' + $Root.Thumbprint) Write-Host ('Trusted Neomilkshake publisher: ' + $Publisher.Thumbprint) } finally { $Root.Dispose() $Publisher.Dispose() }