Skip to content

Commit 6239872

Browse files
committed
Add ability to automatically fetch roots from CCADB, fixes #65
- Add a cron job that once a day fetches roots from CCADB that are included in root stores and saves them into per-log root files (only appending certs for each log shard, but never removing them). This is similar to the sunlight implementation. - Add config flag (enabled by default) to specify if a log will attempt to load its roots from the CCADB roots file in Workers KV. - Remove roots.default.pem and require per-environment roots files to be non-empty if they exist. These can be used to add additional accepted roots on top of the CCADB list, for instance, to allow test/staging CAs to submit log entries. - Add more functionality to x509util::CertPool to support adding and checking inclusion of certs in an existing CertPool.
1 parent c289a56 commit 6239872

File tree

16 files changed

+353
-19826
lines changed

16 files changed

+353
-19826
lines changed

Cargo.lock

Lines changed: 26 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,14 @@ debug = 1
3232
[workspace.dependencies]
3333
anyhow = "1.0"
3434
base64 = "0.22"
35+
base64ct = "1.8.0"
3536
bitcode = { version = "0.6.6", features = ["serde"] }
3637
byteorder = "1.5"
3738
chrono = { version = "0.4", features = ["serde"] }
3839
console_error_panic_hook = "0.1.1"
3940
console_log = { version = "1.0" }
4041
criterion = { version = "0.5", features = ["html_reports"] }
42+
csv = "1.3.1"
4143
generic_log_worker = { path = "crates/generic_log_worker", version = "0.2.0" }
4244
der = "0.7.10"
4345
ed25519-dalek = { version = "2.1.1", features = ["pem"] }

crates/ct_worker/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,9 @@ worker.workspace = true
5858
x509-cert.workspace = true
5959
x509_util.workspace = true
6060
prometheus.workspace = true
61+
chrono.workspace = true
62+
base64ct.workspace = true
63+
csv.workspace = true
6164

6265
[lints.rust]
6366
unexpected_cfgs = { level = "warn", check-cfg = [

crates/ct_worker/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ Follow these instructions to deploy to a custom domain, suitable for running a p
150150
151151
1. Create a file `config.${ENV}.json` with the configuration for the log shards.
152152
153-
1. (Optional) Create a file `roots.${ENV}.pem` with any custom accepted roots for the log shards. By default, `roots.default.pem` will be used. All logs shards deployed within the same Worker script use the same set of roots. Roots can be updated later.
153+
1. (Optional) Create a file `roots.${ENV}.pem` with any custom accepted roots for the log shards, in PEM format. If `enable_ccadb_roots` is set to true, these roots are used in addition to roots auto-pulled from the CCADB list. If `enable_ccadb_roots` is set to false for any logs in the deployment, `roots.${ENV}.pem` is required to exist and contain at least one certificate. All logs shards deployed within the same Worker script use the same set of additional roots. Roots can be updated later, but roots should generally not be removed once added.
154154
155155
1. First set environment variables to specify the log shard name and deployment environment as below and then follow the [instructions above](#deployment-to-a-workersdev-subdomain) to create resources for each log shard.
156156

crates/ct_worker/build.rs

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ fn main() {
3232
let conf = serde_json::from_str::<AppConfig>(config_contents).unwrap_or_else(|e| {
3333
panic!("failed to deserialize JSON config '{config_file}': {e}");
3434
});
35-
for (name, params) in conf.logs {
35+
for (name, params) in &conf.logs {
3636
// Chrome's CT policy (https://googlechrome.github.io/CertificateTransparency/log_policy.html) states:
3737
// "The certificate expiry ranges for CT Logs must be no longer than one calendar year and should be no shorter than six months."
3838
assert!(
@@ -58,20 +58,35 @@ fn main() {
5858
}
5959
}
6060

61-
// Get and validate roots. Use 'roots.default.pem' if no environment-specific roots file is found.
62-
let mut roots_file: &str = &format!("roots.{env}.pem");
63-
if !fs::exists(roots_file).expect("failed to check if file exists") {
64-
roots_file = "roots.default.pem";
61+
// Get and validate roots from an embedded roots file, which must exist but
62+
// can be empty unless 'enable_ccadb_roots' is set to false. If
63+
// 'enable_ccadb_roots' is set to true, the log shard will combine trusted
64+
// roots from the embedded roots file and from a roots file loaded from
65+
// Workers KV.
66+
let roots_file: &str = &format!("roots.{env}.pem");
67+
let roots_file_exists = fs::exists(roots_file).expect("failed to check if file exists");
68+
// If 'enable_ccadb_roots' is
69+
if roots_file_exists {
70+
let roots =
71+
Certificate::load_pem_chain(&fs::read(roots_file).expect("failed to read roots file"))
72+
.expect("unable to decode certificates");
73+
assert!(
74+
!roots.is_empty(),
75+
"roots file does not contain any certificates"
76+
);
77+
} else if conf.logs.values().any(|params| !params.enable_ccadb_roots) {
78+
// If any log shards have 'enable_ccadb_roots' set to false, require a roots file.
79+
panic!("{roots_file} must exist and contain at least one certificate if any logs have 'enable_ccadb_roots' set to false");
6580
}
66-
let roots =
67-
Certificate::load_pem_chain(&fs::read(roots_file).expect("failed to read roots file"))
68-
.expect("unable to decode certificates");
69-
assert!(roots.len() > 50, "Roots file has too few entries");
7081

7182
// Copy to OUT_DIR.
7283
let out_dir = env::var("OUT_DIR").unwrap();
7384
fs::copy(config_file, format!("{out_dir}/config.json")).expect("failed to copy config file");
74-
fs::copy(roots_file, format!("{out_dir}/roots.pem")).expect("failed to copy roots file");
85+
if roots_file_exists {
86+
fs::copy(roots_file, format!("{out_dir}/roots.pem")).expect("failed to copy roots file");
87+
} else {
88+
fs::write(format!("{out_dir}/roots.pem"), "").expect("failed to write empty roots file");
89+
}
7590

7691
println!("cargo::rerun-if-env-changed=DEPLOY_ENV");
7792
println!("cargo::rerun-if-changed=config.schema.json");

crates/ct_worker/config.schema.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,11 @@
108108
"type": "boolean",
109109
"default": true,
110110
"description": "Enables checking the deduplication cache for add-(pre-)chain requests. Can be disabled for tests and benchmarks. If disabled, `kv_namespaces` can be omitted from `wrangler.jsonc`."
111+
},
112+
"enable_ccadb_roots": {
113+
"type": "boolean",
114+
"default": true,
115+
"description": "Enables loading root store trusted roots from the CCADB list, in addition to any roots configured in `roots.<env>.pem`. If enabled, requires a KV namespace with the binding `ccadb_roots` to be configured in `wrangler.jsonc`, as well as a cron trigger so that the CCADB list auto-updates."
111116
}
112117
},
113118
"required": [

crates/ct_worker/config/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ pub struct LogParams {
4040
pub max_batch_entries: usize,
4141
#[serde(default = "default_bool::<true>")]
4242
pub enable_dedup: bool,
43+
#[serde(default = "default_bool::<true>")]
44+
pub enable_ccadb_roots: bool,
4345
}
4446

4547
fn default_bool<const V: bool>() -> bool {

0 commit comments

Comments
 (0)