M3: working desktop app + browser pairing channel
- Builds and runs (tauri build --debug); placeholder icons; persistent VaultCore::open in the OS app-data dir; Secret Key remembered on the device; tray keeps the app serving the extension. - Auto-lock: idle (system idle on Windows/macOS, in-app activity on Linux), screen lock, sleep. - Local IPC for kestrove-nmh: 0700 dir + 0600 Unix socket with peer-uid checks; Windows named pipe (first instance, local only, user DACL, client SID check) behind cfg. - Pairing channel: X25519 → HKDF → 6-word fingerprint confirmed in both UIs, per-connection session keys, AES-256-GCM with strict counters. Test vectors shared with kestrove-browser. - Broker: query/fill/totp/save with origin re-checks; pairing and save prompts in the UI. - kestrove-nmh relays ciphertext only; native-host installers for Linux/macOS/Windows. - Tests: unit tests, tests/nmh_stdio.rs (real kestrove-nmh over stdio), examples/test-host. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
@@ -1,6 +1,23 @@
|
||||
# Native messaging manifests
|
||||
|
||||
The installer fills in the placeholders and registers the manifests:
|
||||
`install.sh` (Linux, macOS) and `install.ps1` (Windows) fill in the placeholders and register the
|
||||
manifests for the current user. Default ids are the development ones: the unpacked Chrome build is pinned
|
||||
to `kogapfibnnpeapfcjkkpgjddbhnjfein` by the manifest key in kestrove-browser, Firefox uses
|
||||
`[email protected]`. Pass `--chrome-id` / `-ChromeId` for a Web Store build.
|
||||
|
||||
```bash
|
||||
native-messaging/install.sh # every browser with a profile dir, host from target/
|
||||
native-messaging/install.sh --browsers chrome --nmh /opt/kestrove/kestrove-nmh
|
||||
native-messaging/install.sh --user-data-dir /tmp/profile # a Chromium started with --user-data-dir (tests)
|
||||
native-messaging/install.sh --uninstall
|
||||
```
|
||||
|
||||
| Browser | macOS (dev only; the Apple app ships its own host) |
|
||||
|---|---|
|
||||
| Chrome | `~/Library/Application Support/Google/Chrome/NativeMessagingHosts/` |
|
||||
| Edge / Brave / Chromium | `~/Library/Application Support/{Microsoft Edge,BraveSoftware/Brave-Browser,Chromium}/NativeMessagingHosts/` |
|
||||
| Firefox | `~/Library/Application Support/Mozilla/NativeMessagingHosts/` |
|
||||
|
||||
|
||||
| Browser | Linux | Windows |
|
||||
|---|---|---|
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "app.kestrove.nmh",
|
||||
"description": "Kestrove native messaging host",
|
||||
"path": "__INSTALL_DIR__/kestrove-nmh",
|
||||
"path": "__NMH_PATH__",
|
||||
"type": "stdio",
|
||||
"allowed_origins": ["chrome-extension://__CHROME_EXTENSION_ID__/"]
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "app.kestrove.nmh",
|
||||
"description": "Kestrove native messaging host",
|
||||
"path": "__INSTALL_DIR__/kestrove-nmh",
|
||||
"path": "__NMH_PATH__",
|
||||
"type": "stdio",
|
||||
"allowed_extensions": ["__FIREFOX_EXTENSION_ID__"]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
# Registers the Kestrove native-messaging host for the current user on Windows (HKCU, no admin).
|
||||
# Linux/macOS: install.sh.
|
||||
#
|
||||
# powershell -ExecutionPolicy Bypass -File install.ps1 [-Nmh C:\path\kestrove-nmh.exe]
|
||||
# [-ChromeId ID] [-FirefoxId ID] [-Browsers chrome,chromium,edge,firefox] [-Uninstall]
|
||||
#
|
||||
# Chrome and Brave share the Google\Chrome key. The manifests go to %LOCALAPPDATA%\Kestrove\NativeMessagingHosts.
|
||||
# allowed_origins / allowed_extensions pin the host to our extension ids (docs/04 A8).
|
||||
param(
|
||||
[string]$Nmh = "",
|
||||
[string]$ChromeId = "kogapfibnnpeapfcjkkpgjddbhnjfein",
|
||||
[string]$FirefoxId = "[email protected]",
|
||||
[string[]]$Browsers = @("chrome", "chromium", "edge", "firefox"),
|
||||
[switch]$Uninstall
|
||||
)
|
||||
$ErrorActionPreference = "Stop"
|
||||
$HostName = "app.kestrove.nmh"
|
||||
$Here = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
|
||||
if ($ChromeId -notmatch '^[a-p]{32}$') { throw "-ChromeId must be 32 letters a-p" }
|
||||
if ($FirefoxId -notmatch '^[A-Za-z0-9._@{}-]+$') { throw "bad -FirefoxId" }
|
||||
|
||||
$Keys = @{
|
||||
chrome = "HKCU:\Software\Google\Chrome\NativeMessagingHosts\$HostName"
|
||||
chromium = "HKCU:\Software\Chromium\NativeMessagingHosts\$HostName"
|
||||
edge = "HKCU:\Software\Microsoft\Edge\NativeMessagingHosts\$HostName"
|
||||
firefox = "HKCU:\Software\Mozilla\NativeMessagingHosts\$HostName"
|
||||
}
|
||||
$OutDir = Join-Path $env:LOCALAPPDATA "Kestrove\NativeMessagingHosts"
|
||||
|
||||
if ($Uninstall) {
|
||||
foreach ($b in $Browsers) {
|
||||
if (Test-Path $Keys[$b]) { Remove-Item $Keys[$b] -Force; Write-Host "removed $($Keys[$b])" }
|
||||
}
|
||||
Remove-Item (Join-Path $OutDir "$HostName.*.json") -ErrorAction SilentlyContinue
|
||||
return
|
||||
}
|
||||
|
||||
if (-not $Nmh) {
|
||||
foreach ($c in @((Join-Path $Here "kestrove-nmh.exe"), (Join-Path $Here "..\src-tauri\target\release\kestrove-nmh.exe"), (Join-Path $Here "..\src-tauri\target\debug\kestrove-nmh.exe"))) {
|
||||
if (Test-Path $c) { $Nmh = $c; break }
|
||||
}
|
||||
}
|
||||
if (-not $Nmh -or -not (Test-Path $Nmh)) { throw "kestrove-nmh.exe not found; pass -Nmh" }
|
||||
$Nmh = (Resolve-Path $Nmh).Path
|
||||
|
||||
New-Item -ItemType Directory -Force -Path $OutDir | Out-Null
|
||||
foreach ($flavor in @("chrome", "firefox")) {
|
||||
$m = Get-Content -Raw (Join-Path $Here "$HostName.$flavor.json") | ConvertFrom-Json
|
||||
$m.path = $Nmh
|
||||
if ($flavor -eq "chrome") { $m.allowed_origins = @("chrome-extension://$ChromeId/") } else { $m.allowed_extensions = @($FirefoxId) }
|
||||
$m | ConvertTo-Json -Depth 4 | Set-Content -Encoding UTF8 (Join-Path $OutDir "$HostName.$flavor.json")
|
||||
}
|
||||
|
||||
foreach ($b in $Browsers) {
|
||||
if (-not $Keys.ContainsKey($b)) { throw "unknown browser $b" }
|
||||
$flavor = if ($b -eq "firefox") { "firefox" } else { "chrome" }
|
||||
New-Item -Force -Path $Keys[$b] | Out-Null
|
||||
Set-Item -Path $Keys[$b] -Value (Join-Path $OutDir "$HostName.$flavor.json")
|
||||
Write-Host "registered $($Keys[$b])"
|
||||
}
|
||||
Executable
+105
@@ -0,0 +1,105 @@
|
||||
#!/bin/sh
|
||||
# Registers the Kestrove native-messaging host with the browsers of the current user (Linux, macOS).
|
||||
# Windows: install.ps1.
|
||||
#
|
||||
# install.sh [--nmh /path/to/kestrove-nmh] [--chrome-id ID] [--firefox-id ID]
|
||||
# [--browsers chrome,chromium,edge,brave,firefox] [--user-data-dir DIR] [--uninstall]
|
||||
#
|
||||
# With neither --browsers nor --user-data-dir it installs for every browser whose profile directory exists.
|
||||
# --user-data-dir adds a Chromium-style profile dir started with --user-data-dir (Chromium then
|
||||
# looks for NativeMessagingHosts/ inside it), e.g. for tests.
|
||||
# allowed_origins / allowed_extensions pin the host to our extension ids (docs/04 A8).
|
||||
set -eu
|
||||
|
||||
HOST=app.kestrove.nmh
|
||||
HERE=$(cd "$(dirname "$0")" && pwd)
|
||||
NMH=""
|
||||
# Development ids: the unpacked Chrome build is pinned by the manifest key in kestrove-browser/scripts/build.mjs.
|
||||
CHROME_ID=kogapfibnnpeapfcjkkpgjddbhnjfein
|
||||
FIREFOX_ID=[email protected]
|
||||
BROWSERS=""
|
||||
USER_DATA_DIR=""
|
||||
UNINSTALL=0
|
||||
|
||||
die() { echo "install.sh: $*" >&2; exit 1; }
|
||||
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--nmh) NMH=$2; shift 2 ;;
|
||||
--chrome-id) CHROME_ID=$2; shift 2 ;;
|
||||
--firefox-id) FIREFOX_ID=$2; shift 2 ;;
|
||||
--browsers) BROWSERS=$(echo "$2" | tr ',' ' '); shift 2 ;;
|
||||
--user-data-dir) USER_DATA_DIR=$2; shift 2 ;;
|
||||
--uninstall) UNINSTALL=1; shift ;;
|
||||
-h|--help) sed -n '2,13p' "$0"; exit 0 ;;
|
||||
*) die "unknown option $1" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
echo "$CHROME_ID" | grep -Eq '^[a-p]{32}$' || die "--chrome-id must be 32 letters a-p"
|
||||
echo "$FIREFOX_ID" | grep -Eq '^[A-Za-z0-9._@{}-]+$' || die "bad --firefox-id"
|
||||
|
||||
if [ "$UNINSTALL" = 0 ]; then
|
||||
if [ -z "$NMH" ]; then
|
||||
# Next to this script (installed layout), else the cargo build output (development).
|
||||
for c in "$HERE/kestrove-nmh" "$HERE/../src-tauri/target/release/kestrove-nmh" "$HERE/../src-tauri/target/debug/kestrove-nmh"; do
|
||||
[ -x "$c" ] && NMH=$c && break
|
||||
done
|
||||
fi
|
||||
[ -n "$NMH" ] || die "kestrove-nmh not found; pass --nmh"
|
||||
[ -x "$NMH" ] || die "$NMH is not executable"
|
||||
NMH=$(cd "$(dirname "$NMH")" && pwd)/$(basename "$NMH")
|
||||
case "$NMH" in *[\"\\\|\&]*) die "the host path may not contain quotes, backslashes, | or &" ;; esac
|
||||
fi
|
||||
|
||||
case "$(uname -s)" in
|
||||
Darwin)
|
||||
BASE="$HOME/Library/Application Support"
|
||||
dir_chrome="$BASE/Google/Chrome"
|
||||
dir_chromium="$BASE/Chromium"
|
||||
dir_edge="$BASE/Microsoft Edge"
|
||||
dir_brave="$BASE/BraveSoftware/Brave-Browser"
|
||||
dir_firefox="$BASE/Mozilla"
|
||||
ff_sub=NativeMessagingHosts
|
||||
;;
|
||||
Linux)
|
||||
CFG=${XDG_CONFIG_HOME:-$HOME/.config}
|
||||
dir_chrome="$CFG/google-chrome"
|
||||
dir_chromium="$CFG/chromium"
|
||||
dir_edge="$CFG/microsoft-edge"
|
||||
dir_brave="$CFG/BraveSoftware/Brave-Browser"
|
||||
dir_firefox="$HOME/.mozilla"
|
||||
ff_sub=native-messaging-hosts
|
||||
;;
|
||||
*) die "unsupported OS $(uname -s); on Windows use install.ps1" ;;
|
||||
esac
|
||||
|
||||
# Auto-detect only when nothing was named: a test run with --user-data-dir must not touch real profiles.
|
||||
[ -n "$BROWSERS$USER_DATA_DIR" ] || for b in chrome chromium edge brave firefox; do
|
||||
eval "d=\$dir_$b"
|
||||
[ -d "$d" ] && BROWSERS="$BROWSERS $b"
|
||||
done
|
||||
|
||||
render() { # template → stdout
|
||||
sed -e "s|__NMH_PATH__|$NMH|" -e "s|__CHROME_EXTENSION_ID__|$CHROME_ID|" -e "s|__FIREFOX_EXTENSION_ID__|$FIREFOX_ID|" "$HERE/$HOST.$1.json"
|
||||
}
|
||||
|
||||
place() { # flavor dir
|
||||
if [ "$UNINSTALL" = 1 ]; then
|
||||
rm -f "$2/$HOST.json" && echo "removed $2/$HOST.json"
|
||||
else
|
||||
mkdir -p "$2"
|
||||
render "$1" > "$2/$HOST.json.tmp" && chmod 644 "$2/$HOST.json.tmp" && mv "$2/$HOST.json.tmp" "$2/$HOST.json"
|
||||
echo "installed $2/$HOST.json"
|
||||
fi
|
||||
}
|
||||
|
||||
for b in $BROWSERS; do
|
||||
case "$b" in
|
||||
chrome|chromium|edge|brave) eval "d=\$dir_$b"; place chrome "$d/NativeMessagingHosts" ;;
|
||||
firefox) place firefox "$dir_firefox/$ff_sub" ;;
|
||||
*) die "unknown browser $b" ;;
|
||||
esac
|
||||
done
|
||||
[ -z "$USER_DATA_DIR" ] || place chrome "$USER_DATA_DIR/NativeMessagingHosts"
|
||||
[ -n "$BROWSERS$USER_DATA_DIR" ] || echo "no supported browser profile found (use --browsers)"
|
||||
Reference in New Issue
Block a user