From 3023735f286e4193181bbe5b568089a6f6585d82 Mon Sep 17 00:00:00 2001 From: Victor Dods Date: Fri, 28 Jul 2023 14:02:25 -0700 Subject: [PATCH 1/2] Added http_client attribute to DIDWeb to avoid costly construction per resolution. DIDWeb struct has an HTTP client to use for DID resolution. It's incredibly slow to create a new reqwest::Client, due to the overhead of loading the system's root certificates. This HTTP client must be specified by constructing the DIDWeb instance using new_with_default_http_client (to use defaults) or DIDWeb::new_with_http_client if there is an specific reqwest::Client that should be reused. Note that this is the recommended approach to using reqwest::Client (see https://docs.rs/reqwest/latest/reqwest/struct.Client.html). --- did-test/src/main.rs | 13 +++++--- did-web/src/lib.rs | 74 ++++++++++++++++++++++++++++++-------------- 2 files changed, 60 insertions(+), 27 deletions(-) diff --git a/did-test/src/main.rs b/did-test/src/main.rs index 37414795e..411e707c9 100644 --- a/did-test/src/main.rs +++ b/did-test/src/main.rs @@ -231,7 +231,8 @@ async fn report_method_web() { let supported_content_types = vec![TYPE_DID_LD_JSON.to_string()]; let did = "did:web:demo.spruceid.com:2021:07:08"; - let did_vector = did_method_vector(&did_web::DIDWeb, did).await; + let did_web_resolver = did_web::DIDWeb::new_with_default_http_client().unwrap(); + let did_vector = did_method_vector(&did_web_resolver, did).await; did_vectors.insert(did.to_string(), did_vector); let dids = did_vectors.keys().cloned().collect(); @@ -537,19 +538,21 @@ async fn report_resolver_web() { executions: Vec::new(), }; + let did_web_resolver = did_web::DIDWeb::new_with_default_http_client().unwrap(); + for did in &[ "did:web:identity.foundation", "did:web:did.actor:nonexistent", ] { report - .resolve(&did_web::DIDWeb, did, &ResolutionInputMetadata::default()) + .resolve(&did_web_resolver, did, &ResolutionInputMetadata::default()) .await; } { let did = &"did:web:identity.foundation"; report - .resolve_representation(&did_web::DIDWeb, did, &ResolutionInputMetadata::default()) + .resolve_representation(&did_web_resolver, did, &ResolutionInputMetadata::default()) .await; } @@ -709,6 +712,8 @@ async fn report_dereferencer_web() { executions: Vec::new(), }; + let did_web_resolver = did_web::DIDWeb::new_with_default_http_client().unwrap(); + for did_url in &[ "did:web:did.actor:nonexistent", "did:web:demo.spruceid.com:2021:07:14:service-example", @@ -716,7 +721,7 @@ async fn report_dereferencer_web() { ] { report .dereference( - &did_web::DIDWeb, + &did_web_resolver, did_url, &DereferencingInputMetadata::default(), ) diff --git a/did-web/src/lib.rs b/did-web/src/lib.rs index ff23c00e4..196c017fa 100644 --- a/did-web/src/lib.rs +++ b/did-web/src/lib.rs @@ -18,7 +18,42 @@ thread_local! { /// did:web Method /// /// [Specification](https://w3c-ccg.github.io/did-method-web/) -pub struct DIDWeb; +/// +/// DIDWeb struct has an HTTP client to use for DID resolution. It's incredibly slow to create a new +/// reqwest::Client, due to the overhead of loading the system's root certificates. This HTTP client must +/// be specified by constructing the DIDWeb instance using new_with_default_http_client (to use defaults) +/// or DIDWeb::new_with_http_client if there is an specific reqwest::Client that should be reused. +/// Note that this is the recommended approach to using reqwest::Client (see +/// https://docs.rs/reqwest/latest/reqwest/struct.Client.html). +pub struct DIDWeb { + http_client: reqwest::Client, +} + +impl DIDWeb { + /// Create an instance of the DIDWeb resolver with a default HTTP client. See also `DIDWeb::new_with_http_client`. + pub fn new_with_default_http_client() -> Result { + let mut headers = reqwest::header::HeaderMap::new(); + + headers.insert( + "User-Agent", + reqwest::header::HeaderValue::from_static(USER_AGENT), + ); + + let http_client = match reqwest::Client::builder().default_headers(headers).build() { + Ok(http_client) => http_client, + Err(err) => { + return Err(format!("Error building HTTP client: {}", err)); + } + }; + + Ok(Self { http_client }) + } + /// Create an instance of the DIDWeb resolver with a specific HTTP client. See also + /// `DIDWeb::new_with_default_http_client`. + pub fn new_with_http_client(http_client: reqwest::Client) -> Self { + Self { http_client } + } +} fn did_web_url(did: &str) -> Result { let mut parts = did.split(':').peekable(); @@ -107,28 +142,17 @@ impl DIDResolver for DIDWeb { }; // TODO: https://w3c-ccg.github.io/did-method-web/#in-transit-security - let mut headers = reqwest::header::HeaderMap::new(); - - headers.insert( - "User-Agent", - reqwest::header::HeaderValue::from_static(USER_AGENT), - ); - - let client = match reqwest::Client::builder().default_headers(headers).build() { - Ok(c) => c, - Err(err) => { - return ( - ResolutionMetadata::from_error(&format!("Error building HTTP client: {}", err)), - Vec::new(), - None, - ) - } - }; let accept = input_metadata .accept .clone() .unwrap_or_else(|| "application/json".to_string()); - let resp = match client.get(&url).header("Accept", accept).send().await { + let resp = match self + .http_client + .get(&url) + .header("Accept", accept) + .send() + .await + { Ok(req) => req, Err(err) => { return ( @@ -282,7 +306,8 @@ mod tests { PROXY.with(|proxy| { proxy.replace(Some(url)); }); - let (res_meta, doc_opt, _doc_meta) = DIDWeb + let did_web_resolver = DIDWeb::new_with_default_http_client().unwrap(); + let (res_meta, doc_opt, _doc_meta) = did_web_resolver .resolve("did:web:localhost", &ResolutionInputMetadata::default()) .await; assert_eq!(res_meta.error, None); @@ -320,21 +345,24 @@ mod tests { ..Default::default() }; let mut context_loader = ssi_json_ld::ContextLoader::default(); + let did_web_resolver = DIDWeb::new_with_default_http_client().unwrap(); let proof = vc - .generate_proof(&key, &issue_options, &DIDWeb, &mut context_loader) + .generate_proof(&key, &issue_options, &did_web_resolver, &mut context_loader) .await .unwrap(); println!("{}", serde_json::to_string_pretty(&proof).unwrap()); vc.add_proof(proof); vc.validate().unwrap(); - let verification_result = vc.verify(None, &DIDWeb, &mut context_loader).await; + let verification_result = vc + .verify(None, &did_web_resolver, &mut context_loader) + .await; println!("{:#?}", verification_result); assert!(verification_result.errors.is_empty()); // test that issuer property is used for verification vc.issuer = Some(Issuer::URI(URI::String("did:example:bad".to_string()))); assert!(!vc - .verify(None, &DIDWeb, &mut context_loader) + .verify(None, &did_web_resolver, &mut context_loader) .await .errors .is_empty()); From 38e742a3d049ea814ea4b910f57c8618d43c4f94 Mon Sep 17 00:00:00 2001 From: Victor Dods Date: Fri, 28 Jul 2023 14:03:11 -0700 Subject: [PATCH 2/2] Added a missing trait use. --- ssi-jws/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/ssi-jws/src/lib.rs b/ssi-jws/src/lib.rs index ffbc67e39..0f3c6d4ce 100644 --- a/ssi-jws/src/lib.rs +++ b/ssi-jws/src/lib.rs @@ -317,6 +317,7 @@ pub fn verify_bytes_warnable( { use ed25519_dalek::Verifier; let public_key = ed25519_dalek::PublicKey::try_from(okp)?; + use k256::ecdsa::signature::Signature; let signature = ed25519_dalek::Signature::from_bytes(signature) .map_err(ssi_jwk::Error::from)?; public_key