From 70a83d4b9e67fb067d4c7541d9e896fcbd854b48 Mon Sep 17 00:00:00 2001 From: Tyler Beckman Date: Sun, 27 Oct 2024 16:35:34 -0600 Subject: [PATCH] feat(api): Add get entries endpoint --- src/api/endpoints.rs | 46 ++++++++++++++++++++++++++++++++++++++++++++ src/api/mod.rs | 37 +++++++++++++++++++++++++++++++++++ src/api/responses.rs | 15 +++++++++++++++ 3 files changed, 98 insertions(+) diff --git a/src/api/endpoints.rs b/src/api/endpoints.rs index 31895c2..be7795d 100644 --- a/src/api/endpoints.rs +++ b/src/api/endpoints.rs @@ -125,3 +125,49 @@ pub const GET_STH_CONSISTENCY: Endpoint = /// ``` pub const GET_PROOF_BY_HASH: Endpoint = (reqwest::Method::GET, "/ct/v1/get-proof-by-hash"); + +/// Reference: https://datatracker.ietf.org/doc/html/rfc6962#section-4.4 +/// ```txt +/// GET https:///ct/v1/get-entries +/// +/// Inputs: +/// +/// start: 0-based index of first entry to retrieve, in decimal. +/// +/// end: 0-based index of last entry to retrieve, in decimal. +/// +/// Outputs: +/// +/// entries: An array of objects, each consisting of +/// +/// leaf_input: The base64-encoded MerkleTreeLeaf structure. +/// +/// extra_data: The base64-encoded unsigned data pertaining to the +/// log entry. In the case of an X509ChainEntry, this is the +/// "certificate_chain". In the case of a PrecertChainEntry, +/// this is the whole "PrecertChainEntry". +/// +/// Note that this message is not signed -- the retrieved data can be +/// verified by constructing the Merkle Tree Hash corresponding to a +/// retrieved STH. All leaves MUST be v1. However, a compliant v1 +/// client MUST NOT construe an unrecognized MerkleLeafType or +/// LogEntryType value as an error. This means it may be unable to parse +/// some entries, but note that each client can inspect the entries it +/// does recognize as well as verify the integrity of the data by +/// treating unrecognized leaves as opaque input to the tree. +/// +/// The "start" and "end" parameters SHOULD be within the range 0 <= x < +/// "tree_size" as returned by "get-sth" in Section 4.3. +/// +/// Logs MAY honor requests where 0 <= "start" < "tree_size" and "end" >= +/// "tree_size" by returning a partial response covering only the valid +/// entries in the specified range. Note that the following restriction +/// may also apply: +/// +/// Logs MAY restrict the number of entries that can be retrieved per +/// "get-entries" request. If a client requests more than the permitted +/// number of entries, the log SHALL return the maximum number of entries +/// permissible. These entries SHALL be sequential beginning with the +/// entry specified by "start". +/// ``` +pub const GET_ENTRIES: Endpoint = (reqwest::Method::GET, "/ct/v1/get-entries"); diff --git a/src/api/mod.rs b/src/api/mod.rs index 67c0968..2ce22fb 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -4,6 +4,7 @@ use reqwest::Url; use responses::{ AddChainRequest, AddChainResponse, + GetEntriesResponse, GetProofByHashResponse, GetSthConsistencyResponse, GetSthResponse @@ -205,4 +206,40 @@ impl CtApiClient { .json() .await } + + /// Fetches the CT log entries within the range `[start, end]` where both + /// parameters are 0-indexed. The response contains both the MerkleTreeLeaf + /// structure and full certificate chain of each entry. Logs may or may not + /// properly honor requests in which the start parameter is less than 0 or + /// the end parameter is greater than the current tree size, to try to + /// ensure these bounds are correct. In addition, there is no guarantee that + /// the resulting response will have the list of entries exactly the size + /// requested, as CT logs can enforce limits on how much data is returned at + /// once. + /// + /// See: [`endpoints::GET_ENTRIES`] + /// + /// ## Errors + /// + /// This may error if either the request failed (due to lack of internet or + /// invalid domain, for example), or if the CT log gave a 4xx/5xx response. + /// The CT log may error the response if it doesn't allow invalid bounds and + /// the start and end parameters were specified incorrectly. + pub async fn get_log_entries( + &self, + start: u64, + end: u64 + ) -> reqwest::Result { + self.inner_client + .request( + endpoints::GET_ENTRIES.0, + self.log_url.to_string() + endpoints::GET_ENTRIES.1 + ) + .query(&[("start", start), ("end", end)]) + .send() + .await? + .error_for_status()? + .json() + .await + } } diff --git a/src/api/responses.rs b/src/api/responses.rs index f8d7429..b3623aa 100644 --- a/src/api/responses.rs +++ b/src/api/responses.rs @@ -51,3 +51,18 @@ pub struct GetProofByHashResponse { pub leaf_index: u64, pub audit_path: Vec } + +/// A response given when fetching CT log entries within a range +/// +/// See: [`super::endpoints::GET_ENTRIES`] +#[derive(Debug, Deserialize)] +pub struct GetEntriesResponse { + pub entries: Vec +} + +/// A specific entry in a [`GetEntriesResponse`] +#[derive(Debug, Deserialize)] +pub struct GetEntriesResponseEntry { + pub leaf_input: String, + pub extra_data: String +} \ No newline at end of file