chore!: make jdrive S3-only
ober
82aa4cc2b7d498ca3d8fc7bff04b061f7631e2eb
--- a/.gitignore +++ b/.gitignore @@ -9,8 +9,6 @@ cache/ tmp/ mount/ dist/ +build/ *-main.c -protonstorage-bin -protonstorage-linux-amd64 -protonstorage-linux-arm64 -protonstorage-freebsd-amd64 +jdrive-bin --- a/.jerboa/security.json +++ b/.jerboa/security.json @@ -3,10 +3,10 @@ "repo": "jerboa-drive", "extends": ["jerboa:cli", "jerboa:crypto", "jerboa:ffi", "jerboa:network", "jerboa:credentialed-client", "jerboa:storage"], "paths": { - "production": ["*.ss", "*.sls", "lib/**/*.ss", "lib/**/*.sls", "protonstorage/**/*.ss", "support/**/*.ss", "Makefile"], + "production": ["*.ss", "*.sls", "lib/**/*.ss", "lib/**/*.sls", "jdrive/**/*.ss", "support/**/*.ss", "Makefile"], "tests": ["test/**", "tests/**", "**/*-test.ss", "fixtures/**"], "generated": ["build/**", "dist/**", "target/**", "*.so", "*.dylib", "*.wpo", "*-main.c"], - "vendor": ["vendor/**", "third_party/**"], + "vendor": ["vendor/**", "third_party/**", ".deps/**"], "docs": ["README.md", "docs/**", "*.md"] }, "policy": { @@ -16,7 +16,7 @@ "process": { "shellInterpolation": "deny" }, "network": { "requireTimeouts": true, "rawRemoteOutputEvidence": "deny" }, "eval": { "stringEval": "deny", "bareRead": "deny", "allowReadEval": false }, - "crypto": { "forbidSecretLogging": true, "requireRustSecCleanBackends": true }, + "crypto": { "forbidSecretLogging": true, "requireReviewedNativeBackends": true }, "credentials": { "plaintextCache": "deny", "rawCredentialEvidence": "deny", @@ -25,23 +25,22 @@ "storage": { "remotePlaintextFilenames": "deny", "remotePlaintextBodies": "deny", - "fuseMountEvidence": "target-required" + "rollbackDetection": "required" }, "targetEvidence": { "storageProof": "fail-closed", "forbidPrivateProofMaterial": true, "requiredMarkers": [ - "proton_live_drive_status=target-evidence-recorded", "s3_encrypted_roundtrip_status=target-evidence-recorded", - "fuse_mount_smoke_status=target-evidence-recorded", "credential_redaction_status=target-evidence-recorded", "no_plaintext_remote_state_status=target-evidence-recorded", "vault_permissions_status=target-evidence-recorded", - "proton_crypto_disabled_status=confirmed", + "manifest_rollback_status=target-evidence-recorded", "target_platform_smoke_status=target-evidence-recorded", + "crypto_ffi_review_status=current-run-recorded", "production_storage_review_status=current-run-recorded", "raw_remote_output_status=not-preserved", - "sensitive_artifact_policy=no-proton-passwords-tokens-s3-keys-vault-passwords-piv-pins-plaintext-files-or-raw-account-output-preserved" + "sensitive_artifact_policy=no-aws-keys-vault-passwords-piv-pins-plaintext-files-or-raw-account-output-preserved" ] } }, --- a/.jerbuild.release-darwin +++ b/.jerbuild.release-darwin @@ -1,25 +1,15 @@ (entry "support/binary-entry.ss") -(output "protonstorage-bin") +(output "jdrive-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") + "build/release-inputs/jerboa-crypto/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") + "build/release-inputs/jerboa-crypto/jerboa_crypto_shim.so" + "build/release-inputs/jerboa-yubikey/yubikey-native/target/release/libyubikey_native.dylib") (ffi-symbols "support/bundle-ffi-symbols.list") (static-native #t) --- a/.jerbuild.release-elf +++ b/.jerbuild.release-elf @@ -1,25 +1,15 @@ (entry "support/binary-entry.ss") -(output "protonstorage-bin") +(output "jdrive-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") + "build/release-inputs/jerboa-crypto/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") + "build/release-inputs/jerboa-crypto/jerboa_crypto_shim.so" + "build/release-inputs/jerboa-yubikey/yubikey-native/target/release/libyubikey_native.so") (ffi-symbols "support/bundle-ffi-symbols.list") (static-native #t) --- a/Makefile +++ b/Makefile @@ -1,9 +1,7 @@ -JERBOA_VERSION ?= v0.2.3 +JERBOA_VERSION ?= v0.2.8 JERBOA_TOOL_DIR ?= $(CURDIR)/.jerboa/bin JERBUILD ?= $(shell if [ -x "$(CURDIR)/jerbuild" ]; then echo "$(CURDIR)/jerbuild"; \ elif [ -x "$(JERBOA_TOOL_DIR)/jerbuild" ]; then echo "$(JERBOA_TOOL_DIR)/jerbuild"; \ - elif [ -x "$(CURDIR)/../jerboa/dist/jerbuild" ]; then echo "$(CURDIR)/../jerboa/dist/jerbuild"; \ - elif [ -x "$(CURDIR)/../jerboa/jerbuild" ]; then echo "$(CURDIR)/../jerboa/jerbuild"; \ elif command -v jerbuild >/dev/null 2>&1; then command -v jerbuild; \ else echo "$(JERBOA_TOOL_DIR)/jerbuild"; fi) JH = $(shell "$(JERBUILD)" --jerboa-home 2>/dev/null) @@ -11,63 +9,28 @@ JERBOA_HOME ?= $(JH) NATIVE_LOADER_OVERRIDE_DIR := $(CURDIR)/support/vendor-overrides DEPS_DIR ?= $(CURDIR)/.deps -LOCAL_PROTON_BRIDGE_DIR := $(CURDIR)/../jerboa-proton-bridge -LOCAL_HTTPS_DIR := $(CURDIR)/../jerboa-https -LOCAL_SSL_DIR := $(CURDIR)/../jerboa-ssl -LOCAL_YUBIKEY_DIR := $(CURDIR)/../jerboa-yubikey -LOCAL_PGP_DIR := $(CURDIR)/../jerboa-pgp -LOCAL_CRYPTO_DIR := $(CURDIR)/../jerboa-crypto -LOCAL_FUSE_DIR := $(CURDIR)/../jerboa-fuse -LOCAL_MAIL_DIR := $(CURDIR)/../jerboa-mail -LOCAL_AWS_DIR := $(CURDIR)/../jerboa-aws -JERBOA_PROTON_BRIDGE_DIR ?= $(if $(wildcard $(LOCAL_PROTON_BRIDGE_DIR)/.),$(realpath $(LOCAL_PROTON_BRIDGE_DIR)),$(DEPS_DIR)/jerboa-proton-bridge) -JERBOA_HTTPS_DIR ?= $(if $(wildcard $(LOCAL_HTTPS_DIR)/.),$(realpath $(LOCAL_HTTPS_DIR)),$(DEPS_DIR)/jerboa-https) -JERBOA_SSL_DIR ?= $(if $(wildcard $(LOCAL_SSL_DIR)/.),$(realpath $(LOCAL_SSL_DIR)),$(DEPS_DIR)/jerboa-ssl) -JERBOA_YUBIKEY_DIR ?= $(if $(wildcard $(LOCAL_YUBIKEY_DIR)/.),$(realpath $(LOCAL_YUBIKEY_DIR)),$(DEPS_DIR)/jerboa-yubikey) -JERBOA_PGP_DIR ?= $(if $(wildcard $(LOCAL_PGP_DIR)/.),$(realpath $(LOCAL_PGP_DIR)),$(DEPS_DIR)/jerboa-pgp) -JERBOA_CRYPTO_DIR ?= $(if $(wildcard $(LOCAL_CRYPTO_DIR)/.),$(realpath $(LOCAL_CRYPTO_DIR)),$(DEPS_DIR)/jerboa-crypto) -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 = $(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 +DEPS_STAMP := $(DEPS_DIR)/.locked +JERBOA_AWS_DIR ?= $(DEPS_DIR)/jerboa-aws +JERBOA_YUBIKEY_DIR ?= $(DEPS_DIR)/jerboa-yubikey +JERBOA_CRYPTO_DIR ?= $(DEPS_DIR)/jerboa-crypto +LIBDIRS = $(NATIVE_LOADER_OVERRIDE_DIR):$(CURDIR):$(JERBOA_AWS_DIR)/lib:$(JERBOA_YUBIKEY_DIR):$(JERBOA_YUBIKEY_DIR)/lib:$(JERBOA_CRYPTO_DIR)/lib:$(JH)/lib JERBOA_NATIVE_DIR ?= $(JH)/jerboa-native-rs/target/release -PROTON_BRIDGE_NATIVE_DIR := $(JERBOA_PROTON_BRIDGE_DIR)/proton-bridge-native/target/release UNAME_S := $(shell uname -s) ifeq ($(UNAME_S),Darwin) - LD_VAR = DYLD_LIBRARY_PATH SO_EXT = .dylib - FUSE_SO_EXT = .so else - LD_VAR = LD_LIBRARY_PATH 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) +YUBIKEY_NATIVE_DIR := $(JERBOA_YUBIKEY_DIR)/yubikey-native/target/release +YUBIKEY_NATIVE_LIB := $(YUBIKEY_NATIVE_DIR)/libyubikey_native$(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:-} -BINARY_OUTPUT ?= protonstorage-bin -LINUX_AMD64_CC ?= x86_64-linux-musl-gcc -LINUX_ARM64_CC ?= aarch64-linux-musl-gcc -FREEBSD_AMD64_CC ?= $(JERBOA_HOME)/support/cross-cc-freebsd-amd64 + JYUBIKEY_NATIVE_LIB=$(YUBIKEY_NATIVE_LIB) +BINARY_OUTPUT ?= jdrive-bin HOST_OS := $(shell uname -s | tr '[:upper:]' '[:lower:]') HOST_ARCH := $(shell uname -m) BUNDLE_OUTPUT ?= dist/jerboa-drive-$(HOST_OS)-$(HOST_ARCH) @@ -78,57 +41,25 @@ 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 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 lint +.PHONY: help ensure-jerboa-tools ensure-deps native-runtime deps run test s3-integration doctor binary-release-inputs binary binary-doctor-smoke audit verify security dependency-evidence sbom reproducibility-report target-evidence release-evidence bundle install clean lint .DEFAULT_GOAL := help -lib/libjerboa_native$(SO_EXT): ensure-jerboa-tools - @mkdir -p lib - @src="$(JERBOA_NATIVE_DIR)/libjerboa_native$(SO_EXT)"; \ - sibling="$(CURDIR)/../jerboa/lib/libjerboa_native$(SO_EXT)"; \ - if [ -f "$$src" ]; then \ - ln -sf "$$src" "$@"; \ - elif [ -f "$$sibling" ]; then \ - ln -sf "$$sibling" "$@"; \ - else \ - echo "ERROR: libjerboa_native$(SO_EXT) not found in $(JERBOA_NATIVE_DIR) or sibling jerboa/lib; run make -C ../jerboa native" >&2; \ - exit 1; \ - fi - help: @echo "jerboa-drive" @echo "" @echo "Development:" - @echo " make run ARGS='help' Run the CLI" - @echo " make doctor Check native Jerboa implementation status" - @echo " make test Run smoke tests" - @echo " make target-evidence Write target Drive/S3/FUSE proof status" - @echo " make release-evidence Write test/audit/SBOM/reproducibility evidence" - @echo " make integration Run opt-in live Drive test when enabled" - @echo " make s3-integration Run opt-in live encrypted S3 test when enabled" + @echo " make run ARGS='help' Run the S3 CLI" + @echo " make doctor Show S3 implementation status" + @echo " make test Run local S3 tests" + @echo " make s3-integration Run opt-in live encrypted S3 test" @echo " make clean Remove generated local artifacts" @echo "" @echo "Build:" - @echo " make Show this help" - @echo " make binary Build ./$(BINARY_OUTPUT) for this host" + @echo " make binary Build ./$(BINARY_OUTPUT)" @echo " make bundle Package ./$(BUNDLE_OUTPUT).tar.gz" @echo " make install Install jdrive to $(INSTALL_BINDIR)" - @echo " make linux-amd64 Cross-build ./protonstorage-linux-amd64" - @echo " make linux-arm64 Cross-build ./protonstorage-linux-arm64" - @echo " make freebsd-amd64 Cross-build ./protonstorage-freebsd-amd64" @echo "" - @echo "Environment:" - @echo " JERBUILD = $(JERBUILD)" - @echo " JERBOA_PROTON_BRIDGE_DIR = $(JERBOA_PROTON_BRIDGE_DIR)" - @echo " JERBOA_YUBIKEY_DIR = $(JERBOA_YUBIKEY_DIR)" - @echo " JERBOA_PGP_DIR = $(JERBOA_PGP_DIR)" - @echo " JERBOA_CRYPTO_DIR = $(JERBOA_CRYPTO_DIR)" - @echo " JERBOA_FUSE_DIR = $(JERBOA_FUSE_DIR)" - @echo " JERBOA_MAIL_DIR = $(JERBOA_MAIL_DIR)" - @echo " JERBOA_AWS_DIR = $(JERBOA_AWS_DIR)" - @echo " JERBOA_HTTPS_DIR = $(JERBOA_HTTPS_DIR)" - @echo " JERBOA_SSL_DIR = $(JERBOA_SSL_DIR)" - @echo " JERBOA_NATIVE_DIR = $(JERBOA_NATIVE_DIR)" - @echo " INSTALL_PREFIX = $(INSTALL_PREFIX)" + @echo "Dependencies are fetched at locked commits into $(DEPS_DIR)." ensure-jerboa-tools: @if "$(JERBUILD)" --jerboa-home >/dev/null 2>&1; then \ @@ -142,47 +73,24 @@ ensure-jerboa-tools: sh support/ensure-jerboa.sh "$(JERBOA_VERSION)" "$(JERBOA_TOOL_DIR)"; \ fi @"$(JERBUILD)" --jerboa-home >/dev/null || { \ - echo "ERROR: Jerboa toolchain is unavailable; set JERBUILD=/path/to/jerbuild or JERBOA_VERSION=<tag>" >&2; \ + echo "ERROR: Jerboa toolchain is unavailable; set JERBUILD=/path/to/jerbuild" >&2; \ exit 1; \ } -ensure-deps: - @test -d "$(JERBOA_PROTON_BRIDGE_DIR)" || { echo "ERROR: JERBOA_PROTON_BRIDGE_DIR missing: $(JERBOA_PROTON_BRIDGE_DIR)" >&2; exit 1; } - @test -d "$(JERBOA_AWS_DIR)" || { echo "ERROR: JERBOA_AWS_DIR missing: $(JERBOA_AWS_DIR)" >&2; exit 1; } - @test -d "$(JERBOA_YUBIKEY_DIR)" || { echo "ERROR: JERBOA_YUBIKEY_DIR missing: $(JERBOA_YUBIKEY_DIR)" >&2; exit 1; } - @test -d "$(JERBOA_PGP_DIR)" || { echo "ERROR: JERBOA_PGP_DIR missing: $(JERBOA_PGP_DIR)" >&2; exit 1; } - @test -d "$(JERBOA_CRYPTO_DIR)" || { echo "ERROR: JERBOA_CRYPTO_DIR missing: $(JERBOA_CRYPTO_DIR)" >&2; exit 1; } - @test -d "$(JERBOA_FUSE_DIR)" || { echo "ERROR: JERBOA_FUSE_DIR missing: $(JERBOA_FUSE_DIR)" >&2; exit 1; } - @test -d "$(JERBOA_MAIL_DIR)" || { echo "ERROR: JERBOA_MAIL_DIR missing: $(JERBOA_MAIL_DIR)" >&2; exit 1; } - @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_STAMP): dependency-lock.tsv support/fetch-locked-deps.sh + @sh support/fetch-locked-deps.sh dependency-lock.tsv .deps + @touch "$@" + +ensure-deps: $(DEPS_STAMP) 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 - @if [ ! -f "$(JERBOA_HTTPS_DIR)/lib/jerboa-https.sls" ]; then \ - $(MAKE) -C "$(JERBOA_HTTPS_DIR)" JERBUILD="$(JERBUILD)" SSL_DIR="$(JERBOA_SSL_DIR)" build; \ - fi - @if [ ! -f "$(JERBOA_CRYPTO_DIR)/lib/jerboa-crypto.sls" ] || [ ! -f "$(JERBOA_CRYPTO_DIR)/jerboa_crypto_shim.so" ]; then \ - $(MAKE) -C "$(JERBOA_CRYPTO_DIR)" JERBUILD="$(JERBUILD)" build; \ - fi - @if [ ! -f "$(JERBOA_FUSE_DIR)/lib/jerboa-fuse.sls" ] || [ ! -f "$(JERBOA_FUSE_DIR)/libjerboa_fuse_mount$(FUSE_SO_EXT)" ]; then \ - $(MAKE) -C "$(JERBOA_FUSE_DIR)" JERBUILD="$(JERBUILD)" build; \ - fi - @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 +deps: ensure-jerboa-tools ensure-deps native-runtime + @$(MAKE) -C "$(JERBOA_AWS_DIR)" JERBUILD="$(JERBUILD)" transpile + @$(MAKE) -C "$(JERBOA_CRYPTO_DIR)" JERBUILD="$(JERBUILD)" build + @$(MAKE) -C "$(JERBOA_YUBIKEY_DIR)" JERBUILD="$(JERBUILD)" native run: deps $(RUN_ENV) \ @@ -195,10 +103,6 @@ test: deps $(RUN_ENV) \ "$(JERBUILD)" exec --libdirs "$(LIBDIRS)" test/test-all.ss -integration: deps - $(RUN_ENV) \ - "$(JERBUILD)" exec --libdirs "$(LIBDIRS)" test/integration-drive.ss - s3-integration: deps $(RUN_ENV) \ "$(JERBUILD)" exec --libdirs "$(LIBDIRS)" test/integration-s3.ss @@ -207,27 +111,20 @@ 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 +binary-doctor-smoke: bundle + "$(BUNDLE_OUTPUT)/jdrive" doctor audit: deps - $(MAKE) -C "$(JERBOA_PROTON_BRIDGE_DIR)" JERBUILD="$(JERBUILD)" audit + @$(MAKE) -C "$(JERBOA_AWS_DIR)" JERBUILD="$(JERBUILD)" security + @$(MAKE) -C "$(JERBOA_CRYPTO_DIR)" JERBUILD="$(JERBUILD)" audit + @$(MAKE) -C "$(JERBOA_YUBIKEY_DIR)" JERBUILD="$(JERBUILD)" audit verify: security test doctor audit reproducibility-report sbom target-evidence @@ -236,46 +133,29 @@ lint: security 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 dependency-lock.tsv || { echo "missing dependency lock" >&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:]]|$$)' .forgejo/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 -R -F 'git clone --depth 1' .forgejo 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 != 3) 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; \ - pattern="(BEGIN (RSA|OPENSSH|EC|DSA|PRIVATE) KEY|ghp_[A-Za-z0-9_]{20,}|github_pat_[A-Za-z0-9_]{20,}|glpat-[A-Za-z0-9_-]{20,}|gldt-[A-Za-z0-9_-]{20,}|glrt-[A-Za-z0-9_-]{20,}|Authorization['\\\"]?[[:space:]]*[:=][[:space:]]*['\\\"]?Bearer[[:space:]]+[A-Za-z0-9_-]{20,}\\.[A-Za-z0-9_-]{20,}\\.[A-Za-z0-9_-]{20,}|PROTONDRIVE_(USERNAME|PASSWORD|VAULT_PASSWORD|ACCESS_TOKEN|REFRESH_TOKEN)=['\\\"]?[A-Za-z0-9_./+=:-]{24,}|JDRIVE_(S3_TEST_SECRET|S3_TEST_ACCESS|VAULT_PASSWORD)=['\\\"]?[A-Za-z0-9_./+=:-]{24,}|AKIA[0-9A-Z]{16}|ASIA[0-9A-Z]{16})"; \ + pattern="(BEGIN (RSA|OPENSSH|EC|DSA|PRIVATE) KEY|ghp_[A-Za-z0-9_]{20,}|github_pat_[A-Za-z0-9_]{20,}|glpat-[A-Za-z0-9_-]{20,}|gldt-[A-Za-z0-9_-]{20,}|glrt-[A-Za-z0-9_-]{20,}|Authorization['\"]?[[:space:]]*[:=][[:space:]]*['\"]?Bearer[[:space:]]+[A-Za-z0-9_-]{20,}\.[A-Za-z0-9_-]{20,}\.[A-Za-z0-9_-]{20,}|AKIA[0-9A-Z]{16}|ASIA[0-9A-Z]{16}|JDRIVE_(S3_TEST_SECRET|S3_TEST_ACCESS|VAULT_PASSWORD)=['\"]?[A-Za-z0-9_./+=:-]{24,})"; \ matches="$$(git grep -n -I -E "$$pattern" -- . ":!*.png" ":!*.jpg" ":!*.jpeg" ":!*.gif" ":!*.so" ":!*.dylib" ":!*.o" ":!*.a" ":!*.boot" ":!*.tar.gz" || true)"; \ - if [ -n "$$matches" ]; then \ - echo "$$matches"; \ - echo "High-confidence secret pattern found."; \ - exit 1; \ - fi; \ + if [ -n "$$matches" ]; then echo "$$matches"; exit 1; fi; \ echo "secret_scan_status=pass" dependency-evidence: deps @set -eu; \ status=0; \ for item in \ - "jerboa-proton-bridge:$(JERBOA_PROTON_BRIDGE_DIR)" \ - "jerboa-yubikey:$(JERBOA_YUBIKEY_DIR)" \ "jerboa-aws:$(JERBOA_AWS_DIR)" \ - "jerboa-pgp:$(JERBOA_PGP_DIR)" \ - "jerboa-crypto:$(JERBOA_CRYPTO_DIR)" \ - "jerboa-fuse:$(JERBOA_FUSE_DIR)" \ - "jerboa-mail:$(JERBOA_MAIL_DIR)" \ - "jerboa-https:$(JERBOA_HTTPS_DIR)" \ - "jerboa-ssl:$(JERBOA_SSL_DIR)"; do \ - label=$${item%%:*}; \ - dir=$${item#*:}; \ + "jerboa-yubikey:$(JERBOA_YUBIKEY_DIR)" \ + "jerboa-crypto:$(JERBOA_CRYPTO_DIR)"; do \ + label=$${item%%:*}; dir=$${item#*:}; \ echo "==> $$label dependency evidence"; \ - if awk 'BEGIN { found=0 } /^[^#[:space:]][^:]*:/ { split($$0, a, ":"); n=split(a[1], t, /[[:space:]]+/); for (i=1; i<=n; i++) if (t[i] == "release-evidence") found=1 } END { exit(found ? 0 : 1) }' "$$dir/Makefile"; then \ - env -u DIST_DIR -u RELEASE_EVIDENCE_DIR -u SBOM_DIR -u REPRO_DIR -u TARGET_EVIDENCE_DIR \ - $(MAKE) -C "$$dir" JERBUILD="$(JERBUILD)" release-evidence || status=1; \ - else \ - echo "$$label release-evidence target missing"; \ - status=1; \ - fi; \ + $(MAKE) -C "$$dir" JERBUILD="$(JERBUILD)" release-evidence || status=1; \ done; \ exit "$$status" @@ -284,15 +164,9 @@ sbom: deps DIST_DIR="$(DIST_DIR)" \ JERBUILD="$(JERBUILD)" \ BINARY_OUTPUT="$(BINARY_OUTPUT)" \ - JERBOA_PROTON_BRIDGE_DIR="$(JERBOA_PROTON_BRIDGE_DIR)" \ - JERBOA_YUBIKEY_DIR="$(JERBOA_YUBIKEY_DIR)" \ JERBOA_AWS_DIR="$(JERBOA_AWS_DIR)" \ - JERBOA_PGP_DIR="$(JERBOA_PGP_DIR)" \ + JERBOA_YUBIKEY_DIR="$(JERBOA_YUBIKEY_DIR)" \ JERBOA_CRYPTO_DIR="$(JERBOA_CRYPTO_DIR)" \ - JERBOA_FUSE_DIR="$(JERBOA_FUSE_DIR)" \ - JERBOA_MAIL_DIR="$(JERBOA_MAIL_DIR)" \ - JERBOA_HTTPS_DIR="$(JERBOA_HTTPS_DIR)" \ - JERBOA_SSL_DIR="$(JERBOA_SSL_DIR)" \ sh scripts/sbom.sh reproducibility-report: deps @@ -318,11 +192,8 @@ release-evidence: sh scripts/release-evidence.sh bundle: binary - JERBOA_PROTON_BRIDGE_DIR="$(JERBOA_PROTON_BRIDGE_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)" \ + JERBOA_CRYPTO_DIR="$(JERBOA_CRYPTO_DIR)" \ bash support/package-bundle.sh "$(BINARY_OUTPUT)" "$(BUNDLE_OUTPUT)" install: bundle @@ -331,48 +202,7 @@ install: bundle ln -sfn "$(INSTALL_OPTDIR)/jdrive" "$(INSTALL_BINDIR)/jdrive" @echo "Installed jdrive -> $(INSTALL_BINDIR)/jdrive" -linux: linux-amd64 - -linux-amd64: - @command -v $(LINUX_AMD64_CC) >/dev/null 2>&1 || { echo "ERROR: $(LINUX_AMD64_CC) not found on PATH."; exit 1; } - @test -d "$(JERBOA_HOME)/.chez-cross-ta6le" || { echo "ERROR: cross Chez prefix missing: $(JERBOA_HOME)/.chez-cross-ta6le"; exit 1; } - @test -f "$(JERBOA_HOME)/build/chez/xc-ta6le/s/xpatch" || { echo "ERROR: xpatch missing: $(JERBOA_HOME)/build/chez/xc-ta6le/s/xpatch"; exit 1; } - $(RUN_ENV) BINARY_LIBDIRS='$(XC_LIBDIRS)' \ - JERBOA_CHEZ_PREFIX=$(JERBOA_HOME)/.chez \ - JERBOA_CROSS_PREFIX=$(JERBOA_HOME)/.chez-cross-ta6le \ - JERBOA_XPATCH=$(JERBOA_HOME)/build/chez/xc-ta6le/s/xpatch \ - TARGET_MACHINE=ta6le CC=$(LINUX_AMD64_CC) \ - bash $(JERBOA_HOME)/support/build-binary.sh support/binary-entry.ss protonstorage-linux-amd64 - -linux-arm64: - @command -v $(LINUX_ARM64_CC) >/dev/null 2>&1 || { echo "ERROR: $(LINUX_ARM64_CC) not found on PATH."; exit 1; } - @test -d "$(JERBOA_HOME)/.chez-cross-tarm64le" || { echo "ERROR: cross Chez prefix missing: $(JERBOA_HOME)/.chez-cross-tarm64le"; exit 1; } - @test -f "$(JERBOA_HOME)/build/chez/xc-tarm64le/s/xpatch" || { echo "ERROR: xpatch missing: $(JERBOA_HOME)/build/chez/xc-tarm64le/s/xpatch"; exit 1; } - $(RUN_ENV) BINARY_LIBDIRS='$(XC_LIBDIRS)' \ - JERBOA_CHEZ_PREFIX=$(JERBOA_HOME)/.chez \ - JERBOA_CROSS_PREFIX=$(JERBOA_HOME)/.chez-cross-tarm64le \ - JERBOA_XPATCH=$(JERBOA_HOME)/build/chez/xc-tarm64le/s/xpatch \ - TARGET_MACHINE=tarm64le CC=$(LINUX_ARM64_CC) \ - bash $(JERBOA_HOME)/support/build-binary.sh support/binary-entry.ss protonstorage-linux-arm64 - -freebsd: freebsd-amd64 - -freebsd-amd64: - @command -v $(firstword $(FREEBSD_AMD64_CC)) >/dev/null 2>&1 || { echo "ERROR: $(FREEBSD_AMD64_CC) not found or not executable."; exit 1; } - @test -d "$(JERBOA_HOME)/.chez-cross-ta6fb" || { echo "ERROR: cross Chez prefix missing: $(JERBOA_HOME)/.chez-cross-ta6fb"; exit 1; } - @test -f "$(JERBOA_HOME)/build/chez/xc-ta6fb/s/xpatch" || { echo "ERROR: xpatch missing: $(JERBOA_HOME)/build/chez/xc-ta6fb/s/xpatch"; exit 1; } - $(RUN_ENV) BINARY_LIBDIRS='$(XC_LIBDIRS)' \ - JERBOA_CHEZ_PREFIX=$(JERBOA_HOME)/.chez \ - JERBOA_CROSS_PREFIX=$(JERBOA_HOME)/.chez-cross-ta6fb \ - JERBOA_XPATCH=$(JERBOA_HOME)/build/chez/xc-ta6fb/s/xpatch \ - TARGET_MACHINE=ta6fb CC='$(FREEBSD_AMD64_CC)' \ - bash $(JERBOA_HOME)/support/build-binary.sh support/binary-entry.ss protonstorage-freebsd-amd64 - clean: - 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 - rm -f protonstorage-linux-arm64-main.c protonstorage-linux-arm64.wp.so - rm -f protonstorage-freebsd-amd64-main.c protonstorage-freebsd-amd64.wp.so + rm -rf cache tmp mount dist build/release-inputs jdrive-bin + rm -f jdrive-bin-main.c jdrive-bin.wp.so rm -f petite_boot.h scheme_boot.h program_boot.h --- a/README.md +++ b/README.md @@ -1,377 +1,114 @@ # jerboa-drive -Jerboa-native encrypted remote drive and storage access. +`jdrive` is a Jerboa-native, client-side encrypted drive backed by an +S3-compatible object store. The remote service sees opaque encrypted objects; +profile configuration and wrapped key material stay under `~/.jdrive` by +default. -This project is intended to become a single deployable Jerboa application for -remote storage, with YubiKey-gated access and no rclone or Go helper in the -runtime path. The Proton Drive backend is currently experimental; the primary -backend is S3-compatible storage with client-side encryption. +The project currently supports one storage backend: S3. -## User Documentation +## Quick start -Start with [docs/user-guide.md](docs/user-guide.md) for installation, local -state layout, authentication, encrypted credential vault setup, listing remote -files, reading and writing Drive content, FUSE mounts, configuration variables, -and troubleshooting. See [docs/security-model.md](docs/security-model.md) for -credential, local state, FUSE, native-code, and release-gate expectations. - -Short installed-binary form: - -```sh -jdrive login --username you@example.com -jdrive ls --username you@example.com -jdrive creds store --profile default --username you@example.com -jdrive tree --profile default --max-depth 3 -``` - -The `protonstorage` command and historical `drive ...` prefix still work, but -neither is required. If Proton requires TOTP and you did not pass -`--totp-code`, `jdrive` prompts for the current code. - -After `jdrive creds store`, commands such as `jdrive ls`, `jdrive volumes`, and -`jdrive mount` use the selected profile's encrypted credentials automatically -when you do not pass `--username` or token options. - -## Encrypted S3 Backend - -The S3 backend stores local profile state under `~/.jdrive`, not XDG. It uses -AWS-compatible credentials from the normal AWS environment/profile files, wraps -a random drive key in a local `scrypt` + `chacha20-poly1305` vault, encrypts -the remote manifest, and stores file contents under opaque S3 object keys. - -Bootstrap a profile: - -```sh -jdrive s3 init --bucket BUCKET --prefix jdrive --region us-east-1 -jdrive s3 profile-status -jdrive s3 unlock-test -``` - -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: - -```sh -jdrive s3 init --bucket BUCKET --yubikey-mode piv -``` - -Daily operations: - -```sh -jdrive s3 mkdir /backups -jdrive s3 cp ./notes.txt /backups/notes.txt -jdrive s3 cp -r ./backup-dir /backups -jdrive s3 cp /backups/notes.txt ./notes-restored.txt -jdrive s3 cp -r /backups ./restored-backups -jdrive s3 cp -r /backups /archive -jdrive s3 mv /archive/backups/notes.txt /archive/notes.txt -jdrive s3 sync ./backup-dir /backups --delete -jdrive s3 check ./backup-dir /backups -jdrive s3 ls /backups -jdrive s3 cat /backups/notes.txt -jdrive s3 cat /backups/notes.txt --offset 0 --size 4096 -jdrive s3 get /backups/notes.txt ./notes-restored.txt -jdrive s3 rm -r /backups/backup-dir -``` - -Set `JDRIVE_VAULT_PASSWORD` or pass `--vault-password-env ENV_NAME` for -automation. Set `JDRIVE_PIV_PIN` or pass `--piv-pin-env ENV_NAME` when the -profile was initialized with `--yubikey-mode piv`. - -Files are stored as independently encrypted chunks by default. Override the -64 MiB default with `--chunk-size N` for testing or provider tuning. Encrypted -chunk objects larger than 16 MiB use S3 multipart upload internally, so the -bucket provider still only sees encrypted object parts. Manifest commits use -S3 conditional writes against the manifest ETag; concurrent writers fail with a -retryable conflict instead of silently overwriting each other. - -`sync` and `cp` treat paths beginning with `/` as encrypted remote paths unless -the source is an existing local absolute path. That supports local-to-remote, -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. File -output — single-file `get` and recursive downloads alike — uses 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: - -- [main.ss](main.ss) - script entry point. -- [protonstorage/cli.ss](protonstorage/cli.ss) - command dispatch. -- [protonstorage/drive/api.ss](protonstorage/drive/api.ss) - direct Proton - Drive HTTPS API calls through Jerboa HTTPS/JSON, including block upload - reservation and multipart encrypted-block upload. -- [protonstorage/drive/auth.ss](protonstorage/drive/auth.ss) - native SRP, - FIDO2/YubiKey, and TOTP auth flow. -- [protonstorage/drive/discovery.ss](protonstorage/drive/discovery.ss) - - active volume/share/root resolution. -- [protonstorage/drive/crypto.ss](protonstorage/drive/crypto.ss) - Drive - name hashing, key/passphrase decrypt wrappers, disabled raw - content-session-key and block decrypt calls, disabled OpenPGP write helpers, - and encrypted-block SHA-256 verification. The disabled OpenPGP/Drive crypto - paths fail closed until the Proton backend is RustSec-clean. -- [protonstorage/drive/client.ss](protonstorage/drive/client.ss) - end-to-end - root unlock, decrypted traversal, file read pipeline, folder-path resolution, - folder creation, file upload, and revision replacement. -- [protonstorage/drive/fs.ss](protonstorage/drive/fs.ss) - recursive FUSE - metadata mapping with reader, mkdir, buffered create/write, unlink/rmdir, - and rename/move callbacks through `jerboa-fuse`. -- [protonstorage/drive/local.ss](protonstorage/drive/local.ss) - local - CLI/FUSE profile paths, cache/staging directory setup, and foreground daemon - marker status. -- [protonstorage/drive/write.ss](protonstorage/drive/write.ss) - create-folder - and create-file request payload assembly from unlocked keys and generated - Drive crypto, encrypted block upload plans, encrypted xattrs, manifest - signatures, and revision commit payloads. The live crypto assembly paths - currently fail closed while Proton OpenPGP/Drive crypto is disabled. -- [protonstorage/s3/config.ss](protonstorage/s3/config.ss) - S3-compatible - endpoint, credential, region, path-style, and object-key helpers. -- [protonstorage/s3/drive.ss](protonstorage/s3/drive.ss) - encrypted S3 drive - profile/vault, encrypted manifest, encrypted object upload/download/list, - chunked encrypted storage, ranged reads, sync/check helpers, checksum - verification, retry wrappers, and optional YubiKey PIV unlock material - through `jerboa-yubikey`. - -The cloned Go/rclone/Proton client projects under `~/mine` are references only. -They are used to map endpoint shapes and crypto behavior while porting. - -## Implemented API Surface - -The Jerboa module `(protonstorage drive api)` currently covers: - -- `/core/v4/users` -- `/core/v4/keys/salts` -- `/core/v4/addresses` -- `/core/v4/addresses/{addressID}` -- `/drive/volumes` -- `/drive/shares` -- `/drive/shares/{shareID}` -- `/drive/shares/{shareID}/links/{linkID}` -- `/drive/shares/{shareID}/folders/{linkID}/children` -- `/drive/shares/{shareID}/links/{linkID}/checkAvailableHashes` -- `/drive/shares/{shareID}/links/{linkID}/move` -- `/drive/shares/{shareID}/folders` -- `/drive/shares/{shareID}/files` -- `/drive/shares/{shareID}/files/{linkID}/revisions` -- `/drive/shares/{shareID}/files/{linkID}/revisions/{revisionID}` -- `/drive/shares/{shareID}/folders/{linkID}/trash_multiple` -- `/drive/shares/{shareID}/folders/{linkID}/delete_multiple` -- `/drive/shares/{shareID}/trash` -- `/drive/blocks` - -Inspect the native surface: +Build the dependencies pinned in `dependency-lock.tsv`, then inspect the CLI: ```sh -make run ARGS='drive status' +make deps +make run ARGS='help' ``` -Authenticated calls use Proton session material from the environment: +Create a profile using the normal AWS credential chain: ```sh -export PROTONDRIVE_UID='...' -export PROTONDRIVE_ACCESS_TOKEN='...' -``` - -Examples: +export AWS_PROFILE=personal +export AWS_REGION=us-east-1 -```sh -make run ARGS='drive user' -make run ARGS='drive volumes' -make run ARGS='drive shares' -make run ARGS='drive link --share SHARE_ID --link LINK_ID' -make run ARGS='drive children --share SHARE_ID --link LINK_ID' +make run ARGS='s3 init --profile default --bucket my-private-bucket' +make run ARGS='s3 profile-status --profile default' ``` -Fresh native login uses SRP and FIDO2/YubiKey when Proton requests it: - -```sh -export PROTONDRIVE_PASSWORD='...' -make run ARGS='drive login --username you@example.com' -make run ARGS='drive root-children-decrypted --username you@example.com' -make run ARGS='drive tree-decrypted --username you@example.com --max-depth 3' -``` +The initializer prompts for a vault password unless +`--vault-password-env NAME` names an environment variable containing it. Use a +unique, high-entropy password and do not store it in the repository. -For local CLI use, Proton login credentials can be stored in the selected -profile as an encrypted vault protected by a separate vault password. The vault -uses `scrypt` and `chacha20-poly1305`; the vault password is still required to -unlock stored credentials. After the first successful full login, the encrypted -vault also stores Proton's reusable session tokens and key pass so later -commands can refresh the session without TOTP. Profiles live under -`~/.jproton/profiles/<profile>/` by default. +Upload, inspect, and restore data: ```sh -make run ARGS='drive credentials store --profile default' -make run ARGS='drive credentials status --profile default' -make run ARGS='drive credentials unlock-test --profile default' -make run ARGS='drive root-children-decrypted --profile default' +make run ARGS='s3 cp notes.txt jdrive:/notes.txt' +make run ARGS='s3 ls jdrive:/' +make run ARGS='s3 cat jdrive:/notes.txt' +make run ARGS='s3 get jdrive:/notes.txt restored-notes.txt' +make run ARGS='s3 sync ./photos jdrive:/photos --delete' ``` -For automation, set `PROTONSTORAGE_VAULT_PASSWORD` or pass -`--vault-password-env ENV_NAME` instead of typing the vault password. +Run `jdrive help` for all commands and options. See +[the user guide](docs/user-guide.md) for profile, sync, range-read, YubiKey, +and packaging details. -The default FUSE command mounts a decrypted, read-only Drive tree: +## Configuration -```sh -mkdir -p /tmp/proton-root -make run ARGS='drive mount-root --username you@example.com --mountpoint /tmp/proton-root' -``` +The main environment variables are: -The writable mount buffers created file data in memory and commits it through -the native encrypted upload/revision pipeline when the file handle is flushed or -released. It also supports folder creation, unlink/rmdir through Proton trash, -and rename/move through the Proton move endpoint: +- `AWS_PROFILE`, `AWS_REGION`, `AWS_DEFAULT_REGION`, and the standard AWS + credential variables used by `jerboa-aws`. +- `JDRIVE_S3_BUCKET`, `JDRIVE_S3_PREFIX`, `JDRIVE_S3_ENDPOINT`, and + `JDRIVE_S3_PATH_STYLE` for the object-store target. +- `JDRIVE_PROFILE` and `JDRIVE_STATE_DIR` for local profile selection. -```sh -mkdir -p /tmp/proton-rw -make run ARGS='drive mount-root-write --username you@example.com --mountpoint /tmp/proton-rw' -``` +Endpoint overrides make MinIO and other S3-compatible services usable. Path +style can be enabled with `--path-style` or `JDRIVE_S3_PATH_STYLE=1`. -The product-level FUSE CLI surface also has a profile-aware mount command and -local state inspection commands. `drive daemon start` currently runs the same -FUSE daemon in the foreground so it can be supervised by a service manager; a -background control socket is not implemented yet (see -[Planned / Not Yet Implemented](#planned--not-yet-implemented)). +## Storage and encryption -```sh -make run ARGS='drive cache init --profile default' -make run ARGS='drive cache status --profile default' -make run ARGS='drive daemon status --profile default' -make run ARGS='drive mount --username you@example.com --mountpoint /tmp/proton-root' -make run ARGS='drive mount --username you@example.com --mountpoint /tmp/proton-rw --writable' -make run ARGS='drive daemon start --username you@example.com --mountpoint /tmp/proton-rw --writable --foreground' -``` +Each profile has a local configuration document and encrypted key vault. Drive +manifests, file records, and chunk records are authenticated and encrypted +before upload. Large files use independently authenticated chunks, enabling +bounded-memory transfer and range reads. Manifest generations are recorded +locally so rollback or tampering is rejected. -File reads use the same recursive pipeline and write plaintext bytes to stdout: +The implementation uses `jerboa-crypto` for OpenSSL-backed primitives, +`jerboa-aws` for SigV4/S3 requests, and `jerboa-yubikey` for optional PIV-backed +unlock material. The detailed trust boundaries are in +[the security model](docs/security-model.md). -```sh -make run ARGS='drive read-file --username you@example.com --link FILE_LINK_ID --offset 0 --size 4096' -``` - -The current read path resolves the root, unlocks the file node key, decrypts -and verifies Proton Drive `ContentKeyPacket` values, selects the active -revision, downloads storage blocks, verifies each encrypted block hash, and -decrypts block packets with the content session key. - -The write-side surface can now generate locked Drive node keys, encrypt signed -link names, encrypt/sign node passphrases, reserve upload blocks, and submit -encrypted block payloads using Proton's `Block` multipart field. It can also -generate `ContentKeyPacket` values and encrypt content blocks with the generated -session key, assemble create-folder/create-file payloads, build encrypted block -metadata with encrypted detached signatures, encrypt revision xattrs, sign -revision manifests, upload blocks, and commit file revisions under decrypted -folder paths. - -Write commands require a fresh native login because Drive key unlock needs -mailbox key material: +## Development ```sh -make run ARGS='drive create-folder --username you@example.com --parent-path / --name docs' -make run ARGS='drive upload-file --username you@example.com --parent-path /docs --path ./notes.txt --mime-type text/plain' -make run ARGS='drive rename-child --username you@example.com --parent-path /docs --name notes.txt --new-name notes-renamed.txt' -make run ARGS='drive move-child --username you@example.com --parent-path /docs --name notes-renamed.txt --new-parent-path /archive' -make run ARGS='drive trash-child --username you@example.com --parent-path /archive --name notes-renamed.txt' +make test +make security +make binary +make bundle ``` -`drive create-root-folder` and `drive upload-root-file` remain as root-folder -aliases. If a file already has an unfinished draft revision, uploads stop by -default. Pass `--replace-existing-draft` to delete that draft revision and -create a new one for an active file. Cross-folder move and FUSE rename -re-encrypt the moved node passphrase for the destination parent key. - -## Reference Clones - -Relevant reference projects cloned under `~/mine`: +On macOS and other non-Linux platforms, `make binary` is the canonical +pre-commit build. Linux release CI uses the repository's static pipeline. +`make bundle` produces `dist/jerboa-drive-<os>-<arch>.tar.gz`; run the packaged +`jdrive` launcher rather than the internal binary directly. -- `/Users/user/mine/rclone` -- `/Users/user/mine/Proton-API-Bridge` -- `/Users/user/mine/rclone-go-proton-api` -- `/Users/user/mine/go-proton-api` -- `/Users/user/mine/gopenpgp` -- `/Users/user/mine/go-crypto` -- `/Users/user/mine/go-srp` -- `/Users/user/mine/proton-drive-upstream` -- `/Users/user/mine/proton-ios-drive` -- `/Users/user/mine/proton-android-drive` -- `/Users/user/mine/proton-webclients` -- `/Users/user/mine/jerboa-proton-bridge` -- `/Users/user/mine/jerboa-yubikey` -- `/Users/user/mine/jerboa-crypto` -- `/Users/user/mine/jerboa-fuse` - -## Build and Test +The local integration test is opt-in because it creates and removes objects in +a real bucket: ```sh -make -make binary -make linux-amd64 -make linux-arm64 -make freebsd-amd64 -make test -make integration make s3-integration -make doctor -make bundle -make release-evidence ``` -`make integration` skips unless `PROTONDRIVE_INTEGRATION=1` is set. When -enabled, it uses `PROTONDRIVE_USERNAME` and `PROTONDRIVE_PASSWORD`, creates a -disposable folder under `PROTONDRIVE_TEST_PARENT_PATH` or `/`, uploads one small -file, moves it between two disposable folders, and trashes the disposable root -during cleanup. - -`make s3-integration` skips unless `JDRIVE_S3_INTEGRATION=1` is set. When -enabled, it requires `JDRIVE_S3_TEST_BUCKET`, uses standard AWS credentials or -`JDRIVE_S3_TEST_AWS_PROFILE`, writes under `JDRIVE_S3_TEST_PREFIX` or a -disposable prefix, syncs two encrypted chunked files, checks them, reads a -range, restores a file, and removes remote test entries. Set -`JDRIVE_S3_TEST_MULTIPART=1` to also upload a large encrypted object through -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 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 -[`docs/release-evidence.md`](docs/release-evidence.md). `make release-evidence` -writes the local evidence bundle under `dist/release-evidence/` and fails -closed while upstream dependency gates remain blocked. +Use only a disposable test prefix and review the test's required environment +before enabling it. -## Planned / Not Yet Implemented +## Repository layout -The following are **not** implemented yet. They are listed here so the README -does not overstate current capability: +- `jdrive/cli.ss` — command parsing and dispatch. +- `jdrive/s3/config.ss` — S3 target configuration. +- `jdrive/s3/drive.ss` — profile vault, encrypted manifests, transfers, sync, + and object operations. +- `test/test-all.ss` — local crypto, manifest, path, and vault tests. +- `support/` — reproducible build, native bundle, and launcher hardening. +- `scripts/` — security and release-evidence checks. -- **Daemon control socket / background lifecycle.** `drive daemon start` runs - the FUSE daemon in the foreground only, so it can be supervised by a service - manager. There is no background daemon and no control socket. -- **Durable operation queue.** Create/upload/rename/move/trash operations are - not journaled; there is no persistent queue that survives a crash or restart. -- **Multi-writer merge.** Concurrent writers are handled fail-fast: manifest - commits use S3 conditional writes against the manifest ETag, so a conflicting - writer fails with a retryable error instead of being merged. There is no - merge/conflict-resolution policy beyond that. -- **Indexed cache states.** On-demand cache-state tracking (online-only, - available, pinned, dirty, uploading) is not implemented. `drive cache init` - and `drive cache status` only create and report local cache/staging paths; - `metadata.db` and indexed cache state are planned persistence layers. +Dependencies are fetched at locked commits into the ignored `.deps/` +directory. Build files never depend on adjacent source checkouts. -Other remaining work: +## Release policy -- Prove the encrypted S3 live integration against each target provider. -- Add richer metadata update operations beyond size-changing writes and - rename/move. -- Collapse the native dynamic library bundle further when Jerboa supports a - fully static bridge path. +`VERSION` is authoritative. Every pull request must advance it and keep package +metadata and user-visible output synchronized. Changes are merged only through +a reviewed Forgejo pull request. --- a/SECURITY.md +++ b/SECURITY.md @@ -1,40 +1,27 @@ -# Security Policy +# Security policy -This repository is part of the Jerboa ecosystem. Treat it as experimental unless the README and release notes explicitly state a stronger support level. +Please report vulnerabilities privately to the repository maintainers on +`git.jerboa.sh`. Do not open a public issue containing credentials, exploit +details, plaintext customer data, or bucket identifiers. -Repository-specific credential, local state, FUSE, native-code, and release -gate details are documented in [docs/security-model.md](docs/security-model.md). +Repository-specific credential, local-state, native-code, object-storage, and +release expectations are documented in +[docs/security-model.md](docs/security-model.md). -Production claims require the release gates tracked in `~/Release-plan.md` and `~/mine/jerboa-production-readiness.md` to be complete for this repository. +Before submitting a security-sensitive change: -## Supported Status +```sh +make security +make test +make binary +make bundle +``` -No public production-support commitment exists yet. Security-sensitive releases must be cut from a clean checkout after: +Do not commit AWS credentials, vault passwords, PIV PINs, profile vaults, +plaintext fixtures derived from real data, authorization headers, or raw +account output. Release and target evidence must be processed by +`scripts/sanitize-evidence.sh` before it is shared. -- `git status --short` shows only intentional release changes.