Harden credential storage and release packaging
ober
025249de619de68b44092ddfbe2385079b50ec2b
--- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,15 +12,6 @@ permissions: env: JERBOA_VERSION: v0.2.3 JERBUILD: ${{ github.workspace }}/.jerboa/bin/jerbuild - JERBOA_AWS_REPO: https://git.sr.ht/~lisp/jerboa-aws - JERBOA_PROTON_BRIDGE_REPO: https://git.sr.ht/~lisp/jerboa-proton-bridge - JERBOA_YUBIKEY_REPO: https://git.sr.ht/~lisp/jerboa-yubikey - JERBOA_PGP_REPO: https://git.sr.ht/~lisp/jerboa-pgp - JERBOA_CRYPTO_REPO: https://git.sr.ht/~lisp/jerboa-crypto - JERBOA_FUSE_REPO: https://git.sr.ht/~lisp/jerboa-fuse - JERBOA_MAIL_REPO: https://git.sr.ht/~lisp/jerboa-mail - JERBOA_HTTPS_REPO: https://git.sr.ht/~lisp/jerboa-https - JERBOA_SSL_REPO: https://git.sr.ht/~lisp/jerboa-ssl JERBOA_AWS_DIR: ${{ github.workspace }}/.deps/jerboa-aws JERBOA_PROTON_BRIDGE_DIR: ${{ github.workspace }}/.deps/jerboa-proton-bridge JERBOA_YUBIKEY_DIR: ${{ github.workspace }}/.deps/jerboa-yubikey @@ -35,7 +26,7 @@ jobs: build-test-audit: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - name: Install system tools run: | @@ -49,18 +40,7 @@ jobs: run: sh support/ensure-jerboa.sh "$JERBOA_VERSION" .jerboa/bin - name: Fetch dependencies - run: | - set -eu - mkdir -p .deps - git clone --depth 1 "$JERBOA_AWS_REPO" "$JERBOA_AWS_DIR" - git clone --depth 1 "$JERBOA_PROTON_BRIDGE_REPO" "$JERBOA_PROTON_BRIDGE_DIR" - git clone --depth 1 "$JERBOA_YUBIKEY_REPO" "$JERBOA_YUBIKEY_DIR" - git clone --depth 1 "$JERBOA_PGP_REPO" "$JERBOA_PGP_DIR" - git clone --depth 1 "$JERBOA_CRYPTO_REPO" "$JERBOA_CRYPTO_DIR" - git clone --depth 1 "$JERBOA_FUSE_REPO" "$JERBOA_FUSE_DIR" - git clone --depth 1 "$JERBOA_MAIL_REPO" "$JERBOA_MAIL_DIR" - git clone --depth 1 "$JERBOA_HTTPS_REPO" "$JERBOA_HTTPS_DIR" - git clone --depth 1 "$JERBOA_SSL_REPO" "$JERBOA_SSL_DIR" + run: sh support/fetch-locked-deps.sh dependency-lock.tsv .deps - name: Test run: make test --- a/.github/workflows/security-baseline.yml +++ b/.github/workflows/security-baseline.yml @@ -13,7 +13,7 @@ jobs: baseline: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - name: Required release files run: | new file mode 100644 --- /dev/null +++ b/.jerbuild.release-darwin @@ -0,0 +1,25 @@ +(entry "support/binary-entry.ss") +(output "protonstorage-bin") +(libdirs + "support/vendor-overrides" + "." + "build/release-inputs/jerboa-aws/lib" + "build/release-inputs/jerboa-proton-bridge" + "build/release-inputs/jerboa-yubikey" + "build/release-inputs/jerboa-yubikey/lib" + "build/release-inputs/jerboa-pgp" + "build/release-inputs/jerboa-crypto/lib" + "build/release-inputs/jerboa-fuse/lib" + "build/release-inputs/jerboa-mail" + "build/release-inputs/jerboa-https/lib" + "build/release-inputs/jerboa-ssl/lib") +(extra-sources "support/bundle-system-symbols.c") +(extra-ldflags + "build/release-inputs/jerboa-ssl/jerboa_ssl_shim.dylib" + "build/release-inputs/jerboa-fuse/libjerboa_fuse_mount.so" + "build/release-inputs/jerboa-proton-bridge/proton-bridge-native/target/release/libproton_bridge_native.dylib" + "build/release-inputs/jerboa-yubikey/yubikey-native/target/release/libyubikey_native.dylib" + "build/release-inputs/jerboa-pgp/pgp-native/target/release/libjpgp_native.dylib") +(main-c "support/bundle-main.c") +(ffi-symbols "support/bundle-ffi-symbols.list") +(static-native #t) new file mode 100644 --- /dev/null +++ b/.jerbuild.release-elf @@ -0,0 +1,25 @@ +(entry "support/binary-entry.ss") +(output "protonstorage-bin") +(libdirs + "support/vendor-overrides" + "." + "build/release-inputs/jerboa-aws/lib" + "build/release-inputs/jerboa-proton-bridge" + "build/release-inputs/jerboa-yubikey" + "build/release-inputs/jerboa-yubikey/lib" + "build/release-inputs/jerboa-pgp" + "build/release-inputs/jerboa-crypto/lib" + "build/release-inputs/jerboa-fuse/lib" + "build/release-inputs/jerboa-mail" + "build/release-inputs/jerboa-https/lib" + "build/release-inputs/jerboa-ssl/lib") +(extra-sources "support/bundle-system-symbols.c") +(extra-ldflags + "build/release-inputs/jerboa-ssl/jerboa_ssl_shim.so" + "build/release-inputs/jerboa-fuse/libjerboa_fuse_mount.so" + "build/release-inputs/jerboa-proton-bridge/proton-bridge-native/target/release/libproton_bridge_native.so" + "build/release-inputs/jerboa-yubikey/yubikey-native/target/release/libyubikey_native.so" + "build/release-inputs/jerboa-pgp/pgp-native/target/release/libjpgp_native.so") +(main-c "support/bundle-main.c") +(ffi-symbols "support/bundle-ffi-symbols.list") +(static-native #t) --- a/Makefile +++ b/Makefile @@ -8,6 +8,7 @@ JERBUILD ?= $(shell if [ -x "$(CURDIR)/jerbuild" ]; then echo "$(CURDIR)/jerbuil else echo "$(JERBOA_TOOL_DIR)/jerbuild"; fi) JH = $(shell "$(JERBUILD)" --jerboa-home 2>/dev/null) JERBOA_HOME ?= $(JH) +NATIVE_LOADER_OVERRIDE_DIR := $(CURDIR)/support/vendor-overrides DEPS_DIR ?= $(CURDIR)/.deps LOCAL_PROTON_BRIDGE_DIR := $(CURDIR)/../jerboa-proton-bridge @@ -28,7 +29,7 @@ JERBOA_CRYPTO_DIR ?= $(if $(wildcard $(LOCAL_CRYPTO_DIR)/.),$(realpath $(LOCAL_C JERBOA_FUSE_DIR ?= $(if $(wildcard $(LOCAL_FUSE_DIR)/.),$(realpath $(LOCAL_FUSE_DIR)),$(DEPS_DIR)/jerboa-fuse) JERBOA_MAIL_DIR ?= $(if $(wildcard $(LOCAL_MAIL_DIR)/.),$(realpath $(LOCAL_MAIL_DIR)),$(DEPS_DIR)/jerboa-mail) JERBOA_AWS_DIR ?= $(if $(wildcard $(LOCAL_AWS_DIR)/.),$(realpath $(LOCAL_AWS_DIR)),$(DEPS_DIR)/jerboa-aws) -LIBDIRS = $(CURDIR):$(JERBOA_AWS_DIR)/lib:$(JERBOA_PROTON_BRIDGE_DIR):$(JERBOA_YUBIKEY_DIR):$(JERBOA_YUBIKEY_DIR)/lib:$(JERBOA_PGP_DIR):$(JERBOA_CRYPTO_DIR)/lib:$(JERBOA_FUSE_DIR)/lib:$(JERBOA_MAIL_DIR):$(JERBOA_HTTPS_DIR)/lib:$(JERBOA_SSL_DIR)/lib:$(JH)/lib +LIBDIRS = $(NATIVE_LOADER_OVERRIDE_DIR):$(CURDIR):$(JERBOA_AWS_DIR)/lib:$(JERBOA_PROTON_BRIDGE_DIR):$(JERBOA_YUBIKEY_DIR):$(JERBOA_YUBIKEY_DIR)/lib:$(JERBOA_PGP_DIR):$(JERBOA_CRYPTO_DIR)/lib:$(JERBOA_FUSE_DIR)/lib:$(JERBOA_MAIL_DIR):$(JERBOA_HTTPS_DIR)/lib:$(JERBOA_SSL_DIR)/lib:$(JH)/lib XC_LIBDIRS = $(LIBDIRS):$(JH)/.chez-cross-ta6le/lib:$(JH)/.chez-cross-tarm64le/lib:$(JH)/.chez-cross-ta6fb/lib JERBOA_NATIVE_DIR ?= $(JH)/jerboa-native-rs/target/release PROTON_BRIDGE_NATIVE_DIR := $(JERBOA_PROTON_BRIDGE_DIR)/proton-bridge-native/target/release @@ -42,10 +43,24 @@ else SO_EXT = .so FUSE_SO_EXT = .so endif +YUBIKEY_NATIVE_LIB := $(JERBOA_YUBIKEY_DIR)/yubikey-native/target/release/libyubikey_native$(SO_EXT) +PGP_NATIVE_LIB := $(JERBOA_PGP_DIR)/pgp-native/target/release/libjpgp_native$(SO_EXT) +PROTON_BRIDGE_NATIVE_LIB := $(PROTON_BRIDGE_NATIVE_DIR)/libproton_bridge_native$(SO_EXT) +SSL_NATIVE_LIB := $(JERBOA_SSL_DIR)/jerboa_ssl_shim$(SO_EXT) +FUSE_NATIVE_LIB := $(JERBOA_FUSE_DIR)/libjerboa_fuse_mount$(FUSE_SO_EXT) +BINARY_NATIVE_INPUT_DIR := $(CURDIR)/build/release-inputs +BINARY_RELEASE_CONFIG := $(if $(filter Darwin,$(UNAME_S)),.jerbuild.release-darwin,.jerbuild.release-elf) RUN_ENV := JERBOA_HOME=$(JH) \ JERBOA_SSL_LIB=$(JERBOA_SSL_DIR) \ + JERBOA_SSL_ALLOW_DYNAMIC_NATIVE=1 \ JERBOA_CRYPTO_LIB=$(JERBOA_CRYPTO_DIR) \ + JERBOA_CRYPTO_ALLOW_DYNAMIC_NATIVE=1 \ JERBOA_FUSE_LIB=$(JERBOA_FUSE_DIR) \ + JPGP_ALLOW_DYNAMIC_NATIVE=1 \ + JPGP_NATIVE_LIB=$(PGP_NATIVE_LIB) \ + JYUBIKEY_DEV_NATIVE=1 \ + JYUBIKEY_NATIVE_LIB=$(YUBIKEY_NATIVE_LIB) \ + PROTON_BRIDGE_NATIVE_LIB=$(PROTON_BRIDGE_NATIVE_LIB) \ DYLD_LIBRARY_PATH=$(JERBOA_NATIVE_DIR):$(JERBOA_CRYPTO_DIR):$(JERBOA_FUSE_DIR):$(PROTON_BRIDGE_NATIVE_DIR):$${DYLD_LIBRARY_PATH:-} \ DYLD_FALLBACK_LIBRARY_PATH=$(JERBOA_NATIVE_DIR):$${DYLD_FALLBACK_LIBRARY_PATH:-} \ LD_LIBRARY_PATH=$(JERBOA_NATIVE_DIR):$(JERBOA_CRYPTO_DIR):$(JERBOA_FUSE_DIR):$(PROTON_BRIDGE_NATIVE_DIR):$${LD_LIBRARY_PATH:-} @@ -63,7 +78,7 @@ DIST_DIR ?= $(CURDIR)/dist RELEASE_EVIDENCE_DIR ?= $(DIST_DIR)/release-evidence TARGET_EVIDENCE_DIR ?= $(DIST_DIR)/target-evidence -.PHONY: help ensure-jerboa-tools ensure-deps deps run test integration s3-integration doctor binary binary-doctor-smoke audit verify security dependency-evidence sbom reproducibility-report target-evidence release-evidence bundle install linux linux-amd64 linux-arm64 freebsd freebsd-amd64 clean +.PHONY: help ensure-jerboa-tools ensure-deps native-runtime deps run test integration s3-integration doctor binary-release-inputs binary binary-doctor-smoke audit verify security dependency-evidence sbom reproducibility-report target-evidence release-evidence bundle install linux linux-amd64 linux-arm64 freebsd freebsd-amd64 clean .DEFAULT_GOAL := help lib/libjerboa_native$(SO_EXT): ensure-jerboa-tools @@ -142,7 +157,11 @@ ensure-deps: @test -d "$(JERBOA_HTTPS_DIR)" || { echo "ERROR: JERBOA_HTTPS_DIR missing: $(JERBOA_HTTPS_DIR)" >&2; exit 1; } @test -d "$(JERBOA_SSL_DIR)" || { echo "ERROR: JERBOA_SSL_DIR missing: $(JERBOA_SSL_DIR)" >&2; exit 1; } -deps: ensure-jerboa-tools ensure-deps lib/libjerboa_native$(SO_EXT) +native-runtime: ensure-jerboa-tools + @command -v cargo >/dev/null 2>&1 || { echo "ERROR: cargo not found on PATH." >&2; exit 1; } + cargo build --release --manifest-path "$(JH)/jerboa-native-rs/Cargo.toml" --no-default-features --features tls,crypto + +deps: ensure-jerboa-tools ensure-deps native-runtime lib/libjerboa_native$(SO_EXT) @if [ ! -f "$(JERBOA_SSL_DIR)/lib/jerboa-ssl.sls" ] || [ ! -f "$(JERBOA_SSL_DIR)/jerboa_ssl_shim$(SO_EXT)" ]; then \ $(MAKE) -C "$(JERBOA_SSL_DIR)" JERBUILD="$(JERBUILD)" build; \ fi @@ -158,6 +177,12 @@ deps: ensure-jerboa-tools ensure-deps lib/libjerboa_native$(SO_EXT) @if [ ! -f "$(PROTON_BRIDGE_NATIVE_DIR)/libproton_bridge_native$(SO_EXT)" ]; then \ $(MAKE) -C "$(JERBOA_PROTON_BRIDGE_DIR)" JERBUILD="$(JERBUILD)" native; \ fi + @if [ ! -f "$(YUBIKEY_NATIVE_LIB)" ]; then \ + $(MAKE) -C "$(JERBOA_YUBIKEY_DIR)" JERBUILD="$(JERBUILD)" native; \ + fi + @if [ ! -f "$(PGP_NATIVE_LIB)" ]; then \ + $(MAKE) -C "$(JERBOA_PGP_DIR)" JERBUILD="$(JERBUILD)" build-native; \ + fi run: deps $(RUN_ENV) \ @@ -178,8 +203,25 @@ s3-integration: deps $(RUN_ENV) \ "$(JERBUILD)" exec --libdirs "$(LIBDIRS)" test/integration-s3.ss -binary: deps - $(RUN_ENV) "$(JERBUILD)" binary --libdirs "$(LIBDIRS)" support/binary-entry.ss $(BINARY_OUTPUT) +binary-release-inputs: deps + rm -rf "$(BINARY_NATIVE_INPUT_DIR)" + mkdir -p "$(BINARY_NATIVE_INPUT_DIR)" + ln -s "$(JERBOA_AWS_DIR)" "$(BINARY_NATIVE_INPUT_DIR)/jerboa-aws" + ln -s "$(JERBOA_PROTON_BRIDGE_DIR)" "$(BINARY_NATIVE_INPUT_DIR)/jerboa-proton-bridge" + ln -s "$(JERBOA_YUBIKEY_DIR)" "$(BINARY_NATIVE_INPUT_DIR)/jerboa-yubikey" + ln -s "$(JERBOA_PGP_DIR)" "$(BINARY_NATIVE_INPUT_DIR)/jerboa-pgp" + ln -s "$(JERBOA_CRYPTO_DIR)" "$(BINARY_NATIVE_INPUT_DIR)/jerboa-crypto" + ln -s "$(JERBOA_FUSE_DIR)" "$(BINARY_NATIVE_INPUT_DIR)/jerboa-fuse" + ln -s "$(JERBOA_MAIL_DIR)" "$(BINARY_NATIVE_INPUT_DIR)/jerboa-mail" + ln -s "$(JERBOA_HTTPS_DIR)" "$(BINARY_NATIVE_INPUT_DIR)/jerboa-https" + ln -s "$(JERBOA_SSL_DIR)" "$(BINARY_NATIVE_INPUT_DIR)/jerboa-ssl" + +binary: binary-release-inputs + JERBOA_NATIVE_A="$(JERBOA_NATIVE_DIR)/libjerboa_native.a" \ + "$(JERBUILD)" build --config "$(BINARY_RELEASE_CONFIG)" + @if [ "$(BINARY_OUTPUT)" != protonstorage-bin ]; then \ + cp protonstorage-bin "$(BINARY_OUTPUT)"; \ + fi binary-doctor-smoke: binary $(RUN_ENV) ./$(BINARY_OUTPUT) doctor @@ -191,6 +233,12 @@ verify: security test doctor audit reproducibility-report sbom target-evidence security: @test -f scripts/sanitize-evidence.sh || { echo "missing scripts/sanitize-evidence.sh" >&2; exit 1; } + @bash scripts/package-security-test.sh + @test -f dependency-lock.tsv || { echo "missing dependency-lock.tsv" >&2; exit 1; } + @test -f support/fetch-locked-deps.sh || { echo "missing locked dependency fetcher" >&2; exit 1; } + @! grep -R -E 'uses:[[:space:]]*[^[:space:]]+@(v[0-9]+|main|master|stable)([[:space:]]|$$)' .github/workflows + @! grep -R -F 'git clone --depth 1' .github support scripts + @awk -F '\t' 'NF != 4 || $$1 !~ /^[a-z0-9-]+$$/ || $$3 !~ /^[0-9a-f]{40}$$/ || $$4 !~ /^[0-9a-f]{40}$$/ { exit 1 } END { if (NR != 9) exit 1 }' dependency-lock.tsv @grep -q 'sanitize-evidence' docs/release-evidence.md || { echo "missing release evidence sanitizer docs" >&2; exit 1; } @grep -q 'host-private' docs/release-evidence.md || { echo "missing host-private proof docs" >&2; exit 1; } @set -eu; \ @@ -269,7 +317,8 @@ release-evidence: bundle: binary JERBOA_PROTON_BRIDGE_DIR="$(JERBOA_PROTON_BRIDGE_DIR)" \ - JERBOA_CRYPTO_DIR="$(JERBOA_CRYPTO_DIR)" \ + JERBOA_YUBIKEY_DIR="$(JERBOA_YUBIKEY_DIR)" \ + JERBOA_PGP_DIR="$(JERBOA_PGP_DIR)" \ JERBOA_FUSE_DIR="$(JERBOA_FUSE_DIR)" \ JERBOA_SSL_DIR="$(JERBOA_SSL_DIR)" \ bash support/package-bundle.sh "$(BINARY_OUTPUT)" "$(BUNDLE_OUTPUT)" @@ -277,17 +326,7 @@ bundle: binary install: bundle mkdir -p "$(INSTALL_BINDIR)" "$(INSTALL_OPTDIR)" rsync -a --delete "$(BUNDLE_OUTPUT)/" "$(INSTALL_OPTDIR)/" - { \ - echo '#!/usr/bin/env bash'; \ - echo 'set -euo pipefail'; \ - echo 'DIR="$(INSTALL_OPTDIR)"'; \ - echo 'export DYLD_LIBRARY_PATH="$$DIR/lib:$${DYLD_LIBRARY_PATH:-}"'; \ - echo 'export LD_LIBRARY_PATH="$$DIR/lib:$${LD_LIBRARY_PATH:-}"'; \ - echo 'export JERBOA_SSL_LIB="$$DIR/lib"'; \ - echo 'export JERBOA_CRYPTO_LIB="$$DIR/lib"'; \ - echo 'exec "$$DIR/bin/protonstorage" "$$@"'; \ - } > "$(INSTALL_BINDIR)/jdrive" - chmod +x "$(INSTALL_BINDIR)/jdrive" + ln -sfn "$(INSTALL_OPTDIR)/jdrive" "$(INSTALL_BINDIR)/jdrive" @echo "Installed jdrive -> $(INSTALL_BINDIR)/jdrive" linux: linux-amd64 @@ -328,7 +367,7 @@ freebsd-amd64: bash $(JERBOA_HOME)/support/build-binary.sh support/binary-entry.ss protonstorage-freebsd-amd64 clean: - rm -rf cache tmp mount dist protonstorage-bin protonstorage-linux-amd64 protonstorage-linux-arm64 protonstorage-freebsd-amd64 + rm -rf cache tmp mount dist build/release-inputs protonstorage-bin protonstorage-linux-amd64 protonstorage-linux-arm64 protonstorage-freebsd-amd64 rm -f lib/libjerboa_native$(SO_EXT) rm -f protonstorage-bin-main.c protonstorage-bin.wp.so rm -f protonstorage-linux-amd64-main.c protonstorage-linux-amd64.wp.so --- a/README.md +++ b/README.md @@ -47,7 +47,8 @@ jdrive s3 profile-status jdrive s3 unlock-test ``` -For S3-compatible providers, add `--endpoint URL --path-style` when needed. +For S3-compatible providers, add an HTTPS `--endpoint URL --path-style` when +needed. Plaintext S3 endpoints are rejected. Use `--aws-profile NAME` to select an AWS profile. Add optional built-in YubiKey PIV unlock material: @@ -92,6 +93,11 @@ remote-to-local, and remote-to-remote encrypted copies. `check` and `sync` support simple `*` wildcards through repeated `--include PATTERN` and `--exclude PATTERN`. +Decrypted remote names are validated again when a manifest is loaded. Recursive +downloads use a retained destination-directory descriptor, no-follow component +walks, and atomic file publication, so traversal components and concurrent +symlink replacement cannot redirect output outside the selected local root. + ## Runtime Status Current runtime code is Scheme/Jerboa only: @@ -329,7 +335,11 @@ provider multipart upload. `make bundle` builds the host binary and writes `dist/protonstorage-<os>-<arch>.tar.gz` with the binary, native Jerboa -libraries, and a wrapper that sets the runtime library search path. +libraries, and a native launcher. The release binary records only +bundle-relative native-library paths. The launcher validates the bundle's +ownership and modes and removes inherited loader and development-native +selectors before starting it; development `make run` keeps its explicit local +library workflow. Release dependency and provenance evidence is documented in [`docs/dependency-provenance.md`](docs/dependency-provenance.md) and new file mode 100644 --- /dev/null +++ b/dependency-lock.tsv @@ -0,0 +1,9 @@ +jerboa-aws https://git.sr.ht/~lisp/jerboa-aws c1e6296c2ca17ec1e3f100876d433b12f613e22a 5c17cc5fc069c06b7a3f40510c3ae05e2b9911f7 +jerboa-proton-bridge https://git.sr.ht/~lisp/jerboa-proton-bridge 43035ebf6b7e679d160699469f7e9c66f922478c dc1b1d495eaae53475376004862dd28fd38dc2b3 +jerboa-yubikey https://git.sr.ht/~lisp/jerboa-yubikey 0091e16bfe2f5d96e7fb18ce016e5b1ce010edae a1fb427573692992cf68fea8eb0aea984cce3bce +jerboa-pgp https://git.sr.ht/~lisp/jerboa-pgp 38c681f41abde4d8ae05fa922c4383035c5e90e6 60300c5757b4bdfe15fb85e24baa9b2053b01a1b +jerboa-crypto https://git.sr.ht/~lisp/jerboa-crypto 74675676105775de23c55df65da27d8d2ef4a1d7 a49d8a0ce63731081f7fb140e13eff8d3efb850e +jerboa-fuse https://git.sr.ht/~lisp/jerboa-fuse 020ab5f8872261142b4f4c8d9379945db42f59fc 7184ad9a92719af4a08850a5f7a9ce4263ababdb +jerboa-mail https://git.sr.ht/~lisp/jerboa-mail 5d6382371decb5ce93016b46a1710d65255401b0 48b15dbdb9e18749b6195f1cbbd7d2804c54b6a6 +jerboa-https https://git.sr.ht/~lisp/jerboa-https c3a4be506edcdddeed37c4b7723bdd226a8511c1 f979837172aff40da3108c365afe45616579176d +jerboa-ssl https://git.sr.ht/~lisp/jerboa-ssl 270b858534941baad6420e4906a26c279f4a8678 4d629174bbff9b1cd2ff1aa696be0cd33bf462e4 --- a/docs/dependency-provenance.md +++ b/docs/dependency-provenance.md @@ -10,7 +10,15 @@ the local source tree and the sibling Jerboa libraries it delegates to: - `jerboa-aws` for S3-compatible storage requests. - `jerboa-pgp`, `jerboa-crypto`, `jerboa-fuse`, `jerboa-mail`, `jerboa-https`, and `jerboa-ssl` for crypto, filesystem, mail/HTTP, and TLS - support. +support. + +CI dependency inputs are declared in the checked-in `dependency-lock.tsv`. +Each row fixes both a 40-hex Git commit and its expected tree hash. The CI +fetcher initializes an empty repository, fetches only that commit, checks the +detached `HEAD`, and rejects a tree mismatch before any dependency build code +runs. Updating a dependency therefore requires a reviewed lock-file change; +default branches are never build inputs. The lock and its SHA-256 are copied +into the generated SBOM evidence. `make dependency-evidence` runs sibling `release-evidence` targets when they exist and fails closed when any required sibling gate fails or is missing. --- a/docs/security-model.md +++ b/docs/security-model.md @@ -43,6 +43,16 @@ marker and vault writers only write known profile-owned files. S3 sync and cp refuse local symlinks to avoid following attacker-controlled paths during tree uploads. +Encrypted manifest paths are validated both when a client creates a name and +after manifest authentication/decryption. Empty, dot, dot-dot, repeated-slash, +alternate-separator, reserved-device, control-character, and overlong +components fail closed. Remote-to-local copies retain a descriptor for the +selected destination root, open/create every descendant directory with +no-follow semantics, and publish each `0600` file by an exclusive temporary +inode plus `fsync` and descriptor-relative atomic rename. A concurrent symlink +or directory replacement therefore cannot redirect the final write outside the +retained root. + Current daemon state is foreground-supervised. `drive daemon stop` clears local marker files; unmounting a live FUSE mount is still delegated to the platform unmount command. @@ -61,6 +71,13 @@ Release builds must audit the Proton bridge Rust dependency graph with dependency provenance for the sibling Jerboa libraries listed in [`dependency-provenance.md`](dependency-provenance.md). +Host release bundles link their native providers directly and rewrite those +dependencies to bundle-relative RPATHs. The `jdrive` launcher resolves its own +canonical location, rejects symlinked, foreign-owned, or group/world-writable +bundle components, clears ambient dynamic-loader and development-native +selectors, and then executes the bundled program. Pathname-based native +overrides remain available only to the explicit `make run` development flow. + ## Network And Remote State Network access is explicit through Proton Drive API calls and S3-compatible --- a/protonstorage/cli.ss +++ b/protonstorage/cli.ss @@ -9,8 +9,7 @@ meta atom? partition sort sort! make-date make-time read-file-string path-join) (only (jerboa core) - getenv directory-files file-info file-info-type - open-process process-status) + getenv directory-files file-info file-info-type) (protonstorage drive api) (protonstorage drive auth) (protonstorage drive client) @@ -28,6 +27,7 @@ proton-session-uid proton-session-access-token proton-session-refresh-token) + (only (proton-bridge terminal) read-secret-from-tty) (only (std text json) json-object->string)) (define version "0.1.0") @@ -213,34 +213,8 @@ (eprintln msg) (exit code)) - (define (disable-tty-echo) - (guard (e [(condition? e) #f]) - (let ([proc (open-process - (list 'path: "sh" - 'arguments: - (list "-c" "stty -echo </dev/tty 2>/dev/null")))]) - (process-status proc) - #t))) - - (define (enable-tty-echo) - (guard (e [(condition? e) #f]) - (let ([proc (open-process - (list 'path: "sh" - 'arguments: - (list "-c" "stty echo </dev/tty 2>/dev/null")))]) - (process-status proc) - #t))) - (define (read-secret prompt) - (display prompt (current-error-port)) - (let ([line #f]) - (dynamic-wind - disable-tty-echo - (lambda () (set! line (get-line (current-input-port)))) - (lambda () - (enable-tty-echo) - (newline (current-error-port)))) - (if (eof-object? line) "" line))) + (read-secret-from-tty prompt)) (define (strip-script-separator args) (if (and (pair? args) (string=? (car args) "--")) --- a/protonstorage/drive/credentials.ss +++ b/protonstorage/drive/credentials.ss @@ -23,9 +23,9 @@ (define vault-version 1) (define vault-cipher "chacha20-poly1305") (define vault-kdf "scrypt") - (define vault-scrypt-N 32768) + (define vault-scrypt-N 65536) (define vault-scrypt-r 8) - (define vault-scrypt-p 1) + (define vault-scrypt-p 2) (define (json-object . fields) (let ([ht (make-hashtable equal-hash equal?)]) --- a/protonstorage/drive/crypto.ss +++ b/protonstorage/drive/crypto.ss @@ -75,7 +75,8 @@ base64-string->u8vector u8vector->base64-string) (jerboa-crypto) - (proton-bridge crypto)) + (proton-bridge crypto) + (proton-bridge native-loader)) (defstruct proton-drive-unlocked-key (id private-key passphrase source)) @@ -211,22 +212,6 @@ [else (error who "native Drive crypto backend failed" rc)])))) - (define *native-paths* - '("libproton_bridge_native.so" - "libproton_bridge_native.dylib" - "proton-bridge-native/target/release/libproton_bridge_native.so" - "proton-bridge-native/target/release/libproton_bridge_native.dylib" - "./proton-bridge-native/target/release/libproton_bridge_native.so" - "./proton-bridge-native/target/release/libproton_bridge_native.dylib" - "../jerboa-proton-bridge/proton-bridge-native/target/release/libproton_bridge_native.so" - "../jerboa-proton-bridge/proton-bridge-native/target/release/libproton_bridge_native.dylib" - "proton-bridge-native/target/debug/libproton_bridge_native.so" - "proton-bridge-native/target/debug/libproton_bridge_native.dylib" - "./proton-bridge-native/target/debug/libproton_bridge_native.so" - "./proton-bridge-native/target/debug/libproton_bridge_native.dylib" - "../jerboa-proton-bridge/proton-bridge-native/target/debug/libproton_bridge_native.so" - "../jerboa-proton-bridge/proton-bridge-native/target/debug/libproton_bridge_native.dylib")) - (define *native-loaded?* #f) (define *native-bindings-ready?* #f) (define c-drive-session-key #f) @@ -234,14 +219,14 @@ (define (try-load-native!) (or *native-loaded?* - (let loop ([ps *native-paths*]) - (cond - [(null? ps) #f] - [(guard (e [#t #f]) - (load-shared-object* (car ps)) #t) - (set! *native-loaded?* #t) - #t] - [else (loop (cdr ps))])))) + (and (proton-crypto-native-available?) + (begin (set! *native-loaded?* #t) #t)) + (let ([path (proton-native-configured-path)]) + (and path + (begin + (load-shared-object* path) + (set! *native-loaded?* #t) + #t))))) (define (ensure-native-bindings!) (when (and (try-load-native!) (not *native-bindings-ready?*)) --- a/protonstorage/s3/drive.ss +++ b/protonstorage/s3/drive.ss @@ -16,6 +16,7 @@ jdrive-s3-encrypt-chunk-bytes jdrive-s3-decrypt-chunk-bytes jdrive-s3-empty-manifest + jdrive-s3-manifest-validate! jdrive-s3-manifest-add-directory jdrive-s3-manifest-add-file jdrive-s3-manifest-list @@ -43,6 +44,7 @@ file-info file-info-size file-info-type) (only (std text json) json-object->string string->json-object) (only (std text base64) base64-string->u8vector u8vector->base64-string) + (std os secure-output) (jerboa-crypto) (yubikey auth) (jerboa-aws s3 api) @@ -58,9 +60,9 @@ (define vault-version 1) (define vault-cipher "chacha20-poly1305") (define vault-kdf "scrypt") - (define vault-scrypt-N 32768) + (define vault-scrypt-N 65536) (define vault-scrypt-r 8) - (define vault-scrypt-p 1) + (define vault-scrypt-p 2) (define manifest-magic (string->utf8 "JDRIVE-MANIFEST-V1\n")) (define file-magic (string->utf8 "JDRIVE-FILE-V1\n")) (define chunk-magic (string->utf8 "JDRIVE-CHUNK-V1\n")) @@ -534,14 +536,91 @@ (number-pad-left index 8) ".bin"))) + (define remote-path-max-bytes 4096) + (define remote-component-max-bytes 255) + + (define (string-downcase-ascii value) + (list->string (map char-downcase (string->list value)))) + + (define (windows-device-component? component) + (let* ([lower (string-downcase-ascii component)] + [separator + (let loop ([i 0]) + (cond + [(= i (string-length lower)) #f] + [(or (char=? (string-ref lower i) #\.) + (char=? (string-ref lower i) #\:)) i] + [else (loop (+ i 1))]))] + [base (if separator (substring lower 0 separator) lower)]) + (or (member base '("con" "prn" "aux" "nul")) + (and (= (string-length base) 4) + (or (string=? (substring base 0 3) "com") + (string=? (substring base 0 3) "lpt")) + (char>=? (string-ref base 3) #\1) + (char<=? (string-ref base 3) #\9))))) + + (define (validate-remote-component! component) + (unless (and (> (string-length component) 0) + (not (string=? component ".")) + (not (string=? component "..")) + (<= (bytevector-length (string->utf8 component)) + remote-component-max-bytes) + (not (windows-device-component? component))) + (error 'remote-path-normalize "unsafe remote path component" component)) + (let ([last (string-ref component (- (string-length component) 1))]) + (when (or (char=? last #\space) (char=? last #\.)) + (error 'remote-path-normalize + "remote path component may not end in space or dot" + component))) + (let loop ([i 0]) + (when (< i (string-length component)) + (let* ([ch (string-ref component i)] + [code (char->integer ch)]) + (when (or (char=? ch #\\) + (= code 0) + (< code 32) + (= code 127)) + (error 'remote-path-normalize + "remote path component contains a forbidden character" + component)) + (loop (+ i 1)))))) + + (define (validate-canonical-remote-path! path) + (when (> (bytevector-length (string->utf8 path)) remote-path-max-bytes) + (error 'remote-path-normalize "remote path exceeds byte limit")) + (unless (and (> (string-length path) 0) + (char=? (string-ref path 0) #\/)) + (error 'remote-path-normalize "remote path must be rooted" path)) + (unless (string=? path "/") + (let loop ([start 1] [at 1]) + (cond + [(= at (string-length path)) + (validate-remote-component! (substring path start at))] + [(char=? (string-ref path at) #\/) + (validate-remote-component! (substring path start at)) + (loop (+ at 1) (+ at 1))] + [else (loop start (+ at 1))]))) + path) + (define (remote-path-normalize path) (let ([path (nonempty-string 'remote-path-normalize 'path path)]) (let ([path (if (char=? (string-ref path 0) #\/) path (string-append "/" path))]) - (let ([trimmed (trim-trailing-slash path)]) - (if (= (string-length trimmed) 0) "/" trimmed))))) + (when (and (> (string-length path) 1) + (char=? (string-ref path (- (string-length path) 1)) #\/) + (char=? (string-ref path (- (string-length path) 2)) #\/)) + (error 'remote-path-normalize + "remote path contains an empty trailing component" path)) + (let* ([trailing-directory-marker? + (and (> (string-length path) 1) + (char=? (string-ref path (- (string-length path) 1)) #\/))] + [normalized + (if trailing-directory-marker? + (substring path 0 (- (string-length path) 1)) + path)]) + (validate-canonical-remote-path! normalized))))) (define (remote-dir-normalize path) (let ([path (remote-path-normalize path)]) @@ -708,6 +787,57 @@ (let ([entries (jmaybe manifest "Entries" '())]) (if (list? entries) entries '()))) + (define (jdrive-s3-manifest-validate! manifest) + (unless (hashtable? manifest) + (error 'jdrive-s3-manifest-validate! "manifest must be a JSON object")) + (let ([version (jref 'jdrive-s3-manifest-validate! manifest "Version")] + [directories (jref 'jdrive-s3-manifest-validate! manifest "Directories")] + [entries (jref 'jdrive-s3-manifest-validate! manifest "Entries")]) + (unless (and (integer? version) (= version 1)) + (error 'jdrive-s3-manifest-validate! "unsupported manifest version" version)) + (unless (list? directories) + (error 'jdrive-s3-manifest-validate! "manifest Directories must be a list")) + (unless (list? entries) + (error 'jdrive-s3-manifest-validate! "manifest Entries must be a list")) + (let directory-loop ([remaining directories] [seen '()]) + (unless (null? remaining) + (let ([path (car remaining)]) + (unless (string? path) + (error 'jdrive-s3-manifest-validate! + "manifest directory path must be a string")) + (let ([normalized (remote-dir-normalize path)]) + (unless (string=? normalized path) + (error 'jdrive-s3-manifest-validate! + "manifest directory path is not canonical" path)) + (when (string-list-member? path seen) + (error 'jdrive-s3-manifest-validate! + "duplicate manifest directory path" path)) + (directory-loop (cdr remaining) (cons path seen)))))) + (let entry-loop ([remaining entries] [seen '()]) + (unless (null? remaining) + (let ([entry (car remaining)]) + (unless (hashtable? entry) + (error 'jdrive-s3-manifest-validate! + "manifest entry must be a JSON object")) + (unless (string=? (jref 'jdrive-s3-manifest-validate! entry "Type") + "file") + (error 'jdrive-s3-manifest-validate! + "manifest entry type must be file")) + (let ([path (jref 'jdrive-s3-manifest-validate! entry "Path")]) + (unless (string? path) + (error 'jdrive-s3-manifest-validate! + "manifest file path must be a string")) + (let ([normalized (remote-path-normalize path)]) + (unless (string=? normalized path) + (error 'jdrive-s3-manifest-validate! + "manifest file path is not canonical" path)) + (when (or (string-list-member? path seen) + (string-list-member? path directories)) + (error 'jdrive-s3-manifest-validate! + "duplicate or ambiguous manifest file path" path)) + (entry-loop (cdr remaining) (cons path seen))))))) + manifest)) + (define (string-list-member? value xs) (let loop ([xs xs]) (cond @@ -844,14 +974,15 @@ (string->utf8 (json-object->string manifest)))) (define (sealed->manifest drive-key sealed) - (string->json-object - (utf8->string - (open-sealed-bytes - 'sealed->manifest - manifest-magic - drive-key - (manifest-aad) - sealed)))) + (jdrive-s3-manifest-validate! + (string->json-object + (utf8->string + (open-sealed-bytes + 'sealed->manifest + manifest-magic + drive-key + (manifest-aad) + sealed))))) (define (load-manifest client bucket config drive-key) (let ([key (manifest-object-key config)]) @@ -1683,10 +1814,11 @@ (add-summary! summary "CopiedBytes" (bytevector-length plain)) (inc-summary! summary "CopiedChunks")))) - (define (copy-entry-to-local! client bucket drive-key path entry local-path summary) - (ensure-directory! (parent-directory local-path)) - (call-with-port - (open-file-output-port local-path (file-options no-fail) (buffer-mode block)) + (define (copy-entry-to-secure-root! + client bucket drive-key path entry root-handle relative-path summary) + (call-with-secure-output-file + root-handle + relative-path (lambda (out) (write-entry-to-port! client bucket drive-key path entry out summary))) (inc-summary! summary "CopiedFiles")) @@ -1698,29 +1830,41 @@ (error 'copy-remote-file-to-local! "remote file not found" path)) (let ([local-path (local-destination-for-remote-file path local-destination)]) - (copy-entry-to-local! client bucket drive-key path entry local-path summary) + (let ([root-handle + (secure-directory-open (parent-directory local-path) #t)] + [name (path-basename local-path)]) + (dynamic-wind + (lambda () (void)) + (lambda () + (copy-entry-to-secure-root! + client bucket drive-key path entry root-handle name summary)) + (lambda () (secure-directory-close root-handle)))) local-path))) (define (copy-remote-tree-to-local! client bucket drive-key manifest remote-root local-destination summary) (let* ([root (remote-dir-normalize remote-root)] - [local-root (local-destination-for-remote-directory root local-destination)]) - (ensure-directory! local-root) - (inc-summary! summary "CreatedDirectories") - (for-each - (lambda (dir) - (when (under-prefix? root dir) - (let ([rel (remote-relative-path root dir)]) - (unless (string=? rel "") - (ensure-directory! (path-join local-root rel)) - (inc-summary! summary "CreatedDirectories"))))) - (manifest-directories manifest)) - (for-each - (lambda (entry) - (let* ([path (entry-path entry)] - [rel (remote-relative-path root path)] - [local-path (path-join local-root rel)]) - (copy-entry-to-local! client bucket drive-key path entry local-path summary))) - (manifest-file-entries-under manifest root '() '())) + [local-root (local-destination-for-remote-directory root local-destination)] + [root-handle (secure-directory-open local-root #t)]) + (dynamic-wind + (lambda () (void)) + (lambda () + (inc-summary! summary "CreatedDirectories") + (for-each + (lambda (dir) + (when (under-prefix? root dir) + (let ([rel (remote-relative-path root dir)]) + (unless (string=? rel "") + (secure-directory-mkdirs root-handle rel) + (inc-summary! summary "CreatedDirectories"))))) + (manifest-directories manifest)) + (for-each + (lambda (entry) + (let* ([path (entry-path entry)] + [rel (remote-relative-path root path)]) + (copy-entry-to-secure-root! + client bucket drive-key path entry root-handle rel summary))) + (manifest-file-entries-under manifest root '() '()))) + (lambda () (secure-directory-close root-handle))) local-root)) (define (copy-single-entry-remote-to-remote! new file mode 100755 --- /dev/null +++ b/scripts/package-security-test.sh @@ -0,0 +1,91 @@ +#!/usr/bin/env bash +set -euo pipefail + +repo_root=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) +tmp=$(mktemp -d "$repo_root/.package-security.XXXXXX") +trap 'rm -rf "$tmp"' EXIT HUP INT TERM +umask 077 + +if grep -nE 'export (DYLD_|LD_|JERBOA_|JPGP_|JYUBIKEY_|PROTON_BRIDGE_)' \ + "$repo_root/support/package-bundle.sh"; then + echo "production package script still emits native-loader overrides" >&2 + exit 1 +fi +if grep -nE "echo 'export (DYLD_|LD_|JERBOA_|JPGP_|JYUBIKEY_|PROTON_BRIDGE_)" \ + "$repo_root/Makefile"; then + echo "installed launcher still emits native-loader overrides" >&2 + exit 1 +fi +grep -q 'ffi-symbols "support/bundle-ffi-symbols.list"' \ + "$repo_root/.jerbuild.release-darwin" +grep -q '(static-native #t)' "$repo_root/.jerbuild.release-darwin" +grep -q 'scrub_loader_environment' "$repo_root/support/bundle-launcher.c" + +mkdir -p "$tmp/bundle/bin" "$tmp/bundle/lib" +cc -std=c11 -O2 -Wall -Wextra -Werror \ + "$repo_root/support/bundle-launcher.c" -o "$tmp/bundle/jdrive" +cc -std=c11 -O2 -Wall -Wextra -Werror \ + "$repo_root/test/bundle-env-probe.c" -o "$tmp/bundle/bin/protonstorage" + +case $(uname -s) in + Darwin) + required='libproton_bridge_native.dylib jerboa_ssl_shim.dylib libyubikey_native.dylib libjpgp_native.dylib libjerboa_fuse_mount.so' + ;; + *) + required='libproton_bridge_native.so jerboa_ssl_shim.so libyubikey_native.so libjpgp_native.so libjerboa_fuse_mount.so' + ;; +esac +for library in $required; do + : > "$tmp/bundle/lib/$library" + chmod 0644 "$tmp/bundle/lib/$library" +done +chmod 0755 "$tmp/bundle" "$tmp/bundle/bin" "$tmp/bundle/lib" \ + "$tmp/bundle/jdrive" "$tmp/bundle/bin/protonstorage" + +output="$tmp/environment.txt" +env \ + JDRIVE_TEST_OUTPUT="$output" \ + DYLD_LIBRARY_PATH=/attacker/dyld \ + DYLD_FALLBACK_LIBRARY_PATH=/attacker/fallback \ + LD_LIBRARY_PATH=/attacker/ld \ + JERBOA_HOME=/attacker/jerboa \ + JERBOA_NATIVE_LIB=/attacker/native \ + JERBOA_SSL_LIB=/attacker/ssl \ + JERBOA_SSL_ALLOW_DYNAMIC_NATIVE=1 \ + JERBOA_CRYPTO_LIB=/attacker/crypto \ + JERBOA_CRYPTO_ALLOW_DYNAMIC_NATIVE=1 \ + JERBOA_FUSE_LIB=/attacker/fuse \ + JERBOA_FUSE_DEV_NATIVE=1 \ + JPGP_NATIVE_LIB=/attacker/pgp \ + JPGP_ALLOW_DYNAMIC_NATIVE=1 \ + JYUBIKEY_NATIVE_LIB=/attacker/yubikey \ + JYUBIKEY_DEV_NATIVE=1 \ + PROTON_BRIDGE_NATIVE_LIB=/attacker/proton \ + CHEZSCHEMELIBDIRS=/attacker/scheme \ + SCHEMEHEAPDIRS=/attacker/heap \ + "$tmp/bundle/jdrive" preserved-argument + +if grep -vE '^(ARG1=preserved-argument|[A-Z0-9_]+=<unset>)$' "$output"; then + echo "bundle launcher preserved a forbidden loader override" >&2 + exit 1 +fi +grep -q '^ARG1=preserved-argument$' "$output" + +chmod 0777 "$tmp/bundle/lib" +if "$tmp/bundle/jdrive" >/dev/null 2>"$tmp/writable.err"; then + echo "bundle launcher accepted a writable library directory" >&2 + exit 1 +fi +grep -q 'group/world-writable bundle component rejected' "$tmp/writable.err" +chmod 0755 "$tmp/bundle/lib" + +first_library=${required%% *} +rm -f "$tmp/bundle/lib/$first_library" +ln -s "${required##* }" "$tmp/bundle/lib/$first_library" +if "$tmp/bundle/jdrive" >/dev/null 2>"$tmp/symlink.err"; then + echo "bundle launcher accepted a symlinked native library" >&2 + exit 1 +fi +grep -q 'non-regular bundle component rejected' "$tmp/symlink.err" + +echo "package_security_status=pass" --- a/scripts/sbom.sh +++ b/scripts/sbom.sh @@ -91,6 +91,8 @@ hash_manifest "$repo_root/docs" "$out_dir/docs.sha256" hash_manifest "$repo_root/support" "$out_dir/support.sha256" hash_manifest "$repo_root/scripts" "$out_dir/scripts.sha256" hash_manifest "$repo_root/.github" "$out_dir/github-workflows.sha256" +sha256_file "$repo_root/dependency-lock.tsv" > "$out_dir/dependency-lock.sha256" +cp "$repo_root/dependency-lock.tsv" "$out_dir/dependency-lock.tsv" find "$repo_root" -maxdepth 1 -type f \ \( -name '*.ss' -o -name 'Makefile' -o -name 'README.md' -o -name 'SECURITY.md' -o -name 'LICENSE' -o -name '*.md' \) \ new file mode 100644 --- /dev/null +++ b/support/bundle-ffi-symbols.list @@ -0,0 +1,97 @@ +jerboa_conn_read +jerboa_conn_write +jerboa_fuse_block_signal +jerboa_fuse_blockstore_create_bv +jerboa_fuse_blockstore_open_bv +jerboa_fuse_close_fd +jerboa_fuse_fsync_fd +jerboa_fuse_gcm_decrypt +jerboa_fuse_gcm_encrypt +jerboa_fuse_get_errno +jerboa_fuse_getgid +jerboa_fuse_getpid +jerboa_fuse_getppid_of +jerboa_fuse_getuid +jerboa_fuse_mount +jerboa_fuse_mount_bv +jerboa_fuse_open_device +jerboa_fuse_pbkdf2_sha256 +jerboa_fuse_pread_full +jerboa_fuse_pwrite_full +jerboa_fuse_rand_bytes +jerboa_fuse_read_fd +jerboa_fuse_secmem_alloc +jerboa_fuse_secmem_copy_in_checked +jerboa_fuse_secmem_copy_out_checked +jerboa_fuse_secmem_free_checked +jerboa_fuse_secmem_zero_checked +jerboa_fuse_unblock_signal +jerboa_fuse_unmount +jerboa_fuse_unmount_bv +jerboa_fuse_unmount_lazy +jerboa_fuse_unmount_lazy_bv +jerboa_fuse_write_fd +jerboa_ssl_cleanup +jerboa_ssl_close +jerboa_ssl_connect +jerboa_ssl_free_buf +jerboa_ssl_init +jerboa_ssl_memcpy +jerboa_ssl_read +jerboa_ssl_read_all +jerboa_ssl_server_accept +jerboa_ssl_server_ctx +jerboa_ssl_server_ctx_free +jerboa_ssl_set_timeout +jerboa_ssl_write +jerboa_tcp_accept +jerboa_tcp_close +jerboa_tcp_conn_wrap +jerboa_tcp_connect +jerboa_tcp_listen +jerboa_tcp_read +jerboa_tcp_read_all +jerboa_tcp_set_timeout +jerboa_tcp_write +jpgp_abi_version +jpgp_age_decrypt +jpgp_age_encrypt +jpgp_age_keygen +jpgp_ed25519_keygen +jpgp_ed25519_sign +jpgp_ed25519_verify +jpgp_pass_decrypt +jpgp_pass_encrypt +jpgp_pgp_decrypt +jpgp_pgp_encrypt +jpgp_pgp_sign +jpgp_pgp_verify +jpgp_sha256 +pb_native_drive_decrypt_block +pb_native_drive_decrypt_session_key +pb_native_drive_encrypt_block +pb_native_drive_generate_content_key +pb_native_drive_generate_key +pb_native_drive_generate_passphrase +pb_native_mailbox_password +pb_native_pgp_decrypt +pb_native_pgp_encrypt +pb_native_pgp_encrypt_signed +pb_native_pgp_sign_detached +pb_native_pgp_sign_detached_encrypted +pb_native_proton_token_passphrase +pb_native_read_secret +pb_native_srp_available +pb_native_srp_last_error +pb_native_srp_proofs +yk_native_abi_version +yk_native_available +yk_native_connect +yk_native_disconnect +yk_native_fido2_assert +yk_native_list_readers +yk_native_random_bytes +yk_native_read_secret_fd +yk_native_read_secret_tty +yk_native_transmit +yk_native_write_secret_fd new file mode 100644 --- /dev/null +++ b/support/bundle-launcher.c @@ -0,0 +1,241 @@ +#define _POSIX_C_SOURCE 200809L + +#include <dirent.h> +#include <errno.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#if defined(__APPLE__) +#include <mach-o/dyld.h> +#elif defined(__FreeBSD__) +#include <sys/sysctl.h> +#endif + +#ifndef PATH_MAX +#define PATH_MAX 4096 +#endif + +static int copy_path(char *out, size_t out_size, const char *value) { + size_t length = strlen(value);