Ariadne 0.1.4: sweep stale BNS root CAs so browsers stop failing after upgrades

Symptom: on any machine that had been through a series of installs, one or
more older "BNS Local Root CA" certs from earlier builds could accumulate in
LocalMachine\Root -- an unconstrained root from before 2026-07, or a rotated
one -- and every install added the current constrained root ALONGSIDE the
stale ones. Two certs with the same Subject-DN defeat browser chain-builders:
Chromium/Edge/Firefox pick one candidate and refuse to try the others, so
every .bch site failed with ERR_CERT_AUTHORITY_INVALID / SEC_ERROR_BAD_SIGNATURE
in every browser EXCEPT Theseus (which trusts the on-chain tls fingerprint
directly, not the machine store).

Reproduced on the dev box: two BNS Local Root CAs present (thumbprints
EB5DBE98... constrained + 23538FD7... unconstrained), DNS answered correctly,
strict Invoke-WebRequest to https://coinspectrum.bch/ failed. Removing the
stale one and re-running the chain-build restored HTTPS 200.

Fix (install.ps1):
- Before Add(imported), enumerate the store and $store.Remove() every
  "BNS Local Root CA" whose thumbprint differs from the one we're about
  to trust. Post-condition: exactly one BNS root remains after install.

Fix (uninstall.ps1):
- Switched from `Get-ChildItem Cert:\LocalMachine\Root` to X509Store direct.
  The Cert: PSDrive is not always loaded in Inno's [UninstallRun] -NoProfile
  context, so uninstall could silently leave the CA behind on the store side
  even while it removed everything else.

Verified end-to-end on this box: 0.1.4 upgrade over 0.1.3 finished cleanly,
BNS Resolver Daemon Running, 127.0.0.1:53 open, exactly one BNS Local Root
CA in the machine store, strict HTTPS to https://coinspectrum.bch/ returned
200 (506,997 bytes) with X509Chain.Build ok=true leaf -> BNS root.

Deployed
- dl.silentmode.st/AriadneResolver-Setup-0.1.4.exe
- s3://bns/silentmode/dl/AriadneResolver-Setup-0.1.4.exe
- silentmode.st manifest advertises 0.1.4
- sha256 30ed5e3bb6d6da5325f854c97976410b4fc63067ed52c810a55e9d2e1bdc4e17
This commit is contained in:
Local Dev 2026-09-27 22:04:00 +02:00
parent 53428a605f
commit 0b6c8093e1
2 changed files with 26 additions and 6 deletions

View file

@ -310,6 +310,19 @@ if (Test-Path $caCert) {
$store = New-Object System.Security.Cryptography.X509Certificates.X509Store('Root','LocalMachine')
$store.Open('ReadWrite')
$imported = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($caCert)
# Sweep any prior "BNS Local Root CA" whose thumbprint differs from the one
# we're about to trust. Two roots with the same Subject-DN confuse browsers'
# chain-builders (Chromium/Edge/Firefox all pick one candidate and refuse to
# try others), which manifests as "resolver installed and DNS answers, but
# every .bch site fails with SEC_ERROR_BAD_SIGNATURE / ERR_CERT_AUTHORITY_INVALID
# in every browser except Theseus". A stale unconstrained root left over from
# an early build is enough to trigger it -- and there's no way to detect it
# without walking the store, because both roots claim the same identity.
# Post-condition: exactly one "BNS Local Root CA" remains after this block.
foreach ($c in @($store.Certificates | Where-Object { $_.Subject -match 'BNS Local Root CA' -and $_.Thumbprint -ne $imported.Thumbprint })) {
Write-Host "[ok] removing stale BNS root CA ($($c.Thumbprint), NotAfter $($c.NotAfter))"
$store.Remove($c)
}
$store.Add($imported)
$store.Close()
$state.caThumbprint = $imported.Thumbprint

View file

@ -2,9 +2,12 @@
$ErrorActionPreference = "Stop"
$stateFile = Join-Path $PSScriptRoot "install-state.json"
Get-DnsClientNrptRule | Where-Object Comment -eq "BNS .bch resolver" | ForEach-Object {
# install.ps1 adds one rule per activated TLD, each commented "BNS .<tld> resolver".
# Match them all -- an exact match on ".bch" used to leave every other TLD's rule
# behind, still pointing at the daemon we are removing.
Get-DnsClientNrptRule | Where-Object { $_.Comment -like "BNS *resolver" } | ForEach-Object {
Remove-DnsClientNrptRule -Name $_.Name -Force
Write-Host "[ok] NRPT rule removed"
Write-Host "[ok] NRPT rule removed ($($_.Namespace -join ', '))"
}
if (Test-Path $stateFile) {
$state = Get-Content $stateFile | ConvertFrom-Json
@ -35,13 +38,17 @@ foreach ($if in Get-DnsClientServerAddress -AddressFamily IPv4 |
# Remove the BNS root CA robustly -- by SUBJECT, not only the thumbprint recorded
# in install-state.json. A missing or stale state file (the daemon was run by
# hand, or this is an upgrade from an older build) must never leave an orphaned
# trusted root in the machine store.
# trusted root in the machine store. Use X509Store direct because the Cert:
# PSDrive is not always loaded in Inno's [UninstallRun] -NoProfile context.
$removed = 0
Get-ChildItem Cert:\LocalMachine\Root | Where-Object { $_.Subject -match "BNS Local Root CA" } | ForEach-Object {
Remove-Item $_.PSPath -Force
Write-Host "[ok] BNS Local Root CA removed ($($_.Thumbprint))"
$store = New-Object System.Security.Cryptography.X509Certificates.X509Store('Root','LocalMachine')
$store.Open('ReadWrite')
foreach ($c in @($store.Certificates | Where-Object { $_.Subject -match 'BNS Local Root CA' })) {
$store.Remove($c)
Write-Host "[ok] BNS Local Root CA removed ($($c.Thumbprint))"
$removed++
}
$store.Close()
if ($removed -eq 0) { Write-Host "[ok] no BNS root CA left in the machine store" }
try {
Remove-Item -Path "HKLM:\SOFTWARE\Policies\Mozilla\Firefox\Certificates" -Recurse -Force -ErrorAction Stop