1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
use super::{Json, RegistryHeader};
use crate::datastore::DataStoreError;
use crate::services::CoreService;
use axum::http::StatusCode;
use axum::{debug_handler, extract::State, response::IntoResponse, routing::post, Router};
use warg_api::v1::monitor::{CheckpointVerificationResponse, MonitorError, VerificationState};
use warg_crypto::hash::Sha256;
use warg_protocol::registry::{LogId, TimestampedCheckpoint};
use warg_protocol::SerdeEnvelope;

#[derive(Clone)]
pub struct Config {
    core_service: CoreService,
}

impl Config {
    pub fn new(core_service: CoreService) -> Self {
        Self { core_service }
    }

    pub fn into_router(self) -> Router {
        Router::new()
            .route("/checkpoint", post(verify_checkpoint))
            .with_state(self)
    }
}

struct MonitorApiError(MonitorError);

impl From<DataStoreError> for MonitorApiError {
    fn from(e: DataStoreError) -> Self {
        tracing::error!("unexpected data store error: {e}");

        Self(MonitorError::Message {
            status: StatusCode::INTERNAL_SERVER_ERROR.as_u16(),
            message: "an error occurred while processing the request".into(),
        })
    }
}

impl IntoResponse for MonitorApiError {
    fn into_response(self) -> axum::response::Response {
        (StatusCode::from_u16(self.0.status()).unwrap(), Json(self.0)).into_response()
    }
}

/// Verifies a checkpoint and its signature.
///
/// Note: Other implementations may choose to perform validation differently
/// and to respond with `Unverified` in cases where the required information
/// is not available or would be too expensive to compute.
///
/// Checkpoint verification is `Verified` when
/// a checkpoint with the provided `log_length` is found in the store
/// and both the log and map roots match the stored checkpoint.
///
/// Checkpoint verification is `Invalid` otherwise.
///
/// Signature verification is `Verified` when either
/// A) It matches the signature on the stored checkpoint, or
/// B) It is a valid and authorized signature by a key in the operator log.
///
/// Signature verification is `Invalid` otherwise.
#[debug_handler]
async fn verify_checkpoint(
    State(config): State<Config>,
    RegistryHeader(_registry_header): RegistryHeader,
    Json(body): Json<SerdeEnvelope<TimestampedCheckpoint>>,
) -> Result<Json<CheckpointVerificationResponse>, MonitorApiError> {
    // Do a first pass checking the provided checkpoint against the data store
    let (checkpoint_verification, signature_verification) =
        try_verify_exact_match(&config.core_service, &body).await;

    // If the signature is `Unverified`, check signature against keys in operator log:
    let signature_verification = if signature_verification == VerificationState::Unverified {
        match config
            .core_service
            .store()
            .verify_timestamped_checkpoint_signature(&LogId::operator_log::<Sha256>(), &body)
            .await
        {
            Ok(_) => VerificationState::Verified,
            Err(error) => match error {
                DataStoreError::UnknownKey(_)
                | DataStoreError::SignatureVerificationFailed(_)
                | DataStoreError::KeyUnauthorized(_) => VerificationState::Invalid,
                _ => return Err(MonitorApiError::from(error)),
            },
        }
    } else {
        signature_verification
    };

    Ok(Json(CheckpointVerificationResponse {
        checkpoint: checkpoint_verification,
        signature: signature_verification,
        retry_after: None,
    }))
}

/// Attempt to verify checkpoint by looking for an exact match in the store.
/// Returns (checkpoint: Invalid, signature: Unverified) if one isn't found.
async fn try_verify_exact_match(
    core_service: &CoreService,
    checkpoint_envelope: &SerdeEnvelope<TimestampedCheckpoint>,
) -> (VerificationState, VerificationState) {
    let checkpoint = &checkpoint_envelope.as_ref().checkpoint;

    // Look for a stored checkpoint with the same log_length as was specified
    let found = core_service
        .store()
        .get_checkpoint(checkpoint.log_length)
        .await;

    if let Ok(found_checkpoint_envelope) = found {
        let found_checkpoint = &found_checkpoint_envelope.as_ref().checkpoint;
        // Check log root and map root
        let log_matches = found_checkpoint.log_root == checkpoint.log_root;
        let map_matches = found_checkpoint.map_root == checkpoint.map_root;

        // A checkpoint is verified if the exact checkpoint was recorded in the store.
        // Otherwise it is considered invalid by the reference implementation.
        let checkpoint_verification = if log_matches && map_matches {
            VerificationState::Verified
        } else {
            VerificationState::Invalid
        };

        // Check for exact match on signature and key ID
        let signature_matches =
            found_checkpoint_envelope.signature() == checkpoint_envelope.signature();
        let key_id_matches = found_checkpoint_envelope.key_id() == checkpoint_envelope.key_id();

        // A checkpoint is verified if the signature and key_id match the found checkpoint.
        // Otherwise it is consdered unverified by this function, but can be checked against known keys afterwards.
        let signature_verification = if signature_matches && key_id_matches {
            VerificationState::Verified
        } else {
            VerificationState::Unverified
        };

        (checkpoint_verification, signature_verification)
    } else {
        (VerificationState::Invalid, VerificationState::Unverified)
    }
}