Add MrTrust GUI and Gitea release build
Some checks failed
Build MrTrust / build-windows (push) Has been cancelled

This commit is contained in:
MrSphay
2026-05-15 23:47:10 +02:00
parent 7d4e9759e6
commit b58b6358f4
20 changed files with 1179 additions and 403 deletions

View File

@@ -0,0 +1,50 @@
[CmdletBinding()]
param(
[string]$OutputPath = ".\dist\MrTrust.exe"
)
$ErrorActionPreference = "Stop"
function Resolve-FullPath {
param([Parameter(Mandatory)][string]$Path)
$executionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($Path)
}
$root = Split-Path -Parent (Split-Path -Parent $MyInvocation.MyCommand.Path)
$sourcePath = Join-Path $root "src\MrTrustLauncher.cs"
$resolvedOutputPath = Resolve-FullPath $OutputPath
$outputDirectory = Split-Path -Parent $resolvedOutputPath
if (-not (Test-Path -LiteralPath $sourcePath)) {
throw "Launcher source not found: $sourcePath"
}
New-Item -ItemType Directory -Force -Path $outputDirectory | Out-Null
$compilerCandidates = @(
"$env:WINDIR\Microsoft.NET\Framework64\v4.0.30319\csc.exe",
"$env:WINDIR\Microsoft.NET\Framework\v4.0.30319\csc.exe"
)
$compiler = $compilerCandidates | Where-Object { Test-Path -LiteralPath $_ } | Select-Object -First 1
if (-not $compiler) {
throw "csc.exe was not found. Run this build on a Windows Gitea runner with .NET Framework installed."
}
& $compiler `
/nologo `
/target:winexe `
/optimize+ `
/platform:anycpu `
/out:$resolvedOutputPath `
/reference:System.Windows.Forms.dll `
/reference:System.Drawing.dll `
$sourcePath
if ($LASTEXITCODE -ne 0) {
throw "csc.exe failed with exit code $LASTEXITCODE."
}
Write-Host "Created EXE:"
Write-Host " $resolvedOutputPath"

View File

@@ -0,0 +1,90 @@
[CmdletBinding(SupportsShouldProcess)]
param(
[string]$CertificatePath = ".\assets\certificates\MrSphay-LocalTrust-Root.cer",
[string]$PublisherCertificatePath = ".\assets\certificates\MrSphay-CodeSigning.cer",
[ValidateSet("CurrentUser", "LocalMachine")]
[string]$Scope = "CurrentUser"
)
$ErrorActionPreference = "Stop"
function Resolve-FullPath {
param([Parameter(Mandatory)][string]$Path)
$executionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($Path)
}
function Test-IsAdministrator {
$identity = [Security.Principal.WindowsIdentity]::GetCurrent()
$principal = [Security.Principal.WindowsPrincipal]::new($identity)
$principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
}
$resolvedCertificatePath = Resolve-FullPath $CertificatePath
if (-not (Test-Path -LiteralPath $resolvedCertificatePath)) {
throw "Certificate file not found: $resolvedCertificatePath. Run scripts\New-MrTrustCertificate.ps1 first or provide -CertificatePath."
}
if ($Scope -eq "LocalMachine" -and -not (Test-IsAdministrator)) {
throw "LocalMachine installation requires an elevated PowerShell session. Use -Scope CurrentUser or run as Administrator."
}
$rootCertificate = [System.Security.Cryptography.X509Certificates.X509Certificate2]::new($resolvedCertificatePath)
if (-not $rootCertificate.Subject.StartsWith("CN=MrSphay", [System.StringComparison]::OrdinalIgnoreCase)) {
throw "Refusing to install an unexpected root certificate subject: $($rootCertificate.Subject)"
}
$resolvedPublisherCertificatePath = Resolve-FullPath $PublisherCertificatePath
$publisherCertificate = $null
if (Test-Path -LiteralPath $resolvedPublisherCertificatePath) {
$publisherCertificate = [System.Security.Cryptography.X509Certificates.X509Certificate2]::new($resolvedPublisherCertificatePath)
if (-not $publisherCertificate.Subject.StartsWith("CN=MrSphay", [System.StringComparison]::OrdinalIgnoreCase)) {
throw "Refusing to install an unexpected publisher certificate subject: $($publisherCertificate.Subject)"
}
}
$rootStore = "Cert:\$Scope\Root"
$publisherStore = "Cert:\$Scope\TrustedPublisher"
Write-Host "MrTrust will install this certificate as trusted for scope '$Scope':"
Write-Host " Root subject: $($rootCertificate.Subject)"
Write-Host " Root issuer: $($rootCertificate.Issuer)"
Write-Host " Root thumbprint: $($rootCertificate.Thumbprint)"
Write-Host " Root expires: $($rootCertificate.NotAfter.ToString('yyyy-MM-dd HH:mm:ss'))"
if ($publisherCertificate) {
Write-Host " Publisher subject: $($publisherCertificate.Subject)"
Write-Host " Publisher thumbprint: $($publisherCertificate.Thumbprint)"
}
else {
Write-Warning "Publisher certificate not found at $resolvedPublisherCertificatePath. Only the root certificate will be installed."
}
Write-Host ""
Write-Warning "Only continue if you trust MrSphay software signed with this certificate chain."
$answer = Read-Host "Type INSTALL to continue"
if ($answer -cne "INSTALL") {
Write-Host "Installation cancelled."
exit 1
}
$existingRoot = Get-ChildItem -Path $rootStore | Where-Object Thumbprint -eq $rootCertificate.Thumbprint
if (-not $existingRoot) {
if ($PSCmdlet.ShouldProcess($rootStore, "Install MrTrust root certificate")) {
Import-Certificate -FilePath $resolvedCertificatePath -CertStoreLocation $rootStore | Out-Null
}
}
if ($publisherCertificate) {
$existingPublisher = Get-ChildItem -Path $publisherStore | Where-Object Thumbprint -eq $publisherCertificate.Thumbprint
if (-not $existingPublisher) {
if ($PSCmdlet.ShouldProcess($publisherStore, "Install MrTrust trusted publisher certificate")) {
Import-Certificate -FilePath $resolvedPublisherCertificatePath -CertStoreLocation $publisherStore | Out-Null
}
}
}
Write-Host "MrTrust certificate installed."
Write-Host "Installed stores:"
Write-Host " $rootStore"
Write-Host " $publisherStore"

View File

@@ -0,0 +1,90 @@
[CmdletBinding()]
param(
[string]$PublisherName = "MrSphay",
[string]$RootSubject = "CN=MrSphay Local Trust Root",
[string]$SigningSubject = "CN=MrSphay Code Signing",
[string]$PublicCertificateDirectory = ".\assets\certificates",
[string]$PrivateDirectory = ".\private",
[int]$ValidYears = 5,
[switch]$SkipPfxExport
)
$ErrorActionPreference = "Stop"
function Resolve-FullPath {
param([Parameter(Mandatory)][string]$Path)
$executionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($Path)
}
if ($ValidYears -lt 1 -or $ValidYears -gt 20) {
throw "ValidYears must be between 1 and 20."
}
$publicDirectory = Resolve-FullPath $PublicCertificateDirectory
$privateDirectory = Resolve-FullPath $PrivateDirectory
New-Item -ItemType Directory -Force -Path $publicDirectory | Out-Null
if (-not $SkipPfxExport) {
New-Item -ItemType Directory -Force -Path $privateDirectory | Out-Null
}
$rootCertPath = Join-Path $publicDirectory "$PublisherName-LocalTrust-Root.cer"
$signingCertPath = Join-Path $publicDirectory "$PublisherName-CodeSigning.cer"
$pfxPath = Join-Path $privateDirectory "$PublisherName-CodeSigning.pfx"
Write-Host "Creating local trust root certificate: $RootSubject"
$rootCertificate = New-SelfSignedCertificate `
-Type Custom `
-Subject $RootSubject `
-KeyAlgorithm RSA `
-KeyLength 4096 `
-HashAlgorithm SHA256 `
-KeyExportPolicy Exportable `
-KeyUsage CertSign, CRLSign, DigitalSignature `
-TextExtension @("2.5.29.19={critical}{text}ca=1&pathlength=1") `
-CertStoreLocation "Cert:\CurrentUser\My" `
-NotAfter (Get-Date).AddYears($ValidYears)
Write-Host "Creating code-signing certificate: $SigningSubject"
$signingCertificate = New-SelfSignedCertificate `
-Type CodeSigningCert `
-Subject $SigningSubject `
-Signer $rootCertificate `
-KeyAlgorithm RSA `
-KeyLength 4096 `
-HashAlgorithm SHA256 `
-KeyExportPolicy Exportable `
-CertStoreLocation "Cert:\CurrentUser\My" `
-NotAfter (Get-Date).AddYears($ValidYears)
Export-Certificate -Cert $rootCertificate -FilePath $rootCertPath -Force | Out-Null
Export-Certificate -Cert $signingCertificate -FilePath $signingCertPath -Force | Out-Null
if (-not $SkipPfxExport) {
$password = Read-Host "Enter a strong password for the private code-signing PFX" -AsSecureString
Export-PfxCertificate -Cert $signingCertificate -FilePath $pfxPath -Password $password -Force | Out-Null
}
Write-Host ""
Write-Host "Created public root certificate:"
Write-Host " $rootCertPath"
Write-Host "Root thumbprint:"
Write-Host " $($rootCertificate.Thumbprint)"
Write-Host ""
Write-Host "Created public signing certificate:"
Write-Host " $signingCertPath"
Write-Host "Signing thumbprint:"
Write-Host " $($signingCertificate.Thumbprint)"
Write-Host ""
if ($SkipPfxExport) {
Write-Host "Skipped private PFX export."
Write-Host "Use this thumbprint for signing from the Windows certificate store:"
Write-Host " $($signingCertificate.Thumbprint)"
}
else {
Write-Host "Created private PFX:"
Write-Host " $pfxPath"
Write-Host ""
Write-Warning "Do not commit, publish, or share the private .pfx file or its password."
}

View File

@@ -0,0 +1,70 @@
[CmdletBinding()]
param(
[string]$Version = "0.1.0",
[string]$OutputDirectory = ".\dist",
[string]$SigningThumbprint,
[switch]$NoTimestamp,
[switch]$AllowUntrustedRoot
)
$ErrorActionPreference = "Stop"
function Resolve-FullPath {
param([Parameter(Mandatory)][string]$Path)
$executionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($Path)
}
$root = Split-Path -Parent (Split-Path -Parent $MyInvocation.MyCommand.Path)
$output = Resolve-FullPath $OutputDirectory
$packageRoot = Join-Path $output "MrTrust-$Version"
$zipPath = Join-Path $output "MrTrust-$Version.zip"
$exePath = Join-Path $output "MrTrust.exe"
if (Test-Path -LiteralPath $packageRoot) {
Remove-Item -LiteralPath $packageRoot -Recurse -Force
}
New-Item -ItemType Directory -Force -Path $packageRoot | Out-Null
New-Item -ItemType Directory -Force -Path (Join-Path $packageRoot "scripts") | Out-Null
New-Item -ItemType Directory -Force -Path (Join-Path $packageRoot "assets\certificates") | Out-Null
New-Item -ItemType Directory -Force -Path (Join-Path $packageRoot "docs") | Out-Null
& (Join-Path $root "scripts\Build-MrTrustExe.ps1") -OutputPath $exePath
if ($SigningThumbprint) {
$signArguments = @{
Path = $exePath
CertificateThumbprint = $SigningThumbprint
}
if ($NoTimestamp) {
$signArguments.NoTimestamp = $true
}
if ($AllowUntrustedRoot) {
$signArguments.AllowUntrustedRoot = $true
}
& (Join-Path $root "scripts\Sign-MrTrustProject.ps1") @signArguments
}
Copy-Item -LiteralPath $exePath -Destination $packageRoot
Copy-Item -LiteralPath (Join-Path $root "MrTrust.ps1") -Destination $packageRoot
Copy-Item -LiteralPath (Join-Path $root "README.md") -Destination $packageRoot
Copy-Item -LiteralPath (Join-Path $root "scripts\Install-MrTrust.ps1") -Destination (Join-Path $packageRoot "scripts")
Copy-Item -LiteralPath (Join-Path $root "scripts\Uninstall-MrTrust.ps1") -Destination (Join-Path $packageRoot "scripts")
Copy-Item -LiteralPath (Join-Path $root "scripts\Start-MrTrustGui.ps1") -Destination (Join-Path $packageRoot "scripts")
Copy-Item -LiteralPath (Join-Path $root "assets\certificates\MrSphay-LocalTrust-Root.cer") -Destination (Join-Path $packageRoot "assets\certificates")
Copy-Item -LiteralPath (Join-Path $root "assets\certificates\MrSphay-CodeSigning.cer") -Destination (Join-Path $packageRoot "assets\certificates")
Copy-Item -LiteralPath (Join-Path $root "assets\certificates\thumbprints.txt") -Destination (Join-Path $packageRoot "assets\certificates")
Copy-Item -LiteralPath (Join-Path $root "docs\security-model.md") -Destination (Join-Path $packageRoot "docs")
if (Test-Path -LiteralPath $zipPath) {
Remove-Item -LiteralPath $zipPath -Force
}
Compress-Archive -Path (Join-Path $packageRoot "*") -DestinationPath $zipPath -Force
Write-Host "Created release package:"
Write-Host " $zipPath"

View File

@@ -0,0 +1,104 @@
[CmdletBinding()]
param(
[Parameter(Mandatory)]
[string[]]$Path,
[string]$PfxPath,
[string]$CertificateThumbprint,
[string]$TimestampServer = "http://timestamp.digicert.com",
[switch]$NoTimestamp,
[switch]$AllowUntrustedRoot
)
$ErrorActionPreference = "Stop"
function Resolve-FullPath {
param([Parameter(Mandatory)][string]$Path)
$executionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($Path)
}
function Get-CodeSigningCertificateFromStore {
param([Parameter(Mandatory)][string]$Thumbprint)
$normalizedThumbprint = $Thumbprint -replace "\s", ""
$certificate = Get-ChildItem "Cert:\CurrentUser\My", "Cert:\LocalMachine\My" |
Where-Object { $_.Thumbprint -eq $normalizedThumbprint } |
Select-Object -First 1
if (-not $certificate) {
throw "No code-signing certificate found with thumbprint $Thumbprint."
}
$certificate
}
if (-not $PfxPath -and -not $CertificateThumbprint) {
throw "Provide either -PfxPath or -CertificateThumbprint."
}
if ($PfxPath -and $CertificateThumbprint) {
throw "Use either -PfxPath or -CertificateThumbprint, not both."
}
if ($PfxPath) {
$resolvedPfxPath = Resolve-FullPath $PfxPath
if (-not (Test-Path -LiteralPath $resolvedPfxPath)) {
throw "PFX file not found: $resolvedPfxPath"
}
$password = Read-Host "Enter PFX password" -AsSecureString
$storageFlags = [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable -bor
[System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::PersistKeySet
$certificate = [System.Security.Cryptography.X509Certificates.X509Certificate2]::new($resolvedPfxPath, $password, $storageFlags)
}
else {
$certificate = Get-CodeSigningCertificateFromStore -Thumbprint $CertificateThumbprint
}
if (-not $certificate.HasPrivateKey) {
throw "The selected certificate does not include a private key and cannot sign files."
}
$targets = foreach ($item in $Path) {
$resolvedPath = Resolve-FullPath $item
if (Test-Path -LiteralPath $resolvedPath -PathType Container) {
Get-ChildItem -LiteralPath $resolvedPath -Recurse -File |
Where-Object { $_.Extension -in ".exe", ".msi", ".dll", ".ps1", ".psm1", ".psd1", ".cat" }
}
elseif (Test-Path -LiteralPath $resolvedPath -PathType Leaf) {
Get-Item -LiteralPath $resolvedPath
}
else {
throw "Path not found: $resolvedPath"
}
}
if (-not $targets) {
throw "No files found to sign."
}
foreach ($target in $targets) {
Write-Host "Signing $($target.FullName)"
$signatureArguments = @{
FilePath = $target.FullName
Certificate = $certificate
HashAlgorithm = "SHA256"
}
if (-not $NoTimestamp -and $TimestampServer) {
$signatureArguments.TimestampServer = $TimestampServer
}
$signature = Set-AuthenticodeSignature @signatureArguments
if ($signature.Status -eq "UnknownError" -and $AllowUntrustedRoot -and $signature.SignerCertificate) {
Write-Warning "Signed $($target.FullName), but the local machine does not trust the signing root yet."
continue
}
if ($signature.Status -ne "Valid") {
throw "Signing failed for $($target.FullName): $($signature.StatusMessage)"
}
}
Write-Host "Signed $($targets.Count) file(s)."

View File

@@ -0,0 +1,324 @@
[CmdletBinding()]
param()
$ErrorActionPreference = "Stop"
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
$script:RootPath = Split-Path -Parent (Split-Path -Parent $MyInvocation.MyCommand.Path)
$script:RootCertificatePath = Join-Path $script:RootPath "assets\certificates\MrSphay-LocalTrust-Root.cer"
$script:PublisherCertificatePath = Join-Path $script:RootPath "assets\certificates\MrSphay-CodeSigning.cer"
function Test-IsAdministrator {
$identity = [Security.Principal.WindowsIdentity]::GetCurrent()
$principal = [Security.Principal.WindowsPrincipal]::new($identity)
$principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
}
function Get-MrTrustCertificate {
param([Parameter(Mandatory)][string]$Path)
if (-not (Test-Path -LiteralPath $Path)) {
throw "Certificate file not found: $Path"
}
[System.Security.Cryptography.X509Certificates.X509Certificate2]::new($Path)
}
function Get-TrustScope {
if ($script:AllUsersCheckBox.Checked) {
"LocalMachine"
}
else {
"CurrentUser"
}
}
function Get-StorePath {
param(
[Parameter(Mandatory)][string]$Scope,
[Parameter(Mandatory)][string]$Store
)
"Cert:\$Scope\$Store"
}
function Test-CertificateInstalled {
param(
[Parameter(Mandatory)]$Certificate,
[Parameter(Mandatory)][string]$Scope,
[Parameter(Mandatory)][string]$Store
)
$storePath = Get-StorePath -Scope $Scope -Store $Store
@(Get-ChildItem -Path $storePath | Where-Object Thumbprint -eq $Certificate.Thumbprint).Count -gt 0
}
function Set-StatusText {
param([Parameter(Mandatory)][string]$Text)
$script:StatusLabel.Text = $Text
}
function Refresh-MrTrustStatus {
try {
$rootCertificate = Get-MrTrustCertificate -Path $script:RootCertificatePath
$publisherCertificate = Get-MrTrustCertificate -Path $script:PublisherCertificatePath
$scope = Get-TrustScope
$rootInstalled = Test-CertificateInstalled -Certificate $rootCertificate -Scope $scope -Store "Root"
$publisherInstalled = Test-CertificateInstalled -Certificate $publisherCertificate -Scope $scope -Store "TrustedPublisher"
$script:RootThumbprintLabel.Text = $rootCertificate.Thumbprint
$script:PublisherThumbprintLabel.Text = $publisherCertificate.Thumbprint
$script:ExpiryLabel.Text = $rootCertificate.NotAfter.ToString("yyyy-MM-dd")
if ($rootInstalled -and $publisherInstalled) {
Set-StatusText "Trusted for $scope"
$script:StatusPill.BackColor = [Drawing.Color]::FromArgb(28, 185, 111)
}
else {
Set-StatusText "Not installed for $scope"
$script:StatusPill.BackColor = [Drawing.Color]::FromArgb(242, 153, 74)
}
}
catch {
Set-StatusText $_.Exception.Message
$script:StatusPill.BackColor = [Drawing.Color]::FromArgb(235, 87, 87)
}
}
function Install-MrTrustCertificates {
$scope = Get-TrustScope
if ($scope -eq "LocalMachine" -and -not (Test-IsAdministrator)) {
[Windows.Forms.MessageBox]::Show(
"All-users trust requires running PowerShell as Administrator.",
"MrTrust",
[Windows.Forms.MessageBoxButtons]::OK,
[Windows.Forms.MessageBoxIcon]::Warning
) | Out-Null
return
}
$rootCertificate = Get-MrTrustCertificate -Path $script:RootCertificatePath
$publisherCertificate = Get-MrTrustCertificate -Path $script:PublisherCertificatePath
$message = "Install MrSphay trust for $scope?`r`n`r`nRoot:`r`n$($rootCertificate.Thumbprint)`r`n`r`nPublisher:`r`n$($publisherCertificate.Thumbprint)`r`n`r`nOnly continue if you trust software signed by MrSphay."
$result = [Windows.Forms.MessageBox]::Show(
$message,
"Install MrTrust",
[Windows.Forms.MessageBoxButtons]::YesNo,
[Windows.Forms.MessageBoxIcon]::Warning
)
if ($result -ne [Windows.Forms.DialogResult]::Yes) {
return
}
Import-Certificate -FilePath $script:RootCertificatePath -CertStoreLocation (Get-StorePath -Scope $scope -Store "Root") | Out-Null
Import-Certificate -FilePath $script:PublisherCertificatePath -CertStoreLocation (Get-StorePath -Scope $scope -Store "TrustedPublisher") | Out-Null
Refresh-MrTrustStatus
}
function Remove-MrTrustCertificates {
$scope = Get-TrustScope
if ($scope -eq "LocalMachine" -and -not (Test-IsAdministrator)) {
[Windows.Forms.MessageBox]::Show(
"All-users removal requires running PowerShell as Administrator.",
"MrTrust",
[Windows.Forms.MessageBoxButtons]::OK,
[Windows.Forms.MessageBoxIcon]::Warning
) | Out-Null
return
}
$rootCertificate = Get-MrTrustCertificate -Path $script:RootCertificatePath
$publisherCertificate = Get-MrTrustCertificate -Path $script:PublisherCertificatePath
$result = [Windows.Forms.MessageBox]::Show(
"Remove MrSphay trust for $scope?",
"Remove MrTrust",
[Windows.Forms.MessageBoxButtons]::YesNo,
[Windows.Forms.MessageBoxIcon]::Question
)
if ($result -ne [Windows.Forms.DialogResult]::Yes) {
return
}
$targets = @(
[pscustomobject]@{ Store = "Root"; Thumbprint = $rootCertificate.Thumbprint },
[pscustomobject]@{ Store = "TrustedPublisher"; Thumbprint = $publisherCertificate.Thumbprint }
)
foreach ($target in $targets) {
$storePath = Get-StorePath -Scope $scope -Store $target.Store
Get-ChildItem -Path $storePath |
Where-Object Thumbprint -eq $target.Thumbprint |
Remove-Item
}
Refresh-MrTrustStatus
}
[Windows.Forms.Application]::EnableVisualStyles()
$form = [Windows.Forms.Form]::new()
$form.Text = "MrTrust"
$form.StartPosition = "CenterScreen"
$form.ClientSize = [Drawing.Size]::new(760, 520)
$form.MinimumSize = [Drawing.Size]::new(720, 500)
$form.BackColor = [Drawing.Color]::FromArgb(22, 26, 29)
$form.Font = [Drawing.Font]::new("Segoe UI", 10)
$header = [Windows.Forms.Panel]::new()
$header.Dock = "Top"
$header.Height = 108
$header.BackColor = [Drawing.Color]::FromArgb(27, 32, 35)
$form.Controls.Add($header)
$accent = [Windows.Forms.Panel]::new()
$accent.Dock = "Left"
$accent.Width = 8
$accent.BackColor = [Drawing.Color]::FromArgb(28, 185, 111)
$header.Controls.Add($accent)
$title = [Windows.Forms.Label]::new()
$title.Text = "MrTrust"
$title.ForeColor = [Drawing.Color]::White
$title.Font = [Drawing.Font]::new("Segoe UI", 24, [Drawing.FontStyle]::Bold)
$title.AutoSize = $true
$title.Location = [Drawing.Point]::new(30, 18)
$header.Controls.Add($title)
$subtitle = [Windows.Forms.Label]::new()
$subtitle.Text = "Trust setup for MrSphay signed Windows apps"
$subtitle.ForeColor = [Drawing.Color]::FromArgb(177, 190, 183)
$subtitle.AutoSize = $true
$subtitle.Location = [Drawing.Point]::new(34, 66)
$header.Controls.Add($subtitle)
$script:StatusPill = [Windows.Forms.Panel]::new()
$script:StatusPill.Size = [Drawing.Size]::new(14, 14)
$script:StatusPill.Location = [Drawing.Point]::new(610, 42)
$script:StatusPill.BackColor = [Drawing.Color]::FromArgb(242, 153, 74)
$header.Controls.Add($script:StatusPill)
$script:StatusLabel = [Windows.Forms.Label]::new()
$script:StatusLabel.Text = "Checking..."
$script:StatusLabel.ForeColor = [Drawing.Color]::FromArgb(225, 231, 227)
$script:StatusLabel.AutoSize = $true
$script:StatusLabel.Location = [Drawing.Point]::new(632, 38)
$header.Controls.Add($script:StatusLabel)
$content = [Windows.Forms.Panel]::new()
$content.Dock = "Fill"
$content.Padding = [Windows.Forms.Padding]::new(30)
$content.BackColor = [Drawing.Color]::FromArgb(22, 26, 29)
$form.Controls.Add($content)
$infoPanel = [Windows.Forms.Panel]::new()
$infoPanel.BackColor = [Drawing.Color]::FromArgb(31, 37, 40)
$infoPanel.Size = [Drawing.Size]::new(700, 210)
$infoPanel.Location = [Drawing.Point]::new(30, 34)
$content.Controls.Add($infoPanel)
$scopeLabel = [Windows.Forms.Label]::new()
$scopeLabel.Text = "Scope"
$scopeLabel.ForeColor = [Drawing.Color]::FromArgb(177, 190, 183)
$scopeLabel.Location = [Drawing.Point]::new(24, 24)
$scopeLabel.AutoSize = $true
$infoPanel.Controls.Add($scopeLabel)
$script:AllUsersCheckBox = [Windows.Forms.CheckBox]::new()
$script:AllUsersCheckBox.Text = "Install for all users (requires Administrator)"
$script:AllUsersCheckBox.ForeColor = [Drawing.Color]::FromArgb(225, 231, 227)
$script:AllUsersCheckBox.Location = [Drawing.Point]::new(24, 50)
$script:AllUsersCheckBox.AutoSize = $true
$script:AllUsersCheckBox.FlatStyle = "Flat"
$script:AllUsersCheckBox.Add_CheckedChanged({ Refresh-MrTrustStatus })
$infoPanel.Controls.Add($script:AllUsersCheckBox)
$rootLabel = [Windows.Forms.Label]::new()
$rootLabel.Text = "Root thumbprint"
$rootLabel.ForeColor = [Drawing.Color]::FromArgb(177, 190, 183)
$rootLabel.Location = [Drawing.Point]::new(24, 92)
$rootLabel.AutoSize = $true
$infoPanel.Controls.Add($rootLabel)
$script:RootThumbprintLabel = [Windows.Forms.Label]::new()
$script:RootThumbprintLabel.Text = "-"
$script:RootThumbprintLabel.ForeColor = [Drawing.Color]::FromArgb(225, 231, 227)
$script:RootThumbprintLabel.Font = [Drawing.Font]::new("Consolas", 9)
$script:RootThumbprintLabel.Location = [Drawing.Point]::new(180, 92)
$script:RootThumbprintLabel.AutoSize = $true
$infoPanel.Controls.Add($script:RootThumbprintLabel)
$publisherLabel = [Windows.Forms.Label]::new()
$publisherLabel.Text = "Publisher thumbprint"
$publisherLabel.ForeColor = [Drawing.Color]::FromArgb(177, 190, 183)
$publisherLabel.Location = [Drawing.Point]::new(24, 128)
$publisherLabel.AutoSize = $true
$infoPanel.Controls.Add($publisherLabel)
$script:PublisherThumbprintLabel = [Windows.Forms.Label]::new()
$script:PublisherThumbprintLabel.Text = "-"
$script:PublisherThumbprintLabel.ForeColor = [Drawing.Color]::FromArgb(225, 231, 227)
$script:PublisherThumbprintLabel.Font = [Drawing.Font]::new("Consolas", 9)
$script:PublisherThumbprintLabel.Location = [Drawing.Point]::new(180, 128)
$script:PublisherThumbprintLabel.AutoSize = $true
$infoPanel.Controls.Add($script:PublisherThumbprintLabel)
$expiryLabelTitle = [Windows.Forms.Label]::new()
$expiryLabelTitle.Text = "Expires"
$expiryLabelTitle.ForeColor = [Drawing.Color]::FromArgb(177, 190, 183)
$expiryLabelTitle.Location = [Drawing.Point]::new(24, 164)
$expiryLabelTitle.AutoSize = $true
$infoPanel.Controls.Add($expiryLabelTitle)
$script:ExpiryLabel = [Windows.Forms.Label]::new()
$script:ExpiryLabel.Text = "-"
$script:ExpiryLabel.ForeColor = [Drawing.Color]::FromArgb(225, 231, 227)
$script:ExpiryLabel.Location = [Drawing.Point]::new(180, 164)
$script:ExpiryLabel.AutoSize = $true
$infoPanel.Controls.Add($script:ExpiryLabel)
$installButton = [Windows.Forms.Button]::new()
$installButton.Text = "Install trust"
$installButton.BackColor = [Drawing.Color]::FromArgb(28, 185, 111)
$installButton.ForeColor = [Drawing.Color]::White
$installButton.FlatStyle = "Flat"
$installButton.Size = [Drawing.Size]::new(180, 46)
$installButton.Location = [Drawing.Point]::new(30, 274)
$installButton.Add_Click({ Install-MrTrustCertificates })
$content.Controls.Add($installButton)
$removeButton = [Windows.Forms.Button]::new()
$removeButton.Text = "Remove trust"
$removeButton.BackColor = [Drawing.Color]::FromArgb(44, 52, 56)
$removeButton.ForeColor = [Drawing.Color]::FromArgb(225, 231, 227)
$removeButton.FlatStyle = "Flat"
$removeButton.Size = [Drawing.Size]::new(180, 46)
$removeButton.Location = [Drawing.Point]::new(230, 274)
$removeButton.Add_Click({ Remove-MrTrustCertificates })
$content.Controls.Add($removeButton)
$refreshButton = [Windows.Forms.Button]::new()
$refreshButton.Text = "Refresh"
$refreshButton.BackColor = [Drawing.Color]::FromArgb(44, 52, 56)
$refreshButton.ForeColor = [Drawing.Color]::FromArgb(225, 231, 227)
$refreshButton.FlatStyle = "Flat"
$refreshButton.Size = [Drawing.Size]::new(140, 46)
$refreshButton.Location = [Drawing.Point]::new(430, 274)
$refreshButton.Add_Click({ Refresh-MrTrustStatus })
$content.Controls.Add($refreshButton)
$note = [Windows.Forms.Label]::new()
$note.Text = "MrTrust installs public certificates only. It does not disable Defender, SmartScreen, UAC, or enterprise policies."
$note.ForeColor = [Drawing.Color]::FromArgb(177, 190, 183)
$note.Location = [Drawing.Point]::new(30, 352)
$note.Size = [Drawing.Size]::new(700, 48)
$content.Controls.Add($note)
$form.Add_Shown({ Refresh-MrTrustStatus })
[Windows.Forms.Application]::Run($form)

View File

@@ -0,0 +1,87 @@
[CmdletBinding(SupportsShouldProcess)]
param(
[string]$CertificatePath = ".\assets\certificates\MrSphay-LocalTrust-Root.cer",
[string]$PublisherCertificatePath = ".\assets\certificates\MrSphay-CodeSigning.cer",
[ValidateSet("CurrentUser", "LocalMachine")]
[string]$Scope = "CurrentUser",
[switch]$Force
)
$ErrorActionPreference = "Stop"
function Resolve-FullPath {
param([Parameter(Mandatory)][string]$Path)
$executionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($Path)
}
function Test-IsAdministrator {
$identity = [Security.Principal.WindowsIdentity]::GetCurrent()
$principal = [Security.Principal.WindowsPrincipal]::new($identity)
$principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
}
if ($Scope -eq "LocalMachine" -and -not (Test-IsAdministrator)) {
throw "LocalMachine removal requires an elevated PowerShell session. Use -Scope CurrentUser or run as Administrator."
}
$resolvedCertificatePath = Resolve-FullPath $CertificatePath
if (-not (Test-Path -LiteralPath $resolvedCertificatePath)) {
throw "Certificate file not found: $resolvedCertificatePath. Provide -CertificatePath to the public MrTrust certificate."
}
$rootCertificate = [System.Security.Cryptography.X509Certificates.X509Certificate2]::new($resolvedCertificatePath)
if (-not $rootCertificate.Subject.StartsWith("CN=MrSphay", [System.StringComparison]::OrdinalIgnoreCase)) {
throw "Refusing to remove using an unexpected root certificate subject: $($rootCertificate.Subject)"
}
$resolvedPublisherCertificatePath = Resolve-FullPath $PublisherCertificatePath
$publisherCertificate = $null
if (Test-Path -LiteralPath $resolvedPublisherCertificatePath) {
$publisherCertificate = [System.Security.Cryptography.X509Certificates.X509Certificate2]::new($resolvedPublisherCertificatePath)
if (-not $publisherCertificate.Subject.StartsWith("CN=MrSphay", [System.StringComparison]::OrdinalIgnoreCase)) {
throw "Refusing to remove using an unexpected publisher certificate subject: $($publisherCertificate.Subject)"
}
}
$targets = @(
[pscustomobject]@{
Store = "Cert:\$Scope\Root"
Thumbprint = $rootCertificate.Thumbprint
}
)
if ($publisherCertificate) {
$targets += [pscustomobject]@{
Store = "Cert:\$Scope\TrustedPublisher"
Thumbprint = $publisherCertificate.Thumbprint
}
}
Write-Host "MrTrust will remove this certificate from scope '$Scope':"
Write-Host " Root subject: $($rootCertificate.Subject)"
Write-Host " Root thumbprint: $($rootCertificate.Thumbprint)"
if ($publisherCertificate) {
Write-Host " Publisher subject: $($publisherCertificate.Subject)"
Write-Host " Publisher thumbprint: $($publisherCertificate.Thumbprint)"
}
Write-Host ""
if (-not $Force) {
$answer = Read-Host "Type REMOVE to continue"
if ($answer -cne "REMOVE") {
Write-Host "Removal cancelled."
exit 1
}
}
foreach ($target in $targets) {
$matchingCertificates = Get-ChildItem -Path $target.Store | Where-Object Thumbprint -eq $target.Thumbprint
foreach ($matchingCertificate in $matchingCertificates) {
if ($PSCmdlet.ShouldProcess($target.Store, "Remove MrTrust certificate $($matchingCertificate.Thumbprint)")) {
Remove-Item -LiteralPath $matchingCertificate.PSPath
}
}
}
Write-Host "MrTrust certificate removed where present."