diff --git a/Cargo.toml b/Cargo.toml index 5b1f245..a89bc30 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,7 @@ 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"] } +tokio = { version = "1.43.0", features = ["full"] } [dependencies.serde_with] version = "3.11.0" @@ -26,4 +27,4 @@ features = ["base64", "chrono"] [dev-dependencies] wiremock = "0.6.2" -async-std = { version = "1.6.5", features = ["attributes"] } +async-std = { version = "1.6.5", features = ["attributes", "tokio1"] } diff --git a/examples/challenge_response.rs b/examples/challenge_response.rs index c910a3f..b9aeb45 100644 --- a/examples/challenge_response.rs +++ b/examples/challenge_response.rs @@ -5,29 +5,40 @@ extern crate veraison_apiclient; use veraison_apiclient::*; -fn my_evidence_builder(nonce: &[u8], accept: &[String]) -> Result<(Vec, String), Error> { +fn my_evidence_builder( + nonce: &[u8], + accept: &[String], + token: Vec, +) -> Result<(Vec, String), Error> { println!("server challenge: {:?}", nonce); println!("acceptable media types: {:#?}", accept); - Ok(( + let mut token = token; + if token.is_empty() { // some very fake evidence - vec![0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff], + token = vec![0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]; + } + + Ok(( + token, // the first acceptable evidence type accept[0].to_string(), )) } -fn main() { +#[async_std::main] +async fn main() { let base_url = "https://localhost:8080"; let discovery = DiscoveryBuilder::new() .with_base_url(base_url.into()) - .with_root_certificate("veraison-root.crt".into()) + .with_root_certificate("./veraison-root.crt".into()) .build() .expect("Failed to start API discovery with the service"); let verification_api = discovery .get_verification_api() + .await .expect("Failed to discover the verification endpoint details"); let relative_endpoint = verification_api @@ -39,14 +50,14 @@ fn main() { // create a ChallengeResponse object let cr = ChallengeResponseBuilder::new() .with_new_session_url(api_endpoint) - .with_root_certificate("veraison-root.crt".into()) + .with_root_certificate("./veraison-root.crt".into()) .build() .unwrap(); let nonce = Nonce::Value(vec![0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef]); // alternatively, to let Veraison pick the challenge: "let nonce = Nonce::Size(32);" - match cr.run(nonce, my_evidence_builder) { + match cr.run(nonce, my_evidence_builder, Vec::new()).await { Err(e) => println!("Error: {}", e), Ok(attestation_result) => println!("Attestation Result: {}", attestation_result), } diff --git a/src/lib.rs b/src/lib.rs index 92de9db..493e8b8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,11 +1,11 @@ -// Copyright 2022 Contributors to the Veraison project. +// Copyright 2022-2025 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 #![allow(clippy::multiple_crate_versions)] use std::{fs::File, io::Read, path::PathBuf}; -use reqwest::{blocking::ClientBuilder, Certificate}; +use reqwest::{Certificate, ClientBuilder}; #[derive(thiserror::Error, PartialEq, Eq)] pub enum Error { @@ -59,7 +59,8 @@ impl std::fmt::Debug for Error { /// The application is passed the session nonce and the list of supported /// evidence media types and shall return the computed evidence together with /// the selected media type. -type EvidenceCreationCb = fn(nonce: &[u8], accepted: &[String]) -> Result<(Vec, String), Error>; +type EvidenceCreationCb = + fn(nonce: &[u8], accepted: &[String], token: Vec) -> Result<(Vec, String), Error>; /// A builder for ChallengeResponse objects pub struct ChallengeResponseBuilder { @@ -99,7 +100,7 @@ impl ChallengeResponseBuilder { .new_session_url .ok_or_else(|| Error::ConfigError("missing API endpoint".to_string()))?; - let mut http_client_builder: ClientBuilder = reqwest::blocking::ClientBuilder::new(); + let mut http_client_builder: ClientBuilder = reqwest::ClientBuilder::new(); if self.root_certificate.is_some() { let mut buf = Vec::new(); @@ -128,7 +129,7 @@ impl Default for ChallengeResponseBuilder { /// be run. Always use the [ChallengeResponseBuilder] to instantiate it. pub struct ChallengeResponse { new_session_url: url::Url, - http_client: reqwest::blocking::Client, + http_client: reqwest::Client, } /// Nonce configuration: either the size (Size) of the nonce generated by the @@ -143,19 +144,23 @@ impl ChallengeResponse { /// Run a challenge-response verification session using the supplied nonce /// configuration and evidence creation callback. Returns the raw attestation results, or an /// error on failure. - pub fn run( + pub async fn run( &self, nonce: Nonce, evidence_creation_cb: EvidenceCreationCb, + token: Vec, ) -> Result { // create new c/r verification session on the veraison side - let (session_url, session) = self.new_session(&nonce)?; + let (session_url, session) = self.new_session(&nonce).await?; // invoke the user-provided evidence builder callback with per-session parameters - let (evidence, media_type) = (evidence_creation_cb)(session.nonce(), session.accept())?; + let (evidence, media_type) = + (evidence_creation_cb)(session.nonce(), session.accept(), token)?; // send evidence for verification to the session endpoint - let attestation_result = self.challenge_response(&evidence, &media_type, &session_url)?; + let attestation_result = self + .challenge_response(&evidence, &media_type, &session_url) + .await?; // return veraison's attestation results Ok(attestation_result) @@ -164,9 +169,12 @@ impl ChallengeResponse { /// Ask Veraison to create a new challenge/response session using the supplied nonce /// configuration. On success, the return value is a tuple of the session URL for subsequent /// operations, plus the session data including the nonce and the list of accept types. - pub fn new_session(&self, nonce: &Nonce) -> Result<(String, ChallengeResponseSession), Error> { + pub async fn new_session( + &self, + nonce: &Nonce, + ) -> Result<(String, ChallengeResponseSession), Error> { // ask veraison for a new session object - let resp = self.new_session_request(nonce)?; + let resp = self.new_session_request(nonce).await?; // expect 201 and a Location header containing the URI of the newly // allocated session @@ -180,7 +188,7 @@ impl ChallengeResponse { // middleware that is unaware of the API. We need something // more robust here that dispatches based on the Content-Type // header. - let pd: ProblemDetails = resp.json()?; + let pd: ProblemDetails = resp.json().await?; return Err(Error::ApiError(format!( "newSession response has unexpected status: {}. Details: {}", @@ -206,13 +214,13 @@ impl ChallengeResponse { .map_err(|e| Error::ApiError(e.to_string()))?; // decode returned session object - let crs: ChallengeResponseSession = resp.json()?; + let crs: ChallengeResponseSession = resp.json().await?; Ok((session_url.to_string(), crs)) } /// Execute a challenge/response operation with the given evidence. - pub fn challenge_response( + pub async fn challenge_response( &self, evidence: &[u8], media_type: &str, @@ -225,14 +233,15 @@ impl ChallengeResponse { .header(reqwest::header::ACCEPT, CRS_MEDIA_TYPE) .header(reqwest::header::CONTENT_TYPE, media_type) .body(evidence.to_owned()) - .send()?; + .send() + .await?; let status = resp.status(); if status.is_success() { match status { reqwest::StatusCode::OK => { - let crs: ChallengeResponseSession = resp.json()?; + let crs: ChallengeResponseSession = resp.json().await?; if crs.status != "complete" { return Err(Error::ApiError(format!( @@ -259,7 +268,7 @@ impl ChallengeResponse { ))), } } else { - let pd: ProblemDetails = resp.json()?; + let pd: ProblemDetails = resp.json().await?; Err(Error::ApiError(format!( "session response has error status: {}. Details: {}", @@ -268,14 +277,15 @@ impl ChallengeResponse { } } - fn new_session_request(&self, nonce: &Nonce) -> Result { + async fn new_session_request(&self, nonce: &Nonce) -> Result { let u = self.new_session_request_url(nonce)?; let r = self .http_client .post(u.as_str()) .header(reqwest::header::ACCEPT, CRS_MEDIA_TYPE) - .send()?; + .send() + .await?; Ok(r) } @@ -403,7 +413,7 @@ impl DiscoveryBuilder { .url .ok_or_else(|| Error::ConfigError("missing API endpoint".to_string()))?; - let mut http_client_builder: ClientBuilder = reqwest::blocking::ClientBuilder::new(); + let mut http_client_builder: ClientBuilder = reqwest::ClientBuilder::new(); if self.root_certificate.is_some() { let mut buf = Vec::new(); @@ -508,7 +518,7 @@ impl VerificationApi { /// Veraison service instance that you are communicating with. pub struct Discovery { verification_url: url::Url, - http_client: reqwest::blocking::Client, + http_client: reqwest::Client, } impl Discovery { @@ -524,20 +534,21 @@ impl Discovery { Ok(Discovery { verification_url, - http_client: reqwest::blocking::Client::new(), + http_client: reqwest::Client::new(), }) } /// Obtains the capabilities and endpoints of the Veraison verification service. - pub fn get_verification_api(&self) -> Result { + pub async fn get_verification_api(&self) -> Result { let response = self .http_client .get(self.verification_url.as_str()) .header(reqwest::header::ACCEPT, DISCOVERY_MEDIA_TYPE) - .send()?; + .send() + .await?; match response.status() { - reqwest::StatusCode::OK => Ok(response.json::()?), + reqwest::StatusCode::OK => Ok(response.json::().await?), _ => Err(Error::ApiError(String::from( "Failed to discover verification endpoint information.", ))), @@ -630,7 +641,7 @@ mod tests { .build() .unwrap(); - let rv = cr.new_session(&nonce).expect("unexpected failure"); + let rv = cr.new_session(&nonce).await.expect("unexpected failure"); // Expect we are given the expected location URL assert_eq!(rv.0, format!("{}/1234", mock_server.uri())); @@ -672,6 +683,7 @@ mod tests { let rv = cr .challenge_response(&evidence_value, media_type, &session_url) + .await .expect("unexpected failure"); // Expect we are given the expected attestation result @@ -722,6 +734,7 @@ mod tests { let verification_api = discovery .get_verification_api() + .await .expect("Failed to get verification endpoint details."); // Check that we've pulled and deserialized everything that we expect