Public Access
Give the desktop a macOS head, signed from the first release
The same application, the same Velopack and the same two-phase person-run release as Windows, with four things forced to differ. Signing is a precondition rather than an improvement: Gatekeeper refuses an un-notarized download outright instead of warning about it, so there was never the "unsigned for now" that ADR 0013 decision 8 argues for on Windows, and release-macos.sh refuses to start without the identities. The packaging split is narrower than it first looked, and the old claim at the foot of ci.yml is why it was worth checking rather than assuming. vpk cross-compiles when told to: 'vpk [osx] bundle' builds a real .app on any platform, and CI now publishes osx-arm64 and bundles it on every main and tag build, which is what catches a restore graph with no macOS native asset. There is no '[osx] pack' off a Mac, and that part is correct — pack drives codesign, notarytool and stapler, which exist nowhere else. The dylib signing loop in the script looks redundant beside vpk's own pass and is not. vpk signs with 'codesign --deep', which is the shape Apple documents as wrong for nested code, and platform-flags has recorded a notarization rejection that names no file since before any of this existed. Signing each native binary inside-out first leaves that pass nothing to get wrong. MacDeviceKeyStore reaches ADR 0007's conclusion through different hardware: a P-256 key in the Secure Enclave under an access control requiring user presence, so the platform enforces the gate rather than this process — which is the whole point of that ADR's amendment. The enclave holds no other kind of key, hence ECIES where Windows uses RSA-OAEP, and the shape that falls out is better than the Windows one: sealing needs only the public half and is silent, so only unlock prompts. IsSupported probes rather than infers, because three ordinary Macs answer no — an Intel machine without a T2, one with no login password, and every unsigned development build, since enclave keys need a signing identity. Two decisions worth stating because they are reversible. arm64 only: a second channel is small work and nobody here has an Intel Mac to walk Phase 18 on, and an x64 package would be the only artefact in this repository reaching users unverified. And the pack id stays DodoSSH.Desktop even though vpk names the bundle after it, so /Applications holds DodoSSH.Desktop.app: decision 2's reasoning binds harder here, because a pack id of DodoSSH would put Velopack's install root on top of ClientPaths.DataDirectory and let an uninstall take the user's un-synced outbox with it. CFBundleDisplayName puts the product name back in front of a person. Measured rather than assumed, since none of it is obvious: the publish and the bundle were both run, LSMinimumSystemVersion is 12.0 because that is the minos in the apphost's own LC_BUILD_VERSION, and vpk copies a custom Info.plist verbatim with no substitution at all — which is why the plist is a template the script renders and not a committed file. What is not done is the half that needs the hardware. There is no macOS runner, so nothing past "it bundles" has ever run. Phase 18 is the whole of the verification, and the two checks most likely to fail are the terminal against WKWebView and the enclave interop, neither of which has executed once.
This commit is contained in:
@@ -2593,3 +2593,120 @@ package manager will not offer to.
|
||||
|
||||
**Failure means:** the channels are not separate, and a public key is signing the application people keep
|
||||
their credentials in.
|
||||
|
||||
## Phase 18 — Installing the macOS client, and being updated by it
|
||||
|
||||
The macOS counterpart of phase 16, and it needs a Mac with a Secure Enclave — an Apple Silicon machine or
|
||||
an Intel one with a T2. Every check here is structurally unreachable by a test for the reasons phase 16
|
||||
gives, plus one this platform adds: **CI has no macOS runner at all**, so this phase is the only place the
|
||||
suite and the application ever run on macOS. Anything `docs/platform-flags.md` marks as unverified on macOS
|
||||
is verified here or nowhere.
|
||||
|
||||
Run `bash scripts/release-macos.sh` first. It stops after packing and notarizing, on purpose, so that
|
||||
everything below happens before anything reaches a user. Phase 16.0 — the feed being readable without
|
||||
credentials — applies unchanged and is not repeated.
|
||||
|
||||
### 18.1 Gatekeeper accepts it on a machine that did not build it · **do this one first**
|
||||
|
||||
The Mac that signed a package trusts it locally whatever happened, so the build machine cannot answer this
|
||||
question about itself. Copy the `.pkg` to a second Mac — or at minimum download it through a browser, which
|
||||
is what applies the quarantine attribute — and open it.
|
||||
|
||||
**Pass:** it installs with no warning beyond the ordinary installer prompts.
|
||||
|
||||
**Failure means:** "cannot be opened because Apple cannot check it for malicious software" is notarization
|
||||
that did not happen or a ticket that did not staple. The script's `spctl --assess` and `xcrun stapler
|
||||
validate` should have caught it before this point, so reaching here means one of those two checks was
|
||||
removed or skipped. Do not distribute the package.
|
||||
|
||||
### 18.2 The Dock shows the product and not the pack id
|
||||
|
||||
Look at the installed application in `/Applications`, in the Dock, and in the menu bar while it runs.
|
||||
|
||||
**Pass:** the menu bar says **DodoSSH**. Finder shows **DodoSSH**. The bundle on disk is
|
||||
`DodoSSH.Desktop.app` and that is expected — see the pack id note in `scripts/release-macos.sh`.
|
||||
|
||||
**Failure means:** "DodoSSH.Desktop" in the menu bar is `CFBundleName` not reaching the bundle, which means
|
||||
the rendered `Info.plist` did not get used. Since vpk copies a custom plist verbatim and substitutes
|
||||
nothing, check the same bundle's `CFBundleShortVersionString` — if it reads `@VERSION@`, the template was
|
||||
passed through unrendered.
|
||||
|
||||
### 18.3 The icon is the mark, at every size
|
||||
|
||||
Look at it in the Dock, in Finder's icon view at a large size, and in `⌘I` Get Info.
|
||||
|
||||
**Pass:** the accent tile and the `>_` mark, crisp at 1024, with the same air around it that Finder and
|
||||
Safari have.
|
||||
|
||||
**Failure means:** a generic application icon is `CFBundleIconFile` naming a file that is not in
|
||||
`Contents/Resources`. An icon that fills its square edge to edge, larger than its neighbours, is
|
||||
`New-MarkPng` having been called with the Windows tile fraction — see `dodossh-icon.ps1`.
|
||||
|
||||
### 18.4 Touch ID guards the device key, and the enclave enforces it
|
||||
|
||||
Register a device key from the security settings page, then lock the vault and unlock it again.
|
||||
|
||||
**Pass:** registering shows **no** prompt at all — sealing uses only the public half — and unlocking raises
|
||||
the system Touch ID sheet saying DodoSSH is trying to *unlock your DodoSSH vault*. The vault opens on a
|
||||
successful touch.
|
||||
|
||||
**Failure means:** a prompt at registration is not a failure of correctness but says the key was not created
|
||||
in the enclave; check that `kSecAttrTokenID` reached the attributes. **No prompt at unlock, with the vault
|
||||
opening anyway, is the serious one** — it means the key is a software key and the access control did nothing,
|
||||
which is precisely the "a gate inside the process is not a gate" mistake `WindowsDeviceKeyStore` documents.
|
||||
|
||||
### 18.5 Declining the fingerprint falls back to the passphrase
|
||||
|
||||
Repeat 18.4 and cancel the Touch ID sheet.
|
||||
|
||||
**Pass:** the unlock screen asks for the passphrase, and it works.
|
||||
|
||||
**Failure means:** an error dialog, or a stuck screen, is `TryLoadAsync` throwing rather than answering
|
||||
null. Every failure it can meet — cancelled, timed out, key invalidated by a password reset — is meant to
|
||||
be indistinguishable and to land on the passphrase.
|
||||
|
||||
### 18.6 A development build offers no device key at all
|
||||
|
||||
Run the application with `dotnet run` rather than from the installed bundle, and open the security settings
|
||||
page.
|
||||
|
||||
**Pass:** registering a device key is not offered.
|
||||
|
||||
**Failure means:** being offered it is `IsSupported` having inferred availability from the OS rather than
|
||||
probing. An unsigned build cannot create an enclave key, so accepting the offer would put a wrap on the
|
||||
server that nothing can ever open and list a capability this machine does not have.
|
||||
|
||||
### 18.7 The terminal works, which is the WKWebView question
|
||||
|
||||
Connect to a host and use the shell: type, run something that scrolls, resize the window.
|
||||
|
||||
**Pass:** the terminal attaches within a second or two and behaves as it does on Windows.
|
||||
|
||||
**Failure means:** a blank pane that reports a renderer timeout after fifteen seconds is the loopback
|
||||
WebSocket not reaching WKWebView. This is the check that most needs walking, because the data plane has
|
||||
never run against this backend — see `TerminalDataPlane`. If it fails, the App Sandbox is the first thing to
|
||||
rule out: the entitlements deliberately do not enable it, and a sandboxed process cannot listen on loopback
|
||||
without `com.apple.security.network.server`.
|
||||
|
||||
### 18.8 An update is offered, downloaded and applied
|
||||
|
||||
With the release installed, cut a second release with a higher version and publish it, then leave the first
|
||||
running.
|
||||
|
||||
**Pass:** the banner appears, downloads, and on applying the application closes and reopens on the new
|
||||
version. The vault's contents and the known hosts survive.
|
||||
|
||||
**Failure means:** an update that never arrives is usually the channel — `osx` here and `osx` in
|
||||
`VelopackUpdateChannel.MacReleaseChannel`, with no error anywhere when they disagree. An update that
|
||||
downloads and fails to apply, leaving the application unable to restart, is library validation: check that
|
||||
`com.apple.security.cs.disable-library-validation` survived into the entitlements.
|
||||
|
||||
### 18.9 Uninstalling does not take the vault with it
|
||||
|
||||
Register a device, sync something, then remove the application.
|
||||
|
||||
**Pass:** `~/Library/Application Support/DodoSSH` still holds the cache and the outbox afterwards.
|
||||
|
||||
**Failure means:** an empty directory is the pack id having been changed to `DodoSSH`, which puts Velopack's
|
||||
install root on top of `ClientPaths.DataDirectory` and makes an uninstall delete a user's un-synced work.
|
||||
This is the single reason the bundle is named `DodoSSH.Desktop.app`.
|
||||
|
||||
Reference in New Issue
Block a user