Skip to content

Commit 3fef382

Browse files
feat!: asynchronous API (#22)
Signed-off-by: Thomas Fossati <[email protected]>
1 parent 8c98e95 commit 3fef382

File tree

3 files changed

+59
-34
lines changed

3 files changed

+59
-34
lines changed

Cargo.toml

+2-1
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,12 @@ thiserror = "2.0.6"
1919
serde = "1.0.216"
2020
chrono = { version = "0.4", default-features = false, features = ["serde"] }
2121
jsonwebkey = { version = "0.3.5", features = ["pkcs-convert"] }
22+
tokio = { version = "1.43.0", features = ["full"] }
2223

2324
[dependencies.serde_with]
2425
version = "3.11.0"
2526
features = ["base64", "chrono"]
2627

2728
[dev-dependencies]
2829
wiremock = "0.6.2"
29-
async-std = { version = "1.6.5", features = ["attributes"] }
30+
async-std = { version = "1.6.5", features = ["attributes", "tokio1"] }

examples/challenge_response.rs

+18-7
Original file line numberDiff line numberDiff line change
@@ -5,29 +5,40 @@ extern crate veraison_apiclient;
55

66
use veraison_apiclient::*;
77

8-
fn my_evidence_builder(nonce: &[u8], accept: &[String]) -> Result<(Vec<u8>, String), Error> {
8+
fn my_evidence_builder(
9+
nonce: &[u8],
10+
accept: &[String],
11+
token: Vec<u8>,
12+
) -> Result<(Vec<u8>, String), Error> {
913
println!("server challenge: {:?}", nonce);
1014
println!("acceptable media types: {:#?}", accept);
1115

12-
Ok((
16+
let mut token = token;
17+
if token.is_empty() {
1318
// some very fake evidence
14-
vec![0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff],
19+
token = vec![0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff];
20+
}
21+
22+
Ok((
23+
token,
1524
// the first acceptable evidence type
1625
accept[0].to_string(),
1726
))
1827
}
1928

20-
fn main() {
29+
#[async_std::main]
30+
async fn main() {
2131
let base_url = "https://localhost:8080";
2232

2333
let discovery = DiscoveryBuilder::new()
2434
.with_base_url(base_url.into())
25-
.with_root_certificate("veraison-root.crt".into())
35+
.with_root_certificate("./veraison-root.crt".into())
2636
.build()
2737
.expect("Failed to start API discovery with the service");
2838

2939
let verification_api = discovery
3040
.get_verification_api()
41+
.await
3142
.expect("Failed to discover the verification endpoint details");
3243

3344
let relative_endpoint = verification_api
@@ -39,14 +50,14 @@ fn main() {
3950
// create a ChallengeResponse object
4051
let cr = ChallengeResponseBuilder::new()
4152
.with_new_session_url(api_endpoint)
42-
.with_root_certificate("veraison-root.crt".into())
53+
.with_root_certificate("./veraison-root.crt".into())
4354
.build()
4455
.unwrap();
4556

4657
let nonce = Nonce::Value(vec![0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef]);
4758
// alternatively, to let Veraison pick the challenge: "let nonce = Nonce::Size(32);"
4859

49-
match cr.run(nonce, my_evidence_builder) {
60+
match cr.run(nonce, my_evidence_builder, Vec::new()).await {
5061
Err(e) => println!("Error: {}", e),
5162
Ok(attestation_result) => println!("Attestation Result: {}", attestation_result),
5263
}

src/lib.rs

+39-26
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
// Copyright 2022 Contributors to the Veraison project.
1+
// Copyright 2022-2025 Contributors to the Veraison project.
22
// SPDX-License-Identifier: Apache-2.0
33

44
#![allow(clippy::multiple_crate_versions)]
55

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

8-
use reqwest::{blocking::ClientBuilder, Certificate};
8+
use reqwest::{Certificate, ClientBuilder};
99

1010
#[derive(thiserror::Error, PartialEq, Eq)]
1111
pub enum Error {
@@ -59,7 +59,8 @@ impl std::fmt::Debug for Error {
5959
/// The application is passed the session nonce and the list of supported
6060
/// evidence media types and shall return the computed evidence together with
6161
/// the selected media type.
62-
type EvidenceCreationCb = fn(nonce: &[u8], accepted: &[String]) -> Result<(Vec<u8>, String), Error>;
62+
type EvidenceCreationCb =
63+
fn(nonce: &[u8], accepted: &[String], token: Vec<u8>) -> Result<(Vec<u8>, String), Error>;
6364

6465
/// A builder for ChallengeResponse objects
6566
pub struct ChallengeResponseBuilder {
@@ -99,7 +100,7 @@ impl ChallengeResponseBuilder {
99100
.new_session_url
100101
.ok_or_else(|| Error::ConfigError("missing API endpoint".to_string()))?;
101102

102-
let mut http_client_builder: ClientBuilder = reqwest::blocking::ClientBuilder::new();
103+
let mut http_client_builder: ClientBuilder = reqwest::ClientBuilder::new();
103104

104105
if self.root_certificate.is_some() {
105106
let mut buf = Vec::new();
@@ -128,7 +129,7 @@ impl Default for ChallengeResponseBuilder {
128129
/// be run. Always use the [ChallengeResponseBuilder] to instantiate it.
129130
pub struct ChallengeResponse {
130131
new_session_url: url::Url,
131-
http_client: reqwest::blocking::Client,
132+
http_client: reqwest::Client,
132133
}
133134

134135
/// Nonce configuration: either the size (Size) of the nonce generated by the
@@ -143,19 +144,23 @@ impl ChallengeResponse {
143144
/// Run a challenge-response verification session using the supplied nonce
144145
/// configuration and evidence creation callback. Returns the raw attestation results, or an
145146
/// error on failure.
146-
pub fn run(
147+
pub async fn run(
147148
&self,
148149
nonce: Nonce,
149150
evidence_creation_cb: EvidenceCreationCb,
151+
token: Vec<u8>,
150152
) -> Result<String, Error> {
151153
// create new c/r verification session on the veraison side
152-
let (session_url, session) = self.new_session(&nonce)?;
154+
let (session_url, session) = self.new_session(&nonce).await?;
153155

154156
// invoke the user-provided evidence builder callback with per-session parameters
155-
let (evidence, media_type) = (evidence_creation_cb)(session.nonce(), session.accept())?;
157+
let (evidence, media_type) =
158+
(evidence_creation_cb)(session.nonce(), session.accept(), token)?;
156159

157160
// send evidence for verification to the session endpoint
158-
let attestation_result = self.challenge_response(&evidence, &media_type, &session_url)?;
161+
let attestation_result = self
162+
.challenge_response(&evidence, &media_type, &session_url)
163+
.await?;
159164

160165
// return veraison's attestation results
161166
Ok(attestation_result)
@@ -164,9 +169,12 @@ impl ChallengeResponse {
164169
/// Ask Veraison to create a new challenge/response session using the supplied nonce
165170
/// configuration. On success, the return value is a tuple of the session URL for subsequent
166171
/// operations, plus the session data including the nonce and the list of accept types.
167-
pub fn new_session(&self, nonce: &Nonce) -> Result<(String, ChallengeResponseSession), Error> {
172+
pub async fn new_session(
173+
&self,
174+
nonce: &Nonce,
175+
) -> Result<(String, ChallengeResponseSession), Error> {
168176
// ask veraison for a new session object
169-
let resp = self.new_session_request(nonce)?;
177+
let resp = self.new_session_request(nonce).await?;
170178

171179
// expect 201 and a Location header containing the URI of the newly
172180
// allocated session
@@ -180,7 +188,7 @@ impl ChallengeResponse {
180188
// middleware that is unaware of the API. We need something
181189
// more robust here that dispatches based on the Content-Type
182190
// header.
183-
let pd: ProblemDetails = resp.json()?;
191+
let pd: ProblemDetails = resp.json().await?;
184192

185193
return Err(Error::ApiError(format!(
186194
"newSession response has unexpected status: {}. Details: {}",
@@ -206,13 +214,13 @@ impl ChallengeResponse {
206214
.map_err(|e| Error::ApiError(e.to_string()))?;
207215

208216
// decode returned session object
209-
let crs: ChallengeResponseSession = resp.json()?;
217+
let crs: ChallengeResponseSession = resp.json().await?;
210218

211219
Ok((session_url.to_string(), crs))
212220
}
213221

214222
/// Execute a challenge/response operation with the given evidence.
215-
pub fn challenge_response(
223+
pub async fn challenge_response(
216224
&self,
217225
evidence: &[u8],
218226
media_type: &str,
@@ -225,14 +233,15 @@ impl ChallengeResponse {
225233
.header(reqwest::header::ACCEPT, CRS_MEDIA_TYPE)
226234
.header(reqwest::header::CONTENT_TYPE, media_type)
227235
.body(evidence.to_owned())
228-
.send()?;
236+
.send()
237+
.await?;
229238

230239
let status = resp.status();
231240

232241
if status.is_success() {
233242
match status {
234243
reqwest::StatusCode::OK => {
235-
let crs: ChallengeResponseSession = resp.json()?;
244+
let crs: ChallengeResponseSession = resp.json().await?;
236245

237246
if crs.status != "complete" {
238247
return Err(Error::ApiError(format!(
@@ -259,7 +268,7 @@ impl ChallengeResponse {
259268
))),
260269
}
261270
} else {
262-
let pd: ProblemDetails = resp.json()?;
271+
let pd: ProblemDetails = resp.json().await?;
263272

264273
Err(Error::ApiError(format!(
265274
"session response has error status: {}. Details: {}",
@@ -268,14 +277,15 @@ impl ChallengeResponse {
268277
}
269278
}
270279

271-
fn new_session_request(&self, nonce: &Nonce) -> Result<reqwest::blocking::Response, Error> {
280+
async fn new_session_request(&self, nonce: &Nonce) -> Result<reqwest::Response, Error> {
272281
let u = self.new_session_request_url(nonce)?;
273282

274283
let r = self
275284
.http_client
276285
.post(u.as_str())
277286
.header(reqwest::header::ACCEPT, CRS_MEDIA_TYPE)
278-
.send()?;
287+
.send()
288+
.await?;
279289

280290
Ok(r)
281291
}
@@ -403,7 +413,7 @@ impl DiscoveryBuilder {
403413
.url
404414
.ok_or_else(|| Error::ConfigError("missing API endpoint".to_string()))?;
405415

406-
let mut http_client_builder: ClientBuilder = reqwest::blocking::ClientBuilder::new();
416+
let mut http_client_builder: ClientBuilder = reqwest::ClientBuilder::new();
407417

408418
if self.root_certificate.is_some() {
409419
let mut buf = Vec::new();
@@ -508,7 +518,7 @@ impl VerificationApi {
508518
/// Veraison service instance that you are communicating with.
509519
pub struct Discovery {
510520
verification_url: url::Url,
511-
http_client: reqwest::blocking::Client,
521+
http_client: reqwest::Client,
512522
}
513523

514524
impl Discovery {
@@ -524,20 +534,21 @@ impl Discovery {
524534

525535
Ok(Discovery {
526536
verification_url,
527-
http_client: reqwest::blocking::Client::new(),
537+
http_client: reqwest::Client::new(),
528538
})
529539
}
530540

531541
/// Obtains the capabilities and endpoints of the Veraison verification service.
532-
pub fn get_verification_api(&self) -> Result<VerificationApi, Error> {
542+
pub async fn get_verification_api(&self) -> Result<VerificationApi, Error> {
533543
let response = self
534544
.http_client
535545
.get(self.verification_url.as_str())
536546
.header(reqwest::header::ACCEPT, DISCOVERY_MEDIA_TYPE)
537-
.send()?;
547+
.send()
548+
.await?;
538549

539550
match response.status() {
540-
reqwest::StatusCode::OK => Ok(response.json::<VerificationApi>()?),
551+
reqwest::StatusCode::OK => Ok(response.json::<VerificationApi>().await?),
541552
_ => Err(Error::ApiError(String::from(
542553
"Failed to discover verification endpoint information.",
543554
))),
@@ -630,7 +641,7 @@ mod tests {
630641
.build()
631642
.unwrap();
632643

633-
let rv = cr.new_session(&nonce).expect("unexpected failure");
644+
let rv = cr.new_session(&nonce).await.expect("unexpected failure");
634645

635646
// Expect we are given the expected location URL
636647
assert_eq!(rv.0, format!("{}/1234", mock_server.uri()));
@@ -672,6 +683,7 @@ mod tests {
672683

673684
let rv = cr
674685
.challenge_response(&evidence_value, media_type, &session_url)
686+
.await
675687
.expect("unexpected failure");
676688

677689
// Expect we are given the expected attestation result
@@ -722,6 +734,7 @@ mod tests {
722734

723735
let verification_api = discovery
724736
.get_verification_api()
737+
.await
725738
.expect("Failed to get verification endpoint details.");
726739

727740
// Check that we've pulled and deserialized everything that we expect

0 commit comments

Comments
 (0)