From be8283e51fb3a8d04ea95b0b998f1fcd01fb1140 Mon Sep 17 00:00:00 2001 From: Preston Locke Date: Wed, 9 Sep 2026 12:23:24 -0500 Subject: [PATCH 1/5] Move tooling into root of the repo --- tooling/.gitignore => .gitignore | 0 tooling/add-roots.sh => add-roots.sh | 0 {tooling/ct_meta_py => ct_meta_py}/__init__.py | 0 ...pull-roots-from-all-shards.sh => pull-roots-from-all-shards.sh | 0 tooling/source_me.sh => source_me.sh | 0 tooling/update_accepted_roots.py => update_accepted_roots.py | 0 6 files changed, 0 insertions(+), 0 deletions(-) rename tooling/.gitignore => .gitignore (100%) rename tooling/add-roots.sh => add-roots.sh (100%) rename {tooling/ct_meta_py => ct_meta_py}/__init__.py (100%) rename tooling/pull-roots-from-all-shards.sh => pull-roots-from-all-shards.sh (100%) rename tooling/source_me.sh => source_me.sh (100%) rename tooling/update_accepted_roots.py => update_accepted_roots.py (100%) diff --git a/tooling/.gitignore b/.gitignore similarity index 100% rename from tooling/.gitignore rename to .gitignore diff --git a/tooling/add-roots.sh b/add-roots.sh similarity index 100% rename from tooling/add-roots.sh rename to add-roots.sh diff --git a/tooling/ct_meta_py/__init__.py b/ct_meta_py/__init__.py similarity index 100% rename from tooling/ct_meta_py/__init__.py rename to ct_meta_py/__init__.py diff --git a/tooling/pull-roots-from-all-shards.sh b/pull-roots-from-all-shards.sh similarity index 100% rename from tooling/pull-roots-from-all-shards.sh rename to pull-roots-from-all-shards.sh diff --git a/tooling/source_me.sh b/source_me.sh similarity index 100% rename from tooling/source_me.sh rename to source_me.sh diff --git a/tooling/update_accepted_roots.py b/update_accepted_roots.py similarity index 100% rename from tooling/update_accepted_roots.py rename to update_accepted_roots.py From 868707c135aeee31b206f8961a7cfc9a73eb6402 Mon Sep 17 00:00:00 2001 From: Preston Locke Date: Wed, 9 Sep 2026 12:49:05 -0500 Subject: [PATCH 2/5] Rename scripts to be more accurate --- update_accepted_roots.py => build_bundle.py | 0 pull-roots-from-all-shards.sh => pull-roots.sh | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename update_accepted_roots.py => build_bundle.py (100%) rename pull-roots-from-all-shards.sh => pull-roots.sh (100%) diff --git a/update_accepted_roots.py b/build_bundle.py similarity index 100% rename from update_accepted_roots.py rename to build_bundle.py diff --git a/pull-roots-from-all-shards.sh b/pull-roots.sh similarity index 100% rename from pull-roots-from-all-shards.sh rename to pull-roots.sh From 8e3a40651feae6e752d3fc82b321f90b7acdca60 Mon Sep 17 00:00:00 2001 From: Preston Locke Date: Wed, 9 Sep 2026 12:39:02 -0500 Subject: [PATCH 3/5] Merge tooling readme contents into root readme --- README.md | 51 +++++++++++++++++++++++------------------------ tooling/README.md | 41 ------------------------------------- 2 files changed, 25 insertions(+), 67 deletions(-) delete mode 100644 tooling/README.md diff --git a/README.md b/README.md index 497e12a..08531b2 100644 --- a/README.md +++ b/README.md @@ -1,42 +1,41 @@ # Let's Encrypt Certificate Transparency Logs -This repository contains all Root Certificate Authorities from whom [Let's Encrypt's Certificate Transparency Logs](https://letsencrypt.org/docs/ct-logs/) accept leaf certificates. +This repository contains all root CAs from whom [Let's Encrypt's Certificate Transparency Logs](https://letsencrypt.org/docs/ct-logs/) accept leaf certificates. -Let's Encrypt operates two publicly-accessible [Certificate Transparency](https://www.certificate-transparency.org/what-is-ct) logs: -* **Oak** -* **Sapling** +Certificates are stored in the `roots/` directory: -## Oak +1. The `roots/common/` directory, which is included in bundles for testing and production logs. -Oak is a production log, containing only certificates which are trusted by the [Mozilla Root Program](https://www.mozilla.org/en-US/about/governance/policies/security-group/certs/policy/). +2. The `roots/testing/` directory, which is only included in the bundle for testing logs. -## Sapling - -Sapling is a preproduction log, intended for certificates which are not publicly trusted, but which are issued by Certificate Authorities who either issue or are expected to issue publicly trusted certificates. In other words, Sapling is used by trusted Certificate Authorities in their testing infrastructures. +# Submitting a CA root for inclusion -## Testflume +First, you'll need to install [certigo](https://github.com/square/certigo). -[Testflume no longer exists](https://groups.google.com/a/chromium.org/g/ct-policy/c/CLBlt5rSsAk) and has been replaced by the Sapling test log. +Next, fork this GitHub repository and add your root certificate(s) to the store: -## ct-test-srv +``` +./add-roots.sh roots/testing example_new_ca.pem +``` -The [Boulder](https://github.com/letsencrypt/boulder/tree/main/test/ct-test-srv) codebase contains a piece of software named `ct-test-srv` which implements RFC6962 `add-chain` and `add-pre-chain` endpoints. This software is sufficient for development and other testing environments. It does not persist data. +Create a [new pull +request](https://github.com/letsencrypt/ct-log-metadata/compare) and fill out +the provided template. All communication will be performed via responses to your +PR. -# Submitting a CA root for inclusion +Upon approval, Let's Encrypt staff will merge your change, which publishes new +accepted-roots bundles (see below). Rolling a new bundle out to our running CT +logs is a separate, SRE-gated process and isn't automatic. -Create a [New Issue](https://github.com/letsencrypt/ct-log-metadata/issues/new/choose) and fill out the provided template. All communication will be performed via responses to your Github Issue. Upon approval, Let's Encrypt staff will create a Pull Request to include your certificates and update our Certificate Transparency logs. +# Other tooling -# What roots does a log contain? +To dump all root certificates a log currently accepts: -Calling the `get-roots` endpoint for a [Trillian](https://github.com/google/trillian) backed log will return a JSON structure containing each root as base64 encoded DER. - -Example retrieving all the roots from a CT log and viewing certificate content: ``` -counter=1 -for root in $(curl -sL https://oak.ct.letsencrypt.org/2023/ct/v1/get-roots | jq -r '.certificates[]'); do - echo -n "${root}" | base64 -d > /tmp/${counter}.crt - counter=$((counter+1)) -done - -openssl x509 -inform DER -in /tmp/${counter}.crt -noout -issuer -serial +mkdir /tmp/downloaded-roots/ +./pull-roots.sh https://log.twig.ct.letsencrypt.org/2026h2/ /tmp/downloaded-roots/ ``` + +The `build_bundle.py` script is used in CI by the release build process. You +shouldn't need to use it directly for submitting a new root. + diff --git a/tooling/README.md b/tooling/README.md deleted file mode 100644 index 59fdba8..0000000 --- a/tooling/README.md +++ /dev/null @@ -1,41 +0,0 @@ -# Overview - -We decided to store the issuer files with human readable names, rather than the serial or fingerprint. This decision is completely arbitrary. - -Certificates for the logs come from three places: - -1. The "[PEM of Root Certificates in Mozilla’s Root Store with the Websites (TLS/SSL) Trust Bit Enabled (CSV)](https://ccadb-public.secure.force.com/mozilla/IncludedRootsDistrustTLSSSLPEMCSV?TrustBitsInclude=Websites)" export from ccadb.org - Note: The version used is also stored in `accepted_roots` for ease of evaluation. - -2. The `additional_roots/common/` directory - -3. The `additional_roots/${log_name}/` directory (currently only `sapling`) - -To use the tooling in this folder, you will need [certigo](https://github.com/square/certigo). - -# Usage - -Create a new accepted roots file from the root certificates in each respective logs folder. This will not apply the change to a running shard. That work is done in another repository and requires SRE change control. -``` -./update_accepted_roots.py --log oak --shard 2022 -``` - -Add a root certificate to an `additional_roots` subdirectory. -``` -./add-root-to-log.sh additional_roots/common example_new_ca.pem -``` - -To get all root certificates currently applied to a log shard: - -``` -./pull-roots-from-all-shards.sh -``` - -To perform analysis on roots pulled from each shard: -``` -./diff_accepted_roots.py oak/tmp.FOO-2023 oak/tmp.BAR-2024h1 -``` - -## Notes - -When adding a new shard, the scripts will need to be updated to account for the new shard. From 9af02d0f2be686a51f1391bf5e15ee72af7f2fd5 Mon Sep 17 00:00:00 2001 From: Preston Locke Date: Wed, 9 Sep 2026 12:40:17 -0500 Subject: [PATCH 4/5] Update lints to run on root dir --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ed4877a..c17b602 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,14 +23,14 @@ jobs: - name: Analysing the code with pylint run: | - python -m pylint -E tooling/ + python -m pylint -E . - name: Checking format with Black run: | - python -m black --check tooling/ + python -m black --check . - name: Checking format with Flake8 run: | - python -m flake8 tooling/ + python -m flake8 . From 2a9accd1dd9ff24deb601c2fca59eef64ca2f341 Mon Sep 17 00:00:00 2001 From: Preston Locke Date: Wed, 9 Sep 2026 13:20:17 -0500 Subject: [PATCH 5/5] Update dependencies in linting workflow --- .github/workflows/ci.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c17b602..0d74786 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,18 +8,18 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - name: Set up Python 3.9 - uses: actions/setup-python@v2 + - uses: actions/checkout@v7 + - name: Set up Python 3.14 + uses: actions/setup-python@v7 with: - python-version: 3.9 + python-version: 3.14 - name: Install Linting Tools run: | python -m pip install --upgrade pip - pip install --user pylint==2.6.0 - pip install --user black~=22.3 - pip install --user flake8~=4.0 + pip install --user pylint==4.0.8 + pip install --user black==26.5.1 + pip install --user flake8==7.3.0 - name: Analysing the code with pylint run: |