Skip to content

Commit b5a7614

Browse files
Add HTTPS support (#20)
* 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]>
1 parent d5a144a commit b5a7614

File tree

4 files changed

+135
-22
lines changed

4 files changed

+135
-22
lines changed

Cargo.toml

+6-6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "veraison-apiclient"
3-
version = "0.0.1"
3+
version = "0.0.2"
44
edition = "2021"
55
repository = "https://github.com/veraison/rust-apiclient"
66
readme = "README.md"
@@ -12,18 +12,18 @@ categories = ["web-programming"]
1212
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
1313

1414
[dependencies]
15-
reqwest = { version = "0.11", features = ["json", "rustls-tls", "blocking"] }
15+
reqwest = { version = "0.12.9", features = ["json", "rustls-tls", "blocking"] }
1616
url = { version = "2", features = ["serde"] }
1717
base64 = "0.13.0"
18-
thiserror = "1"
19-
serde = "1.0.144"
18+
thiserror = "2.0.6"
19+
serde = "1.0.216"
2020
chrono = { version = "0.4", default-features = false, features = ["serde"] }
2121
jsonwebkey = { version = "0.3.5", features = ["pkcs-convert"] }
2222

2323
[dependencies.serde_with]
24-
version = "1.14.0"
24+
version = "3.11.0"
2525
features = ["base64", "chrono"]
2626

2727
[dev-dependencies]
28-
wiremock = "0.5"
28+
wiremock = "0.6.2"
2929
async-std = { version = "1.6.5", features = ["attributes"] }

examples/challenge_response.rs

+11-5
Original file line numberDiff line numberDiff line change
@@ -18,24 +18,30 @@ fn my_evidence_builder(nonce: &[u8], accept: &[String]) -> Result<(Vec<u8>, Stri
1818
}
1919

2020
fn main() {
21-
let base_url = "http://127.0.0.1:8080";
21+
let base_url = "https://localhost:8080";
2222

23-
let discovery = Discovery::from_base_url(String::from(base_url))
24-
.expect("Failed to start API discovery with the service.");
23+
let discovery_api_endpoint = format!("{}{}", base_url, "/.well-known/veraison/verification");
24+
25+
let discovery = DiscoveryBuilder::new()
26+
.with_url(discovery_api_endpoint)
27+
.with_root_certificate("veraison-root.crt".into())
28+
.build()
29+
.expect("Failed to start API discovery with the service");
2530

2631
let verification_api = discovery
2732
.get_verification_api()
28-
.expect("Failed to discover the verification endpoint details.");
33+
.expect("Failed to discover the verification endpoint details");
2934

3035
let relative_endpoint = verification_api
3136
.get_api_endpoint("newChallengeResponseSession")
32-
.expect("Could not locate a newChallengeResponseSession endpoint.");
37+
.expect("Could not locate a newChallengeResponseSession endpoint");
3338

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

3641
// create a ChallengeResponse object
3742
let cr = ChallengeResponseBuilder::new()
3843
.with_new_session_url(api_endpoint)
44+
.with_root_certificate("veraison-root.crt".into())
3945
.build()
4046
.unwrap();
4147

examples/veraison-root.crt

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
-----BEGIN CERTIFICATE-----
2+
MIIBfDCCASGgAwIBAgIUGFllXaV04uJz42tPnHXwOkaux50wCgYIKoZIzj0EAwIw
3+
EzERMA8GA1UECgwIVmVyYWlzb24wHhcNMjQwNTIxMTAxNzA1WhcNMzQwNTE5MTAx
4+
NzA1WjATMREwDwYDVQQKDAhWZXJhaXNvbjBZMBMGByqGSM49AgEGCCqGSM49AwEH
5+
A0IABCYxQeR0gnM4/4CvQBmIgNSm6SAal29OYm7GBpq/y0rZWolA3FlHChm3nIZe
6+
qXAtKvK4rkolWSLiaRNN1mEWYG6jUzBRMB0GA1UdDgQWBBTq/aQhL7+hx9EOG+X0
7+
Q/YbAWuGDjAfBgNVHSMEGDAWgBTq/aQhL7+hx9EOG+X0Q/YbAWuGDjAPBgNVHRMB
8+
Af8EBTADAQH/MAoGCCqGSM49BAMCA0kAMEYCIQCAqRST0CFtgWVXpBtYoTldREXb
9+
hGryGCivO3Jkv6LZ5wIhAMqlRBGBPbz8sgS+QQCA0pbhXFt7kMQpH3hrR/tEIeW2
10+
-----END CERTIFICATE-----

src/lib.rs

+108-11
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33

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

6+
use std::{fs::File, io::Read, path::PathBuf};
7+
8+
use reqwest::{blocking::ClientBuilder, Certificate};
9+
610
#[derive(thiserror::Error, PartialEq, Eq)]
711
pub enum Error {
812
#[error("configuration error: {0}")]
@@ -25,6 +29,12 @@ impl From<reqwest::Error> for Error {
2529
}
2630
}
2731

32+
impl From<std::io::Error> for Error {
33+
fn from(re: std::io::Error) -> Self {
34+
Error::ConfigError(re.to_string())
35+
}
36+
}
37+
2838
impl From<jsonwebkey::ConversionError> for Error {
2939
fn from(e: jsonwebkey::ConversionError) -> Self {
3040
Error::DataConversionError(e.to_string())
@@ -54,35 +64,56 @@ type EvidenceCreationCb = fn(nonce: &[u8], accepted: &[String]) -> Result<(Vec<u
5464
/// A builder for ChallengeResponse objects
5565
pub struct ChallengeResponseBuilder {
5666
new_session_url: Option<String>,
57-
// TODO(tho) add TLS config / authn tokens etc.
67+
root_certificate: Option<PathBuf>,
5868
}
5969

6070
impl ChallengeResponseBuilder {
6171
/// default constructor
6272
pub fn new() -> Self {
6373
Self {
6474
new_session_url: None,
75+
root_certificate: None,
6576
}
6677
}
6778

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

87+
/// Use this method to add a custom root certificate. For example, this can
88+
/// be used to connect to a server that has a self-signed certificate which
89+
/// is not present in (and does not need to be added to) the system's trust
90+
/// anchor store.
91+
pub fn with_root_certificate(mut self, v: PathBuf) -> ChallengeResponseBuilder {
92+
self.root_certificate = Some(v);
93+
self
94+
}
95+
7696
/// Instantiate a valid ChallengeResponse object, or fail with an error.
7797
pub fn build(self) -> Result<ChallengeResponse, Error> {
7898
let new_session_url_str = self
7999
.new_session_url
80100
.ok_or_else(|| Error::ConfigError("missing API endpoint".to_string()))?;
81101

102+
let mut http_client_builder: ClientBuilder = reqwest::blocking::ClientBuilder::new();
103+
104+
if self.root_certificate.is_some() {
105+
let mut buf = Vec::new();
106+
File::open(self.root_certificate.unwrap())?.read_to_end(&mut buf)?;
107+
let cert = Certificate::from_pem(&buf)?;
108+
http_client_builder = http_client_builder.add_root_certificate(cert);
109+
}
110+
111+
let http_client = http_client_builder.use_rustls_tls().build()?;
112+
82113
Ok(ChallengeResponse {
83114
new_session_url: url::Url::parse(&new_session_url_str)
84115
.map_err(|e| Error::ConfigError(e.to_string()))?,
85-
http_client: reqwest::blocking::Client::builder().build()?,
116+
http_client,
86117
})
87118
}
88119
}
@@ -330,6 +361,68 @@ pub struct VerificationApi {
330361
api_endpoints: std::collections::HashMap<String, String>,
331362
}
332363

364+
/// A builder for Discovery objects
365+
pub struct DiscoveryBuilder {
366+
url: Option<String>,
367+
root_certificate: Option<PathBuf>,
368+
}
369+
370+
impl DiscoveryBuilder {
371+
/// default constructor
372+
pub fn new() -> Self {
373+
Self {
374+
url: None,
375+
root_certificate: None,
376+
}
377+
}
378+
379+
/// Use this method to supply the URL of the discovery endpoint, e.g.:
380+
/// "https://veraison.example/.well-known/veraison/verification"
381+
pub fn with_url(mut self, v: String) -> DiscoveryBuilder {
382+
self.url = Some(v);
383+
self
384+
}
385+
386+
/// Use this method to add a custom root certificate. For example, this can
387+
/// be used to connect to a server that has a self-signed certificate which
388+
/// is not present in (and does not need to be added to) the system's trust
389+
/// anchor store.
390+
pub fn with_root_certificate(mut self, v: PathBuf) -> DiscoveryBuilder {
391+
self.root_certificate = Some(v);
392+
self
393+
}
394+
395+
/// Instantiate a valid Discovery object, or fail with an error.
396+
pub fn build(self) -> Result<Discovery, Error> {
397+
let url = self
398+
.url
399+
.ok_or_else(|| Error::ConfigError("missing API endpoint".to_string()))?;
400+
401+
let mut http_client_builder: ClientBuilder = reqwest::blocking::ClientBuilder::new();
402+
403+
if self.root_certificate.is_some() {
404+
let mut buf = Vec::new();
405+
File::open(self.root_certificate.unwrap())?.read_to_end(&mut buf)?;
406+
let cert = Certificate::from_pem(&buf)?;
407+
http_client_builder = http_client_builder.add_root_certificate(cert);
408+
}
409+
410+
let http_client = http_client_builder.use_rustls_tls().build()?;
411+
412+
Ok(Discovery {
413+
verification_url: url::Url::parse(&url)
414+
.map_err(|e| Error::ConfigError(e.to_string()))?,
415+
http_client,
416+
})
417+
}
418+
}
419+
420+
impl Default for DiscoveryBuilder {
421+
fn default() -> Self {
422+
Self::new()
423+
}
424+
}
425+
333426
impl VerificationApi {
334427
/// Obtains the EAR verification public key encoded in ASN.1 DER format.
335428
pub fn ear_verification_key_as_der(&self) -> Result<Vec<u8>, Error> {
@@ -406,31 +499,27 @@ impl VerificationApi {
406499
/// This structure allows Veraison endpoints and service capabilities to be discovered
407500
/// dynamically.
408501
///
409-
/// Use [`Discovery::from_base_url()`] to create an instance of this structure for the
502+
/// Use [`DiscoveryBuilder`] to create an instance of this structure for the
410503
/// Veraison service instance that you are communicating with.
411504
pub struct Discovery {
412-
provisioning_url: url::Url, //TODO: The provisioning URL discovery is not implemented yet.
413505
verification_url: url::Url,
414506
http_client: reqwest::blocking::Client,
415507
}
416508

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

424-
let mut provisioning_url = base_url.clone();
425-
provisioning_url.set_path(".well-known/veraison/provisioning");
426-
427517
let mut verification_url = base_url;
428518
verification_url.set_path(".well-known/veraison/verification");
429519

430520
Ok(Discovery {
431-
provisioning_url,
432521
verification_url,
433-
http_client: reqwest::blocking::Client::builder().build()?,
522+
http_client: reqwest::blocking::Client::new(),
434523
})
435524
}
436525

@@ -621,7 +710,15 @@ mod tests {
621710
.mount(&mock_server)
622711
.await;
623712

624-
let discovery = Discovery::from_base_url(mock_server.uri())
713+
let discovery_api_endpoint = format!(
714+
"{}{}",
715+
mock_server.uri(),
716+
"/.well-known/veraison/verification"
717+
);
718+
719+
let discovery = DiscoveryBuilder::new()
720+
.with_url(discovery_api_endpoint)
721+
.build()
625722
.expect("Failed to create Discovery client.");
626723

627724
let verification_api = discovery

0 commit comments

Comments
 (0)