diff --git a/setup/install.ps1 b/setup/install.ps1 index d14c6e8..09938a0 100644 --- a/setup/install.ps1 +++ b/setup/install.ps1 @@ -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 diff --git a/setup/uninstall.ps1 b/setup/uninstall.ps1 index ef560b3..f144be4 100644 --- a/setup/uninstall.ps1 +++ b/setup/uninstall.ps1 @@ -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 . 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