Skip to content

Commit 3c8b66b

Browse files
committed
mtc_worker: Update log and cosigner ID
Assign all deployed logs ("dev1", "dev2", and "shard1") a distinct OID from the space on Cloudflare's PEN allocated for MTC experiments. Also, use a distinct OID for the cosigner for each log. (Each has its own secret key.) This is a breaking change that will require a migration to first delete all durable object data. The reason is that signatures for existing artifacts no longer verify: ``` dev2: Initializing log from fetch handler dev2: Log exists, not creating ▲ [WARNING] dev2 failed to submit timed-out batch: Error: Error: invalid signature for key localhost:8787/logs/dev2+f0b8bc08 - Cause: Error: invalid signature for key localhost:8787/logs/dev2+f0b8bc08 [wrangler:info] POST /logs/dev2/add-entry 503 Service Unavailable (1069ms) ✘ [ERROR] Uncaught invalid signature for key localhost:8787/logs/dev2+f0b8bc08 ✘ [ERROR] Uncaught Error: invalid signature for key localhost:8787/logs/dev2+f0b8bc08 ``` Alternatively, we could spin down these logs and spin up new ones.
1 parent 3bcfa5e commit 3c8b66b

File tree

9 files changed

+62
-45
lines changed

9 files changed

+62
-45
lines changed

crates/mtc_api/src/subtree_cosignature.rs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use ed25519_dalek::{
99
};
1010
use length_prefixed::WriteLengthPrefixedBytesExt;
1111
use signed_note::{compute_key_id, KeyName, NoteError, NoteSignature, NoteVerifier, SignatureType};
12-
use tlog_tiles::{CheckpointText, CheckpointSigner, Hash, LeafIndex, UnixTimestamp};
12+
use tlog_tiles::{CheckpointSigner, CheckpointText, Hash, LeafIndex, UnixTimestamp};
1313

1414
#[derive(Clone)]
1515
pub struct TrustAnchorID(pub Vec<u8>);
@@ -56,6 +56,21 @@ impl MTCSubtreeCosigner {
5656

5757
Ok(self.k.try_sign(&serialized)?.to_vec())
5858
}
59+
60+
/// Return the log ID as bytes.
61+
pub fn log_id(&self) -> &[u8] {
62+
&self.v.log_id.0
63+
}
64+
65+
/// Return the cosigner ID as bytes.
66+
pub fn cosigner_id(&self) -> &[u8] {
67+
&self.v.cosigner_id.0
68+
}
69+
70+
/// Return the verifying key as bytes.
71+
pub fn verifying_key(&self) -> &[u8] {
72+
self.v.verifying_key.as_bytes()
73+
}
5974
}
6075

6176
/// Support signing tlog-checkpoint with the subtree cosigner.

crates/mtc_worker/config.bootstrap-mtca.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
"logs": {
44
"shard1": {
55
"description": "Cloudflare bootstrap MTCA shard 1",
6-
"log_id": "13335.1",
6+
"log_id": "44363.48.5",
7+
"cosigner_id": "44363.48.6",
78
"submission_url": "https://bootstrap-mtca.cloudflareresearch.com/logs/shard1/",
89
"monitoring_url": "https://bootstrap-mtca-shard1.cloudflareresearch.com"
910
}

crates/mtc_worker/config.dev.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,15 @@
33
"logs": {
44
"dev1": {
55
"description": "MTCA Dev1",
6-
"log_id": "13335.1",
6+
"log_id": "44363.48.1",
7+
"cosigner_id": "44363.48.2",
78
"submission_url": "http://localhost:8787/logs/dev1/",
89
"location_hint": "enam"
910
},
1011
"dev2": {
1112
"description": "MTCA Dev2",
12-
"log_id": "13335.2",
13+
"log_id": "44363.48.3",
14+
"cosigner_id": "44363.48.4",
1315
"submission_url": "http://localhost:8787/logs/dev2/",
1416
"location_hint": "enam",
1517
"max_certificate_lifetime_secs": 100,

crates/mtc_worker/config.schema.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,11 @@
2828
},
2929
"log_id": {
3030
"type": "string",
31-
"description": "The log name (a trust anchor ID) in dotted decimal notation (e.g., 32473.1)."
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)."
3236
},
3337
"max_certificate_lifetime_secs": {
3438
"type": "integer",

crates/mtc_worker/config/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ pub struct AppConfig {
1515
pub struct LogParams {
1616
pub description: Option<String>,
1717
pub log_id: String,
18+
pub cosigner_id: String,
1819
#[serde(default = "default_usize::<604_800>")]
1920
pub max_certificate_lifetime_secs: usize,
2021
#[serde(default = "default_usize::<3600>")]

crates/mtc_worker/src/cleaner_do.rs

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
use std::time::Duration;
22

3-
use crate::{load_checkpoint_signers, load_origin, CONFIG};
3+
use crate::{load_cosigner, load_origin, CONFIG};
44
use generic_log_worker::{get_durable_object_name, CleanerConfig, GenericCleaner, CLEANER_BINDING};
55
use mtc_api::BootstrapMtcPendingLogEntry;
66
use signed_note::VerifierList;
7-
use tlog_tiles::PendingLogEntry;
7+
use tlog_tiles::{CheckpointSigner, PendingLogEntry};
88
#[allow(clippy::wildcard_imports)]
99
use worker::*;
1010

@@ -26,12 +26,7 @@ impl DurableObject for Cleaner {
2626
origin: load_origin(name),
2727
data_path: BootstrapMtcPendingLogEntry::DATA_TILE_PATH,
2828
aux_path: BootstrapMtcPendingLogEntry::AUX_TILE_PATH,
29-
verifiers: VerifierList::new(
30-
load_checkpoint_signers(&env, name)
31-
.iter()
32-
.map(|s| s.verifier())
33-
.collect(),
34-
),
29+
verifiers: VerifierList::new(vec![load_cosigner(&env, name).verifier()]),
3530
clean_interval: Duration::from_secs(params.clean_interval_secs),
3631
};
3732

crates/mtc_worker/src/frontend_worker.rs

Lines changed: 14 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,7 @@
33

44
//! Entrypoint for the static CT submission APIs.
55
6-
use crate::{
7-
load_checkpoint_signers, load_origin, load_roots, load_signing_key, SequenceMetadata, CONFIG,
8-
};
6+
use crate::{load_cosigner, load_origin, load_roots, SequenceMetadata, CONFIG};
97
use der::{
108
asn1::{SetOfVec, UtcTime, Utf8StringRef},
119
Any, Encode, Tag,
@@ -24,13 +22,13 @@ use mtc_api::{
2422
serialize_signatureless_cert, AddEntryRequest, AddEntryResponse, BootstrapMtcLogEntry,
2523
GetRootsResponse, LandmarkSequence, ID_RDNA_TRUSTANCHOR_ID, LANDMARK_KEY,
2624
};
27-
use p256::pkcs8::EncodePublicKey;
2825
use serde::{Deserialize, Serialize};
2926
use serde_with::{base64::Base64, serde_as};
30-
use signed_note::{NoteVerifier, VerifierList};
27+
use signed_note::VerifierList;
3128
use std::time::Duration;
3229
use tlog_tiles::{
33-
open_checkpoint, CheckpointText, LeafIndex, PendingLogEntry, PendingLogEntryBlob,
30+
open_checkpoint, CheckpointSigner, CheckpointText, LeafIndex, PendingLogEntry,
31+
PendingLogEntryBlob,
3432
};
3533
#[allow(clippy::wildcard_imports)]
3634
use worker::*;
@@ -48,7 +46,11 @@ struct MetadataResponse<'a> {
4846
#[serde(skip_serializing_if = "Option::is_none")]
4947
description: &'a Option<String>,
5048
#[serde_as(as = "Base64")]
51-
key: &'a [u8],
49+
log_id: &'a [u8],
50+
#[serde_as(as = "Base64")]
51+
cosigner_id: &'a [u8],
52+
#[serde_as(as = "Base64")]
53+
cosigner_public_key: &'a [u8],
5254
submission_url: &'a str,
5355
monitoring_url: &'a str,
5456
}
@@ -222,13 +224,12 @@ async fn main(req: Request, env: Env, _ctx: Context) -> Result<Response> {
222224
.get("/logs/:log/metadata", |_req, ctx| {
223225
let name = ctx.data;
224226
let params = &CONFIG.logs[name];
225-
let verifying_key = load_signing_key(&ctx.env, name)?.verifying_key();
226-
let key = verifying_key
227-
.to_public_key_der()
228-
.map_err(|e| e.to_string())?;
227+
let cosigner = load_cosigner(&ctx.env, name);
229228
Response::from_json(&MetadataResponse {
230229
description: &params.description,
231-
key: key.as_bytes(),
230+
log_id: cosigner.log_id(),
231+
cosigner_id: cosigner.cosigner_id(),
232+
cosigner_public_key: cosigner.verifying_key(),
232233
submission_url: &params.submission_url,
233234
monitoring_url: if params.monitoring_url.is_empty() {
234235
&params.submission_url
@@ -461,12 +462,7 @@ async fn get_current_checkpoint(
461462
.ok_or("no checkpoint in object storage".to_string())?;
462463

463464
let origin = &load_origin(name);
464-
let verifiers = &VerifierList::new(
465-
load_checkpoint_signers(env, name)
466-
.iter()
467-
.map(|s| s.verifier())
468-
.collect::<Vec<Box<dyn NoteVerifier>>>(),
469-
);
465+
let verifiers = &VerifierList::new(vec![load_cosigner(env, name).verifier()]);
470466
let (checkpoint, _timestamp) =
471467
open_checkpoint(origin.as_str(), verifiers, now_millis(), &checkpoint_bytes)
472468
.map_err(|e| e.to_string())?;

crates/mtc_worker/src/lib.rs

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use signed_note::KeyName;
1212
use std::collections::HashMap;
1313
use std::str::FromStr;
1414
use std::sync::{LazyLock, OnceLock};
15-
use tlog_tiles::{CheckpointSigner, SequenceMetadata};
15+
use tlog_tiles::SequenceMetadata;
1616
use tokio::sync::OnceCell;
1717
#[allow(clippy::wildcard_imports)]
1818
use worker::*;
@@ -59,23 +59,26 @@ pub(crate) fn load_ed25519_key(
5959
}
6060
}
6161

62-
pub(crate) fn load_checkpoint_signers(env: &Env, name: &str) -> Vec<Box<dyn CheckpointSigner>> {
62+
pub(crate) fn load_cosigner(env: &Env, name: &str) -> MTCSubtreeCosigner {
6363
let origin = load_origin(name);
6464

6565
// Parse the log ID, an ASN.1 `RELATIVE OID` in decimal-dotted string form.
66-
let log_id_relative_oid = RelativeOid::from_str(&CONFIG.logs[name].log_id).unwrap();
66+
let log_id = {
67+
let relative_oid = RelativeOid::from_str(&CONFIG.logs[name].log_id).unwrap();
68+
// Get the BER/DER serialization of the content bytes, as described in
69+
// <https://datatracker.ietf.org/doc/html/draft-ietf-tls-trust-anchor-ids-01#name-trust-anchor-identifiers>.
70+
TrustAnchorID(relative_oid.as_bytes().to_vec())
71+
};
72+
73+
// Likewise for the cosigner ID.
74+
let cosigner_id = {
75+
let relative_oid = RelativeOid::from_str(&CONFIG.logs[name].cosigner_id).unwrap();
76+
TrustAnchorID(relative_oid.as_bytes().to_vec())
77+
};
6778

68-
// Get the BER/DER serialization of the content bytes, as described in <https://datatracker.ietf.org/doc/html/draft-ietf-tls-trust-anchor-ids-01#name-trust-anchor-identifiers>.
69-
let log_id = TrustAnchorID(log_id_relative_oid.as_bytes().to_vec());
70-
71-
// TODO should the CA cosigner have a different ID than the log itself?
72-
let cosigner_id = log_id.clone();
7379
let signing_key = load_signing_key(env, name).unwrap().clone();
7480

75-
// Make the checkpoint signers from the secret keys and put them in a vec
76-
let signer = MTCSubtreeCosigner::new(cosigner_id, log_id, origin.clone(), signing_key);
77-
78-
vec![Box::new(signer)]
81+
MTCSubtreeCosigner::new(cosigner_id, log_id, origin.clone(), signing_key)
7982
}
8083

8184
pub(crate) fn load_origin(name: &str) -> KeyName {

crates/mtc_worker/src/sequencer_do.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
66
use std::{future::Future, pin::Pin, time::Duration};
77

8-
use crate::{load_checkpoint_signers, load_origin, CONFIG};
8+
use crate::{load_cosigner, load_origin, CONFIG};
99
use generic_log_worker::{
1010
get_durable_object_name, load_public_bucket, CheckpointCallbacker, GenericSequencer,
1111
SequencerConfig, SEQUENCER_BINDING,
@@ -31,7 +31,7 @@ impl DurableObject for Sequencer {
3131
let config = SequencerConfig {
3232
name: name.to_string(),
3333
origin: load_origin(name),
34-
checkpoint_signers: load_checkpoint_signers(&env, name),
34+
checkpoint_signers: vec![Box::new(load_cosigner(&env, name))],
3535
checkpoint_extension: Box::new(|_| vec![]), // no checkpoint extension for MTC
3636
sequence_interval: Duration::from_millis(params.sequence_interval_millis),
3737
max_sequence_skips: params.max_sequence_skips,

0 commit comments

Comments
 (0)