Security hardening and release readiness
ober
36048076df97c7678aa67cdb6822bbf8f0823bf6
new file mode 100644 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,63 @@ +name: CI + +on: + push: + branches: [main, master] + pull_request: + workflow_dispatch: + +permissions: + contents: read + +env: + JERBOA_VERSION: v0.2.3 + JERBUILD: ${{ github.workspace }}/.jerboa/bin/jerbuild + JERBOA_YUBIKEY_REPO: https://git.sr.ht/~lisp/jerboa-yubikey + 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_YUBIKEY_DIR: ${{ github.workspace }}/.deps/jerboa-yubikey + JERBOA_MAIL_DIR: ${{ github.workspace }}/.deps/jerboa-mail + JERBOA_HTTPS_DIR: ${{ github.workspace }}/.deps/jerboa-https + JERBOA_SSL_DIR: ${{ github.workspace }}/.deps/jerboa-ssl + +jobs: + build-test-audit: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install system tools + run: | + set -eu + sudo apt-get update + sudo apt-get install -y --no-install-recommends build-essential git curl ca-certificates libssl-dev pkg-config + command -v cargo + cargo --version + + - name: Install Jerboa toolchain + run: sh support/ensure-jerboa.sh "$JERBOA_VERSION" .jerboa/bin + + - name: Fetch dependencies + run: | + set -eu + mkdir -p .deps + git clone --depth 1 "$JERBOA_YUBIKEY_REPO" "$JERBOA_YUBIKEY_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" + + - name: Test + run: make test + + - name: CLI status smoke + run: make run ARGS='status' + + - name: Install cargo-audit + run: cargo install cargo-audit --locked + + - name: Audit Rust dependencies + run: make audit + + - name: Release evidence + run: make release-evidence new file mode 100644 --- /dev/null +++ b/.github/workflows/security-baseline.yml @@ -0,0 +1,35 @@ +name: Security Baseline + +on: + push: + branches: [main, master] + pull_request: + workflow_dispatch: + +permissions: + contents: read + +jobs: + baseline: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Required release files + run: | + set -eu + test -f LICENSE + test -f SECURITY.md + test -f .gitignore + find . -maxdepth 1 -iname "README*" -type f | grep -q . + + - name: High-confidence secret scan + run: | + set -eu + pattern="(BEGIN (RSA|OPENSSH|EC|DSA|PRIVATE) KEY|ghp_[A-Za-z0-9_]{20,}|github_pat_[A-Za-z0-9_]{20,}|sk-(ant-api03|proj|svcacct)-[A-Za-z0-9_-]{30,}|AKIA[0-9A-Z]{16})" + 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 --- a/.gitignore +++ b/.gitignore @@ -5,7 +5,12 @@ *.wpo *.boot *.log +.deps/ +.jerboa/* +!.jerboa/ +!.jerboa/security.json target/ tmp/ cache/ vault.local +dist/ new file mode 100644 --- /dev/null +++ b/.jerboa/security.json @@ -0,0 +1,45 @@ +{ + "version": 1, + "repo": "jerboa-proton-bridge", + "extends": ["jerboa:cli", "jerboa:crypto", "jerboa:ffi", "jerboa:network", "jerboa:credentialed-client"], + "paths": { + "production": ["*.ss", "*.sls", "lib/**/*.ss", "lib/**/*.sls", "src/**/*.{ss,sls,c,h,rs}", "native/**/*.{c,h,rs}", "proton-bridge/**/*.ss", "proton-bridge-native/src/**/*.rs", "Makefile"], + "tests": ["test/**", "tests/**", "**/*-test.ss", "fixtures/**"], + "generated": ["build/**", "dist/**", "target/**", "*.so", "*.dylib", "*.wpo"], + "vendor": ["vendor/**", "third_party/**"], + "docs": ["README.md", "docs/**", "*.md"] + }, + "policy": { + "failOn": ["critical", "high"], + "imports": { "directChezscheme": "allow-in-ffi-boundaries" }, + "ffi": { "allowed": true, "requireDynamicWindCleanup": true }, + "process": { "shellInterpolation": "deny" }, + "network": { "requireTimeouts": true, "rawAccountOutputEvidence": "deny" }, + "eval": { "stringEval": "deny", "bareRead": "deny", "allowReadEval": false }, + "crypto": { "forbidSecretLogging": true, "requireRustSecCleanBackends": true }, + "credentials": { + "sessionStore": "none", + "plaintextCache": "deny", + "passwordAuthenticatedLocalProtocol": "deny-by-default", + "rawCredentialEvidence": "deny" + }, + "targetEvidence": { + "protonYubikeyProof": "fail-closed", + "requiredMarkers": [ + "fido2_payload_probe_status=target-evidence-recorded", + "yubikey_assertion_status=target-evidence-recorded", + "least_privilege_proton_account_status=confirmed", + "diagnostic_secret_redaction_status=target-evidence-recorded", + "no_store_artifact_status=target-evidence-recorded", + "srp_disabled_status=confirmed", + "openpgp_disabled_status=confirmed", + "drive_crypto_disabled_status=confirmed", + "target_platform_smoke_status=target-evidence-recorded", + "production_integration_review_status=current-run-recorded", + "raw_account_output_status=not-preserved", + "sensitive_artifact_policy=no-proton-passwords-mailbox-passwords-tokens-fido2-pins-decrypted-keys-plaintext-mail-or-raw-account-output-preserved" + ] + } + }, + "suppressions": [] +} --- a/LICENSE +++ b/LICENSE @@ -1,5 +1,201 @@ -This project is intended to be GPL-3.0-or-later compatible because it is a -Jerboa rewrite informed by Proton Mail Bridge, which is GPL-3.0-or-later. + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ -Add the full GPL-3.0-or-later license text before distributing binaries or -accepting external contributions. + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. --- a/Makefile +++ b/Makefile @@ -1,13 +1,42 @@ -JERBOA_HOME ?= $(realpath $(CURDIR)/../jerboa) -SCHEME ?= $(JERBOA_HOME)/.chez/bin/scheme -JERBOA_YUBIKEY_DIR ?= $(realpath $(CURDIR)/../jerboa-yubikey) -JERBOA_MAIL_DIR ?= $(realpath $(CURDIR)/../jerboa-mail) -JERBOA_HTTPS_DIR ?= $(realpath $(CURDIR)/../jerboa-https) -JERBOA_SSL_DIR ?= $(realpath $(CURDIR)/../jerboa-ssl) -LIBDIRS := $(CURDIR):$(JERBOA_YUBIKEY_DIR):$(JERBOA_MAIL_DIR):$(JERBOA_HTTPS_DIR)/lib:$(JERBOA_SSL_DIR)/lib:$(JERBOA_HOME)/lib +JERBOA_VERSION ?= v0.2.3 +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) + +DEPS_DIR ?= $(CURDIR)/.deps +DIST_DIR ?= $(CURDIR)/dist +RELEASE_EVIDENCE_DIR ?= $(DIST_DIR)/release-evidence +TARGET_EVIDENCE_DIR ?= $(DIST_DIR)/target-evidence +LOCAL_YUBIKEY_DIR := $(CURDIR)/../jerboa-yubikey +LOCAL_MAIL_DIR := $(CURDIR)/../jerboa-mail +LOCAL_HTTPS_DIR := $(CURDIR)/../jerboa-https +LOCAL_SSL_DIR := $(CURDIR)/../jerboa-ssl +JERBOA_YUBIKEY_DIR ?= $(if $(wildcard $(LOCAL_YUBIKEY_DIR)/.),$(realpath $(LOCAL_YUBIKEY_DIR)),$(DEPS_DIR)/jerboa-yubikey) +JERBOA_MAIL_DIR ?= $(if $(wildcard $(LOCAL_MAIL_DIR)/.),$(realpath $(LOCAL_MAIL_DIR)),$(DEPS_DIR)/jerboa-mail) +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) +LIBDIRS = $(CURDIR):$(JERBOA_YUBIKEY_DIR):$(JERBOA_YUBIKEY_DIR)/lib:$(JERBOA_MAIL_DIR):$(JERBOA_HTTPS_DIR)/lib:$(JERBOA_SSL_DIR)/lib:$(JH)/lib NATIVE_MANIFEST := proton-bridge-native/Cargo.toml +NATIVE_LOCK := proton-bridge-native/Cargo.lock +NATIVE_DIR := $(CURDIR)/proton-bridge-native/target/release + +UNAME_S := $(shell uname -s) +ifeq ($(UNAME_S),Darwin) + LD_VAR = DYLD_LIBRARY_PATH + SO_EXT = .dylib +else + LD_VAR = LD_LIBRARY_PATH + SO_EXT = .so +endif +LD_ENV = $(LD_VAR)="$(JERBOA_SSL_DIR):$(NATIVE_DIR)" +NATIVE_LIB := $(NATIVE_DIR)/libproton_bridge_native$(SO_EXT) -.PHONY: help native run test clean +.PHONY: help ensure-jerboa-tools ensure-deps deps native run test audit verify security dependency-evidence sbom reproducibility-report target-evidence release-evidence clean .DEFAULT_GOAL := help help: @@ -17,27 +46,141 @@ help: @echo " make run ARGS='--help' Run the CLI" @echo " make native Build the native Proton SRP helper" @echo " make test Run smoke tests" + @echo " make audit Run the RustSec native dependency audit" + @echo " make verify Run release verification checks" + @echo " make target-evidence Write target Proton/YubiKey proof status" + @echo " make release-evidence Write test/audit/SBOM/reproducibility evidence under dist/" @echo " make clean Remove local generated files" @echo "" @echo "Environment:" - @echo " JERBOA_HOME = $(JERBOA_HOME)" - @echo " SCHEME = $(SCHEME)" + @echo " JERBUILD = $(JERBUILD)" @echo " JERBOA_YUBIKEY_DIR = $(JERBOA_YUBIKEY_DIR)" @echo " JERBOA_MAIL_DIR = $(JERBOA_MAIL_DIR)" @echo " JERBOA_HTTPS_DIR = $(JERBOA_HTTPS_DIR)" @echo " JERBOA_SSL_DIR = $(JERBOA_SSL_DIR)" -native: +ensure-jerboa-tools: + @if "$(JERBUILD)" --jerboa-home >/dev/null 2>&1; then \ + echo "=== Using Jerboa toolchain: $(JERBUILD) ==="; \ + elif [ -x "$(JERBOA_TOOL_DIR)/jerbuild" ] && "$(JERBOA_TOOL_DIR)/jerbuild" --jerboa-home >/dev/null 2>&1; then \ + echo "=== Using downloaded Jerboa toolchain: $(JERBOA_TOOL_DIR) ==="; \ + elif command -v jerbuild >/dev/null 2>&1 && jerbuild --jerboa-home >/dev/null 2>&1; then \ + echo "=== Using Jerboa toolchain from PATH: $$(command -v jerbuild) ==="; \ + else \ + echo "=== Fetching Jerboa $(JERBOA_VERSION) release tools into $(JERBOA_TOOL_DIR) ==="; \ + 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; \ + exit 1; \ + } + +ensure-deps: + @test -d "$(JERBOA_YUBIKEY_DIR)" || { echo "ERROR: JERBOA_YUBIKEY_DIR missing: $(JERBOA_YUBIKEY_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: ensure-jerboa-tools ensure-deps + @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 + +native: deps cargo build --manifest-path $(NATIVE_MANIFEST) --release -run: - JERBOA_HOME=$(JERBOA_HOME) JERBOA_SSL_LIB=$(JERBOA_SSL_DIR) \ - $(SCHEME) -q --libdirs $(LIBDIRS) --script main.ss -- $(ARGS) +run: native + JERBOA_SSL_LIB="$(JERBOA_SSL_DIR)" $(LD_ENV) \ + "$(JERBUILD)" exec --libdirs "$(LIBDIRS)" main.ss -- $(ARGS) test: native - JERBOA_HOME=$(JERBOA_HOME) JERBOA_SSL_LIB=$(JERBOA_SSL_DIR) \ - $(SCHEME) -q --libdirs $(LIBDIRS) --script test/test-all.ss + JERBOA_SSL_LIB="$(JERBOA_SSL_DIR)" $(LD_ENV) \ + "$(JERBUILD)" exec --libdirs "$(LIBDIRS)" test/test-all.ss + +audit: + @if cargo audit --version >/dev/null 2>&1; then \ + cargo audit --file "$(NATIVE_LOCK)" -D warnings; \ + else \ + echo "ERROR: cargo audit is not installed; install cargo-audit to run the native dependency audit gate." >&2; \ + exit 1; \ + fi + +verify: test audit + +security: + @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,}|PROTON_(PASSWORD|MAILBOX_PASSWORD|ACCESS_TOKEN|REFRESH_TOKEN|TOKEN)=['\\\"]?[A-Za-z0-9_./+=:-]{24,}|AKIA[0-9A-Z]{16}|ASIA[0-9A-Z]{16})"; \ + 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; \ + echo "secret_scan_status=pass" + +dependency-evidence: deps + @set -eu; \ + status=0; \ + for item in \ + "jerboa-yubikey:$(JERBOA_YUBIKEY_DIR)" \ + "jerboa-mail:$(JERBOA_MAIL_DIR)" \ + "jerboa-https:$(JERBOA_HTTPS_DIR)" \ + "jerboa-ssl:$(JERBOA_SSL_DIR)"; do \ + label="$${item%%:*}"; \ + dir="$${item#*:}"; \ + echo "==> $$label dependency evidence"; \ + if [ -f "$$dir/Makefile" ] && awk 'BEGIN{found=0} /^[[:space:]]*#/ { next } /^[^[:space:]][^:]*:/ { line=$$0; sub(/:.*/, "", line); n=split(line,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 \ + $(MAKE) -C "$$dir" JERBUILD="$(JERBUILD)" release-evidence || status=1; \ + else \ + echo "$$label release-evidence target missing"; \ + status=1; \ + fi; \ + done; \ + exit $$status + +sbom: native + @REPO_ROOT="$(CURDIR)" \ + DIST_DIR="$(DIST_DIR)" \ + JERBUILD="$(JERBUILD)" \ + JERBOA_YUBIKEY_DIR="$(JERBOA_YUBIKEY_DIR)" \ + JERBOA_MAIL_DIR="$(JERBOA_MAIL_DIR)" \ + JERBOA_HTTPS_DIR="$(JERBOA_HTTPS_DIR)" \ + JERBOA_SSL_DIR="$(JERBOA_SSL_DIR)" \ + NATIVE_MANIFEST="$(NATIVE_MANIFEST)" \ + NATIVE_LOCK="$(NATIVE_LOCK)" \ + NATIVE_LIB="$(NATIVE_LIB)" \ + sh scripts/sbom.sh + +reproducibility-report: ensure-jerboa-tools ensure-deps + @REPO_ROOT="$(CURDIR)" \ + DIST_DIR="$(DIST_DIR)" \ + JERBUILD="$(JERBUILD)" \ + MAKE="$(MAKE)" \ + NATIVE_LIB="$(NATIVE_LIB)" \ + sh scripts/reproducibility-report.sh + +target-evidence: + @REPO_ROOT="$(CURDIR)" \ + DIST_DIR="$(DIST_DIR)" \ + TARGET_EVIDENCE_DIR="$(TARGET_EVIDENCE_DIR)" \ + sh scripts/target-evidence.sh + +release-evidence: + @REPO_ROOT="$(CURDIR)" \ + DIST_DIR="$(DIST_DIR)" \ + RELEASE_EVIDENCE_DIR="$(RELEASE_EVIDENCE_DIR)" \ + TARGET_EVIDENCE_DIR="$(TARGET_EVIDENCE_DIR)" \ + JERBUILD="$(JERBUILD)" \ + JERBOA_YUBIKEY_DIR="$(JERBOA_YUBIKEY_DIR)" \ + JERBOA_MAIL_DIR="$(JERBOA_MAIL_DIR)" \ + JERBOA_HTTPS_DIR="$(JERBOA_HTTPS_DIR)" \ + JERBOA_SSL_DIR="$(JERBOA_SSL_DIR)" \ + sh scripts/release-evidence.sh clean: cargo clean --manifest-path $(NATIVE_MANIFEST) - rm -rf tmp cache + rm -rf tmp cache dist new file mode 100644 --- /dev/null +++ b/NOTICE @@ -0,0 +1,9 @@ +jerboa-proton-bridge + +This repository is licensed under Apache License 2.0. + +This project is a Jerboa rewrite/reimplementation effort that used Proton Mail +Bridge as a GPL-3.0-or-later behavioral reference. Do not copy upstream Proton +Mail Bridge GPL source into this repository without an explicit license review. +If copied GPL material is ever introduced, this Apache-2.0 licensing decision +must be revisited before distribution. --- a/README.md +++ b/README.md @@ -17,7 +17,6 @@ Current companion libraries: - `/Users/user/mine/jerboa-yubikey` - `/Users/user/mine/jerboa-mail` -- `/Users/user/mine/jerboa-pgp` - `/Users/user/mine/jerboa-https` - `/Users/user/mine/jerboa-crypto` @@ -79,8 +78,8 @@ make native make test ``` -Current command support includes live auth, read-only mail, and two offline -auth probes: +Current command support includes policy/status probes, FIDO2 payload probing, +and fail-closed live-auth/read-only mail scaffolding: ```sh make run ARGS='status' @@ -94,25 +93,39 @@ make run ARGS='login --auth-info auth-info.json --username USER' make run ARGS='login --auth-options auth-options.json' ``` -The live login path performs `/auth/v4/info`, generates and submits the SRP -proof, verifies Proton's `ServerProof`, and then submits a YubiKey-backed -FIDO2 assertion when Proton requires FIDO2. It prints only the authenticated -UID and does not persist the session. +The live login path is intentionally disabled in the strict build before it +prompts for a Proton password. Proton SRP proof generation requires signed +modulus verification; the previous rPGP/RSA dependency path was removed until +an audit-clean verifier is available. The FIDO2 `--auth-options` probe remains +available because it does not require Proton credentials. -The read-only `folders`, `list`, `message-json`, and `show` commands perform -a fresh interactive auth for each invocation. They fetch Proton API JSON -directly and do not store a reusable refresh token or local IMAP password. +The read-only `folders`, `list`, `message-json`, and `show` commands are wired +for fresh per-invocation auth, but they currently fail closed at the SRP gate. +They do not store a reusable refresh token or local IMAP password. The supported session policy is deliberately `per-invocation` and `no-store`; there is no local protocol listener. -The native helper also derives Proton's salted mailbox key passphrase from -`/core/v4/keys/salts`, decrypts Proton address-key tokens as binary -passphrases, verifies token signatures, and decrypts selected OpenPGP message -bodies. `show` renders the best text body through `jerboa-mail`. - -`auth-info.json` is the `/auth/v4/info` response. The command prompts for the -Proton password and emits the SRP `/auth/v4` request body plus the expected -server proof to verify after Proton responds. +The release security model is documented in +[`docs/security-model.md`](docs/security-model.md). Current release gates are +`make test`, source scanning, tracked/history secret scanning, Rust dependency +audit, sibling dependency evidence, SBOM output, and native helper +reproducibility evidence. + +`make release-evidence` writes the local release-evidence bundle under +`dist/release-evidence/`. It is allowed to fail after writing evidence; that +means a production gate is blocked. The former RustSec `rsa` blocker has been +removed from the native dependency graph by disabling the OpenPGP/SRP proof and +Drive crypto paths until a RustSec-clean backend exists. + +The native helper currently derives Proton's salted mailbox key passphrase from +`/core/v4/keys/salts`. Proton SRP proofs, address-key token decryption, +OpenPGP message decryption/signing/encryption, and Proton Drive crypto symbols +remain present for ABI compatibility but return explicit unsupported errors. +`show` cannot decrypt mail until an audit-clean OpenPGP backend is integrated. + +`auth-info.json` is the `/auth/v4/info` response. In this strict build, +`login --auth-info` exits before reading a password because SRP proof +generation is disabled until signed modulus verification is RustSec-clean. `auth-options.json` may contain either the raw Proton `AuthenticationOptions` object or the full auth JSON containing new file mode 100644 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,57 @@ +# Security Policy + +`jerboa-proton-bridge` is a native Proton Mail client experiment with +YubiKey-gated authentication goals. In the strict build, Proton SRP proof +generation and OpenPGP mailbox decryption fail closed until their verifier and +crypto backends are RustSec-clean. The repo deliberately does not expose the +upstream Bridge-style reusable local IMAP password in the default mode. + +## Supported Status + +No public production-support commitment exists yet. Treat current builds as +experimental until the release checklist in `~/Release-plan.md` and +`~/mine/jerboa-production-readiness.md` is complete for this repository. + +## Security Expectations + +- Do not persist Proton account passwords, mailbox passwords, refresh tokens, + decrypted private keys, derived passphrases, or plaintext mail by default. +- Do not add a password-authenticated local IMAP/SMTP listener without a new + threat model, isolation plan, and regression suite. +- Keep the supported session policy `per-invocation` and `no-store` unless a + YubiKey-gated encrypted cache is designed and reviewed. +- Diagnostics must not print passwords, refresh/access tokens, SRP secrets, + FIDO2 PINs, decrypted key material, raw Proton API responses containing + secrets, or plaintext message bodies except for explicit `show` output. +- Target Proton/YubiKey release proof must be marker-validated; raw account + output, auth JSON, tokens, mailbox passwords, FIDO2 PINs, decrypted key + material, and plaintext mail must never be preserved in evidence. +- Native Rust FFI boundaries must validate buffer lengths, return explicit + errors, and avoid top-level dynamic-loader crashes in static binaries. +- Release builds must pass `make test`, the baseline secret scan, the MCP + source scanner, sibling dependency evidence, SBOM/reproducibility evidence, + and a Rust dependency audit. + +## Current Production Gate + +`proton-bridge-native` no longer ships the rPGP/RSA dependency stack. The +native ABI keeps the SRP, OpenPGP, and Proton Drive symbols, but those paths +return explicit unsupported errors before reading credential or payload +arguments. Production use of live Proton auth, message decryption, or Drive +crypto remains blocked until an audit-clean SRP signed-modulus verifier and +OpenPGP backend are integrated and reviewed. + +`make target-evidence` records explicit target-only Proton/YubiKey status. +`JPROTON_BRIDGE_REQUIRE_TARGET_PROOF=1` fails closed unless +`JPROTON_BRIDGE_TARGET_PROOF_FILE` points to a marker-complete proof file. +Accepted proof files are copied into release evidence and hashed. Missing, +empty, or incomplete proof files are recorded as `status=blocked-target-proof`. + +Session, credential, and FFI details are documented in +[`docs/security-model.md`](docs/security-model.md). + +## Reporting + +Before public release, report issues privately to the repository owner. After +public release, replace this section with a dedicated advisory contact, +supported versions, and disclosure window. new file mode 100644 --- /dev/null +++ b/docs/dependency-provenance.md @@ -0,0 +1,59 @@ +# Dependency Provenance + +`jerboa-proton-bridge` is a credentialed Proton Mail client. Its production +release gate treats every native and sibling dependency as part of the trusted +computing base. + +## Native Rust Helper + +The native helper is built from `proton-bridge-native/Cargo.toml` and +`proton-bridge-native/Cargo.lock`. Release candidates must run: + +```sh +make audit +``` + +The audit gate uses `cargo audit --file proton-bridge-native/Cargo.lock +-D warnings` and fails closed when `cargo audit` is unavailable or when RustSec +reports an advisory. + +The former OpenPGP stack resolved `rsa 0.9.10` through `pgp 0.19.0` and +`proton-srp`'s default `pgpinternal` feature. RustSec reports +`RUSTSEC-2023-0071` for `rsa`, so that dependency path was removed from the +production graph. `proton-srp` is now built with default features disabled; +only mailbox password derivation remains active in the native helper. + +The native ABI still exports SRP, OpenPGP, and Proton Drive symbols for Scheme +compatibility, but those symbols return unsupported errors before reading +credential or payload arguments. Live Proton auth, OpenPGP message decryption, +address-key token decryption, and Drive crypto remain blocked until a +RustSec-clean SRP signed-modulus verifier and OpenPGP backend are integrated +and reviewed. + +On 2026-06-21, current crates.io checks showed that `pgp 0.19.0` was still the +latest rPGP release and still resolved `rsa 0.9.10`; `proton-srp 0.8.2` was the +latest `proton-srp` release. The `sad-rsa` hardened fork could not be used as a +direct Cargo patch for that graph because Cargo does not replace a transitive +dependency on the `rsa` package with a differently named package. Keep +`cargo audit --file proton-bridge-native/Cargo.lock -D warnings` fail-closed. + +## Sibling Jerboa Dependencies + +The CLI depends on these local Jerboa repos: + +- `jerboa-yubikey` for FIDO2/WebAuthn assertion support. +- `jerboa-mail` for MIME/body rendering. +- `jerboa-https` for HTTPS client behavior. +- `jerboa-ssl` for TLS/OpenSSL integration. + +`make dependency-evidence` runs each sibling `release-evidence` target when it +exists. Missing sibling release evidence is a production blocker, not a warning. +Sibling evidence discovery parses Makefiles for the `release-evidence` target +instead of using a dry-run recipe execution, so stale copied evidence is not +accepted when a sibling target is missing. + +## Runtime Policy + +The default runtime remains `per-invocation` and `no-store`: no local reusable +IMAP/SMTP password, refresh-token store, plaintext cache, or decrypted key +cache is part of the production posture. new file mode 100644 --- /dev/null +++ b/docs/release-evidence.md @@ -0,0 +1,82 @@ +# Release Evidence + +Release evidence is written under `dist/release-evidence/`: + +```sh +make release-evidence +``` + +The target records: + +- current Git state, +- high-confidence secret scan output, +- smoke tests and CLI `status`/`--help` output, +- RustSec dependency audit output, +- sibling Jerboa dependency release evidence, +- target Proton/YubiKey proof status, +- native helper reproducibility output, +- SBOM/dependency manifests, and +- source and native-artifact hashes. + +The target intentionally returns nonzero after collecting evidence when any +production gate fails. A failed `make release-evidence` with populated evidence +is therefore an actionable blocked release state. + +For this repo, a public or production release requires: + +- clean `cargo audit` for `proton-bridge-native/Cargo.lock`, +- complete release evidence for `jerboa-yubikey`, `jerboa-mail`, + `jerboa-https`, and `jerboa-ssl`, +- matching repeated native helper build hashes, +- live least-privilege Proton/YubiKey integration evidence before enabling + live auth, +- explicit review of all diagnostic output for secret non-disclosure, and +- external review of the FIDO2, FFI, and no-store boundaries. + +The strict build intentionally disables SRP proof generation, OpenPGP +decryption/signing/encryption, and Proton Drive crypto until those paths have +RustSec-clean native backends. Evidence for this state must show fail-closed +tests and SBOM posture entries rather than live credential processing. + +## Target Proton/YubiKey Proof + +`make target-evidence` always writes `dist/target-evidence/status.txt`. +Without a proof file, local evidence records target-only gates as +`blocked-not-run` and `target_proton_yubikey_proof_status=not-run`. + +Release hosts may attach reviewed proof with: + +```sh +JPROTON_BRIDGE_TARGET_PROOF_FILE=/path/to/proof.txt make target-evidence +``` + +Production/release review can require that proof: + +```sh +JPROTON_BRIDGE_REQUIRE_TARGET_PROOF=1 \ +JPROTON_BRIDGE_TARGET_PROOF_FILE=/path/to/proof.txt \ +make release-evidence +``` + +The proof file must contain these exact marker lines and must not contain raw +account output, auth JSON, tokens, passwords, FIDO2 PINs, decrypted keys, or +plaintext mail: + +```text +fido2_payload_probe_status=target-evidence-recorded +yubikey_assertion_status=target-evidence-recorded +least_privilege_proton_account_status=confirmed +diagnostic_secret_redaction_status=target-evidence-recorded +no_store_artifact_status=target-evidence-recorded +srp_disabled_status=confirmed +openpgp_disabled_status=confirmed +drive_crypto_disabled_status=confirmed +target_platform_smoke_status=target-evidence-recorded +production_integration_review_status=current-run-recorded +raw_account_output_status=not-preserved +sensitive_artifact_policy=no-proton-passwords-mailbox-passwords-tokens-fido2-pins-decrypted-keys-plaintext-mail-or-raw-account-output-preserved +``` + +Missing, empty, or marker-incomplete proof files fail closed as +`status=blocked-target-proof`. Accepted proof files are copied to +`dist/target-evidence/target-proton-yubikey-proof.txt` and hashed. new file mode 100644 --- /dev/null +++ b/docs/security-model.md @@ -0,0 +1,93 @@ +# Security Model + +`jerboa-proton-bridge` is not a drop-in Proton Mail Bridge clone. The default +mode is a read-only CLI design that avoids long-lived local protocol +credentials. In the strict build, live Proton auth and OpenPGP decrypt paths +fail closed until their native verifier stack is RustSec-clean. + +## Session And Credential Policy + +Default policy: + +- `session-lifetime`: `per-invocation` +- `session-store`: `none` +- `local-protocol`: `cli-only` +- password-authenticated IMAP/SMTP: disabled +- plaintext cache: disabled +- encrypted cache: disabled until a YubiKey-gated design is reviewed + +When the corresponding feature is enabled, the CLI may read: + +- Proton username from `--username` +- Proton account password from an interactive prompt or `--password-env` +- mailbox password from an interactive prompt or `--mailbox-password-env` when + Proton reports two-password mode +- FIDO2 PIN from an interactive prompt when `--prompt-pin` is used + +Environment-variable password input is for automation only. Prefer a process +supervisor or shell session that avoids persistent command history. Do not pass +passwords directly in argv. + +## No-Store Requirements + +The default path must not write these values to disk: + +- Proton account passwords +- mailbox passwords +- access tokens +- refresh tokens +- decrypted private keys +- derived mailbox passphrases +- FIDO2 PINs +- plaintext message bodies or attachments + +If a future cache is added, it must require an external unlock factor such as +YubiKey PIV/WebAuthn plus a user passphrase or OS keyring secret. A generated +build-local secret is not acceptable production encryption. + +## Logging And Output + +Normal diagnostics may report phase, policy, and authenticated UID. They must +not print SRP password material, refresh/access tokens, raw auth responses, +FIDO2 PINs, decrypted key material, or message bodies. + +The `show` command intentionally prints selected decrypted mail content. Treat +that output as plaintext sensitive data because terminals, shells, scrollback, +and caller processes may retain it. + +## Native Boundary + +The Rust native helper implements mailbox passphrase derivation. It keeps the +SRP, OpenPGP, and Proton Drive C ABI symbols, but those symbols return +unsupported errors before reading credential or payload arguments. Scheme FFI +callers use lazy loading so static binaries do not crash during module +initialization when dynamic loading is unavailable. + +FFI rules: + +- output buffers are length-prefixed with little-endian u32 values +- callers retry when the native helper reports insufficient buffer capacity +- bytevector length fields are checked before writing +- native errors are surfaced as Scheme errors without embedding secrets +- shared libraries are loaded only when an availability check or native call is + made +- disabled SRP/OpenPGP/Drive symbols fail closed with zero-length outputs + +## Release Requirements + +Before a production release: + +- Run `make test` from a clean checkout. +- Run `jerboa_security_scan` on the project source set. +- Run a tracked-file and history secret scan for Proton credentials, tokens, + auth JSON, key material, and plaintext mail exports. +- Run `cargo audit` for `proton-bridge-native`. +- Review `jerboa-yubikey`, `jerboa-https`, `jerboa-ssl`, `jerboa-mail`, and + any reintroduced OpenPGP dependency versions. +- Produce SBOM/reproducibility notes for released binaries. +- Produce marker-complete target Proton/YubiKey proof with no preserved raw + account output, auth JSON, credentials, token material, FIDO2 PINs, decrypted + keys, or plaintext mail. +- Treat RustSec advisories in any SRP/OpenPGP verifier dependency graph as + release blockers unless an external security review explicitly accepts the + risk. --- a/main.ss +++ b/main.ss @@ -1,29 +1,6 @@ #!chezscheme ;;; Script entry point for jerboa-proton-bridge. -(import (except (chezscheme) - make-hash-table hash-table? - sort sort! - printf fprintf - path-extension path-absolute? - with-input-from-string with-output-to-string - iota 1+ 1- - partition - make-date make-time)) - -(define home (or (getenv "HOME") ".")) -(define jerboa-dir - (or (getenv "JERBOA_HOME") - (string-append home "/mine/jerboa"))) -(define project-dir (current-directory)) - -(library-directories - (append - (list (cons project-dir project-dir) - (cons (string-append jerboa-dir "/lib") - (string-append jerboa-dir "/lib"))) - (library-directories))) - (import (proton-bridge cli)) (run-cli (command-line-arguments)) --- a/proton-bridge-native/Cargo.lock +++ b/proton-bridge-native/Cargo.lock @@ -3,103 +3,12 @@ version = 4 [[package]] -name = "adler2" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" -