generated from MrSphay/codex-agent-repository-kit
Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f5303fc536 | ||
|
|
a1819900b1 | ||
|
|
46f9f95dcf | ||
|
|
8ce78741bb | ||
|
|
63f134e55e | ||
|
|
e37a0b56c4 | ||
|
|
a5cea35069 | ||
|
|
f3c353b821 | ||
|
|
d3234e03b8 | ||
| 631a66dab1 | |||
| 16e5d1377c | |||
| 7a2a643d15 | |||
| e885978aba | |||
| d6e2d5ab52 | |||
| 84a5df7216 | |||
| 01148f4703 | |||
| 93ca15a881 | |||
|
|
cf32e3b20e | ||
|
|
46026cb62c |
@@ -8,42 +8,95 @@ on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build-windows:
|
||||
runs-on: windows-latest
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
MRTRUST_VERSION: 0.1.4
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Verify PowerShell scripts
|
||||
shell: powershell
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: 8.0.x
|
||||
|
||||
- name: Build Windows EXE
|
||||
shell: bash
|
||||
run: |
|
||||
$scripts = @()
|
||||
$scripts += @(Get-ChildItem . -Filter *.ps1)
|
||||
$scripts += @(Get-ChildItem .\scripts -Filter *.ps1)
|
||||
foreach ($script in $scripts) {
|
||||
$tokens = $null
|
||||
$errors = $null
|
||||
[System.Management.Automation.Language.Parser]::ParseFile($script.FullName, [ref]$tokens, [ref]$errors) | Out-Null
|
||||
if ($errors) { throw $errors }
|
||||
}
|
||||
set -euo pipefail
|
||||
dotnet publish src/MrTrustLauncher.csproj \
|
||||
--configuration Release \
|
||||
--runtime win-x64 \
|
||||
--output dist/build \
|
||||
-p:EnableWindowsTargeting=true \
|
||||
-p:PublishSingleFile=true \
|
||||
-p:SelfContained=true
|
||||
cp dist/build/MrTrust.exe dist/MrTrust.exe
|
||||
|
||||
- name: Build release ZIP
|
||||
shell: powershell
|
||||
shell: bash
|
||||
run: |
|
||||
$arguments = @("-ExecutionPolicy", "Bypass", "-File", ".\scripts\New-MrTrustRelease.ps1", "-Version", "0.1.0")
|
||||
if ($env:MRTRUST_SIGNING_THUMBPRINT) {
|
||||
$arguments += @("-SigningThumbprint", $env:MRTRUST_SIGNING_THUMBPRINT)
|
||||
}
|
||||
powershell @arguments
|
||||
set -euo pipefail
|
||||
version="${MRTRUST_VERSION}"
|
||||
package_root="dist/MrTrust-${version}"
|
||||
rm -rf "$package_root" "dist/MrTrust-${version}.zip"
|
||||
mkdir -p "$package_root"
|
||||
cp dist/MrTrust.exe "$package_root/"
|
||||
cp README.md "$package_root/"
|
||||
(cd dist && zip -r "MrTrust-${version}.zip" "MrTrust-${version}")
|
||||
|
||||
- name: Show package contents
|
||||
shell: powershell
|
||||
shell: bash
|
||||
run: |
|
||||
Get-ChildItem .\dist -Recurse | Select-Object FullName, Length
|
||||
find dist -maxdepth 4 -type f -printf '%p %s bytes\n'
|
||||
|
||||
- name: Upload release artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: MrTrust-0.1.0
|
||||
path: dist/MrTrust-0.1.0.zip
|
||||
name: MrTrust-${{ env.MRTRUST_VERSION }}
|
||||
path: dist/MrTrust-${{ env.MRTRUST_VERSION }}.zip
|
||||
|
||||
- name: Attach ZIP to Gitea release
|
||||
if: (github.event_name == 'push' && github.ref == 'refs/heads/main') || github.event_name == 'workflow_dispatch'
|
||||
shell: bash
|
||||
env:
|
||||
GITEA_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
version="${MRTRUST_VERSION}"
|
||||
asset_name="MrTrust-${version}.zip"
|
||||
api="https://git.wilkensxl.de/api/v1/repos/MrSphay/MrTrust"
|
||||
release_response="$(mktemp)"
|
||||
status="$(curl -sS -o "$release_response" -w "%{http_code}" -H "Authorization: token ${GITEA_TOKEN}" "${api}/releases/tags/v${version}")"
|
||||
if [ "$status" = "404" ]; then
|
||||
release_json="$(VERSION="$version" python3 -c 'import json, os; version = os.environ["VERSION"]; print(json.dumps({"tag_name": f"v{version}", "target_commitish": "main", "name": f"MrTrust v{version}", "body": f"MrTrust v{version} release built by the Gitea runner.", "draft": False, "prerelease": False}))')"
|
||||
curl -fsS \
|
||||
-X POST \
|
||||
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$release_json" \
|
||||
"${api}/releases" > "$release_response"
|
||||
elif [ "$status" != "200" ]; then
|
||||
cat "$release_response" >&2
|
||||
exit 1
|
||||
fi
|
||||
release_json="$(cat "$release_response")"
|
||||
release_id="$(printf '%s' "$release_json" | python3 -c 'import json,sys; print(json.load(sys.stdin)["id"])')"
|
||||
if [ -z "$release_id" ]; then
|
||||
echo "Could not resolve release id for v${version}" >&2
|
||||
exit 1
|
||||
fi
|
||||
existing_asset_id="$(RELEASE_JSON="$release_json" ASSET_NAME="$asset_name" python3 -c 'import json, os; release = json.loads(os.environ["RELEASE_JSON"]); asset_name = os.environ["ASSET_NAME"]; print(next((str(asset.get("id", "")) for asset in release.get("assets", []) if asset.get("name") == asset_name), ""))')"
|
||||
if [ -n "$existing_asset_id" ]; then
|
||||
curl -fsS \
|
||||
-X DELETE \
|
||||
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||
"${api}/releases/${release_id}/assets/${existing_asset_id}"
|
||||
fi
|
||||
curl -fsS \
|
||||
-X POST \
|
||||
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||
-F "attachment=@dist/${asset_name}" \
|
||||
"${api}/releases/${release_id}/assets?name=${asset_name}"
|
||||
|
||||
@@ -16,8 +16,10 @@ MrTrust manages explicit Windows certificate trust for MrSphay software.
|
||||
- `assets/certificates/` contains public certificates only.
|
||||
- `private/` is ignored and may contain local signing material.
|
||||
- `docs/integration-prompt.md` is the prompt for adding MrTrust to other projects.
|
||||
- `docs/agent-target-integration.md` is the autonomous runbook for agents modifying target projects.
|
||||
- `mrtrust.integration.json` is the machine-readable integration contract.
|
||||
- `docs/security-model.md` documents the intended behavior and limits.
|
||||
- `MrTrust.ps1 gui` is the user-facing GUI entry point.
|
||||
- `MrTrust.exe` is the user-facing standalone trust installer. The PowerShell scripts are source/build internals.
|
||||
|
||||
## Verification
|
||||
|
||||
|
||||
26
CHANGELOG.md
26
CHANGELOG.md
@@ -1,9 +1,33 @@
|
||||
# Changelog
|
||||
|
||||
## 0.1.4
|
||||
|
||||
- Added a diagnostics tab for checking `.exe`, `.msi`, `.dll`, and `.cat` files.
|
||||
- Added signature status, MrSphay signer matching, and Mark-of-the-Web detection.
|
||||
- Added an in-app SmartScreen explanation to clarify reputation versus local certificate trust.
|
||||
- Added subtle Modrinth-style hover, press, progress, and accent animations.
|
||||
|
||||
## 0.1.3
|
||||
|
||||
- Added Ubuntu-runner signing support through `osslsigncode` and PFX secrets.
|
||||
|
||||
## 0.1.2
|
||||
|
||||
- Made `MrTrust.exe` a standalone user-facing release artifact.
|
||||
- Added an autonomous target-project integration runbook for agents.
|
||||
- Added `mrtrust.integration.json` as a machine-readable integration contract.
|
||||
- Updated agent-facing documentation to prefer the standalone `MrTrust.exe` integration path.
|
||||
|
||||
## 0.1.1
|
||||
|
||||
- Added a custom MrTrust application icon and embedded it into the launcher.
|
||||
- Improved the GUI layout so status text is not clipped.
|
||||
- Updated the Gitea artifact version to `0.1.1`.
|
||||
|
||||
## 0.1.0
|
||||
|
||||
- Added MrTrust certificate generation, installation, removal, and signing scripts.
|
||||
- Added a simple Windows GUI for installing and removing MrTrust.
|
||||
- Added a Windows launcher EXE and Gitea Runner workflow for release ZIP builds.
|
||||
- Added a Windows launcher EXE and Ubuntu Gitea Runner workflow for release ZIP builds.
|
||||
- Added integration prompt for other Windows projects.
|
||||
- Added security model documentation.
|
||||
|
||||
307
README.md
307
README.md
@@ -1,123 +1,240 @@
|
||||
# MrTrust
|
||||
|
||||
MrTrust is a small Windows trust-onboarding kit for MrSphay software.
|
||||
## Brought To You By The Fine People Of MrSphay
|
||||
|
||||
It is designed for this workflow:
|
||||
Good morning, citizen.
|
||||
|
||||
1. MrSphay creates a private code-signing certificate once.
|
||||
2. MrSphay publishes only the public trust certificate with MrTrust.
|
||||
3. A user runs MrTrust once and explicitly approves installing that public certificate.
|
||||
4. MrSphay projects signed with the matching certificate chain are shown as trusted on that PC.
|
||||
|
||||
MrTrust does not bypass Microsoft Defender or SmartScreen. Windows can still scan, quarantine, or warn about suspicious files. This project only manages normal Windows certificate trust with visible user consent.
|
||||
|
||||
## What It Contains
|
||||
|
||||
- `MrTrust.ps1 gui` opens a simple Windows interface for installing or removing trust.
|
||||
- `scripts/New-MrTrustCertificate.ps1` creates a local root certificate and a code-signing certificate for the publisher.
|
||||
- `scripts/Install-MrTrust.ps1` installs the public trust certificate for the current user or the local machine.
|
||||
- `scripts/Uninstall-MrTrust.ps1` removes the MrTrust certificate again.
|
||||
- `scripts/Sign-MrTrustProject.ps1` signs `.exe`, `.msi`, `.ps1`, and other Authenticode-compatible files.
|
||||
- `scripts/New-MrTrustRelease.ps1` builds a distributable ZIP package.
|
||||
- `docs/integration-prompt.md` is a prompt you can paste into other Windows projects.
|
||||
|
||||
## Quick Start For MrSphay
|
||||
|
||||
Create the certificates:
|
||||
|
||||
```powershell
|
||||
.\scripts\New-MrTrustCertificate.ps1
|
||||
```
|
||||
|
||||
This writes:
|
||||
|
||||
- public certificates to `assets\certificates\`
|
||||
- private signing material to `private\`
|
||||
|
||||
The `private\` directory is ignored by git. Do not publish `.pfx` files or passwords.
|
||||
|
||||
Install the public trust certificate on your own PC:
|
||||
|
||||
```powershell
|
||||
.\MrTrust.ps1 install
|
||||
```
|
||||
|
||||
Open the GUI:
|
||||
|
||||
```powershell
|
||||
.\MrTrust.ps1 gui
|
||||
```
|
||||
|
||||
Sign another project build:
|
||||
|
||||
```powershell
|
||||
.\MrTrust.ps1 sign `
|
||||
-Path "C:\Path\To\App.exe" `
|
||||
-PfxPath ".\private\MrSphay-CodeSigning.pfx"
|
||||
```
|
||||
|
||||
Remove the trust certificate:
|
||||
|
||||
```powershell
|
||||
.\MrTrust.ps1 uninstall
|
||||
```
|
||||
|
||||
Build a user-facing ZIP release:
|
||||
|
||||
```powershell
|
||||
.\scripts\New-MrTrustRelease.ps1 -Version 0.1.0
|
||||
```
|
||||
|
||||
The Gitea workflow `.gitea/workflows/build.yml` builds the same ZIP on a Windows runner and uploads it as an artifact.
|
||||
If the Windows runner has the private signing certificate installed, set `MRTRUST_SIGNING_THUMBPRINT` to sign the launcher during the build.
|
||||
|
||||
## User Installation
|
||||
|
||||
For normal users, distribute MrTrust with the public certificate file:
|
||||
Has Windows ever looked at your freshly downloaded MrSphay program and said:
|
||||
|
||||
```text
|
||||
assets\certificates\MrSphay-LocalTrust-Root.cer
|
||||
assets\certificates\MrSphay-CodeSigning.cer
|
||||
Unknown publisher? Sounds suspicious, pal.
|
||||
```
|
||||
|
||||
The user runs:
|
||||
Then step right up to **MrTrust**, the cheerful little trust-onboarding utility that helps your PC recognize signed MrSphay software without poking holes in Windows security.
|
||||
|
||||
```powershell
|
||||
.\MrTrust.ps1 gui
|
||||
One click. One confirmation. A brighter tomorrow for properly signed applications.
|
||||
|
||||
MrTrust installs public certificates only after you say so. It does not disable Microsoft Defender, SmartScreen, UAC, firewall rules, company policies, common sense, or the big red security lever nobody should touch.
|
||||
|
||||
## Download Your Complimentary Trust Appliance
|
||||
|
||||
Latest release page:
|
||||
|
||||
```text
|
||||
https://git.wilkensxl.de/MrSphay/MrTrust/releases
|
||||
```
|
||||
|
||||
By default, MrTrust installs trust only for the current Windows user:
|
||||
Download the newest:
|
||||
|
||||
```text
|
||||
MrTrust-<version>.zip
|
||||
```
|
||||
|
||||
Extract it, then run:
|
||||
|
||||
```text
|
||||
MrTrust.exe
|
||||
```
|
||||
|
||||
That is the normal user version. It is standalone and carries the public MrSphay certificates it needs.
|
||||
|
||||
## Operating Your Trust-O-Matic 3000
|
||||
|
||||
Inside the friendly GUI:
|
||||
|
||||
- `Install trust` tells Windows to trust MrSphay public signing certificates.
|
||||
- `Remove trust` politely takes that trust back out again.
|
||||
- `Refresh` checks whether your PC is currently feeling cooperative.
|
||||
|
||||
Default installation scope:
|
||||
|
||||
```text
|
||||
Root certificate -> Cert:\CurrentUser\Root
|
||||
Code-signing certificate -> Cert:\CurrentUser\TrustedPublisher
|
||||
```
|
||||
|
||||
For all users on the machine, run PowerShell as Administrator:
|
||||
That means the trust applies only to the current Windows user.
|
||||
|
||||
```powershell
|
||||
.\MrTrust.ps1 install -Scope LocalMachine
|
||||
For all users on the PC, run `MrTrust.exe` as Administrator and choose the all-users option. Please operate administrator privileges responsibly. The future depends on it.
|
||||
|
||||
## How The Magic Works
|
||||
|
||||
There is no magic. That is how you know it is working.
|
||||
|
||||
The approved flow:
|
||||
|
||||
1. A MrSphay app is signed during its release build.
|
||||
2. You run `MrTrust.exe`.
|
||||
3. You review the certificate details.
|
||||
4. You confirm the trust installation.
|
||||
5. Windows can validate signed MrSphay apps on that PC.
|
||||
|
||||
If the app is not signed, MrTrust cannot help it. Even the finest paperwork cannot identify a person who never showed up.
|
||||
|
||||
## Safety Notice From The Department Of Not Breaking Windows
|
||||
|
||||
MrTrust does not:
|
||||
|
||||
- make unsigned programs trusted
|
||||
- bypass Defender
|
||||
- bypass SmartScreen
|
||||
- remove UAC prompts
|
||||
- silently install certificates
|
||||
- install private signing keys on user machines
|
||||
- make sketchy software less sketchy
|
||||
|
||||
Windows may still scan, block, warn, quarantine, or ask questions. MrTrust only handles normal certificate trust.
|
||||
|
||||
## Public Certificate Values
|
||||
|
||||
These values are public and safe to use in documentation, agent prompts, and integration metadata:
|
||||
|
||||
```text
|
||||
Publisher:
|
||||
MrSphay
|
||||
|
||||
Root certificate thumbprint:
|
||||
39F7458E6E2C1126E93E6A1F228196006B174DF2
|
||||
|
||||
Code-signing certificate thumbprint:
|
||||
A024A89200469F099EC4A172B4F96F6428AFD41B
|
||||
```
|
||||
|
||||
## Using This Repo With Other Agents
|
||||
They are also stored here:
|
||||
|
||||
Yes. Give another agent this repository URL and the target Windows project, then paste `docs/integration-prompt.md`.
|
||||
```text
|
||||
assets/certificates/thumbprints.txt
|
||||
mrtrust.integration.json
|
||||
```
|
||||
|
||||
Both sides have to be wired:
|
||||
## For The Workshop Crew
|
||||
|
||||
- MrTrust side: users install the public trust certificates once.
|
||||
- Target project side: release artifacts are signed with the MrSphay code-signing certificate.
|
||||
- Installer side, optional: the target app can offer "Open MrTrust" or bundle the MrTrust ZIP, but it must not silently change trust.
|
||||
Local maintainer commands:
|
||||
|
||||
If the target project is not signed, MrTrust cannot make it trusted.
|
||||
```powershell
|
||||
.\MrTrust.ps1 gui
|
||||
.\MrTrust.ps1 install
|
||||
.\MrTrust.ps1 uninstall
|
||||
```
|
||||
|
||||
## Important Limits
|
||||
Create or refresh local certificates:
|
||||
|
||||
- This only helps for programs signed with the matching MrSphay certificate chain.
|
||||
- It does not make unsigned programs trusted.
|
||||
- It does not disable Defender, SmartScreen, UAC, or enterprise policies.
|
||||
- Public distribution without warnings is still best handled with a recognized commercial code-signing certificate.
|
||||
```powershell
|
||||
.\scripts\New-MrTrustCertificate.ps1
|
||||
```
|
||||
|
||||
## Recommended Project Integration
|
||||
Build a release ZIP locally:
|
||||
|
||||
Use `docs/integration-prompt.md` in another Windows project. The prompt tells Codex or another assistant to add a visible trust check, a link or bundled copy of MrTrust, and a signing step without hiding security changes from the user.
|
||||
```powershell
|
||||
.\scripts\New-MrTrustRelease.ps1 -Version 0.1.4
|
||||
```
|
||||
|
||||
Sign an artifact locally on Windows:
|
||||
|
||||
```powershell
|
||||
.\MrTrust.ps1 sign `
|
||||
-Path "C:\Path\To\App.exe" `
|
||||
-CertificateThumbprint A024A89200469F099EC4A172B4F96F6428AFD41B
|
||||
```
|
||||
|
||||
Private signing material belongs only in:
|
||||
|
||||
```text
|
||||
private/
|
||||
Bitwarden
|
||||
Gitea repository secrets
|
||||
```
|
||||
|
||||
Never commit `.pfx` files, private keys, passwords, or Base64-encoded signing material. That is not trust. That is handing out the vault keys at the snack counter.
|
||||
|
||||
## Gitea Secrets For Other Projects
|
||||
|
||||
For another project to sign Windows release artifacts on an Ubuntu Gitea runner, add these secrets to that target repository:
|
||||
|
||||
```text
|
||||
MRTRUST_CODESIGN_PFX_BASE64
|
||||
MRTRUST_CODESIGN_PFX_PASSWORD
|
||||
```
|
||||
|
||||
Optional timestamp override:
|
||||
|
||||
```text
|
||||
MRTRUST_TIMESTAMP_URL
|
||||
```
|
||||
|
||||
The first two values are private signing credentials. Keep them in Bitwarden and Gitea Secrets only.
|
||||
|
||||
Ubuntu helper script:
|
||||
|
||||
```text
|
||||
scripts/Sign-MrTrustProjectLinux.sh
|
||||
```
|
||||
|
||||
It signs supported Windows artifacts with `osslsigncode`:
|
||||
|
||||
```text
|
||||
.exe
|
||||
.msi
|
||||
.dll
|
||||
.cat
|
||||
```
|
||||
|
||||
PowerShell scripts should be signed on Windows, not Ubuntu.
|
||||
|
||||
## Installing MrTrust Into Another Project
|
||||
|
||||
Give your coding agent this repository:
|
||||
|
||||
```text
|
||||
https://git.wilkensxl.de/MrSphay/MrTrust
|
||||
```
|
||||
|
||||
Tell it to read:
|
||||
|
||||
```text
|
||||
mrtrust.integration.json
|
||||
docs/agent-target-integration.md
|
||||
docs/integration-prompt.md
|
||||
```
|
||||
|
||||
The target project should end up with:
|
||||
|
||||
- signed Windows release artifacts
|
||||
- a visible optional MrTrust setup path
|
||||
- a link to or bundled copy of `MrTrust.exe`
|
||||
- documentation for installing and removing trust
|
||||
- no committed private signing material
|
||||
|
||||
Remember the two-part handshake:
|
||||
|
||||
- MrTrust side: the user installs public trust certificates once.
|
||||
- Target project side: the app is signed with the MrSphay code-signing certificate.
|
||||
|
||||
No signature, no trust. No trust, no victory parade.
|
||||
|
||||
## Current Build
|
||||
|
||||
The Gitea workflow builds `MrTrust.exe` on `ubuntu-latest` with .NET Windows cross-targeting.
|
||||
|
||||
On pushes to `main`, it:
|
||||
|
||||
1. builds the standalone Windows executable
|
||||
2. packages `MrTrust-0.1.4.zip`
|
||||
3. uploads the workflow artifact
|
||||
4. attaches the ZIP to the Gitea release
|
||||
|
||||
Manual `workflow_dispatch` runs build artifacts but do not attach release assets. This prevents duplicate release uploads, which are bad for morale and paperwork.
|
||||
|
||||
## Final Safety Reminder
|
||||
|
||||
MrTrust is intentionally visible and reversible:
|
||||
|
||||
- the GUI shows the trust state
|
||||
- installation requires confirmation
|
||||
- removal is available in the same tool
|
||||
- public certificates are embedded in the executable
|
||||
- private signing material is never needed on user machines
|
||||
|
||||
For broad public distribution without SmartScreen reputation delays, a recognized commercial code-signing certificate is still the cleanest option.
|
||||
|
||||
Thank you for choosing MrTrust. Stay signed, stay verified, and keep your release pipeline tidy.
|
||||
|
||||
BIN
assets/MrTrust.ico
Normal file
BIN
assets/MrTrust.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.6 KiB |
209
docs/agent-target-integration.md
Normal file
209
docs/agent-target-integration.md
Normal file
@@ -0,0 +1,209 @@
|
||||
# Agent Target Integration Runbook
|
||||
|
||||
Use this file when you are an agent modifying another Windows project to support MrTrust.
|
||||
|
||||
## Non-Negotiable Goal
|
||||
|
||||
Modify the target project so that, after a user has installed trust with `MrTrust.exe`, the target project's MrSphay-signed Windows release artifacts start and validate normally.
|
||||
|
||||
Do not modify Windows security policy, Defender, SmartScreen, UAC, or certificate stores silently. MrTrust must remain visible, optional, and reversible.
|
||||
|
||||
## Inputs To Read First
|
||||
|
||||
1. Read `mrtrust.integration.json`.
|
||||
2. Read `docs/integration-prompt.md`.
|
||||
3. Inspect the target project for:
|
||||
- build commands
|
||||
- release artifact paths
|
||||
- installer technology
|
||||
- existing signing steps
|
||||
- user-facing docs
|
||||
- CI workflows
|
||||
|
||||
If the target project already has equivalent trust or signing integration, update it instead of duplicating it.
|
||||
|
||||
## Required Target Project Changes
|
||||
|
||||
Apply every item that fits the target project:
|
||||
|
||||
1. Add a user-facing MrTrust entry point.
|
||||
- If the project has an installer, add an optional action named `Open MrTrust` or `Trust MrSphay signed apps`.
|
||||
- If the project has an app settings/about/help screen, add a button or menu item that launches `MrTrust.exe`.
|
||||
- If the project has no UI or installer, add release documentation linking to or bundling `MrTrust.exe`.
|
||||
|
||||
2. Bundle or link the standalone executable.
|
||||
- Preferred bundled path: `MrTrust\MrTrust.exe`.
|
||||
- Source repository: `https://git.wilkensxl.de/MrSphay/MrTrust`.
|
||||
- Release page: `https://git.wilkensxl.de/MrSphay/MrTrust/releases`.
|
||||
- Do not require users to run `MrTrust.ps1`.
|
||||
- Do not copy MrTrust source scripts into user-facing release output unless the target project explicitly ships source.
|
||||
|
||||
3. Add signing for Windows artifacts.
|
||||
- Sign `.exe`, `.msi`, `.dll`, `.ps1`, `.psm1`, `.psd1`, and `.cat` release artifacts.
|
||||
- Use the public thumbprint from `mrtrust.integration.json`.
|
||||
- Only run signing where private signing material is securely available.
|
||||
- Do not commit `.pfx`, private keys, passwords, tokens, or signing secrets.
|
||||
- On the available `ubuntu-latest` Gitea runners, use `osslsigncode` with PFX secrets for `.exe`, `.dll`, `.msi`, and `.cat`.
|
||||
- PowerShell script signing (`.ps1`, `.psm1`, `.psd1`) still requires a Windows signing environment. If no Windows runner exists, document that limitation instead of blocking PE/MSI signing.
|
||||
|
||||
4. Add verification.
|
||||
- Verify the target project still builds.
|
||||
- Verify the release output contains the app artifacts and either `MrTrust.exe` or clear MrTrust download instructions.
|
||||
- Verify signing status where the OS/tooling is available.
|
||||
- Verify no private signing material is tracked or present in release output.
|
||||
|
||||
## Default Commands
|
||||
|
||||
Open MrTrust from a bundled release:
|
||||
|
||||
```powershell
|
||||
.\MrTrust\MrTrust.exe
|
||||
```
|
||||
|
||||
Sign one artifact from a Windows release runner:
|
||||
|
||||
```powershell
|
||||
.\MrTrust\MrTrust.exe sign -Path .\dist\App.exe -CertificateThumbprint A024A89200469F099EC4A172B4F96F6428AFD41B
|
||||
```
|
||||
|
||||
Sign a release directory:
|
||||
|
||||
```powershell
|
||||
.\MrTrust\MrTrust.exe sign -Path .\dist -CertificateThumbprint A024A89200469F099EC4A172B4F96F6428AFD41B
|
||||
```
|
||||
|
||||
Check a signature:
|
||||
|
||||
```powershell
|
||||
Get-AuthenticodeSignature .\dist\App.exe | Format-List Status,SignerCertificate,StatusMessage
|
||||
```
|
||||
|
||||
Sign a release directory on an Ubuntu Gitea runner:
|
||||
|
||||
```bash
|
||||
bash ./MrTrust/scripts/Sign-MrTrustProjectLinux.sh ./dist
|
||||
```
|
||||
|
||||
## Installer Patterns
|
||||
|
||||
### Inno Setup
|
||||
|
||||
Bundle `MrTrust.exe` and add an optional task or post-install action:
|
||||
|
||||
```ini
|
||||
[Files]
|
||||
Source: "MrTrust\MrTrust.exe"; DestDir: "{app}\MrTrust"; Flags: ignoreversion
|
||||
|
||||
[Run]
|
||||
Filename: "{app}\MrTrust\MrTrust.exe"; Description: "Open MrTrust"; Flags: postinstall skipifsilent nowait
|
||||
```
|
||||
|
||||
### NSIS
|
||||
|
||||
```nsis
|
||||
SetOutPath "$INSTDIR\MrTrust"
|
||||
File "MrTrust\MrTrust.exe"
|
||||
CreateShortcut "$SMPROGRAMS\$StartMenuFolder\Open MrTrust.lnk" "$INSTDIR\MrTrust\MrTrust.exe"
|
||||
```
|
||||
|
||||
### WiX
|
||||
|
||||
Install `MrTrust.exe` as a regular file under an application `MrTrust` folder and expose a Start Menu shortcut or installer UI action. Do not run it silently during install.
|
||||
|
||||
### Electron Builder
|
||||
|
||||
Add `MrTrust\MrTrust.exe` to `extraResources`, then add a Help/About action that launches the copied executable with the platform shell API. Keep the action user-initiated.
|
||||
|
||||
### Portable ZIP
|
||||
|
||||
Place `MrTrust.exe` next to the app under:
|
||||
|
||||
```text
|
||||
MrTrust\MrTrust.exe
|
||||
```
|
||||
|
||||
Document that users should run it once before launching signed MrSphay apps if Windows does not yet trust the publisher.
|
||||
|
||||
## CI Signing Patterns
|
||||
|
||||
### Gitea Actions On Ubuntu Runner
|
||||
|
||||
Use this when only `ubuntu-latest`, `ubuntu-24.04`, or `ubuntu-22.04` runners are available.
|
||||
|
||||
Required Gitea secrets:
|
||||
|
||||
```text
|
||||
MRTRUST_CODESIGN_PFX_BASE64
|
||||
MRTRUST_CODESIGN_PFX_PASSWORD
|
||||
```
|
||||
|
||||
Create `MRTRUST_CODESIGN_PFX_BASE64` locally from the private `.pfx`:
|
||||
|
||||
```powershell
|
||||
[Convert]::ToBase64String([IO.File]::ReadAllBytes(".\private\MrSphay-CodeSigning.pfx")) | Set-Clipboard
|
||||
```
|
||||
|
||||
Then paste the clipboard value into the Gitea secret. Do not commit the `.pfx` or the base64 value.
|
||||
|
||||
Ubuntu workflow step:
|
||||
|
||||
```yaml
|
||||
- name: Install signing tool
|
||||
shell: bash
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y osslsigncode
|
||||
|
||||
- name: Sign Windows artifacts
|
||||
shell: bash
|
||||
env:
|
||||
MRTRUST_CODESIGN_PFX_BASE64: ${{ secrets.MRTRUST_CODESIGN_PFX_BASE64 }}
|
||||
MRTRUST_CODESIGN_PFX_PASSWORD: ${{ secrets.MRTRUST_CODESIGN_PFX_PASSWORD }}
|
||||
run: |
|
||||
bash ./MrTrust/scripts/Sign-MrTrustProjectLinux.sh ./dist
|
||||
```
|
||||
|
||||
This signs `.exe`, `.dll`, `.msi`, and `.cat` artifacts. It does not sign PowerShell script files.
|
||||
|
||||
### Gitea Actions On Windows Runner
|
||||
|
||||
```yaml
|
||||
- name: Sign Windows artifacts
|
||||
shell: powershell
|
||||
run: |
|
||||
.\MrTrust\MrTrust.exe sign -Path .\dist -CertificateThumbprint A024A89200469F099EC4A172B4F96F6428AFD41B
|
||||
```
|
||||
|
||||
Use this only on a runner where the matching private code-signing certificate is installed in `Cert:\CurrentUser\My` or `Cert:\LocalMachine\My`.
|
||||
|
||||
### Local Secure Release Machine
|
||||
|
||||
```powershell
|
||||
.\MrTrust\MrTrust.exe sign -Path .\dist -CertificateThumbprint A024A89200469F099EC4A172B4F96F6428AFD41B
|
||||
```
|
||||
|
||||
Run this after build and before packaging.
|
||||
|
||||
## Autonomy Rules
|
||||
|
||||
Make reasonable target-project-specific choices without asking the user when:
|
||||
|
||||
- artifact paths are discoverable from existing build scripts
|
||||
- installer technology is obvious from repository files
|
||||
- there is already a docs or release notes location
|
||||
- CI already has a Windows release job you can extend
|
||||
|
||||
Stop and ask the user only when:
|
||||
|
||||
- signing requires a private certificate that is not present and no secret mechanism exists
|
||||
- the target project has multiple conflicting release systems and no primary release path is identifiable
|
||||
- a requested change would silently alter trust or weaken security policy
|
||||
|
||||
## Completion Checklist
|
||||
|
||||
- Target project has a visible MrTrust user path.
|
||||
- Target project links to or bundles standalone `MrTrust.exe`.
|
||||
- Supported Windows release artifacts are signed or the blocker is explicitly documented.
|
||||
- User docs explain install and remove trust.
|
||||
- No private signing material is committed.
|
||||
- Target project build/release verification ran, or the exact blocker is documented.
|
||||
@@ -2,6 +2,10 @@
|
||||
|
||||
Use this prompt in another Windows project when you want Codex or another agent to add MrTrust support.
|
||||
|
||||
Before changing the target project, read:
|
||||
- `mrtrust.integration.json`
|
||||
- `docs/agent-target-integration.md`
|
||||
|
||||
```text
|
||||
Integrate MrTrust support into this Windows project.
|
||||
|
||||
@@ -10,22 +14,33 @@ Goal:
|
||||
- The project must not disable Defender, SmartScreen, UAC, or Windows security policy.
|
||||
- All trust changes must be visible and require explicit user confirmation.
|
||||
|
||||
Agent contract:
|
||||
- You are modifying the target Windows project, not MrTrust itself.
|
||||
- Use `mrtrust.integration.json` and `docs/agent-target-integration.md` as the authoritative integration contract.
|
||||
- Add MrTrust as a visible, optional trust-onboarding path for users.
|
||||
- Add or update the target project's release/signing pipeline so produced Windows artifacts are signed.
|
||||
- Do not mark the task complete after only copying documentation. The target project needs user-facing integration and signing support where applicable.
|
||||
|
||||
Implementation requirements:
|
||||
- Add a docs section that explains MrTrust and links to or bundles the MrTrust release ZIP.
|
||||
- Treat `MrTrust.exe` from the release ZIP as the user-facing integration point. Do not require users to copy or run MrTrust PowerShell source files.
|
||||
- Add a build or release signing step that signs Windows artifacts with the MrSphay code-signing certificate.
|
||||
- Do not commit any .pfx file, private key, certificate password, or signing secret.
|
||||
- If the project has an installer, add an optional "Open MrTrust" action that runs:
|
||||
powershell.exe -ExecutionPolicy Bypass -File .\MrTrust\MrTrust.ps1 gui
|
||||
.\MrTrust\MrTrust.exe
|
||||
- Prefer CurrentUser certificate installation by default.
|
||||
- Only offer LocalMachine installation when the user explicitly chooses an all-users install and the process is elevated.
|
||||
- Add an uninstall path or documentation that runs:
|
||||
powershell.exe -ExecutionPolicy Bypass -File .\MrTrust\MrTrust.ps1 uninstall
|
||||
- Add an uninstall path or documentation that opens MrTrust again and tells the user to choose "Remove trust".
|
||||
- Keep the UI wording clear: the user is trusting MrSphay signed software, not bypassing Windows security.
|
||||
- If this project produces an .exe, .msi, .dll, .ps1, .psm1, .psd1, or .cat release artifact, sign it with:
|
||||
powershell.exe -ExecutionPolicy Bypass -File .\MrTrust\MrTrust.ps1 sign -Path <artifact-path> -CertificateThumbprint A024A89200469F099EC4A172B4F96F6428AFD41B
|
||||
- If this project produces an .exe, .msi, .dll, or .cat release artifact on an Ubuntu Gitea runner, sign it with:
|
||||
bash ./MrTrust/scripts/Sign-MrTrustProjectLinux.sh <artifact-path>
|
||||
- Treat the certificate thumbprint as public metadata, but never commit private signing material.
|
||||
- Configure Gitea secrets `MRTRUST_CODESIGN_PFX_BASE64` and `MRTRUST_CODESIGN_PFX_PASSWORD` for Ubuntu runner signing.
|
||||
- If the target project needs .ps1, .psm1, or .psd1 signing, use a Windows signing environment or document that script signing is not available on the current Ubuntu-only runners.
|
||||
- Do not put private signing material into the target repository or user-facing release ZIP.
|
||||
|
||||
Verification:
|
||||
- Confirm the target project's user-facing release contains either a link to the MrTrust release ZIP or a bundled copy of `MrTrust.exe`.
|
||||
- Confirm unsigned builds still show as unsigned.
|
||||
- Confirm signed builds validate after MrTrust installation.
|
||||
- Confirm the MrTrust certificate can be removed again.
|
||||
|
||||
@@ -1,7 +1,16 @@
|
||||
{
|
||||
"name": "codex-agent-repository-kit",
|
||||
"version": "1.0.5",
|
||||
"version": "1.0.6",
|
||||
"description": "Universal repository baseline for Codex-assisted projects.",
|
||||
"mrtrustIntegration": {
|
||||
"contract": "mrtrust.integration.json",
|
||||
"agentRunbook": "docs/agent-target-integration.md",
|
||||
"prompt": "docs/integration-prompt.md",
|
||||
"standaloneExecutable": "MrTrust.exe",
|
||||
"publicThumbprint": "A024A89200469F099EC4A172B4F96F6428AFD41B",
|
||||
"sourceRepository": "https://git.wilkensxl.de/MrSphay/MrTrust",
|
||||
"releasePage": "https://git.wilkensxl.de/MrSphay/MrTrust/releases"
|
||||
},
|
||||
"agentResponsibilities": [
|
||||
"Read manifest.json before copying files.",
|
||||
"Use copyMap target paths unless the repository already has an equivalent convention.",
|
||||
|
||||
@@ -13,6 +13,34 @@
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"mrtrustIntegration": {
|
||||
"type": "object",
|
||||
"required": ["contract", "agentRunbook", "prompt", "standaloneExecutable", "publicThumbprint"],
|
||||
"properties": {
|
||||
"contract": {
|
||||
"type": "string"
|
||||
},
|
||||
"agentRunbook": {
|
||||
"type": "string"
|
||||
},
|
||||
"prompt": {
|
||||
"type": "string"
|
||||
},
|
||||
"standaloneExecutable": {
|
||||
"type": "string"
|
||||
},
|
||||
"publicThumbprint": {
|
||||
"type": "string"
|
||||
},
|
||||
"sourceRepository": {
|
||||
"type": "string"
|
||||
},
|
||||
"releasePage": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": true
|
||||
},
|
||||
"agentResponsibilities": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
|
||||
86
mrtrust.integration.json
Normal file
86
mrtrust.integration.json
Normal file
@@ -0,0 +1,86 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"name": "MrTrust",
|
||||
"purpose": "Add explicit MrSphay trust onboarding and signing support to Windows target projects.",
|
||||
"sourceRepository": "https://git.wilkensxl.de/MrSphay/MrTrust",
|
||||
"releasePage": "https://git.wilkensxl.de/MrSphay/MrTrust/releases",
|
||||
"userFacingReleaseArtifact": {
|
||||
"fileName": "MrTrust.exe",
|
||||
"releaseZipNamePattern": "MrTrust-<version>.zip",
|
||||
"recommendedBundledPath": "MrTrust\\MrTrust.exe",
|
||||
"distribution": "Bundle this file directly or link to the MrTrust release ZIP.",
|
||||
"launchCommand": ".\\MrTrust\\MrTrust.exe",
|
||||
"removeTrustInstruction": "Open MrTrust and choose Remove trust."
|
||||
},
|
||||
"certificate": {
|
||||
"publisher": "MrSphay",
|
||||
"publicThumbprint": "A024A89200469F099EC4A172B4F96F6428AFD41B",
|
||||
"defaultTrustScope": "CurrentUser",
|
||||
"allUsersTrustScope": "LocalMachine",
|
||||
"privateMaterialPolicy": "Never commit .pfx files, private keys, passwords, tokens, or signing secrets."
|
||||
},
|
||||
"signing": {
|
||||
"supportedExtensions": [
|
||||
".exe",
|
||||
".msi",
|
||||
".dll",
|
||||
".ps1",
|
||||
".psm1",
|
||||
".psd1",
|
||||
".cat"
|
||||
],
|
||||
"ubuntuRunner": {
|
||||
"supportedExtensions": [
|
||||
".exe",
|
||||
".msi",
|
||||
".dll",
|
||||
".cat"
|
||||
],
|
||||
"requiredTool": "osslsigncode",
|
||||
"helperScript": "scripts/Sign-MrTrustProjectLinux.sh",
|
||||
"requiredSecrets": [
|
||||
"MRTRUST_CODESIGN_PFX_BASE64",
|
||||
"MRTRUST_CODESIGN_PFX_PASSWORD"
|
||||
],
|
||||
"preferredCommand": "bash ./MrTrust/scripts/Sign-MrTrustProjectLinux.sh <artifact-path>"
|
||||
},
|
||||
"windowsRunner": {
|
||||
"supportedExtensions": [
|
||||
".exe",
|
||||
".msi",
|
||||
".dll",
|
||||
".ps1",
|
||||
".psm1",
|
||||
".psd1",
|
||||
".cat"
|
||||
],
|
||||
"preferredCommand": ".\\MrTrust\\MrTrust.exe sign -Path <artifact-path> -CertificateThumbprint A024A89200469F099EC4A172B4F96F6428AFD41B"
|
||||
},
|
||||
"preferredCommand": "bash ./MrTrust/scripts/Sign-MrTrustProjectLinux.sh <artifact-path>",
|
||||
"ciGuidance": "On ubuntu-latest runners, sign PE/MSI/CAT artifacts with osslsigncode using a PFX stored in Gitea secrets. Use Windows runners only when signing PowerShell scripts or when Windows certificate store signing is required.",
|
||||
"unsignedBehavior": "Unsigned builds should remain unsigned. MrTrust only makes correctly signed MrSphay artifacts validate after the user has installed trust."
|
||||
},
|
||||
"targetProjectAgentContract": {
|
||||
"modifyTargetProject": true,
|
||||
"requiredOutcomes": [
|
||||
"Expose a visible optional Open MrTrust or trust setup path.",
|
||||
"Link to or bundle the standalone MrTrust.exe.",
|
||||
"Sign Windows release artifacts when the target project produces supported artifact types.",
|
||||
"Document how users install and remove MrTrust trust.",
|
||||
"Verify no private signing material is present in the target repository or release artifacts."
|
||||
],
|
||||
"forbiddenOutcomes": [
|
||||
"Do not silently install certificates.",
|
||||
"Do not bypass Defender, SmartScreen, UAC, firewall, or Windows security policy.",
|
||||
"Do not claim MrTrust makes unsigned software trusted.",
|
||||
"Do not commit private signing material."
|
||||
],
|
||||
"fallbackWhenNoInstallerExists": [
|
||||
"Add release documentation that links to or bundles MrTrust.exe.",
|
||||
"Add a release signing step for supported Windows artifacts.",
|
||||
"Add verification notes explaining that the app starts normally after the user installs MrTrust and the artifact signature validates."
|
||||
]
|
||||
},
|
||||
"agentRunbook": "docs/agent-target-integration.md",
|
||||
"prompt": "docs/integration-prompt.md"
|
||||
}
|
||||
@@ -13,13 +13,41 @@ function Resolve-FullPath {
|
||||
|
||||
$root = Split-Path -Parent (Split-Path -Parent $MyInvocation.MyCommand.Path)
|
||||
$sourcePath = Join-Path $root "src\MrTrustLauncher.cs"
|
||||
$iconPath = Join-Path $root "assets\MrTrust.ico"
|
||||
$resolvedOutputPath = Resolve-FullPath $OutputPath
|
||||
$outputDirectory = Split-Path -Parent $resolvedOutputPath
|
||||
$payloadFiles = @(
|
||||
@{ Path = "MrTrust.ps1"; ResourceName = "MrTrust.Payload.MrTrust.ps1" },
|
||||
@{ Path = "scripts\Build-MrTrustExe.ps1"; ResourceName = "MrTrust.Payload.scripts.Build-MrTrustExe.ps1" },
|
||||
@{ Path = "scripts\Install-MrTrust.ps1"; ResourceName = "MrTrust.Payload.scripts.Install-MrTrust.ps1" },
|
||||
@{ Path = "scripts\New-MrTrustCertificate.ps1"; ResourceName = "MrTrust.Payload.scripts.New-MrTrustCertificate.ps1" },
|
||||
@{ Path = "scripts\New-MrTrustIcon.ps1"; ResourceName = "MrTrust.Payload.scripts.New-MrTrustIcon.ps1" },
|
||||
@{ Path = "scripts\New-MrTrustRelease.ps1"; ResourceName = "MrTrust.Payload.scripts.New-MrTrustRelease.ps1" },
|
||||
@{ Path = "scripts\Sign-MrTrustProject.ps1"; ResourceName = "MrTrust.Payload.scripts.Sign-MrTrustProject.ps1" },
|
||||
@{ Path = "scripts\Sign-MrTrustProjectLinux.sh"; ResourceName = "MrTrust.Payload.scripts.Sign-MrTrustProjectLinux.sh" },
|
||||
@{ Path = "scripts\Start-MrTrustGui.ps1"; ResourceName = "MrTrust.Payload.scripts.Start-MrTrustGui.ps1" },
|
||||
@{ Path = "scripts\Uninstall-MrTrust.ps1"; ResourceName = "MrTrust.Payload.scripts.Uninstall-MrTrust.ps1" },
|
||||
@{ Path = "assets\MrTrust.ico"; ResourceName = "MrTrust.Payload.assets.MrTrust.ico" },
|
||||
@{ Path = "assets\certificates\MrSphay-LocalTrust-Root.cer"; ResourceName = "MrTrust.Payload.assets.certificates.MrSphay-LocalTrust-Root.cer" },
|
||||
@{ Path = "assets\certificates\MrSphay-CodeSigning.cer"; ResourceName = "MrTrust.Payload.assets.certificates.MrSphay-CodeSigning.cer" },
|
||||
@{ Path = "assets\certificates\thumbprints.txt"; ResourceName = "MrTrust.Payload.assets.certificates.thumbprints.txt" }
|
||||
)
|
||||
|
||||
if (-not (Test-Path -LiteralPath $sourcePath)) {
|
||||
throw "Launcher source not found: $sourcePath"
|
||||
}
|
||||
|
||||
if (-not (Test-Path -LiteralPath $iconPath)) {
|
||||
& (Join-Path $root "scripts\New-MrTrustIcon.ps1") -OutputPath $iconPath
|
||||
}
|
||||
|
||||
foreach ($payloadFile in $payloadFiles) {
|
||||
$payloadPath = Join-Path $root $payloadFile.Path
|
||||
if (-not (Test-Path -LiteralPath $payloadPath)) {
|
||||
throw "Payload file not found: $payloadPath"
|
||||
}
|
||||
}
|
||||
|
||||
New-Item -ItemType Directory -Force -Path $outputDirectory | Out-Null
|
||||
|
||||
$compilerCandidates = @(
|
||||
@@ -32,19 +60,29 @@ 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
|
||||
$compilerArguments = @(
|
||||
"/nologo",
|
||||
"/target:winexe",
|
||||
"/optimize+",
|
||||
"/platform:anycpu",
|
||||
"/out:$resolvedOutputPath",
|
||||
"/win32icon:$iconPath",
|
||||
"/reference:System.Windows.Forms.dll",
|
||||
"/reference:System.Drawing.dll"
|
||||
)
|
||||
|
||||
foreach ($payloadFile in $payloadFiles) {
|
||||
$payloadPath = Join-Path $root $payloadFile.Path
|
||||
$compilerArguments += "/resource:$payloadPath,$($payloadFile.ResourceName)"
|
||||
}
|
||||
|
||||
$compilerArguments += $sourcePath
|
||||
|
||||
& $compiler @compilerArguments
|
||||
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "csc.exe failed with exit code $LASTEXITCODE."
|
||||
}
|
||||
|
||||
Write-Host "Created EXE:"
|
||||
Write-Host "Created standalone EXE:"
|
||||
Write-Host " $resolvedOutputPath"
|
||||
|
||||
61
scripts/New-MrTrustIcon.ps1
Normal file
61
scripts/New-MrTrustIcon.ps1
Normal file
@@ -0,0 +1,61 @@
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[string]$OutputPath = ".\assets\MrTrust.ico"
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
function Resolve-FullPath {
|
||||
param([Parameter(Mandatory)][string]$Path)
|
||||
|
||||
$executionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($Path)
|
||||
}
|
||||
|
||||
Add-Type -AssemblyName System.Drawing
|
||||
|
||||
$resolvedOutputPath = Resolve-FullPath $OutputPath
|
||||
$outputDirectory = Split-Path -Parent $resolvedOutputPath
|
||||
New-Item -ItemType Directory -Force -Path $outputDirectory | Out-Null
|
||||
|
||||
$bitmap = [Drawing.Bitmap]::new(64, 64)
|
||||
$graphics = [Drawing.Graphics]::FromImage($bitmap)
|
||||
$graphics.SmoothingMode = [Drawing.Drawing2D.SmoothingMode]::AntiAlias
|
||||
$graphics.Clear([Drawing.Color]::Transparent)
|
||||
|
||||
$backgroundBrush = [Drawing.SolidBrush]::new([Drawing.Color]::FromArgb(27, 32, 35))
|
||||
$accentBrush = [Drawing.SolidBrush]::new([Drawing.Color]::FromArgb(28, 185, 111))
|
||||
$lightBrush = [Drawing.SolidBrush]::new([Drawing.Color]::FromArgb(225, 231, 227))
|
||||
$shadowBrush = [Drawing.SolidBrush]::new([Drawing.Color]::FromArgb(70, 0, 0, 0))
|
||||
$outlinePen = [Drawing.Pen]::new([Drawing.Color]::FromArgb(71, 84, 90), 3)
|
||||
|
||||
$graphics.FillRectangle($shadowBrush, 9, 10, 48, 48)
|
||||
$graphics.FillRectangle($backgroundBrush, 7, 7, 48, 48)
|
||||
$graphics.DrawRectangle($outlinePen, 7, 7, 48, 48)
|
||||
$graphics.FillRectangle($accentBrush, 13, 13, 12, 36)
|
||||
$graphics.FillRectangle($accentBrush, 13, 13, 30, 12)
|
||||
$graphics.FillRectangle($lightBrush, 31, 29, 14, 20)
|
||||
|
||||
$font = [Drawing.Font]::new("Segoe UI", 18, [Drawing.FontStyle]::Bold, [Drawing.GraphicsUnit]::Pixel)
|
||||
$graphics.DrawString("T", $font, $lightBrush, 32, 27)
|
||||
|
||||
$handle = $bitmap.GetHicon()
|
||||
$icon = [Drawing.Icon]::FromHandle($handle)
|
||||
$stream = [IO.File]::Open($resolvedOutputPath, [IO.FileMode]::Create)
|
||||
try {
|
||||
$icon.Save($stream)
|
||||
}
|
||||
finally {
|
||||
$stream.Dispose()
|
||||
$icon.Dispose()
|
||||
$font.Dispose()
|
||||
$outlinePen.Dispose()
|
||||
$shadowBrush.Dispose()
|
||||
$lightBrush.Dispose()
|
||||
$accentBrush.Dispose()
|
||||
$backgroundBrush.Dispose()
|
||||
$graphics.Dispose()
|
||||
$bitmap.Dispose()
|
||||
}
|
||||
|
||||
Write-Host "Created icon:"
|
||||
Write-Host " $resolvedOutputPath"
|
||||
@@ -20,15 +20,17 @@ $output = Resolve-FullPath $OutputDirectory
|
||||
$packageRoot = Join-Path $output "MrTrust-$Version"
|
||||
$zipPath = Join-Path $output "MrTrust-$Version.zip"
|
||||
$exePath = Join-Path $output "MrTrust.exe"
|
||||
$iconPath = Join-Path $root "assets\MrTrust.ico"
|
||||
|
||||
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
|
||||
|
||||
if (-not (Test-Path -LiteralPath $iconPath)) {
|
||||
& (Join-Path $root "scripts\New-MrTrustIcon.ps1") -OutputPath $iconPath
|
||||
}
|
||||
|
||||
& (Join-Path $root "scripts\Build-MrTrustExe.ps1") -OutputPath $exePath
|
||||
|
||||
@@ -50,15 +52,7 @@ if ($SigningThumbprint) {
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
71
scripts/Sign-MrTrustProjectLinux.sh
Normal file
71
scripts/Sign-MrTrustProjectLinux.sh
Normal file
@@ -0,0 +1,71 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
if [ "$#" -lt 1 ]; then
|
||||
echo "Usage: Sign-MrTrustProjectLinux.sh <artifact> [artifact...]" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
if [ -z "${MRTRUST_CODESIGN_PFX_BASE64:-}" ]; then
|
||||
echo "MRTRUST_CODESIGN_PFX_BASE64 is required." >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
if [ -z "${MRTRUST_CODESIGN_PFX_PASSWORD:-}" ]; then
|
||||
echo "MRTRUST_CODESIGN_PFX_PASSWORD is required." >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
if ! command -v osslsigncode >/dev/null 2>&1; then
|
||||
if command -v apt-get >/dev/null 2>&1; then
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
apt-get update
|
||||
apt-get install -y osslsigncode
|
||||
else
|
||||
echo "osslsigncode is not installed and apt-get is unavailable." >&2
|
||||
exit 2
|
||||
fi
|
||||
fi
|
||||
|
||||
work_dir="$(mktemp -d)"
|
||||
trap 'rm -rf "$work_dir"' EXIT
|
||||
|
||||
pfx_path="$work_dir/mrtrust-codesign.pfx"
|
||||
printf '%s' "$MRTRUST_CODESIGN_PFX_BASE64" | base64 -d > "$pfx_path"
|
||||
|
||||
timestamp_url="${MRTRUST_TIMESTAMP_URL:-http://timestamp.digicert.com}"
|
||||
|
||||
for artifact in "$@"; do
|
||||
if [ ! -f "$artifact" ]; then
|
||||
echo "Artifact not found: $artifact" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
case "${artifact##*.}" in
|
||||
exe|EXE|msi|MSI|dll|DLL|cat|CAT)
|
||||
;;
|
||||
*)
|
||||
echo "Unsupported artifact for osslsigncode: $artifact" >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
|
||||
signed_path="$work_dir/$(basename "$artifact").signed"
|
||||
args=(
|
||||
sign
|
||||
-pkcs12 "$pfx_path"
|
||||
-pass "$MRTRUST_CODESIGN_PFX_PASSWORD"
|
||||
-n "MrSphay"
|
||||
-i "https://git.wilkensxl.de/MrSphay"
|
||||
-in "$artifact"
|
||||
-out "$signed_path"
|
||||
)
|
||||
|
||||
if [ -n "$timestamp_url" ]; then
|
||||
args+=( -t "$timestamp_url" )
|
||||
fi
|
||||
|
||||
osslsigncode "${args[@]}"
|
||||
mv "$signed_path" "$artifact"
|
||||
echo "Signed $artifact"
|
||||
done
|
||||
@@ -9,6 +9,26 @@ 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"
|
||||
$script:IconPath = Join-Path $script:RootPath "assets\MrTrust.ico"
|
||||
$script:SelectedFilePath = $null
|
||||
$script:CurrentAccent = 0
|
||||
|
||||
$colors = @{
|
||||
Background = [Drawing.Color]::FromArgb(15, 20, 20)
|
||||
Shell = [Drawing.Color]::FromArgb(20, 27, 27)
|
||||
Panel = [Drawing.Color]::FromArgb(27, 36, 35)
|
||||
PanelAlt = [Drawing.Color]::FromArgb(34, 45, 43)
|
||||
PanelSelected = [Drawing.Color]::FromArgb(31, 64, 49)
|
||||
Border = [Drawing.Color]::FromArgb(53, 66, 64)
|
||||
Text = [Drawing.Color]::FromArgb(239, 244, 241)
|
||||
Muted = [Drawing.Color]::FromArgb(157, 172, 166)
|
||||
Green = [Drawing.Color]::FromArgb(0, 175, 91)
|
||||
GreenHover = [Drawing.Color]::FromArgb(0, 199, 104)
|
||||
GreenSoft = [Drawing.Color]::FromArgb(34, 76, 52)
|
||||
Orange = [Drawing.Color]::FromArgb(242, 153, 74)
|
||||
Red = [Drawing.Color]::FromArgb(235, 87, 87)
|
||||
Blue = [Drawing.Color]::FromArgb(82, 166, 255)
|
||||
}
|
||||
|
||||
function Test-IsAdministrator {
|
||||
$identity = [Security.Principal.WindowsIdentity]::GetCurrent()
|
||||
@@ -27,12 +47,7 @@ function Get-MrTrustCertificate {
|
||||
}
|
||||
|
||||
function Get-TrustScope {
|
||||
if ($script:AllUsersCheckBox.Checked) {
|
||||
"LocalMachine"
|
||||
}
|
||||
else {
|
||||
"CurrentUser"
|
||||
}
|
||||
if ($script:AllUsersCheckBox.Checked) { "LocalMachine" } else { "CurrentUser" }
|
||||
}
|
||||
|
||||
function Get-StorePath {
|
||||
@@ -55,14 +70,153 @@ function Test-CertificateInstalled {
|
||||
@(Get-ChildItem -Path $storePath | Where-Object Thumbprint -eq $Certificate.Thumbprint).Count -gt 0
|
||||
}
|
||||
|
||||
function Set-StatusText {
|
||||
param([Parameter(Mandatory)][string]$Text)
|
||||
function Set-Busy {
|
||||
param([bool]$Busy)
|
||||
|
||||
$script:StatusLabel.Text = $Text
|
||||
$script:ProgressBar.Visible = $Busy
|
||||
if ($Busy) {
|
||||
$script:ProgressBar.Style = "Marquee"
|
||||
}
|
||||
}
|
||||
|
||||
function Refresh-MrTrustStatus {
|
||||
function Set-StatusText {
|
||||
param(
|
||||
[Parameter(Mandatory)][string]$Text,
|
||||
[Parameter(Mandatory)][Drawing.Color]$Color
|
||||
)
|
||||
|
||||
$script:StatusLabel.Text = $Text
|
||||
$script:StatusPill.BackColor = $Color
|
||||
}
|
||||
|
||||
function Add-AnimatedButton {
|
||||
param(
|
||||
[Parameter(Mandatory)][Windows.Forms.Button]$Button,
|
||||
[Parameter(Mandatory)][Drawing.Color]$Normal,
|
||||
[Parameter(Mandatory)][Drawing.Color]$Hover
|
||||
)
|
||||
|
||||
$normalColor = $Normal
|
||||
$hoverColor = $Hover
|
||||
$textColor = $colors.Text
|
||||
$borderColor = $colors.Border
|
||||
|
||||
$Button.FlatStyle = "Flat"
|
||||
$Button.FlatAppearance.BorderColor = $borderColor
|
||||
$Button.FlatAppearance.BorderSize = 1
|
||||
$Button.BackColor = $normalColor
|
||||
$Button.ForeColor = $textColor
|
||||
$Button.Cursor = [Windows.Forms.Cursors]::Hand
|
||||
$Button.Add_MouseEnter({ param($sender, $eventArgs) $sender.BackColor = $hoverColor }.GetNewClosure())
|
||||
$Button.Add_MouseLeave({ param($sender, $eventArgs) $sender.BackColor = $normalColor }.GetNewClosure())
|
||||
}
|
||||
|
||||
function New-Label {
|
||||
param(
|
||||
[string]$Text,
|
||||
[int]$X,
|
||||
[int]$Y,
|
||||
[int]$Width = 220,
|
||||
[int]$Height = 24,
|
||||
[Drawing.Color]$Color = $colors.Muted,
|
||||
[Drawing.Font]$Font = $null
|
||||
)
|
||||
|
||||
$label = [Windows.Forms.Label]::new()
|
||||
$label.Text = $Text
|
||||
$label.Location = [Drawing.Point]::new($X, $Y)
|
||||
$label.Size = [Drawing.Size]::new($Width, $Height)
|
||||
$label.ForeColor = $Color
|
||||
if ($Font) { $label.Font = $Font }
|
||||
$label
|
||||
}
|
||||
|
||||
function New-Card {
|
||||
param(
|
||||
[int]$X,
|
||||
[int]$Y,
|
||||
[int]$Width,
|
||||
[int]$Height,
|
||||
[Drawing.Color]$BackColor = $colors.Panel
|
||||
)
|
||||
|
||||
$panel = [Windows.Forms.Panel]::new()
|
||||
$panel.Location = [Drawing.Point]::new($X, $Y)
|
||||
$panel.Size = [Drawing.Size]::new($Width, $Height)
|
||||
$panel.BackColor = $BackColor
|
||||
$panel.BorderStyle = "FixedSingle"
|
||||
$panel.Anchor = "Top,Left,Right"
|
||||
$panel
|
||||
}
|
||||
|
||||
function New-PageTitle {
|
||||
param(
|
||||
[string]$Title,
|
||||
[string]$Description
|
||||
)
|
||||
|
||||
$titleLabel = New-Label -Text $Title -X 0 -Y 0 -Width 760 -Height 34 -Color $colors.Text -Font ([Drawing.Font]::new("Segoe UI", 15, [Drawing.FontStyle]::Bold))
|
||||
$descriptionLabel = New-Label -Text $Description -X 1 -Y 38 -Width 850 -Height 24 -Color $colors.Muted
|
||||
[pscustomobject]@{
|
||||
Title = $titleLabel
|
||||
Description = $descriptionLabel
|
||||
}
|
||||
}
|
||||
|
||||
function New-NavButton {
|
||||
param(
|
||||
[Parameter(Mandatory)][string]$Key,
|
||||
[Parameter(Mandatory)][string]$Text,
|
||||
[Parameter(Mandatory)][int]$Y
|
||||
)
|
||||
|
||||
$button = [Windows.Forms.Button]::new()
|
||||
$button.Text = $Text
|
||||
$button.Tag = $Key
|
||||
$button.Size = [Drawing.Size]::new(174, 44)
|
||||
$button.Location = [Drawing.Point]::new(22, $Y)
|
||||
$button.TextAlign = "MiddleLeft"
|
||||
$button.Padding = [Windows.Forms.Padding]::new(14, 0, 0, 0)
|
||||
$button.Font = [Drawing.Font]::new("Segoe UI", 10, [Drawing.FontStyle]::Bold)
|
||||
$button.FlatStyle = "Flat"
|
||||
$button.FlatAppearance.BorderSize = 1
|
||||
$button.FlatAppearance.BorderColor = $colors.Shell
|
||||
$button.BackColor = $colors.Shell
|
||||
$button.ForeColor = $colors.Muted
|
||||
$button.Cursor = [Windows.Forms.Cursors]::Hand
|
||||
$button.Add_MouseEnter({ param($sender, $eventArgs) if ($script:ActivePage -ne $sender.Tag) { $sender.BackColor = $colors.PanelAlt } }.GetNewClosure())
|
||||
$button.Add_MouseLeave({ param($sender, $eventArgs) if ($script:ActivePage -ne $sender.Tag) { $sender.BackColor = $colors.Shell } }.GetNewClosure())
|
||||
$button.Add_Click({ param($sender, $eventArgs) Show-MrTrustPage -Key $sender.Tag })
|
||||
$button
|
||||
}
|
||||
|
||||
function Show-MrTrustPage {
|
||||
param([Parameter(Mandatory)][string]$Key)
|
||||
|
||||
$script:ActivePage = $Key
|
||||
|
||||
foreach ($pageKey in $script:Pages.Keys) {
|
||||
$script:Pages[$pageKey].Visible = $pageKey -eq $Key
|
||||
}
|
||||
|
||||
foreach ($buttonKey in $script:NavButtons.Keys) {
|
||||
$button = $script:NavButtons[$buttonKey]
|
||||
if ($buttonKey -eq $Key) {
|
||||
$button.BackColor = $colors.PanelSelected
|
||||
$button.FlatAppearance.BorderColor = $colors.Green
|
||||
$button.ForeColor = $colors.Text
|
||||
}
|
||||
else {
|
||||
$button.BackColor = $colors.Shell
|
||||
$button.FlatAppearance.BorderColor = $colors.Shell
|
||||
$button.ForeColor = $colors.Muted
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function Update-TrustStatus {
|
||||
try {
|
||||
Set-Busy $true
|
||||
$rootCertificate = Get-MrTrustCertificate -Path $script:RootCertificatePath
|
||||
$publisherCertificate = Get-MrTrustCertificate -Path $script:PublisherCertificatePath
|
||||
$scope = Get-TrustScope
|
||||
@@ -73,79 +227,58 @@ function Refresh-MrTrustStatus {
|
||||
$script:RootThumbprintLabel.Text = $rootCertificate.Thumbprint
|
||||
$script:PublisherThumbprintLabel.Text = $publisherCertificate.Thumbprint
|
||||
$script:ExpiryLabel.Text = $rootCertificate.NotAfter.ToString("yyyy-MM-dd")
|
||||
$script:ScopeValueLabel.Text = $scope
|
||||
|
||||
if ($rootInstalled -and $publisherInstalled) {
|
||||
Set-StatusText "Trusted for $scope"
|
||||
$script:StatusPill.BackColor = [Drawing.Color]::FromArgb(28, 185, 111)
|
||||
Set-StatusText -Text "Trusted" -Color $colors.Green
|
||||
$script:TrustSummaryLabel.Text = "MrSphay public trust is installed for $scope."
|
||||
}
|
||||
else {
|
||||
Set-StatusText "Not installed for $scope"
|
||||
$script:StatusPill.BackColor = [Drawing.Color]::FromArgb(242, 153, 74)
|
||||
Set-StatusText -Text "Not installed" -Color $colors.Orange
|
||||
$script:TrustSummaryLabel.Text = "Trust is not fully installed for $scope."
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Set-StatusText $_.Exception.Message
|
||||
$script:StatusPill.BackColor = [Drawing.Color]::FromArgb(235, 87, 87)
|
||||
Set-StatusText -Text "Error" -Color $colors.Red
|
||||
$script:TrustSummaryLabel.Text = $_.Exception.Message
|
||||
}
|
||||
finally {
|
||||
Set-Busy $false
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
[Windows.Forms.MessageBox]::Show("All-users trust requires running MrTrust as Administrator.", "MrTrust", "OK", "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`nThis does not disable Defender or SmartScreen."
|
||||
$result = [Windows.Forms.MessageBox]::Show($message, "Install MrTrust", "YesNo", "Warning")
|
||||
if ($result -ne [Windows.Forms.DialogResult]::Yes) { return }
|
||||
|
||||
$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
|
||||
}
|
||||
|
||||
Set-Busy $true
|
||||
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
|
||||
Update-TrustStatus
|
||||
}
|
||||
|
||||
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
|
||||
[Windows.Forms.MessageBox]::Show("All-users removal requires running MrTrust as Administrator.", "MrTrust", "OK", "Warning") | Out-Null
|
||||
return
|
||||
}
|
||||
|
||||
$result = [Windows.Forms.MessageBox]::Show("Remove MrSphay trust for $scope?", "Remove MrTrust", "YesNo", "Question")
|
||||
if ($result -ne [Windows.Forms.DialogResult]::Yes) { return }
|
||||
|
||||
Set-Busy $true
|
||||
$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 }
|
||||
@@ -158,7 +291,86 @@ function Remove-MrTrustCertificates {
|
||||
Remove-Item
|
||||
}
|
||||
|
||||
Refresh-MrTrustStatus
|
||||
Update-TrustStatus
|
||||
}
|
||||
|
||||
function Test-MarkOfTheWeb {
|
||||
param([Parameter(Mandatory)][string]$Path)
|
||||
|
||||
try {
|
||||
$stream = Get-Content -LiteralPath $Path -Stream Zone.Identifier -ErrorAction Stop
|
||||
($stream -join "`n") -match "ZoneId\s*=\s*[3-4]"
|
||||
}
|
||||
catch {
|
||||
$false
|
||||
}
|
||||
}
|
||||
|
||||
function Get-SmartScreenExplanation {
|
||||
param(
|
||||
[bool]$SignedByMrSphay,
|
||||
[bool]$HasMotw
|
||||
)
|
||||
|
||||
if ($SignedByMrSphay -and $HasMotw) {
|
||||
"Publisher trust can be valid while SmartScreen still warns because the downloaded file has Internet origin and low Microsoft reputation."
|
||||
}
|
||||
elseif ($SignedByMrSphay) {
|
||||
"The publisher matches MrSphay. SmartScreen may still warn until Microsoft reputation builds for this exact app and publisher."
|
||||
}
|
||||
elseif ($HasMotw) {
|
||||
"This file came from the Internet and is not signed by MrSphay. SmartScreen warnings are expected."
|
||||
}
|
||||
else {
|
||||
"MrTrust can only help with MrSphay-signed files. SmartScreen reputation is separate from local certificate trust."
|
||||
}
|
||||
}
|
||||
|
||||
function Test-SelectedFile {
|
||||
param([Parameter(Mandatory)][string]$Path)
|
||||
|
||||
Set-Busy $true
|
||||
try {
|
||||
if (-not (Test-Path -LiteralPath $Path -PathType Leaf)) {
|
||||
throw "File not found: $Path"
|
||||
}
|
||||
|
||||
$signature = Get-AuthenticodeSignature -LiteralPath $Path
|
||||
$hasMotw = Test-MarkOfTheWeb -Path $Path
|
||||
$signer = $signature.SignerCertificate
|
||||
$signedByMrSphay = $false
|
||||
if ($signer) {
|
||||
$signedByMrSphay = $signer.Thumbprint -eq "A024A89200469F099EC4A172B4F96F6428AFD41B" -or $signer.Subject -like "*MrSphay*"
|
||||
}
|
||||
|
||||
$script:FileNameLabel.Text = [IO.Path]::GetFileName($Path)
|
||||
$script:FilePathLabel.Text = $Path
|
||||
$script:SignatureStatusLabel.Text = "$($signature.Status)"
|
||||
$script:SignerLabel.Text = if ($signer) { $signer.Subject } else { "No signer certificate" }
|
||||
$script:MrSphayMatchLabel.Text = if ($signedByMrSphay) { "Yes" } else { "No" }
|
||||
$script:MotwLabel.Text = if ($hasMotw) { "Yes" } else { "No" }
|
||||
$script:SmartScreenLabel.Text = Get-SmartScreenExplanation -SignedByMrSphay $signedByMrSphay -HasMotw $hasMotw
|
||||
|
||||
if ($signedByMrSphay -and $signature.Status -eq "Valid") {
|
||||
$script:FileVerdictLabel.Text = "Looks good: signed by MrSphay and locally valid."
|
||||
$script:FileVerdictLabel.ForeColor = $colors.Green
|
||||
}
|
||||
elseif ($signedByMrSphay) {
|
||||
$script:FileVerdictLabel.Text = "Signed by MrSphay, but local validation is not fully valid: $($signature.Status)"
|
||||
$script:FileVerdictLabel.ForeColor = $colors.Orange
|
||||
}
|
||||
else {
|
||||
$script:FileVerdictLabel.Text = "Not a MrSphay-signed file."
|
||||
$script:FileVerdictLabel.ForeColor = $colors.Red
|
||||
}
|
||||
}
|
||||
catch {
|
||||
$script:FileVerdictLabel.Text = $_.Exception.Message
|
||||
$script:FileVerdictLabel.ForeColor = $colors.Red
|
||||
}
|
||||
finally {
|
||||
Set-Busy $false
|
||||
}
|
||||
}
|
||||
|
||||
[Windows.Forms.Application]::EnableVisualStyles()
|
||||
@@ -166,159 +378,295 @@ function Remove-MrTrustCertificates {
|
||||
$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.ClientSize = [Drawing.Size]::new(1120, 720)
|
||||
$form.MinimumSize = [Drawing.Size]::new(1040, 680)
|
||||
$form.BackColor = $colors.Background
|
||||
$form.Font = [Drawing.Font]::new("Segoe UI", 10)
|
||||
if (Test-Path -LiteralPath $script:IconPath) {
|
||||
$form.Icon = [Drawing.Icon]::new($script:IconPath)
|
||||
}
|
||||
|
||||
$header = [Windows.Forms.Panel]::new()
|
||||
$header.Dock = "Top"
|
||||
$header.Height = 108
|
||||
$header.BackColor = [Drawing.Color]::FromArgb(27, 32, 35)
|
||||
$header.Height = 96
|
||||
$header.BackColor = $colors.Background
|
||||
$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)
|
||||
$brandLine = [Windows.Forms.Panel]::new()
|
||||
$brandLine.Dock = "Bottom"
|
||||
$brandLine.Height = 1
|
||||
$brandLine.BackColor = $colors.Border
|
||||
$header.Controls.Add($brandLine)
|
||||
|
||||
$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)
|
||||
$logoBox = [Windows.Forms.PictureBox]::new()
|
||||
$logoBox.Size = [Drawing.Size]::new(42, 42)
|
||||
$logoBox.Location = [Drawing.Point]::new(28, 26)
|
||||
$logoBox.SizeMode = "StretchImage"
|
||||
if (Test-Path -LiteralPath $script:IconPath) {
|
||||
$logoBox.Image = [Drawing.Icon]::new($script:IconPath).ToBitmap()
|
||||
}
|
||||
$header.Controls.Add($logoBox)
|
||||
|
||||
$title = New-Label -Text "MrTrust" -X 86 -Y 20 -Width 260 -Height 36 -Color $colors.Text -Font ([Drawing.Font]::new("Segoe UI", 18, [Drawing.FontStyle]::Bold))
|
||||
$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)
|
||||
$subtitle = New-Label -Text "Local certificate trust for MrSphay signed Windows apps" -X 88 -Y 56 -Width 520 -Height 24 -Color $colors.Muted
|
||||
$header.Controls.Add($subtitle)
|
||||
|
||||
$statusCard = [Windows.Forms.Panel]::new()
|
||||
$statusCard.Anchor = "Top,Right"
|
||||
$statusCard.Location = [Drawing.Point]::new(780, 20)
|
||||
$statusCard.Size = [Drawing.Size]::new(300, 56)
|
||||
$statusCard.BackColor = $colors.Panel
|
||||
$statusCard.BorderStyle = "FixedSingle"
|
||||
$header.Controls.Add($statusCard)
|
||||
|
||||
$statusText = New-Label -Text "Trust status" -X 18 -Y 7 -Width 130 -Height 20 -Color $colors.Muted -Font ([Drawing.Font]::new("Segoe UI", 8.5))
|
||||
$statusCard.Controls.Add($statusText)
|
||||
|
||||
$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:StatusPill.Location = [Drawing.Point]::new(18, 31)
|
||||
$script:StatusPill.BackColor = $colors.Orange
|
||||
$statusCard.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)
|
||||
$script:StatusLabel = New-Label -Text "Checking..." -X 40 -Y 26 -Width 210 -Height 24 -Color $colors.Text -Font ([Drawing.Font]::new("Segoe UI", 10, [Drawing.FontStyle]::Bold))
|
||||
$script:StatusLabel.AutoEllipsis = $true
|
||||
$statusCard.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)
|
||||
$script:ProgressBar = [Windows.Forms.ProgressBar]::new()
|
||||
$script:ProgressBar.Anchor = "Top,Left,Right"
|
||||
$script:ProgressBar.Location = [Drawing.Point]::new(28, 86)
|
||||
$script:ProgressBar.Size = [Drawing.Size]::new(1052, 4)
|
||||
$script:ProgressBar.Visible = $false
|
||||
$header.Controls.Add($script:ProgressBar)
|
||||
|
||||
$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)
|
||||
$shell = [Windows.Forms.Panel]::new()
|
||||
$shell.Dock = "Fill"
|
||||
$shell.BackColor = $colors.Background
|
||||
$form.Controls.Add($shell)
|
||||
|
||||
$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)
|
||||
$sidebar = [Windows.Forms.Panel]::new()
|
||||
$sidebar.Dock = "Left"
|
||||
$sidebar.Width = 220
|
||||
$sidebar.BackColor = $colors.Shell
|
||||
$shell.Controls.Add($sidebar)
|
||||
|
||||
$sideAccent = [Windows.Forms.Panel]::new()
|
||||
$sideAccent.Dock = "Left"
|
||||
$sideAccent.Width = 4
|
||||
$sideAccent.BackColor = $colors.Green
|
||||
$sidebar.Controls.Add($sideAccent)
|
||||
|
||||
$navTitle = New-Label -Text "Navigation" -X 24 -Y 26 -Width 160 -Height 22 -Color $colors.Muted -Font ([Drawing.Font]::new("Segoe UI", 8.5, [Drawing.FontStyle]::Bold))
|
||||
$sidebar.Controls.Add($navTitle)
|
||||
|
||||
$script:NavButtons = @{}
|
||||
$script:NavButtons.Trust = New-NavButton -Key "Trust" -Text "Trust" -Y 58
|
||||
$script:NavButtons.Diagnostics = New-NavButton -Key "Diagnostics" -Text "File scan" -Y 110
|
||||
$script:NavButtons.SmartScreen = New-NavButton -Key "SmartScreen" -Text "SmartScreen" -Y 162
|
||||
foreach ($navButton in $script:NavButtons.Values) {
|
||||
$sidebar.Controls.Add($navButton)
|
||||
}
|
||||
|
||||
$sideNote = New-Label -Text "Installs public certificates only. No Defender, SmartScreen, UAC, firewall, or policy bypasses." -X 24 -Y 510 -Width 164 -Height 96 -Color $colors.Muted -Font ([Drawing.Font]::new("Segoe UI", 8.5))
|
||||
$sideNote.Anchor = "Left,Bottom"
|
||||
$sidebar.Controls.Add($sideNote)
|
||||
|
||||
$contentHost = [Windows.Forms.Panel]::new()
|
||||
$contentHost.Dock = "Fill"
|
||||
$contentHost.BackColor = $colors.Background
|
||||
$contentHost.Padding = [Windows.Forms.Padding]::new(34, 30, 34, 30)
|
||||
$shell.Controls.Add($contentHost)
|
||||
|
||||
$script:Pages = @{}
|
||||
|
||||
$trustPage = [Windows.Forms.Panel]::new()
|
||||
$trustPage.Dock = "Fill"
|
||||
$trustPage.BackColor = $colors.Background
|
||||
$contentHost.Controls.Add($trustPage)
|
||||
$script:Pages.Trust = $trustPage
|
||||
|
||||
$trustTitle = New-PageTitle -Title "Trust" -Description "Install, remove, and verify the MrSphay public trust certificates."
|
||||
$trustPage.Controls.Add($trustTitle.Title)
|
||||
$trustPage.Controls.Add($trustTitle.Description)
|
||||
|
||||
$trustPanel = New-Card -X 0 -Y 82 -Width 820 -Height 236
|
||||
$trustPage.Controls.Add($trustPanel)
|
||||
|
||||
$scopeLabel = New-Label -Text "Scope" -X 24 -Y 22 -Width 160 -Height 22 -Color $colors.Muted -Font ([Drawing.Font]::new("Segoe UI", 8.5, [Drawing.FontStyle]::Bold))
|
||||
$trustPanel.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.Text = "All users (LocalMachine, requires Administrator)"
|
||||
$script:AllUsersCheckBox.ForeColor = $colors.Text
|
||||
$script:AllUsersCheckBox.Location = [Drawing.Point]::new(24, 46)
|
||||
$script:AllUsersCheckBox.AutoSize = $true
|
||||
$script:AllUsersCheckBox.FlatStyle = "Flat"
|
||||
$script:AllUsersCheckBox.Add_CheckedChanged({ Refresh-MrTrustStatus })
|
||||
$infoPanel.Controls.Add($script:AllUsersCheckBox)
|
||||
$script:AllUsersCheckBox.Add_CheckedChanged({ Update-TrustStatus })
|
||||
$trustPanel.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)
|
||||
$trustPanel.Controls.Add((New-Label -Text "Root thumbprint" -X 24 -Y 90 -Width 170 -Height 24))
|
||||
$script:RootThumbprintLabel = New-Label -Text "-" -X 210 -Y 90 -Width 560 -Height 24 -Color $colors.Text -Font ([Drawing.Font]::new("Consolas", 9))
|
||||
$script:RootThumbprintLabel.AutoEllipsis = $true
|
||||
$trustPanel.Controls.Add($script:RootThumbprintLabel)
|
||||
|
||||
$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)
|
||||
$trustPanel.Controls.Add((New-Label -Text "Publisher thumbprint" -X 24 -Y 124 -Width 170 -Height 24))
|
||||
$script:PublisherThumbprintLabel = New-Label -Text "-" -X 210 -Y 124 -Width 560 -Height 24 -Color $colors.Text -Font ([Drawing.Font]::new("Consolas", 9))
|
||||
$script:PublisherThumbprintLabel.AutoEllipsis = $true
|
||||
$trustPanel.Controls.Add($script:PublisherThumbprintLabel)
|
||||
|
||||
$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)
|
||||
$trustPanel.Controls.Add((New-Label -Text "Expires" -X 24 -Y 158 -Width 170 -Height 24))
|
||||
$script:ExpiryLabel = New-Label -Text "-" -X 210 -Y 158 -Width 180 -Height 24 -Color $colors.Text
|
||||
$trustPanel.Controls.Add($script:ExpiryLabel)
|
||||
|
||||
$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)
|
||||
$trustPanel.Controls.Add((New-Label -Text "Active scope" -X 24 -Y 192 -Width 170 -Height 24))
|
||||
$script:ScopeValueLabel = New-Label -Text "-" -X 210 -Y 192 -Width 200 -Height 24 -Color $colors.Text
|
||||
$trustPanel.Controls.Add($script:ScopeValueLabel)
|
||||
|
||||
$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)
|
||||
$summaryPanel = New-Card -X 0 -Y 338 -Width 820 -Height 76 -BackColor $colors.PanelAlt
|
||||
$trustPage.Controls.Add($summaryPanel)
|
||||
|
||||
$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)
|
||||
$script:TrustSummaryLabel = New-Label -Text "Checking trust state..." -X 22 -Y 18 -Width 760 -Height 34 -Color $colors.Text -Font ([Drawing.Font]::new("Segoe UI", 10, [Drawing.FontStyle]::Bold))
|
||||
$summaryPanel.Controls.Add($script:TrustSummaryLabel)
|
||||
|
||||
$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.Size = [Drawing.Size]::new(160, 44)
|
||||
$installButton.Location = [Drawing.Point]::new(0, 438)
|
||||
Add-AnimatedButton -Button $installButton -Normal $colors.Green -Hover $colors.GreenHover
|
||||
$installButton.Add_Click({ Install-MrTrustCertificates })
|
||||
$content.Controls.Add($installButton)
|
||||
$trustPage.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.Size = [Drawing.Size]::new(160, 44)
|
||||
$removeButton.Location = [Drawing.Point]::new(176, 438)
|
||||
Add-AnimatedButton -Button $removeButton -Normal $colors.PanelAlt -Hover $colors.Border
|
||||
$removeButton.Add_Click({ Remove-MrTrustCertificates })
|
||||
$content.Controls.Add($removeButton)
|
||||
$trustPage.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)
|
||||
$refreshButton.Size = [Drawing.Size]::new(124, 44)
|
||||
$refreshButton.Location = [Drawing.Point]::new(352, 438)
|
||||
Add-AnimatedButton -Button $refreshButton -Normal $colors.PanelAlt -Hover $colors.Border
|
||||
$refreshButton.Add_Click({ Update-TrustStatus })
|
||||
$trustPage.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)
|
||||
$note = New-Label -Text "Trust actions are reversible and scoped to the selected Windows certificate store." -X 0 -Y 508 -Width 820 -Height 26 -Color $colors.Muted
|
||||
$trustPage.Controls.Add($note)
|
||||
|
||||
$form.Add_Shown({ Refresh-MrTrustStatus })
|
||||
$diagnosticsPage = [Windows.Forms.Panel]::new()
|
||||
$diagnosticsPage.Dock = "Fill"
|
||||
$diagnosticsPage.BackColor = $colors.Background
|
||||
$diagnosticsPage.Visible = $false
|
||||
$contentHost.Controls.Add($diagnosticsPage)
|
||||
$script:Pages.Diagnostics = $diagnosticsPage
|
||||
|
||||
$diagnosticsTitle = New-PageTitle -Title "File scan" -Description "Inspect a Windows executable, installer, catalog, or DLL for signature and origin signals."
|
||||
$diagnosticsPage.Controls.Add($diagnosticsTitle.Title)
|
||||
$diagnosticsPage.Controls.Add($diagnosticsTitle.Description)
|
||||
|
||||
$filePanel = New-Card -X 0 -Y 82 -Width 820 -Height 420
|
||||
$diagnosticsPage.Controls.Add($filePanel)
|
||||
|
||||
$chooseButton = [Windows.Forms.Button]::new()
|
||||
$chooseButton.Text = "Choose file"
|
||||
$chooseButton.Size = [Drawing.Size]::new(150, 42)
|
||||
$chooseButton.Location = [Drawing.Point]::new(24, 24)
|
||||
Add-AnimatedButton -Button $chooseButton -Normal $colors.Green -Hover $colors.GreenHover
|
||||
$filePanel.Controls.Add($chooseButton)
|
||||
|
||||
$scanButton = [Windows.Forms.Button]::new()
|
||||
$scanButton.Text = "Scan again"
|
||||
$scanButton.Size = [Drawing.Size]::new(130, 42)
|
||||
$scanButton.Location = [Drawing.Point]::new(190, 24)
|
||||
Add-AnimatedButton -Button $scanButton -Normal $colors.PanelAlt -Hover $colors.Border
|
||||
$filePanel.Controls.Add($scanButton)
|
||||
|
||||
$script:FileVerdictLabel = New-Label -Text "Choose a Windows app or installer to inspect." -X 24 -Y 88 -Width 760 -Height 30 -Color $colors.Muted -Font ([Drawing.Font]::new("Segoe UI", 11, [Drawing.FontStyle]::Bold))
|
||||
$filePanel.Controls.Add($script:FileVerdictLabel)
|
||||
|
||||
$filePanel.Controls.Add((New-Label -Text "File" -X 24 -Y 136 -Width 170 -Height 24))
|
||||
$script:FileNameLabel = New-Label -Text "-" -X 210 -Y 136 -Width 560 -Height 24 -Color $colors.Text
|
||||
$script:FileNameLabel.AutoEllipsis = $true
|
||||
$filePanel.Controls.Add($script:FileNameLabel)
|
||||
|
||||
$filePanel.Controls.Add((New-Label -Text "Path" -X 24 -Y 170 -Width 170 -Height 24))
|
||||
$script:FilePathLabel = New-Label -Text "-" -X 210 -Y 170 -Width 560 -Height 40 -Color $colors.Text
|
||||
$script:FilePathLabel.AutoEllipsis = $true
|
||||
$filePanel.Controls.Add($script:FilePathLabel)
|
||||
|
||||
$filePanel.Controls.Add((New-Label -Text "Signature status" -X 24 -Y 222 -Width 170 -Height 24))
|
||||
$script:SignatureStatusLabel = New-Label -Text "-" -X 210 -Y 222 -Width 560 -Height 24 -Color $colors.Text
|
||||
$filePanel.Controls.Add($script:SignatureStatusLabel)
|
||||
|
||||
$filePanel.Controls.Add((New-Label -Text "Signer" -X 24 -Y 256 -Width 170 -Height 24))
|
||||
$script:SignerLabel = New-Label -Text "-" -X 210 -Y 256 -Width 560 -Height 36 -Color $colors.Text
|
||||
$script:SignerLabel.AutoEllipsis = $true
|
||||
$filePanel.Controls.Add($script:SignerLabel)
|
||||
|
||||
$filePanel.Controls.Add((New-Label -Text "MrSphay match" -X 24 -Y 306 -Width 170 -Height 24))
|
||||
$script:MrSphayMatchLabel = New-Label -Text "-" -X 210 -Y 306 -Width 160 -Height 24 -Color $colors.Text
|
||||
$filePanel.Controls.Add($script:MrSphayMatchLabel)
|
||||
|
||||
$filePanel.Controls.Add((New-Label -Text "Mark-of-the-Web" -X 390 -Y 306 -Width 160 -Height 24))
|
||||
$script:MotwLabel = New-Label -Text "-" -X 560 -Y 306 -Width 160 -Height 24 -Color $colors.Text
|
||||
$filePanel.Controls.Add($script:MotwLabel)
|
||||
|
||||
$filePanel.Controls.Add((New-Label -Text "SmartScreen note" -X 24 -Y 352 -Width 170 -Height 24))
|
||||
$script:SmartScreenLabel = New-Label -Text "-" -X 210 -Y 352 -Width 560 -Height 48 -Color $colors.Muted
|
||||
$filePanel.Controls.Add($script:SmartScreenLabel)
|
||||
|
||||
$chooseButton.Add_Click({
|
||||
$dialog = [Windows.Forms.OpenFileDialog]::new()
|
||||
$dialog.Filter = "Windows apps and installers (*.exe;*.msi;*.dll;*.cat)|*.exe;*.msi;*.dll;*.cat|All files (*.*)|*.*"
|
||||
if ($dialog.ShowDialog() -eq [Windows.Forms.DialogResult]::OK) {
|
||||
$script:SelectedFilePath = $dialog.FileName
|
||||
Test-SelectedFile -Path $script:SelectedFilePath
|
||||
}
|
||||
})
|
||||
$scanButton.Add_Click({
|
||||
if ($script:SelectedFilePath) {
|
||||
Test-SelectedFile -Path $script:SelectedFilePath
|
||||
}
|
||||
})
|
||||
|
||||
$helpPage = [Windows.Forms.Panel]::new()
|
||||
$helpPage.Dock = "Fill"
|
||||
$helpPage.BackColor = $colors.Background
|
||||
$helpPage.Visible = $false
|
||||
$contentHost.Controls.Add($helpPage)
|
||||
$script:Pages.SmartScreen = $helpPage
|
||||
|
||||
$helpTitle = New-PageTitle -Title "SmartScreen" -Description "Understand what MrTrust can verify and what Windows reputation still controls."
|
||||
$helpPage.Controls.Add($helpTitle.Title)
|
||||
$helpPage.Controls.Add($helpTitle.Description)
|
||||
|
||||
$helpPanel = New-Card -X 0 -Y 82 -Width 820 -Height 330
|
||||
$helpPage.Controls.Add($helpPanel)
|
||||
|
||||
$helpPanel.Controls.Add((New-Label -Text "Local trust" -X 24 -Y 24 -Width 180 -Height 24 -Color $colors.Text -Font ([Drawing.Font]::new("Segoe UI", 11, [Drawing.FontStyle]::Bold))))
|
||||
$helpPanel.Controls.Add((New-Label -Text "MrTrust installs the public MrSphay root and publisher certificates into the selected Windows certificate stores." -X 24 -Y 54 -Width 740 -Height 42 -Color $colors.Muted))
|
||||
|
||||
$helpPanel.Controls.Add((New-Label -Text "Windows reputation" -X 24 -Y 118 -Width 220 -Height 24 -Color $colors.Text -Font ([Drawing.Font]::new("Segoe UI", 11, [Drawing.FontStyle]::Bold))))
|
||||
$helpPanel.Controls.Add((New-Label -Text "SmartScreen can still warn for a new or rarely downloaded file, even when the signature is valid and the publisher is recognized." -X 24 -Y 148 -Width 740 -Height 42 -Color $colors.Muted))
|
||||
|
||||
$helpPanel.Controls.Add((New-Label -Text "What to check" -X 24 -Y 212 -Width 180 -Height 24 -Color $colors.Text -Font ([Drawing.Font]::new("Segoe UI", 11, [Drawing.FontStyle]::Bold))))
|
||||
$helpPanel.Controls.Add((New-Label -Text "Use File scan to verify signature status, MrSphay signer matching, and Mark-of-the-Web. MrTrust never disables SmartScreen." -X 24 -Y 242 -Width 740 -Height 46 -Color $colors.Muted))
|
||||
|
||||
$pulseTimer = [Windows.Forms.Timer]::new()
|
||||
$pulseTimer.Interval = 100
|
||||
$pulseTimer.Add_Tick({
|
||||
$script:CurrentAccent = ($script:CurrentAccent + 1) % 40
|
||||
$value = 130 + [Math]::Abs(20 - $script:CurrentAccent) * 4
|
||||
$sideAccent.BackColor = [Drawing.Color]::FromArgb(0, [Math]::Min(205, $value), 91)
|
||||
})
|
||||
$pulseTimer.Start()
|
||||
|
||||
$form.Add_Shown({
|
||||
Show-MrTrustPage -Key "Trust"
|
||||
Update-TrustStatus
|
||||
})
|
||||
[Windows.Forms.Application]::Run($form)
|
||||
|
||||
@@ -1,34 +1,50 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace MrTrust
|
||||
{
|
||||
internal static class MrTrustLauncher
|
||||
{
|
||||
[STAThread]
|
||||
private static int Main()
|
||||
{
|
||||
string baseDirectory = AppDomain.CurrentDomain.BaseDirectory;
|
||||
string scriptPath = Path.Combine(baseDirectory, "MrTrust.ps1");
|
||||
private const string PayloadResourcePrefix = "MrTrust.Payload.";
|
||||
|
||||
if (!File.Exists(scriptPath))
|
||||
private static readonly PayloadFile[] PayloadFiles =
|
||||
{
|
||||
MessageBox.Show(
|
||||
"MrTrust.ps1 was not found next to MrTrust.exe.",
|
||||
"MrTrust",
|
||||
MessageBoxButtons.OK,
|
||||
MessageBoxIcon.Error);
|
||||
return 1;
|
||||
}
|
||||
new PayloadFile("MrTrust.ps1", "MrTrust.ps1"),
|
||||
new PayloadFile("scripts.Build-MrTrustExe.ps1", Path.Combine("scripts", "Build-MrTrustExe.ps1")),
|
||||
new PayloadFile("scripts.Install-MrTrust.ps1", Path.Combine("scripts", "Install-MrTrust.ps1")),
|
||||
new PayloadFile("scripts.New-MrTrustCertificate.ps1", Path.Combine("scripts", "New-MrTrustCertificate.ps1")),
|
||||
new PayloadFile("scripts.New-MrTrustIcon.ps1", Path.Combine("scripts", "New-MrTrustIcon.ps1")),
|
||||
new PayloadFile("scripts.New-MrTrustRelease.ps1", Path.Combine("scripts", "New-MrTrustRelease.ps1")),
|
||||
new PayloadFile("scripts.Sign-MrTrustProject.ps1", Path.Combine("scripts", "Sign-MrTrustProject.ps1")),
|
||||
new PayloadFile("scripts.Sign-MrTrustProjectLinux.sh", Path.Combine("scripts", "Sign-MrTrustProjectLinux.sh")),
|
||||
new PayloadFile("scripts.Start-MrTrustGui.ps1", Path.Combine("scripts", "Start-MrTrustGui.ps1")),
|
||||
new PayloadFile("scripts.Uninstall-MrTrust.ps1", Path.Combine("scripts", "Uninstall-MrTrust.ps1")),
|
||||
new PayloadFile("assets.MrTrust.ico", Path.Combine("assets", "MrTrust.ico")),
|
||||
new PayloadFile("assets.certificates.MrSphay-LocalTrust-Root.cer", Path.Combine("assets", "certificates", "MrSphay-LocalTrust-Root.cer")),
|
||||
new PayloadFile("assets.certificates.MrSphay-CodeSigning.cer", Path.Combine("assets", "certificates", "MrSphay-CodeSigning.cer")),
|
||||
new PayloadFile("assets.certificates.thumbprints.txt", Path.Combine("assets", "certificates", "thumbprints.txt"))
|
||||
};
|
||||
|
||||
[STAThread]
|
||||
private static int Main(string[] args)
|
||||
{
|
||||
string baseDirectory = string.Empty;
|
||||
|
||||
try
|
||||
{
|
||||
baseDirectory = ExtractPayload();
|
||||
string scriptPath = Path.Combine(baseDirectory, "MrTrust.ps1");
|
||||
string commandArguments = BuildCommandArguments(args);
|
||||
|
||||
ProcessStartInfo startInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = "powershell.exe",
|
||||
Arguments = "-NoProfile -ExecutionPolicy Bypass -File \"" + scriptPath + "\" gui",
|
||||
Arguments = "-NoProfile -ExecutionPolicy Bypass -File " + QuoteArgument(scriptPath) + " " + commandArguments,
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true,
|
||||
WorkingDirectory = baseDirectory
|
||||
@@ -40,9 +56,10 @@ namespace MrTrust
|
||||
{
|
||||
throw new InvalidOperationException("PowerShell could not be started.");
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
process.WaitForExit();
|
||||
return process.ExitCode;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -53,6 +70,123 @@ namespace MrTrust
|
||||
MessageBoxIcon.Error);
|
||||
return 1;
|
||||
}
|
||||
finally
|
||||
{
|
||||
TryDeleteDirectory(baseDirectory);
|
||||
}
|
||||
}
|
||||
|
||||
private static string ExtractPayload()
|
||||
{
|
||||
Assembly assembly = Assembly.GetExecutingAssembly();
|
||||
string versionKey = GetPayloadVersionKey(assembly);
|
||||
string targetDirectory = Path.Combine(
|
||||
Path.GetTempPath(),
|
||||
"MrTrust",
|
||||
"standalone",
|
||||
versionKey,
|
||||
Guid.NewGuid().ToString("N"));
|
||||
|
||||
foreach (PayloadFile payloadFile in PayloadFiles)
|
||||
{
|
||||
string targetPath = Path.Combine(targetDirectory, payloadFile.RelativePath);
|
||||
string targetParent = Path.GetDirectoryName(targetPath);
|
||||
if (!string.IsNullOrEmpty(targetParent))
|
||||
{
|
||||
Directory.CreateDirectory(targetParent);
|
||||
}
|
||||
|
||||
using (Stream stream = assembly.GetManifestResourceStream(PayloadResourcePrefix + payloadFile.ResourceName))
|
||||
{
|
||||
if (stream == null)
|
||||
{
|
||||
throw new FileNotFoundException("Embedded MrTrust payload file was not found.", payloadFile.RelativePath);
|
||||
}
|
||||
|
||||
using (FileStream file = File.Create(targetPath))
|
||||
{
|
||||
stream.CopyTo(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return targetDirectory;
|
||||
}
|
||||
|
||||
private static void TryDeleteDirectory(string directory)
|
||||
{
|
||||
if (string.IsNullOrEmpty(directory) || !Directory.Exists(directory))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Directory.Delete(directory, true);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Best-effort cleanup only. A locked icon or antivirus scan should not mask the command result.
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetPayloadVersionKey(Assembly assembly)
|
||||
{
|
||||
string location = Application.ExecutablePath;
|
||||
if (File.Exists(location))
|
||||
{
|
||||
FileInfo fileInfo = new FileInfo(location);
|
||||
return fileInfo.Length.ToString("x") + "-" + fileInfo.LastWriteTimeUtc.Ticks.ToString("x");
|
||||
}
|
||||
|
||||
return assembly.GetName().Version.ToString();
|
||||
}
|
||||
|
||||
private static string BuildCommandArguments(string[] args)
|
||||
{
|
||||
string[] effectiveArgs = args.Length == 0 ? new[] { "gui" } : args;
|
||||
return string.Join(" ", effectiveArgs.Select(QuoteArgument).ToArray());
|
||||
}
|
||||
|
||||
private static string QuoteArgument(string value)
|
||||
{
|
||||
if (string.IsNullOrEmpty(value))
|
||||
{
|
||||
return "\"\"";
|
||||
}
|
||||
|
||||
if (value.IndexOfAny(new[] { ' ', '\t', '\n', '\r', '"' }) < 0)
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.Append('"');
|
||||
foreach (char character in value)
|
||||
{
|
||||
if (character == '"')
|
||||
{
|
||||
builder.Append('\\');
|
||||
}
|
||||
|
||||
builder.Append(character);
|
||||
}
|
||||
|
||||
builder.Append('"');
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
private sealed class PayloadFile
|
||||
{
|
||||
public PayloadFile(string resourceName, string relativePath)
|
||||
{
|
||||
ResourceName = resourceName;
|
||||
RelativePath = relativePath;
|
||||
}
|
||||
|
||||
public string ResourceName { get; private set; }
|
||||
|
||||
public string RelativePath { get; private set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
32
src/MrTrustLauncher.csproj
Normal file
32
src/MrTrustLauncher.csproj
Normal file
@@ -0,0 +1,32 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<UseWindowsForms>true</UseWindowsForms>
|
||||
<EnableWindowsTargeting>true</EnableWindowsTargeting>
|
||||
<AssemblyName>MrTrust</AssemblyName>
|
||||
<RootNamespace>MrTrust</RootNamespace>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<PublishSingleFile>true</PublishSingleFile>
|
||||
<SelfContained>true</SelfContained>
|
||||
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||||
<ApplicationIcon>..\assets\MrTrust.ico</ApplicationIcon>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="..\MrTrust.ps1" LogicalName="MrTrust.Payload.MrTrust.ps1" />
|
||||
<EmbeddedResource Include="..\scripts\Build-MrTrustExe.ps1" LogicalName="MrTrust.Payload.scripts.Build-MrTrustExe.ps1" />
|
||||
<EmbeddedResource Include="..\scripts\Install-MrTrust.ps1" LogicalName="MrTrust.Payload.scripts.Install-MrTrust.ps1" />
|
||||
<EmbeddedResource Include="..\scripts\New-MrTrustCertificate.ps1" LogicalName="MrTrust.Payload.scripts.New-MrTrustCertificate.ps1" />
|
||||
<EmbeddedResource Include="..\scripts\New-MrTrustIcon.ps1" LogicalName="MrTrust.Payload.scripts.New-MrTrustIcon.ps1" />
|
||||
<EmbeddedResource Include="..\scripts\New-MrTrustRelease.ps1" LogicalName="MrTrust.Payload.scripts.New-MrTrustRelease.ps1" />
|
||||
<EmbeddedResource Include="..\scripts\Sign-MrTrustProject.ps1" LogicalName="MrTrust.Payload.scripts.Sign-MrTrustProject.ps1" />
|
||||
<EmbeddedResource Include="..\scripts\Sign-MrTrustProjectLinux.sh" LogicalName="MrTrust.Payload.scripts.Sign-MrTrustProjectLinux.sh" />
|
||||
<EmbeddedResource Include="..\scripts\Start-MrTrustGui.ps1" LogicalName="MrTrust.Payload.scripts.Start-MrTrustGui.ps1" />
|
||||
<EmbeddedResource Include="..\scripts\Uninstall-MrTrust.ps1" LogicalName="MrTrust.Payload.scripts.Uninstall-MrTrust.ps1" />
|
||||
<EmbeddedResource Include="..\assets\MrTrust.ico" LogicalName="MrTrust.Payload.assets.MrTrust.ico" />
|
||||
<EmbeddedResource Include="..\assets\certificates\MrSphay-LocalTrust-Root.cer" LogicalName="MrTrust.Payload.assets.certificates.MrSphay-LocalTrust-Root.cer" />
|
||||
<EmbeddedResource Include="..\assets\certificates\MrSphay-CodeSigning.cer" LogicalName="MrTrust.Payload.assets.certificates.MrSphay-CodeSigning.cer" />
|
||||
<EmbeddedResource Include="..\assets\certificates\thumbprints.txt" LogicalName="MrTrust.Payload.assets.certificates.thumbprints.txt" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
Reference in New Issue
Block a user