diff --git a/.github/workflows/update-boringssl.yml b/.github/workflows/update-boringssl.yml new file mode 100644 index 00000000..7c00ff05 --- /dev/null +++ b/.github/workflows/update-boringssl.yml @@ -0,0 +1,96 @@ +name: update-boringssl + +on: + workflow_dispatch: + inputs: + revision: + description: Optional BoringSSL revision SHA. Leave empty for latest. + required: false + type: string + schedule: + - cron: '0 9 * * 1' + +permissions: + contents: write + pull-requests: write + +concurrency: + group: update-boringssl + cancel-in-progress: false + +jobs: + roll: + name: Roll BoringSSL + runs-on: ubuntu-latest + timeout-minutes: 60 + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - uses: subosito/flutter-action@v2 + with: + channel: stable + cache: true + + - name: Install system dependencies + run: | + sudo apt-get update -y + sudo apt-get install -y jq ninja-build libgtk-3-dev + flutter config --no-analytics + + - name: Configure git + run: | + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + + - name: Run BoringSSL roll script + id: roll + shell: bash + run: | + set -euo pipefail + revision='${{ github.event.inputs.revision || '' }}' + if [[ -n "$revision" ]]; then + bash ./tool/bump-boringssl-revision.sh "$revision" + else + bash ./tool/bump-boringssl-revision.sh + fi + + new_revision="$(tr -d ' \t\n\r' < tool/REVISION)" + echo "revision=$new_revision" >> "$GITHUB_OUTPUT" + echo "short_revision=${new_revision:0:8}" >> "$GITHUB_OUTPUT" + + - name: Check for changes + id: changes + shell: bash + run: | + if git diff --quiet; then + echo "changed=false" >> "$GITHUB_OUTPUT" + else + echo "changed=true" >> "$GITHUB_OUTPUT" + fi + + - name: Create pull request + if: steps.changes.outputs.changed == 'true' + uses: peter-evans/create-pull-request@v7 + with: + branch: "update-boringssl-${{ steps.roll.outputs.short_revision }}" + delete-branch: true + commit-message: "chore: update BoringSSL to ${{ steps.roll.outputs.short_revision }}" + title: "chore: update BoringSSL to ${{ steps.roll.outputs.short_revision }}" + body: | + Updates vendored BoringSSL to `${{ steps.roll.outputs.revision }}` using `tool/bump-boringssl-revision.sh`. + + This workflow is intentionally thin: it just provisions the environment, runs the script, and opens a PR if the script produced a diff. + labels: | + dependencies + boringssl-update + + - name: Summary + shell: bash + run: | + if [[ '${{ steps.changes.outputs.changed }}' == 'true' ]]; then + echo "Created or updated a PR for BoringSSL revision ${{ steps.roll.outputs.revision }}" + else + echo "No vendored changes were produced for BoringSSL revision ${{ steps.roll.outputs.revision }}" + fi diff --git a/lib/src/boringssl/lookup/lookup.dart b/lib/src/boringssl/lookup/lookup.dart index 40c9bc9f..4581ccf9 100644 --- a/lib/src/boringssl/lookup/lookup.dart +++ b/lib/src/boringssl/lookup/lookup.dart @@ -28,8 +28,13 @@ export 'symbols.generated.dart' show Sym; ) external Pointer _nativeWebcryptoLookupSymbol(int index); -/// Resolve lookup from native assets first, then fall back to the legacy -/// runtime loading strategy used by `flutter pub run webcrypto:setup`. +@Native( + symbol: 'webcrypto_get_CBB_size', + assetId: 'package:webcrypto/webcrypto.dart', +) +external int _nativeWebcryptoGetCbbSize(); + +/// Resolve lookup from the bundled native asset for `package:webcrypto`. Pointer lookup(String symbolName) { final sym = symFromString(symbolName); return _nativeWebcryptoLookupSymbol(sym.index).cast(); @@ -38,6 +43,9 @@ Pointer lookup(String symbolName) { /// Gives access to BoringSSL symbols. final BoringSsl ssl = BoringSsl.fromLookup(lookup); +/// Gets the native `sizeof(CBB)` value from the bundled helper library. +int nativeWebcryptoGetCbbSize() => _nativeWebcryptoGetCbbSize(); + /// ERR_GET_LIB returns the library code for the error. This is one of the /// ERR_LIB_* values. /// diff --git a/lib/src/boringssl/lookup/utils.dart b/lib/src/boringssl/lookup/utils.dart index cb956ffc..457df6f2 100644 --- a/lib/src/boringssl/lookup/utils.dart +++ b/lib/src/boringssl/lookup/utils.dart @@ -69,9 +69,9 @@ lookupLibraryInDotDartTool() { /// Find the `.dart_tool/` folder, returns `null` if unable to find it. Uri? _findDotDartTool() { - // HACK: We have no good mechanism for finding the library created by: - // flutter pub run webcrypto:setup - // So we search relative to the script path and CWD. + // HACK: We have no good mechanism for finding the legacy + // `.dart_tool/webcrypto/` output, so we search relative to the script path + // and CWD. // Find script directory Uri root = Platform.script.resolve('./'); diff --git a/lib/src/impl_ffi/impl_ffi.dart b/lib/src/impl_ffi/impl_ffi.dart index 7f985909..e53bebbf 100644 --- a/lib/src/impl_ffi/impl_ffi.dart +++ b/lib/src/impl_ffi/impl_ffi.dart @@ -29,7 +29,8 @@ import 'package:webcrypto/src/third_party/boringssl/generated_bindings.dart'; import '../jsonwebkey.dart' show JsonWebKey; import '../webcrypto/webcrypto.dart'; import '../impl_interface/impl_interface.dart'; -import '../boringssl/lookup/lookup.dart' show ssl, ERR_GET_LIB, ERR_GET_REASON; +import '../boringssl/lookup/lookup.dart' + show ssl, nativeWebcryptoGetCbbSize, ERR_GET_LIB, ERR_GET_REASON; part 'impl_ffi.aescbc.dart'; part 'impl_ffi.aesctr.dart'; diff --git a/lib/src/impl_ffi/impl_ffi.utils.dart b/lib/src/impl_ffi/impl_ffi.utils.dart index 5ea46b4e..a44df3e3 100644 --- a/lib/src/impl_ffi/impl_ffi.utils.dart +++ b/lib/src/impl_ffi/impl_ffi.utils.dart @@ -308,12 +308,14 @@ extension on _Scope { ffi.Pointer createCBS(List data) { final cbs = this(); - ssl.CBS_init(cbs, dataAsPointer(data), data.length); + cbs.ref.data = dataAsPointer(data); + cbs.ref.len = data.length; return cbs; } ffi.Pointer createCBB([int sizeHint = 4096]) { - final cbb = this(); + final cbbSize = nativeWebcryptoGetCbbSize(); + final cbb = allocate(cbbSize).cast(); ssl.CBB_zero(cbb); _checkOp(ssl.CBB_init(cbb, sizeHint) == 1, fallback: 'allocation failure'); defer(() => ssl.CBB_cleanup(cbb)); diff --git a/lib/src/third_party/boringssl/ffigen.yaml b/lib/src/third_party/boringssl/ffigen.yaml index e015bb82..ce721aa4 100644 --- a/lib/src/third_party/boringssl/ffigen.yaml +++ b/lib/src/third_party/boringssl/ffigen.yaml @@ -11,9 +11,9 @@ headers: - '../../../../third_party/boringssl/src/include/openssl/cipher.h' - '../../../../third_party/boringssl/src/include/openssl/crypto.h' - '../../../../third_party/boringssl/src/include/openssl/digest.h' - - '../../../../third_party/boringssl/src/include/openssl/ec_key.h' - '../../../../third_party/boringssl/src/include/openssl/ec.h' - '../../../../third_party/boringssl/src/include/openssl/ecdh.h' + - '../../../../third_party/boringssl/src/include/openssl/ec_key.h' - '../../../../third_party/boringssl/src/include/openssl/ecdsa.h' - '../../../../third_party/boringssl/src/include/openssl/err.h' - '../../../../third_party/boringssl/src/include/openssl/evp.h' diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 494ade75..a5a7e283 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -12,24 +12,22 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Build script used by `flutter pub run webcrypto:setup` to create -# `.dart_tool/webcrypto/webcrypto.{so|dylib|dll}` for use by `flutter test`. -# -# When running as a plugin platform specific build scripts will be used: -# - `android/CMakeLists.txt`, for Android, -# - TODO: `ios/podspec...` -# -# This script is very similar to platform specific build scripts. +# Build script used by native hooks and local tooling to create the shared +# `webcrypto` library for the current host platform. cmake_minimum_required(VERSION 3.10.0) project(webcrypto) +# Set C++ standard to C++17 for BoringSSL compatibility +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + enable_language(ASM) # Set as required by ../third_party/boringssl/sources.cmake included below set(BORINGSSL_ROOT ../third_party/boringssl/) -# Import sources as generated by tool/update-boringssl.py +# Import sources as generated by tool/bump-boringssl-revision.sh # This provides variables, and requires BORINGSSL_ROOT to be set. # - crypto_sources # - crypto_sources_linux_aarch64 @@ -37,8 +35,11 @@ set(BORINGSSL_ROOT ../third_party/boringssl/) # - crypto_sources_linux_ppc64le # - crypto_sources_linux_x86 # - crypto_sources_linux_x86_64 -# - crypto_sources_mac_x86 -# - crypto_sources_mac_x86_64 +# - crypto_sources_apple_aarch64 +# - crypto_sources_apple_arm +# - crypto_sources_apple_x86 +# - crypto_sources_apple_x86_64 +# - crypto_sources_win_aarch64 # - crypto_sources_win_x86 # - crypto_sources_win_x86_64 include( @@ -85,7 +86,7 @@ if(${CMAKE_SYSTEM_NAME} STREQUAL "Windows") set(PLATFORM "win") elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Darwin" OR ${CMAKE_SYSTEM_NAME} STREQUAL "iOS") - set(PLATFORM "mac") + set(PLATFORM "apple") else() # Assume we're on linux or similar platform set(PLATFORM "linux") @@ -108,6 +109,7 @@ if(MSVC) "C4267" # conversion from 'size_t' to 'int', possible loss of data "C4706" # assignment within conditional expression "C4141" + "C4201" # nonstandard extension used: nameless struct/union ) string(REPLACE "C" " -wd" MSVC_DISABLED_WARNINGS_STR ${MSVC_DISABLED_WARNINGS_LIST}) @@ -132,6 +134,8 @@ if(WIN32) add_definitions(-DNOMINMAX) # Allow use of fopen. add_definitions(-D_CRT_SECURE_NO_WARNINGS) + # Ensure proper Windows entropy sources + add_definitions(-DBORINGSSL_UNSAFE_DETERMINISTIC_MODE=0) endif() add_library( diff --git a/src/webcrypto.c b/src/webcrypto.c index 96d1726f..48d25358 100644 --- a/src/webcrypto.c +++ b/src/webcrypto.c @@ -22,3 +22,7 @@ WEBCRYPTO_EXPORT void* webcrypto_lookup_symbol(int32_t index) { return _webcrypto_symbol_table[index]; } + +WEBCRYPTO_EXPORT size_t webcrypto_get_CBB_size(void) { + return sizeof(CBB); +} diff --git a/src/webcrypto.h b/src/webcrypto.h index 707a9a09..f29466d1 100644 --- a/src/webcrypto.h +++ b/src/webcrypto.h @@ -14,6 +14,7 @@ * limitations under the License. */ +#include #include // Macro for annotating all functions to be exported @@ -26,4 +27,7 @@ // Function to lookup BoringSSL symbols based on index in the Sym enum. // See src/symbols.yaml for details. -WEBCRYPTO_EXPORT void* webcrypto_lookup_symbol(int32_t index); \ No newline at end of file +WEBCRYPTO_EXPORT void* webcrypto_lookup_symbol(int32_t index); + +// Helper function to get the size of CBB structure for FFI allocation. +WEBCRYPTO_EXPORT size_t webcrypto_get_CBB_size(void); diff --git a/third_party/boringssl/README.md b/third_party/boringssl/README.md index 1e5a5788..1dfc67e8 100644 --- a/third_party/boringssl/README.md +++ b/third_party/boringssl/README.md @@ -3,9 +3,8 @@ **GENERATED FOLDER DO NOT MODIFY** This folder contains sources from BoringSSL allowing `package:webcrypto` to -incorporate libcrypto from BoringSSL. Contents of this folder is generated -using `tool/update-boringssl.py` which utilizes scripts and procedures from -`src/INCORPORATING.md` to faciliate embedding of libcrypto from BoringSSL. +incorporate libcrypto from BoringSSL. Contents of this folder are generated +using `tool/bump-boringssl-revision.sh`. Files in this folder are subject to `LICENSE` from the BoringSSL project. diff --git a/third_party/boringssl/sources.cmake b/third_party/boringssl/sources.cmake index 8b2cc7fe..1edab490 100644 --- a/third_party/boringssl/sources.cmake +++ b/third_party/boringssl/sources.cmake @@ -15,7 +15,7 @@ # **GENERATED FILE DO NOT MODIFY** # # This file is generated using: -# `tool/update-boringssl.py` +# `tool/bump-boringssl-revision.sh` set(crypto_sources ${BORINGSSL_ROOT}err_data.c diff --git a/tool/REVISION b/tool/REVISION new file mode 100644 index 00000000..83e1d789 --- /dev/null +++ b/tool/REVISION @@ -0,0 +1 @@ +a873ab7906bc5b1431821864df8036068aab972d diff --git a/tool/bump-boringssl-revision.sh b/tool/bump-boringssl-revision.sh new file mode 100644 index 00000000..d7b4fc79 --- /dev/null +++ b/tool/bump-boringssl-revision.sh @@ -0,0 +1,483 @@ +#!/bin/bash + +# Copyright 2026 Google LLC +# +# 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. + +set -euo pipefail + +# Script to update BoringSSL revision and regenerate all necessary files. +# Usage: ./tool/bump-boringssl-revision.sh [revision] [--dry-run] + +if [[ "${1:-}" == "--help" || "${1:-}" == "-h" ]]; then + cat <<'EOF' +Usage: ./tool/bump-boringssl-revision.sh [revision] [--dry-run] + +Updates BoringSSL to the specified revision or latest if no revision is given. + +Arguments: + revision Optional. Specific BoringSSL revision (SHA) to update to. + --dry-run Optional. Report the target revision without changing files. +EOF + exit 0 +fi + +DRY_RUN=false +TARGET_REVISION="" + +while [[ $# -gt 0 ]]; do + case "$1" in + --dry-run) + DRY_RUN=true + shift + ;; + -*) + echo "Unknown option: $1" >&2 + exit 1 + ;; + *) + if [[ -z "$TARGET_REVISION" ]]; then + TARGET_REVISION="$1" + else + echo "Multiple revisions specified" >&2 + exit 1 + fi + shift + ;; + esac +done + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" +ROOT="$DIR/.." +REVISION_FILE="$DIR/REVISION" +BORINGSSL_REPOSITORY='https://boringssl.googlesource.com/boringssl' +PYTHON_BIN="" + +log_info() { + echo "[info] $1" +} + +log_success() { + echo "[ok] $1" +} + +log_error() { + echo "[error] $1" >&2 +} + +section() { + echo "" + echo "### $1" + echo "" +} + +get_current_revision() { + if [[ -f "$REVISION_FILE" ]]; then + tr -d ' \t\n\r' < "$REVISION_FILE" + else + log_error "REVISION file not found at $REVISION_FILE" + exit 1 + fi +} + +# update_revision +update_revision() { + local new_revision="$1" + echo "$new_revision" > "$REVISION_FILE" + log_success "Updated REVISION file to: $new_revision" +} + +get_latest_revision() { + git ls-remote "$BORINGSSL_REPOSITORY" HEAD | awk '{print $1}' +} + +# check_command +check_command() { + local command="$1" + local hint="$2" + if ! command -v "$command" >/dev/null 2>&1; then + log_error "$hint" + exit 1 + fi +} + +# resolve_python +resolve_python() { + local command + for command in python3 python; do + if command -v "$command" >/dev/null 2>&1 && + "$command" -c "import sys" >/dev/null 2>&1; then + echo "$command" + return 0 + fi + done + + log_error "python3 or python is required to enumerate BoringSSL source files" + exit 1 +} + +cleanup_boringssl() { + local path="$ROOT/third_party/boringssl" + log_info "Cleaning up old BoringSSL files..." + rm -rf "$path" + mkdir -p "$path" +} + +# git_clone_boringssl +git_clone_boringssl() { + local revision="$1" + local target="$2" + + log_info "Cloning BoringSSL repository..." + git clone "$BORINGSSL_REPOSITORY" "$target" >/dev/null 2>&1 + log_info "Checking out revision: $revision" + git -C "$target" checkout --detach "$revision" >/dev/null 2>&1 +} + +# write_build_manifest +write_build_manifest() { + local src_root="$1" + local manifest="$2" + + "$PYTHON_BIN" - "$src_root" > "$manifest" <<'PY' +import json +import os +import sys + +boringssl_root = os.path.abspath(sys.argv[1]) +sources_json = os.path.join(boringssl_root, "gen", "sources.json") +if not os.path.exists(sources_json): + sources_json = os.path.join(boringssl_root, "sources.json") + +if not os.path.exists(sources_json): + raise SystemExit(f"Could not find sources.json in {boringssl_root}") + +with open(sources_json, encoding="utf-8") as f: + sources = json.load(f) + +bcm = sources.get("bcm", {}) +crypto = sources.get("crypto", {}) +test_support = sources.get("test_support", {}) + + +def classify_asm(path): + normalized = path[4:] if path.startswith("src/") else path + + if normalized.endswith(".asm"): + if "-x86-win.asm" in normalized or "586-win.asm" in normalized: + return "win_x86" + return "win_x86_64" + + if normalized.endswith("-win.S"): + return "win_aarch64" + + if normalized.endswith("-apple.S"): + if "armv7" in normalized or "armv4" in normalized: + return "apple_arm" + if "armv8" in normalized: + return "apple_aarch64" + if "-x86-" in normalized or "x86-apple" in normalized: + return "apple_x86" + return "apple_x86_64" + + if normalized.endswith("-linux.S"): + if "ppc" in normalized: + return "linux_ppc64le" + if "armv7" in normalized or "armv4" in normalized: + return "linux_arm" + if "armv8" in normalized: + return "linux_aarch64" + if "-x86-" in normalized or "586-linux" in normalized: + return "linux_x86" + return "linux_x86_64" + + if normalized == "crypto/curve25519/asm/x25519-asm-arm.S": + return "linux_arm" + if normalized == "crypto/poly1305/poly1305_arm_asm.S": + return "linux_arm" + if normalized == "crypto/hrss/asm/poly_rq_mul.S": + return "linux_x86_64" + if normalized.startswith("third_party/fiat/asm/"): + return "linux_x86_64" + + return None + + +asm_outputs = { + "apple_aarch64": [], + "apple_arm": [], + "apple_x86": [], + "apple_x86_64": [], + "linux_aarch64": [], + "linux_arm": [], + "linux_ppc64le": [], + "linux_x86": [], + "linux_x86_64": [], + "win_aarch64": [], + "win_x86": [], + "win_x86_64": [], +} + +for file in ( + bcm.get("asm", []) + + bcm.get("nasm", []) + + crypto.get("asm", []) + + crypto.get("nasm", []) + + test_support.get("asm", []) + + test_support.get("nasm", []) +): + key = classify_asm(file) + if key is not None: + asm_outputs[key].append(file) + +payload = { + "crypto_sources": sorted(bcm.get("srcs", []) + crypto.get("srcs", [])), + "crypto_headers": sorted(crypto.get("hdrs", [])), + "crypto_internal_headers": sorted( + bcm.get("internal_hdrs", []) + + crypto.get("internal_hdrs", []) + ), + "fips_fragments": [], + "asm_outputs": {key: sorted(files) for key, files in asm_outputs.items() if files}, +} +json.dump(payload, sys.stdout, indent=2, sort_keys=True) +sys.stdout.write("\n") +PY +} + +prefix_src_tree_path() { + local path="$1" + if [[ "$path" == src/* ]]; then + echo "$path" + return + fi + if [[ "$path" == */* ]]; then + echo "src/$path" + else + echo "$path" + fi +} + +# write_sources_cmake +write_sources_cmake() { + local manifest="$1" + local dest="$ROOT/third_party/boringssl/sources.cmake" + + log_info "Writing sources.cmake..." + + cat > "$dest" <<'EOF' +# Copyright 2026 Google LLC +# +# 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. + +# **GENERATED FILE DO NOT MODIFY** +# +# This file is generated using: +# `tool/bump-boringssl-revision.sh` +EOF + + echo "" >> "$dest" + echo "set(crypto_sources" >> "$dest" + jq -r '.crypto_sources[]' "$manifest" | tr -d '\r' | while read -r file; do + echo " \${BORINGSSL_ROOT}$(prefix_src_tree_path "$file")" >> "$dest" + done + echo ")" >> "$dest" + + jq -r '.asm_outputs | keys[]' "$manifest" | tr -d '\r' | while read -r key; do + echo "" >> "$dest" + echo "set(crypto_sources_$key" >> "$dest" + jq -r --arg key "$key" '.asm_outputs[$key][]' "$manifest" | tr -d '\r' | while read -r file; do + echo " \${BORINGSSL_ROOT}$file" >> "$dest" + done + echo ")" >> "$dest" + done +} + +# copy_manifest_group +copy_manifest_group() { + local manifest="$1" + local jq_selector="$2" + local src_root="$3" + local dest_root="$4" + + jq -r "$jq_selector" "$manifest" | tr -d '\r' | while read -r file; do + if [[ -n "$file" ]]; then + local src="$src_root/$file" + local dst="$dest_root/$(prefix_src_tree_path "$file")" + mkdir -p "$(dirname "$dst")" + cp "$src" "$dst" + fi + done +} + +# copy_asm_outputs +copy_asm_outputs() { + local manifest="$1" + local src_root="$2" + local dest_root="$3" + + jq -r '.asm_outputs[]?[]' "$manifest" | tr -d '\r' | while read -r file; do + if [[ -n "$file" ]]; then + local src="$src_root/$file" + local dst="$dest_root/$file" + mkdir -p "$(dirname "$dst")" + cp "$src" "$dst" + fi + done +} + +# copy_sources +copy_sources() { + local manifest="$1" + local src_root="$2" + local dest_root="$ROOT/third_party/boringssl" + + log_info "Copying BoringSSL sources..." + copy_manifest_group "$manifest" '.crypto_headers[]' "$src_root" "$dest_root" + copy_manifest_group "$manifest" '.crypto_sources[]' "$src_root" "$dest_root" + copy_manifest_group "$manifest" '.crypto_internal_headers[]' "$src_root" "$dest_root" + copy_manifest_group "$manifest" '.fips_fragments[]' "$src_root" "$dest_root" + copy_asm_outputs "$manifest" "$src_root" "$dest_root" + + log_info "Copying root files..." + for file in README.md LICENSE INCORPORATING.md; do + if [[ -f "$src_root/$file" ]]; then + cp "$src_root/$file" "$dest_root/" + fi + done +} + +write_boringssl_readme() { + local readme_dst="$ROOT/third_party/boringssl/README.md" + + cat > "$readme_dst" <<'EOF' +# Incorporation of BoringSSL in `package:webcrypto` + +**GENERATED FOLDER DO NOT MODIFY** + +This folder contains sources from BoringSSL allowing `package:webcrypto` to +incorporate libcrypto from BoringSSL. Contents of this folder are generated +using `tool/bump-boringssl-revision.sh`. + +Files in this folder are subject to `LICENSE` from the BoringSSL project. + +Notice that this folder does NOT contain all source files from the BoringSSL +project. Only source files required to build `package:webcrypto` have been +retained. This is essential to minimize package size. For additional source +files and information about BoringSSL refer to the [BoringSSL repository][1]. + +[1]: https://boringssl.googlesource.com/boringssl/ +EOF +} + +# update_boringssl_sources +update_boringssl_sources() { + local revision="$1" + local temp_dir + temp_dir=$(mktemp -d) + local src_root="$temp_dir/boringssl" + local manifest="$temp_dir/build-files.json" + + log_info "Starting BoringSSL update to revision: $revision" + + cleanup_boringssl + git_clone_boringssl "$revision" "$src_root" + + log_info "Enumerating source files using upstream sources.json" + write_build_manifest "$src_root" "$manifest" + + local source_count + source_count=$(jq '.crypto_sources | length' "$manifest") + local internal_header_count + internal_header_count=$(jq '(.crypto_internal_headers | length) + (.fips_fragments | length)' "$manifest") + local asm_count + asm_count=$(jq '[.asm_outputs[] | length] | add // 0' "$manifest") + log_info "Found $source_count source files, $internal_header_count internal headers, and $asm_count assembly files" + + write_sources_cmake "$manifest" + copy_sources "$manifest" "$src_root" + write_boringssl_readme + + rm -rf "$temp_dir" + log_success "Updated vendored BoringSSL sources" +} + +main() { + local current_revision + current_revision=$(get_current_revision) + log_info "Current BoringSSL revision: $current_revision" + + if [[ -n "$TARGET_REVISION" ]]; then + log_info "Using specified revision: $TARGET_REVISION" + else + TARGET_REVISION=$(get_latest_revision) + log_info "Using latest revision: $TARGET_REVISION" + fi + + if [[ "$current_revision" == "$TARGET_REVISION" ]]; then + log_info "Already at revision $TARGET_REVISION; regenerating files to verify the checkout matches the recorded revision" + else + log_info "Update needed: $current_revision -> $TARGET_REVISION" + fi + + if [[ "$DRY_RUN" == true ]]; then + log_info "DRY RUN: Would update from $current_revision to $TARGET_REVISION" + return 0 + fi + + check_command git "git is not installed or not in PATH" + check_command dart "dart is required to regenerate bindings and run tests" + check_command jq "jq is required to parse generated BoringSSL source metadata" + PYTHON_BIN=$(resolve_python) + + section "Cleaning up build artifacts" + log_info "Running clean.sh..." + bash "$DIR/clean.sh" + + section "Updating BoringSSL sources" + update_boringssl_sources "$TARGET_REVISION" + + update_revision "$TARGET_REVISION" + + section "Getting Dart dependencies" + log_info "Running 'dart pub get --no-example'..." + cd "$ROOT" + dart pub get --no-example + + section "Generating symbols table" + log_info "Running generate_symbols_table.dart..." + dart "$DIR/generate_symbols_table.dart" + + section "Updating FFI bindings" + log_info "Running update-bindings.sh..." + bash "$DIR/update-bindings.sh" + + section "Running tests" + log_info "Running test.sh..." + bash "$DIR/test.sh" + + log_success "BoringSSL update completed successfully" + log_info "Updated from $current_revision to $TARGET_REVISION" +} + +main "$@" diff --git a/tool/clean.sh b/tool/clean.sh index d0b04a9b..47356965 100755 --- a/tool/clean.sh +++ b/tool/clean.sh @@ -17,18 +17,21 @@ DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" ROOT="$DIR/.." -# Remove all the generated files -# Not removing .cxx/ and .gradle/ can cause problems when jumping between -# flutter versions. +# Remove generated build artifacts for the root Dart package and the Flutter +# example app. The root package is no longer a Flutter project, so `flutter +# clean` only applies inside example/. -cd "$ROOT" -flutter clean +rm -rf "$ROOT/.dart_tool/" +rm -rf "$ROOT/build/" -cd "$ROOT/example/" -flutter clean - -cd "$ROOT" -rm -rf android/.cxx/ -rm -rf example/android/.gradle/ -rm -f example/.packages +if [ -d "$ROOT/example/" ]; then + ( + cd "$ROOT/example/" + flutter clean + ) + rm -rf "$ROOT/example/.dart_tool/" + rm -rf "$ROOT/example/build/" + rm -rf "$ROOT/example/android/.gradle/" + rm -f "$ROOT/example/.packages" +fi diff --git a/tool/test.sh b/tool/test.sh index e75c0ae9..d6192b39 100755 --- a/tool/test.sh +++ b/tool/test.sh @@ -17,26 +17,36 @@ set -e section() { echo ''; echo "### $1"; echo '';} +run_with_xvfb() { + if command -v xvfb-run >/dev/null 2>&1; then + xvfb-run "$@" + else + "$@" + fi +} + DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" cd "$DIR/.." -# We use 'flutter pub get' because example/ requires flutter! -section 'Running "flutter pub get"' -flutter pub get +section 'Running "dart pub get --no-example"' +dart pub get --no-example section 'dart test (vm,chrome,firefox)' -xvfb-run dart test -p vm,chrome,firefox +run_with_xvfb dart test -p vm,chrome,firefox cd "$DIR/../example" +section 'Running "flutter pub get" in example/' +flutter pub get + # List devices and run integration tests on each device (if available) # Skip "chrome" because it's not supported by integration test system. DEVICE_IDS=$(flutter devices --machine | grep '"sdk"') for DEVICE in linux android; do if echo "$DEVICE_IDS" | grep -i "$DEVICE" > /dev/null; then section "Running integration tests on $DEVICE" - xvfb-run flutter test integration_test/webcrypto_test.dart -d "$DEVICE" + run_with_xvfb flutter test integration_test/webcrypto_test.dart -d "$DEVICE" else section "Skipping integration tests on $DEVICE (missing device)" fi diff --git a/tool/update-bindings.sh b/tool/update-bindings.sh index cfdca997..9ff4da9e 100755 --- a/tool/update-bindings.sh +++ b/tool/update-bindings.sh @@ -20,7 +20,7 @@ DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" cd "$DIR/.." -flutter pub get +dart pub get --no-example -flutter pub run ffigen --config=lib/src/boringssl/bindings/ffigen.yaml -flutter pub run ffigen --config=lib/src/third_party/boringssl/ffigen.yaml +dart run ffigen --config=lib/src/boringssl/bindings/ffigen.yaml +dart run ffigen --config=lib/src/third_party/boringssl/ffigen.yaml diff --git a/tool/update-boringssl.py b/tool/update-boringssl.py deleted file mode 100755 index 54eb5dda..00000000 --- a/tool/update-boringssl.py +++ /dev/null @@ -1,297 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2020 Google LLC -# -# 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. - -# Roll BoringSSL into third_party/boringssl/ -# Remember to bump BORINGSSL_REVISION. - -import os -import os.path -import shutil -import subprocess -import sys -import tempfile - -TOOL_PATH = os.path.dirname(os.path.realpath(__file__)) -ROOT_PATH = os.path.dirname(TOOL_PATH) - -BORINGSSL_REPOSITORY = 'https://boringssl.googlesource.com/boringssl' -BORINGSSL_REVISION = 'a6d321b11fa80496b7c8ae6405468c212d4f5c87' - - -def cleanup(): - """ Remove boringssl sources and generated files """ - paths = [ - os.path.join(ROOT_PATH, 'third_party', 'boringssl'), - os.path.join(ROOT_PATH, 'darwin', 'third_party', 'boringssl') - ] - for p in paths: - if os.path.exists(p): - shutil.rmtree(p) - mkdirp(p) - - -def git_clone(target): - """ Clone BoringSSL into target/src """ - src = os.path.join(target, 'src') - mkdirp(src) - subprocess.check_call( - ['git', 'clone', BORINGSSL_REPOSITORY, src], - ) - subprocess.check_call( - ['git', 'checkout', '--detach', BORINGSSL_REVISION], - cwd=src, - ) - - -# Files from BoringSSL that should always be retained -FILES_TO_RETAIN = [ - 'src/README.md', - 'src/LICENSE', - 'src/INCORPORATING.md', -] - -BORINGSSL_FOLDER_README = """# Incorporation of BoringSSL in `package:webcrypto` - -**GENERATED FOLDER DO NOT MODIFY** - -This folder contains sources from BoringSSL allowing `package:webcrypto` to -incorporate libcrypto from BoringSSL. Contents of this folder is generated -using `tool/update-boringssl.py` which utilizes scripts and procedures from -`src/INCORPORATING.md` to faciliate embedding of libcrypto from BoringSSL. - -Files in this folder are subject to `LICENSE` from the BoringSSL project. - -Notice that this folder does NOT contain all source files from the BoringSSL -project. Only source files required to build `package:webcrypto` have been -retained. This is essential to minimize package size. For additional source -files and information about BoringSSL refer to the [BoringSSL repository][1]. - -[1]: https://boringssl.googlesource.com/boringssl/ -""" - -SOURCES_CMAKE_HEADER = """# Copyright 2020 Google LLC -# -# 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. - -# **GENERATED FILE DO NOT MODIFY** -# -# This file is generated using: -# `tool/update-boringssl.py` -""" - -FAKE_DARWIN_SOURCE_HEADER = """/* - * Copyright 2020 Google LLC - * - * 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. - */ - -// **GENERATED FILE DO NOT MODIFY** -// -// This file is generated using: -// `tool/update-boringssl.py` -""" - - -class BoringSSLGenerator(object): - """ - Generator for src/util/generate_build_files.py from BoringSSL. - - This simply stores the variables, so we easily access them in function. - """ - - def WriteFiles(self, file_sets, asm_outputs): - """ - WriteFiles will be called by generate_build_files.main(..) - - Parameters - ---------- - file_sets : dict - A dict mapping from targets to list of files. - asm_outputs : list - A list of nested tuples on the form: - ((os, arch), list_of_files) - for each operating system and architecture. - - All file paths are relative to root the BoringSSL repository. - """ - self.file_sets = file_sets - self.asm_outputs = asm_outputs - - -def writeFile(path_relative_root, contents): - with open(os.path.join(ROOT_PATH, path_relative_root), 'w') as f: - f.write(contents) - - -def writeSourcesCmake(g): - """ - Write third_party/boringssl/sources.cmake - """ - def define(variable, files): - """ Define variable in sources.cmake to hold files """ - s = '' - s += '\nset(' + variable + '\n' - s += '\n'.join(( - ' ${BORINGSSL_ROOT}' + f for f in sorted(files) - )) - s += '\n)\n' - return s - - # Define sources for libcrypto - sources_cmake = '' - sources_cmake += SOURCES_CMAKE_HEADER - sources_cmake += define('crypto_sources', g.file_sets['crypto']) - - # Define and sources various ASM files used by libcrypto - for ((osname, arch), asm_files) in g.asm_outputs: - name = 'crypto_sources_%s_%s' % (osname, arch) - sources_cmake += define(name, asm_files) - - # Write third_party/boringssl/sources.cmake - p = os.path.join('third_party', 'boringssl', 'sources.cmake') - writeFile(p, sources_cmake) - - -def copySourceFiles(g, boringssl_clone): - """ - Copy source files into third_party/boringssl/ - """ - files_to_copy = [] - # Copy libcrypto sources - files_to_copy += g.file_sets['crypto'] - # Copy public headers - files_to_copy += g.file_sets['crypto_headers'] - # Copy internal headers (otherwise, we can't build) - files_to_copy += g.file_sets['crypto_internal_headers'] - # Copy fips_fragments (otherwise, we can't build) - files_to_copy += g.file_sets['fips_fragments'] - # Copy various ASM files used by libcrypto - for ((osname, arch), asm_files) in g.asm_outputs: - files_to_copy += asm_files - # Copy static files - files_to_copy += FILES_TO_RETAIN - - for f in sorted(set(files_to_copy)): - src = os.path.join(boringssl_clone, f) - dst = os.path.join(ROOT_PATH, 'third_party', 'boringssl', f) - mkdirp(os.path.dirname(dst)) - shutil.copy(src, dst) - - -def writeFakeDarwinSource(g): - """ - Write fake-source files that each #include "../..." the original source - file for darwin/ - """ - for f in sorted(set(g.file_sets['crypto'])): - target = os.path.join(ROOT_PATH, 'darwin', 'third_party', 'boringssl', f) - original = os.path.join(ROOT_PATH, 'third_party', 'boringssl', f) - rel = os.path.relpath(original, os.path.dirname(target)) - mkdirp(os.path.dirname(target)) - contents = '' - contents += FAKE_DARWIN_SOURCE_HEADER - contents += '\n' - contents += '#include "'+rel+'"\n' - writeFile(os.path.join('darwin', 'third_party', 'boringssl', f), contents) - - -def generate(boringssl_clone): - # Change directory into boringssl_clone because generate_build_files.py - # expects to run from this location - os.chdir(boringssl_clone) - - # Import src/util/generate_build_files.py - sys.path.append(os.path.join(boringssl_clone, 'src', 'util')) - import generate_build_files - - g = BoringSSLGenerator() - generate_build_files.EMBED_TEST_DATA = False - generate_build_files.main([g]) - - # Write third_party/boringssl/sources.cmake - writeSourcesCmake(g) - - # Copy source files into third_party/boringssl/ - copySourceFiles(g, boringssl_clone) - - # Write fake-source files for darwin/ which use #include "../..." to include - # the original source file. This is necessary because webcrypto.podspec - # cannot reference sources not under the darwin/ folder. - # But the C-preprocessor can still include them :D - writeFakeDarwinSource(g) - - # Add a README.md to the third_party/boringssl/ folder - readmePath = os.path.join('third_party', 'boringssl', 'README.md') - writeFile(readmePath, BORINGSSL_FOLDER_README) - - # Copy LICENSE file for BoringSSL into third_party/boringssl/LICENSE - # because all files in this folder are copied or generated from BoringSSL. - LICENSE_src = os.path.join(boringssl_clone, 'src', 'LICENSE') - LICENSE_dst = os.path.join( - ROOT_PATH, 'third_party', 'boringssl', 'LICENSE') - shutil.copy(LICENSE_src, LICENSE_dst) - - -def mkdirp(path): - if not os.path.isdir(path): - os.makedirs(path) - - -def main(): - if shutil.which('go') is None: - print('Could not find "go" on $PATH') - return 1 - if shutil.which('git') is None: - print('Could not find "git" on $PATH') - return 1 - if shutil.which('perl') is None: - print('Could not find "perl" on $PATH') - return 1 - try: - print('Updating third_party/boringssl/') - tmp = tempfile.mkdtemp(prefix='update-boringssl-') - cleanup() - git_clone(tmp) - generate(tmp) - print('Updated to BoringSSL revision: ' + BORINGSSL_REVISION) - return 0 - finally: - shutil.rmtree(tmp) - return 1 - - -if __name__ == '__main__': - sys.exit(main())