Security hardening and release readiness
ober
cab0aa9862c022fc8f154aae2ca5708fa11b349a
new file mode 100644 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,35 @@ +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 + +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 curl ca-certificates git pkg-config libssl-dev openssl + + - name: Install Jerboa toolchain + run: sh support/ensure-jerboa.sh "$JERBOA_VERSION" .jerboa/bin + + - name: Test + run: make test + + - name: Audit OpenSSL linkage + run: make audit 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 @@ -1,4 +1,8 @@ lib/ +.jerboa/* +!.jerboa/ +!.jerboa/security.json +dist/ *.so *.dylib *.wpo new file mode 100644 --- /dev/null +++ b/.jerboa/security.json @@ -0,0 +1,63 @@ +{ + "version": 1, + "repo": "jerboa-crypto", + "extends": ["jerboa:ffi", "jerboa:crypto"], + "classification": "OpenSSL libcrypto bindings for hashes, HMAC, symmetric encryption, Ed25519, and scrypt", + "license": "Apache-2.0", + "paths": { + "production": ["src/**/*.ss", "jerboa_crypto_shim.c", "Makefile"], + "tests": ["tests/**"], + "generated": ["dist/**", "lib/**", ".jerboa/bin/**", "*.so", "*.dylib", "*.wpo", "*.o"], + "docs": ["README.md", "SECURITY.md", "docs/**", "*.md"] + }, + "policy": { + "failOn": ["critical", "high", "medium"], + "crypto": { + "primitiveImplementations": "deny-custom-primitives", + "legacyAlgorithms": "compatibility-only", + "algorithmPolicy": "docs/algorithm-policy.md" + }, + "ffi": { + "nativeBoundary": "jerboa_crypto_shim.c", + "requireLazyLoading": true, + "requireSchemeLengthChecks": true, + "requireCNullAndLengthChecks": true + }, + "release": { + "requireEvidence": true, + "requireOpenSSLAdvisoryCheck": true, + "requireTargetProofBeforeProductionClaim": true, + "rejectHostPrivateEvidence": true + } + }, + "targetEvidence": { + "script": "scripts/target-evidence.sh", + "optionalProofFileEnv": "JCRYPTO_TARGET_PROOF_FILE", + "requiredProofEnv": "JCRYPTO_REQUIRE_TARGET_PROOF", + "maxProofBytes": 65536, + "targetProofStatus": "target_crypto_proof_status", + "productionStatus": "production_crypto_status", + "sensitiveArtifactPolicy": "no-secrets-private-paths-host-data-or-plaintext-crypto-material", + "requiredMarkers": [ + "release_host_openssl_advisory_status=reviewed", + "target_openssl_linkage_status=reviewed", + "platform_cve_review_status=reviewed", + "external_crypto_ffi_review_status=reviewed", + "algorithm_policy_exception_review_status=reviewed", + "downstream_protocol_integration_status=reviewed", + "cross_platform_smoke_status=reviewed", + "sensitive_artifact_policy=no-secrets-private-paths-host-data-or-plaintext-crypto-material" + ] + }, + "releaseGates": [ + "make security", + "make test", + "make audit", + "make openssl-advisory-check", + "make sbom", + "make reproducibility-report", + "make target-evidence", + "make release-evidence" + ], + "suppressions": [] +} new file mode 100644 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + 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,51 +1,122 @@ -JERBUILD ?= jerbuild -JH := $(shell $(JERBUILD) --jerboa-home 2>/dev/null) -ifeq ($(JH),) -$(error jerbuild not found on PATH (or '$(JERBUILD) --jerboa-home' failed). Install jerbuild, or set JERBUILD=/path/to/jerbuild) -endif +JERBOA_VERSION ?= v0.2.3 +JERBOA_TOOL_DIR ?= $(CURDIR)/.jerboa/bin +JERBUILD ?= $(shell if [ -x "$(CURDIR)/../jerboa/dist/jerbuild" ]; then printf '%s\n' "$(CURDIR)/../jerboa/dist/jerbuild"; elif command -v jerbuild >/dev/null 2>&1; then command -v jerbuild; else printf '%s\n' "$(JERBOA_TOOL_DIR)/jerbuild"; fi) +JH = $(shell "$(JERBUILD)" --jerboa-home 2>/dev/null) LIBDIRS = lib:$(JH)/lib CC ?= cc +DIST_DIR ?= dist/release-evidence +SBOM_DIR ?= dist/sbom +TARGET_EVIDENCE_DIR ?= dist/target-evidence +WARN_CFLAGS ?= -Wall -Wextra -Werror +HARDEN_CFLAGS ?= -D_FORTIFY_SOURCE=2 -fstack-protector-strong +HARDEN_LDFLAGS ?= -# OpenSSL — Linux has it in standard paths; macOS needs brew's openssl@3 +# OpenSSL — Linux has it in standard paths; macOS should prefer the newest +# patched Homebrew line available. UNAME := $(shell uname -s) ifeq ($(UNAME),Darwin) -OPENSSL_PREFIX ?= $(shell brew --prefix openssl@3 2>/dev/null) +OPENSSL_PREFIX ?= $(shell brew --prefix openssl@4 2>/dev/null || brew --prefix openssl@3 2>/dev/null) ifneq ($(OPENSSL_PREFIX),) OPENSSL_CFLAGS := -I$(OPENSSL_PREFIX)/include OPENSSL_LIBS := -L$(OPENSSL_PREFIX)/lib -lcrypto +OPENSSL_BIN ?= $(OPENSSL_PREFIX)/bin/openssl else OPENSSL_LIBS := -lcrypto +OPENSSL_BIN ?= openssl endif else OPENSSL_LIBS := -lcrypto +OPENSSL_BIN ?= openssl +HARDEN_LDFLAGS += -Wl,-z,relro -Wl,-z,now endif -CFLAGS ?= -shared -fPIC -O2 $(OPENSSL_CFLAGS) -LIBS ?= $(OPENSSL_LIBS) +CFLAGS ?= -shared -fPIC -O2 $(WARN_CFLAGS) $(HARDEN_CFLAGS) $(OPENSSL_CFLAGS) +LIBS ?= $(HARDEN_LDFLAGS) $(OPENSSL_LIBS) SHIM = jerboa_crypto_shim.so -.PHONY: all build transpile test clean shim +.PHONY: all build transpile test clean shim ensure-jerboa-tools security audit openssl-advisory-check sbom reproducibility-report target-evidence verify release-evidence all: build +ensure-jerboa-tools: + @if ! "$(JERBUILD)" --jerboa-home >/dev/null 2>&1; then \ + echo "=== Installing Jerboa toolchain $(JERBOA_VERSION) into $(JERBOA_TOOL_DIR) ==="; \ + sh support/ensure-jerboa.sh "$(JERBOA_VERSION)" "$(JERBOA_TOOL_DIR)"; \ + fi + shim: $(SHIM) $(SHIM): jerboa_crypto_shim.c $(CC) $(CFLAGS) -o $@ $< $(LIBS) -transpile: - $(JERBUILD) transpile src lib --force +transpile: ensure-jerboa-tools + @$(JERBUILD) transpile src lib --force build: shim transpile test: build - JERBOA_CRYPTO_LIB=$(CURDIR) \ + @JERBOA_CRYPTO_LIB=$(CURDIR) \ DYLD_LIBRARY_PATH=$(CURDIR) \ LD_LIBRARY_PATH=$(CURDIR) \ $(JERBUILD) exec --libdirs "$(LIBDIRS)" tests/crypto-test.ss +security: scripts/security-check.sh + @sh scripts/security-check.sh + +audit: shim + @"$(OPENSSL_BIN)" version -a + @if command -v otool >/dev/null 2>&1; then otool -L "$(SHIM)"; elif command -v ldd >/dev/null 2>&1; then ldd "$(SHIM)"; fi + +openssl-advisory-check: + @OPENSSL="$(OPENSSL_BIN)" scripts/openssl-advisory-check.sh + +sbom: build + @OPENSSL_BIN="$(OPENSSL_BIN)" SHIM="$(SHIM)" scripts/sbom.sh "$(SBOM_DIR)" + +reproducibility-report: + @JERBUILD="$(JERBUILD)" OPENSSL_PREFIX="$(OPENSSL_PREFIX)" OPENSSL_BIN="$(OPENSSL_BIN)" scripts/reproducibility-report.sh dist/reproducibility + +target-evidence: scripts/target-evidence.sh + @REPO_ROOT="$(CURDIR)" TARGET_EVIDENCE_DIR="$(TARGET_EVIDENCE_DIR)" sh scripts/target-evidence.sh + +verify: security test audit sbom reproducibility-report target-evidence + +release-evidence: security test audit target-evidence + rm -rf "$(DIST_DIR)" + mkdir -p "$(DIST_DIR)" + OPENSSL="$(OPENSSL_BIN)" scripts/openssl-advisory-check.sh "$(DIST_DIR)/openssl-advisory-check.txt" + JERBUILD="$(JERBUILD)" OPENSSL_PREFIX="$(OPENSSL_PREFIX)" OPENSSL_BIN="$(OPENSSL_BIN)" scripts/reproducibility-report.sh dist/reproducibility + OPENSSL_BIN="$(OPENSSL_BIN)" SHIM="$(SHIM)" scripts/sbom.sh "$(SBOM_DIR)" > "$(DIST_DIR)/sbom.txt" + rm -rf "$(DIST_DIR)/reproducibility" "$(DIST_DIR)/sbom" "$(DIST_DIR)/target-evidence" + cp -R dist/reproducibility "$(DIST_DIR)/reproducibility" + cp -R "$(SBOM_DIR)" "$(DIST_DIR)/sbom" + cp -R "$(TARGET_EVIDENCE_DIR)" "$(DIST_DIR)/target-evidence" + git rev-parse HEAD > "$(DIST_DIR)/git-commit.txt" + git status --short > "$(DIST_DIR)/git-status.txt" + { printf 'platform='; uname -srm; printf 'jerbuild_version='; "$(JERBUILD)" --version; if "$(JERBUILD)" --jerboa-home >/dev/null 2>&1; then printf 'jerboa_home_status=present\n'; else printf 'jerboa_home_status=missing\n'; fi; } > "$(DIST_DIR)/build-environment.txt" + ($(CC) --version || $(CC) -v) > "$(DIST_DIR)/cc-version.txt" 2>&1 || true + "$(OPENSSL_BIN)" version -a > "$(DIST_DIR)/openssl-version.txt" + $(MAKE) security > "$(DIST_DIR)/security.log" 2>&1 + $(MAKE) test > "$(DIST_DIR)/test.log" 2>&1 + $(MAKE) audit > "$(DIST_DIR)/audit.log" 2>&1 + @if command -v otool >/dev/null 2>&1; then \ + otool -L "$(SHIM)" > "$(DIST_DIR)/native-linkage.txt"; \ + elif command -v ldd >/dev/null 2>&1; then \ + ldd "$(SHIM)" > "$(DIST_DIR)/native-linkage.txt"; \ + else \ + echo "No native linkage inspector found." > "$(DIST_DIR)/native-linkage.txt"; \ + fi + { test -f .jerboa/security.json && printf '%s\n' .jerboa/security.json; find Makefile jerboa_crypto_shim.c src lib tests docs scripts -type f \( -name 'Makefile' -o -name '*.c' -o -name '*.h' -o -name '*.sh' -o -name '*.ss' -o -name '*.sls' -o -name '*.md' \) -print; } | sort > "$(DIST_DIR)/source-inputs.txt" + shasum -a 256 jerboa_crypto_shim.c "$(SHIM)" > "$(DIST_DIR)/shim-sha256.txt" + sh scripts/sanitize-evidence.sh "$(DIST_DIR)" + sh scripts/sanitize-evidence.sh "$(SBOM_DIR)" + sh scripts/sanitize-evidence.sh dist/reproducibility + sh scripts/sanitize-evidence.sh "$(TARGET_EVIDENCE_DIR)" + grep -q '^sbom_status=present$$' "$(DIST_DIR)/sbom/manifest.txt" + grep -q '^status=match$$' "$(DIST_DIR)/reproducibility/result.txt" + clean: rm -f $(SHIM) chez_crypto_shim.so rm -rf lib --- a/README.md +++ b/README.md @@ -1,13 +1,17 @@ -# chez-crypto +# jerboa-crypto -OpenSSL libcrypto bindings for Chez Scheme. +OpenSSL `libcrypto` bindings for Jerboa. Provides hash digests, HMAC, symmetric encryption, Ed25519 signatures, and scrypt KDF. +Status: experimental. Treat this as a low-level FFI binding until the release +gates in `SECURITY.md` are complete. + ## Requirements -- Chez Scheme 10.x -- OpenSSL 3.x (`libcrypto`) +- Jerboa and `jerbuild` +- A supported, patched OpenSSL `libcrypto`. Release evidence currently accepts + OpenSSL 4.0.1+, 3.6.3+, 3.5.7+, 3.4.6+, or 3.0.21+. - GCC ## Build & Test @@ -15,8 +19,21 @@ Provides hash digests, HMAC, symmetric encryption, Ed25519 signatures, and scryp ```bash make make test +make audit +make sbom +make reproducibility-report +make target-evidence +make release-evidence ``` +Production support is still blocked until `make target-evidence` records a +marker-complete proof through `JCRYPTO_TARGET_PROOF_FILE`; production release +hosts should set `JCRYPTO_REQUIRE_TARGET_PROOF=1`. The proof covers target +OpenSSL advisory/linkage review, platform CVE review, external crypto/FFI +review, algorithm-policy exceptions, downstream protocol integration, and +cross-platform smoke. It must not contain keys, tokens, plaintext crypto +material, private paths, hostnames, customer data, or raw command output. + ## API ### Random @@ -29,6 +46,12 @@ make test - `(digest-size algo)` → integer - Streaming: `make-digest-ctx`, `digest-init!`, `digest-update!`, `digest-final!`, `free-digest-ctx` +MD5 and SHA-1 are exposed for compatibility and test-vector use, not for new +security designs. + +Production consumers should use the default allowlist in +`docs/algorithm-policy.md` unless a protocol standard requires otherwise. + ### HMAC - `(hmac algo key data)` → bytevector - `(hmac-md5 key data)`, `(hmac-sha256 key data)`, etc. @@ -46,4 +69,7 @@ make test ### KDF - `(scrypt password salt size [N r p])` → derived key bytevector -# chez-crypto + +## License + +Apache License 2.0. new file mode 100644 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,61 @@ +# Security Policy + +`jerboa-crypto` is an experimental OpenSSL `libcrypto` binding for Jerboa. It +must not be advertised as production-ready until the release gates tracked in +`~/Release-plan.md` and the repository production-readiness tracker are +complete. + +## Supported Status + +No public production-support commitment exists yet. Security-sensitive releases +must be cut from a clean checkout after: + +- `make test` passes. +- `make audit` records the OpenSSL version and linked library path for the + release artifact. +- `make openssl-advisory-check` accepts the linked OpenSSL release line. +- `make sbom` records source, generated library, native shim, OpenSSL, and + linkage evidence. +- `make reproducibility-report` reports `status=match`. +- `make target-evidence` records reviewed release-host crypto proof through + `JCRYPTO_TARGET_PROOF_FILE`, or records the local blocked status. Set + `JCRYPTO_REQUIRE_TARGET_PROOF=1` on production release hosts. +- `make release-evidence` archives the release-candidate evidence described in + `docs/release-evidence.md`. +- The high-confidence secret scan and Jerboa security scanner are clean. +- OpenSSL CVE posture for the target platform is reviewed and documented in + `docs/openssl-advisory-review.md` or the release notes. +- New protocol integrations follow `docs/algorithm-policy.md` or document a + reviewed exception. +- History has been reviewed or intentionally reset before first public release. + +## Cryptography Posture + +- This repo delegates cryptographic primitives to OpenSSL. Do not add custom + primitive implementations. +- MD5 and SHA-1 are exposed only for compatibility and test-vector use. They + must not be recommended for new security designs. +- CBC-mode ciphers are compatibility surfaces only unless a protocol-specific + construction and padding behavior review is recorded. +- Cipher APIs validate key, IV, nonce, tag, and Ed25519 key lengths before + crossing the native boundary. +- See `docs/ffi-boundary.md` for ownership, lifetime, and release-review notes. + +## FFI Expectations + +- Native symbols are loaded lazily through `(jerboa ffi)` so imports and static + binaries do not crash before the caller needs crypto functionality. +- Scheme owns all bytevector inputs and outputs. C functions must treat pointers + as borrowed for the duration of the call. +- C entry points must reject null pointers and negative lengths even when the + Scheme layer validates normal callers. +- OpenSSL dependency drift must be reviewed for every release candidate. +- Target proof files must be marker-only summaries. Do not store private keys, + tokens, plaintext crypto material, raw command output, private paths, + hostnames, or customer data in release evidence. + +## 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/algorithm-policy.md @@ -0,0 +1,51 @@ +# jerboa-crypto Algorithm Policy + +`jerboa-crypto` is a low-level binding over OpenSSL. It exposes some generic +OpenSSL digest and cipher names for compatibility, but production applications +must choose an explicit protocol-level allowlist instead of passing user or +configuration strings through unchecked. + +## Default Production Allowlist + +Use these names for new designs unless a protocol standard requires otherwise: + +- Digests: `sha256`, `sha384`, `sha512`. +- HMAC: `hmac-sha256`, `hmac-sha384`, `hmac-sha512`. +- AEAD encryption: `chacha20-poly1305`, `aes-256-gcm`, or `aes-128-gcm`. +- Signatures: Ed25519. +- Password KDF: `scrypt` with parameters selected by the calling protocol and + documented in that protocol's release evidence. + +## Legacy Or Compatibility Only + +These remain available for parsing old formats, test vectors, or compatibility +with an existing protocol, but must not be selected for new authentication, +integrity, or encryption designs: + +- `md5`, `sha1`, and HMAC variants built on MD5 or SHA-1. +- CBC-mode ciphers such as `aes-256-cbc`, unless the protocol already provides + a reviewed MAC-then-encrypt or encrypt-then-MAC construction and padding + behavior is covered by tests. + +## Rejected For Production Defaults + +Do not add new wrappers or examples for: + +- Custom cryptographic primitives implemented in this repository. +- Raw RSA/DSA/ECDSA flows without a protocol-specific padding, hash, key-size, + and side-channel review. +- Unauthenticated encryption for new data at rest or transport protocols. +- OpenSSL algorithms enabled only by legacy providers unless the release notes + document the provider, platform, and accepted risk. + +## Release Evidence + +Every release candidate should record: + +- The linked OpenSSL release line and advisory check output. +- SBOM evidence for source, native shim, generated Jerboa library, and OpenSSL + toolchain/linkage. +- Reproducibility evidence for the native shim and generated library. +- Any protocol-specific deviations from the default allowlist above. +- Marker-complete target proof for external crypto/FFI review and downstream + protocol integration review before production support is claimed. new file mode 100644 --- /dev/null +++ b/docs/ffi-boundary.md @@ -0,0 +1,56 @@ +# jerboa-crypto FFI Boundary + +`jerboa-crypto` exposes a small Jerboa API over OpenSSL `libcrypto` through +`jerboa_crypto_shim.c`. + +## Trust Boundaries + +- Scheme callers are trusted to choose algorithms appropriate to their protocol, + but all bytevector sizes are still validated before native calls. +- OpenSSL algorithm names are untrusted strings. The Scheme layer rejects empty + names and names containing NUL before passing them to C. +- Native pointers are borrowed. C must not retain Scheme-owned bytevectors after + returning. +- The shared object is loaded lazily through `(jerboa ffi)` to avoid import-time + crashes in static binaries or feature probes. + +## Memory And Lifetime Rules + +- Scheme owns all input and output buffers. +- Digest, HMAC, cipher, AEAD, Ed25519, and scrypt functions check bytevector + lengths before converting them to C `int`. +- Cipher and AEAD calls verify key/IV/nonce lengths against OpenSSL metadata + before passing pointers to `EVP_*` APIs. +- C entry points reject null pointers, negative lengths, and unsupported tag or + Ed25519 key sizes as a second line of defense. +- Streaming context pointers returned by OpenSSL must be freed with the matching + `free-*` procedure. + +## Crypto Caveats + +- OpenSSL provides the primitive implementations. This repository should not add + custom cryptographic primitives. +- MD5 and SHA-1 remain available for compatibility. They are not acceptable for + new integrity or signature designs. +- AEAD APIs currently emit and require 16-byte tags. +- The public API accepts arbitrary OpenSSL cipher and digest names; release + notes for consumers should document protocol-specific allowlists. The default + production policy is in `docs/algorithm-policy.md`. + +## Release Gates + +Before production release: + +- `make test` must pass from a clean checkout. +- `make audit` must record OpenSSL version and dynamic linkage. +- `make openssl-advisory-check` must accept the linked OpenSSL release line. +- `make sbom` must record source, generated library, native shim, OpenSSL, and + linkage evidence. +- `make reproducibility-report` must report `status=match`. +- `make target-evidence` must record reviewed target OpenSSL, platform CVE, + external crypto/FFI, downstream integration, and cross-platform smoke proof + before any production support claim. +- Jerboa security scanner and the high-confidence secret scan must be clean. +- OpenSSL CVE status and platform support must be documented in release notes + or `docs/openssl-advisory-review.md`. +- Algorithm-policy exceptions must be documented. new file mode 100644 --- /dev/null +++ b/docs/openssl-advisory-review.md @@ -0,0 +1,50 @@ +# OpenSSL Advisory Review + +Review date: 2026-06-21 + +`jerboa-crypto` is a low-level `libcrypto` FFI boundary. A release candidate is +not acceptable if it links an OpenSSL release line below the patched versions +listed by upstream for the 2026-06-09 advisory set. + +## Accepted OpenSSL Lines + +Release evidence must pass `make openssl-advisory-check`. The current policy +accepts these upstream-supported minimums: + +- OpenSSL 4.0.1 or newer on the 4.0 line. +- OpenSSL 3.6.3 or newer on the 3.6 line. +- OpenSSL 3.5.7 or newer on the 3.5 LTS line. +- OpenSSL 3.4.6 or newer on the 3.4 line. +- OpenSSL 3.0.21 or newer on the 3.0 LTS line. + +OpenSSL 3.6.2 is rejected for production release. It predates the 2026-06-09 +security patch release set, which includes a High severity issue fixed in +3.6.3 and 4.0.1. + +Official sources: + +- https://openssl-library.org/source/ +- https://openssl-library.org/news/secadv/20260609.txt +- https://openssl-library.org/news/vulnerabilities/ + +## Local Review Result + +The local release path uses Homebrew `openssl@4` after upgrading that keg to +OpenSSL 4.0.1. + +`jerboa-crypto` delegates digest, HMAC, cipher, AEAD, Ed25519, random, and KDF +operations to OpenSSL. It does not call the PKCS#7, CMS decryption, PKCS#12, +QUIC, OCSP, or AES-OCB one-shot APIs implicated by several 2026-06-09 issues, +but it must still link a patched OpenSSL because this repo exposes raw +cryptographic primitives to downstream Jerboa libraries and applications. + +## Release Gate + +Before publishing: + +- Run `make clean`. +- Run `make release-evidence JERBUILD=/path/to/jerbuild`. +- Confirm `dist/release-evidence/openssl-advisory-check.txt` reports + `status=pass`. +- Confirm `dist/release-evidence/reproducibility/result.txt` reports + `status=match`. new file mode 100644 --- /dev/null +++ b/docs/release-evidence.md @@ -0,0 +1,85 @@ +# Release Evidence + +`jerboa-crypto` is a low-level OpenSSL `libcrypto` boundary. It is not +production-ready until each release candidate has current build/test, SBOM, +OpenSSL linkage, advisory/CVE, reproducibility, algorithm-policy, and +downstream integration evidence. + +Run from a clean checkout: + +```sh +make clean +make release-evidence JERBUILD=/path/to/jerbuild +make target-evidence +``` + +The `release-evidence` target runs `make test`, `make audit`, the OpenSSL +advisory gate, SBOM generation, and a two-clean-build reproducibility check. It writes +untracked artifacts under `dist/release-evidence/`: + +- `git-commit.txt` and `git-status.txt`. +- `build-environment.txt` with OS and Jerboa toolchain identity. +- `cc-version.txt` and `openssl-version.txt`. +- `native-linkage.txt` from `otool -L` or `ldd`. +- `openssl-advisory-check.txt` with the accepted OpenSSL release-line result. +- `sbom.txt` and `sbom/` with source, generated library, native shim, OpenSSL, + and linkage evidence. +- `reproducibility/result.txt` plus first/second native shim and generated + library hashes. +- `target-evidence/`, a local blocked-status record or a reviewed target proof + copied with a SHA-256 sidecar. +- `source-inputs.txt` for source, docs, tests, and evidence scripts. +- `shim-sha256.txt` for the C shim source and built native shim. + +Before publishing, review OpenSSL advisories for the linked package or distro +build and document any accepted CVE or platform risk in the release notes. +The current accepted OpenSSL release lines are documented in +`docs/openssl-advisory-review.md`, and `make release-evidence` rejects older +OpenSSL versions automatically. + +Protocol integrations must either follow `docs/algorithm-policy.md` or record a +reviewed exception in release notes. + +Reproducibility evidence is claimed only for two clean builds in the same +toolchain environment. The automated report compares the generated Jerboa +library and native shim artifact. It archives hashes, status, and diffs only; +raw build logs and native artifacts stay in scratch space. + +Release evidence must remain host-neutral. Use status fields such as +`jerboa_home_status=present` instead of private toolchain paths, and `uname +-srm` instead of full host-identifying platform banners. Do not store keys, +tokens, plaintext crypto material, private paths, hostnames, customer data, or +raw target command output as proof. + +## Target Production Proof + +`scripts/target-evidence.sh` writes `dist/target-evidence/status.txt`. +Without a proof file, local release evidence records: + +- `target_evidence_status: local-release-proof-recorded` +- `target_crypto_proof_status: not-run` +- `release_host_openssl_advisory_status: blocked-not-run` +- `target_openssl_linkage_status: blocked-not-run` +- `platform_cve_review_status: blocked-not-run` +- `external_crypto_ffi_review_status: blocked-not-run` +- `algorithm_policy_exception_review_status: blocked-not-run` +- `downstream_protocol_integration_status: blocked-not-run` +- `cross_platform_smoke_status: blocked-not-run` +- `production_crypto_status: blocked-not-run` + +To attach reviewed target evidence, set `JCRYPTO_TARGET_PROOF_FILE` to a file +that contains these exact markers: + +```text +release_host_openssl_advisory_status=reviewed +target_openssl_linkage_status=reviewed +platform_cve_review_status=reviewed +external_crypto_ffi_review_status=reviewed +algorithm_policy_exception_review_status=reviewed +downstream_protocol_integration_status=reviewed +cross_platform_smoke_status=reviewed +sensitive_artifact_policy=no-secrets-private-paths-host-data-or-plaintext-crypto-material +``` + +Set `JCRYPTO_REQUIRE_TARGET_PROOF=1` on production release hosts to fail closed +when proof is missing or incomplete. Proof files must be marker summaries only. --- a/jerboa_crypto_shim.c +++ b/jerboa_crypto_shim.c @@ -11,9 +11,18 @@ #include <stdlib.h> #include <string.h> +static int bad_len(int len) { + return len < 0; +} + +static int missing_input(const void *ptr, int len) { + return len > 0 && ptr == NULL; +} + /* ---- Error handling ---- */ int jerboa_crypto_err_get(char *buf, int buflen) { + if (buf == NULL || buflen <= 0) return -1; unsigned long e = ERR_get_error(); if (e == 0) { buf[0] = 0; @@ -26,6 +35,7 @@ int jerboa_crypto_err_get(char *buf, int buflen) { /* ---- Random ---- */ int jerboa_rand_bytes(unsigned char *buf, int n) { + if (buf == NULL || bad_len(n)) return -1; return RAND_bytes(buf, n); } @@ -40,16 +50,19 @@ void jerboa_digest_ctx_free(void *ctx) { } int jerboa_digest_init(void *ctx, const char *algo) { + if (ctx == NULL || algo == NULL) return -10; const EVP_MD *md = EVP_get_digestbyname(algo); if (!md) return -1; return EVP_DigestInit_ex((EVP_MD_CTX *)ctx, md, NULL) == 1 ? 0 : -2; } int jerboa_digest_update(void *ctx, const unsigned char *data, int len) { + if (ctx == NULL || bad_len(len) || missing_input(data, len)) return -10; return EVP_DigestUpdate((EVP_MD_CTX *)ctx, data, len) == 1 ? 0 : -1; } int jerboa_digest_final(void *ctx, unsigned char *out, int *outlen) { + if (ctx == NULL || out == NULL || outlen == NULL) return -10; unsigned int len = 0; int rc = EVP_DigestFinal_ex((EVP_MD_CTX *)ctx, out, &len); *outlen = (int)len; @@ -57,6 +70,7 @@ int jerboa_digest_final(void *ctx, unsigned char *out, int *outlen) { } int jerboa_digest_size(const char *algo) { + if (algo == NULL) return -10; const EVP_MD *md = EVP_get_digestbyname(algo); if (!md) return -1; return EVP_MD_size(md); @@ -65,6 +79,10 @@ int jerboa_digest_size(const char *algo) { /* One-shot digest */ int jerboa_digest(const char *algo, const unsigned char *data, int datalen, unsigned char *out, int *outlen) { + if (algo == NULL || out == NULL || outlen == NULL || + bad_len(datalen) || missing_input(data, datalen)) { + return -10; + } const EVP_MD *md = EVP_get_digestbyname(algo); if (!md) return -1; EVP_MD_CTX *ctx = EVP_MD_CTX_new(); @@ -86,6 +104,11 @@ int jerboa_digest(const char *algo, const unsigned char *data, int datalen, int jerboa_hmac(const char *algo, const unsigned char *key, int keylen, const unsigned char *data, int datalen, unsigned char *out, int *outlen) { + if (algo == NULL || out == NULL || outlen == NULL || + bad_len(keylen) || bad_len(datalen) || + missing_input(key, keylen) || missing_input(data, datalen)) { + return -10; + } const EVP_MD *md = EVP_get_digestbyname(algo); if (!md) return -1; unsigned int len = 0; @@ -105,18 +128,21 @@ void jerboa_cipher_ctx_free(void *ctx) { } int jerboa_cipher_key_length(const char *algo) { + if (algo == NULL) return -10; const EVP_CIPHER *c = EVP_get_cipherbyname(algo); if (!c) return -1; return EVP_CIPHER_key_length(c); } int jerboa_cipher_iv_length(const char *algo) { + if (algo == NULL) return -10; const EVP_CIPHER *c = EVP_get_cipherbyname(algo); if (!c) return -1; return EVP_CIPHER_iv_length(c); } int jerboa_cipher_block_size(const char *algo) { + if (algo == NULL) return -10; const EVP_CIPHER *c = EVP_get_cipherbyname(algo); if (!c) return -1; return EVP_CIPHER_block_size(c); @@ -124,6 +150,7 @@ int jerboa_cipher_block_size(const char *algo) { int jerboa_encrypt_init(void *ctx, const char *algo, const unsigned char *key, const unsigned char *iv) { + if (ctx == NULL || algo == NULL || key == NULL || iv == NULL) return -10; const EVP_CIPHER *c = EVP_get_cipherbyname(algo); if (!c) return -1; return EVP_EncryptInit_ex((EVP_CIPHER_CTX *)ctx, c, NULL, key, iv) == 1 ? 0 : -2; @@ -131,15 +158,21 @@ int jerboa_encrypt_init(void *ctx, const char *algo, int jerboa_encrypt_update(void *ctx, const unsigned char *in, int inlen, unsigned char *out, int *outlen) { + if (ctx == NULL || out == NULL || outlen == NULL || + bad_len(inlen) || missing_input(in, inlen)) { + return -10; + } return EVP_EncryptUpdate((EVP_CIPHER_CTX *)ctx, out, outlen, in, inlen) == 1 ? 0 : -1; } int jerboa_encrypt_final(void *ctx, unsigned char *out, int *outlen) { + if (ctx == NULL || out == NULL || outlen == NULL) return -10; return EVP_EncryptFinal_ex((EVP_CIPHER_CTX *)ctx, out, outlen) == 1 ? 0 : -1; } int jerboa_decrypt_init(void *ctx, const char *algo, const unsigned char *key, const unsigned char *iv) { + if (ctx == NULL || algo == NULL || key == NULL || iv == NULL) return -10; const EVP_CIPHER *c = EVP_get_cipherbyname(algo); if (!c) return -1;