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,
|
||||
"**/.DS_Store": 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",
|
||||
"reqwest",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
"tokio",
|
||||
"x509-parser",
|
||||
|
|
|
@ -8,8 +8,9 @@ anyhow = "1.0.90"
|
|||
der-parser = "9.0.0"
|
||||
nom = "7.1.3"
|
||||
num-traits = "0.2.19"
|
||||
reqwest = { version = "0.12.8", features = ["json"], optional = true }
|
||||
serde = { version = "1.0.210", features = ["derive"], optional = true }
|
||||
reqwest = { version = "0.12.8", features = ["json"] }
|
||||
serde = { version = "1.0.210", features = ["derive"] }
|
||||
serde_json = "1.0.132"
|
||||
sha2 = "0.10.8"
|
||||
x509-parser = "0.16.0"
|
||||
|
||||
|
@ -18,7 +19,3 @@ base64ct = "1.6.0"
|
|||
reqwest = { version = "0.12.8", features = ["json"] }
|
||||
serde = { version = "1.0.210", features = ["derive"] }
|
||||
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
|
||||
|
||||
#[cfg(feature = "api")]
|
||||
pub mod api;
|
||||
pub mod merkle;
|
||||
pub mod parsing;
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
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::{
|
||||
ChainEntry,
|
||||
EntryExtraData,
|
||||
|
@ -83,9 +85,7 @@ pub fn parse_timestamped_entry(
|
|||
/// 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
|
||||
/// response before use.
|
||||
pub fn parse_merkle_tree_leaf(
|
||||
input: &[u8]
|
||||
) -> nom::IResult<&[u8], MerkleTreeLeaf, X509Error> {
|
||||
pub fn parse_merkle_tree_leaf(input: &[u8]) -> nom::IResult<&[u8], MerkleTreeLeaf, X509Error> {
|
||||
nom::combinator::map(
|
||||
nom::sequence::pair(
|
||||
// Parse version byte
|
||||
|
|
|
@ -29,7 +29,7 @@ pub fn parse_opaque_tbs_certificate(input: &[u8]) -> X509Result<TbsCertificate>
|
|||
/// TBSCertificate tbs_certificate;
|
||||
/// } PreCert;
|
||||
/// ```
|
||||
///
|
||||
///
|
||||
/// [RFC6962]: https://datatracker.ietf.org/doc/html/rfc6962
|
||||
pub fn parse_precert(input: &[u8]) -> X509Result<PreCert> {
|
||||
nom::combinator::map(
|
||||
|
@ -42,4 +42,4 @@ pub fn parse_precert(input: &[u8]) -> X509Result<PreCert> {
|
|||
tbs_certificate
|
||||
}
|
||||
)(input)
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue