Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "veraison-apiclient"
version = "0.0.1"
version = "0.0.2"
edition = "2021"
repository = "https://github.com/veraison/rust-apiclient"
readme = "README.md"
Expand All @@ -12,18 +12,18 @@ categories = ["web-programming"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
reqwest = { version = "0.11", features = ["json", "rustls-tls", "blocking"] }
reqwest = { version = "0.12.9", features = ["json", "rustls-tls", "blocking"] }
url = { version = "2", features = ["serde"] }
base64 = "0.13.0"
thiserror = "1"
serde = "1.0.144"
thiserror = "2.0.6"
serde = "1.0.216"
chrono = { version = "0.4", default-features = false, features = ["serde"] }
jsonwebkey = { version = "0.3.5", features = ["pkcs-convert"] }

[dependencies.serde_with]
version = "1.14.0"
version = "3.11.0"
features = ["base64", "chrono"]

[dev-dependencies]
wiremock = "0.5"
wiremock = "0.6.2"
async-std = { version = "1.6.5", features = ["attributes"] }
16 changes: 11 additions & 5 deletions examples/challenge_response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,24 +18,30 @@ fn my_evidence_builder(nonce: &[u8], accept: &[String]) -> Result<(Vec<u8>, Stri
}

fn main() {
let base_url = "http://127.0.0.1:8080";
let base_url = "https://localhost:8080";

let discovery = Discovery::from_base_url(String::from(base_url))
.expect("Failed to start API discovery with the service.");
let discovery_api_endpoint = format!("{}{}", base_url, "/.well-known/veraison/verification");

let discovery = DiscoveryBuilder::new()
.with_url(discovery_api_endpoint)
.with_root_certificate("veraison-root.crt".into())
.build()
.expect("Failed to start API discovery with the service");

let verification_api = discovery
.get_verification_api()
.expect("Failed to discover the verification endpoint details.");
.expect("Failed to discover the verification endpoint details");

let relative_endpoint = verification_api
.get_api_endpoint("newChallengeResponseSession")
.expect("Could not locate a newChallengeResponseSession endpoint.");
.expect("Could not locate a newChallengeResponseSession endpoint");

let api_endpoint = format!("{}{}", base_url, relative_endpoint);

// create a ChallengeResponse object
let cr = ChallengeResponseBuilder::new()
.with_new_session_url(api_endpoint)
.with_root_certificate("veraison-root.crt".into())
.build()
.unwrap();

Expand Down
10 changes: 10 additions & 0 deletions examples/veraison-root.crt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
-----BEGIN CERTIFICATE-----
MIIBfDCCASGgAwIBAgIUGFllXaV04uJz42tPnHXwOkaux50wCgYIKoZIzj0EAwIw
EzERMA8GA1UECgwIVmVyYWlzb24wHhcNMjQwNTIxMTAxNzA1WhcNMzQwNTE5MTAx
NzA1WjATMREwDwYDVQQKDAhWZXJhaXNvbjBZMBMGByqGSM49AgEGCCqGSM49AwEH
A0IABCYxQeR0gnM4/4CvQBmIgNSm6SAal29OYm7GBpq/y0rZWolA3FlHChm3nIZe
qXAtKvK4rkolWSLiaRNN1mEWYG6jUzBRMB0GA1UdDgQWBBTq/aQhL7+hx9EOG+X0
Q/YbAWuGDjAfBgNVHSMEGDAWgBTq/aQhL7+hx9EOG+X0Q/YbAWuGDjAPBgNVHRMB
Af8EBTADAQH/MAoGCCqGSM49BAMCA0kAMEYCIQCAqRST0CFtgWVXpBtYoTldREXb
hGryGCivO3Jkv6LZ5wIhAMqlRBGBPbz8sgS+QQCA0pbhXFt7kMQpH3hrR/tEIeW2
-----END CERTIFICATE-----
119 changes: 108 additions & 11 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@

#![allow(clippy::multiple_crate_versions)]

use std::{fs::File, io::Read, path::PathBuf};

use reqwest::{blocking::ClientBuilder, Certificate};

#[derive(thiserror::Error, PartialEq, Eq)]
pub enum Error {
#[error("configuration error: {0}")]
Expand All @@ -25,6 +29,12 @@ impl From<reqwest::Error> for Error {
}
}

impl From<std::io::Error> for Error {
fn from(re: std::io::Error) -> Self {
Error::ConfigError(re.to_string())
}
}

impl From<jsonwebkey::ConversionError> for Error {
fn from(e: jsonwebkey::ConversionError) -> Self {
Error::DataConversionError(e.to_string())
Expand Down Expand Up @@ -54,35 +64,56 @@ type EvidenceCreationCb = fn(nonce: &[u8], accepted: &[String]) -> Result<(Vec<u
/// A builder for ChallengeResponse objects
pub struct ChallengeResponseBuilder {
new_session_url: Option<String>,
// TODO(tho) add TLS config / authn tokens etc.
root_certificate: Option<PathBuf>,
}

impl ChallengeResponseBuilder {
/// default constructor
pub fn new() -> Self {
Self {
new_session_url: None,
root_certificate: None,
}
}

/// Use this method to supply the URL of the verification endpoint that will create
/// new challenge-response sessions, e.g.
/// new challenge-response sessions, e.g.:
/// "https://veraison.example/challenge-response/v1/newSession".
pub fn with_new_session_url(mut self, v: String) -> ChallengeResponseBuilder {
self.new_session_url = Some(v);
self
}

/// Use this method to add a custom root certificate. For example, this can
/// be used to connect to a server that has a self-signed certificate which
/// is not present in (and does not need to be added to) the system's trust
/// anchor store.
pub fn with_root_certificate(mut self, v: PathBuf) -> ChallengeResponseBuilder {
self.root_certificate = Some(v);
self
}

/// Instantiate a valid ChallengeResponse object, or fail with an error.
pub fn build(self) -> Result<ChallengeResponse, Error> {
let new_session_url_str = self
.new_session_url
.ok_or_else(|| Error::ConfigError("missing API endpoint".to_string()))?;

let mut http_client_builder: ClientBuilder = reqwest::blocking::ClientBuilder::new();

if self.root_certificate.is_some() {
let mut buf = Vec::new();
File::open(self.root_certificate.unwrap())?.read_to_end(&mut buf)?;
let cert = Certificate::from_pem(&buf)?;
http_client_builder = http_client_builder.add_root_certificate(cert);
}

let http_client = http_client_builder.use_rustls_tls().build()?;

Ok(ChallengeResponse {
new_session_url: url::Url::parse(&new_session_url_str)
.map_err(|e| Error::ConfigError(e.to_string()))?,
http_client: reqwest::blocking::Client::builder().build()?,
http_client,
})
}
}
Expand Down Expand Up @@ -330,6 +361,68 @@ pub struct VerificationApi {
api_endpoints: std::collections::HashMap<String, String>,
}

/// A builder for Discovery objects
pub struct DiscoveryBuilder {
url: Option<String>,
root_certificate: Option<PathBuf>,
}

impl DiscoveryBuilder {
/// default constructor
pub fn new() -> Self {
Self {
url: None,
root_certificate: None,
}
}

/// Use this method to supply the URL of the discovery endpoint, e.g.:
/// "https://veraison.example/.well-known/veraison/verification"
pub fn with_url(mut self, v: String) -> DiscoveryBuilder {
self.url = Some(v);
self
}

/// Use this method to add a custom root certificate. For example, this can
/// be used to connect to a server that has a self-signed certificate which
/// is not present in (and does not need to be added to) the system's trust
/// anchor store.
pub fn with_root_certificate(mut self, v: PathBuf) -> DiscoveryBuilder {
self.root_certificate = Some(v);
self
}

/// Instantiate a valid Discovery object, or fail with an error.
pub fn build(self) -> Result<Discovery, Error> {
let url = self
.url
.ok_or_else(|| Error::ConfigError("missing API endpoint".to_string()))?;

let mut http_client_builder: ClientBuilder = reqwest::blocking::ClientBuilder::new();

if self.root_certificate.is_some() {
let mut buf = Vec::new();
File::open(self.root_certificate.unwrap())?.read_to_end(&mut buf)?;
let cert = Certificate::from_pem(&buf)?;
http_client_builder = http_client_builder.add_root_certificate(cert);
}

let http_client = http_client_builder.use_rustls_tls().build()?;

Ok(Discovery {
verification_url: url::Url::parse(&url)
.map_err(|e| Error::ConfigError(e.to_string()))?,
http_client,
})
}
}

impl Default for DiscoveryBuilder {
fn default() -> Self {
Self::new()
}
}

impl VerificationApi {
/// Obtains the EAR verification public key encoded in ASN.1 DER format.
pub fn ear_verification_key_as_der(&self) -> Result<Vec<u8>, Error> {
Expand Down Expand Up @@ -406,31 +499,27 @@ impl VerificationApi {
/// This structure allows Veraison endpoints and service capabilities to be discovered
/// dynamically.
///
/// Use [`Discovery::from_base_url()`] to create an instance of this structure for the
/// Use [`DiscoveryBuilder`] to create an instance of this structure for the
/// Veraison service instance that you are communicating with.
pub struct Discovery {
provisioning_url: url::Url, //TODO: The provisioning URL discovery is not implemented yet.
verification_url: url::Url,
http_client: reqwest::blocking::Client,
}

impl Discovery {
#[deprecated(since = "0.0.2", note = "please use the `DiscoveryBuilder` instead")]
/// Establishes client API discovery for the Veraison service instance running at the
/// given base URL.
pub fn from_base_url(base_url_str: String) -> Result<Discovery, Error> {
let base_url =
url::Url::parse(&base_url_str).map_err(|e| Error::ConfigError(e.to_string()))?;

let mut provisioning_url = base_url.clone();
provisioning_url.set_path(".well-known/veraison/provisioning");

let mut verification_url = base_url;
verification_url.set_path(".well-known/veraison/verification");

Ok(Discovery {
provisioning_url,
verification_url,
http_client: reqwest::blocking::Client::builder().build()?,
http_client: reqwest::blocking::Client::new(),
})
}

Expand Down Expand Up @@ -621,7 +710,15 @@ mod tests {
.mount(&mock_server)
.await;

let discovery = Discovery::from_base_url(mock_server.uri())
let discovery_api_endpoint = format!(
"{}{}",
mock_server.uri(),
"/.well-known/veraison/verification"
);

let discovery = DiscoveryBuilder::new()
.with_url(discovery_api_endpoint)
.build()
.expect("Failed to create Discovery client.");

let verification_api = discovery
Expand Down
Loading