Compare commits
No commits in common. "032a99f4fc7b2b583e8e52f45e88a8a036124a63" and "f888aa47d0f31caf58251967b196341b93b2e52b" have entirely different histories.
032a99f4fc
...
f888aa47d0
9 changed files with 11 additions and 131 deletions
4
.vscode/settings.json
vendored
4
.vscode/settings.json
vendored
|
@ -18,7 +18,5 @@
|
||||||
"**/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,6 +189,7 @@ dependencies = [
|
||||||
"num-traits",
|
"num-traits",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"serde",
|
"serde",
|
||||||
|
"serde_json",
|
||||||
"sha2",
|
"sha2",
|
||||||
"tokio",
|
"tokio",
|
||||||
"x509-parser",
|
"x509-parser",
|
||||||
|
|
|
@ -8,8 +8,9 @@ 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"], optional = true }
|
reqwest = { version = "0.12.8", features = ["json"] }
|
||||||
serde = { version = "1.0.210", features = ["derive"], optional = true }
|
serde = { version = "1.0.210", features = ["derive"] }
|
||||||
|
serde_json = "1.0.132"
|
||||||
sha2 = "0.10.8"
|
sha2 = "0.10.8"
|
||||||
x509-parser = "0.16.0"
|
x509-parser = "0.16.0"
|
||||||
|
|
||||||
|
@ -18,7 +19,3 @@ 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"]
|
|
||||||
|
|
|
@ -1,46 +0,0 @@
|
||||||
//! 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");
|
|
|
@ -1,47 +0,0 @@
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,21 +0,0 @@
|
||||||
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,7 +10,5 @@
|
||||||
//!
|
//!
|
||||||
//! [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;
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
use x509_parser::{error::X509Error, prelude::X509Certificate};
|
use x509_parser::{error::X509Error, prelude::X509Certificate};
|
||||||
|
|
||||||
use super::structures::{parse_opaque_asn1_cert, parse_precert};
|
use super::
|
||||||
|
structures::{parse_opaque_asn1_cert, parse_precert}
|
||||||
|
;
|
||||||
use crate::merkle::types::{
|
use crate::merkle::types::{
|
||||||
ChainEntry,
|
ChainEntry,
|
||||||
EntryExtraData,
|
EntryExtraData,
|
||||||
|
@ -83,9 +85,7 @@ pub fn parse_timestamped_entry(
|
||||||
/// This function assumes a binary format for the leaf, rather than a
|
/// This function assumes a binary format for the leaf, rather than a
|
||||||
/// base64-encoded version, so make sure to manually decode it from the HTTP
|
/// base64-encoded version, so make sure to manually decode it from the HTTP
|
||||||
/// response before use.
|
/// response before use.
|
||||||
pub fn parse_merkle_tree_leaf(
|
pub fn parse_merkle_tree_leaf(input: &[u8]) -> nom::IResult<&[u8], MerkleTreeLeaf, X509Error> {
|
||||||
input: &[u8]
|
|
||||||
) -> nom::IResult<&[u8], MerkleTreeLeaf, X509Error> {
|
|
||||||
nom::combinator::map(
|
nom::combinator::map(
|
||||||
nom::sequence::pair(
|
nom::sequence::pair(
|
||||||
// Parse version byte
|
// Parse version byte
|
||||||
|
|
Loading…
Reference in a new issue