feat(api): Start on API requests with add-chain and boilerplate
This commit is contained in:
parent
84aa99f261
commit
032a99f4fc
7 changed files with 125 additions and 5 deletions
4
.vscode/settings.json
vendored
4
.vscode/settings.json
vendored
|
@ -18,5 +18,7 @@
|
||||||
"**/CVS": true,
|
"**/CVS": true,
|
||||||
"**/.DS_Store": true,
|
"**/.DS_Store": true,
|
||||||
"**/Thumbs.db": true
|
"**/Thumbs.db": true
|
||||||
}
|
},
|
||||||
|
"rust-analyzer.cargo.features": "all",
|
||||||
|
"rust-analyzer.check.features": "all"
|
||||||
}
|
}
|
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -189,7 +189,6 @@ dependencies = [
|
||||||
"num-traits",
|
"num-traits",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
|
||||||
"sha2",
|
"sha2",
|
||||||
"tokio",
|
"tokio",
|
||||||
"x509-parser",
|
"x509-parser",
|
||||||
|
|
|
@ -8,9 +8,8 @@ anyhow = "1.0.90"
|
||||||
der-parser = "9.0.0"
|
der-parser = "9.0.0"
|
||||||
nom = "7.1.3"
|
nom = "7.1.3"
|
||||||
num-traits = "0.2.19"
|
num-traits = "0.2.19"
|
||||||
reqwest = { version = "0.12.8", features = ["json"] }
|
reqwest = { version = "0.12.8", features = ["json"], optional = true }
|
||||||
serde = { version = "1.0.210", features = ["derive"] }
|
serde = { version = "1.0.210", features = ["derive"], optional = true }
|
||||||
serde_json = "1.0.132"
|
|
||||||
sha2 = "0.10.8"
|
sha2 = "0.10.8"
|
||||||
x509-parser = "0.16.0"
|
x509-parser = "0.16.0"
|
||||||
|
|
||||||
|
@ -19,3 +18,7 @@ base64ct = "1.6.0"
|
||||||
reqwest = { version = "0.12.8", features = ["json"] }
|
reqwest = { version = "0.12.8", features = ["json"] }
|
||||||
serde = { version = "1.0.210", features = ["derive"] }
|
serde = { version = "1.0.210", features = ["derive"] }
|
||||||
tokio = { version = "1.41.0", features = ["rt-multi-thread", "macros"] }
|
tokio = { version = "1.41.0", features = ["rt-multi-thread", "macros"] }
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default-features = []
|
||||||
|
api = ["dep:reqwest", "dep:serde"]
|
||||||
|
|
46
src/api/endpoints.rs
Normal file
46
src/api/endpoints.rs
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
//! A module containing constants for each API endpoint, specifying the method
|
||||||
|
//! and path of each. This is primarily used for the [api module](`crate::api`)
|
||||||
|
//! internals, but could be used externally also.
|
||||||
|
|
||||||
|
type Endpoint<'a> = (reqwest::Method, &'a str);
|
||||||
|
|
||||||
|
/// Reference: https://datatracker.ietf.org/doc/html/rfc6962#section-4.1
|
||||||
|
/// ```txt
|
||||||
|
/// POST https://<log server>/ct/v1/add-chain
|
||||||
|
///
|
||||||
|
/// Inputs:
|
||||||
|
///
|
||||||
|
/// chain: An array of base64-encoded certificates. The first
|
||||||
|
/// element is the end-entity certificate; the second chains to the
|
||||||
|
/// first and so on to the last, which is either the root
|
||||||
|
/// certificate or a certificate that chains to a known root
|
||||||
|
/// certificate.
|
||||||
|
///
|
||||||
|
/// Outputs:
|
||||||
|
///
|
||||||
|
/// sct_version: The version of the SignedCertificateTimestamp
|
||||||
|
/// structure, in decimal. A compliant v1 implementation MUST NOT
|
||||||
|
/// expect this to be 0 (i.e., v1).
|
||||||
|
///
|
||||||
|
/// id: The log ID, base64 encoded. Since log clients who request an
|
||||||
|
/// SCT for inclusion in TLS handshakes are not required to verify
|
||||||
|
/// it, we do not assume they know the ID of the log.
|
||||||
|
///
|
||||||
|
/// timestamp: The SCT timestamp, in decimal.
|
||||||
|
///
|
||||||
|
/// extensions: An opaque type for future expansion. It is likely
|
||||||
|
/// that not all participants will need to understand data in this
|
||||||
|
/// field. Logs should set this to the empty string. Clients
|
||||||
|
/// should decode the base64-encoded data and include it in the
|
||||||
|
/// SCT.
|
||||||
|
///
|
||||||
|
/// signature: The SCT signature, base64 encoded.
|
||||||
|
///
|
||||||
|
/// If the "sct_version" is not v1, then a v1 client may be unable to
|
||||||
|
/// verify the signature. It MUST NOT construe this as an error. (Note:
|
||||||
|
/// Log clients don't need to be able to verify this structure; only TLS
|
||||||
|
/// clients do. If we were to serve the structure as a binary blob, then
|
||||||
|
/// we could completely change it without requiring an upgrade to v1
|
||||||
|
/// clients.)
|
||||||
|
/// ```
|
||||||
|
pub const ADD_CHAIN_ENDPOINT: Endpoint = (reqwest::Method::POST, "/ct/v1/add-chain");
|
47
src/api/mod.rs
Normal file
47
src/api/mod.rs
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
use reqwest::Url;
|
||||||
|
use responses::{AddChainRequest, AddChainResponse};
|
||||||
|
|
||||||
|
pub mod endpoints;
|
||||||
|
pub mod responses;
|
||||||
|
|
||||||
|
pub struct CtApiClient {
|
||||||
|
inner_client: reqwest::Client,
|
||||||
|
log_url: Url
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CtApiClient {
|
||||||
|
pub fn new(log_url: Url) -> reqwest::Result<Self> {
|
||||||
|
Ok(Self {
|
||||||
|
inner_client: reqwest::Client::builder().https_only(true).build()?,
|
||||||
|
log_url
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds a chain to the CT log.
|
||||||
|
///
|
||||||
|
/// See: [`endpoints::ADD_CHAIN_ENDPOINT`]
|
||||||
|
///
|
||||||
|
/// ## 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.
|
||||||
|
/// Specifically, compliant CT logs will reject chains that do not verify
|
||||||
|
/// sequentially from the first entry (end-user certificate) to the last
|
||||||
|
/// entry (a trusted root or an intermediate signed by the a trusted root).
|
||||||
|
pub async fn add_chain(
|
||||||
|
&self,
|
||||||
|
chain: Vec<String>
|
||||||
|
) -> reqwest::Result<AddChainResponse> {
|
||||||
|
self.inner_client
|
||||||
|
.request(
|
||||||
|
endpoints::ADD_CHAIN_ENDPOINT.0,
|
||||||
|
endpoints::ADD_CHAIN_ENDPOINT.1
|
||||||
|
)
|
||||||
|
.json(&AddChainRequest { chain })
|
||||||
|
.send()
|
||||||
|
.await?
|
||||||
|
.error_for_status()?
|
||||||
|
.json()
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
21
src/api/responses.rs
Normal file
21
src/api/responses.rs
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
/// A request payload for adding a chain to a CT log
|
||||||
|
///
|
||||||
|
/// See: [`super::endpoints::ADD_CHAIN_ENDPOINT`]
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
pub struct AddChainRequest {
|
||||||
|
pub chain: Vec<String>
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A response given when adding a chain to a CT log
|
||||||
|
///
|
||||||
|
/// See: [`super::endpoints::ADD_CHAIN_ENDPOINT`]
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct AddChainResponse {
|
||||||
|
pub sct_version: u8,
|
||||||
|
pub log_id: String,
|
||||||
|
pub timestamp: u64,
|
||||||
|
pub extensions: String,
|
||||||
|
pub signature: String
|
||||||
|
}
|
|
@ -10,5 +10,7 @@
|
||||||
//!
|
//!
|
||||||
//! [RFC6926]: https://datatracker.ietf.org/doc/html/rfc6962
|
//! [RFC6926]: https://datatracker.ietf.org/doc/html/rfc6962
|
||||||
|
|
||||||
|
#[cfg(feature = "api")]
|
||||||
|
pub mod api;
|
||||||
pub mod merkle;
|
pub mod merkle;
|
||||||
pub mod parsing;
|
pub mod parsing;
|
||||||
|
|
Loading…
Reference in a new issue