warg_server/api/v1/
monitor.rs

1use super::{Json, RegistryHeader};
2use crate::datastore::DataStoreError;
3use crate::services::CoreService;
4use axum::http::StatusCode;
5use axum::{debug_handler, extract::State, response::IntoResponse, routing::post, Router};
6use warg_api::v1::monitor::{CheckpointVerificationResponse, MonitorError, VerificationState};
7use warg_crypto::hash::Sha256;
8use warg_protocol::registry::{LogId, TimestampedCheckpoint};
9use warg_protocol::SerdeEnvelope;
10
11#[derive(Clone)]
12pub struct Config {
13    core_service: CoreService,
14}
15
16impl Config {
17    pub fn new(core_service: CoreService) -> Self {
18        Self { core_service }
19    }
20
21    pub fn into_router(self) -> Router {
22        Router::new()
23            .route("/checkpoint", post(verify_checkpoint))
24            .with_state(self)
25    }
26}
27
28struct MonitorApiError(MonitorError);
29
30impl From<DataStoreError> for MonitorApiError {
31    fn from(e: DataStoreError) -> Self {
32        tracing::error!("unexpected data store error: {e}");
33
34        Self(MonitorError::Message {
35            status: StatusCode::INTERNAL_SERVER_ERROR.as_u16(),
36            message: "an error occurred while processing the request".into(),
37        })
38    }
39}
40
41impl IntoResponse for MonitorApiError {
42    fn into_response(self) -> axum::response::Response {
43        (StatusCode::from_u16(self.0.status()).unwrap(), Json(self.0)).into_response()
44    }
45}
46
47/// Verifies a checkpoint and its signature.
48///
49/// Note: Other implementations may choose to perform validation differently
50/// and to respond with `Unverified` in cases where the required information
51/// is not available or would be too expensive to compute.
52///
53/// Checkpoint verification is `Verified` when
54/// a checkpoint with the provided `log_length` is found in the store
55/// and both the log and map roots match the stored checkpoint.
56///
57/// Checkpoint verification is `Invalid` otherwise.
58///
59/// Signature verification is `Verified` when either
60/// A) It matches the signature on the stored checkpoint, or
61/// B) It is a valid and authorized signature by a key in the operator log.
62///
63/// Signature verification is `Invalid` otherwise.
64#[debug_handler]
65async fn verify_checkpoint(
66    State(config): State<Config>,
67    RegistryHeader(_registry_header): RegistryHeader,
68    Json(body): Json<SerdeEnvelope<TimestampedCheckpoint>>,
69) -> Result<Json<CheckpointVerificationResponse>, MonitorApiError> {
70    // Do a first pass checking the provided checkpoint against the data store
71    let (checkpoint_verification, signature_verification) =
72        try_verify_exact_match(&config.core_service, &body).await;
73
74    // If the signature is `Unverified`, check signature against keys in operator log:
75    let signature_verification = if signature_verification == VerificationState::Unverified {
76        match config
77            .core_service
78            .store()
79            .verify_timestamped_checkpoint_signature(&LogId::operator_log::<Sha256>(), &body)
80            .await
81        {
82            Ok(_) => VerificationState::Verified,
83            Err(error) => match error {
84                DataStoreError::UnknownKey(_)
85                | DataStoreError::SignatureVerificationFailed(_)
86                | DataStoreError::KeyUnauthorized(_) => VerificationState::Invalid,
87                _ => return Err(MonitorApiError::from(error)),
88            },
89        }
90    } else {
91        signature_verification
92    };
93
94    Ok(Json(CheckpointVerificationResponse {
95        checkpoint: checkpoint_verification,
96        signature: signature_verification,
97        retry_after: None,
98    }))
99}
100
101/// Attempt to verify checkpoint by looking for an exact match in the store.
102/// Returns (checkpoint: Invalid, signature: Unverified) if one isn't found.
103async fn try_verify_exact_match(
104    core_service: &CoreService,
105    checkpoint_envelope: &SerdeEnvelope<TimestampedCheckpoint>,
106) -> (VerificationState, VerificationState) {
107    let checkpoint = &checkpoint_envelope.as_ref().checkpoint;
108
109    // Look for a stored checkpoint with the same log_length as was specified
110    let found = core_service
111        .store()
112        .get_checkpoint(checkpoint.log_length)
113        .await;
114
115    if let Ok(found_checkpoint_envelope) = found {
116        let found_checkpoint = &found_checkpoint_envelope.as_ref().checkpoint;
117        // Check log root and map root
118        let log_matches = found_checkpoint.log_root == checkpoint.log_root;
119        let map_matches = found_checkpoint.map_root == checkpoint.map_root;
120
121        // A checkpoint is verified if the exact checkpoint was recorded in the store.
122        // Otherwise it is considered invalid by the reference implementation.
123        let checkpoint_verification = if log_matches && map_matches {
124            VerificationState::Verified
125        } else {
126            VerificationState::Invalid
127        };
128
129        // Check for exact match on signature and key ID
130        let signature_matches =
131            found_checkpoint_envelope.signature() == checkpoint_envelope.signature();
132        let key_id_matches = found_checkpoint_envelope.key_id() == checkpoint_envelope.key_id();
133
134        // A checkpoint is verified if the signature and key_id match the found checkpoint.
135        // Otherwise it is consdered unverified by this function, but can be checked against known keys afterwards.
136        let signature_verification = if signature_matches && key_id_matches {
137            VerificationState::Verified
138        } else {
139            VerificationState::Unverified
140        };
141
142        (checkpoint_verification, signature_verification)
143    } else {
144        (VerificationState::Invalid, VerificationState::Unverified)
145    }
146}