Skip to content

Commit

Permalink
Add HTTPS support (#20)
Browse files Browse the repository at this point in the history
* chore: update deps

Signed-off-by: Thomas Fossati <[email protected]>

* feat: add HTTPS support

Signed-off-by: Thomas Fossati <[email protected]>

* chore: update example code to use HTTPS

Signed-off-by: Thomas Fossati <[email protected]>

---------

Signed-off-by: Thomas Fossati <[email protected]>
  • Loading branch information
thomas-fossati authored Dec 12, 2024
1 parent d5a144a commit b5a7614
Show file tree
Hide file tree
Showing 4 changed files with 135 additions and 22 deletions.
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

0 comments on commit b5a7614

Please sign in to comment.