├── CODEOWNERS ├── .semgrepignore ├── fuzz ├── corpus │ └── fuzz_parse_tile_path │ │ ├── seed1 │ │ ├── seed2 │ │ ├── seed6 │ │ ├── seed3 │ │ ├── seed5 │ │ ├── seed7 │ │ ├── seed8 │ │ └── seed4 ├── .gitignore ├── Cargo.toml └── fuzz_targets │ ├── fuzz_parse_checkpoint.rs │ └── fuzz_parse_tile_path.rs ├── .gitignore ├── crates ├── ct_worker │ ├── reset-dev.sh │ ├── .gitignore │ ├── config │ │ ├── Cargo.toml │ │ └── src │ │ │ └── lib.rs │ ├── scripts │ │ ├── create-root-kv.sh │ │ ├── add_cert_to_local_dev.py │ │ └── create-log.sh │ ├── src │ │ ├── batcher_do.rs │ │ ├── cleaner_do.rs │ │ ├── sequencer_do.rs │ │ └── lib.rs │ ├── config.cftest.json │ ├── Cargo.toml │ ├── .dev.vars │ ├── config.dev.json │ ├── config.raio.json │ ├── roots.cftest.pem │ ├── roots.dev.pem │ └── build.rs ├── mtc_worker │ ├── reset-dev.sh │ ├── .gitignore │ ├── config │ │ ├── Cargo.toml │ │ └── src │ │ │ └── lib.rs │ ├── config.dev.json │ ├── .dev.vars │ ├── config.bootstrap-mtca.json │ ├── src │ │ ├── batcher_do.rs │ │ └── cleaner_do.rs │ ├── LICENSE │ ├── test-dev.sh │ ├── Cargo.toml │ ├── README.md │ ├── build.rs │ ├── wrangler.jsonc │ ├── dev-bootstrap-roots.pem │ └── config.schema.json ├── generic_log_worker │ ├── .gitignore │ ├── README.md │ ├── src │ │ ├── util.rs │ │ └── obs │ │ │ ├── mod.rs │ │ │ └── logs.rs │ ├── Cargo.toml │ └── tests │ │ ├── add-chain.json │ │ └── add-pre-chain.json ├── tlog_tiles │ ├── tests │ │ └── http_get │ │ │ ├── get-sth │ │ │ ├── get-sth-consistency-first-3654490-second-961984011 │ │ │ └── get-proof-by-hash-tree-size-961984011-hash-nCs3mfIydh9x4CKyQ-2Bu2xtmpxmFggA5MLymHwNQSlLg-3D │ ├── README.md │ ├── Cargo.toml │ ├── LICENSE │ └── src │ │ ├── lib.rs │ │ └── cosignature_v1.rs ├── length_prefixed │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── static_ct_api │ ├── README.md │ ├── tests │ │ ├── google-gts-root-r4.pem │ │ ├── mismatching-sig-alg.pem │ │ ├── leaf-cert.pem │ │ ├── fake-root-ca-cert.pem │ │ ├── fake-intermediate-with-policy-constraints-cert.pem │ │ ├── fake-intermediate-with-name-constraints-cert.pem │ │ ├── fake-intermediate-with-invalid-name-constraints-cert.pem │ │ ├── test-cert.pem │ │ ├── ca-cert.pem │ │ ├── subleaf.chain │ │ ├── subleaf.misordered.chain │ │ ├── precert-valid.pem │ │ ├── cloudflare.pem │ │ ├── fake-intermediate-cert.pem │ │ ├── fake-intermediate-with-mismatching-sig-alg.pem │ │ ├── fake-ca-cert.pem │ │ ├── real-precert-intermediate.pem │ │ └── real-precert-with-eku.pem │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── x509_util │ └── Cargo.toml ├── mtc_api │ ├── Cargo.toml │ └── src │ │ └── relative_oid.rs └── signed_note │ ├── Cargo.toml │ ├── README.md │ ├── .vscode │ └── launch.json │ ├── benches │ └── benchmark_verify.rs │ └── LICENSE ├── renovate.json ├── .github └── workflows │ └── rust.yml ├── README.md └── Cargo.toml /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @cloudflare/crypto 2 | -------------------------------------------------------------------------------- /.semgrepignore: -------------------------------------------------------------------------------- 1 | .dev.vars 2 | test.mjs 3 | -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_parse_tile_path/seed1: -------------------------------------------------------------------------------- 1 | tile/4/0/001 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | node_modules 3 | .wrangler 4 | .vscode/* -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_parse_tile_path/seed2: -------------------------------------------------------------------------------- 1 | tile/4/0/001.p/5 2 | -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_parse_tile_path/seed6: -------------------------------------------------------------------------------- 1 | tile/3/5/123/456/078 2 | -------------------------------------------------------------------------------- /crates/ct_worker/reset-dev.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | rm -r .wrangler/state -------------------------------------------------------------------------------- /crates/mtc_worker/reset-dev.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | rm -r .wrangler/state -------------------------------------------------------------------------------- /fuzz/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | corpus 3 | artifacts 4 | coverage 5 | -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_parse_tile_path/seed3: -------------------------------------------------------------------------------- 1 | tile/3/5/x123/x456/078 2 | -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_parse_tile_path/seed5: -------------------------------------------------------------------------------- 1 | tile/1/0/x003/x057/500 2 | -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_parse_tile_path/seed7: -------------------------------------------------------------------------------- 1 | tile/3/-1/123/456/078 2 | -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_parse_tile_path/seed8: -------------------------------------------------------------------------------- 1 | tile/1/data/x003/x057/500 2 | -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_parse_tile_path/seed4: -------------------------------------------------------------------------------- 1 | tile/3/5/x123/x456/078.p/2 2 | -------------------------------------------------------------------------------- /crates/ct_worker/.gitignore: -------------------------------------------------------------------------------- 1 | package-lock.json 2 | package.json 3 | dist/ 4 | -------------------------------------------------------------------------------- /crates/mtc_worker/.gitignore: -------------------------------------------------------------------------------- 1 | package-lock.json 2 | package.json 3 | dist/ 4 | -------------------------------------------------------------------------------- /crates/generic_log_worker/.gitignore: -------------------------------------------------------------------------------- 1 | package-lock.json 2 | package.json 3 | dist/ 4 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:recommended", 5 | "group:all", 6 | "schedule:monthly" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /crates/tlog_tiles/tests/http_get/get-sth: -------------------------------------------------------------------------------- 1 | {"tree_size":961984011,"timestamp":1726494206646,"sha256_root_hash":"AWxD3p811N3m4WTinWRXPvBZHfT9IR9g/R4C7SKLPiU=","tree_head_signature":"BAMASDBGAiEAhgLgYXQHUc0ct9yTdrlh/3wGJoUt19FVi4Snx6OXepICIQDqxvQHjeARt1eTqRKFxt3c8ZRpObexaNJEyth4j9tJDg=="} -------------------------------------------------------------------------------- /crates/length_prefixed/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "length_prefixed" 3 | version.workspace = true 4 | authors.workspace = true 5 | edition.workspace = true 6 | license.workspace = true 7 | readme.workspace = true 8 | homepage.workspace = true 9 | repository.workspace = true 10 | description.workspace = true 11 | 12 | [dependencies] 13 | byteorder.workspace = true 14 | -------------------------------------------------------------------------------- /crates/mtc_worker/config/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mtc_worker_config" 3 | publish = false 4 | version.workspace = true 5 | authors.workspace = true 6 | edition.workspace = true 7 | license.workspace = true 8 | readme.workspace = true 9 | homepage.workspace = true 10 | repository.workspace = true 11 | description = "Configuration for mtc_worker" 12 | 13 | [dependencies] 14 | serde.workspace = true -------------------------------------------------------------------------------- /crates/ct_worker/config/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ct_worker_config" 3 | publish = false 4 | version.workspace = true 5 | authors.workspace = true 6 | edition.workspace = true 7 | license.workspace = true 8 | readme.workspace = true 9 | homepage.workspace = true 10 | repository.workspace = true 11 | description = "Configuration for ct_worker" 12 | 13 | [dependencies] 14 | chrono.workspace = true 15 | serde.workspace = true -------------------------------------------------------------------------------- /crates/tlog_tiles/README.md: -------------------------------------------------------------------------------- 1 | # Tlog Tiles 2 | 3 | A Rust implementation of the [C2SP tlog-tiles](https://c2sp.org/tlog-tiles) and [C2SP checkpoint](https://c2sp.org/tlog-checkpoint) specifications. 4 | 5 | ## Test 6 | 7 | cargo test 8 | 9 | ## Acknowledgements 10 | 11 | The project ports code from [tlog](https://golang.org/x/mod/sumdb/tlog). 12 | 13 | ## License 14 | 15 | The project is licensed under the [BSD-3-Clause License](./LICENSE). 16 | -------------------------------------------------------------------------------- /crates/static_ct_api/README.md: -------------------------------------------------------------------------------- 1 | # Static CT API 2 | 3 | A Rust implementation of the [Static Certificate Transparency API](https://c2sp.org/static-ct-api). 4 | 5 | ## Test 6 | 7 | cargo test 8 | 9 | ## Acknowledgements 10 | 11 | This project ports code from [sunlight](https://github.com/FiloSottile/sunlight) and [certificate-transparency-go](https://github.com/google/certificate-transparency-go). 12 | 13 | ## License 14 | 15 | The project is licensed under the [BSD-3-Clause License](./LICENSE). -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v4 19 | - name: Clippy 20 | run: cargo clippy -- -D warnings 21 | - name: Build 22 | run: cargo build --verbose 23 | - name: Run tests 24 | run: cargo test --verbose 25 | -------------------------------------------------------------------------------- /crates/x509_util/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "x509_util" 3 | version.workspace = true 4 | authors.workspace = true 5 | edition.workspace = true 6 | license.workspace = true 7 | readme.workspace = true 8 | homepage.workspace = true 9 | repository.workspace = true 10 | description.workspace = true 11 | 12 | [dependencies] 13 | der.workspace = true 14 | sha2.workspace = true 15 | thiserror.workspace = true 16 | x509-cert.workspace = true 17 | x509-verify.workspace = true 18 | 19 | [dev-dependencies] 20 | chrono.workspace = true 21 | -------------------------------------------------------------------------------- /fuzz/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tlog-fuzz" 3 | version = "0.0.0" 4 | publish = false 5 | edition = "2021" 6 | 7 | [package.metadata] 8 | cargo-fuzz = true 9 | 10 | [dependencies] 11 | libfuzzer-sys.workspace = true 12 | tlog_tiles.workspace = true 13 | 14 | [[bin]] 15 | name = "fuzz_parse_tile_path" 16 | path = "fuzz_targets/fuzz_parse_tile_path.rs" 17 | test = false 18 | doc = false 19 | bench = false 20 | 21 | [[bin]] 22 | name = "fuzz_parse_checkpoint" 23 | path = "fuzz_targets/fuzz_parse_checkpoint.rs" 24 | test = false 25 | doc = false 26 | bench = false 27 | -------------------------------------------------------------------------------- /crates/mtc_api/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mtc_api" 3 | version.workspace = true 4 | authors.workspace = true 5 | edition.workspace = true 6 | license.workspace = true 7 | readme.workspace = true 8 | homepage.workspace = true 9 | repository.workspace = true 10 | description.workspace = true 11 | 12 | [dependencies] 13 | byteorder.workspace = true 14 | der.workspace = true 15 | ed25519-dalek.workspace = true 16 | length_prefixed.workspace = true 17 | rand.workspace = true 18 | serde.workspace = true 19 | serde_with.workspace = true 20 | sha2.workspace = true 21 | signed_note.workspace = true 22 | thiserror.workspace = true 23 | tlog_tiles.workspace = true 24 | x509-cert.workspace = true 25 | x509_util.workspace = true 26 | -------------------------------------------------------------------------------- /crates/mtc_worker/config.dev.json: -------------------------------------------------------------------------------- 1 | { 2 | "logging_level": "info", 3 | "logs": { 4 | "dev1": { 5 | "description": "MTCA Dev1", 6 | "log_id": "44363.48.1", 7 | "cosigner_id": "44363.48.2", 8 | "submission_url": "http://localhost:8787/logs/dev1/", 9 | "location_hint": "enam" 10 | }, 11 | "dev2": { 12 | "description": "MTCA Dev2", 13 | "log_id": "44363.48.3", 14 | "cosigner_id": "44363.48.4", 15 | "submission_url": "http://localhost:8787/logs/dev2/", 16 | "location_hint": "enam", 17 | "max_certificate_lifetime_secs": 100, 18 | "landmark_interval_secs": 10 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /crates/static_ct_api/tests/google-gts-root-r4.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICCjCCAZGgAwIBAgIQbkepyIuUtui7OyrYorLBmTAKBggqhkjOPQQDAzBHMQsw 3 | CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU 4 | MBIGA1UEAxMLR1RTIFJvb3QgUjQwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAw 5 | MDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZp 6 | Y2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjQwdjAQBgcqhkjOPQIBBgUrgQQA 7 | IgNiAATzdHOnaItgrkO4NcWBMHtLSZ37wWHO5t5GvWvVYRg1rkDdc/eJkTBa6zzu 8 | hXyiQHY7qca4R9gq55KRanPpsXI5nymfopjTX15YhmUPoYRlBtHci8nHc8iMai/l 9 | xKvRHYqjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud 10 | DgQWBBSATNbrdP9JNqPV2Py1PsVq8JQdjDAKBggqhkjOPQQDAwNnADBkAjBqUFJ0 11 | CMRw3J5QdCHojXohw0+WbhXRIjVhLfoIN+4Zba3bssx9BzT1YBkstTTZbyACMANx 12 | sbqjYAuG7ZoIapVon+Kz4ZNkfF6Tpt95LY2F45TPI11xzPKwTdb+mciUqXWi4w== 13 | -----END CERTIFICATE----- 14 | -------------------------------------------------------------------------------- /crates/mtc_worker/.dev.vars: -------------------------------------------------------------------------------- 1 | SIGNING_KEY_dev1="-----BEGIN PRIVATE KEY-----\nMC4CAQAwBQYDK2VwBCIEIBCLa48rfJHyYIhRoP83GJzwEtEJfdVKeI6YvrZLhiUG\n-----END PRIVATE KEY-----\n" 2 | WITNESS_KEY_dev1="-----BEGIN PRIVATE KEY-----\nMC4CAQAwBQYDK2VwBCIEIHwiErJNKCNGZL+Osj+O8MqSMiwPP4kdcC4iTpojV9Od\n-----END PRIVATE KEY-----\n" 3 | SIGNING_KEY_dev2="-----BEGIN PRIVATE KEY-----\nMC4CAQAwBQYDK2VwBCIEINaOgrQNAgPl9+sEQqCxEnvlblHlC3JbrM04/uryf3we\n-----END PRIVATE KEY-----\n" 4 | WITNESS_KEY_dev2="-----BEGIN PRIVATE KEY-----\nMC4CAQAwBQYDK2VwBCIEINKYH1WadDgPJEXYzLx0OWzNoi4hRcpUnYpoWrTc7BDO\n-----END PRIVATE KEY-----\n" 5 | SIGNING_KEY_shard1="-----BEGIN PRIVATE KEY-----\nMC4CAQAwBQYDK2VwBCIEINaOgrQNAgPl9+sEQqCxEnvlblHlC3JbrM04/uryf3we\n-----END PRIVATE KEY-----\n" 6 | WITNESS_KEY_shard1="-----BEGIN PRIVATE KEY-----\nMC4CAQAwBQYDK2VwBCIEINKYH1WadDgPJEXYzLx0OWzNoi4hRcpUnYpoWrTc7BDO\n-----END PRIVATE KEY-----\n" 7 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/fuzz_parse_checkpoint.rs: -------------------------------------------------------------------------------- 1 | // Ported from "mod" (https://pkg.go.dev/golang.org/x/mod) 2 | // Copyright 2009 The Go Authors 3 | // Licensed under the BSD-3-Clause license found in the LICENSE file or at https://opensource.org/licenses/BSD-3-Clause 4 | // 5 | // This ports code from the original Go project "mod" and adapts it to Rust idioms. 6 | // 7 | // Modifications and Rust implementation Copyright (c) 2025 Cloudflare, Inc. 8 | // Licensed under the BSD-3-Clause license found in the LICENSE file or at https://opensource.org/licenses/BSD-3-Clause 9 | 10 | //! This file contains code ported from the original project [tlog](https://pkg.go.dev/golang.org/x/mod/sumdb/tlog). 11 | //! 12 | //! References: 13 | //! - [note_test.go](https://cs.opensource.google/go/x/mod/+/refs/tags/v0.21.0:sumdb/tlog/note_test.go) 14 | 15 | #![no_main] 16 | 17 | use libfuzzer_sys::fuzz_target; 18 | use tlog_tiles::checkpoint::CheckpointText; 19 | 20 | fuzz_target!(|data: &[u8]| { 21 | let _ = CheckpointText::from_bytes(data); 22 | }); 23 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/fuzz_parse_tile_path.rs: -------------------------------------------------------------------------------- 1 | // Ported from "mod" (https://pkg.go.dev/golang.org/x/mod) 2 | // Copyright 2009 The Go Authors 3 | // Licensed under the BSD-3-Clause license found in the LICENSE file or at https://opensource.org/licenses/BSD-3-Clause 4 | // 5 | // This ports code from the original Go project "mod" and adapts it to Rust idioms. 6 | // 7 | // Modifications and Rust implementation Copyright (c) 2025 Cloudflare, Inc. 8 | // Licensed under the BSD-3-Clause license found in the LICENSE file or at https://opensource.org/licenses/BSD-3-Clause 9 | 10 | //! This file contains code ported from the original project [tlog](https://pkg.go.dev/golang.org/x/mod/sumdb/tlog). 11 | //! 12 | //! References: 13 | //! - [tlog_test.go](https://cs.opensource.google/go/x/mod/+/refs/tags/v0.21.0:sumdb/tlog/tlog_test.go) 14 | 15 | #![no_main] 16 | 17 | use libfuzzer_sys::fuzz_target; 18 | use tlog_tiles::{tile::Tile, PathElem}; 19 | 20 | fuzz_target!(|data: &str| { 21 | let _ = Tile::from_path(data, true, PathElem::Data); 22 | }); 23 | -------------------------------------------------------------------------------- /crates/ct_worker/scripts/create-root-kv.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e -o pipefail 4 | cd "$(dirname "$0")/.." || exit # this script assumes it's runnnig inside the ct_worker dir 5 | 6 | # Helper script to create resources for a log shard. 7 | 8 | if [ -z "${ENV}" ] || [ -z "${CLOUDFLARE_ACCOUNT_ID}" ]; then 9 | echo "ENV and CLOUDFLARE_ACCOUNT_ID must all be set" 10 | exit 1 11 | fi 12 | 13 | WRANGLER_CONF=${WRANGLER_CONF:-wrangler.jsonc} 14 | 15 | while true; do 16 | read -rp "Do you want to proceed with ENV=${ENV}, CLOUDFLARE_ACCOUNT_ID=${CLOUDFLARE_ACCOUNT_ID}? (y/N) " yn 17 | case $yn in 18 | [yY] ) echo "Proceeding..."; break;; 19 | [nN] ) echo "Exiting..."; exit;; 20 | * ) echo "Invalid input. Please enter 'y' or 'N'.";; 21 | esac 22 | done 23 | 24 | # Create KV namespace if it does not already exist 25 | npx wrangler \ 26 | -e="${ENV}" \ 27 | -c "${WRANGLER_CONF}" \ 28 | kv namespace create \ 29 | static-ct-ccadb-roots \ 30 | --update-config \ 31 | --binding ccadb_roots 32 | -------------------------------------------------------------------------------- /crates/signed_note/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "signed_note" 3 | readme = "README.md" 4 | version.workspace = true 5 | authors.workspace = true 6 | edition.workspace = true 7 | license.workspace = true 8 | homepage.workspace = true 9 | repository.workspace = true 10 | description = "An implementation of c2sp.org/signed-note" 11 | categories = ["cryptography"] 12 | keywords = ["ct", "certificate", "transparency", "crypto", "pki"] 13 | 14 | [package.metadata.release] 15 | release = false 16 | 17 | # https://github.com/rustwasm/wasm-pack/issues/1247 18 | [package.metadata.wasm-pack.profile.release] 19 | wasm-opt = false 20 | 21 | [lib] 22 | crate-type = ["rlib"] 23 | 24 | [dependencies] 25 | base64.workspace = true 26 | ed25519-dalek = { workspace = true, features=["alloc", "rand_core"] } 27 | rand_core.workspace = true 28 | sha2.workspace = true 29 | signature.workspace = true 30 | thiserror.workspace = true 31 | 32 | [dev-dependencies] 33 | criterion.workspace = true 34 | rand.workspace = true 35 | 36 | [[bench]] 37 | name = "benchmark_verify" 38 | harness = false 39 | -------------------------------------------------------------------------------- /crates/tlog_tiles/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tlog_tiles" 3 | readme = "README.md" 4 | version.workspace = true 5 | authors.workspace = true 6 | edition.workspace = true 7 | license.workspace = true 8 | homepage.workspace = true 9 | repository.workspace = true 10 | description = "An implementation of c2sp.org/tlog-tiles and c2sp.org/tlog-checkpoint" 11 | categories = ["cryptography"] 12 | keywords = ["ct", "certificate", "transparency", "crypto", "pki"] 13 | 14 | [package.metadata.release] 15 | release = false 16 | 17 | # https://github.com/rustwasm/wasm-pack/issues/1247 18 | [package.metadata.wasm-pack.profile.release] 19 | wasm-opt = false 20 | 21 | [lib] 22 | crate-type = ["rlib"] 23 | 24 | [dev-dependencies] 25 | serde_json.workspace = true 26 | 27 | [dependencies] 28 | base64.workspace = true 29 | byteorder.workspace = true 30 | ed25519-dalek.workspace = true 31 | length_prefixed.workspace = true 32 | rand.workspace = true 33 | serde.workspace = true 34 | sha2.workspace = true 35 | signed_note.workspace = true 36 | thiserror.workspace = true 37 | url.workspace = true 38 | x509_util.workspace = true 39 | -------------------------------------------------------------------------------- /crates/mtc_worker/config.bootstrap-mtca.json: -------------------------------------------------------------------------------- 1 | { 2 | "logging_level": "info", 3 | "logs": { 4 | "shard1": { 5 | "description": "Cloudflare bootstrap MTCA shard 1", 6 | "log_id": "13335.1", 7 | "cosigner_id": "13335.1", 8 | "submission_url": "https://bootstrap-mtca.cloudflareresearch.com/logs/shard1/", 9 | "monitoring_url": "https://bootstrap-mtca-shard1.cloudflareresearch.com" 10 | }, 11 | "shard2": { 12 | "description": "Cloudflare bootstrap MTCA shard 2", 13 | "log_id": "44363.48.5", 14 | "cosigner_id": "44363.48.6", 15 | "submission_url": "https://bootstrap-mtca.cloudflareresearch.com/logs/shard2/", 16 | "monitoring_url": "https://bootstrap-mtca-shard2.cloudflareresearch.com" 17 | }, 18 | "shard3": { 19 | "description": "Cloudflare bootstrap MTCA shard 3", 20 | "log_id": "44363.48.8", 21 | "cosigner_id": "44363.48.9", 22 | "submission_url": "https://bootstrap-mtca.cloudflareresearch.com/logs/shard3/", 23 | "monitoring_url": "https://bootstrap-mtca-shard3.cloudflareresearch.com" 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /crates/static_ct_api/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "static_ct_api" 3 | readme = "README.md" 4 | version.workspace = true 5 | authors.workspace = true 6 | edition.workspace = true 7 | license.workspace = true 8 | homepage.workspace = true 9 | repository.workspace = true 10 | description = "An implementation of c2sp.org/static-ct-api" 11 | categories = ["cryptography"] 12 | keywords = ["ct", "certificate", "transparency", "crypto", "pki"] 13 | 14 | [package.metadata.release] 15 | release = false 16 | 17 | # https://github.com/rustwasm/wasm-pack/issues/1247 18 | [package.metadata.wasm-pack.profile.release] 19 | wasm-opt = false 20 | 21 | [lib] 22 | crate-type = ["rlib"] 23 | 24 | [dev-dependencies] 25 | ed25519-dalek.workspace = true 26 | 27 | [dependencies] 28 | base64.workspace = true 29 | byteorder.workspace = true 30 | chrono.workspace = true 31 | der.workspace = true 32 | length_prefixed.workspace = true 33 | p256.workspace = true 34 | serde.workspace = true 35 | serde_with.workspace = true 36 | sha2.workspace = true 37 | signature.workspace = true 38 | signed_note.workspace = true 39 | thiserror.workspace = true 40 | tlog_tiles.workspace = true 41 | x509-cert.workspace = true 42 | x509-verify.workspace = true 43 | x509_util.workspace = true 44 | -------------------------------------------------------------------------------- /crates/ct_worker/src/batcher_do.rs: -------------------------------------------------------------------------------- 1 | use crate::CONFIG; 2 | use generic_log_worker::{get_durable_object_name, BatcherConfig, GenericBatcher, BATCHER_BINDING}; 3 | #[allow(clippy::wildcard_imports)] 4 | use worker::*; 5 | 6 | #[durable_object(fetch)] 7 | struct Batcher(GenericBatcher); 8 | 9 | impl DurableObject for Batcher { 10 | fn new(state: State, env: Env) -> Self { 11 | let name = get_durable_object_name( 12 | &env, 13 | &state, 14 | BATCHER_BINDING, 15 | &mut CONFIG 16 | .logs 17 | .iter() 18 | .map(|(name, params)| (name.as_str(), params.num_batchers)), 19 | ); 20 | let params = &CONFIG.logs[name]; 21 | let config = BatcherConfig { 22 | name: name.to_string(), 23 | max_batch_entries: params.max_batch_entries, 24 | batch_timeout_millis: params.batch_timeout_millis, 25 | enable_dedup: params.enable_dedup, 26 | location_hint: params.location_hint.clone(), 27 | }; 28 | Batcher(GenericBatcher::new(state, env, config)) 29 | } 30 | 31 | async fn fetch(&self, req: Request) -> Result { 32 | self.0.fetch(req).await 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /crates/generic_log_worker/README.md: -------------------------------------------------------------------------------- 1 | # Generic Log Worker 2 | 3 | A Rust implementation of a tiled Merkle Tree log for deployment on [Cloudflare Workers](https://workers.cloudflare.com/). This is compatible with different specific protocols/signing formats, eg [Static CT](https://c2sp.org/static-ct-api) and [tlog](https://github.com/C2SP/C2SP/blob/main/tlog-checkpoint.md). 4 | 5 | ## Architecture 6 | 7 | The 'brain' of each log is a single-threaded 'Sequencer' Durable Object, and much of the system is architected around offloading as much work as possible to other components of the system (like 'Batcher' Durable Objects) to improve overall throughput. Read the [blog post](https://blog.cloudflare.com/azul-certificate-transparency-log) for more details, specifically about the Static CT specialization of this crate. 8 | 9 | > :warning: **Warning** The software in this crate is written specifically for the [Cloudflare Durable Objects](https://developers.cloudflare.com/durable-objects/) execution model, with single-threaded execution and [input/output gates](https://blog.cloudflare.com/durable-objects-easy-fast-correct-choose-three) to avoid race conditions. Running it elsewhere could lead to concurrency bugs. 10 | 11 | ![System Diagram](doc/img/static-ct.drawio.svg) 12 | -------------------------------------------------------------------------------- /crates/mtc_worker/src/batcher_do.rs: -------------------------------------------------------------------------------- 1 | use crate::CONFIG; 2 | use generic_log_worker::{get_durable_object_name, BatcherConfig, GenericBatcher, BATCHER_BINDING}; 3 | #[allow(clippy::wildcard_imports)] 4 | use worker::*; 5 | 6 | #[durable_object(fetch)] 7 | struct Batcher(GenericBatcher); 8 | 9 | impl DurableObject for Batcher { 10 | fn new(state: State, env: Env) -> Self { 11 | let name = get_durable_object_name( 12 | &env, 13 | &state, 14 | BATCHER_BINDING, 15 | &mut CONFIG 16 | .logs 17 | .iter() 18 | .map(|(name, params)| (name.as_str(), params.num_batchers)), 19 | ); 20 | let params = &CONFIG.logs[name]; 21 | let config = BatcherConfig { 22 | name: name.to_string(), 23 | max_batch_entries: params.max_batch_entries, 24 | batch_timeout_millis: params.batch_timeout_millis, 25 | enable_dedup: false, // deduplication is not currently supported 26 | location_hint: params.location_hint.clone(), 27 | }; 28 | Batcher(GenericBatcher::new(state, env, config)) 29 | } 30 | 31 | async fn fetch(&self, req: Request) -> Result { 32 | self.0.fetch(req).await 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /crates/generic_log_worker/src/util.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2025 Cloudflare, Inc. 2 | // Licensed under the BSD-3-Clause license found in the LICENSE file or at https://opensource.org/licenses/BSD-3-Clause 3 | 4 | //! Utility functions. 5 | 6 | #[cfg(test)] 7 | use parking_lot::ReentrantMutex; 8 | #[cfg(test)] 9 | use std::sync::atomic::{AtomicBool, AtomicU64, Ordering}; 10 | 11 | /// Returns the current Unix timestamp at millisecond precision. 12 | #[cfg(not(test))] 13 | pub fn now_millis() -> u64 { 14 | worker::Date::now().as_millis() 15 | } 16 | #[cfg(test)] 17 | pub fn now_millis() -> u64 { 18 | let _lock = TIME_MUX.lock(); 19 | if FREEZE_TIME.load(Ordering::Relaxed) { 20 | GLOBAL_TIME.load(Ordering::Relaxed) 21 | } else { 22 | GLOBAL_TIME.fetch_add(1, Ordering::Relaxed) 23 | } 24 | } 25 | 26 | #[cfg(test)] 27 | static GLOBAL_TIME: AtomicU64 = AtomicU64::new(0); 28 | 29 | #[cfg(test)] 30 | static FREEZE_TIME: AtomicBool = AtomicBool::new(false); 31 | 32 | #[cfg(test)] 33 | pub(crate) static TIME_MUX: ReentrantMutex<()> = ReentrantMutex::new(()); 34 | 35 | #[cfg(test)] 36 | pub(crate) fn set_freeze_time(b: bool) { 37 | FREEZE_TIME.store(b, Ordering::Relaxed); 38 | } 39 | 40 | #[cfg(test)] 41 | pub(crate) fn set_global_time(time: u64) { 42 | GLOBAL_TIME.store(time, Ordering::Relaxed); 43 | } 44 | -------------------------------------------------------------------------------- /crates/generic_log_worker/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "generic_log_worker" 3 | readme = "README.md" 4 | publish = false 5 | version.workspace = true 6 | authors.workspace = true 7 | edition.workspace = true 8 | license.workspace = true 9 | homepage.workspace = true 10 | repository.workspace = true 11 | description = "An implementation of a tiled Merkle Tree-backed log on Cloudflare Workers" 12 | categories = ["cryptography"] 13 | keywords = ["transparency", "crypto"] 14 | 15 | [dev-dependencies] 16 | rand = { workspace = true, features = ["small_rng"] } 17 | itertools.workspace = true 18 | parking_lot.workspace = true 19 | futures-executor.workspace = true 20 | static_ct_api.workspace = true 21 | 22 | [dependencies] 23 | anyhow.workspace = true 24 | base64.workspace = true 25 | bitcode.workspace = true 26 | byteorder.workspace = true 27 | console_error_panic_hook.workspace = true 28 | console_log.workspace = true 29 | ed25519-dalek.workspace = true 30 | futures-util.workspace = true 31 | hex.workspace = true 32 | log = { workspace = true, features = ["kv"] } 33 | p256.workspace = true 34 | prometheus.workspace = true 35 | rand.workspace = true 36 | serde-wasm-bindgen.workspace = true 37 | serde.workspace = true 38 | serde_bytes.workspace = true 39 | serde_json.workspace = true 40 | sha2.workspace = true 41 | signed_note.workspace = true 42 | thiserror.workspace = true 43 | tlog_tiles.workspace = true 44 | tokio.workspace = true 45 | worker.workspace = true 46 | -------------------------------------------------------------------------------- /crates/tlog_tiles/tests/http_get/get-sth-consistency-first-3654490-second-961984011: -------------------------------------------------------------------------------- 1 | {"consistency":["80Sfbj6UQEdVSZCSIgh9NMPj3vCBzcc1A48B2crJSWg=","FZcrgAHUk6nNPviJZq8kEK6jCG1d18pJOFE45ZDc1TQ=","Q67reWXmPBo1UfmwBap4Xi9PMPfFdUSSw3WGlRZdG7k=","eJ7CyhR4PxzBxDIsWgrVYW1eFUb9ndgf46tFmiqVrxA=","QrDwp062Grm5WY8KdrhFsBuuNjGzRox2Jb8PmVL5OYw=","XM4YMjMEdLH/XJam/ZWyaP14k3cmsOtnPsOWQ54lW1s=","SGiBh+jOTGccXU48ZzgQ88lRcBmtB4wz4oSDndv2QTg=","WAS6txqNLYKGL1GdBdpqmV42TTfn6cJsuDsqLXuTCNI=","QFmRS0rirUA7ZN3lxFjDncV+E1OCrn3NUB4RCp+5fzc=","Hdbp7It2XBNZmNDBpCN/KPd7vfVqxgzwOosqVIaEA9E=","mxvHL0ZiblBdD7j7pIa1jlfB0gSqfJ6oX0UGo7L0VsU=","q5hjyvoW6TL/PThgcitpDcSTrnKX2p1+aqscqH2euRA=","jTOWDqt3IHj2NVFeezyBz2ofpru+ztEywqKUAfFclKI=","GIRn0w3HqJdvU9j1WVD//eR6lk2AaTVjyzjsJNP8U88=","x52GJvxG81fnQBrgOTbll1A5tz95/LXK0Ag1NhB0r1I=","6/0OqrcTgc3o5dD5PymmoC96lB9yLPFuTHVvLy8fSGk=","Gf+iKLfcXO5l4JEFmsZvSdG5q1uEeWPfYLrds6pmjz0=","DGuaco+McyM3AfRSF6qX7RwSetwe2ieYJmxw5e9rVx0=","2tQJ9V32hnYp+fdpP6TcjmgJmLeNVOMKM0Ml1kk4cNU=","3PapK+IkyJr8I9yAErAmzuZezzLMzsg+PfeRCgNW8NQ=","s6xiYeejb+wtqHA+URJZ2I/SCPyIW8coP6aB00FJbMY=","z9lSpmdTcr5M5ZzKlc4EPh3SrSypKFdk6JUF/UccaZU=","GXIElfmnRuWfbA4z7ubV/UwlpSRUbUeec7oL5HDDnh0=","pR0SH/NYP6s9+8azWwaeUXA4Ze/3WCIgMfh5YW6wvcE=","f3GuAKYVrF8RBg3s15jvA/YvV9oAliWtzP/imwPhQVA=","WHn8zDxlVgkV+lSlmzaOdfSBrTHoRr5nT3d76q0KKWE=","f2uV2ZxVgULzLPKuZcowRY3ZY7fRlX59iDBjV1R8kNQ=","NzQVjJsvQwbi7WStMr9oFkh9VbHfK7LB6o7WwAqwNTs=","C0LttBeYHZHgL+032JIcJC3CTBIyza/hsbmJUgJfmDI=","30HzZbwT4DdqHQi4f7+WRG6M4YwVGaKgGXicDvSFT6k="]} -------------------------------------------------------------------------------- /crates/tlog_tiles/tests/http_get/get-proof-by-hash-tree-size-961984011-hash-nCs3mfIydh9x4CKyQ-2Bu2xtmpxmFggA5MLymHwNQSlLg-3D: -------------------------------------------------------------------------------- 1 | {"leaf_index":10000,"audit_path":["T46AmJsuOM/gFiGlUp+IJJebp+1Z5qratFNW7U0JyOQ=","9Jj1Feodn42uRYEfffkV4HF/EIPFQ5Zo9IwkxbQ2uhk=","D53/1oCNebF4J59HHgioovKZxrmrKcVuCVhy3Izg8Ck=","wSFDgGSrsXRPA+1c8WHoAFCsWeUv8NDyP/Q/SgsyVyY=","516/RcwsgdwdzHn18v1dJHqBkgFUbNhmCIWOGjsMJaE=","naTFY2ajLFgrgs5XUl+zJYZnnlJksLGsA1VYwpLfqbY=","mKBRJo6qao+0Kdh/rhGkZUbHUAxMhrtrclrTpIbNjIg=","Co4ueOFE7lpifLio94irxxsv1wFMMaK3tPu2ZQkHlro=","yRgy800K6JMYCC05sWfqF2kvCg2riT/pVM+nEa6V5+0=","n2O38XJZC8/Wpbwi9HhgVizlUyeUHVZvYyQB1607MRc=","go3tAjuNfNSCj87y2Rg1xOroDknGN1eaf3cgvzObf7Q=","8D0Mj3/J7ySSnFb0afIHYXv6rlOhECnsebY6XKi1IR8=","6rcez46ZU8M5vPiWVMAUfJfAOV0pPP/mch/lYzZhj/M=","2rtIeXrPvSdUyHc5maIgSAVB2BzaKaupWxSAiVayDE4=","nniW/IMFQPN5xw8UsHFn7CRStYbm1gLu7NNa6kkRqJQ=","mRPPi7sQS22MduIEyvcNzCNFqIt6kLwYPkNKrVtIcu8=","JvTivzESNS9wHI8aph5a7uieVmtS/p9PciMZ5D6GyOo=","UTyQd+Ym0wY4zG+qK806bBORwi1CSVvFQ/KknnqoZj8=","hXODrqN+ff+U6zPqRQ+ZQKs6im1fPcxIerDSfNwflhE=","II6GE+WY/DrpZzfHztLwkR46o37zVkNLCyPYlf68zA0=","d609kWWcNBllbA8Jb/ad4XAVVGnScLRmtP8FYyr+Dck=","rikBsP0G0gioXRpBFED2CzQdSLrGPvRd0bEcbEZw1rI=","GXIElfmnRuWfbA4z7ubV/UwlpSRUbUeec7oL5HDDnh0=","pR0SH/NYP6s9+8azWwaeUXA4Ze/3WCIgMfh5YW6wvcE=","f3GuAKYVrF8RBg3s15jvA/YvV9oAliWtzP/imwPhQVA=","WHn8zDxlVgkV+lSlmzaOdfSBrTHoRr5nT3d76q0KKWE=","f2uV2ZxVgULzLPKuZcowRY3ZY7fRlX59iDBjV1R8kNQ=","NzQVjJsvQwbi7WStMr9oFkh9VbHfK7LB6o7WwAqwNTs=","C0LttBeYHZHgL+032JIcJC3CTBIyza/hsbmJUgJfmDI=","30HzZbwT4DdqHQi4f7+WRG6M4YwVGaKgGXicDvSFT6k="]} -------------------------------------------------------------------------------- /crates/ct_worker/config.cftest.json: -------------------------------------------------------------------------------- 1 | { 2 | "logging_level": "info", 3 | "logs": { 4 | "cftest2025h1a": { 5 | "description": "Cloudflare Research 'cftest2025h1a' log", 6 | "log_type": "test", 7 | "submission_url": "https://static-ct.cloudflareresearch.com/logs/cftest2025h1a/", 8 | "monitoring_url": "https://static-ct-public-cftest2025h1a.cloudflareresearch.com/", 9 | "temporal_interval": { 10 | "start_inclusive": "2025-01-01T00:00:00Z", 11 | "end_exclusive": "2025-07-01T00:00:00Z" 12 | }, 13 | "max_sequence_skips": 1, 14 | "sequence_interval_millis": 750, 15 | "sequence_skip_threshold_millis": 250, 16 | "location_hint": "enam" 17 | }, 18 | "cftest2025h2a": { 19 | "description": "Cloudflare Research 'cftest2025h2a' log", 20 | "log_type": "test", 21 | "submission_url": "https://static-ct.cloudflareresearch.com/logs/cftest2025h2a/", 22 | "monitoring_url": "https://static-ct-public-cftest2025h2a.cloudflareresearch.com/", 23 | "temporal_interval": { 24 | "start_inclusive": "2025-07-01T00:00:00Z", 25 | "end_exclusive": "2026-01-01T00:00:00Z" 26 | }, 27 | "max_sequence_skips": 1, 28 | "sequence_interval_millis": 750, 29 | "sequence_skip_threshold_millis": 250, 30 | "location_hint": "enam" 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /crates/mtc_worker/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2025 Cloudflare, Inc. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | - Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | - Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | - Neither the name of the copyright holder nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /crates/mtc_worker/src/cleaner_do.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use crate::{load_checkpoint_cosigner, load_origin, CONFIG}; 4 | use generic_log_worker::{get_durable_object_name, CleanerConfig, GenericCleaner, CLEANER_BINDING}; 5 | use mtc_api::BootstrapMtcPendingLogEntry; 6 | use signed_note::VerifierList; 7 | use tlog_tiles::{CheckpointSigner, PendingLogEntry}; 8 | #[allow(clippy::wildcard_imports)] 9 | use worker::*; 10 | 11 | #[durable_object(alarm)] 12 | struct Cleaner(GenericCleaner); 13 | 14 | impl DurableObject for Cleaner { 15 | fn new(state: State, env: Env) -> Self { 16 | let name = get_durable_object_name( 17 | &env, 18 | &state, 19 | CLEANER_BINDING, 20 | &mut CONFIG.logs.keys().map(|name| (name.as_str(), 0)), 21 | ); 22 | let params = &CONFIG.logs[name]; 23 | 24 | let config = CleanerConfig { 25 | name: name.to_string(), 26 | origin: load_origin(name), 27 | data_path: BootstrapMtcPendingLogEntry::DATA_TILE_PATH, 28 | aux_path: BootstrapMtcPendingLogEntry::AUX_TILE_PATH, 29 | verifiers: VerifierList::new(vec![load_checkpoint_cosigner(&env, name).verifier()]), 30 | clean_interval: Duration::from_secs(params.clean_interval_secs), 31 | }; 32 | 33 | Cleaner(GenericCleaner::new(state, &env, config)) 34 | } 35 | 36 | async fn fetch(&self, req: Request) -> Result { 37 | self.0.fetch(req).await 38 | } 39 | 40 | async fn alarm(&self) -> Result { 41 | self.0.alarm().await 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /crates/signed_note/README.md: -------------------------------------------------------------------------------- 1 | # Signed Note 2 | 3 | A Rust implementation of the [C2SP signed-note](https://c2sp.org/signed-note) specification. 4 | 5 | ## Example 6 | 7 | Here is a well-formed signed note: 8 | 9 | If you think cryptography is the answer to your problem, 10 | then you don't know what your problem is 11 | — PeterNeumann x08go/ZJkuBS9UG/SffcvIAQxVBtiFupLLr8pAcElZInNIuGUgYN1FFYC2pZSNXgKvqfqdngotpRZb6KE6RyyBwJnAM= 12 | 13 | It can be constructed and displayed using: 14 | 15 | use note::{Note, StandardSigner}; 16 | let skey = "PRIVATE+KEY+PeterNeumann+c74f20a3+AYEKFALVFGyNhPJEMzD1QIDr+Y7hfZx09iUvxdXHKDFz"; 17 | let text = "If you think cryptography is the answer to your problem,\n\ 18 | then you don't know what your problem is.\n"; 19 | let signer = StandardSigner::new(skey).unwrap(); 20 | let mut n = Note::new(text, &[]).unwrap(); 21 | n.add_sigs(&[&signer]).unwrap(); 22 | let want = "If you think cryptography is the answer to your problem,\n\ 23 | then you don't know what your problem is.\n\ 24 | \n\ 25 | — PeterNeumann x08go/ZJkuBS9UG/SffcvIAQxVBtiFupLLr8pAcElZInNIuGUgYN1FFYC2pZSNXgKvqfqdngotpRZb6KE6RyyBwJnAM=\n"; 26 | assert_eq!(&n.to_string(), want); 27 | 28 | See documentation for more complete examples. 29 | 30 | cargo doc --open 31 | 32 | ## Test 33 | 34 | cargo test 35 | 36 | ## Benchmark 37 | 38 | cargo bench 39 | 40 | ## Acknowledgements 41 | 42 | The project ports code from [note](https://golang.org/x/mod/sumdb/note). 43 | 44 | ## License 45 | 46 | The project is licensed under the [BSD-3-Clause License](./LICENSE). -------------------------------------------------------------------------------- /crates/ct_worker/src/cleaner_do.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use crate::{load_checkpoint_signers, load_origin, CONFIG}; 4 | use generic_log_worker::{get_durable_object_name, CleanerConfig, GenericCleaner, CLEANER_BINDING}; 5 | use signed_note::VerifierList; 6 | use static_ct_api::StaticCTPendingLogEntry; 7 | use tlog_tiles::PendingLogEntry; 8 | #[allow(clippy::wildcard_imports)] 9 | use worker::*; 10 | 11 | #[durable_object(alarm)] 12 | struct Cleaner(GenericCleaner); 13 | 14 | impl DurableObject for Cleaner { 15 | fn new(state: State, env: Env) -> Self { 16 | let name = get_durable_object_name( 17 | &env, 18 | &state, 19 | CLEANER_BINDING, 20 | &mut CONFIG.logs.keys().map(|name| (name.as_str(), 0)), 21 | ); 22 | let params = &CONFIG.logs[name]; 23 | 24 | let config = CleanerConfig { 25 | name: name.to_string(), 26 | origin: load_origin(name), 27 | data_path: StaticCTPendingLogEntry::DATA_TILE_PATH, 28 | aux_path: StaticCTPendingLogEntry::AUX_TILE_PATH, 29 | verifiers: VerifierList::new( 30 | load_checkpoint_signers(&env, name) 31 | .iter() 32 | .map(|s| s.verifier()) 33 | .collect(), 34 | ), 35 | clean_interval: Duration::from_secs(params.clean_interval_secs), 36 | }; 37 | 38 | Cleaner(GenericCleaner::new(state, &env, config)) 39 | } 40 | 41 | async fn fetch(&self, req: Request) -> Result { 42 | self.0.fetch(req).await 43 | } 44 | 45 | async fn alarm(&self) -> Result { 46 | self.0.alarm().await 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /crates/static_ct_api/tests/mismatching-sig-alg.pem: -------------------------------------------------------------------------------- 1 | Certificate: 2 | Data: 3 | Version: 3 (0x2) 4 | Serial Number: 0 (0x0) 5 | Signature Algorithm: ecdsa-with-SHA256 6 | Issuer: 7 | Validity 8 | Not Before: Nov 10 23:00:00 2009 GMT 9 | Not After : Aug 10 23:00:00 2019 GMT 10 | Subject: 11 | Subject Public Key Info: 12 | Public Key Algorithm: id-ecPublicKey 13 | Public-Key: (256 bit) 14 | pub: 15 | 04:27:0c:60:cf:ca:31:8a:91:db:8f:67:c7:95:04: 16 | 6d:df:18:31:99:2c:97:8a:71:8d:7b:c2:b9:79:3d: 17 | 02:86:21:a9:1b:5a:0c:55:03:cc:cc:18:2c:1b:96: 18 | 52:81:dc:70:62:7f:c9:f4:67:cd:d3:f4:43:31:b4: 19 | a5:75:0b:19:3b 20 | ASN1 OID: prime256v1 21 | NIST CURVE: P-256 22 | X509v3 extensions: 23 | X509v3 Subject Alternative Name: critical 24 | DNS:asd 25 | Signature Algorithm: ecdsa-with-SHA384 26 | Signature Value: 27 | 30:44:02:20:3b:b7:b6:5d:a4:13:a1:f2:d8:42:5e:36:b9:e5: 28 | 41:7a:90:1c:ea:45:3f:11:6e:b7:b0:7d:b0:f7:bc:22:8b:35: 29 | 02:20:25:7a:88:77:4c:8a:fd:9d:e8:93:33:93:6d:f7:c3:80: 30 | ba:a1:1c:51:3e:04:b6:7f:c1:ff:a2:3a:c4:87:ac:f9 31 | -----BEGIN CERTIFICATE----- 32 | MIIBAjCBqqADAgECAgEAMAoGCCqGSM49BAMCMAAwHhcNMDkxMTEwMjMwMDAwWhcN 33 | MTkwODEwMjMwMDAwWjAAMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEJwxgz8ox 34 | ipHbj2fHlQRt3xgxmSyXinGNe8K5eT0ChiGpG1oMVQPMzBgsG5ZSgdxwYn/J9GfN 35 | 0/RDMbSldQsZO6MVMBMwEQYDVR0RAQH/BAcwBYIDYXNkMAoGCCqGSM49BAMDA0cA 36 | MEQCIDu3tl2kE6Hy2EJeNrnlQXqQHOpFPxFut7B9sPe8Ios1AiAleoh3TIr9neiT 37 | M5Nt98OAuqEcUT4Etn/B/6I6xIes+Q== 38 | -----END CERTIFICATE----- 39 | -------------------------------------------------------------------------------- /crates/generic_log_worker/src/obs/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod logs; 2 | pub mod metrics; 3 | 4 | #[derive(Clone)] 5 | pub struct Wshim { 6 | token: String, 7 | socket: worker::Fetcher, 8 | } 9 | 10 | pub trait WshimData { 11 | fn endpoint() -> &'static str; 12 | fn to_body(&self) -> Vec; 13 | } 14 | 15 | impl Wshim { 16 | pub fn from_env(env: &worker::Env) -> worker::Result { 17 | Ok(Self { 18 | token: env.var("WSHIM_TOKEN")?.to_string(), 19 | socket: env.get_binding("WSHIM_SOCKET")?, 20 | }) 21 | } 22 | 23 | pub async fn flush(&self, data: E) { 24 | let fetch_result = self 25 | .socket 26 | .fetch( 27 | format!("https://workers-logging.cfdata.org/{}", E::endpoint()), 28 | Some(worker::RequestInit { 29 | method: worker::Method::Post, 30 | headers: worker::Headers::from_iter([( 31 | "Authorization".to_owned(), 32 | format!("Bearer: {}", self.token), 33 | )]), 34 | body: Some(data.to_body().into()), 35 | ..Default::default() 36 | }), 37 | ) 38 | .await; 39 | let response = match fetch_result { 40 | Ok(response) => response, 41 | Err(e) => { 42 | worker::console_error!("failed to post to wshim /{}: {e:?}", E::endpoint()); 43 | return; 44 | } 45 | }; 46 | if response.status_code() != 200 { 47 | worker::console_error!( 48 | "post to wshim /{} failed with status code: {}", 49 | E::endpoint(), 50 | response.status_code() 51 | ); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Azul 2 | 3 | Azul (short for [azulejos](https://en.wikipedia.org/wiki/Azulejo), the colorful Portuguese and Spanish ceramic tiles) contains an implementation of a tiled certificate transparency log compatible with the [Static CT API](https://c2sp.org/static-ct-api), built for deployment on [Cloudflare Workers](https://workers.cloudflare.com). It also contains several crates implementing various C2SP specifications. Read the [blog post](https://blog.cloudflare.com/azul-certificate-transparency-log) for more details. 4 | 5 | The crates in the repository are organized as follows: 6 | 7 | - **[ct_worker](crates/ct_worker)**: A Static CT API log implementation for deployment on Cloudflare Workers. 8 | - **[static_ct_api](crates/static_ct_api)** ([crates.io](https://crates.io/crates/static_ct_api)): An implementation of the [C2SP static-ct-api](https://c2sp.org/static-ct-api) specification. 9 | - **[signed_note](crates/signed_note)** ([crates.io](https://crates.io/crates/signed_note)): An implementation of the [C2SP signed-note](https://c2sp.org/signed-note) specification. 10 | - **[tlog_tiles](crates/tlog_tiles)** ([crates.io](https://crates.io/crates/tlog_tiles)): An implementation of the [C2SP tlog-tiles](https://c2sp.org/tlog-tiles) and [C2SP checkpoint](https://c2sp.org/tlog-checkpoint) specifications. 11 | 12 | ## Deploy 13 | 14 | See instructions in the [ct_worker](crates/ct_worker/README.md) crate for deployment instructions. 15 | 16 | ## Build 17 | 18 | cargo build 19 | 20 | ## Test 21 | 22 | cargo test 23 | 24 | ## Benchmark 25 | 26 | cargo bench 27 | 28 | ## Fuzz 29 | 30 | Follow setup instructions from (requires nightly compiler). 31 | 32 | cargo fuzz run fuzz_parse_tile_path 33 | cargo fuzz run fuzz_parse_tree 34 | cargo fuzz run fuzz_parse_record 35 | -------------------------------------------------------------------------------- /crates/static_ct_api/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2025 Cloudflare, Inc. 2 | // Licensed under the BSD-3-Clause license found in the LICENSE file or at https://opensource.org/licenses/BSD-3-Clause 3 | 4 | pub mod rfc6962; 5 | pub mod static_ct; 6 | 7 | pub use rfc6962::*; 8 | pub use static_ct::*; 9 | 10 | #[derive(thiserror::Error, Debug)] 11 | pub enum StaticCTError { 12 | #[error(transparent)] 13 | Tlog(#[from] tlog_tiles::TlogError), 14 | #[error(transparent)] 15 | Signature(#[from] signature::Error), 16 | #[error(transparent)] 17 | Note(#[from] signed_note::NoteError), 18 | #[error(transparent)] 19 | IO(#[from] std::io::Error), 20 | #[error(transparent)] 21 | Der(#[from] der::Error), 22 | #[error(transparent)] 23 | X509(#[from] x509_verify::spki::Error), 24 | #[error(transparent)] 25 | Validation(#[from] x509_util::ValidationError), 26 | #[error("unexpected extension")] 27 | UnexpectedExtension, 28 | #[error("malformed")] 29 | Malformed, 30 | #[error("missing leaf_index extension")] 31 | MissingLeafIndex, 32 | #[error("unknown type")] 33 | UnknownType, 34 | #[error("trailing data")] 35 | TrailingData, 36 | #[error("invalid certificate chain per CT")] 37 | InvalidChain, 38 | #[error("invalid leaf certificate per CT")] 39 | InvalidLeaf, 40 | #[error("CT poison extension is not critical or invalid")] 41 | InvalidCTPoison, 42 | #[error("missing precertificate issuer")] 43 | MissingPrecertIssuer, 44 | #[error("missing precertificate signing certificate issuer")] 45 | MissingPrecertSigningCertificateIssuer, 46 | #[error( 47 | "{}certificate submitted to add-{}chain", if *.is_precert { "pre-" } else { "final " }, if *.is_precert { "" } else { "pre-" } 48 | )] 49 | EndpointMismatch { is_precert: bool }, 50 | } 51 | -------------------------------------------------------------------------------- /crates/signed_note/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "lldb", 9 | "request": "launch", 10 | "name": "Build", 11 | "cargo": { 12 | "args": [ 13 | "build", 14 | "--manifest-path", 15 | "${fileDirname}/../Cargo.toml" 16 | ] 17 | } 18 | }, 19 | { 20 | "type": "lldb", 21 | "request": "launch", 22 | "name": "Build Docs", 23 | "cargo": { 24 | "args": [ 25 | "doc", 26 | //"--document-private-items", 27 | "--manifest-path", 28 | "${fileDirname}/../Cargo.toml" 29 | ] 30 | } 31 | }, 32 | { 33 | "type": "lldb", 34 | "request": "launch", 35 | "name": "Test", 36 | "cargo": { 37 | "args": [ 38 | "test", 39 | "--no-run", 40 | "--manifest-path", 41 | "${fileDirname}/../Cargo.toml" 42 | ] 43 | }, 44 | }, 45 | { 46 | "type": "lldb", 47 | "request": "launch", 48 | "name": "Benchmark", 49 | "cargo": { 50 | "args": [ 51 | "bench", 52 | "--manifest-path", 53 | "${fileDirname}/../Cargo.toml" 54 | ] 55 | } 56 | }, 57 | { 58 | "type": "lldb", 59 | "request": "launch", 60 | "name": "Test Matching Selected Text", 61 | "cargo": { 62 | "args": [ 63 | "test", 64 | "--no-run", 65 | "--manifest-path", 66 | "${fileDirname}/../Cargo.toml" 67 | ] 68 | }, 69 | "args": [ 70 | "${selectedText}" 71 | ], 72 | }, 73 | ] 74 | } -------------------------------------------------------------------------------- /crates/mtc_worker/test-dev.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | bootstrap_cert_hostname="cloudflareresearch.com" 6 | landmark_interval_secs=`jq '.logs.dev2.landmark_interval_secs' config.dev.json` 7 | submission_url=`jq -r '.logs.dev2.submission_url' config.dev.json` 8 | 9 | # Get a bootstrap certificate chain. 10 | bootstrap_cert_chain=`mktemp` 11 | echo | openssl s_client \ 12 | -connect ${bootstrap_cert_hostname}:443 \ 13 | -servername ${bootstrap_cert_hostname} \ 14 | -showcerts 2>/dev/null |\ 15 | sed -n '/-----BEGIN CERTIFICATE-----/,/-----END CERTIFICATE-----/p' \ 16 | > ${bootstrap_cert_chain} 17 | 18 | spki_der=`openssl x509 -in ${bootstrap_cert_chain} -pubkey -noout |\ 19 | openssl pkey -pubin -inform pem -outform der | base64` 20 | 21 | add_entry_req=`cat ${bootstrap_cert_chain} |\ 22 | while (set -o pipefail; 23 | openssl x509 -outform DER 2>/dev/null |\ 24 | base64); do :; done |\ 25 | sed '/^$/d' | sed 's/.*/"&"/' | jq -sc '{"chain":.}'` 26 | 27 | # Add entry for the bootstrap certificate. 28 | add_entry_resp=`curl -f --no-progress-meter -X POST \ 29 | -H "Content-Type: application/json" \ 30 | -d ${add_entry_req} \ 31 | "${submission_url}add-entry"` 32 | 33 | leaf_index=`echo ${add_entry_resp} | jq '.leaf_index'` 34 | echo "Leaf index: ${leaf_index}" 35 | 36 | # Wait for the next landmark to be minted. 37 | echo "Waiting ${landmark_interval_secs}s for the next landmark" 38 | sleep ${landmark_interval_secs} 39 | 40 | get_cert_req="{\"leaf_index\":${leaf_index},\"spki_der\":\"${spki_der}\"}" 41 | 42 | # Fetch the completed MTC. 43 | get_cert_resp=`curl -f --no-progress-meter -X POST \ 44 | -H "Content-Type: application/json" \ 45 | -d ${get_cert_req} \ 46 | "${submission_url}get-certificate"` 47 | 48 | landmark_id=`echo ${get_cert_resp} | jq '.landmark_id'` 49 | echo "Landmark id: ${landmark_id}" 50 | 51 | echo ${get_cert_resp} | jq -r '.data' | base64 -d |\ 52 | openssl x509 -inform DER -outform PEM 53 | -------------------------------------------------------------------------------- /crates/ct_worker/scripts/add_cert_to_local_dev.py: -------------------------------------------------------------------------------- 1 | import base64 2 | from datetime import datetime 3 | import json 4 | import urllib.request 5 | import socket 6 | import ssl 7 | import sys 8 | 9 | HOST = "www.google.com" 10 | 11 | # Need >=3.13 to use conn.get_unverified_chain() 12 | if sys.version_info < (3, 13, 0): 13 | cur_version = f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}" 14 | print("Error: This script requires Python 3.13 or newer. ") 15 | print(f"Current version is {cur_version}") 16 | exit(1) 17 | 18 | # Make a connection to HOST 19 | context = ssl.create_default_context() 20 | conn = context.wrap_socket(socket.socket(socket.AF_INET), server_hostname=HOST) 21 | conn.connect((HOST, 443)) 22 | 23 | # Get the leaf cert. That's the not_after we use to decide which shard to submit to 24 | parsed_cert = conn.getpeercert() 25 | # Get the cert chain. This is what we'll submit 26 | der_cert_chain = conn.get_unverified_chain() 27 | b64_cert_chain = [str(base64.b64encode(c), encoding="utf-8") for c in der_cert_chain] 28 | 29 | # Figure out the year and half-year that this not_after belongs to. Eg dev2026h2a is the second half 30 | # of 2026 31 | not_after = datetime.strptime(parsed_cert["notAfter"], "%b %d %H:%M:%S %Y %Z") 32 | not_after_year = not_after.year 33 | not_after_half = 1 if not_after.month < 7 else 2 34 | log_name = f"dev{not_after_year}h{not_after_half}a" 35 | 36 | # Make a POST request to localhost add-chain 37 | payload = {"chain": b64_cert_chain} 38 | url = f"http://localhost:8787/logs/{log_name}/ct/v1/add-chain" 39 | request = urllib.request.Request( 40 | url, 41 | data=bytes(json.dumps(payload), encoding="utf-8"), 42 | headers={'Content-Type': 'application/json'}, 43 | method='POST' 44 | ) 45 | try: 46 | urllib.request.urlopen(request, timeout=5) 47 | except Exception as e: 48 | print("Error: ", e) 49 | sys.exit(1) 50 | 51 | print(f"Successfully submitted to {url}") 52 | -------------------------------------------------------------------------------- /crates/length_prefixed/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2025 Cloudflare, Inc. 2 | // Licensed under the BSD-3-Clause license found in the LICENSE file or at https://opensource.org/licenses/BSD-3-Clause 3 | 4 | use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; 5 | use std::io::{Read, Write}; 6 | use std::marker::Sized; 7 | 8 | pub trait ReadLengthPrefixedBytesExt: Read { 9 | /// Read big-endian length-prefixed bytes from the reader. 10 | /// 11 | /// # Errors 12 | /// 13 | /// Returns the same errors as 14 | /// [`Read::read_exact`](https://doc.rust-lang.org/std/io/trait.Read.html#method.read_exact). 15 | /// 16 | /// # Panics 17 | /// 18 | /// `read_uint` requires that `1 <= nbytes <= 8`, and will panic otherwise. 19 | #[inline] 20 | fn read_length_prefixed(&mut self, nbytes: usize) -> std::io::Result> { 21 | let length = self.read_uint::(nbytes)?; 22 | let mut buffer = vec![0; usize::try_from(length).unwrap()]; 23 | self.read_exact(&mut buffer)?; 24 | Ok(buffer) 25 | } 26 | } 27 | 28 | /// All types that implement `Read` get methods defined in 29 | /// `ReadLengthPrefixedBytesExt` for free. 30 | impl ReadLengthPrefixedBytesExt for R {} 31 | 32 | pub trait WriteLengthPrefixedBytesExt: Write { 33 | /// Write big-endian length-prefixed bytes to the writer. 34 | /// 35 | /// # Errors 36 | /// 37 | /// Returns the same errors as 38 | /// [`Write::write_all`](https://doc.rust-lang.org/std/io/trait.Write.html#method.write_all). 39 | #[inline] 40 | fn write_length_prefixed(&mut self, data: &[u8], nbytes: usize) -> std::io::Result<()> { 41 | self.write_uint::(data.len() as u64, nbytes)?; 42 | self.write_all(data) 43 | } 44 | } 45 | 46 | /// All types that implement `Write` get methods defined in 47 | /// `WriteLengthPrefixedBytesExt` for free. 48 | impl WriteLengthPrefixedBytesExt for W {} 49 | -------------------------------------------------------------------------------- /crates/ct_worker/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ct_worker" 3 | publish = false 4 | version.workspace = true 5 | authors.workspace = true 6 | edition.workspace = true 7 | license.workspace = true 8 | homepage.workspace = true 9 | repository.workspace = true 10 | description = "An implementation of c2sp.org/static-ct-api on Cloudflare Workers" 11 | categories = ["cryptography"] 12 | keywords = ["ct", "certificate", "transparency", "crypto", "pki"] 13 | 14 | [package.metadata.release] 15 | release = false 16 | 17 | # https://github.com/rustwasm/wasm-pack/issues/1351 18 | [package.metadata.wasm-pack.profile.dev.wasm-bindgen] 19 | dwarf-debug-info = true 20 | 21 | [lib] 22 | crate-type = ["cdylib"] 23 | 24 | [build-dependencies] 25 | chrono.workspace = true 26 | config = { path = "./config", package = "ct_worker_config" } 27 | generic_log_worker.workspace = true 28 | jsonschema.workspace = true 29 | serde_json.workspace = true 30 | serde.workspace = true 31 | url.workspace = true 32 | x509-cert.workspace = true 33 | 34 | [dev-dependencies] 35 | rand = { workspace = true, features = ["small_rng"] } 36 | itertools.workspace = true 37 | parking_lot.workspace = true 38 | futures-executor.workspace = true 39 | 40 | [dependencies] 41 | base64.workspace = true 42 | config = { path = "./config", package = "ct_worker_config" } 43 | generic_log_worker.workspace = true 44 | ed25519-dalek.workspace = true 45 | getrandom.workspace = true 46 | log.workspace = true 47 | p256.workspace = true 48 | serde.workspace = true 49 | serde_json.workspace = true 50 | serde_with.workspace = true 51 | static_ct_api.workspace = true 52 | signed_note.workspace = true 53 | tlog_tiles.workspace = true 54 | tokio.workspace = true 55 | worker.workspace = true 56 | x509-cert.workspace = true 57 | x509_util.workspace = true 58 | chrono.workspace = true 59 | base64ct.workspace = true 60 | csv.workspace = true 61 | 62 | [lints.rust] 63 | unexpected_cfgs = { level = "warn", check-cfg = [ 64 | 'cfg(wasm_bindgen_unstable_test_coverage)', 65 | ] } 66 | 67 | [package.metadata.cargo-machete] 68 | ignored = ["getrandom"] 69 | -------------------------------------------------------------------------------- /crates/mtc_worker/config/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2025 Cloudflare, Inc. 2 | // Licensed under the BSD-3-Clause license found in the LICENSE file or at https://opensource.org/licenses/BSD-3-Clause 3 | 4 | // CT log configuration, in a separate crate to allow build.rs to use it. 5 | use serde::Deserialize; 6 | use std::collections::HashMap; 7 | 8 | #[derive(Deserialize, Debug)] 9 | pub struct AppConfig { 10 | pub logging_level: Option, 11 | pub logs: HashMap, 12 | } 13 | 14 | #[derive(Deserialize, Debug)] 15 | pub struct LogParams { 16 | pub description: Option, 17 | pub log_id: String, 18 | pub cosigner_id: String, 19 | #[serde(default = "default_usize::<604_800>")] 20 | pub max_certificate_lifetime_secs: usize, 21 | #[serde(default = "default_usize::<3600>")] 22 | pub landmark_interval_secs: usize, 23 | #[serde(default)] 24 | pub monitoring_url: String, 25 | pub submission_url: String, 26 | pub location_hint: Option, 27 | #[serde(default = "default_u64::<1000>")] 28 | pub sequence_interval_millis: u64, 29 | #[serde(default = "default_usize::<0>")] 30 | pub max_sequence_skips: usize, 31 | pub sequence_skip_threshold_millis: Option, 32 | #[serde(default = "default_u8::<8>")] 33 | pub num_batchers: u8, 34 | #[serde(default = "default_u64::<1000>")] 35 | pub batch_timeout_millis: u64, 36 | #[serde(default = "default_usize::<100>")] 37 | pub max_batch_entries: usize, 38 | #[serde(default = "default_u64::<60>")] 39 | pub clean_interval_secs: u64, 40 | } 41 | 42 | impl LogParams { 43 | /// Return the maximum number of landmarks that cover unexpired certificates at any given time. 44 | pub fn max_landmarks(&self) -> usize { 45 | self.max_certificate_lifetime_secs 46 | .div_ceil(self.landmark_interval_secs) 47 | + 1 48 | } 49 | } 50 | 51 | fn default_u8() -> u8 { 52 | V 53 | } 54 | fn default_u64() -> u64 { 55 | V 56 | } 57 | fn default_usize() -> usize { 58 | V 59 | } 60 | -------------------------------------------------------------------------------- /crates/ct_worker/config/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2025 Cloudflare, Inc. 2 | // Licensed under the BSD-3-Clause license found in the LICENSE file or at https://opensource.org/licenses/BSD-3-Clause 3 | 4 | // CT log configuration, in a separate crate to allow build.rs to use it. 5 | use chrono::{DateTime, Utc}; 6 | use serde::{Deserialize, Serialize}; 7 | use std::collections::HashMap; 8 | 9 | #[derive(Serialize, Deserialize, Debug)] 10 | pub struct TemporalInterval { 11 | pub start_inclusive: DateTime, 12 | pub end_exclusive: DateTime, 13 | } 14 | 15 | #[derive(Deserialize, Debug)] 16 | pub struct AppConfig { 17 | pub logging_level: Option, 18 | pub logs: HashMap, 19 | } 20 | 21 | #[derive(Deserialize, Debug)] 22 | pub struct LogParams { 23 | pub description: Option, 24 | pub log_type: Option, 25 | #[serde(default)] 26 | pub monitoring_url: String, 27 | pub submission_url: String, 28 | pub temporal_interval: TemporalInterval, 29 | pub location_hint: Option, 30 | #[serde(default = "default_u64::<1000>")] 31 | pub sequence_interval_millis: u64, 32 | #[serde(default = "default_usize::<0>")] 33 | pub max_sequence_skips: usize, 34 | pub sequence_skip_threshold_millis: Option, 35 | #[serde(default = "default_u8::<8>")] 36 | pub num_batchers: u8, 37 | #[serde(default = "default_u64::<100>")] 38 | pub batch_timeout_millis: u64, 39 | #[serde(default = "default_usize::<256>")] 40 | pub max_batch_entries: usize, 41 | #[serde(default = "default_bool::")] 42 | pub enable_dedup: bool, 43 | #[serde(default = "default_bool::")] 44 | pub enable_ccadb_roots: bool, 45 | #[serde(default = "default_u64::<60>")] 46 | pub clean_interval_secs: u64, 47 | #[serde(default = "default_bool::")] 48 | pub read_only: bool, 49 | } 50 | 51 | fn default_bool() -> bool { 52 | V 53 | } 54 | fn default_u8() -> u8 { 55 | V 56 | } 57 | fn default_u64() -> u64 { 58 | V 59 | } 60 | fn default_usize() -> usize { 61 | V 62 | } 63 | -------------------------------------------------------------------------------- /crates/ct_worker/src/sequencer_do.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2025 Cloudflare, Inc. 2 | // Licensed under the BSD-3-Clause license found in the LICENSE file or at https://opensource.org/licenses/BSD-3-Clause 3 | 4 | //! Sequencer is the 'brain' of the CT log, responsible for sequencing entries and maintaining log state. 5 | 6 | use std::time::Duration; 7 | 8 | use crate::{load_checkpoint_signers, load_origin, CONFIG}; 9 | use generic_log_worker::{ 10 | empty_checkpoint_callback, get_durable_object_name, GenericSequencer, SequencerConfig, 11 | SEQUENCER_BINDING, 12 | }; 13 | use static_ct_api::StaticCTLogEntry; 14 | #[allow(clippy::wildcard_imports)] 15 | use worker::*; 16 | 17 | #[durable_object(alarm)] 18 | struct Sequencer(GenericSequencer); 19 | 20 | impl DurableObject for Sequencer { 21 | fn new(state: State, env: Env) -> Self { 22 | let name = get_durable_object_name( 23 | &env, 24 | &state, 25 | SEQUENCER_BINDING, 26 | &mut CONFIG.logs.keys().map(|name| (name.as_str(), 0)), 27 | ); 28 | let params = &CONFIG.logs[name]; 29 | 30 | let config = SequencerConfig { 31 | name: name.to_string(), 32 | origin: load_origin(name), 33 | checkpoint_signers: load_checkpoint_signers(&env, name), 34 | checkpoint_extension: Box::new(|_| vec![]), // no checkpoint extensions for CT 35 | sequence_interval: Duration::from_millis(params.sequence_interval_millis), 36 | max_sequence_skips: params.max_sequence_skips, 37 | enable_dedup: params.enable_dedup, 38 | sequence_skip_threshold_millis: params.sequence_skip_threshold_millis, 39 | location_hint: params.location_hint.clone(), 40 | checkpoint_callback: empty_checkpoint_callback(), 41 | }; 42 | 43 | Sequencer(GenericSequencer::new(state, env, config)) 44 | } 45 | 46 | async fn fetch(&self, req: Request) -> Result { 47 | self.0.fetch(req).await 48 | } 49 | 50 | async fn alarm(&self) -> Result { 51 | self.0.alarm().await 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /crates/mtc_worker/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mtc_worker" 3 | publish = false 4 | version.workspace = true 5 | authors.workspace = true 6 | edition.workspace = true 7 | license.workspace = true 8 | homepage.workspace = true 9 | repository.workspace = true 10 | description = "An implementation of a Merkle Tree Certificates CA on Cloudflare Workers" 11 | categories = ["cryptography"] 12 | keywords = ["certificate", "transparency", "crypto", "pki"] 13 | 14 | [features] 15 | dev-bootstrap-roots = [] 16 | 17 | [package.metadata.release] 18 | release = false 19 | 20 | # https://github.com/rustwasm/wasm-pack/issues/1351 21 | [package.metadata.wasm-pack.profile.dev.wasm-bindgen] 22 | dwarf-debug-info = true 23 | 24 | [lib] 25 | crate-type = ["cdylib"] 26 | 27 | [build-dependencies] 28 | chrono.workspace = true 29 | config = { path = "./config", package = "mtc_worker_config" } 30 | generic_log_worker.workspace = true 31 | jsonschema.workspace = true 32 | mtc_api.workspace = true 33 | serde_json.workspace = true 34 | serde.workspace = true 35 | url.workspace = true 36 | x509-cert.workspace = true 37 | der.workspace = true 38 | 39 | [dev-dependencies] 40 | rand = { workspace = true, features = ["small_rng"] } 41 | itertools.workspace = true 42 | parking_lot.workspace = true 43 | futures-executor.workspace = true 44 | 45 | [dependencies] 46 | base64.workspace = true 47 | base64ct.workspace = true 48 | chrono.workspace = true 49 | config = { path = "./config", package = "mtc_worker_config" } 50 | csv.workspace = true 51 | der.workspace = true 52 | generic_log_worker.workspace = true 53 | ed25519-dalek.workspace = true 54 | getrandom.workspace = true 55 | log.workspace = true 56 | p256.workspace = true 57 | serde.workspace = true 58 | serde_json.workspace = true 59 | serde_with.workspace = true 60 | signed_note.workspace = true 61 | tlog_tiles.workspace = true 62 | tokio.workspace = true 63 | worker.workspace = true 64 | x509-cert.workspace = true 65 | x509_util.workspace = true 66 | mtc_api.workspace = true 67 | 68 | [lints.rust] 69 | unexpected_cfgs = { level = "warn", check-cfg = [ 70 | 'cfg(wasm_bindgen_unstable_test_coverage)', 71 | ] } 72 | 73 | [package.metadata.cargo-machete] 74 | ignored = ["getrandom"] 75 | -------------------------------------------------------------------------------- /crates/signed_note/benches/benchmark_verify.rs: -------------------------------------------------------------------------------- 1 | // Ported from "mod" (https://pkg.go.dev/golang.org/x/mod) 2 | // Copyright 2009 The Go Authors 3 | // Licensed under the BSD-3-Clause license found in the LICENSE file or at https://opensource.org/licenses/BSD-3-Clause 4 | // 5 | // This ports code from the original Go project "mod" and adapts it to Rust idioms. 6 | // 7 | // Modifications and Rust implementation Copyright (c) 2025 Cloudflare, Inc. 8 | // Licensed under the BSD-3-Clause license found in the LICENSE file or at https://opensource.org/licenses/BSD-3-Clause 9 | 10 | //! This file contains code ported from the original project [note](https://pkg.go.dev/golang.org/x/mod/sumdb/note). 11 | //! 12 | //! References: 13 | //! - [note_test.go](https://cs.opensource.google/go/x/mod/+/refs/tags/v0.21.0:sumdb/note/note_test.go) 14 | 15 | use criterion::{criterion_group, criterion_main, Criterion}; 16 | use signed_note::{Ed25519NoteVerifier, Note, NoteError, VerifierList}; 17 | use std::hint::black_box; 18 | 19 | fn benchmark_verify(c: &mut Criterion) { 20 | let vkey = "PeterNeumann+c74f20a3+ARpc2QcUPDhMQegwxbzhKqiBfsVkmqq/LDE4izWy10TW"; 21 | let msg = "If you think cryptography is the answer to your problem,\n\ 22 | then you don't know what your problem is.\n\ 23 | \n\ 24 | — PeterNeumann x08go/ZJkuBS9UG/SffcvIAQxVBtiFupLLr8pAcElZInNIuGUgYN1FFYC2pZSNXgKvqfqdngotpRZb6KE6RyyBwJnAM=\n".as_bytes(); 25 | 26 | let verifier = Ed25519NoteVerifier::new_from_encoded_key(vkey).unwrap(); 27 | 28 | c.bench_function("Sig0", |b| { 29 | b.iter(|| { 30 | let err = Note::from_bytes(black_box(msg)) 31 | .unwrap() 32 | .verify(&VerifierList::new(black_box(vec![]))) 33 | .unwrap_err(); 34 | assert!(matches!(err, NoteError::UnverifiedNote)); 35 | }); 36 | }); 37 | 38 | c.bench_function("Sig1", |b| { 39 | b.iter(|| { 40 | let (verified_sigs, unverified_sigs) = Note::from_bytes(black_box(msg)) 41 | .unwrap() 42 | .verify(black_box(&VerifierList::new(vec![Box::new( 43 | verifier.clone(), 44 | )]))) 45 | .unwrap(); 46 | assert_eq!(verified_sigs.len(), 1); 47 | assert!(unverified_sigs.is_empty()); 48 | }); 49 | }); 50 | } 51 | 52 | criterion_group!(benches, benchmark_verify); 53 | criterion_main!(benches); 54 | -------------------------------------------------------------------------------- /crates/mtc_worker/README.md: -------------------------------------------------------------------------------- 1 | # Merkle Tree CA Worker 2 | 3 | A Rust implementation of a [Merkle Tree CA](https://github.com/davidben/merkle-tree-certs/) (MTCA) for deployment on [Cloudflare Workers](https://workers.cloudflare.com/). 4 | 5 | Much of the API and the internal architecture of the Merkle Tree CA is shared by the [Static CT Log](../ct_worker/README.md). This Worker also implements issuance of Merkle Tree Certificates (MTCs). The issuance API should be considered unstable. For now, its primary purpose is to support an experimental deployment of the MTC specification. 6 | 7 | ## Development 8 | 9 | `node` and `npm` are required to run the Worker locally. First, use `npm` to install `wrangler`: 10 | 11 | ```bash 12 | npm install -g wrangler@latest 13 | ``` 14 | 15 | Then use `wrangler` to run the Worker locally from this directory: 16 | 17 | ```bash 18 | npx wrangler dev -e=dev 19 | ``` 20 | 21 | The Worker doesn't implement a full-blown MTCA. Instead, it implements what we call a **bootstrap MTCA**. For every MTC requested, the requester must provide a **bootstrap certificate**. A bootstrap certificate is a standard X.509 certificate chain that must have a path to a root certificate trusted by `mtc_worker`. By default, the root store used is the intersection of Chrome's and Mozilla's trust stores. 22 | 23 | To test the basic functionality, run the following script from this directory: 24 | 25 | ```bash 26 | ./test-dev.sh 27 | ``` 28 | 29 | This script does the following: 30 | 31 | 1. Fetch a bootstrap certificate chain. 32 | 33 | 1. Submit the bootstrap certificate chain to the MTCA running locally. 34 | 35 | 1. Wait for the next landmark to be minted. The landmark interval is defined in [`config.dev.json`](./config.dev.json). 36 | 37 | 1. Request the signatureless MTC from the MTCA running locally 38 | 39 | ### Overriding the trust store 40 | 41 | It may be useful to provide your own roots for testing. To do so: 42 | 43 | 1. Build the Worker with the `"dev-bootstrap-roots"` feature. Note that `wrangler` invokes `cargo` with a custom build script, so the simplest thing to do is to edit the `Cargo.toml` file by adding `"dev-boostrap-roots"` to the default feature set. 44 | 45 | 1. Append your roots to [`dev-bootstrap-roots.pem`](./dev-bootstrap-roots.pem). 46 | 47 | ## Deployment 48 | 49 | See the [`ct_worker` documentation](../ct_worker/README.md#deployment-to-a-custom-domain) for deployment to a custom domain. 50 | 51 | ## License 52 | 53 | The project is licensed under the [BSD-3-Clause License](./LICENSE). 54 | -------------------------------------------------------------------------------- /crates/ct_worker/.dev.vars: -------------------------------------------------------------------------------- 1 | SIGNING_KEY_dev2025h1a="-----BEGIN PRIVATE KEY-----\nMIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQguu9K86g+++gKa7ag\ntkPw5E3xPhTeSkj69l0VL06EeQGhRANCAAQLOWnBI0PojH8rWoAoitlJ+Ip6iQl4\nWycheJdCWsXF2NzbOLM5aFMGpz3Bwm5egkGCzrLbhGW7z9p3FAI0N08o\n-----END PRIVATE KEY-----\n" 2 | WITNESS_KEY_dev2025h1a="-----BEGIN PRIVATE KEY-----\nMC4CAQAwBQYDK2VwBCIEIHwiErJNKCNGZL+Osj+O8MqSMiwPP4kdcC4iTpojV9Od\n-----END PRIVATE KEY-----\n" 3 | SIGNING_KEY_dev2025h2a="-----BEGIN PRIVATE KEY-----\nMIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgR2CdNf+JR6afd0gb\n+lMSINqCqiLDb7L88lo1qhxBynOhRANCAARLjKsvuNqvDER1Jasmnfm55/vz1Rgu\nZr8XTHtt8GlbYpac3nK4MTleB44Ap5YzdGnJwJkXbFEYCnaIcUJrg+2o\n-----END PRIVATE KEY-----\n" 4 | WITNESS_KEY_dev2025h2a="-----BEGIN PRIVATE KEY-----\nMC4CAQAwBQYDK2VwBCIEINKYH1WadDgPJEXYzLx0OWzNoi4hRcpUnYpoWrTc7BDO\n-----END PRIVATE KEY-----\n" 5 | SIGNING_KEY_dev2026h1a="-----BEGIN PRIVATE KEY-----\nMIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgMcpVNLWTILGapBcQ\n0e59OCX+MC3ik6S/o3EzrBvISi2hRANCAATuqQUZCcCoG0yQWPiXy11zQwhUCNjw\nQb7fWqyzNGBZSgeDeUXB1+F1J3x6Nv9wb+PWj91XRYKN5zMpBwoZXrfz\n-----END PRIVATE KEY-----\n" 6 | WITNESS_KEY_dev2026h1a="-----BEGIN PRIVATE KEY-----\nMC4CAQAwBQYDK2VwBCIEIGttRSMUB4BfaxIWodXTMiGqLnBMaNAmOdms0gVelXvn\n-----END PRIVATE KEY-----\n" 7 | SIGNING_KEY_dev2026h2a="-----BEGIN PRIVATE KEY-----\nMIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgt14ClEtNlyhyy8IQ\njYA1gV0KH35xcHbaJ5g5tU7TbGqhRANCAAR1lWiAPxLrYQ5OjAWIwaYRDckh2+GD\nS2pyO25lj/lWJg94IY2MY/CaRbrIuWGUWjBRJczMjDBkajeaFC//dpG9\n-----END PRIVATE KEY-----\n" 8 | WITNESS_KEY_dev2026h2a="-----BEGIN PRIVATE KEY-----\nMC4CAQAwBQYDK2VwBCIEIFBSB+nqtrOlOg5f3GiVYlt9Q1ni9s+ooqPBDyciEQzw\n-----END PRIVATE KEY-----\n" 9 | SIGNING_KEY_dev2027h1a="-----BEGIN PRIVATE KEY-----\nMIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQggM2mn5ZSIIMwO7XG\nn2t6qcJWBUCx2rO2F6nAdY6OdIahRANCAARQiB2uy0Xl37DU8SROPUaQugrkqwRI\nw3JFQfZql6u7y++P68b5mad7vQShq5Js0kZ0YPV6rRlVJ5elhe2NQ5Dp\n-----END PRIVATE KEY-----\n" 10 | WITNESS_KEY_dev2027h1a="-----BEGIN PRIVATE KEY-----\nMC4CAQAwBQYDK2VwBCIEINECRYfEk5MrjaJsb11e05m+5JPBI9u/sjLw8iWanaHI\n-----END PRIVATE KEY-----\n" 11 | SIGNING_KEY_dev2027h2a="-----BEGIN PRIVATE KEY-----\nMIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgiFMQhVR1SyEE8Qba\nvsZGkNKESFww6dhLd+qtFkjIxeyhRANCAATnRzos2J2mVztHcgVDp0W95o4ENRsm\nAcvpTgLIgk4XB3RlmV4Ye1+I76n1tY4SJgOXccTDWfQVLUD8ilI2eC9f\n-----END PRIVATE KEY-----\n" 12 | WITNESS_KEY_dev2027h2a="-----BEGIN PRIVATE KEY-----\nMC4CAQAwBQYDK2VwBCIEIAFzi3DVqaBuo3Ms2aurLSzOdgXH2MY1ezNOUqTd33HQ\n-----END PRIVATE KEY-----\n" 13 | -------------------------------------------------------------------------------- /crates/static_ct_api/tests/leaf-cert.pem: -------------------------------------------------------------------------------- 1 | Certificate: 2 | Data: 3 | Version: 3 (0x2) 4 | Serial Number: 3735928559 (0xdeadbeef) 5 | Signature Algorithm: ecdsa-with-SHA256 6 | Issuer: C=GB, ST=London, L=London, O=Google, OU=Eng, CN=FakeIntermediateAuthority 7 | Validity 8 | Not Before: Feb 13 11:38:39 2018 GMT 9 | Not After : Mar 28 11:38:39 2025 GMT 10 | Subject: C=GB, ST=London, O=Google, OU=Eng, CN=leaf01.csr.pem 11 | Subject Public Key Info: 12 | Public Key Algorithm: id-ecPublicKey 13 | Public-Key: (256 bit) 14 | pub: 15 | 04:eb:37:4e:52:45:9c:46:d5:a8:b8:c5:ed:58:b9: 16 | 30:29:a6:70:8a:69:a0:26:5c:9e:2f:6e:b8:6b:23: 17 | 6c:84:e1:46:3a:98:36:82:44:a5:8a:17:8b:41:82: 18 | 32:f4:2d:e0:08:5b:7e:07:38:52:fc:47:56:28:27: 19 | 9b:ed:60:8b:ac 20 | ASN1 OID: prime256v1 21 | NIST CURVE: P-256 22 | X509v3 extensions: 23 | X509v3 Subject Key Identifier: 24 | 3F:B2:2F:41:FC:11:9A:D3:8D:A6:85:80:84:86:AE:7E:73:2E:69:5D 25 | X509v3 Authority Key Identifier: 26 | 01:02:03:04 27 | X509v3 Key Usage: critical 28 | Digital Signature, Non Repudiation, Key Encipherment, Data Encipherment, Key Agreement, Encipher Only, Decipher Only 29 | X509v3 Subject Alternative Name: 30 | DNS:leaf01.csr.pem 31 | Signature Algorithm: ecdsa-with-SHA256 32 | Signature Value: 33 | 30:46:02:21:00:b5:2a:f3:39:1e:06:b7:77:b2:ad:a8:83:1b: 34 | 83:38:64:5e:3a:25:51:e9:57:1f:00:53:72:db:08:11:65:3d: 35 | f4:02:21:00:a1:4e:5d:b5:9a:8b:10:6e:15:a3:2a:bd:d9:80: 36 | 91:96:7c:1a:4f:8f:91:dc:44:9f:13:ff:57:f0:5e:ce:32:34 37 | -----BEGIN CERTIFICATE----- 38 | MIICGjCCAb+gAwIBAgIFAN6tvu8wCgYIKoZIzj0EAwIwcjELMAkGA1UEBhMCR0Ix 39 | DzANBgNVBAgTBkxvbmRvbjEPMA0GA1UEBxMGTG9uZG9uMQ8wDQYDVQQKEwZHb29n 40 | bGUxDDAKBgNVBAsTA0VuZzEiMCAGA1UEAxMZRmFrZUludGVybWVkaWF0ZUF1dGhv 41 | cml0eTAeFw0xODAyMTMxMTM4MzlaFw0yNTAzMjgxMTM4MzlaMFYxCzAJBgNVBAYT 42 | AkdCMQ8wDQYDVQQIDAZMb25kb24xDzANBgNVBAoMBkdvb2dsZTEMMAoGA1UECwwD 43 | RW5nMRcwFQYDVQQDDA5sZWFmMDEuY3NyLnBlbTBZMBMGByqGSM49AgEGCCqGSM49 44 | AwEHA0IABOs3TlJFnEbVqLjF7Vi5MCmmcIppoCZcni9uuGsjbIThRjqYNoJEpYoX 45 | i0GCMvQt4Ahbfgc4UvxHVignm+1gi6yjXjBcMB0GA1UdDgQWBBQ/si9B/BGa042m 46 | hYCEhq5+cy5pXTAPBgNVHSMECDAGgAQBAgMEMA8GA1UdDwEB/wQFAwMH+YAwGQYD 47 | VR0RBBIwEIIObGVhZjAxLmNzci5wZW0wCgYIKoZIzj0EAwIDSQAwRgIhALUq8zke 48 | Brd3sq2ogxuDOGReOiVR6VcfAFNy2wgRZT30AiEAoU5dtZqLEG4Voyq92YCRlnwa 49 | T4+R3ESfE/9X8F7OMjQ= 50 | -----END CERTIFICATE----- 51 | -------------------------------------------------------------------------------- /crates/static_ct_api/tests/fake-root-ca-cert.pem: -------------------------------------------------------------------------------- 1 | Certificate: 2 | Data: 3 | Version: 3 (0x2) 4 | Serial Number: 67554046 (0x406cafe) 5 | Signature Algorithm: ecdsa-with-SHA256 6 | Issuer: C=GB, ST=London, L=London, O=Google, OU=Eng, CN=FakeCertificateAuthority 7 | Validity 8 | Not Before: Dec 7 15:13:36 2016 GMT 9 | Not After : Dec 5 15:13:36 2026 GMT 10 | Subject: C=GB, ST=London, L=London, O=Google, OU=Eng, CN=FakeCertificateAuthority 11 | Subject Public Key Info: 12 | Public Key Algorithm: id-ecPublicKey 13 | Public-Key: (256 bit) 14 | pub: 15 | 04:f2:d3:07:ef:7e:df:cf:ce:f4:f4:0a:5b:bc:9e: 16 | 3f:cb:1c:fd:0c:46:dc:85:fb:c1:f6:d3:b2:ba:1d: 17 | 51:f1:98:6c:48:a8:15:46:45:63:ca:df:d6:c9:ac: 18 | cf:60:3b:c7:4e:dd:b8:d2:16:ab:a0:09:24:1d:09: 19 | 66:1e:4d:eb:a1 20 | ASN1 OID: prime256v1 21 | NIST CURVE: P-256 22 | X509v3 extensions: 23 | X509v3 Subject Key Identifier: 24 | 01:02:03:04 25 | X509v3 Authority Key Identifier: 26 | 01:02:03:04 27 | X509v3 Basic Constraints: critical 28 | CA:TRUE, pathlen:10 29 | X509v3 Key Usage: critical 30 | Digital Signature, Non Repudiation, Key Encipherment, Data Encipherment, Key Agreement, Certificate Sign, CRL Sign, Encipher Only, Decipher Only 31 | Signature Algorithm: ecdsa-with-SHA256 32 | Signature Value: 33 | 30:46:02:21:00:a6:28:49:39:43:6f:80:e4:43:a6:1e:3b:aa: 34 | 89:5e:c2:25:60:2a:e1:39:bd:55:43:ae:4d:5c:a9:a6:ef:ac: 35 | 65:02:21:00:c9:c5:08:c6:59:93:b4:86:70:a5:6b:54:2b:5b: 36 | fc:0c:88:6b:b0:23:07:2b:c7:0c:27:de:87:2d:96:80:d5:56 37 | -----BEGIN CERTIFICATE----- 38 | MIICHDCCAcGgAwIBAgIEBAbK/jAKBggqhkjOPQQDAjBxMQswCQYDVQQGEwJHQjEP 39 | MA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xDzANBgNVBAoTBkdvb2ds 40 | ZTEMMAoGA1UECxMDRW5nMSEwHwYDVQQDExhGYWtlQ2VydGlmaWNhdGVBdXRob3Jp 41 | dHkwHhcNMTYxMjA3MTUxMzM2WhcNMjYxMjA1MTUxMzM2WjBxMQswCQYDVQQGEwJH 42 | QjEPMA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xDzANBgNVBAoTBkdv 43 | b2dsZTEMMAoGA1UECxMDRW5nMSEwHwYDVQQDExhGYWtlQ2VydGlmaWNhdGVBdXRo 44 | b3JpdHkwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATy0wfvft/PzvT0Clu8nj/L 45 | HP0MRtyF+8H207K6HVHxmGxIqBVGRWPK39bJrM9gO8dO3bjSFqugCSQdCWYeTeuh 46 | o0cwRTANBgNVHQ4EBgQEAQIDBDAPBgNVHSMECDAGgAQBAgMEMBIGA1UdEwEB/wQI 47 | MAYBAf8CAQowDwYDVR0PAQH/BAUDAwf/gDAKBggqhkjOPQQDAgNJADBGAiEApihJ 48 | OUNvgORDph47qolewiVgKuE5vVVDrk1cqabvrGUCIQDJxQjGWZO0hnCla1QrW/wM 49 | iGuwIwcrxwwn3octloDVVg== 50 | -----END CERTIFICATE----- 51 | -------------------------------------------------------------------------------- /crates/static_ct_api/tests/fake-intermediate-with-policy-constraints-cert.pem: -------------------------------------------------------------------------------- 1 | Certificate: 2 | Data: 3 | Version: 3 (0x2) 4 | Serial Number: 1111638594 (0x42424242) 5 | Signature Algorithm: ecdsa-with-SHA256 6 | Issuer: C=GB, ST=London, L=London, O=Google, OU=Eng, CN=FakeCertificateAuthority 7 | Validity 8 | Not Before: Feb 13 09:33:59 2018 GMT 9 | Not After : Dec 23 09:33:59 2027 GMT 10 | Subject: C=GB, ST=London, L=London, O=Google, OU=Eng, CN=FakeIntermediateAuthority 11 | Subject Public Key Info: 12 | Public Key Algorithm: id-ecPublicKey 13 | Public-Key: (256 bit) 14 | pub: 15 | 04:f1:bf:2d:e8:8c:66:40:e3:a8:d1:54:e0:42:49: 16 | 02:cb:dd:47:08:85:c2:67:41:4c:eb:f7:87:cd:8d: 17 | a3:09:c8:18:cc:2e:30:53:16:32:aa:d5:9c:08:73: 18 | c6:76:fa:fa:3a:38:e9:34:35:9c:51:d1:ee:12:81: 19 | 5d:98:5f:5d:5d 20 | ASN1 OID: prime256v1 21 | NIST CURVE: P-256 22 | X509v3 extensions: 23 | X509v3 Subject Key Identifier: 24 | 01:02:03:04 25 | X509v3 Authority Key Identifier: 26 | 01:02:03:04 27 | X509v3 Basic Constraints: critical 28 | CA:TRUE, pathlen:10 29 | X509v3 Policy Constraints: critical 30 | Require Explicit Policy:0 31 | X509v3 Key Usage: critical 32 | Digital Signature, Non Repudiation, Key Encipherment, Data Encipherment, Key Agreement, Certificate Sign, CRL Sign, Encipher Only, Decipher Only 33 | Signature Algorithm: ecdsa-with-SHA256 34 | Signature Value: 35 | 30:44:02:20:4c:aa:27:8f:d9:83:32:76:40:17:a1:a8:00:1d: 36 | bc:d1:45:b2:53:c6:47:77:48:f1:c3:89:68:5d:f4:7f:5c:52: 37 | 02:20:39:68:40:5c:fd:f0:2a:e2:3f:34:45:b3:19:2d:e3:4d: 38 | 58:cd:76:42:19:09:cf:5c:1c:e5:f1:71:e0:39:62:b9 39 | -----BEGIN CERTIFICATE----- 40 | MIICLDCCAdOgAwIBAgIEQkJCQjAKBggqhkjOPQQDAjBxMQswCQYDVQQGEwJHQjEP 41 | MA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xDzANBgNVBAoTBkdvb2ds 42 | ZTEMMAoGA1UECxMDRW5nMSEwHwYDVQQDExhGYWtlQ2VydGlmaWNhdGVBdXRob3Jp 43 | dHkwHhcNMTgwMjEzMDkzMzU5WhcNMjcxMjIzMDkzMzU5WjByMQswCQYDVQQGEwJH 44 | QjEPMA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xDzANBgNVBAoTBkdv 45 | b2dsZTEMMAoGA1UECxMDRW5nMSIwIAYDVQQDExlGYWtlSW50ZXJtZWRpYXRlQXV0 46 | aG9yaXR5MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE8b8t6IxmQOOo0VTgQkkC 47 | y91HCIXCZ0FM6/eHzY2jCcgYzC4wUxYyqtWcCHPGdvr6OjjpNDWcUdHuEoFdmF9d 48 | XaNYMFYwDQYDVR0OBAYEBAECAwQwDwYDVR0jBAgwBoAEAQIDBDASBgNVHRMBAf8E 49 | CDAGAQH/AgEKMA8GA1UdJAEB/wQFMAOAAQAwDwYDVR0PAQH/BAUDAwf/gDAKBggq 50 | hkjOPQQDAgNHADBEAiBMqieP2YMydkAXoagAHbzRRbJTxkd3SPHDiWhd9H9cUgIg 51 | OWhAXP3wKuI/NEWzGS3jTVjNdkIZCc9cHOXxceA5Yrk= 52 | -----END CERTIFICATE----- 53 | -------------------------------------------------------------------------------- /crates/static_ct_api/tests/fake-intermediate-with-name-constraints-cert.pem: -------------------------------------------------------------------------------- 1 | Certificate: 2 | Data: 3 | Version: 3 (0x2) 4 | Serial Number: 1111638594 (0x42424242) 5 | Signature Algorithm: ecdsa-with-SHA256 6 | Issuer: C=GB, ST=London, L=London, O=Google, OU=Eng, CN=FakeCertificateAuthority 7 | Validity 8 | Not Before: Feb 13 11:33:08 2018 GMT 9 | Not After : Dec 23 11:33:08 2027 GMT 10 | Subject: C=GB, ST=London, L=London, O=Google, OU=Eng, CN=FakeIntermediateAuthority 11 | Subject Public Key Info: 12 | Public Key Algorithm: id-ecPublicKey 13 | Public-Key: (256 bit) 14 | pub: 15 | 04:f1:bf:2d:e8:8c:66:40:e3:a8:d1:54:e0:42:49: 16 | 02:cb:dd:47:08:85:c2:67:41:4c:eb:f7:87:cd:8d: 17 | a3:09:c8:18:cc:2e:30:53:16:32:aa:d5:9c:08:73: 18 | c6:76:fa:fa:3a:38:e9:34:35:9c:51:d1:ee:12:81: 19 | 5d:98:5f:5d:5d 20 | ASN1 OID: prime256v1 21 | NIST CURVE: P-256 22 | X509v3 extensions: 23 | X509v3 Subject Key Identifier: 24 | 01:02:03:04 25 | X509v3 Authority Key Identifier: 26 | 01:02:03:04 27 | X509v3 Basic Constraints: critical 28 | CA:TRUE, pathlen:10 29 | X509v3 Key Usage: critical 30 | Digital Signature, Non Repudiation, Key Encipherment, Data Encipherment, Key Agreement, Certificate Sign, CRL Sign, Encipher Only, Decipher Only 31 | X509v3 Name Constraints: 32 | Permitted: 33 | DNS:.csr.pem 34 | Signature Algorithm: ecdsa-with-SHA256 35 | Signature Value: 36 | 30:46:02:21:00:fd:11:41:d8:1f:2b:b5:49:8e:27:6e:70:93: 37 | 2c:f1:c2:e7:b0:a2:40:e2:c6:89:45:fc:99:a5:9b:dc:21:fb: 38 | f6:02:21:00:b7:4f:98:bf:1f:dc:92:e7:db:7c:aa:33:7a:40: 39 | 36:1d:58:19:aa:96:3d:5e:5b:46:5f:47:f6:e3:7d:75:19:4f 40 | -----BEGIN CERTIFICATE----- 41 | MIICNjCCAdugAwIBAgIEQkJCQjAKBggqhkjOPQQDAjBxMQswCQYDVQQGEwJHQjEP 42 | MA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xDzANBgNVBAoTBkdvb2ds 43 | ZTEMMAoGA1UECxMDRW5nMSEwHwYDVQQDExhGYWtlQ2VydGlmaWNhdGVBdXRob3Jp 44 | dHkwHhcNMTgwMjEzMTEzMzA4WhcNMjcxMjIzMTEzMzA4WjByMQswCQYDVQQGEwJH 45 | QjEPMA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xDzANBgNVBAoTBkdv 46 | b2dsZTEMMAoGA1UECxMDRW5nMSIwIAYDVQQDExlGYWtlSW50ZXJtZWRpYXRlQXV0 47 | aG9yaXR5MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE8b8t6IxmQOOo0VTgQkkC 48 | y91HCIXCZ0FM6/eHzY2jCcgYzC4wUxYyqtWcCHPGdvr6OjjpNDWcUdHuEoFdmF9d 49 | XaNgMF4wDQYDVR0OBAYEBAECAwQwDwYDVR0jBAgwBoAEAQIDBDASBgNVHRMBAf8E 50 | CDAGAQH/AgEKMA8GA1UdDwEB/wQFAwMH/4AwFwYDVR0eBBAwDqAMMAqCCC5jc3Iu 51 | cGVtMAoGCCqGSM49BAMCA0kAMEYCIQD9EUHYHyu1SY4nbnCTLPHC57CiQOLGiUX8 52 | maWb3CH79gIhALdPmL8f3JLn23yqM3pANh1YGaqWPV5bRl9H9uN9dRlP 53 | -----END CERTIFICATE----- 54 | -------------------------------------------------------------------------------- /crates/static_ct_api/tests/fake-intermediate-with-invalid-name-constraints-cert.pem: -------------------------------------------------------------------------------- 1 | Certificate: 2 | Data: 3 | Version: 3 (0x2) 4 | Serial Number: 1111638594 (0x42424242) 5 | Signature Algorithm: ecdsa-with-SHA256 6 | Issuer: C=GB, ST=London, L=London, O=Google, OU=Eng, CN=FakeCertificateAuthority 7 | Validity 8 | Not Before: Feb 13 11:42:37 2018 GMT 9 | Not After : Dec 23 11:42:37 2027 GMT 10 | Subject: C=GB, ST=London, L=London, O=Google, OU=Eng, CN=FakeIntermediateAuthority 11 | Subject Public Key Info: 12 | Public Key Algorithm: id-ecPublicKey 13 | Public-Key: (256 bit) 14 | pub: 15 | 04:f1:bf:2d:e8:8c:66:40:e3:a8:d1:54:e0:42:49: 16 | 02:cb:dd:47:08:85:c2:67:41:4c:eb:f7:87:cd:8d: 17 | a3:09:c8:18:cc:2e:30:53:16:32:aa:d5:9c:08:73: 18 | c6:76:fa:fa:3a:38:e9:34:35:9c:51:d1:ee:12:81: 19 | 5d:98:5f:5d:5d 20 | ASN1 OID: prime256v1 21 | NIST CURVE: P-256 22 | X509v3 extensions: 23 | X509v3 Subject Key Identifier: 24 | 01:02:03:04 25 | X509v3 Authority Key Identifier: 26 | 01:02:03:04 27 | X509v3 Basic Constraints: critical 28 | CA:TRUE, pathlen:10 29 | X509v3 Key Usage: critical 30 | Digital Signature, Non Repudiation, Key Encipherment, Data Encipherment, Key Agreement, Certificate Sign, CRL Sign, Encipher Only, Decipher Only 31 | X509v3 Name Constraints: 32 | Permitted: 33 | DNS:.xyzzy.pem 34 | Signature Algorithm: ecdsa-with-SHA256 35 | Signature Value: 36 | 30:45:02:20:3f:0a:40:60:b6:9e:ea:a5:cd:eb:e4:0e:7c:bc: 37 | 40:22:b2:e2:14:07:e8:ab:fa:4a:85:2a:41:18:20:f0:31:1a: 38 | 02:21:00:a4:64:91:6d:79:47:79:0f:16:06:62:a9:88:8b:92: 39 | 6d:40:fa:54:cb:c9:4f:bc:3f:53:27:e5:cd:12:16:53:7a 40 | -----BEGIN CERTIFICATE----- 41 | MIICNzCCAd2gAwIBAgIEQkJCQjAKBggqhkjOPQQDAjBxMQswCQYDVQQGEwJHQjEP 42 | MA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xDzANBgNVBAoTBkdvb2ds 43 | ZTEMMAoGA1UECxMDRW5nMSEwHwYDVQQDExhGYWtlQ2VydGlmaWNhdGVBdXRob3Jp 44 | dHkwHhcNMTgwMjEzMTE0MjM3WhcNMjcxMjIzMTE0MjM3WjByMQswCQYDVQQGEwJH 45 | QjEPMA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xDzANBgNVBAoTBkdv 46 | b2dsZTEMMAoGA1UECxMDRW5nMSIwIAYDVQQDExlGYWtlSW50ZXJtZWRpYXRlQXV0 47 | aG9yaXR5MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE8b8t6IxmQOOo0VTgQkkC 48 | y91HCIXCZ0FM6/eHzY2jCcgYzC4wUxYyqtWcCHPGdvr6OjjpNDWcUdHuEoFdmF9d 49 | XaNiMGAwDQYDVR0OBAYEBAECAwQwDwYDVR0jBAgwBoAEAQIDBDASBgNVHRMBAf8E 50 | CDAGAQH/AgEKMA8GA1UdDwEB/wQFAwMH/4AwGQYDVR0eBBIwEKAOMAyCCi54eXp6 51 | eS5wZW0wCgYIKoZIzj0EAwIDSAAwRQIgPwpAYLae6qXN6+QOfLxAIrLiFAfoq/pK 52 | hSpBGCDwMRoCIQCkZJFteUd5DxYGYqmIi5JtQPpUy8lPvD9TJ+XNEhZTeg== 53 | -----END CERTIFICATE----- 54 | -------------------------------------------------------------------------------- /crates/ct_worker/scripts/create-log.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e -o pipefail 4 | cd "$(dirname "$0")/.." || exit # this script assumes it's runnnig inside the ct_worker dir 5 | 6 | # Helper script to create resources for a log shard. 7 | 8 | if [ -z "${ENV}" ] || [ -z "${LOG_NAME}" ] || [ -z "${CLOUDFLARE_ACCOUNT_ID}" ]; then 9 | echo "ENV, LOG_NAME, and CLOUDFLARE_ACCOUNT_ID must all be set" 10 | exit 1 11 | fi 12 | 13 | WRANGLER_CONF=${WRANGLER_CONF:-wrangler.jsonc} 14 | 15 | while true; do 16 | if [ "${LOCATION}" ]; then 17 | L=", LOCATION=${LOCATION}" 18 | fi 19 | read -rp "Do you want to proceed with ENV=${ENV}, LOG_NAME=${LOG_NAME}${L}, CLOUDFLARE_ACCOUNT_ID=${CLOUDFLARE_ACCOUNT_ID}? (y/N) " yn 20 | case $yn in 21 | [yY] ) echo "Proceeding..."; break;; 22 | [nN] ) echo "Exiting..."; exit;; 23 | * ) echo "Invalid input. Please enter 'y' or 'N'.";; 24 | esac 25 | done 26 | 27 | 28 | # https://github.com/cloudflare/azul/pull/169#discussion_r2582145507 29 | location=() 30 | if [ "${LOCATION}" ]; then 31 | location=(--location "${LOCATION}") 32 | fi 33 | 34 | # Create R2 bucket if it does not already exist 35 | npx wrangler \ 36 | -e="${ENV}" \ 37 | -c "${WRANGLER_CONF}" \ 38 | r2 bucket create \ 39 | "static-ct-public-${LOG_NAME}" \ 40 | --update-config \ 41 | --binding "public_${LOG_NAME}" "${location[@]}" 42 | 43 | # Create KV namespace if it does not already exist 44 | npx wrangler \ 45 | -e="${ENV}" \ 46 | -c "${WRANGLER_CONF}" \ 47 | kv namespace create \ 48 | "static-ct-cache-${LOG_NAME}" \ 49 | --update-config \ 50 | --binding "cache_${LOG_NAME}" 51 | 52 | # Create witness and log signing keys if they do not already exist 53 | if npx wrangler -e="${ENV}" -c "${WRANGLER_CONF}" secret list | grep -q "WITNESS_KEY_${LOG_NAME}"; then 54 | echo "WITNESS_KEY_${LOG_NAME} already exists" 55 | else 56 | openssl genpkey -algorithm ed25519 | 57 | npx wrangler -e="${ENV}" -c "${WRANGLER_CONF}" secret put "WITNESS_KEY_${LOG_NAME}" 58 | fi 59 | if npx wrangler -e="${ENV}" -c "${WRANGLER_CONF}" secret list | grep -q "SIGNING_KEY_${LOG_NAME}"; then 60 | echo "SIGNING_KEY_${LOG_NAME} already exists" 61 | else 62 | openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 | 63 | npx wrangler -e="${ENV}" -c "${WRANGLER_CONF}" secret put "SIGNING_KEY_${LOG_NAME}" 64 | fi 65 | 66 | echo "DONE" 67 | echo "NOTE: If you intend to run wrangler dev with this log, you must add the appropriate signing keys to .dev.vars" 68 | echo "~~~~~~" 69 | printf 'echo -n "SIGNING_KEY_%s=\\"" >> .dev.vars\n' "${LOG_NAME}" 70 | printf 'openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 | sed '\''s/$/\\\\n/g'\'' | tr -d '\''\\n'\'' >> .dev.vars\n' 71 | printf 'echo \\" >> .dev.vars\n' 72 | printf 'echo -n "WITNESS_KEY_%s=\\"" >> .dev.vars\n' "${LOG_NAME}" 73 | printf 'openssl genpkey -algorithm ed25519 | sed '\''s/$/\\\\n/g'\'' | tr -d '\''\\n'\'' >> .dev.vars\n' 74 | printf 'echo \\" >> .dev.vars\n' 75 | -------------------------------------------------------------------------------- /crates/ct_worker/config.dev.json: -------------------------------------------------------------------------------- 1 | { 2 | "logging_level": "info", 3 | "logs": { 4 | "dev2025h1a": { 5 | "description": "Dev 2025h1a", 6 | "log_type": "test", 7 | "submission_url": "http://localhost:8787/logs/dev2025h1a/", 8 | "temporal_interval": { 9 | "start_inclusive": "2025-01-01T00:00:00Z", 10 | "end_exclusive": "2025-07-01T00:00:00Z" 11 | }, 12 | "location_hint": "enam" 13 | }, 14 | "dev2025h2a": { 15 | "description": "Dev 2025h2a", 16 | "log_type": "test", 17 | "submission_url": "http://localhost:8787/logs/dev2025h2a/", 18 | "temporal_interval": { 19 | "start_inclusive": "2025-07-01T00:00:00Z", 20 | "end_exclusive": "2026-01-01T00:00:00Z" 21 | }, 22 | "max_sequence_skips": 1, 23 | "sequence_interval_millis": 750, 24 | "sequence_skip_threshold_millis": 250, 25 | "location_hint": "enam" 26 | }, 27 | "dev2026h1a": { 28 | "description": "Dev 2026h1a", 29 | "log_type": "test", 30 | "submission_url": "http://localhost:8787/logs/dev2026h1a/", 31 | "temporal_interval": { 32 | "start_inclusive": "2026-01-01T00:00:00Z", 33 | "end_exclusive": "2026-07-01T00:00:00Z" 34 | }, 35 | "location_hint": "enam" 36 | }, 37 | "dev2026h2a": { 38 | "description": "Dev 2026h2a", 39 | "log_type": "test", 40 | "submission_url": "http://localhost:8787/logs/dev2026h2a/", 41 | "temporal_interval": { 42 | "start_inclusive": "2026-07-01T00:00:00Z", 43 | "end_exclusive": "2027-01-01T00:00:00Z" 44 | }, 45 | "max_sequence_skips": 1, 46 | "sequence_interval_millis": 750, 47 | "sequence_skip_threshold_millis": 250, 48 | "location_hint": "enam" 49 | }, 50 | "dev2027h1a": { 51 | "description": "Dev 2027h1a", 52 | "log_type": "test", 53 | "submission_url": "http://localhost:8787/logs/dev2027h1a/", 54 | "temporal_interval": { 55 | "start_inclusive": "2027-01-01T00:00:00Z", 56 | "end_exclusive": "2027-07-01T00:00:00Z" 57 | }, 58 | "location_hint": "enam" 59 | }, 60 | "dev2027h2a": { 61 | "description": "Dev 2027h2a", 62 | "log_type": "test", 63 | "submission_url": "http://localhost:8787/logs/dev2027h2a/", 64 | "temporal_interval": { 65 | "start_inclusive": "2027-07-01T00:00:00Z", 66 | "end_exclusive": "2028-01-01T00:00:00Z" 67 | }, 68 | "max_sequence_skips": 1, 69 | "sequence_interval_millis": 750, 70 | "sequence_skip_threshold_millis": 250, 71 | "location_hint": "enam" 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /crates/signed_note/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2025 Cloudflare, Inc. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of the copyright holder nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | ======================================================================== 30 | 31 | Copyright 2009 The Go Authors. 32 | 33 | Redistribution and use in source and binary forms, with or without 34 | modification, are permitted provided that the following conditions are 35 | met: 36 | 37 | * Redistributions of source code must retain the above copyright 38 | notice, this list of conditions and the following disclaimer. 39 | * Redistributions in binary form must reproduce the above 40 | copyright notice, this list of conditions and the following disclaimer 41 | in the documentation and/or other materials provided with the 42 | distribution. 43 | * Neither the name of Google LLC nor the names of its 44 | contributors may be used to endorse or promote products derived from 45 | this software without specific prior written permission. 46 | 47 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 48 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 49 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 50 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 51 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 52 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 53 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 54 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 55 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 56 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 57 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 58 | -------------------------------------------------------------------------------- /crates/generic_log_worker/src/obs/logs.rs: -------------------------------------------------------------------------------- 1 | use crate::obs::WshimData; 2 | use serde::Serialize; 3 | use std::{ 4 | collections::HashMap, 5 | sync::{Mutex, Once}, 6 | }; 7 | 8 | pub struct Logger { 9 | entries: Mutex>, 10 | } 11 | 12 | #[derive(Serialize)] 13 | struct LogEntry { 14 | message: String, 15 | level: String, 16 | #[serde(flatten)] 17 | fields: HashMap, 18 | } 19 | 20 | impl WshimData for &'static Logger { 21 | fn endpoint() -> &'static str { 22 | "log" 23 | } 24 | fn to_body(&self) -> Vec { 25 | let logs = std::mem::take(&mut *self.entries.lock().unwrap()); 26 | 27 | // schema 28 | // logs: { message: { message: string; ...fields } }[] 29 | serde_json::to_vec(&serde_json::json!({ 30 | "logs": logs 31 | .into_iter() 32 | .map(|log_entry| serde_json::json!({ "message": log_entry })) 33 | .collect::>(), 34 | })) 35 | .unwrap() 36 | } 37 | } 38 | 39 | pub static LOGGER: Logger = Logger { 40 | entries: Mutex::new(Vec::new()), 41 | }; 42 | 43 | pub fn init(level: Option<&str>) { 44 | static INIT: Once = Once::new(); 45 | INIT.call_once(|| { 46 | let level = level 47 | .and_then(|l| l.parse().ok()) 48 | .unwrap_or(log::Level::Info); 49 | log::set_max_level(level.to_level_filter()); 50 | log::set_logger(&LOGGER).unwrap(); 51 | }); 52 | } 53 | 54 | impl log::Log for Logger { 55 | fn enabled(&self, metadata: &log::Metadata) -> bool { 56 | metadata.level() <= log::max_level() 57 | } 58 | 59 | fn log(&self, record: &log::Record) { 60 | console_log::log(record); 61 | let mut fields = HashMap::new(); 62 | if let Some(module) = record.module_path() { 63 | if let Some(file) = record.file() { 64 | if let Some(line) = record.line() { 65 | fields.insert("location".to_owned(), format!("{module}::{file}:{line}")); 66 | } 67 | } 68 | } 69 | struct Visitor<'m>(&'m mut HashMap); 70 | impl<'kvs> log::kv::VisitSource<'kvs> for Visitor<'_> { 71 | fn visit_pair( 72 | &mut self, 73 | key: log::kv::Key<'kvs>, 74 | value: log::kv::Value<'kvs>, 75 | ) -> Result<(), log::kv::Error> { 76 | self.0.insert(key.as_str().to_owned(), value.to_string()); 77 | Ok(()) 78 | } 79 | } 80 | record 81 | .key_values() 82 | .visit(&mut Visitor(&mut fields)) 83 | .unwrap(); 84 | self.entries.lock().unwrap().push(LogEntry { 85 | message: format!("{}", record.args()), 86 | level: record.level().to_string(), 87 | fields, 88 | }); 89 | } 90 | 91 | fn flush(&self) { 92 | // flushing the logs is an async process so we can't call it from here. But that is okay, 93 | // this function is optional and not guaranteed to called anyway and we already flush at 94 | // the end of each request. 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2025 Cloudflare, Inc. All rights reserved. 2 | # SPDX-License-Identifier: BSD-3-Clause 3 | 4 | [workspace] 5 | # This is needed to avoid pulling in tokio features in wasm targets, due to new features in version 0.0.18 of the `worker` crate 6 | # See: https://doc.rust-lang.org/edition-guide/rust-2021/default-cargo-resolver.html#details 7 | resolver = "2" 8 | members = ["crates/*", "fuzz"] 9 | 10 | [workspace.package] 11 | version = "0.2.0" 12 | authors = ["Luke Valenta "] 13 | edition = "2021" 14 | license = "BSD-3-Clause" 15 | readme = "README.md" 16 | homepage = "https://github.com/cloudflare/azul" 17 | repository = "https://github.com/cloudflare/azul" 18 | description = "An implementation of the Static Certificate Transparency API on Cloudflare Workers" 19 | 20 | [profile.release] 21 | opt-level = "s" 22 | # Recommendations from https://developers.cloudflare.com/workers/languages/rust/#binary-size-wasm-opt: 23 | strip = true 24 | lto = true 25 | codegen-units = 1 26 | 27 | [profile.release-symbols] 28 | inherits = "release" 29 | opt-level = 3 30 | debug = 1 31 | 32 | [workspace.dependencies] 33 | anyhow = "1.0" 34 | base64 = "0.22" 35 | base64ct = "1.8.0" 36 | bitcode = { version = "0.6.6", features = ["serde"] } 37 | byteorder = "1.5" 38 | chrono = { version = "0.4", features = ["serde"] } 39 | console_error_panic_hook = "0.1.1" 40 | console_log = { version = "1.0" } 41 | criterion = { version = "0.5", features = ["html_reports"] } 42 | csv = "1.3.1" 43 | generic_log_worker = { path = "crates/generic_log_worker", version = "0.2.0" } 44 | der = "0.7.10" 45 | ed25519-dalek = { version = "2.1.1", features = ["pem"] } 46 | futures-executor = "0.3.31" 47 | futures-util = "0.3.31" 48 | getrandom = { version = "0.2", features = ["js"] } 49 | hex = "0.4" 50 | itertools = "0.14.0" 51 | jsonschema = "0.30" 52 | length_prefixed = { path = "crates/length_prefixed" } 53 | libfuzzer-sys = "0.4" 54 | log = { version = "0.4" } 55 | mtc_api = { version = "0.2.0", path = "crates/mtc_api" } 56 | p256 = { version = "0.13", features = ["ecdsa"] } 57 | parking_lot = "0.12" 58 | prometheus = "0.14" 59 | rand = "0.8.5" 60 | rand_core = "0.6.4" 61 | serde = { version = "1.0", features = ["derive"] } 62 | serde-wasm-bindgen = "0.6.5" 63 | serde_bytes = "0.11" 64 | serde_json = "1.0" 65 | serde_with = { version = "3.9.0", features = ["base64"] } 66 | sha2 = "0.11.0-rc.2" 67 | signature = "2.2.0" 68 | signed_note = { path = "crates/signed_note", version = "0.2.0" } 69 | static_ct_api = { path = "crates/static_ct_api", version = "0.2.0" } 70 | thiserror = "2.0" 71 | tlog_tiles = { path = "crates/tlog_tiles", version = "0.2.0" } 72 | tokio = { version = "1", features = ["sync"] } 73 | url = "2.2" 74 | worker = "0.6.6" 75 | x509-cert = "0.2.5" 76 | x509-verify = { version = "0.4.4", features = [ 77 | "md2", 78 | "md5", 79 | "sha1", 80 | "dsa", 81 | "rsa", 82 | "k256", 83 | "p192", 84 | "p224", 85 | "p256", 86 | "p384", 87 | "ecdsa", 88 | "ed25519", 89 | "x509", 90 | "pem", 91 | ] } 92 | x509_util = { path = "crates/x509_util" } 93 | 94 | [patch.crates-io] 95 | der = { git = "https://github.com/lukevalenta/formats", branch = "relative-oid-tag-v0.7.10" } 96 | -------------------------------------------------------------------------------- /crates/ct_worker/config.raio.json: -------------------------------------------------------------------------------- 1 | { 2 | "logging_level": "info", 3 | "logs": { 4 | "raio2025h2b": { 5 | "description": "Cloudflare 'Raio2025h2b' log", 6 | "submission_url": "https://ct.cloudflare.com/logs/raio2025h2b/", 7 | "monitoring_url": "https://raio2025h2b.ct.cloudflare.com/", 8 | "temporal_interval": { 9 | "start_inclusive": "2025-07-01T00:00:00Z", 10 | "end_exclusive": "2026-01-01T00:00:00Z" 11 | }, 12 | "max_sequence_skips": 1, 13 | "sequence_interval_millis": 750, 14 | "sequence_skip_threshold_millis": 250, 15 | "location_hint": "wnam" 16 | }, 17 | "raio2026h1a": { 18 | "description": "Cloudflare 'Raio2026h1a' log", 19 | "submission_url": "https://ct.cloudflare.com/logs/raio2026h1a/", 20 | "monitoring_url": "https://raio2026h1a.ct.cloudflare.com/", 21 | "temporal_interval": { 22 | "start_inclusive": "2026-01-01T00:00:00Z", 23 | "end_exclusive": "2026-07-01T00:00:00Z" 24 | }, 25 | "max_sequence_skips": 1, 26 | "sequence_interval_millis": 750, 27 | "sequence_skip_threshold_millis": 250, 28 | "location_hint": "wnam" 29 | }, 30 | "raio2026h2a": { 31 | "description": "Cloudflare 'Raio2026h2a' log", 32 | "submission_url": "https://ct.cloudflare.com/logs/raio2026h2a/", 33 | "monitoring_url": "https://raio2026h2a.ct.cloudflare.com/", 34 | "temporal_interval": { 35 | "start_inclusive": "2026-07-01T00:00:00Z", 36 | "end_exclusive": "2027-01-01T00:00:00Z" 37 | }, 38 | "max_sequence_skips": 1, 39 | "sequence_interval_millis": 750, 40 | "sequence_skip_threshold_millis": 250, 41 | "location_hint": "wnam" 42 | }, 43 | "raio2027h1a": { 44 | "description": "Cloudflare 'Raio2027h1a' log", 45 | "submission_url": "https://ct.cloudflare.com/logs/raio2027h1a/", 46 | "monitoring_url": "https://raio2027h1a.ct.cloudflare.com/", 47 | "temporal_interval": { 48 | "start_inclusive": "2027-01-01T00:00:00Z", 49 | "end_exclusive": "2027-07-01T00:00:00Z" 50 | }, 51 | "max_sequence_skips": 1, 52 | "sequence_interval_millis": 750, 53 | "sequence_skip_threshold_millis": 250, 54 | "location_hint": "wnam" 55 | }, 56 | "raio2027h2a": { 57 | "description": "Cloudflare 'Raio2027h2a' log", 58 | "submission_url": "https://ct.cloudflare.com/logs/raio2027h2a/", 59 | "monitoring_url": "https://raio2027h2a.ct.cloudflare.com/", 60 | "temporal_interval": { 61 | "start_inclusive": "2027-07-01T00:00:00Z", 62 | "end_exclusive": "2028-01-01T00:00:00Z" 63 | }, 64 | "max_sequence_skips": 1, 65 | "sequence_interval_millis": 750, 66 | "sequence_skip_threshold_millis": 250, 67 | "location_hint": "wnam" 68 | } 69 | } 70 | } -------------------------------------------------------------------------------- /crates/static_ct_api/tests/test-cert.pem: -------------------------------------------------------------------------------- 1 | Certificate: 2 | Data: 3 | Version: 3 (0x2) 4 | Serial Number: 6 (0x6) 5 | Signature Algorithm: sha1WithRSAEncryption 6 | Issuer: C=GB, O=Certificate Transparency CA, ST=Wales, L=Erw Wen 7 | Validity 8 | Not Before: Jun 1 00:00:00 2012 GMT 9 | Not After : Jun 1 00:00:00 2022 GMT 10 | Subject: C=GB, O=Certificate Transparency, ST=Wales, L=Erw Wen 11 | Subject Public Key Info: 12 | Public Key Algorithm: rsaEncryption 13 | Public-Key: (1024 bit) 14 | Modulus: 15 | 00:b1:fa:37:93:61:11:f8:79:2d:a2:08:1c:3f:e4: 16 | 19:25:00:85:31:dc:7f:2c:65:7b:d9:e1:de:47:04: 17 | 16:0b:4c:9f:19:d5:4a:da:44:70:40:4c:1c:51:34: 18 | 1b:8f:1f:75:38:dd:dd:28:d9:ac:a4:83:69:fc:56: 19 | 46:dd:cc:76:17:f8:16:8a:ae:5b:41:d4:33:31:fc: 20 | a2:da:df:c8:04:d5:72:08:94:90:61:f9:ee:f9:02: 21 | ca:47:ce:88:c6:44:e0:00:f0:6e:ee:cc:ab:dc:9d: 22 | d2:f6:8a:22:cc:b0:9d:c7:6e:0d:bc:73:52:77:65: 23 | b1:a3:7a:8c:67:62:53:dc:c1 24 | Exponent: 65537 (0x10001) 25 | X509v3 extensions: 26 | X509v3 Subject Key Identifier: 27 | 6A:0D:98:2A:3B:62:C4:4B:6D:2E:F4:E9:BB:7A:01:AA:9C:B7:98:E2 28 | X509v3 Authority Key Identifier: 29 | keyid:5F:9D:88:0D:C8:73:E6:54:D4:F8:0D:D8:E6:B0:C1:24:B4:47:C3:55 30 | DirName:/C=GB/O=Certificate Transparency CA/ST=Wales/L=Erw Wen 31 | serial:00 32 | X509v3 Basic Constraints: 33 | CA:FALSE 34 | Signature Algorithm: sha1WithRSAEncryption 35 | Signature Value: 36 | 17:1c:d8:4a:ac:41:4a:9a:03:0f:22:aa:c8:f6:88:b0:81:b2: 37 | 70:9b:84:8b:4e:55:11:40:6c:d7:07:fe:d0:28:59:7a:9f:ae: 38 | fc:2e:ee:29:78:d6:33:aa:ac:14:ed:32:35:19:7d:a8:7e:0f: 39 | 71:b8:87:5f:1a:c9:e7:8b:28:17:49:dd:ed:d0:07:e3:ec:f5: 40 | 06:45:f8:cb:f6:67:25:6c:d6:a1:64:7b:5e:13:20:3b:b8:58: 41 | 2d:e7:d6:69:6f:65:6d:1c:60:b9:5f:45:6b:7f:cf:33:85:71: 42 | 90:8f:1c:69:72:7d:24:c4:fc:cd:24:92:95:79:58:14:d1:da: 43 | c0:e6 44 | -----BEGIN CERTIFICATE----- 45 | MIICyjCCAjOgAwIBAgIBBjANBgkqhkiG9w0BAQUFADBVMQswCQYDVQQGEwJHQjEk 46 | MCIGA1UEChMbQ2VydGlmaWNhdGUgVHJhbnNwYXJlbmN5IENBMQ4wDAYDVQQIEwVX 47 | YWxlczEQMA4GA1UEBxMHRXJ3IFdlbjAeFw0xMjA2MDEwMDAwMDBaFw0yMjA2MDEw 48 | MDAwMDBaMFIxCzAJBgNVBAYTAkdCMSEwHwYDVQQKExhDZXJ0aWZpY2F0ZSBUcmFu 49 | c3BhcmVuY3kxDjAMBgNVBAgTBVdhbGVzMRAwDgYDVQQHEwdFcncgV2VuMIGfMA0G 50 | CSqGSIb3DQEBAQUAA4GNADCBiQKBgQCx+jeTYRH4eS2iCBw/5BklAIUx3H8sZXvZ 51 | 4d5HBBYLTJ8Z1UraRHBATBxRNBuPH3U43d0o2aykg2n8VkbdzHYX+BaKrltB1DMx 52 | /KLa38gE1XIIlJBh+e75AspHzojGROAA8G7uzKvcndL2iiLMsJ3Hbg28c1J3ZbGj 53 | eoxnYlPcwQIDAQABo4GsMIGpMB0GA1UdDgQWBBRqDZgqO2LES20u9Om7egGqnLeY 54 | 4jB9BgNVHSMEdjB0gBRfnYgNyHPmVNT4DdjmsMEktEfDVaFZpFcwVTELMAkGA1UE 55 | BhMCR0IxJDAiBgNVBAoTG0NlcnRpZmljYXRlIFRyYW5zcGFyZW5jeSBDQTEOMAwG 56 | A1UECBMFV2FsZXMxEDAOBgNVBAcTB0VydyBXZW6CAQAwCQYDVR0TBAIwADANBgkq 57 | hkiG9w0BAQUFAAOBgQAXHNhKrEFKmgMPIqrI9oiwgbJwm4SLTlURQGzXB/7QKFl6 58 | n678Lu4peNYzqqwU7TI1GX2ofg9xuIdfGsnniygXSd3t0Afj7PUGRfjL9mclbNah 59 | ZHteEyA7uFgt59Zpb2VtHGC5X0Vrf88zhXGQjxxpcn0kxPzNJJKVeVgU0drA5g== 60 | -----END CERTIFICATE----- 61 | -------------------------------------------------------------------------------- /crates/static_ct_api/tests/ca-cert.pem: -------------------------------------------------------------------------------- 1 | Certificate: 2 | Data: 3 | Version: 3 (0x2) 4 | Serial Number: 0 (0x0) 5 | Signature Algorithm: sha1WithRSAEncryption 6 | Issuer: C=GB, O=Certificate Transparency CA, ST=Wales, L=Erw Wen 7 | Validity 8 | Not Before: Jun 1 00:00:00 2012 GMT 9 | Not After : Jun 1 00:00:00 2022 GMT 10 | Subject: C=GB, O=Certificate Transparency CA, ST=Wales, L=Erw Wen 11 | Subject Public Key Info: 12 | Public Key Algorithm: rsaEncryption 13 | Public-Key: (1024 bit) 14 | Modulus: 15 | 00:d5:8a:68:53:62:10:a2:71:19:93:6e:77:83:21: 16 | 18:1c:2a:40:13:c6:d0:7b:8c:76:eb:91:57:d3:d0: 17 | fb:4b:3b:51:6e:ce:cb:d1:c9:8d:91:c5:2f:74:3f: 18 | ab:63:5d:55:09:9c:d1:3a:ba:f3:1a:e5:41:44:24: 19 | 51:a7:4c:78:16:f2:24:3c:f8:48:cf:28:31:cc:e6: 20 | 7b:a0:4a:5a:23:81:9f:3c:ba:37:e6:24:d9:c3:bd: 21 | b2:99:b8:39:dd:fe:26:31:d2:cb:3a:84:fc:7b:b2: 22 | b5:c5:2f:cf:c1:4f:ff:40:6f:5c:d4:46:69:cb:b2: 23 | f7:cf:df:86:fb:6a:b9:d1:b1 24 | Exponent: 65537 (0x10001) 25 | X509v3 extensions: 26 | X509v3 Subject Key Identifier: 27 | 5F:9D:88:0D:C8:73:E6:54:D4:F8:0D:D8:E6:B0:C1:24:B4:47:C3:55 28 | X509v3 Authority Key Identifier: 29 | keyid:5F:9D:88:0D:C8:73:E6:54:D4:F8:0D:D8:E6:B0:C1:24:B4:47:C3:55 30 | DirName:/C=GB/O=Certificate Transparency CA/ST=Wales/L=Erw Wen 31 | serial:00 32 | X509v3 Basic Constraints: 33 | CA:TRUE 34 | Signature Algorithm: sha1WithRSAEncryption 35 | Signature Value: 36 | 06:08:cc:4a:6d:64:f2:20:5e:14:6c:04:b2:76:f9:2b:0e:fa: 37 | 94:a5:da:f2:3a:fc:38:06:60:6d:39:90:d0:a1:ea:23:3d:40: 38 | 29:57:69:46:3b:04:66:61:e7:fa:1d:17:99:15:20:9a:ea:2e: 39 | 0a:77:51:76:41:12:27:d7:c0:03:07:c7:47:0e:61:58:4f:d7: 40 | 33:42:24:72:7f:51:d6:90:bc:47:a9:df:35:4d:b0:f6:eb:25: 41 | 95:5d:e1:89:3c:4d:d5:20:2b:24:a2:f3:e4:40:d2:74:b5:4e: 42 | 1b:d3:76:26:9c:a9:62:89:b7:6e:ca:a4:10:90:e1:4f:3b:0a: 43 | 94:2e 44 | -----BEGIN CERTIFICATE----- 45 | MIIC0DCCAjmgAwIBAgIBADANBgkqhkiG9w0BAQUFADBVMQswCQYDVQQGEwJHQjEk 46 | MCIGA1UEChMbQ2VydGlmaWNhdGUgVHJhbnNwYXJlbmN5IENBMQ4wDAYDVQQIEwVX 47 | YWxlczEQMA4GA1UEBxMHRXJ3IFdlbjAeFw0xMjA2MDEwMDAwMDBaFw0yMjA2MDEw 48 | MDAwMDBaMFUxCzAJBgNVBAYTAkdCMSQwIgYDVQQKExtDZXJ0aWZpY2F0ZSBUcmFu 49 | c3BhcmVuY3kgQ0ExDjAMBgNVBAgTBVdhbGVzMRAwDgYDVQQHEwdFcncgV2VuMIGf 50 | MA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDVimhTYhCicRmTbneDIRgcKkATxtB7 51 | jHbrkVfT0PtLO1FuzsvRyY2RxS90P6tjXVUJnNE6uvMa5UFEJFGnTHgW8iQ8+EjP 52 | KDHM5nugSlojgZ88ujfmJNnDvbKZuDnd/iYx0ss6hPx7srXFL8/BT/9Ab1zURmnL 53 | svfP34b7arnRsQIDAQABo4GvMIGsMB0GA1UdDgQWBBRfnYgNyHPmVNT4DdjmsMEk 54 | tEfDVTB9BgNVHSMEdjB0gBRfnYgNyHPmVNT4DdjmsMEktEfDVaFZpFcwVTELMAkG 55 | A1UEBhMCR0IxJDAiBgNVBAoTG0NlcnRpZmljYXRlIFRyYW5zcGFyZW5jeSBDQTEO 56 | MAwGA1UECBMFV2FsZXMxEDAOBgNVBAcTB0VydyBXZW6CAQAwDAYDVR0TBAUwAwEB 57 | /zANBgkqhkiG9w0BAQUFAAOBgQAGCMxKbWTyIF4UbASydvkrDvqUpdryOvw4BmBt 58 | OZDQoeojPUApV2lGOwRmYef6HReZFSCa6i4Kd1F2QRIn18ADB8dHDmFYT9czQiRy 59 | f1HWkLxHqd81TbD26yWVXeGJPE3VICskovPkQNJ0tU4b03YmnKliibduyqQQkOFP 60 | OwqULg== 61 | -----END CERTIFICATE----- 62 | -------------------------------------------------------------------------------- /crates/static_ct_api/tests/subleaf.chain: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICAjCCAaigAwIBAgIFAN6tvu8wCgYIKoZIzj0EAwIwdTELMAkGA1UEBhMCR0Ix 3 | DzANBgNVBAgTBkxvbmRvbjEPMA0GA1UEBxMGTG9uZG9uMQ8wDQYDVQQKEwZHb29n 4 | bGUxDDAKBgNVBAsTA0VuZzElMCMGA1UEAxMcRmFrZVN1YkludGVybWVkaWF0ZUF1 5 | dGhvcml0eTAeFw0xODA0MDYxMTAxMDFaFw0yNTA1MTkxMTAxMDFaMFcxCzAJBgNV 6 | BAYTAkdCMQ8wDQYDVQQIDAZMb25kb24xDzANBgNVBAoMBkdvb2dsZTEMMAoGA1UE 7 | CwwDRW5nMRgwFgYDVQQDDA9zdWJsZWFmLmNzci5wZW0wWTATBgcqhkjOPQIBBggq 8 | hkjOPQMBBwNCAATrN05SRZxG1ai4xe1YuTAppnCKaaAmXJ4vbrhrI2yE4UY6mDaC 9 | RKWKF4tBgjL0LeAIW34HOFL8R1YoJ5vtYIuso0MwQTAdBgNVHQ4EFgQUP7IvQfwR 10 | mtONpoWAhIaufnMuaV0wDwYDVR0jBAgwBoAECgsMDTAPBgNVHQ8BAf8EBQMDB/mA 11 | MAoGCCqGSM49BAMCA0gAMEUCIQDlNWeY4ia5oU7JhTPoDubhuORdfqgVwr0MFENf 12 | NLDQ/gIgOPLRom6vbqZC1EuT234zn1csFnSkDp9EFOS1z7URJPk= 13 | -----END CERTIFICATE----- 14 | -----BEGIN CERTIFICATE----- 15 | MIICPDCCAeKgAwIBAgIEEhISEjAKBggqhkjOPQQDAjByMQswCQYDVQQGEwJHQjEP 16 | MA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xDzANBgNVBAoTBkdvb2ds 17 | ZTEMMAoGA1UECxMDRW5nMSIwIAYDVQQDExlGYWtlSW50ZXJtZWRpYXRlQXV0aG9y 18 | aXR5MB4XDTE4MDQwNjExMDEwMVoXDTI4MDIxMzExMDEwMVowdTELMAkGA1UEBhMC 19 | R0IxDzANBgNVBAgTBkxvbmRvbjEPMA0GA1UEBxMGTG9uZG9uMQ8wDQYDVQQKEwZH 20 | b29nbGUxDDAKBgNVBAsTA0VuZzElMCMGA1UEAxMcRmFrZVN1YkludGVybWVkaWF0 21 | ZUF1dGhvcml0eTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABGViTbXYtpdRZ0f4 22 | QhdHo39C9s8zCtWAmWsnHub3d0OlhJxazVV1uHTgJGDE7Euff4vyH0D2j7xnjsmV 23 | GGenafOjYzBhMA0GA1UdDgQGBAQKCwwNMA8GA1UdIwQIMAaABAoLDA0wDwYDVR0T 24 | AQH/BAUwAwEB/zAPBgNVHQ8BAf8EBQMDB/+AMB0GA1UdJQQWMBQGCCsGAQUFBwMB 25 | BggrBgEFBQcDAjAKBggqhkjOPQQDAgNIADBFAiBRmlMEQHyEenjH9eft8K/9Aj0s 26 | Q5TMzI8domdMGSTktwIhAKIJWSuJsn9C0QXKFAxlOVJsjlgIIuLuyUvrrbKKlz45 27 | -----END CERTIFICATE----- 28 | -----BEGIN CERTIFICATE----- 29 | MIICODCCAd6gAwIBAgIEAQEBATAKBggqhkjOPQQDAjBxMQswCQYDVQQGEwJHQjEP 30 | MA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xDzANBgNVBAoTBkdvb2ds 31 | ZTEMMAoGA1UECxMDRW5nMSEwHwYDVQQDExhGYWtlQ2VydGlmaWNhdGVBdXRob3Jp 32 | dHkwHhcNMTgwNDA2MTEwMTAxWhcNMjgwMjEzMTEwMTAxWjByMQswCQYDVQQGEwJH 33 | QjEPMA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xDzANBgNVBAoTBkdv 34 | b2dsZTEMMAoGA1UECxMDRW5nMSIwIAYDVQQDExlGYWtlSW50ZXJtZWRpYXRlQXV0 35 | aG9yaXR5MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEEgjivMuD8A9GCkFMrYcP 36 | xWS7aFP5VKV4Bm46cibTzCMuQZVBgSxgkZ3nMbPFo0fif2f84yrcum4ntUHVX3uV 37 | bKNjMGEwDQYDVR0OBAYEBAoLDA0wDwYDVR0jBAgwBoAEAQIDBDAPBgNVHRMBAf8E 38 | BTADAQH/MA8GA1UdDwEB/wQFAwMH/4AwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsG 39 | AQUFBwMCMAoGCCqGSM49BAMCA0gAMEUCIEEDVMxuFamEthOVmELENUvfWIzwxKft 40 | 5vO/TNsFG/rMAiEAtKzSTGMqtqKeR5Nnj9vOSPWYnj2R6Dmdvcw3fbcMvyQ= 41 | -----END CERTIFICATE----- 42 | -----BEGIN CERTIFICATE----- 43 | MIICHDCCAcGgAwIBAgIEBAbK/jAKBggqhkjOPQQDAjBxMQswCQYDVQQGEwJHQjEP 44 | MA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xDzANBgNVBAoTBkdvb2ds 45 | ZTEMMAoGA1UECxMDRW5nMSEwHwYDVQQDExhGYWtlQ2VydGlmaWNhdGVBdXRob3Jp 46 | dHkwHhcNMTYxMjA3MTUxMzM2WhcNMjYxMjA1MTUxMzM2WjBxMQswCQYDVQQGEwJH 47 | QjEPMA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xDzANBgNVBAoTBkdv 48 | b2dsZTEMMAoGA1UECxMDRW5nMSEwHwYDVQQDExhGYWtlQ2VydGlmaWNhdGVBdXRo 49 | b3JpdHkwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATy0wfvft/PzvT0Clu8nj/L 50 | HP0MRtyF+8H207K6HVHxmGxIqBVGRWPK39bJrM9gO8dO3bjSFqugCSQdCWYeTeuh 51 | o0cwRTANBgNVHQ4EBgQEAQIDBDAPBgNVHSMECDAGgAQBAgMEMBIGA1UdEwEB/wQI 52 | MAYBAf8CAQowDwYDVR0PAQH/BAUDAwf/gDAKBggqhkjOPQQDAgNJADBGAiEApihJ 53 | OUNvgORDph47qolewiVgKuE5vVVDrk1cqabvrGUCIQDJxQjGWZO0hnCla1QrW/wM 54 | iGuwIwcrxwwn3octloDVVg== 55 | -----END CERTIFICATE----- 56 | -------------------------------------------------------------------------------- /crates/static_ct_api/tests/subleaf.misordered.chain: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICAjCCAaigAwIBAgIFAN6tvu8wCgYIKoZIzj0EAwIwdTELMAkGA1UEBhMCR0Ix 3 | DzANBgNVBAgTBkxvbmRvbjEPMA0GA1UEBxMGTG9uZG9uMQ8wDQYDVQQKEwZHb29n 4 | bGUxDDAKBgNVBAsTA0VuZzElMCMGA1UEAxMcRmFrZVN1YkludGVybWVkaWF0ZUF1 5 | dGhvcml0eTAeFw0xODA0MDYxMTAxMDFaFw0yNTA1MTkxMTAxMDFaMFcxCzAJBgNV 6 | BAYTAkdCMQ8wDQYDVQQIDAZMb25kb24xDzANBgNVBAoMBkdvb2dsZTEMMAoGA1UE 7 | CwwDRW5nMRgwFgYDVQQDDA9zdWJsZWFmLmNzci5wZW0wWTATBgcqhkjOPQIBBggq 8 | hkjOPQMBBwNCAATrN05SRZxG1ai4xe1YuTAppnCKaaAmXJ4vbrhrI2yE4UY6mDaC 9 | RKWKF4tBgjL0LeAIW34HOFL8R1YoJ5vtYIuso0MwQTAdBgNVHQ4EFgQUP7IvQfwR 10 | mtONpoWAhIaufnMuaV0wDwYDVR0jBAgwBoAECgsMDTAPBgNVHQ8BAf8EBQMDB/mA 11 | MAoGCCqGSM49BAMCA0gAMEUCIQDlNWeY4ia5oU7JhTPoDubhuORdfqgVwr0MFENf 12 | NLDQ/gIgOPLRom6vbqZC1EuT234zn1csFnSkDp9EFOS1z7URJPk= 13 | -----END CERTIFICATE----- 14 | -----BEGIN CERTIFICATE----- 15 | MIICODCCAd6gAwIBAgIEAQEBATAKBggqhkjOPQQDAjBxMQswCQYDVQQGEwJHQjEP 16 | MA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xDzANBgNVBAoTBkdvb2ds 17 | ZTEMMAoGA1UECxMDRW5nMSEwHwYDVQQDExhGYWtlQ2VydGlmaWNhdGVBdXRob3Jp 18 | dHkwHhcNMTgwNDA2MTEwMTAxWhcNMjgwMjEzMTEwMTAxWjByMQswCQYDVQQGEwJH 19 | QjEPMA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xDzANBgNVBAoTBkdv 20 | b2dsZTEMMAoGA1UECxMDRW5nMSIwIAYDVQQDExlGYWtlSW50ZXJtZWRpYXRlQXV0 21 | aG9yaXR5MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEEgjivMuD8A9GCkFMrYcP 22 | xWS7aFP5VKV4Bm46cibTzCMuQZVBgSxgkZ3nMbPFo0fif2f84yrcum4ntUHVX3uV 23 | bKNjMGEwDQYDVR0OBAYEBAoLDA0wDwYDVR0jBAgwBoAEAQIDBDAPBgNVHRMBAf8E 24 | BTADAQH/MA8GA1UdDwEB/wQFAwMH/4AwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsG 25 | AQUFBwMCMAoGCCqGSM49BAMCA0gAMEUCIEEDVMxuFamEthOVmELENUvfWIzwxKft 26 | 5vO/TNsFG/rMAiEAtKzSTGMqtqKeR5Nnj9vOSPWYnj2R6Dmdvcw3fbcMvyQ= 27 | -----END CERTIFICATE----- 28 | -----BEGIN CERTIFICATE----- 29 | MIICPDCCAeKgAwIBAgIEEhISEjAKBggqhkjOPQQDAjByMQswCQYDVQQGEwJHQjEP 30 | MA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xDzANBgNVBAoTBkdvb2ds 31 | ZTEMMAoGA1UECxMDRW5nMSIwIAYDVQQDExlGYWtlSW50ZXJtZWRpYXRlQXV0aG9y 32 | aXR5MB4XDTE4MDQwNjExMDEwMVoXDTI4MDIxMzExMDEwMVowdTELMAkGA1UEBhMC 33 | R0IxDzANBgNVBAgTBkxvbmRvbjEPMA0GA1UEBxMGTG9uZG9uMQ8wDQYDVQQKEwZH 34 | b29nbGUxDDAKBgNVBAsTA0VuZzElMCMGA1UEAxMcRmFrZVN1YkludGVybWVkaWF0 35 | ZUF1dGhvcml0eTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABGViTbXYtpdRZ0f4 36 | QhdHo39C9s8zCtWAmWsnHub3d0OlhJxazVV1uHTgJGDE7Euff4vyH0D2j7xnjsmV 37 | GGenafOjYzBhMA0GA1UdDgQGBAQKCwwNMA8GA1UdIwQIMAaABAoLDA0wDwYDVR0T 38 | AQH/BAUwAwEB/zAPBgNVHQ8BAf8EBQMDB/+AMB0GA1UdJQQWMBQGCCsGAQUFBwMB 39 | BggrBgEFBQcDAjAKBggqhkjOPQQDAgNIADBFAiBRmlMEQHyEenjH9eft8K/9Aj0s 40 | Q5TMzI8domdMGSTktwIhAKIJWSuJsn9C0QXKFAxlOVJsjlgIIuLuyUvrrbKKlz45 41 | -----END CERTIFICATE----- 42 | -----BEGIN CERTIFICATE----- 43 | MIICHDCCAcGgAwIBAgIEBAbK/jAKBggqhkjOPQQDAjBxMQswCQYDVQQGEwJHQjEP 44 | MA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xDzANBgNVBAoTBkdvb2ds 45 | ZTEMMAoGA1UECxMDRW5nMSEwHwYDVQQDExhGYWtlQ2VydGlmaWNhdGVBdXRob3Jp 46 | dHkwHhcNMTYxMjA3MTUxMzM2WhcNMjYxMjA1MTUxMzM2WjBxMQswCQYDVQQGEwJH 47 | QjEPMA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xDzANBgNVBAoTBkdv 48 | b2dsZTEMMAoGA1UECxMDRW5nMSEwHwYDVQQDExhGYWtlQ2VydGlmaWNhdGVBdXRo 49 | b3JpdHkwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATy0wfvft/PzvT0Clu8nj/L 50 | HP0MRtyF+8H207K6HVHxmGxIqBVGRWPK39bJrM9gO8dO3bjSFqugCSQdCWYeTeuh 51 | o0cwRTANBgNVHQ4EBgQEAQIDBDAPBgNVHSMECDAGgAQBAgMEMBIGA1UdEwEB/wQI 52 | MAYBAf8CAQowDwYDVR0PAQH/BAUDAwf/gDAKBggqhkjOPQQDAgNJADBGAiEApihJ 53 | OUNvgORDph47qolewiVgKuE5vVVDrk1cqabvrGUCIQDJxQjGWZO0hnCla1QrW/wM 54 | iGuwIwcrxwwn3octloDVVg== 55 | -----END CERTIFICATE----- 56 | -------------------------------------------------------------------------------- /crates/static_ct_api/tests/precert-valid.pem: -------------------------------------------------------------------------------- 1 | Certificate: 2 | Data: 3 | Version: 3 (0x2) 4 | Serial Number: 7 (0x7) 5 | Signature Algorithm: sha1WithRSAEncryption 6 | Issuer: C=GB, O=Certificate Transparency CA, ST=Wales, L=Erw Wen 7 | Validity 8 | Not Before: Jun 1 00:00:00 2012 GMT 9 | Not After : Jun 1 00:00:00 2022 GMT 10 | Subject: C=GB, O=Certificate Transparency, ST=Wales, L=Erw Wen 11 | Subject Public Key Info: 12 | Public Key Algorithm: rsaEncryption 13 | Public-Key: (1024 bit) 14 | Modulus: 15 | 00:be:ef:98:e7:c2:68:77:ae:38:5f:75:32:5a:0c: 16 | 1d:32:9b:ed:f1:8f:aa:f4:d7:96:bf:04:7e:b7:e1: 17 | ce:15:c9:5b:a2:f8:0e:e4:58:bd:7d:b8:6f:8a:4b: 18 | 25:21:91:a7:9b:d7:00:c3:8e:9c:03:89:b4:5c:d4: 19 | dc:9a:12:0a:b2:1e:0c:b4:1c:d0:e7:28:05:a4:10: 20 | cd:9c:5b:db:5d:49:27:72:6d:af:17:10:f6:01:87: 21 | 37:7e:a2:5b:1a:1e:39:ee:d0:b8:81:19:dc:15:4d: 22 | c6:8f:7d:a8:e3:0c:af:15:8a:33:e6:c9:50:9f:4a: 23 | 05:b0:14:09:ff:5d:d8:7e:b5 24 | Exponent: 65537 (0x10001) 25 | X509v3 extensions: 26 | X509v3 Subject Key Identifier: 27 | 20:31:54:1A:F2:5C:05:FF:D8:65:8B:68:43:79:4F:5E:90:36:F7:B4 28 | X509v3 Authority Key Identifier: 29 | keyid:5F:9D:88:0D:C8:73:E6:54:D4:F8:0D:D8:E6:B0:C1:24:B4:47:C3:55 30 | DirName:/C=GB/O=Certificate Transparency CA/ST=Wales/L=Erw Wen 31 | serial:00 32 | X509v3 Basic Constraints: 33 | CA:FALSE 34 | CT Precertificate Poison: critical 35 | NULL 36 | Signature Algorithm: sha1WithRSAEncryption 37 | Signature Value: 38 | 02:a1:c3:9e:01:5a:f5:4d:ff:02:3c:33:60:87:5f:ff:34:37: 39 | 55:2f:1f:09:01:bd:c2:54:31:5f:33:72:b7:23:fb:15:fb:ce: 40 | cc:4d:f4:71:a0:ce:4d:8c:54:65:5d:84:87:97:fb:28:1e:3d: 41 | fa:bb:46:2d:2c:68:4b:05:6f:ea:7b:63:b4:70:ff:16:6e:32: 42 | d4:46:06:35:b3:d2:bc:6d:a8:24:9b:26:30:e7:1f:c3:4f:08: 43 | f2:3d:d4:ee:22:8f:8f:74:f6:3d:78:63:11:dd:0a:58:11:40: 44 | 5f:90:6c:ca:2c:2d:3e:eb:fc:81:99:64:eb:d8:cf:7c:08:86: 45 | 3f:be 46 | -----BEGIN CERTIFICATE----- 47 | MIIC3zCCAkigAwIBAgIBBzANBgkqhkiG9w0BAQUFADBVMQswCQYDVQQGEwJHQjEk 48 | MCIGA1UEChMbQ2VydGlmaWNhdGUgVHJhbnNwYXJlbmN5IENBMQ4wDAYDVQQIEwVX 49 | YWxlczEQMA4GA1UEBxMHRXJ3IFdlbjAeFw0xMjA2MDEwMDAwMDBaFw0yMjA2MDEw 50 | MDAwMDBaMFIxCzAJBgNVBAYTAkdCMSEwHwYDVQQKExhDZXJ0aWZpY2F0ZSBUcmFu 51 | c3BhcmVuY3kxDjAMBgNVBAgTBVdhbGVzMRAwDgYDVQQHEwdFcncgV2VuMIGfMA0G 52 | CSqGSIb3DQEBAQUAA4GNADCBiQKBgQC+75jnwmh3rjhfdTJaDB0ym+3xj6r015a/ 53 | BH634c4VyVui+A7kWL19uG+KSyUhkaeb1wDDjpwDibRc1NyaEgqyHgy0HNDnKAWk 54 | EM2cW9tdSSdyba8XEPYBhzd+olsaHjnu0LiBGdwVTcaPfajjDK8VijPmyVCfSgWw 55 | FAn/Xdh+tQIDAQABo4HBMIG+MB0GA1UdDgQWBBQgMVQa8lwF/9hli2hDeU9ekDb3 56 | tDB9BgNVHSMEdjB0gBRfnYgNyHPmVNT4DdjmsMEktEfDVaFZpFcwVTELMAkGA1UE 57 | BhMCR0IxJDAiBgNVBAoTG0NlcnRpZmljYXRlIFRyYW5zcGFyZW5jeSBDQTEOMAwG 58 | A1UECBMFV2FsZXMxEDAOBgNVBAcTB0VydyBXZW6CAQAwCQYDVR0TBAIwADATBgor 59 | BgEEAdZ5AgQDAQH/BAIFADANBgkqhkiG9w0BAQUFAAOBgQACocOeAVr1Tf8CPDNg 60 | h1//NDdVLx8JAb3CVDFfM3K3I/sV+87MTfRxoM5NjFRlXYSHl/soHj36u0YtLGhL 61 | BW/qe2O0cP8WbjLURgY1s9K8bagkmyYw5x/DTwjyPdTuIo+PdPY9eGMR3QpYEUBf 62 | kGzKLC0+6/yBmWTr2M98CIY/vg== 63 | -----END CERTIFICATE----- 64 | -------------------------------------------------------------------------------- /crates/mtc_api/src/relative_oid.rs: -------------------------------------------------------------------------------- 1 | use crate::MtcError; 2 | use std::str::FromStr; 3 | 4 | /// ASN.1 `RELATIVE OID`. 5 | /// 6 | /// TODO upstream this to the `der` crate. 7 | #[derive(Clone)] 8 | pub struct RelativeOid { 9 | ber: Vec, 10 | arcs: Vec, 11 | } 12 | 13 | impl RelativeOid { 14 | fn from_arcs(arcs: &[u32]) -> Result { 15 | let mut ber = Vec::new(); 16 | for arc in arcs { 17 | for j in (0..=4).rev() { 18 | #[allow(clippy::cast_possible_truncation)] 19 | let cur = (arc >> (j * 7)) as u8; 20 | 21 | if cur != 0 || j == 0 { 22 | let mut to_write = cur & 0x7f; // lower 7 bits 23 | 24 | if j != 0 { 25 | to_write |= 0x80; 26 | } 27 | ber.push(to_write); 28 | } 29 | } 30 | } 31 | if ber.len() > 255 { 32 | return Err(MtcError::Dynamic("invalid relative OID".into())); 33 | } 34 | Ok(Self { 35 | ber, 36 | arcs: arcs.to_vec(), 37 | }) 38 | } 39 | 40 | /// Returns the DER-encoded content bytes. 41 | pub fn as_bytes(&self) -> &[u8] { 42 | &self.ber 43 | } 44 | } 45 | 46 | impl std::fmt::Display for RelativeOid { 47 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 48 | for arc in self.arcs.iter().take(self.arcs.len() - 1) { 49 | write!(f, "{}.", arc)?; 50 | } 51 | write!(f, "{}", self.arcs[self.arcs.len() - 1]) 52 | } 53 | } 54 | 55 | impl FromStr for RelativeOid { 56 | type Err = MtcError; 57 | /// Parse the [`RelativeOid`] from a decimal-dotted string. 58 | fn from_str(s: &str) -> Result { 59 | let parts = s.split('.'); 60 | let mut arcs = Vec::new(); 61 | for part in parts { 62 | let i = part.parse::()?; 63 | arcs.push(i); 64 | } 65 | Self::from_arcs(&arcs) 66 | } 67 | } 68 | 69 | #[cfg(test)] 70 | mod tests { 71 | 72 | use der::{Any, Encode, Tag}; 73 | 74 | use super::*; 75 | 76 | #[test] 77 | fn encode_tagged() { 78 | let relative_oid = RelativeOid::from_str("13335.2").unwrap(); 79 | let any = Any::new(Tag::RelativeOid, relative_oid.as_bytes()).unwrap(); 80 | assert_eq!(any.to_der().unwrap(), b"\x0d\x03\xe8\x17\x02"); 81 | } 82 | 83 | #[test] 84 | fn encode_string() { 85 | let relative_oid = RelativeOid::from_str("13335.2").unwrap(); 86 | assert_eq!(relative_oid.to_string(), "13335.2"); 87 | } 88 | 89 | #[test] 90 | fn decode_string_encode_bytes() { 91 | struct TestCase { 92 | s: &'static str, 93 | b: &'static [u8], 94 | } 95 | for TestCase { s, b } in [ 96 | TestCase { 97 | s: "237", 98 | b: &[129, 109], 99 | }, 100 | TestCase { 101 | s: "1.2.3.4", 102 | b: &[1, 2, 3, 4], 103 | }, 104 | TestCase { 105 | s: "13335.2", 106 | b: &[232, 23, 2], 107 | }, 108 | TestCase { 109 | s: "44363.48.10", 110 | b: &[130, 218, 75, 48, 10], 111 | }, 112 | ] { 113 | let relative_oid = RelativeOid::from_str(s).unwrap(); 114 | assert_eq!(relative_oid.as_bytes(), b); 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /crates/mtc_worker/build.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2025 Cloudflare, Inc. 2 | // Licensed under the BSD-3-Clause license found in the LICENSE file or at https://opensource.org/licenses/BSD-3-Clause 3 | 4 | // Build script to include per-environment configuration and trusted roots. 5 | 6 | use config::AppConfig; 7 | use der::asn1::Utf8StringRef; 8 | use der::{asn1::SetOfVec, Any, Tag}; 9 | use mtc_api::ID_RDNA_TRUSTANCHOR_ID; 10 | use std::env; 11 | use std::fs; 12 | use url::Url; 13 | use x509_cert::{ 14 | attr::AttributeTypeAndValue, 15 | name::{RdnSequence, RelativeDistinguishedName}, 16 | }; 17 | 18 | fn main() { 19 | let env = env::var("DEPLOY_ENV").unwrap_or_else(|_| "dev".to_string()); 20 | let config_file = &format!("config.{env}.json"); 21 | let config_contents = &fs::read_to_string(config_file).unwrap_or_else(|e| { 22 | panic!("failed to read config file '{config_file}': {e}"); 23 | }); 24 | 25 | // Validate the config json against the schema. 26 | let json = serde_json::from_str(config_contents).unwrap_or_else(|e| { 27 | panic!("failed to deserialize JSON config '{config_file}': {e}"); 28 | }); 29 | let schema = serde_json::from_str(include_str!("config.schema.json")).unwrap_or_else(|e| { 30 | panic!("failed to deserialize JSON schema 'config.schema.json': {e}"); 31 | }); 32 | jsonschema::validate(&schema, &json).unwrap_or_else(|e| { 33 | panic!("config '{config_file}' does not match schema 'config.schema.json': {e}"); 34 | }); 35 | 36 | // Validate the config parameters. 37 | let conf = serde_json::from_str::(config_contents).unwrap_or_else(|e| { 38 | panic!("failed to deserialize JSON config '{config_file}': {e}"); 39 | }); 40 | for (name, params) in conf.logs { 41 | // Make sure we can create the RDN sequence for the issuer log ID. 42 | let _ = RdnSequence::from(vec![RelativeDistinguishedName( 43 | SetOfVec::from_iter([AttributeTypeAndValue { 44 | oid: ID_RDNA_TRUSTANCHOR_ID, 45 | value: Any::new( 46 | Tag::Utf8String, 47 | Utf8StringRef::new(¶ms.log_id).unwrap().as_bytes(), 48 | ) 49 | .unwrap(), 50 | }]) 51 | .unwrap(), 52 | )]); 53 | 54 | // Valid location hints: https://developers.cloudflare.com/durable-objects/reference/data-location/#supported-locations-1 55 | if let Some(location) = ¶ms.location_hint { 56 | assert!( 57 | ["wnam", "enam", "sam", "weur", "eeur", "apac", "oc", "afr", "me",] 58 | .contains(&location.as_str()), 59 | "{name} invalid location hint: {location}" 60 | ); 61 | } 62 | 63 | check_url(¶ms.submission_url); 64 | if !params.monitoring_url.is_empty() { 65 | check_url(¶ms.monitoring_url); 66 | } 67 | } 68 | 69 | // Copy to OUT_DIR. 70 | let out_dir = env::var("OUT_DIR").unwrap(); 71 | fs::copy(config_file, format!("{out_dir}/config.json")).expect("failed to copy config file"); 72 | 73 | println!("cargo::rerun-if-env-changed=DEPLOY_ENV"); 74 | println!("cargo::rerun-if-changed=config.schema.json"); 75 | println!("cargo::rerun-if-changed={config_file}"); 76 | } 77 | 78 | // Validate the URL prefix according to https://datatracker.ietf.org/doc/html/rfc6962#section-4. 79 | // "The prefix can include a path as well as a server name and a port." 80 | fn check_url(s: &str) { 81 | let u = Url::parse(s).unwrap(); 82 | assert!(["http", "https"].contains(&u.scheme()), "invalid scheme"); 83 | assert!(u.domain().is_some(), "invalid domain"); 84 | assert_eq!( 85 | u.as_str(), 86 | &format!("{}{}", u.origin().ascii_serialization(), u.path()), 87 | "invalid URL components" 88 | ); 89 | } 90 | -------------------------------------------------------------------------------- /crates/tlog_tiles/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2025 Cloudflare, Inc. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of the copyright holder nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | ======================================================================== 30 | 31 | Copyright 2023 The Sunlight Authors 32 | 33 | Permission to use, copy, modify, and/or distribute this software for any 34 | purpose with or without fee is hereby granted, provided that the above 35 | copyright notice and this permission notice appear in all copies. 36 | 37 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 38 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 39 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 40 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 41 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 42 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 43 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 44 | 45 | ======================================================================== 46 | 47 | Copyright 2009 The Go Authors. 48 | 49 | Redistribution and use in source and binary forms, with or without 50 | modification, are permitted provided that the following conditions are 51 | met: 52 | 53 | * Redistributions of source code must retain the above copyright 54 | notice, this list of conditions and the following disclaimer. 55 | * Redistributions in binary form must reproduce the above 56 | copyright notice, this list of conditions and the following disclaimer 57 | in the documentation and/or other materials provided with the 58 | distribution. 59 | * Neither the name of Google LLC nor the names of its 60 | contributors may be used to endorse or promote products derived from 61 | this software without specific prior written permission. 62 | 63 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 64 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 65 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 66 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 67 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 68 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 69 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 70 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 71 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 72 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 73 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 74 | -------------------------------------------------------------------------------- /crates/static_ct_api/tests/cloudflare.pem: -------------------------------------------------------------------------------- 1 | Certificate chain 2 | 0 s:CN=26d54cff.sni.cloudflaressl.com 3 | i:C=US, O=Google Trust Services, CN=WE1 4 | a:PKEY: EC, (prime256v1); sigalg: ecdsa-with-SHA256 5 | v:NotBefore: Sep 10 18:11:56 2025 GMT; NotAfter: Dec 9 19:11:41 2025 GMT 6 | -----BEGIN CERTIFICATE----- 7 | MIIDyTCCA3CgAwIBAgIQQOWg2Nc9XhAT1toRzArhVzAKBggqhkjOPQQDAjA7MQsw 8 | CQYDVQQGEwJVUzEeMBwGA1UEChMVR29vZ2xlIFRydXN0IFNlcnZpY2VzMQwwCgYD 9 | VQQDEwNXRTEwHhcNMjUwOTEwMTgxMTU2WhcNMjUxMjA5MTkxMTQxWjApMScwJQYD 10 | VQQDEx4yNmQ1NGNmZi5zbmkuY2xvdWRmbGFyZXNzbC5jb20wWTATBgcqhkjOPQIB 11 | BggqhkjOPQMBBwNCAATucLtnF8oOVucAgsWa82hdrSAkYJwy19iZTdlBeqTZQUvE 12 | 3VabmY4/xxWQ3womrnpQE6F2dul51ilAalRqTLGOo4ICZjCCAmIwDgYDVR0PAQH/ 13 | BAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwHQYDVR0O 14 | BBYEFPgeK75wrmRIIvqJyzwf/y2QzmHdMB8GA1UdIwQYMBaAFJB3kjVnxP+ozKnm 15 | e9mAeXvMk/k4MF4GCCsGAQUFBwEBBFIwUDAnBggrBgEFBQcwAYYbaHR0cDovL28u 16 | cGtpLmdvb2cvcy93ZTEvUU9VMCUGCCsGAQUFBzAChhlodHRwOi8vaS5wa2kuZ29v 17 | Zy93ZTEuY3J0MDkGA1UdEQQyMDCCHjI2ZDU0Y2ZmLnNuaS5jbG91ZGZsYXJlc3Ns 18 | LmNvbYIOY2xvdWRmbGFyZS5jb20wEwYDVR0gBAwwCjAIBgZngQwBAgEwNgYDVR0f 19 | BC8wLTAroCmgJ4YlaHR0cDovL2MucGtpLmdvb2cvd2UxL1h3N2s5OTVCdlZnLmNy 20 | bDCCAQMGCisGAQQB1nkCBAIEgfQEgfEA7wB1AN3cyjSV1+EWBeeVMvrHn/g9HFDf 21 | 2wA6FBJ2Ciysu8gqAAABmTUKsOAAAAQDAEYwRAIgSbCq102Ac0iSU6uTmAw1JcUj 22 | uLjCUKbHfX1jUi+4E0gCIDc2CklQ/04aH+En9ItnhzQ/zbKLEMkkkPshMYyQsLPx 23 | AHYAzPsPaoVxCWX+lZtTzumyfCLphVwNl422qX5UwP5MDbAAAAGZNQqw9AAABAMA 24 | RzBFAiBHdDICfuFVofCodDiC+VQ39Nw+EQVrohHSg7o8qyTPugIhAKh0/D8fcasn 25 | 8KaxEDLhxlQilBzQW9gP/iOlL4LasCAKMAoGCCqGSM49BAMCA0cAMEQCIFCuCbXl 26 | cBlfWavWj7O0lN4aoxYVIsbbtF5VJqCEJfv5AiBLkdTa3bls3f8PbF0PZiec3QnQ 27 | 51nWenFitQt3t0xyUA== 28 | -----END CERTIFICATE----- 29 | 1 s:C=US, O=Google Trust Services, CN=WE1 30 | i:C=US, O=Google Trust Services LLC, CN=GTS Root R4 31 | a:PKEY: EC, (prime256v1); sigalg: ecdsa-with-SHA384 32 | v:NotBefore: Dec 13 09:00:00 2023 GMT; NotAfter: Feb 20 14:00:00 2029 GMT 33 | -----BEGIN CERTIFICATE----- 34 | MIICnzCCAiWgAwIBAgIQf/MZd5csIkp2FV0TttaF4zAKBggqhkjOPQQDAzBHMQsw 35 | CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU 36 | MBIGA1UEAxMLR1RTIFJvb3QgUjQwHhcNMjMxMjEzMDkwMDAwWhcNMjkwMjIwMTQw 37 | MDAwWjA7MQswCQYDVQQGEwJVUzEeMBwGA1UEChMVR29vZ2xlIFRydXN0IFNlcnZp 38 | Y2VzMQwwCgYDVQQDEwNXRTEwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARvzTr+ 39 | Z1dHTCEDhUDCR127WEcPQMFcF4XGGTfn1XzthkubgdnXGhOlCgP4mMTG6J7/EFmP 40 | LCaY9eYmJbsPAvpWo4H+MIH7MA4GA1UdDwEB/wQEAwIBhjAdBgNVHSUEFjAUBggr 41 | BgEFBQcDAQYIKwYBBQUHAwIwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQU 42 | kHeSNWfE/6jMqeZ72YB5e8yT+TgwHwYDVR0jBBgwFoAUgEzW63T/STaj1dj8tT7F 43 | avCUHYwwNAYIKwYBBQUHAQEEKDAmMCQGCCsGAQUFBzAChhhodHRwOi8vaS5wa2ku 44 | Z29vZy9yNC5jcnQwKwYDVR0fBCQwIjAgoB6gHIYaaHR0cDovL2MucGtpLmdvb2cv 45 | ci9yNC5jcmwwEwYDVR0gBAwwCjAIBgZngQwBAgEwCgYIKoZIzj0EAwMDaAAwZQIx 46 | AOcCq1HW90OVznX+0RGU1cxAQXomvtgM8zItPZCuFQ8jSBJSjz5keROv9aYsAm5V 47 | sQIwJonMaAFi54mrfhfoFNZEfuNMSQ6/bIBiNLiyoX46FohQvKeIoJ99cx7sUkFN 48 | 7uJW 49 | -----END CERTIFICATE----- 50 | 2 s:C=US, O=Google Trust Services LLC, CN=GTS Root R4 51 | i:C=BE, O=GlobalSign nv-sa, OU=Root CA, CN=GlobalSign Root CA 52 | a:PKEY: EC, (secp384r1); sigalg: sha256WithRSAEncryption 53 | v:NotBefore: Nov 15 03:43:21 2023 GMT; NotAfter: Jan 28 00:00:42 2028 GMT 54 | -----BEGIN CERTIFICATE----- 55 | MIIDejCCAmKgAwIBAgIQf+UwvzMTQ77dghYQST2KGzANBgkqhkiG9w0BAQsFADBX 56 | MQswCQYDVQQGEwJCRTEZMBcGA1UEChMQR2xvYmFsU2lnbiBudi1zYTEQMA4GA1UE 57 | CxMHUm9vdCBDQTEbMBkGA1UEAxMSR2xvYmFsU2lnbiBSb290IENBMB4XDTIzMTEx 58 | NTAzNDMyMVoXDTI4MDEyODAwMDA0MlowRzELMAkGA1UEBhMCVVMxIjAgBgNVBAoT 59 | GUdvb2dsZSBUcnVzdCBTZXJ2aWNlcyBMTEMxFDASBgNVBAMTC0dUUyBSb290IFI0 60 | MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE83Rzp2iLYK5DuDXFgTB7S0md+8Fhzube 61 | Rr1r1WEYNa5A3XP3iZEwWus87oV8okB2O6nGuEfYKueSkWpz6bFyOZ8pn6KY019e 62 | WIZlD6GEZQbR3IvJx3PIjGov5cSr0R2Ko4H/MIH8MA4GA1UdDwEB/wQEAwIBhjAd 63 | BgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDwYDVR0TAQH/BAUwAwEB/zAd 64 | BgNVHQ4EFgQUgEzW63T/STaj1dj8tT7FavCUHYwwHwYDVR0jBBgwFoAUYHtmGkUN 65 | l8qJUC99BM00qP/8/UswNgYIKwYBBQUHAQEEKjAoMCYGCCsGAQUFBzAChhpodHRw 66 | Oi8vaS5wa2kuZ29vZy9nc3IxLmNydDAtBgNVHR8EJjAkMCKgIKAehhxodHRwOi8v 67 | Yy5wa2kuZ29vZy9yL2dzcjEuY3JsMBMGA1UdIAQMMAowCAYGZ4EMAQIBMA0GCSqG 68 | SIb3DQEBCwUAA4IBAQAYQrsPBtYDh5bjP2OBDwmkoWhIDDkic574y04tfzHpn+cJ 69 | odI2D4SseesQ6bDrarZ7C30ddLibZatoKiws3UL9xnELz4ct92vID24FfVbiI1hY 70 | +SW6FoVHkNeWIP0GCbaM4C6uVdF5dTUsMVs/ZbzNnIdCp5Gxmx5ejvEau8otR/Cs 71 | kGN+hr/W5GvT1tMBjgWKZ1i4//emhA1JG1BbPzoLJQvyEotc03lXjTaCzv8mEbep 72 | 8RqZ7a2CPsgRbuvTPBwcOMBBmuFeU88+FSBX6+7iP0il8b4Z0QFqIwwMHfs/L6K1 73 | vepuoxtGzi4CZ68zJpiq1UvSqTbFJjtbD4seiMHl 74 | -----END CERTIFICATE----- 75 | -------------------------------------------------------------------------------- /crates/tlog_tiles/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Ported from "mod" (https://pkg.go.dev/golang.org/x/mod) 2 | // Copyright 2009 The Go Authors 3 | // Licensed under the BSD-3-Clause license found in the LICENSE file or at https://opensource.org/licenses/BSD-3-Clause 4 | // 5 | // This ports code from the original Go project "mod" and adapts it to Rust idioms. 6 | // 7 | // Modifications and Rust implementation Copyright (c) 2025 Cloudflare, Inc. 8 | // Licensed under the BSD-3-Clause license found in the LICENSE file or at https://opensource.org/licenses/BSD-3-Clause 9 | 10 | //! # tlog tiles 11 | //! 12 | //! Implementation of the [C2SP tlog-tiles](https://c2sp.org/tlog-tiles) and [C2SP checkpoint](https://c2sp.org/tlog-checkpoint) specifications. 13 | //! 14 | //! This file contains code ported from the original project [tlog](https://pkg.go.dev/golang.org/x/mod/sumdb/tlog). 15 | //! 16 | //! References: 17 | //! - [ct_test.go](https://cs.opensource.google/go/x/mod/+/refs/tags/v0.21.0:sumdb/tlog/ct_test.go) 18 | 19 | pub mod checkpoint; 20 | pub mod cosignature_v1; 21 | pub mod entries; 22 | pub mod tile; 23 | pub mod tlog; 24 | 25 | pub use checkpoint::*; 26 | pub use cosignature_v1::*; 27 | pub use entries::*; 28 | pub use tile::*; 29 | pub use tlog::*; 30 | 31 | #[cfg(test)] 32 | mod tests { 33 | use super::*; 34 | use base64::prelude::*; 35 | use serde::{Deserialize, Deserializer}; 36 | use std::fs::File; 37 | use std::io::Read; 38 | use std::path::PathBuf; 39 | use url::form_urlencoded; 40 | 41 | #[derive(Deserialize)] 42 | struct CtTree { 43 | #[serde(rename = "tree_size")] 44 | size: u64, 45 | #[serde(rename = "sha256_root_hash")] 46 | hash: Hash, 47 | } 48 | 49 | #[derive(Deserialize)] 50 | struct CtEntries { 51 | entries: Vec, 52 | } 53 | 54 | #[derive(Deserialize)] 55 | struct CtEntry { 56 | #[serde(rename = "leaf_input", deserialize_with = "from_base64")] 57 | data: Vec, 58 | } 59 | 60 | fn from_base64<'de, D: Deserializer<'de>>(d: D) -> Result, D::Error> { 61 | let base64 = String::deserialize(d)?; 62 | BASE64_STANDARD 63 | .decode(&base64) 64 | .map_err(serde::de::Error::custom) 65 | } 66 | 67 | #[derive(Deserialize)] 68 | struct CtInclusionProof { 69 | #[serde(rename = "audit_path")] 70 | proof: Vec, 71 | } 72 | 73 | #[derive(Deserialize)] 74 | struct CtConsistencyProof { 75 | consistency: Vec, 76 | } 77 | 78 | // Returns vendored HTTP responses from CT logs for use in tests. 79 | fn http_get Deserialize<'de>>(url: &str) -> T { 80 | let basename = &url 81 | .rsplit_once('/') 82 | .unwrap() 83 | .1 84 | .to_string() 85 | .replace(|c| !char::is_ascii_alphanumeric(&c), "-"); 86 | 87 | let path: PathBuf = [env!("CARGO_MANIFEST_DIR"), "tests/http_get", basename] 88 | .iter() 89 | .collect(); 90 | let mut file = File::open(path).expect("Unable to open file"); 91 | let mut body = String::new(); 92 | 93 | file.read_to_string(&mut body).expect("Unable to read file"); 94 | 95 | serde_json::from_str(&body).expect("File contained invalid JSON") 96 | } 97 | 98 | #[test] 99 | fn test_certificate_transparency() -> Result<(), TlogError> { 100 | let root: CtTree = http_get("http://ct.googleapis.com/logs/argon2020/ct/v1/get-sth"); 101 | 102 | let leaf: CtEntries = http_get( 103 | "http://ct.googleapis.com/logs/argon2020/ct/v1/get-entries?start=10000&end=10000", 104 | ); 105 | 106 | let hash = record_hash(&leaf.entries[0].data); 107 | 108 | let url = format!( 109 | "http://ct.googleapis.com/logs/argon2020/ct/v1/get-proof-by-hash?tree_size={}&hash={}", 110 | root.size, 111 | form_urlencoded::byte_serialize(hash.to_string().as_bytes()).collect::() 112 | ); 113 | let rp: CtInclusionProof = http_get(&url); 114 | 115 | verify_inclusion_proof(&rp.proof, root.size, root.hash, 10000, hash)?; 116 | 117 | let url = format!( 118 | "http://ct.googleapis.com/logs/argon2020/ct/v1/get-sth-consistency?first=3654490&second={}", 119 | root.size); 120 | let tp: CtConsistencyProof = http_get(&url); 121 | 122 | let oh = Hash::parse_hash("AuIZ5V6sDUj1vn3Y1K85oOaQ7y+FJJKtyRTl1edIKBQ=")?; 123 | verify_consistency_proof(&tp.consistency, root.size, root.hash, 3_654_490, oh)?; 124 | 125 | Ok(()) 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /crates/static_ct_api/tests/fake-intermediate-cert.pem: -------------------------------------------------------------------------------- 1 | Certificate: 2 | Data: 3 | Version: 3 (0x2) 4 | Serial Number: 4792439526061490155 (0x42822a5b866fbfeb) 5 | Signature Algorithm: sha256WithRSAEncryption 6 | Issuer: C=GB, ST=London, L=London, O=Google, OU=Eng, CN=FakeCertificateAuthority 7 | Validity 8 | Not Before: May 13 14:26:44 2016 GMT 9 | Not After : Jul 12 14:26:44 2019 GMT 10 | Subject: C=GB, ST=London, L=London, O=Google, OU=Eng, CN=FakeIntermediateAuthority 11 | Subject Public Key Info: 12 | Public Key Algorithm: rsaEncryption 13 | Public-Key: (2048 bit) 14 | Modulus: 15 | 00:ca:a4:0c:7a:6d:e9:26:22:d4:67:19:c8:29:40: 16 | c6:bd:cb:44:39:e7:fa:84:01:1d:b3:04:15:48:37: 17 | fa:55:d5:98:4b:2a:ff:14:0e:d6:ce:27:6b:29:d5: 18 | e8:8d:39:eb:be:97:be:53:21:d2:a3:f2:27:ef:46: 19 | 68:1c:6f:84:77:85:b4:68:78:7a:d4:3d:50:49:89: 20 | 8f:9e:6b:4a:ce:74:c0:0f:c8:68:38:7e:ae:82:ae: 21 | 91:0c:6d:87:24:c4:48:f3:e0:8e:a8:3e:0c:f8:e1: 22 | e8:7f:a1:dd:29:f4:d0:eb:3a:b2:38:77:0f:1a:4e: 23 | a6:14:c4:b1:db:5b:ed:f9:a4:f0:9d:1e:d8:a8:d0: 24 | 40:28:d6:fc:69:44:0b:37:37:e7:d6:fd:29:b0:70: 25 | 36:47:00:89:81:5a:c9:51:cf:2d:a0:80:76:fc:d8: 26 | 57:28:87:81:71:e4:10:4b:39:16:51:f2:85:ed:a0: 27 | 34:41:bf:f3:52:28:f1:cd:c4:dc:31:f9:26:14:fd: 28 | b6:65:51:2f:76:e9:82:94:fc:2a:be:1a:a0:58:54: 29 | d8:b5:de:e3:96:08:07:50:3d:0e:35:26:e5:3a:c7: 30 | 67:e8:8d:b6:f1:34:61:f6:0c:47:d2:fd:0b:51:cf: 31 | a6:99:97:d4:26:a1:12:14:dd:a2:0e:e5:68:4d:75: 32 | f7:c5 33 | Exponent: 65537 (0x10001) 34 | X509v3 extensions: 35 | X509v3 Authority Key Identifier: 36 | 01:02:03:04 37 | X509v3 Basic Constraints: critical 38 | CA:TRUE, pathlen:0 39 | X509v3 Key Usage: critical 40 | Digital Signature, Non Repudiation, Key Encipherment, Data Encipherment, Key Agreement, Certificate Sign, CRL Sign, Encipher Only, Decipher Only 41 | Signature Algorithm: sha256WithRSAEncryption 42 | Signature Value: 43 | 01:e2:3a:0c:00:bc:4c:e1:ac:d3:10:54:0c:fc:6b:e4:ac:c8: 44 | c2:00:05:74:39:3f:c5:9b:25:e1:e3:90:88:a9:13:8f:b9:66: 45 | 99:2b:65:55:ea:f6:9f:30:39:d9:18:9c:e1:f1:e1:63:62:f4: 46 | f5:46:41:b2:c6:f4:8b:9f:87:d7:e9:93:c7:32:c9:15:83:8b: 47 | e5:76:d3:f0:8d:36:d6:b0:32:ad:c2:95:5d:dd:58:2f:7c:4e: 48 | 3e:16:5f:f0:57:0c:27:98:da:32:b8:8d:81:95:f9:db:38:dc: 49 | 76:15:d1:3a:01:9a:fb:eb:71:ca:bf:53:bc:d8:30:61:5c:42: 50 | 22:81:0a:5c:f9:6d:31:3e:18:cb:eb:65:67:0e:e4:0f:cb:87: 51 | 7f:22:d9:84:85:d6:2f:12:7c:35:67:00:e0:65:02:06:66:96: 52 | 57:21:78:7a:46:b1:67:d2:9d:db:88:96:55:2f:4e:c4:6f:10: 53 | 8b:1a:6a:a7:d5:2e:5e:50:a5:15:c1:3a:af:2d:6e:32:bc:e7: 54 | fd:a0:e9:e6:ab:d6:8c:4f:84:9d:70:f6:17:6c:f9:64:c5:5e: 55 | 49:87:91:6b:ca:25:e6:d8:d7:7b:77:39:f4:a3:03:28:5a:45: 56 | 2b:7c:85:dc:c3:cc:74:c5:c2:33:e3:1d:3f:21:e9:d5:3b:fe: 57 | 13:1d:91:48 58 | -----BEGIN CERTIFICATE----- 59 | MIIDnTCCAoWgAwIBAgIIQoIqW4Zvv+swDQYJKoZIhvcNAQELBQAwcTELMAkGA1UE 60 | BhMCR0IxDzANBgNVBAgMBkxvbmRvbjEPMA0GA1UEBwwGTG9uZG9uMQ8wDQYDVQQK 61 | DAZHb29nbGUxDDAKBgNVBAsMA0VuZzEhMB8GA1UEAwwYRmFrZUNlcnRpZmljYXRl 62 | QXV0aG9yaXR5MB4XDTE2MDUxMzE0MjY0NFoXDTE5MDcxMjE0MjY0NFowcjELMAkG 63 | A1UEBhMCR0IxDzANBgNVBAgMBkxvbmRvbjEPMA0GA1UEBwwGTG9uZG9uMQ8wDQYD 64 | VQQKDAZHb29nbGUxDDAKBgNVBAsMA0VuZzEiMCAGA1UEAwwZRmFrZUludGVybWVk 65 | aWF0ZUF1dGhvcml0eTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMqk 66 | DHpt6SYi1GcZyClAxr3LRDnn+oQBHbMEFUg3+lXVmEsq/xQO1s4naynV6I05676X 67 | vlMh0qPyJ+9GaBxvhHeFtGh4etQ9UEmJj55rSs50wA/IaDh+roKukQxthyTESPPg 68 | jqg+DPjh6H+h3Sn00Os6sjh3DxpOphTEsdtb7fmk8J0e2KjQQCjW/GlECzc359b9 69 | KbBwNkcAiYFayVHPLaCAdvzYVyiHgXHkEEs5FlHyhe2gNEG/81Io8c3E3DH5JhT9 70 | tmVRL3bpgpT8Kr4aoFhU2LXe45YIB1A9DjUm5TrHZ+iNtvE0YfYMR9L9C1HPppmX 71 | 1CahEhTdog7laE1198UCAwEAAaM4MDYwDwYDVR0jBAgwBoAEAQIDBDASBgNVHRMB 72 | Af8ECDAGAQH/AgEAMA8GA1UdDwEB/wQFAwMH/4AwDQYJKoZIhvcNAQELBQADggEB 73 | AAHiOgwAvEzhrNMQVAz8a+SsyMIABXQ5P8WbJeHjkIipE4+5ZpkrZVXq9p8wOdkY 74 | nOHx4WNi9PVGQbLG9Iufh9fpk8cyyRWDi+V20/CNNtawMq3ClV3dWC98Tj4WX/BX 75 | DCeY2jK4jYGV+ds43HYV0ToBmvvrccq/U7zYMGFcQiKBClz5bTE+GMvrZWcO5A/L 76 | h38i2YSF1i8SfDVnAOBlAgZmllcheHpGsWfSnduIllUvTsRvEIsaaqfVLl5QpRXB 77 | Oq8tbjK85/2g6ear1oxPhJ1w9hds+WTFXkmHkWvKJebY13t3OfSjAyhaRSt8hdzD 78 | zHTFwjPjHT8h6dU7/hMdkUg= 79 | -----END CERTIFICATE----- 80 | -------------------------------------------------------------------------------- /crates/static_ct_api/tests/fake-intermediate-with-mismatching-sig-alg.pem: -------------------------------------------------------------------------------- 1 | Certificate: 2 | Data: 3 | Version: 3 (0x2) 4 | Serial Number: 4792439526061490155 (0x42822a5b866fbfeb) 5 | Signature Algorithm: sha256WithRSAEncryption 6 | Issuer: C=GB, ST=London, L=London, O=Google, OU=Eng, CN=FakeCertificateAuthority 7 | Validity 8 | Not Before: May 13 14:26:44 2016 GMT 9 | Not After : Jul 12 14:26:44 2019 GMT 10 | Subject: C=GB, ST=London, L=London, O=Google, OU=Eng, CN=FakeIntermediateAuthority 11 | Subject Public Key Info: 12 | Public Key Algorithm: rsaEncryption 13 | Public-Key: (2048 bit) 14 | Modulus: 15 | 00:ca:a4:0c:7a:6d:e9:26:22:d4:67:19:c8:29:40: 16 | c6:bd:cb:44:39:e7:fa:84:01:1d:b3:04:15:48:37: 17 | fa:55:d5:98:4b:2a:ff:14:0e:d6:ce:27:6b:29:d5: 18 | e8:8d:39:eb:be:97:be:53:21:d2:a3:f2:27:ef:46: 19 | 68:1c:6f:84:77:85:b4:68:78:7a:d4:3d:50:49:89: 20 | 8f:9e:6b:4a:ce:74:c0:0f:c8:68:38:7e:ae:82:ae: 21 | 91:0c:6d:87:24:c4:48:f3:e0:8e:a8:3e:0c:f8:e1: 22 | e8:7f:a1:dd:29:f4:d0:eb:3a:b2:38:77:0f:1a:4e: 23 | a6:14:c4:b1:db:5b:ed:f9:a4:f0:9d:1e:d8:a8:d0: 24 | 40:28:d6:fc:69:44:0b:37:37:e7:d6:fd:29:b0:70: 25 | 36:47:00:89:81:5a:c9:51:cf:2d:a0:80:76:fc:d8: 26 | 57:28:87:81:71:e4:10:4b:39:16:51:f2:85:ed:a0: 27 | 34:41:bf:f3:52:28:f1:cd:c4:dc:31:f9:26:14:fd: 28 | b6:65:51:2f:76:e9:82:94:fc:2a:be:1a:a0:58:54: 29 | d8:b5:de:e3:96:08:07:50:3d:0e:35:26:e5:3a:c7: 30 | 67:e8:8d:b6:f1:34:61:f6:0c:47:d2:fd:0b:51:cf: 31 | a6:99:97:d4:26:a1:12:14:dd:a2:0e:e5:68:4d:75: 32 | f7:c5 33 | Exponent: 65537 (0x10001) 34 | X509v3 extensions: 35 | X509v3 Authority Key Identifier: 36 | 01:02:03:04 37 | X509v3 Basic Constraints: critical 38 | CA:TRUE, pathlen:0 39 | X509v3 Key Usage: critical 40 | Digital Signature, Non Repudiation, Key Encipherment, Data Encipherment, Key Agreement, Certificate Sign, CRL Sign, Encipher Only, Decipher Only 41 | Signature Algorithm: ecdsa-with-SHA256 42 | Signature Value: 43 | 01:e2:3a:0c:00:bc:4c:e1:ac:d3:10:54:0c:fc:6b:e4:ac:c8: 44 | c2:00:05:74:39:3f:c5:9b:25:e1:e3:90:88:a9:13:8f:b9:66: 45 | 99:2b:65:55:ea:f6:9f:30:39:d9:18:9c:e1:f1:e1:63:62:f4: 46 | f5:46:41:b2:c6:f4:8b:9f:87:d7:e9:93:c7:32:c9:15:83:8b: 47 | e5:76:d3:f0:8d:36:d6:b0:32:ad:c2:95:5d:dd:58:2f:7c:4e: 48 | 3e:16:5f:f0:57:0c:27:98:da:32:b8:8d:81:95:f9:db:38:dc: 49 | 76:15:d1:3a:01:9a:fb:eb:71:ca:bf:53:bc:d8:30:61:5c:42: 50 | 22:81:0a:5c:f9:6d:31:3e:18:cb:eb:65:67:0e:e4:0f:cb:87: 51 | 7f:22:d9:84:85:d6:2f:12:7c:35:67:00:e0:65:02:06:66:96: 52 | 57:21:78:7a:46:b1:67:d2:9d:db:88:96:55:2f:4e:c4:6f:10: 53 | 8b:1a:6a:a7:d5:2e:5e:50:a5:15:c1:3a:af:2d:6e:32:bc:e7: 54 | fd:a0:e9:e6:ab:d6:8c:4f:84:9d:70:f6:17:6c:f9:64:c5:5e: 55 | 49:87:91:6b:ca:25:e6:d8:d7:7b:77:39:f4:a3:03:28:5a:45: 56 | 2b:7c:85:dc:c3:cc:74:c5:c2:33:e3:1d:3f:21:e9:d5:3b:fe: 57 | 13:1d:91:48 58 | -----BEGIN CERTIFICATE----- 59 | MIIDmjCCAoWgAwIBAgIIQoIqW4Zvv+swDQYJKoZIhvcNAQELBQAwcTELMAkGA1UE 60 | BhMCR0IxDzANBgNVBAgMBkxvbmRvbjEPMA0GA1UEBwwGTG9uZG9uMQ8wDQYDVQQK 61 | DAZHb29nbGUxDDAKBgNVBAsMA0VuZzEhMB8GA1UEAwwYRmFrZUNlcnRpZmljYXRl 62 | QXV0aG9yaXR5MB4XDTE2MDUxMzE0MjY0NFoXDTE5MDcxMjE0MjY0NFowcjELMAkG 63 | A1UEBhMCR0IxDzANBgNVBAgMBkxvbmRvbjEPMA0GA1UEBwwGTG9uZG9uMQ8wDQYD 64 | VQQKDAZHb29nbGUxDDAKBgNVBAsMA0VuZzEiMCAGA1UEAwwZRmFrZUludGVybWVk 65 | aWF0ZUF1dGhvcml0eTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMqk 66 | DHpt6SYi1GcZyClAxr3LRDnn+oQBHbMEFUg3+lXVmEsq/xQO1s4naynV6I05676X 67 | vlMh0qPyJ+9GaBxvhHeFtGh4etQ9UEmJj55rSs50wA/IaDh+roKukQxthyTESPPg 68 | jqg+DPjh6H+h3Sn00Os6sjh3DxpOphTEsdtb7fmk8J0e2KjQQCjW/GlECzc359b9 69 | KbBwNkcAiYFayVHPLaCAdvzYVyiHgXHkEEs5FlHyhe2gNEG/81Io8c3E3DH5JhT9 70 | tmVRL3bpgpT8Kr4aoFhU2LXe45YIB1A9DjUm5TrHZ+iNtvE0YfYMR9L9C1HPppmX 71 | 1CahEhTdog7laE1198UCAwEAAaM4MDYwDwYDVR0jBAgwBoAEAQIDBDASBgNVHRMB 72 | Af8ECDAGAQH/AgEAMA8GA1UdDwEB/wQFAwMH/4AwCgYIKoZIzj0EAwIDggEBAAHi 73 | OgwAvEzhrNMQVAz8a+SsyMIABXQ5P8WbJeHjkIipE4+5ZpkrZVXq9p8wOdkYnOHx 74 | 4WNi9PVGQbLG9Iufh9fpk8cyyRWDi+V20/CNNtawMq3ClV3dWC98Tj4WX/BXDCeY 75 | 2jK4jYGV+ds43HYV0ToBmvvrccq/U7zYMGFcQiKBClz5bTE+GMvrZWcO5A/Lh38i 76 | 2YSF1i8SfDVnAOBlAgZmllcheHpGsWfSnduIllUvTsRvEIsaaqfVLl5QpRXBOq8t 77 | bjK85/2g6ear1oxPhJ1w9hds+WTFXkmHkWvKJebY13t3OfSjAyhaRSt8hdzDzHTF 78 | wjPjHT8h6dU7/hMdkUg= 79 | -----END CERTIFICATE----- 80 | -------------------------------------------------------------------------------- /crates/static_ct_api/tests/fake-ca-cert.pem: -------------------------------------------------------------------------------- 1 | Certificate: 2 | Data: 3 | Version: 3 (0x2) 4 | Serial Number: 5 | b6:31:d2:ac:21:ab:65:20 6 | Signature Algorithm: sha256WithRSAEncryption 7 | Issuer: C=GB, ST=London, L=London, O=Google, OU=Eng, CN=FakeCertificateAuthority 8 | Validity 9 | Not Before: Jul 11 12:23:26 2016 GMT 10 | Not After : Jul 11 12:23:26 2017 GMT 11 | Subject: C=GB, ST=London, L=London, O=Google, OU=Eng, CN=FakeCertificateAuthority 12 | Subject Public Key Info: 13 | Public Key Algorithm: rsaEncryption 14 | Public-Key: (2048 bit) 15 | Modulus: 16 | 00:a5:41:9a:7a:2d:98:a3:b5:78:6f:15:21:db:0c: 17 | c1:0e:a1:f8:26:f5:b3:b2:67:85:dc:a1:e6:b7:83: 18 | 6d:da:63:da:d0:f6:a3:ff:bc:43:f5:2b:9f:00:19: 19 | 6e:6b:60:4b:43:20:6e:e2:cb:2e:b6:65:ed:9b:dc: 20 | 80:c3:e1:5a:96:af:60:78:0e:0e:fb:8f:ea:3e:3d: 21 | c9:67:8f:a4:57:1c:ba:e4:f3:37:a9:2f:dd:11:9d: 22 | 10:5d:e5:d6:ef:d4:3b:06:d9:34:43:42:bb:bb:be: 23 | 43:40:2b:e3:b6:d1:b5:6c:58:12:34:96:14:d4:fc: 24 | 49:79:c5:26:8c:24:7d:b3:12:f5:f6:3e:b7:41:46: 25 | 6b:6d:3a:41:fd:7c:e3:b5:fc:96:6c:c6:cc:ad:8d: 26 | 48:09:73:44:64:ea:4f:17:1d:0a:4b:14:5a:19:07: 27 | 4a:32:0f:41:2e:e4:85:bd:a1:e1:9b:de:63:7c:3b: 28 | bc:ec:aa:93:2a:0b:a8:c7:24:34:54:42:38:a5:d1: 29 | 0c:c4:f9:9e:7c:69:42:71:77:d7:95:aa:bb:13:3d: 30 | f3:cc:c7:5d:b3:fd:76:25:25:e3:da:14:0e:59:81: 31 | e8:2c:58:e8:09:29:7d:22:02:91:95:81:eb:55:6f: 32 | 2f:17:b9:af:4a:f3:84:8b:24:6e:ea:14:6b:bb:90: 33 | 84:35 34 | Exponent: 65537 (0x10001) 35 | X509v3 extensions: 36 | X509v3 Subject Key Identifier: 37 | 01:02:03:04 38 | X509v3 Authority Key Identifier: 39 | 01:02:03:04 40 | X509v3 Basic Constraints: critical 41 | CA:TRUE, pathlen:10 42 | X509v3 Key Usage: critical 43 | Digital Signature, Non Repudiation, Key Encipherment, Data Encipherment, Key Agreement, Certificate Sign, CRL Sign, Encipher Only, Decipher Only 44 | Signature Algorithm: sha256WithRSAEncryption 45 | Signature Value: 46 | 92:be:33:eb:d5:d4:32:e7:9e:4e:65:2a:e8:3f:67:b8:f4:d7: 47 | 34:ab:95:11:6a:5d:ba:fd:57:9b:94:6e:8d:20:be:fb:7a:e1: 48 | 49:ca:39:ea:92:d3:81:5a:b1:87:a3:9f:50:a4:e0:1e:11:de: 49 | c4:d1:07:a1:ca:d1:97:1a:92:bd:73:9a:11:ec:6a:9a:52:11: 50 | 2d:40:e1:3b:4f:3c:1f:81:3f:4c:ab:6a:02:84:4f:8b:18:36: 51 | 7a:cc:5c:a9:0e:25:2b:cd:57:53:88:d9:eb:82:b1:ce:62:76: 52 | 56:d4:23:9e:01:b3:6d:2b:49:ea:d4:3a:c2:f5:76:a7:b3:2d: 53 | 24:97:6f:b4:1c:74:6b:95:85:f6:b5:41:56:82:3c:ed:be:96: 54 | 1e:5e:6a:2d:7b:f7:fd:7d:6e:3f:fb:c2:ec:61:b3:7c:7f:3b: 55 | f5:9c:64:61:5f:02:93:87:cd:81:f9:7e:53:3e:c1:f5:79:85: 56 | f4:41:87:c7:ca:bd:af:ab:2b:a4:aa:a8:1d:2c:50:ad:23:8f: 57 | db:13:1d:71:8a:85:bd:ac:59:6c:c4:53:c5:71:0c:90:91:f3: 58 | 0b:41:ef:da:6e:27:bb:09:57:9c:97:b9:d7:fc:20:96:c5:75: 59 | 96:ce:2e:6c:a8:b6:6e:b0:4d:0f:3e:01:95:ea:8b:cd:ae:47: 60 | d0:d9:01:b7 61 | -----BEGIN CERTIFICATE----- 62 | MIIDrDCCApSgAwIBAgIJALYx0qwhq2UgMA0GCSqGSIb3DQEBCwUAMHExCzAJBgNV 63 | BAYTAkdCMQ8wDQYDVQQIDAZMb25kb24xDzANBgNVBAcMBkxvbmRvbjEPMA0GA1UE 64 | CgwGR29vZ2xlMQwwCgYDVQQLDANFbmcxITAfBgNVBAMMGEZha2VDZXJ0aWZpY2F0 65 | ZUF1dGhvcml0eTAeFw0xNjA3MTExMjIzMjZaFw0xNzA3MTExMjIzMjZaMHExCzAJ 66 | BgNVBAYTAkdCMQ8wDQYDVQQIDAZMb25kb24xDzANBgNVBAcMBkxvbmRvbjEPMA0G 67 | A1UECgwGR29vZ2xlMQwwCgYDVQQLDANFbmcxITAfBgNVBAMMGEZha2VDZXJ0aWZp 68 | Y2F0ZUF1dGhvcml0eTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKVB 69 | mnotmKO1eG8VIdsMwQ6h+Cb1s7Jnhdyh5reDbdpj2tD2o/+8Q/UrnwAZbmtgS0Mg 70 | buLLLrZl7ZvcgMPhWpavYHgODvuP6j49yWePpFccuuTzN6kv3RGdEF3l1u/UOwbZ 71 | NENCu7u+Q0Ar47bRtWxYEjSWFNT8SXnFJowkfbMS9fY+t0FGa206Qf1847X8lmzG 72 | zK2NSAlzRGTqTxcdCksUWhkHSjIPQS7khb2h4ZveY3w7vOyqkyoLqMckNFRCOKXR 73 | DMT5nnxpQnF315WquxM988zHXbP9diUl49oUDlmB6CxY6AkpfSICkZWB61VvLxe5 74 | r0rzhIskbuoUa7uQhDUCAwEAAaNHMEUwDQYDVR0OBAYEBAECAwQwDwYDVR0jBAgw 75 | BoAEAQIDBDASBgNVHRMBAf8ECDAGAQH/AgEKMA8GA1UdDwEB/wQFAwMH/4AwDQYJ 76 | KoZIhvcNAQELBQADggEBAJK+M+vV1DLnnk5lKug/Z7j01zSrlRFqXbr9V5uUbo0g 77 | vvt64UnKOeqS04FasYejn1Ck4B4R3sTRB6HK0Zcakr1zmhHsappSES1A4TtPPB+B 78 | P0yragKET4sYNnrMXKkOJSvNV1OI2euCsc5idlbUI54Bs20rSerUOsL1dqezLSSX 79 | b7QcdGuVhfa1QVaCPO2+lh5eai179/19bj/7wuxhs3x/O/WcZGFfApOHzYH5flM+ 80 | wfV5hfRBh8fKva+rK6SqqB0sUK0jj9sTHXGKhb2sWWzEU8VxDJCR8wtB79puJ7sJ 81 | V5yXudf8IJbFdZbOLmyotm6wTQ8+AZXqi82uR9DZAbc= 82 | -----END CERTIFICATE----- 83 | -------------------------------------------------------------------------------- /crates/ct_worker/roots.cftest.pem: -------------------------------------------------------------------------------- 1 | # letsencrypt-stg-root-x1.pem 2 | -----BEGIN CERTIFICATE----- 3 | MIIFmDCCA4CgAwIBAgIQU9C87nMpOIFKYpfvOHFHFDANBgkqhkiG9w0BAQsFADBm 4 | MQswCQYDVQQGEwJVUzEzMDEGA1UEChMqKFNUQUdJTkcpIEludGVybmV0IFNlY3Vy 5 | aXR5IFJlc2VhcmNoIEdyb3VwMSIwIAYDVQQDExkoU1RBR0lORykgUHJldGVuZCBQ 6 | ZWFyIFgxMB4XDTE1MDYwNDExMDQzOFoXDTM1MDYwNDExMDQzOFowZjELMAkGA1UE 7 | BhMCVVMxMzAxBgNVBAoTKihTVEFHSU5HKSBJbnRlcm5ldCBTZWN1cml0eSBSZXNl 8 | YXJjaCBHcm91cDEiMCAGA1UEAxMZKFNUQUdJTkcpIFByZXRlbmQgUGVhciBYMTCC 9 | AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALbagEdDTa1QgGBWSYkyMhsc 10 | ZXENOBaVRTMX1hceJENgsL0Ma49D3MilI4KS38mtkmdF6cPWnL++fgehT0FbRHZg 11 | jOEr8UAN4jH6omjrbTD++VZneTsMVaGamQmDdFl5g1gYaigkkmx8OiCO68a4QXg4 12 | wSyn6iDipKP8utsE+x1E28SA75HOYqpdrk4HGxuULvlr03wZGTIf/oRt2/c+dYmD 13 | oaJhge+GOrLAEQByO7+8+vzOwpNAPEx6LW+crEEZ7eBXih6VP19sTGy3yfqK5tPt 14 | TdXXCOQMKAp+gCj/VByhmIr+0iNDC540gtvV303WpcbwnkkLYC0Ft2cYUyHtkstO 15 | fRcRO+K2cZozoSwVPyB8/J9RpcRK3jgnX9lujfwA/pAbP0J2UPQFxmWFRQnFjaq6 16 | rkqbNEBgLy+kFL1NEsRbvFbKrRi5bYy2lNms2NJPZvdNQbT/2dBZKmJqxHkxCuOQ 17 | FjhJQNeO+Njm1Z1iATS/3rts2yZlqXKsxQUzN6vNbD8KnXRMEeOXUYvbV4lqfCf8 18 | mS14WEbSiMy87GB5S9ucSV1XUrlTG5UGcMSZOBcEUpisRPEmQWUOTWIoDQ5FOia/ 19 | GI+Ki523r2ruEmbmG37EBSBXdxIdndqrjy+QVAmCebyDx9eVEGOIpn26bW5LKeru 20 | mJxa/CFBaKi4bRvmdJRLAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMB 21 | Af8EBTADAQH/MB0GA1UdDgQWBBS182Xy/rAKkh/7PH3zRKCsYyXDFDANBgkqhkiG 22 | 9w0BAQsFAAOCAgEAncDZNytDbrrVe68UT6py1lfF2h6Tm2p8ro42i87WWyP2LK8Y 23 | nLHC0hvNfWeWmjZQYBQfGC5c7aQRezak+tHLdmrNKHkn5kn+9E9LCjCaEsyIIn2j 24 | qdHlAkepu/C3KnNtVx5tW07e5bvIjJScwkCDbP3akWQixPpRFAsnP+ULx7k0aO1x 25 | qAeaAhQ2rgo1F58hcflgqKTXnpPM02intVfiVVkX5GXpJjK5EoQtLceyGOrkxlM/ 26 | sTPq4UrnypmsqSagWV3HcUlYtDinc+nukFk6eR4XkzXBbwKajl0YjztfrCIHOn5Q 27 | CJL6TERVDbM/aAPly8kJ1sWGLuvvWYzMYgLzDul//rUF10gEMWaXVZV51KpS9DY/ 28 | 5CunuvCXmEQJHo7kGcViT7sETn6Jz9KOhvYcXkJ7po6d93A/jy4GKPIPnsKKNEmR 29 | xUuXY4xRdh45tMJnLTUDdC9FIU0flTeO9/vNpVA8OPU1i14vCz+MU8KX1bV3GXm/ 30 | fxlB7VBBjX9v5oUep0o/j68R/iDlCOM4VVfRa8gX6T2FU7fNdatvGro7uQzIvWof 31 | gN9WUwCbEMBy/YhBSrXycKA8crgGg3x1mIsopn88JKwmMBa68oS7EHM9w7C4y71M 32 | 7DiA+/9Qdp9RBWJpTS9i/mDnJg1xvo8Xz49mrrgfmcAXTCJqXi24NatI3Oc= 33 | -----END CERTIFICATE----- 34 | 35 | # letsencrypt-stg-root-x2.pem 36 | -----BEGIN CERTIFICATE----- 37 | MIICTjCCAdSgAwIBAgIRAIPgc3k5LlLVLtUUvs4K/QcwCgYIKoZIzj0EAwMwaDEL 38 | MAkGA1UEBhMCVVMxMzAxBgNVBAoTKihTVEFHSU5HKSBJbnRlcm5ldCBTZWN1cml0 39 | eSBSZXNlYXJjaCBHcm91cDEkMCIGA1UEAxMbKFNUQUdJTkcpIEJvZ3VzIEJyb2Nj 40 | b2xpIFgyMB4XDTIwMDkwNDAwMDAwMFoXDTQwMDkxNzE2MDAwMFowaDELMAkGA1UE 41 | BhMCVVMxMzAxBgNVBAoTKihTVEFHSU5HKSBJbnRlcm5ldCBTZWN1cml0eSBSZXNl 42 | YXJjaCBHcm91cDEkMCIGA1UEAxMbKFNUQUdJTkcpIEJvZ3VzIEJyb2Njb2xpIFgy 43 | MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEOvS+w1kCzAxYOJbA06Aw0HFP2tLBLKPo 44 | FQqR9AMskl1nC2975eQqycR+ACvYelA8rfwFXObMHYXJ23XLB+dAjPJVOJ2OcsjT 45 | VqO4dcDWu+rQ2VILdnJRYypnV1MMThVxo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYD 46 | VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU3tGjWWQOwZo2o0busBB2766XlWYwCgYI 47 | KoZIzj0EAwMDaAAwZQIwRcp4ZKBsq9XkUuN8wfX+GEbY1N5nmCRc8e80kUkuAefo 48 | uc2j3cICeXo1cOybQ1iWAjEA3Ooawl8eQyR4wrjCofUE8h44p0j7Yl/kBlJZT8+9 49 | vbtH7QiVzeKCOTQPINyRql6P 50 | -----END CERTIFICATE----- 51 | 52 | # DigiCert CT Test Root 53 | -----BEGIN CERTIFICATE----- 54 | MIIB8jCCAXigAwIBAgIRAIyOQJBSOUS0rZpkdkjxtDwwCgYIKoZIzj0EAwMwOTEX 55 | MBUGA1UEChMORGlnaUNlcnQsIEluYy4xHjAcBgNVBAMTFURpZ2lDZXJ0IENUIFRl 56 | c3QgUm9vdDAgFw0yMzAxMTkxNDM0MDNaGA8yMDUzMDExOTE0MzQwM1owOTEXMBUG 57 | A1UEChMORGlnaUNlcnQsIEluYy4xHjAcBgNVBAMTFURpZ2lDZXJ0IENUIFRlc3Qg 58 | Um9vdDB2MBAGByqGSM49AgEGBSuBBAAiA2IABABQuhdtznMJNe6yNwml0T158MQ2 59 | KkQyunEHgOMpxeXY6Io9fXnMtkdj2qP9CCqhND2kp93f/oqPMw2PcEjar2v3HiOI 60 | jdzBsm7ecw0JWy0Zz4/8Lmpla3FAhHS5BoKTuKNCMEAwDgYDVR0PAQH/BAQDAgGG 61 | MA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFONDnZfhq7kEGhdBqpL8TJE0+cHn 62 | MAoGCCqGSM49BAMDA2gAMGUCMQCVr77mcjhS5tiV+2mjkOTH+44wj4mphb5Q8MPd 63 | NMBAMQzdGhRpANjB+TNLLNjXd3oCMAesBJGQBLbv1sQvbHEn0ie9oTl32+IxUUpu 64 | 7I+a+N4XXDZyKsVNL54hI61d+sS6cg== 65 | -----END CERTIFICATE----- 66 | 67 | # Raytonne Staging 68 | -----BEGIN CERTIFICATE----- 69 | MIICUzCCAdigAwIBAgIJOqCV9Q6lUL/FMAoGCCqGSM49BAMDMG4xCzAJBgNVBAYT 70 | AkNOMRIwEAYDVQQIDAnmsrPljZfnnIExJzAlBgNVBAoMHuays+WNl+eRnui2uOWV 71 | hui0uOaciemZkOWFrOWPuDEiMCAGA1UEAwwZ55Ge6La44ri65a6b77yIU3RhZ2lu 72 | Z++8iTAeFw0yMTA3MDEwMDAwMDBaFw0zNjAxMDEwMDAwMDBaMG4xCzAJBgNVBAYT 73 | AkNOMRIwEAYDVQQIDAnmsrPljZfnnIExJzAlBgNVBAoMHuays+WNl+eRnui2uOWV 74 | hui0uOaciemZkOWFrOWPuDEiMCAGA1UEAwwZ55Ge6La44ri65a6b77yIU3RhZ2lu 75 | Z++8iTB2MBAGByqGSM49AgEGBSuBBAAiA2IABEcACuitImXwh1UnPQtSdSFezvuk 76 | DrB1zrZ62DrXqQDhM3YtAf4d4CAgwDnFhdjjitN0haSBqV2/WLsaummwPIr86mur 77 | t8t+PZbIAor6eFdUm0dTNj2qMykbKMtVkjEeDaNCMEAwDwYDVR0TAQH/BAUwAwEB 78 | /zAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0OBBYEFGsylsgxPDWNk3pv6GEjpknNULzO 79 | MAoGCCqGSM49BAMDA2kAMGYCMQDBjfgJnRx32lWxA6ktDaqxxtGuHdRm/+InJ0NO 80 | 862fDHVLiNSMWluqk2ET18DMxFoCMQDLQ53QrXgs8W6MlzVtrcTnylUCYIUVPKTA 81 | Lohqkfpo2uYCpwZjTNVOrAfw5ij1ivk= 82 | -----END CERTIFICATE----- 83 | -------------------------------------------------------------------------------- /crates/ct_worker/roots.dev.pem: -------------------------------------------------------------------------------- 1 | # letsencrypt-stg-root-x1.pem 2 | -----BEGIN CERTIFICATE----- 3 | MIIFmDCCA4CgAwIBAgIQU9C87nMpOIFKYpfvOHFHFDANBgkqhkiG9w0BAQsFADBm 4 | MQswCQYDVQQGEwJVUzEzMDEGA1UEChMqKFNUQUdJTkcpIEludGVybmV0IFNlY3Vy 5 | aXR5IFJlc2VhcmNoIEdyb3VwMSIwIAYDVQQDExkoU1RBR0lORykgUHJldGVuZCBQ 6 | ZWFyIFgxMB4XDTE1MDYwNDExMDQzOFoXDTM1MDYwNDExMDQzOFowZjELMAkGA1UE 7 | BhMCVVMxMzAxBgNVBAoTKihTVEFHSU5HKSBJbnRlcm5ldCBTZWN1cml0eSBSZXNl 8 | YXJjaCBHcm91cDEiMCAGA1UEAxMZKFNUQUdJTkcpIFByZXRlbmQgUGVhciBYMTCC 9 | AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALbagEdDTa1QgGBWSYkyMhsc 10 | ZXENOBaVRTMX1hceJENgsL0Ma49D3MilI4KS38mtkmdF6cPWnL++fgehT0FbRHZg 11 | jOEr8UAN4jH6omjrbTD++VZneTsMVaGamQmDdFl5g1gYaigkkmx8OiCO68a4QXg4 12 | wSyn6iDipKP8utsE+x1E28SA75HOYqpdrk4HGxuULvlr03wZGTIf/oRt2/c+dYmD 13 | oaJhge+GOrLAEQByO7+8+vzOwpNAPEx6LW+crEEZ7eBXih6VP19sTGy3yfqK5tPt 14 | TdXXCOQMKAp+gCj/VByhmIr+0iNDC540gtvV303WpcbwnkkLYC0Ft2cYUyHtkstO 15 | fRcRO+K2cZozoSwVPyB8/J9RpcRK3jgnX9lujfwA/pAbP0J2UPQFxmWFRQnFjaq6 16 | rkqbNEBgLy+kFL1NEsRbvFbKrRi5bYy2lNms2NJPZvdNQbT/2dBZKmJqxHkxCuOQ 17 | FjhJQNeO+Njm1Z1iATS/3rts2yZlqXKsxQUzN6vNbD8KnXRMEeOXUYvbV4lqfCf8 18 | mS14WEbSiMy87GB5S9ucSV1XUrlTG5UGcMSZOBcEUpisRPEmQWUOTWIoDQ5FOia/ 19 | GI+Ki523r2ruEmbmG37EBSBXdxIdndqrjy+QVAmCebyDx9eVEGOIpn26bW5LKeru 20 | mJxa/CFBaKi4bRvmdJRLAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMB 21 | Af8EBTADAQH/MB0GA1UdDgQWBBS182Xy/rAKkh/7PH3zRKCsYyXDFDANBgkqhkiG 22 | 9w0BAQsFAAOCAgEAncDZNytDbrrVe68UT6py1lfF2h6Tm2p8ro42i87WWyP2LK8Y 23 | nLHC0hvNfWeWmjZQYBQfGC5c7aQRezak+tHLdmrNKHkn5kn+9E9LCjCaEsyIIn2j 24 | qdHlAkepu/C3KnNtVx5tW07e5bvIjJScwkCDbP3akWQixPpRFAsnP+ULx7k0aO1x 25 | qAeaAhQ2rgo1F58hcflgqKTXnpPM02intVfiVVkX5GXpJjK5EoQtLceyGOrkxlM/ 26 | sTPq4UrnypmsqSagWV3HcUlYtDinc+nukFk6eR4XkzXBbwKajl0YjztfrCIHOn5Q 27 | CJL6TERVDbM/aAPly8kJ1sWGLuvvWYzMYgLzDul//rUF10gEMWaXVZV51KpS9DY/ 28 | 5CunuvCXmEQJHo7kGcViT7sETn6Jz9KOhvYcXkJ7po6d93A/jy4GKPIPnsKKNEmR 29 | xUuXY4xRdh45tMJnLTUDdC9FIU0flTeO9/vNpVA8OPU1i14vCz+MU8KX1bV3GXm/ 30 | fxlB7VBBjX9v5oUep0o/j68R/iDlCOM4VVfRa8gX6T2FU7fNdatvGro7uQzIvWof 31 | gN9WUwCbEMBy/YhBSrXycKA8crgGg3x1mIsopn88JKwmMBa68oS7EHM9w7C4y71M 32 | 7DiA+/9Qdp9RBWJpTS9i/mDnJg1xvo8Xz49mrrgfmcAXTCJqXi24NatI3Oc= 33 | -----END CERTIFICATE----- 34 | 35 | # letsencrypt-stg-root-x2.pem 36 | -----BEGIN CERTIFICATE----- 37 | MIICTjCCAdSgAwIBAgIRAIPgc3k5LlLVLtUUvs4K/QcwCgYIKoZIzj0EAwMwaDEL 38 | MAkGA1UEBhMCVVMxMzAxBgNVBAoTKihTVEFHSU5HKSBJbnRlcm5ldCBTZWN1cml0 39 | eSBSZXNlYXJjaCBHcm91cDEkMCIGA1UEAxMbKFNUQUdJTkcpIEJvZ3VzIEJyb2Nj 40 | b2xpIFgyMB4XDTIwMDkwNDAwMDAwMFoXDTQwMDkxNzE2MDAwMFowaDELMAkGA1UE 41 | BhMCVVMxMzAxBgNVBAoTKihTVEFHSU5HKSBJbnRlcm5ldCBTZWN1cml0eSBSZXNl 42 | YXJjaCBHcm91cDEkMCIGA1UEAxMbKFNUQUdJTkcpIEJvZ3VzIEJyb2Njb2xpIFgy 43 | MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEOvS+w1kCzAxYOJbA06Aw0HFP2tLBLKPo 44 | FQqR9AMskl1nC2975eQqycR+ACvYelA8rfwFXObMHYXJ23XLB+dAjPJVOJ2OcsjT 45 | VqO4dcDWu+rQ2VILdnJRYypnV1MMThVxo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYD 46 | VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU3tGjWWQOwZo2o0busBB2766XlWYwCgYI 47 | KoZIzj0EAwMDaAAwZQIwRcp4ZKBsq9XkUuN8wfX+GEbY1N5nmCRc8e80kUkuAefo 48 | uc2j3cICeXo1cOybQ1iWAjEA3Ooawl8eQyR4wrjCofUE8h44p0j7Yl/kBlJZT8+9 49 | vbtH7QiVzeKCOTQPINyRql6P 50 | -----END CERTIFICATE----- 51 | 52 | # DigiCert CT Test Root 53 | -----BEGIN CERTIFICATE----- 54 | MIIB8jCCAXigAwIBAgIRAIyOQJBSOUS0rZpkdkjxtDwwCgYIKoZIzj0EAwMwOTEX 55 | MBUGA1UEChMORGlnaUNlcnQsIEluYy4xHjAcBgNVBAMTFURpZ2lDZXJ0IENUIFRl 56 | c3QgUm9vdDAgFw0yMzAxMTkxNDM0MDNaGA8yMDUzMDExOTE0MzQwM1owOTEXMBUG 57 | A1UEChMORGlnaUNlcnQsIEluYy4xHjAcBgNVBAMTFURpZ2lDZXJ0IENUIFRlc3Qg 58 | Um9vdDB2MBAGByqGSM49AgEGBSuBBAAiA2IABABQuhdtznMJNe6yNwml0T158MQ2 59 | KkQyunEHgOMpxeXY6Io9fXnMtkdj2qP9CCqhND2kp93f/oqPMw2PcEjar2v3HiOI 60 | jdzBsm7ecw0JWy0Zz4/8Lmpla3FAhHS5BoKTuKNCMEAwDgYDVR0PAQH/BAQDAgGG 61 | MA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFONDnZfhq7kEGhdBqpL8TJE0+cHn 62 | MAoGCCqGSM49BAMDA2gAMGUCMQCVr77mcjhS5tiV+2mjkOTH+44wj4mphb5Q8MPd 63 | NMBAMQzdGhRpANjB+TNLLNjXd3oCMAesBJGQBLbv1sQvbHEn0ie9oTl32+IxUUpu 64 | 7I+a+N4XXDZyKsVNL54hI61d+sS6cg== 65 | -----END CERTIFICATE----- 66 | 67 | # Raytonne Staging 68 | -----BEGIN CERTIFICATE----- 69 | MIICUzCCAdigAwIBAgIJOqCV9Q6lUL/FMAoGCCqGSM49BAMDMG4xCzAJBgNVBAYT 70 | AkNOMRIwEAYDVQQIDAnmsrPljZfnnIExJzAlBgNVBAoMHuays+WNl+eRnui2uOWV 71 | hui0uOaciemZkOWFrOWPuDEiMCAGA1UEAwwZ55Ge6La44ri65a6b77yIU3RhZ2lu 72 | Z++8iTAeFw0yMTA3MDEwMDAwMDBaFw0zNjAxMDEwMDAwMDBaMG4xCzAJBgNVBAYT 73 | AkNOMRIwEAYDVQQIDAnmsrPljZfnnIExJzAlBgNVBAoMHuays+WNl+eRnui2uOWV 74 | hui0uOaciemZkOWFrOWPuDEiMCAGA1UEAwwZ55Ge6La44ri65a6b77yIU3RhZ2lu 75 | Z++8iTB2MBAGByqGSM49AgEGBSuBBAAiA2IABEcACuitImXwh1UnPQtSdSFezvuk 76 | DrB1zrZ62DrXqQDhM3YtAf4d4CAgwDnFhdjjitN0haSBqV2/WLsaummwPIr86mur 77 | t8t+PZbIAor6eFdUm0dTNj2qMykbKMtVkjEeDaNCMEAwDwYDVR0TAQH/BAUwAwEB 78 | /zAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0OBBYEFGsylsgxPDWNk3pv6GEjpknNULzO 79 | MAoGCCqGSM49BAMDA2kAMGYCMQDBjfgJnRx32lWxA6ktDaqxxtGuHdRm/+InJ0NO 80 | 862fDHVLiNSMWluqk2ET18DMxFoCMQDLQ53QrXgs8W6MlzVtrcTnylUCYIUVPKTA 81 | Lohqkfpo2uYCpwZjTNVOrAfw5ij1ivk= 82 | -----END CERTIFICATE----- 83 | -------------------------------------------------------------------------------- /crates/mtc_worker/wrangler.jsonc: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mtc", 3 | "main": "build/worker/shim.mjs", 4 | "compatibility_date": "2025-09-25", 5 | "workers_dev": false, 6 | "build": { 7 | "command": "echo 'Default environment not configured. Please specify an environment with the \"-e\" flag.' && exit 1" 8 | }, 9 | "triggers": { 10 | "crons": [ 11 | "23 4 * * *" 12 | ] 13 | }, 14 | "env": { 15 | "dev": { 16 | "build": { 17 | // Change '--release' to '--dev' to compile with debug symbols. 18 | // DEPLOY_ENV is used in build.rs to select per-environment 19 | // config and roots. Add `--features dev-bootstrap-roots` to the 20 | // worker-build command to enable dev bootstrap roots (which 21 | // should NOT be enabled in a production deployment). 22 | "command": "cargo install -q worker-build@0.1.14 && DEPLOY_ENV=dev worker-build --release" 23 | }, 24 | "workers_dev": true, 25 | "kv_namespaces": [ 26 | { 27 | "id": "e4aab0c89ebe492c9c81c444c78a31ff", 28 | "binding": "ccadb_roots" 29 | } 30 | ], 31 | "r2_buckets": [ 32 | { 33 | "bucket_name": "mtc-public-dev1", 34 | "binding": "public_dev1" 35 | }, 36 | { 37 | "bucket_name": "mtc-public-dev2", 38 | "binding": "public_dev2" 39 | } 40 | ], 41 | "durable_objects": { 42 | "bindings": [ 43 | { 44 | "name": "SEQUENCER", 45 | "class_name": "Sequencer" 46 | }, 47 | { 48 | "name": "BATCHER", 49 | "class_name": "Batcher" 50 | }, 51 | { 52 | "name": "CLEANER", 53 | "class_name": "Cleaner" 54 | } 55 | ] 56 | }, 57 | "migrations": [ 58 | { 59 | // tag should be unique for each entry 60 | "tag": "v1", 61 | "new_sqlite_classes": [ 62 | "Sequencer", 63 | "Batcher" 64 | ] 65 | }, 66 | { 67 | "tag": "v2", 68 | "new_sqlite_classes": [ 69 | "Cleaner" 70 | ] 71 | } 72 | ] 73 | }, 74 | "bootstrap-mtca": { 75 | "build": { 76 | // Change '--release' to '--dev' to compile with debug symbols. 77 | // DEPLOY_ENV is used in build.rs to select per-environment config and roots. 78 | "command": "cargo install -q worker-build@0.1.14 && DEPLOY_ENV=bootstrap-mtca worker-build --release" 79 | }, 80 | "route": { 81 | "pattern": "bootstrap-mtca.cloudflareresearch.com", 82 | "custom_domain": true 83 | }, 84 | "kv_namespaces": [ 85 | { 86 | "id": "e4aab0c89ebe492c9c81c444c78a31ff", 87 | "binding": "ccadb_roots" 88 | } 89 | ], 90 | "r2_buckets": [ 91 | { 92 | "bucket_name": "bootstrap-mtca-shard1", 93 | "binding": "public_shard1" 94 | }, 95 | { 96 | "bucket_name": "bootstrap-mtca-shard2", 97 | "binding": "public_shard2" 98 | }, 99 | { 100 | "bucket_name": "bootstrap-mtca-shard3", 101 | "binding": "public_shard3" 102 | } 103 | ], 104 | "durable_objects": { 105 | "bindings": [ 106 | { 107 | "name": "BATCHER", 108 | "class_name": "Batcher" 109 | }, 110 | { 111 | "name": "CLEANER", 112 | "class_name": "Cleaner" 113 | }, 114 | { 115 | "name": "SEQUENCER", 116 | "class_name": "Sequencer" 117 | } 118 | ] 119 | }, 120 | "migrations": [ 121 | { 122 | // tag should be unique for each entry 123 | "tag": "v1", 124 | "new_sqlite_classes": [ 125 | "Batcher", 126 | "Cleaner", 127 | "Sequencer" 128 | ] 129 | } 130 | ] 131 | } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /crates/mtc_worker/dev-bootstrap-roots.pem: -------------------------------------------------------------------------------- 1 | # Root certificate for generating test data for Bushbaby. 2 | Certificate: 3 | Data: 4 | Version: 3 (0x2) 5 | Serial Number: 2023 (0x7e7) 6 | Signature Algorithm: ecdsa-with-SHA256 7 | Issuer: C=US, ST=California, L=San Francisco, O=My Corp, CN=My Corp Root CA 8 | Validity 9 | Not Before: Sep 26 20:53:47 2025 GMT 10 | Not After : Sep 26 20:53:47 2035 GMT 11 | Subject: C=US, ST=California, L=San Francisco, O=My Corp, CN=My Corp Root CA 12 | Subject Public Key Info: 13 | Public Key Algorithm: id-ecPublicKey 14 | Public-Key: (256 bit) 15 | pub: 16 | 04:da:58:ff:b6:8f:ce:41:79:b2:3e:5d:48:2d:fc: 17 | 01:d6:fe:d1:fe:97:91:15:28:9d:64:a7:c3:80:99: 18 | d9:20:92:28:cd:9e:0e:14:57:85:37:0a:e8:2c:9d: 19 | 1d:c7:e2:03:04:61:f7:71:b8:33:51:f8:91:69:ad: 20 | cb:38:d2:56:38 21 | ASN1 OID: prime256v1 22 | NIST CURVE: P-256 23 | X509v3 extensions: 24 | X509v3 Key Usage: critical 25 | Digital Signature, Certificate Sign 26 | X509v3 Basic Constraints: critical 27 | CA:TRUE 28 | X509v3 Subject Key Identifier: 29 | 9B:6B:39:DD:FB:E2:22:55:F3:20:55:17:53:3D:81:B4:C3:5E:EB:BB 30 | Signature Algorithm: ecdsa-with-SHA256 31 | Signature Value: 32 | 30:44:02:20:08:5e:82:d8:d5:a0:d5:6f:27:85:f8:76:8c:05: 33 | 2d:9e:e5:30:b9:b8:ca:07:99:a1:b6:91:6e:74:43:0d:a0:3e: 34 | 02:20:57:6e:cc:e2:38:b2:8c:65:97:ef:e0:a8:9c:f3:44:86: 35 | 7f:1f:7a:47:8a:fc:56:8a:69:32:b0:da:65:c1:63:21 36 | -----BEGIN CERTIFICATE----- 37 | MIIB/TCCAaSgAwIBAgICB+cwCgYIKoZIzj0EAwIwZjELMAkGA1UEBhMCVVMxEzAR 38 | BgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNhbiBGcmFuY2lzY28xEDAOBgNV 39 | BAoTB015IENvcnAxGDAWBgNVBAMTD015IENvcnAgUm9vdCBDQTAeFw0yNTA5MjYy 40 | MDUzNDdaFw0zNTA5MjYyMDUzNDdaMGYxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpD 41 | YWxpZm9ybmlhMRYwFAYDVQQHEw1TYW4gRnJhbmNpc2NvMRAwDgYDVQQKEwdNeSBD 42 | b3JwMRgwFgYDVQQDEw9NeSBDb3JwIFJvb3QgQ0EwWTATBgcqhkjOPQIBBggqhkjO 43 | PQMBBwNCAATaWP+2j85BebI+XUgt/AHW/tH+l5EVKJ1kp8OAmdkgkijNng4UV4U3 44 | CugsnR3H4gMEYfdxuDNR+JFprcs40lY4o0IwQDAOBgNVHQ8BAf8EBAMCAoQwDwYD 45 | VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUm2s53fviIlXzIFUXUz2BtMNe67swCgYI 46 | KoZIzj0EAwIDRwAwRAIgCF6C2NWg1W8nhfh2jAUtnuUwubjKB5mhtpFudEMNoD4C 47 | IFduzOI4soxll+/gqJzzRIZ/H3pHivxWimkysNplwWMh 48 | -----END CERTIFICATE----- 49 | 50 | # Root certificate for Bastion internal tests. 51 | Certificate: 52 | Data: 53 | Version: 3 (0x2) 54 | Serial Number: 55 | 15:10:dd:ed:01:a1:e6:de:07:73:21:69:0d:b8:5e:05:f1:63:7d:1a 56 | Signature Algorithm: ecdsa-with-SHA256 57 | Issuer: C=US, ST=California, L=San Francisco, O=CloudFlare, OU=CloudFlare Test Certificate Authority 58 | Validity 59 | Not Before: Jun 20 20:02:00 2024 GMT 60 | Not After : May 27 20:02:00 2124 GMT 61 | Subject: C=US, ST=California, L=San Francisco, O=CloudFlare, OU=CloudFlare Test Certificate Authority 62 | Subject Public Key Info: 63 | Public Key Algorithm: id-ecPublicKey 64 | Public-Key: (256 bit) 65 | pub: 66 | 04:3d:03:11:5c:bd:5a:28:70:f7:ce:e6:4b:36:c7: 67 | a1:00:42:7e:3b:1e:90:86:ba:bf:a8:08:56:1e:88: 68 | 40:bf:11:5f:3f:2e:a7:66:d8:67:15:f1:0c:99:e1: 69 | d5:05:8d:3a:23:ba:3e:fc:7a:24:6b:5a:2a:1e:d4: 70 | 74:cc:cb:23:64 71 | ASN1 OID: prime256v1 72 | NIST CURVE: P-256 73 | X509v3 extensions: 74 | X509v3 Key Usage: critical 75 | Certificate Sign, CRL Sign 76 | X509v3 Basic Constraints: critical 77 | CA:TRUE 78 | X509v3 Subject Key Identifier: 79 | E0:EB:B8:6C:00:31:BB:75:36:E6:7C:25:2D:8D:2B:B1:18:37:67:EB 80 | Signature Algorithm: ecdsa-with-SHA256 81 | Signature Value: 82 | 30:46:02:21:00:b9:7f:48:ee:c8:69:28:23:75:78:c1:91:9a: 83 | 02:f7:ed:c9:7e:ef:8c:23:81:0d:f6:08:9e:c0:32:8d:a1:31: 84 | 81:02:21:00:ba:42:b4:7b:32:6a:ac:67:0b:60:8a:8b:45:e7: 85 | 51:b6:27:3e:0a:ee:6c:55:c9:39:9e:c7:d4:5b:b1:ba:a3:61 86 | -----BEGIN CERTIFICATE----- 87 | MIICRTCCAeqgAwIBAgIUFRDd7QGh5t4HcyFpDbheBfFjfRowCgYIKoZIzj0EAwIw 88 | fzELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNh 89 | biBGcmFuY2lzY28xEzARBgNVBAoTCkNsb3VkRmxhcmUxLjAsBgNVBAsTJUNsb3Vk 90 | RmxhcmUgVGVzdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwIBcNMjQwNjIwMjAwMjAw 91 | WhgPMjEyNDA1MjcyMDAyMDBaMH8xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxp 92 | Zm9ybmlhMRYwFAYDVQQHEw1TYW4gRnJhbmNpc2NvMRMwEQYDVQQKEwpDbG91ZEZs 93 | YXJlMS4wLAYDVQQLEyVDbG91ZEZsYXJlIFRlc3QgQ2VydGlmaWNhdGUgQXV0aG9y 94 | aXR5MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEPQMRXL1aKHD3zuZLNsehAEJ+ 95 | Ox6Qhrq/qAhWHohAvxFfPy6nZthnFfEMmeHVBY06I7o+/Hoka1oqHtR0zMsjZKNC 96 | MEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFODr 97 | uGwAMbt1NuZ8JS2NK7EYN2frMAoGCCqGSM49BAMCA0kAMEYCIQC5f0juyGkoI3V4 98 | wZGaAvftyX7vjCOBDfYInsAyjaExgQIhALpCtHsyaqxnC2CKi0XnUbYnPgrubFXJ 99 | OZ7H1FuxuqNh 100 | -----END CERTIFICATE----- 101 | -------------------------------------------------------------------------------- /crates/ct_worker/build.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2025 Cloudflare, Inc. 2 | // Licensed under the BSD-3-Clause license found in the LICENSE file or at https://opensource.org/licenses/BSD-3-Clause 3 | 4 | // Build script to include per-environment configuration and trusted roots. 5 | 6 | use chrono::Months; 7 | use config::AppConfig; 8 | use std::env; 9 | use std::fs; 10 | use url::Url; 11 | use x509_cert::Certificate; 12 | 13 | fn main() { 14 | let env = env::var("DEPLOY_ENV").unwrap_or_else(|_| "dev".to_string()); 15 | let config_file = &format!("config.{env}.json"); 16 | let config_contents = &fs::read_to_string(config_file).unwrap_or_else(|e| { 17 | panic!("failed to read config file '{config_file}': {e}"); 18 | }); 19 | 20 | // Validate the config json against the schema. 21 | let json = serde_json::from_str(config_contents).unwrap_or_else(|e| { 22 | panic!("failed to deserialize JSON config '{config_file}': {e}"); 23 | }); 24 | let schema = serde_json::from_str(include_str!("config.schema.json")).unwrap_or_else(|e| { 25 | panic!("failed to deserialize JSON schema 'config.schema.json': {e}"); 26 | }); 27 | jsonschema::validate(&schema, &json).unwrap_or_else(|e| { 28 | panic!("config '{config_file}' does not match schema 'config.schema.json': {e}"); 29 | }); 30 | 31 | // Validate the config parameters. 32 | let conf = serde_json::from_str::(config_contents).unwrap_or_else(|e| { 33 | panic!("failed to deserialize JSON config '{config_file}': {e}"); 34 | }); 35 | for (name, params) in &conf.logs { 36 | // Chrome's CT policy (https://googlechrome.github.io/CertificateTransparency/log_policy.html) states: 37 | // "The certificate expiry ranges for CT Logs must be no longer than one calendar year and should be no shorter than six months." 38 | assert!( 39 | (params.temporal_interval.start_inclusive + Months::new(6) 40 | ..=params.temporal_interval.start_inclusive + Months::new(12)) 41 | .contains(¶ms.temporal_interval.end_exclusive), 42 | "{name} invalid temporal interval: [{}, {})", 43 | params.temporal_interval.start_inclusive, 44 | params.temporal_interval.end_exclusive 45 | ); 46 | // Valid location hints: https://developers.cloudflare.com/durable-objects/reference/data-location/#supported-locations-1 47 | if let Some(location) = ¶ms.location_hint { 48 | assert!( 49 | ["wnam", "enam", "sam", "weur", "eeur", "apac", "oc", "afr", "me",] 50 | .contains(&location.as_str()), 51 | "{name} invalid location hint: {location}" 52 | ); 53 | } 54 | 55 | check_url(¶ms.submission_url); 56 | if !params.monitoring_url.is_empty() { 57 | check_url(¶ms.monitoring_url); 58 | } 59 | } 60 | 61 | // Get and validate roots from an embedded roots file, which must exist if 62 | // 'enable_ccadb_roots' is false. If 'enable_ccadb_roots' is true, the log 63 | // shard will combine trusted roots from the embedded roots file and from a 64 | // roots file loaded from Workers KV. 65 | let roots_file: &str = &format!("roots.{env}.pem"); 66 | let roots_file_exists = fs::exists(roots_file).expect("failed to check if file exists"); 67 | if roots_file_exists { 68 | let roots = 69 | Certificate::load_pem_chain(&fs::read(roots_file).expect("failed to read roots file")) 70 | .expect("unable to decode certificates"); 71 | assert!( 72 | !roots.is_empty(), 73 | "roots file does not contain any certificates" 74 | ); 75 | } else if conf.logs.values().any(|params| !params.enable_ccadb_roots) { 76 | // If any log shards have 'enable_ccadb_roots' set to false, require a roots file. 77 | panic!("{roots_file} must exist and contain at least one certificate if any logs have 'enable_ccadb_roots' set to false"); 78 | } 79 | 80 | // Copy to OUT_DIR. 81 | let out_dir = env::var("OUT_DIR").unwrap(); 82 | fs::copy(config_file, format!("{out_dir}/config.json")).expect("failed to copy config file"); 83 | if roots_file_exists { 84 | fs::copy(roots_file, format!("{out_dir}/roots.pem")).expect("failed to copy roots file"); 85 | } else { 86 | fs::write(format!("{out_dir}/roots.pem"), "").expect("failed to write empty roots file"); 87 | } 88 | 89 | println!("cargo::rerun-if-env-changed=DEPLOY_ENV"); 90 | println!("cargo::rerun-if-changed=config.schema.json"); 91 | println!("cargo::rerun-if-changed={config_file}"); 92 | println!("cargo::rerun-if-changed={roots_file}"); 93 | } 94 | 95 | // Validate the URL prefix according to https://datatracker.ietf.org/doc/html/rfc6962#section-4. 96 | // "The prefix can include a path as well as a server name and a port." 97 | fn check_url(s: &str) { 98 | let u = Url::parse(s).unwrap(); 99 | assert!(["http", "https"].contains(&u.scheme()), "invalid scheme"); 100 | assert!(u.domain().is_some(), "invalid domain"); 101 | assert_eq!( 102 | u.as_str(), 103 | &format!("{}{}", u.origin().ascii_serialization(), u.path()), 104 | "invalid URL components" 105 | ); 106 | } 107 | -------------------------------------------------------------------------------- /crates/generic_log_worker/tests/add-chain.json: -------------------------------------------------------------------------------- 1 | {"chain":["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"]} 2 | -------------------------------------------------------------------------------- /crates/static_ct_api/tests/real-precert-intermediate.pem: -------------------------------------------------------------------------------- 1 | Certificate: 2 | Data: 3 | Version: 3 (0x2) 4 | Serial Number: 5 | 01:e3:b4:9d:77:cd:f4:0c:06:19:16:b6:e3 6 | Signature Algorithm: sha256WithRSAEncryption 7 | Issuer: OU=GlobalSign Root CA - R2, O=GlobalSign, CN=GlobalSign 8 | Validity 9 | Not Before: Jun 15 00:00:42 2017 GMT 10 | Not After : Dec 15 00:00:42 2021 GMT 11 | Subject: C=US, O=Google Trust Services, CN=GTS CA 1D2 12 | Subject Public Key Info: 13 | Public Key Algorithm: rsaEncryption 14 | Public-Key: (2048 bit) 15 | Modulus: 16 | 00:b2:d9:7b:e1:e1:d7:3f:1c:91:72:ff:f9:10:cd: 17 | 87:15:79:74:b7:3e:47:8b:b2:61:55:fd:0c:36:c6: 18 | 7e:77:42:3a:b2:fa:52:5b:0b:71:81:d6:4d:d5:e9: 19 | 2b:24:4d:23:5e:8b:2b:72:5f:21:55:b5:29:ef:44: 20 | cb:eb:82:52:ab:3e:27:a4:92:49:41:4a:de:a8:dd: 21 | 31:e0:3c:df:6d:7a:4d:2d:d6:6d:09:b0:0e:e3:61: 22 | f2:b2:fe:90:6c:5a:7b:10:64:49:b4:0b:3c:08:f2: 23 | ea:79:0c:6c:a6:1a:89:6a:56:32:a0:29:a2:30:82: 24 | 8f:81:51:0c:f3:a2:b9:d9:75:b9:22:9e:27:14:ba: 25 | 4a:2f:2c:63:58:87:f1:5d:10:e6:5f:91:bb:b9:5b: 26 | cc:47:e2:1e:75:b6:8c:8f:cc:75:5d:57:05:e7:82: 27 | c6:84:0e:74:72:2a:cb:3b:55:f5:6e:70:eb:66:69: 28 | c3:24:bb:38:93:35:9b:68:61:2f:9b:d6:ae:a6:77: 29 | 72:7c:71:48:58:33:10:af:e9:80:82:1d:b5:07:40: 30 | 1b:f6:3d:ec:a2:ad:47:9d:b4:94:29:34:b3:8c:2f: 31 | cd:25:03:58:35:c0:25:a4:55:5f:e1:b3:07:56:3d: 32 | c8:d0:63:b8:20:fb:8c:1d:43:2c:f8:f9:a9:d5:ec: 33 | 6f:97 34 | Exponent: 65537 (0x10001) 35 | X509v3 extensions: 36 | X509v3 Key Usage: critical 37 | Digital Signature, Certificate Sign, CRL Sign 38 | X509v3 Extended Key Usage: 39 | TLS Web Server Authentication, TLS Web Client Authentication 40 | X509v3 Basic Constraints: critical 41 | CA:TRUE, pathlen:0 42 | X509v3 Subject Key Identifier: 43 | B1:DD:32:5D:E8:B7:37:72:D2:CE:5C:CE:26:FE:47:79:E2:01:08:E9 44 | X509v3 Authority Key Identifier: 45 | 9B:E2:07:57:67:1C:1E:C0:6A:06:DE:59:B4:9A:2D:DF:DC:19:86:2E 46 | Authority Information Access: 47 | OCSP - URI:http://ocsp.pki.goog/gsr2 48 | X509v3 CRL Distribution Points: 49 | Full Name: 50 | URI:http://crl.pki.goog/gsr2/gsr2.crl 51 | X509v3 Certificate Policies: 52 | Policy: 2.23.140.1.2.1 53 | CPS: https://pki.goog/repository/ 54 | Signature Algorithm: sha256WithRSAEncryption 55 | Signature Value: 56 | 71:4a:c4:c3:23:ae:f7:e3:b2:02:79:8c:13:e8:53:8e:80:c5: 57 | f0:e3:ef:71:60:a9:a9:7b:34:65:85:34:bd:47:3b:03:57:16: 58 | 00:99:48:3a:e0:e0:f0:ea:cd:b6:48:3c:d5:ab:72:f0:d0:1b: 59 | cb:64:2d:3b:0d:74:68:d7:74:88:31:7c:6a:ba:0e:f0:8c:4d: 60 | 78:ce:da:10:f4:8a:96:45:97:a9:97:ad:c5:35:1a:18:64:e8: 61 | 93:b6:0d:9d:1f:b9:5e:1d:80:ea:e7:5b:9c:8e:ae:0e:a6:84: 62 | d2:d1:17:ce:b3:fb:f6:81:4f:3c:e6:68:9f:cf:f1:a6:76:c5: 63 | 7d:a7:f3:dd:7d:58:0f:e0:f6:61:01:1c:51:8e:76:33:2b:48: 64 | 9d:5c:81:51:72:08:17:ba:fd:01:d3:ee:46:f9:f4:b2:68:40: 65 | 99:31:01:6c:4f:1b:c6:56:eb:81:73:d2:79:52:05:92:26:5b: 66 | 71:cd:9d:c4:d2:ce:23:77:0f:41:7a:69:5e:21:25:c6:f8:b7: 67 | ff:7a:f7:47:de:c2:00:7b:9c:5a:45:9c:2a:4e:46:90:d9:75: 68 | 2c:d8:ff:8c:ee:cc:dc:69:eb:6c:e6:15:d0:a3:ff:48:0b:ac: 69 | 55:df:df:25:9d:42:b6:51:a3:66:95:60:c5:d0:22:e7:22:7a: 70 | 51:a5:cc:87 71 | -----BEGIN CERTIFICATE----- 72 | MIIESjCCAzKgAwIBAgINAeO0nXfN9AwGGRa24zANBgkqhkiG9w0BAQsFADBMMSAw 73 | HgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMjETMBEGA1UEChMKR2xvYmFs 74 | U2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjAeFw0xNzA2MTUwMDAwNDJaFw0yMTEy 75 | MTUwMDAwNDJaMEIxCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVHb29nbGUgVHJ1c3Qg 76 | U2VydmljZXMxEzARBgNVBAMTCkdUUyBDQSAxRDIwggEiMA0GCSqGSIb3DQEBAQUA 77 | A4IBDwAwggEKAoIBAQCy2Xvh4dc/HJFy//kQzYcVeXS3PkeLsmFV/Qw2xn53Qjqy 78 | +lJbC3GB1k3V6SskTSNeiytyXyFVtSnvRMvrglKrPiekkklBSt6o3THgPN9tek0t 79 | 1m0JsA7jYfKy/pBsWnsQZEm0CzwI8up5DGymGolqVjKgKaIwgo+BUQzzornZdbki 80 | nicUukovLGNYh/FdEOZfkbu5W8xH4h51toyPzHVdVwXngsaEDnRyKss7VfVucOtm 81 | acMkuziTNZtoYS+b1q6md3J8cUhYMxCv6YCCHbUHQBv2PeyirUedtJQpNLOML80l 82 | A1g1wCWkVV/hswdWPcjQY7gg+4wdQyz4+anV7G+XAgMBAAGjggEzMIIBLzAOBgNV 83 | HQ8BAf8EBAMCAYYwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMBIGA1Ud 84 | EwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFLHdMl3otzdy0s5czib+R3niAQjpMB8G 85 | A1UdIwQYMBaAFJviB1dnHB7AagbeWbSaLd/cGYYuMDUGCCsGAQUFBwEBBCkwJzAl 86 | BggrBgEFBQcwAYYZaHR0cDovL29jc3AucGtpLmdvb2cvZ3NyMjAyBgNVHR8EKzAp 87 | MCegJaAjhiFodHRwOi8vY3JsLnBraS5nb29nL2dzcjIvZ3NyMi5jcmwwPwYDVR0g 88 | BDgwNjA0BgZngQwBAgEwKjAoBggrBgEFBQcCARYcaHR0cHM6Ly9wa2kuZ29vZy9y 89 | ZXBvc2l0b3J5LzANBgkqhkiG9w0BAQsFAAOCAQEAcUrEwyOu9+OyAnmME+hTjoDF 90 | 8OPvcWCpqXs0ZYU0vUc7A1cWAJlIOuDg8OrNtkg81aty8NAby2QtOw10aNd0iDF8 91 | aroO8IxNeM7aEPSKlkWXqZetxTUaGGTok7YNnR+5Xh2A6udbnI6uDqaE0tEXzrP7 92 | 9oFPPOZon8/xpnbFfafz3X1YD+D2YQEcUY52MytInVyBUXIIF7r9AdPuRvn0smhA 93 | mTEBbE8bxlbrgXPSeVIFkiZbcc2dxNLOI3cPQXppXiElxvi3/3r3R97CAHucWkWc 94 | Kk5GkNl1LNj/jO7M3GnrbOYV0KP/SAusVd/fJZ1CtlGjZpVgxdAi5yJ6UaXMhw== 95 | -----END CERTIFICATE----- 96 | -------------------------------------------------------------------------------- /crates/generic_log_worker/tests/add-pre-chain.json: -------------------------------------------------------------------------------- 1 | {"chain":["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"]} 2 | -------------------------------------------------------------------------------- /crates/static_ct_api/tests/real-precert-with-eku.pem: -------------------------------------------------------------------------------- 1 | Certificate: 2 | Data: 3 | Version: 3 (0x2) 4 | Serial Number: 5 | 9c:a4:07:e2:25:f9:7c:c2:0a:00:00:00:00:20:6e:e5 6 | Signature Algorithm: sha256WithRSAEncryption 7 | Issuer: C=US, O=Google Trust Services, CN=GTS CA 1D2 8 | Validity 9 | Not Before: Mar 23 12:23:44 2020 GMT 10 | Not After : Jun 21 12:23:44 2020 GMT 11 | Subject: CN=certificate.transparency.dev 12 | Subject Public Key Info: 13 | Public Key Algorithm: rsaEncryption 14 | Public-Key: (2048 bit) 15 | Modulus: 16 | 00:a8:7e:59:c0:e5:3b:da:3c:bf:04:51:91:eb:9f: 17 | 6c:1b:cf:9f:90:dc:22:89:1c:b5:98:24:69:2e:26: 18 | 2d:61:92:04:0f:2e:f1:da:ec:ea:3a:d9:cc:3a:82: 19 | e2:b8:3a:7d:6c:79:79:f7:36:c5:52:a4:bb:46:1d: 20 | 2f:0b:6c:5f:00:31:af:24:e9:4a:1b:32:63:1a:b5: 21 | c3:28:9c:a7:0a:b5:73:e2:c1:a7:b5:1e:11:ae:cd: 22 | 19:79:0c:62:06:cf:80:f0:ed:e2:72:82:bb:b4:84: 23 | 0e:9d:c9:7d:3b:fb:4e:05:49:3a:14:0f:86:92:01: 24 | 49:52:2c:cc:a0:e1:ef:86:fe:18:00:83:69:6c:90: 25 | c6:7b:a9:42:df:57:9c:7b:61:06:80:23:b2:5f:95: 26 | 95:1e:9b:34:6f:ab:a3:21:1b:2b:8e:9f:34:4f:ec: 27 | e8:9a:48:74:81:2f:9b:12:67:54:a1:46:76:96:9a: 28 | 1e:9d:c3:ee:bf:6a:e8:49:72:57:28:b1:12:c4:ca: 29 | 41:84:96:f7:32:4a:4a:9e:59:2d:48:3e:ac:29:0c: 30 | f4:f4:03:28:33:1a:73:10:48:29:68:12:e3:f9:7e: 31 | f4:5f:01:54:b0:73:c6:a8:72:b6:84:54:05:23:36: 32 | b6:db:3f:d8:e5:27:89:4c:dc:bb:b1:c9:9e:e7:7e: 33 | b0:b5 34 | Exponent: 65537 (0x10001) 35 | X509v3 extensions: 36 | X509v3 Key Usage: critical 37 | Digital Signature, Key Encipherment 38 | X509v3 Extended Key Usage: 39 | TLS Web Server Authentication 40 | X509v3 Basic Constraints: critical 41 | CA:FALSE 42 | X509v3 Subject Key Identifier: 43 | B8:E0:AF:4F:7C:48:F3:FF:EB:FC:5E:A5:34:36:2D:56:54:AC:97:6B 44 | X509v3 Authority Key Identifier: 45 | B1:DD:32:5D:E8:B7:37:72:D2:CE:5C:CE:26:FE:47:79:E2:01:08:E9 46 | Authority Information Access: 47 | OCSP - URI:http://ocsp.pki.goog/gts1d2 48 | CA Issuers - URI:http://pki.goog/gsr2/GTS1D2.crt 49 | X509v3 Subject Alternative Name: 50 | DNS:certificate.transparency.dev 51 | X509v3 Certificate Policies: 52 | Policy: 2.23.140.1.2.1 53 | Policy: 1.3.6.1.4.1.11129.2.5.3 54 | X509v3 CRL Distribution Points: 55 | Full Name: 56 | URI:http://crl.pki.goog/GTS1D2.crl 57 | CT Precertificate Poison: critical 58 | NULL 59 | Signature Algorithm: sha256WithRSAEncryption 60 | Signature Value: 61 | 51:fe:93:53:7a:e1:6d:34:ce:a2:1d:4d:32:c5:39:a5:e8:1e: 62 | ee:97:56:33:84:5a:5e:5c:be:13:64:92:66:df:a7:79:82:c8: 63 | 35:c6:4d:8f:ff:da:a1:cc:4d:70:b0:a7:1c:73:69:d5:08:ea: 64 | 53:f4:8e:73:27:5a:9d:5a:c7:39:0a:19:dd:51:21:94:3c:31: 65 | b5:cd:06:2d:50:bf:90:09:3e:62:ca:a3:bf:f2:74:9d:2b:33: 66 | 38:e9:9f:f1:b7:2f:e2:3c:e4:8a:d4:63:57:c7:bd:27:fd:94: 67 | 15:c5:03:82:95:35:79:d6:84:0f:90:01:47:53:af:ed:12:d6: 68 | 9c:63:04:1b:06:83:87:83:a1:34:f0:05:d8:8b:c6:b9:39:ce: 69 | 9c:32:ac:bf:04:d5:8d:b8:2f:ee:61:55:b9:f3:b9:b8:93:c7: 70 | 6d:9c:39:68:b4:39:d8:67:5d:cb:5b:bd:d5:a1:b8:d9:18:16: 71 | 7c:f3:ff:7a:77:d9:cc:68:f3:c8:ee:b4:52:06:37:6c:8e:23: 72 | 69:1c:49:81:1c:08:26:80:a1:05:8b:ed:f5:dc:33:c6:84:7a: 73 | e3:ef:2f:c3:22:02:a0:33:8d:48:61:8a:98:27:34:e8:75:5d: 74 | eb:56:93:a3:be:2e:c5:04:ab:d6:88:cc:53:c6:9c:db:9f:aa: 75 | 5d:eb:c6:82 76 | -----BEGIN CERTIFICATE----- 77 | MIIEZTCCA02gAwIBAgIRAJykB+Il+XzCCgAAAAAgbuUwDQYJKoZIhvcNAQELBQAw 78 | QjELMAkGA1UEBhMCVVMxHjAcBgNVBAoTFUdvb2dsZSBUcnVzdCBTZXJ2aWNlczET 79 | MBEGA1UEAxMKR1RTIENBIDFEMjAeFw0yMDAzMjMxMjIzNDRaFw0yMDA2MjExMjIz 80 | NDRaMCcxJTAjBgNVBAMTHGNlcnRpZmljYXRlLnRyYW5zcGFyZW5jeS5kZXYwggEi 81 | MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCoflnA5TvaPL8EUZHrn2wbz5+Q 82 | 3CKJHLWYJGkuJi1hkgQPLvHa7Oo62cw6guK4On1seXn3NsVSpLtGHS8LbF8AMa8k 83 | 6UobMmMatcMonKcKtXPiwae1HhGuzRl5DGIGz4Dw7eJygru0hA6dyX07+04FSToU 84 | D4aSAUlSLMyg4e+G/hgAg2lskMZ7qULfV5x7YQaAI7JflZUemzRvq6MhGyuOnzRP 85 | 7OiaSHSBL5sSZ1ShRnaWmh6dw+6/auhJclcosRLEykGElvcySkqeWS1IPqwpDPT0 86 | AygzGnMQSCloEuP5fvRfAVSwc8aocraEVAUjNrbbP9jlJ4lM3LuxyZ7nfrC1AgMB 87 | AAGjggFvMIIBazAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEw 88 | DAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUuOCvT3xI8//r/F6lNDYtVlSsl2swHwYD 89 | VR0jBBgwFoAUsd0yXei3N3LSzlzOJv5HeeIBCOkwZAYIKwYBBQUHAQEEWDBWMCcG 90 | CCsGAQUFBzABhhtodHRwOi8vb2NzcC5wa2kuZ29vZy9ndHMxZDIwKwYIKwYBBQUH 91 | MAKGH2h0dHA6Ly9wa2kuZ29vZy9nc3IyL0dUUzFEMi5jcnQwJwYDVR0RBCAwHoIc 92 | Y2VydGlmaWNhdGUudHJhbnNwYXJlbmN5LmRldjAhBgNVHSAEGjAYMAgGBmeBDAEC 93 | ATAMBgorBgEEAdZ5AgUDMC8GA1UdHwQoMCYwJKAioCCGHmh0dHA6Ly9jcmwucGtp 94 | Lmdvb2cvR1RTMUQyLmNybDATBgorBgEEAdZ5AgQDAQH/BAIFADANBgkqhkiG9w0B 95 | AQsFAAOCAQEAUf6TU3rhbTTOoh1NMsU5pege7pdWM4RaXly+E2SSZt+neYLINcZN 96 | j//aocxNcLCnHHNp1QjqU/SOcydanVrHOQoZ3VEhlDwxtc0GLVC/kAk+Ysqjv/J0 97 | nSszOOmf8bcv4jzkitRjV8e9J/2UFcUDgpU1edaED5ABR1Ov7RLWnGMEGwaDh4Oh 98 | NPAF2IvGuTnOnDKsvwTVjbgv7mFVufO5uJPHbZw5aLQ52Gddy1u91aG42RgWfPP/ 99 | enfZzGjzyO60UgY3bI4jaRxJgRwIJoChBYvt9dwzxoR64+8vwyICoDONSGGKmCc0 100 | 6HVd61aTo74uxQSr1ojMU8ac25+qXevGgg== 101 | -----END CERTIFICATE----- 102 | -------------------------------------------------------------------------------- /crates/ct_worker/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2025 Cloudflare, Inc. 2 | // Licensed under the BSD-3-Clause license found in the LICENSE file or at https://opensource.org/licenses/BSD-3-Clause 3 | 4 | #![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/README.md"))] 5 | 6 | use crate::ccadb_roots_cron::{ccadb_roots_filename, update_ccadb_roots, CCADB_ROOTS_NAMESPACE}; 7 | use config::AppConfig; 8 | use ed25519_dalek::SigningKey as Ed25519SigningKey; 9 | use p256::{ecdsa::SigningKey as EcdsaSigningKey, pkcs8::DecodePrivateKey}; 10 | use signed_note::KeyName; 11 | use static_ct_api::StaticCTCheckpointSigner; 12 | use std::collections::HashMap; 13 | use std::sync::{LazyLock, OnceLock}; 14 | use tlog_tiles::{CheckpointSigner, Ed25519CheckpointSigner, SequenceMetadata}; 15 | use tokio::sync::OnceCell; 16 | use worker::{Env, Result}; 17 | use x509_util::CertPool; 18 | 19 | mod batcher_do; 20 | mod ccadb_roots_cron; 21 | mod cleaner_do; 22 | mod frontend_worker; 23 | mod sequencer_do; 24 | 25 | // Application configuration. 26 | static CONFIG: LazyLock = LazyLock::new(|| { 27 | serde_json::from_str::(include_str!(concat!(env!("OUT_DIR"), "/config.json"))) 28 | .expect("Failed to parse config") 29 | }); 30 | 31 | static SIGNING_KEY_MAP: OnceLock>> = OnceLock::new(); 32 | static WITNESS_KEY_MAP: OnceLock>> = OnceLock::new(); 33 | static ROOTS: OnceCell = OnceCell::const_new(); 34 | 35 | pub(crate) fn load_signing_key(env: &Env, name: &str) -> Result<&'static EcdsaSigningKey> { 36 | let once = &SIGNING_KEY_MAP.get_or_init(|| { 37 | CONFIG 38 | .logs 39 | .keys() 40 | .map(|name| (name.clone(), OnceLock::new())) 41 | .collect() 42 | })[name]; 43 | if let Some(key) = once.get() { 44 | Ok(key) 45 | } else { 46 | let key = EcdsaSigningKey::from_pkcs8_pem( 47 | &env.secret(&format!("SIGNING_KEY_{name}"))?.to_string(), 48 | ) 49 | .map_err(|e| e.to_string())?; 50 | Ok(once.get_or_init(|| key)) 51 | } 52 | } 53 | 54 | pub(crate) fn load_witness_key(env: &Env, name: &str) -> Result<&'static Ed25519SigningKey> { 55 | let once = &WITNESS_KEY_MAP.get_or_init(|| { 56 | CONFIG 57 | .logs 58 | .keys() 59 | .map(|name| (name.clone(), OnceLock::new())) 60 | .collect() 61 | })[name]; 62 | if let Some(key) = once.get() { 63 | Ok(key) 64 | } else { 65 | let key = Ed25519SigningKey::from_pkcs8_pem( 66 | &env.secret(&format!("WITNESS_KEY_{name}"))?.to_string(), 67 | ) 68 | .map_err(|e| e.to_string())?; 69 | Ok(once.get_or_init(|| key)) 70 | } 71 | } 72 | 73 | pub(crate) fn load_checkpoint_signers(env: &Env, name: &str) -> Vec> { 74 | let origin = load_origin(name); 75 | let signing_key = load_signing_key(env, name).unwrap().clone(); 76 | let witness_key = load_witness_key(env, name).unwrap().clone(); 77 | 78 | // Make the checkpoint signers from the secret keys and put them in a vec 79 | let signer = StaticCTCheckpointSigner::new(origin.clone(), signing_key) 80 | .map_err(|e| format!("could not create static-ct checkpoint signer: {e}")) 81 | .unwrap(); 82 | let witness = Ed25519CheckpointSigner::new(origin, witness_key) 83 | .map_err(|e| format!("could not create ed25519 checkpoint signer: {e}")) 84 | .unwrap(); 85 | 86 | vec![Box::new(signer), Box::new(witness)] 87 | } 88 | 89 | pub(crate) fn load_origin(name: &str) -> KeyName { 90 | // https://github.com/C2SP/C2SP/blob/main/static-ct-api.md#checkpoints 91 | // The origin line MUST be the submission prefix of the log as a schema-less URL with no trailing slashes. 92 | KeyName::new( 93 | CONFIG.logs[name] 94 | .submission_url 95 | .trim_start_matches("http://") 96 | .trim_start_matches("https://") 97 | .trim_end_matches('/') 98 | .to_string(), 99 | ) 100 | .expect("invalid origin name") 101 | } 102 | 103 | async fn load_roots(env: &Env, name: &str) -> Result<&'static CertPool> { 104 | // Load embedded roots. 105 | ROOTS 106 | .get_or_try_init(|| async { 107 | let pem = include_bytes!(concat!(env!("OUT_DIR"), "/roots.pem")); 108 | let mut pool = CertPool::default(); 109 | // load_pem_chain fails on empty input: https://github.com/RustCrypto/formats/pull/1965 110 | if !pem.is_empty() { 111 | pool.append_certs_from_pem(pem) 112 | .map_err(|e| format!("failed to load PEM chain: {e}"))?; 113 | } 114 | 115 | // Load additional roots from the CCADB roots file in Workers KV. 116 | if CONFIG.logs[name].enable_ccadb_roots { 117 | let key = ccadb_roots_filename(name); 118 | let kv = env.kv(CCADB_ROOTS_NAMESPACE)?; 119 | let pem = if let Some(pem) = kv.get(&key).text().await? { 120 | pem 121 | } else { 122 | // The roots file might not exist if the CCADB roots cron job hasn't 123 | // run yet. Try to create it once before failing. 124 | update_ccadb_roots(&[&key], &kv).await?; 125 | kv.get(&key) 126 | .text() 127 | .await? 128 | .ok_or(format!("{name}: '{key}' not found in KV"))? 129 | }; 130 | pool.append_certs_from_pem(pem.as_bytes()) 131 | .map_err(|e| format!("failed to add CCADB certs to pool: {e}"))?; 132 | } 133 | Ok(pool) 134 | }) 135 | .await 136 | } 137 | -------------------------------------------------------------------------------- /crates/mtc_worker/config.schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "type": "object", 4 | "additionalProperties": false, 5 | "properties": { 6 | "logging_level": { 7 | "type": "string", 8 | "enum": [ 9 | "debug", 10 | "info", 11 | "warn", 12 | "error" 13 | ], 14 | "description": "Log verbosity." 15 | }, 16 | "logs": { 17 | "type": "object", 18 | "description": "Dictionary MTC log shard names to configurations.", 19 | "additionalProperties": false, 20 | "patternProperties": { 21 | "^[a-zA-Z0-9_]+$": { 22 | "type": "object", 23 | "additionalProperties": false, 24 | "properties": { 25 | "description": { 26 | "type": "string", 27 | "description": "Description of the log." 28 | }, 29 | "log_id": { 30 | "type": "string", 31 | "description": "The log ID (a trust anchor ID) in dotted decimal notation (e.g., 32473.1)." 32 | }, 33 | "cosigner_id": { 34 | "type": "string", 35 | "description": "The cosigner's ID (a trust anchor ID) in dotted decimal notation (e.g., 32473.1)." 36 | }, 37 | "max_certificate_lifetime_secs": { 38 | "type": "integer", 39 | "default": 604800, 40 | "description": "The maximum lifetime for issued certificates. The actual lifetime could be less, for example, to fit within the bootstrap certificate's validity window." 41 | }, 42 | "landmark_interval_secs": { 43 | "type": "integer", 44 | "minimum": 1, 45 | "default": 3600, 46 | "description": "The time between publishing landmarks. This is used to calculate `max_landmarks` as `ceil(max_certificate_timetime_secs / landmark_interval_secs) + 1`." 47 | }, 48 | "submission_url": { 49 | "type": "string", 50 | "description": "URL for log submissions." 51 | }, 52 | "monitoring_url": { 53 | "type": "string", 54 | "default": "", 55 | "description": "URL for log monitoring. If unspecified, use the submission URL and the Worker will proxy requests to the R2 bucket." 56 | }, 57 | "location_hint": { 58 | "type": "string", 59 | "description": "Provide a hint to place the log in a specific geographic location. See https://developers.cloudflare.com/durable-objects/reference/data-location/ for supported locations. If unspecified, the Durable Object will be created in proximity to the first request." 60 | }, 61 | "sequence_interval_millis": { 62 | "type": "integer", 63 | "minimum": 100, 64 | "default": 1000, 65 | "description": "The duration in between sequencing operations, in milliseconds." 66 | }, 67 | "max_sequence_skips": { 68 | "type": "integer", 69 | "minimum": 0, 70 | "default": 0, 71 | "description": "The maximum number of times sequencing can be skipped to avoid creating partial tiles. If non-zero, pending entries may be delayed by either a multiple of the sequence interval or sequence_skip_threshold_millis if set." 72 | }, 73 | "sequence_skip_threshold_millis": { 74 | "type": "integer", 75 | "minimum": 0, 76 | "description": "If provided, entries will only be skipped by sequencing (when max_sequenced_skips is non-zero) if they have been in the pool for less than this timeout." 77 | }, 78 | "num_batchers": { 79 | "type": "integer", 80 | "minimum": 0, 81 | "default": 8, 82 | "maximum": 255, 83 | "description": "The number of batchers to use to proxy requests to the sequencer. If zero, requests from the frontend worker go directly to the sequencer." 84 | }, 85 | "batch_timeout_millis": { 86 | "type": "integer", 87 | "minimum": 100, 88 | "default": 100, 89 | "description": "The maximum duration to wait before submitting a batch to the sequencer, in milliseconds." 90 | }, 91 | "max_batch_entries": { 92 | "type": "integer", 93 | "minimum": 1, 94 | "default": 256, 95 | "description": "The maximum number of entries per batch." 96 | }, 97 | "clean_interval_secs": { 98 | "type": "integer", 99 | "minimum": 1, 100 | "default": 60, 101 | "description": "How long to wait in between runs of the partial tile cleaner." 102 | } 103 | }, 104 | "required": [ 105 | "log_id", 106 | "submission_url" 107 | ] 108 | } 109 | } 110 | } 111 | }, 112 | "required": [ 113 | "logs" 114 | ] 115 | } 116 | -------------------------------------------------------------------------------- /crates/tlog_tiles/src/cosignature_v1.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2025 Cloudflare, Inc. 2 | // Licensed under the BSD-3-Clause license found in the LICENSE file or at https://opensource.org/licenses/BSD-3-Clause 3 | use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; 4 | use ed25519_dalek::{ 5 | Signer as Ed25519Signer, SigningKey as Ed25519SigningKey, Verifier as Ed25519Verifier, 6 | VerifyingKey as Ed25519VerifyingKey, 7 | }; 8 | use signed_note::{compute_key_id, KeyName, NoteError, NoteSignature, NoteVerifier, SignatureType}; 9 | 10 | use crate::{CheckpointText, CheckpointSigner, UnixTimestamp}; 11 | 12 | /// Implementation of [`CheckpointSigner`] that produces a timestamped Ed25519 cosignature/v1 (alg 0x04 from ). 13 | pub struct CosignatureV1CheckpointSigner { 14 | v: CosignatureV1NoteVerifier, 15 | k: Ed25519SigningKey, 16 | } 17 | 18 | impl CosignatureV1CheckpointSigner { 19 | /// Returns a new `CosignatureV1CheckpointSigner`. 20 | pub fn new(name: KeyName, k: Ed25519SigningKey) -> Self { 21 | Self { 22 | v: CosignatureV1NoteVerifier::new(name, k.verifying_key()), 23 | k, 24 | } 25 | } 26 | } 27 | 28 | impl CheckpointSigner for CosignatureV1CheckpointSigner { 29 | fn name(&self) -> &KeyName { 30 | self.v.name() 31 | } 32 | 33 | fn key_id(&self) -> u32 { 34 | self.v.key_id() 35 | } 36 | 37 | fn sign( 38 | &self, 39 | timestamp_unix_millis: UnixTimestamp, 40 | checkpoint: &CheckpointText, 41 | ) -> Result { 42 | // Timestamp is in seconds. 43 | let timestamp_unix_secs = timestamp_unix_millis / 1000; 44 | let mut msg = format!("cosignature/v1\ntime {timestamp_unix_secs}\n").into_bytes(); 45 | msg.extend(checkpoint.to_bytes()); 46 | 47 | // Ed25519 signing cannot fail 48 | let sig = self.k.try_sign(&msg).unwrap(); 49 | 50 | // Now format the final signature according to . 51 | // struct timestamped_signature { 52 | // u64 timestamp; 53 | // u8 signature[64]; 54 | // } 55 | let mut note_sig = Vec::new(); 56 | note_sig 57 | .write_u64::(timestamp_unix_secs) 58 | .unwrap(); 59 | note_sig.extend(&sig.to_bytes()); 60 | 61 | // Return the note signature. 62 | Ok(NoteSignature::new( 63 | self.name().clone(), 64 | self.key_id(), 65 | note_sig, 66 | )) 67 | } 68 | 69 | fn verifier(&self) -> Box { 70 | Box::new(self.v.clone()) 71 | } 72 | } 73 | 74 | /// [`CosignatureV1NoteVerifier`] is the verifier for the timestamped Ed25519 cosignature type defined in . 75 | #[derive(Clone)] 76 | pub struct CosignatureV1NoteVerifier { 77 | name: KeyName, 78 | id: u32, 79 | verifying_key: Ed25519VerifyingKey, 80 | } 81 | 82 | impl CosignatureV1NoteVerifier { 83 | pub fn new(name: KeyName, verifying_key: Ed25519VerifyingKey) -> Self { 84 | let id = { 85 | let pubkey = [ 86 | &[SignatureType::CosignatureV1 as u8], 87 | verifying_key.to_bytes().as_slice(), 88 | ] 89 | .concat(); 90 | compute_key_id(&name, &pubkey) 91 | }; 92 | Self { 93 | name, 94 | id, 95 | verifying_key, 96 | } 97 | } 98 | } 99 | 100 | impl NoteVerifier for CosignatureV1NoteVerifier { 101 | fn name(&self) -> &KeyName { 102 | &self.name 103 | } 104 | 105 | fn key_id(&self) -> u32 { 106 | self.id 107 | } 108 | 109 | fn verify(&self, msg: &[u8], mut sig: &[u8]) -> bool { 110 | // The message itself should be a valid checkpoint. 111 | let Ok(checkpoint) = CheckpointText::from_bytes(msg) else { 112 | return false; 113 | }; 114 | // timestamped_signature.timestamp 115 | let Ok(sig_timestamp) = sig.read_u64::() else { 116 | return false; 117 | }; 118 | // timestamped_signature.signature 119 | let sig_bytes: [u8; ed25519_dalek::SIGNATURE_LENGTH] = match sig.try_into() { 120 | Ok(ok) => ok, 121 | Err(_) => return false, 122 | }; 123 | 124 | // Construct message to be signed from . 125 | let mut msg = format!("cosignature/v1\ntime {sig_timestamp}\n").into_bytes(); 126 | msg.extend(checkpoint.to_bytes()); 127 | self.verifying_key 128 | .verify(&msg, &ed25519_dalek::Signature::from_bytes(&sig_bytes)) 129 | .is_ok() 130 | } 131 | 132 | fn extract_timestamp_millis(&self, mut sig: &[u8]) -> Result, NoteError> { 133 | // The timestamp is the first 8 bytes of the signature, and is in seconds. 134 | let ts = sig 135 | .read_u64::() 136 | .map_err(|_| NoteError::Timestamp)?; 137 | Ok(Some(ts * 1000)) 138 | } 139 | } 140 | 141 | #[cfg(test)] 142 | mod tests { 143 | 144 | use crate::{open_checkpoint, record_hash, TreeWithTimestamp}; 145 | 146 | use super::*; 147 | use rand::rngs::OsRng; 148 | use signed_note::VerifierList; 149 | 150 | #[test] 151 | fn test_cosignature_v1_sign_verify() { 152 | let mut rng = OsRng; 153 | 154 | let origin = "example.com/origin"; 155 | let timestamp = 100; 156 | let tree_size = 4; 157 | 158 | // Make a tree head and sign it 159 | let tree = TreeWithTimestamp::new(tree_size, record_hash(b"hello world"), timestamp); 160 | let signer = { 161 | let sk = Ed25519SigningKey::generate(&mut rng); 162 | let name = KeyName::new("my-signer".into()).unwrap(); 163 | CosignatureV1CheckpointSigner::new(name, sk) 164 | }; 165 | let checkpoint = tree.sign(origin, &[], &[&signer], &mut rng).unwrap(); 166 | 167 | // Now verify the signed checkpoint 168 | let verifier = signer.verifier(); 169 | open_checkpoint( 170 | origin, 171 | &VerifierList::new(vec![verifier]), 172 | timestamp, 173 | &checkpoint, 174 | ) 175 | .unwrap(); 176 | } 177 | } 178 | --------------------------------------------------------------------------------