From 72fea608519c5f60cf0f43a5485a4764c92fd4c2 Mon Sep 17 00:00:00 2001 From: Diane Huxley Date: Mon, 8 Jul 2024 16:00:14 -0700 Subject: [PATCH 1/7] cli did create web --- crates/web5/src/dids/methods/did_web/mod.rs | 29 ++++++++++++++++++--- crates/web5_cli/src/dids/create.rs | 26 +++++++++++++++--- 2 files changed, 49 insertions(+), 6 deletions(-) diff --git a/crates/web5/src/dids/methods/did_web/mod.rs b/crates/web5/src/dids/methods/did_web/mod.rs index abcfc27e..d127f9de 100644 --- a/crates/web5/src/dids/methods/did_web/mod.rs +++ b/crates/web5/src/dids/methods/did_web/mod.rs @@ -1,14 +1,14 @@ mod resolver; use super::{MethodError, Result}; -use crate::dids::{ - data_model::document::Document, +use crate::{crypto::jwk::Jwk, dids::{ + data_model::{document::Document, verification_method::VerificationMethod}, did::Did, resolution::{ resolution_metadata::{ResolutionMetadata, ResolutionMetadataError}, resolution_result::ResolutionResult, }, -}; +}}; use resolver::Resolver; #[derive(Clone)] @@ -18,6 +18,29 @@ pub struct DidWeb { } impl DidWeb { + pub fn new(domain: &str, public_jwk: Jwk) -> Result { + let did = format!("did:web:{}", domain); + + let verification_method = VerificationMethod { + id: format!("{}#key-0", did), + r#type: "JsonWebKey2020".to_string(), + controller: did.clone(), + public_key_jwk: public_jwk, + }; + + let document = Document { + id: did.clone(), + context: Some(vec!["https://www.w3.org/ns/did/v1".to_string()]), + verification_method: vec![verification_method], + ..Default::default() + }; + + Ok(DidWeb { + did: Did::new(&did)?, + document + }) + } + pub async fn from_uri(uri: &str) -> Result { let resolution_result = DidWeb::resolve(uri); match resolution_result.document { diff --git a/crates/web5_cli/src/dids/create.rs b/crates/web5_cli/src/dids/create.rs index e31201eb..8c536d07 100644 --- a/crates/web5_cli/src/dids/create.rs +++ b/crates/web5_cli/src/dids/create.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use web5::{ crypto::dsa::ed25519::{Ed25519Generator, Ed25519Signer}, dids::{ - methods::{did_dht::DidDht, did_jwk::DidJwk}, + methods::{did_dht::DidDht, did_jwk::DidJwk, did_web::DidWeb}, portable_did::PortableDid, }, }; @@ -17,7 +17,12 @@ pub enum Commands { json_escape: bool, }, Web { + #[arg(long)] domain: String, + #[arg(long)] + no_indent: bool, + #[arg(long)] + json_escape: bool, }, Dht { #[arg(long)] @@ -63,8 +68,23 @@ impl Commands { print_portable_did(portable_did, no_indent, json_escape); } - Commands::Web { domain: _ } => { - println!("🚧 not currently supported 🚧"); + Commands::Web { + domain, + no_indent, + json_escape, + } => { + let private_jwk = Ed25519Generator::generate(); + let mut public_jwk = private_jwk.clone(); + public_jwk.d = None; + + let did_web = DidWeb::new(domain, public_jwk).unwrap(); + let portable_did = PortableDid { + did_uri: did_web.did.uri, + document: did_web.document, + private_jwks: vec![private_jwk], + }; + + print_portable_did(portable_did, no_indent, json_escape) } Commands::Dht { no_publish, From 9d60e258dc227b01319486e23336e8760d212a98 Mon Sep 17 00:00:00 2001 From: Diane Huxley Date: Mon, 8 Jul 2024 16:23:24 -0700 Subject: [PATCH 2/7] Lint --- crates/web5/src/dids/methods/did_web/mod.rs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/crates/web5/src/dids/methods/did_web/mod.rs b/crates/web5/src/dids/methods/did_web/mod.rs index d127f9de..834557ac 100644 --- a/crates/web5/src/dids/methods/did_web/mod.rs +++ b/crates/web5/src/dids/methods/did_web/mod.rs @@ -1,14 +1,17 @@ mod resolver; use super::{MethodError, Result}; -use crate::{crypto::jwk::Jwk, dids::{ - data_model::{document::Document, verification_method::VerificationMethod}, - did::Did, - resolution::{ - resolution_metadata::{ResolutionMetadata, ResolutionMetadataError}, - resolution_result::ResolutionResult, +use crate::{ + crypto::jwk::Jwk, + dids::{ + data_model::{document::Document, verification_method::VerificationMethod}, + did::Did, + resolution::{ + resolution_metadata::{ResolutionMetadata, ResolutionMetadataError}, + resolution_result::ResolutionResult, + }, }, -}}; +}; use resolver::Resolver; #[derive(Clone)] @@ -37,7 +40,7 @@ impl DidWeb { Ok(DidWeb { did: Did::new(&did)?, - document + document, }) } From 5a03e7c439ea7fe94ec2472e73056e1c40754a83 Mon Sep 17 00:00:00 2001 From: Kendall Weihe Date: Tue, 9 Jul 2024 10:29:50 -0400 Subject: [PATCH 3/7] Add did:web constructor to APID --- docs/API_DESIGN.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/API_DESIGN.md b/docs/API_DESIGN.md index 40eaf3d9..cee790ca 100644 --- a/docs/API_DESIGN.md +++ b/docs/API_DESIGN.md @@ -2,7 +2,7 @@ **Last Updated** May 30, 2024 -**Version** 2.0.0 +**Version** 2.1.0 **[Custom DSL](./CUSTOM_DSL.md) Version**: 0.1.0 @@ -582,8 +582,13 @@ resolution_result = DidJwk.resolve(uri) ### `DidWeb` +> [!NOTE] +> +> The `CONSTRUCTOR(domain: string, public_jwk: Jwk)` does not publish the DID Document to a host, but merely creates the instance of the `did:web` in the local scope. + ```pseudocode! CLASS DidWeb + CONSTRUCTOR(domain: string, public_jwk: Jwk) CONSTRUCTOR(uri: string) STATIC METHOD resolve(uri: string): ResolutionResult ``` From 86cbc0d42abe7e1316115850bf770d4f37cfbb24 Mon Sep 17 00:00:00 2001 From: Diane Huxley Date: Thu, 11 Jul 2024 16:06:12 -0700 Subject: [PATCH 4/7] Normalize and encoded url --- crates/web5_cli/Cargo.toml | 1 + crates/web5_cli/src/dids/create.rs | 41 ++++++++++++++++++++++++++++-- 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/crates/web5_cli/Cargo.toml b/crates/web5_cli/Cargo.toml index 1aa4906b..36d5f023 100644 --- a/crates/web5_cli/Cargo.toml +++ b/crates/web5_cli/Cargo.toml @@ -11,4 +11,5 @@ chrono = { workspace = true } clap = { version = "4.5.7", features = ["derive"] } serde_json = { workspace = true } web5 = { path = "../web5" } +url = "2.5.2" uuid = { workspace = true } \ No newline at end of file diff --git a/crates/web5_cli/src/dids/create.rs b/crates/web5_cli/src/dids/create.rs index 8c536d07..efe03608 100644 --- a/crates/web5_cli/src/dids/create.rs +++ b/crates/web5_cli/src/dids/create.rs @@ -1,4 +1,5 @@ use clap::Subcommand; +use url::Url; use std::sync::Arc; use web5::{ crypto::dsa::ed25519::{Ed25519Generator, Ed25519Signer}, @@ -17,7 +18,6 @@ pub enum Commands { json_escape: bool, }, Web { - #[arg(long)] domain: String, #[arg(long)] no_indent: bool, @@ -77,7 +77,44 @@ impl Commands { let mut public_jwk = private_jwk.clone(); public_jwk.d = None; - let did_web = DidWeb::new(domain, public_jwk).unwrap(); + let valid_url = if domain.starts_with("http://") || domain.starts_with("https://") { + let url = Url::parse(domain).expect("Invalid URL"); + + // Ensure "http://" is only allowed for localhost or 127.0.0.1 + if url.scheme() == "http" && !(url.host_str() == Some("localhost") || url.host_str() == Some("127.0.0.1")) { + panic!("Only https is allowed except for localhost or 127.0.0.1 with http"); + } + + // Get the trimmed URL string without the scheme + let trimmed_url = url[url::Position::BeforeHost..].to_string(); + + // Remove the scheme + let normalized = if trimmed_url.starts_with("//") { + &trimmed_url[2..] + } else { + &trimmed_url + }; + + normalized.to_string() + } else { + Url::parse(&format!("https://{}", domain)).expect("Invalid URL"); + domain.clone() + }; + + let normalized = if valid_url.ends_with("/did.json") { + valid_url.trim_end_matches("/did.json").to_string() + } else if valid_url.ends_with("/.well-known") { + valid_url.trim_end_matches("/.well-known").to_string() + } else if valid_url.ends_with("/") { + valid_url.trim_end_matches("/").to_string() + } else { + valid_url.clone() + }; + + let encoded_domain = normalized.replace(":", "%3A"); + let encoded_domain = encoded_domain.replace("/", ":"); + + let did_web = DidWeb::new(&encoded_domain, public_jwk).unwrap(); let portable_did = PortableDid { did_uri: did_web.did.uri, document: did_web.document, From 906f2510b026a7070e74d3e2f916282e93ab13d9 Mon Sep 17 00:00:00 2001 From: Kendall Weihe Date: Tue, 16 Jul 2024 09:37:38 -0400 Subject: [PATCH 5/7] Fix normalization logic --- crates/web5_cli/src/dids/create.rs | 36 +++++++++++++++++------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/crates/web5_cli/src/dids/create.rs b/crates/web5_cli/src/dids/create.rs index efe03608..b46c7352 100644 --- a/crates/web5_cli/src/dids/create.rs +++ b/crates/web5_cli/src/dids/create.rs @@ -1,6 +1,6 @@ use clap::Subcommand; -use url::Url; use std::sync::Arc; +use url::Url; use web5::{ crypto::dsa::ed25519::{Ed25519Generator, Ed25519Signer}, dids::{ @@ -79,37 +79,41 @@ impl Commands { let valid_url = if domain.starts_with("http://") || domain.starts_with("https://") { let url = Url::parse(domain).expect("Invalid URL"); - + // Ensure "http://" is only allowed for localhost or 127.0.0.1 - if url.scheme() == "http" && !(url.host_str() == Some("localhost") || url.host_str() == Some("127.0.0.1")) { + if url.scheme() == "http" + && !(url.host_str() == Some("localhost") + || url.host_str() == Some("127.0.0.1")) + { panic!("Only https is allowed except for localhost or 127.0.0.1 with http"); } - + // Get the trimmed URL string without the scheme let trimmed_url = url[url::Position::BeforeHost..].to_string(); - + // Remove the scheme let normalized = if trimmed_url.starts_with("//") { &trimmed_url[2..] } else { &trimmed_url }; - + normalized.to_string() } else { Url::parse(&format!("https://{}", domain)).expect("Invalid URL"); domain.clone() }; - - let normalized = if valid_url.ends_with("/did.json") { - valid_url.trim_end_matches("/did.json").to_string() - } else if valid_url.ends_with("/.well-known") { - valid_url.trim_end_matches("/.well-known").to_string() - } else if valid_url.ends_with("/") { - valid_url.trim_end_matches("/").to_string() - } else { - valid_url.clone() - }; + + let mut normalized = valid_url.clone(); + if normalized.ends_with("/") { + normalized = normalized.trim_end_matches("/").to_string() + } + if normalized.ends_with("/did.json") { + normalized = normalized.trim_end_matches("/did.json").to_string() + } + if normalized.ends_with("/.well-known") { + normalized = normalized.trim_end_matches("/.well-known").to_string() + } let encoded_domain = normalized.replace(":", "%3A"); let encoded_domain = encoded_domain.replace("/", ":"); From c9a8e739c1e538226e9035083da14fd3d7f4313f Mon Sep 17 00:00:00 2001 From: Kendall Weihe Date: Tue, 16 Jul 2024 09:50:34 -0400 Subject: [PATCH 6/7] Move normalization and encoding features into native DidWeb::new() --- crates/web5/src/dids/methods/did_web/mod.rs | 52 ++++++++++++++++++++- crates/web5_cli/src/dids/create.rs | 44 +---------------- 2 files changed, 51 insertions(+), 45 deletions(-) diff --git a/crates/web5/src/dids/methods/did_web/mod.rs b/crates/web5/src/dids/methods/did_web/mod.rs index 834557ac..dd8a26c3 100644 --- a/crates/web5/src/dids/methods/did_web/mod.rs +++ b/crates/web5/src/dids/methods/did_web/mod.rs @@ -13,6 +13,7 @@ use crate::{ }, }; use resolver::Resolver; +use url::Url; #[derive(Clone)] pub struct DidWeb { @@ -22,11 +23,58 @@ pub struct DidWeb { impl DidWeb { pub fn new(domain: &str, public_jwk: Jwk) -> Result { - let did = format!("did:web:{}", domain); + let domain = &domain.to_string(); + let valid_url = if domain.starts_with("http://") || domain.starts_with("https://") { + let url = Url::parse(domain).map_err(|e| { + MethodError::DidCreationFailure(format!("url parse failure {}", e.to_string())) + })?; + + // Ensure "http://" is only allowed for localhost or 127.0.0.1 + if url.scheme() == "http" + && !(url.host_str() == Some("localhost") || url.host_str() == Some("127.0.0.1")) + { + return Err(MethodError::DidCreationFailure( + "only https is allowed except for localhost or 127.0.0.1 with http".to_string(), + )); + } + + // Get the trimmed URL string without the scheme + let trimmed_url = url[url::Position::BeforeHost..].to_string(); + + // Remove the scheme + let normalized = if let Some(trimmed) = trimmed_url.strip_prefix("//") { + trimmed + } else { + &trimmed_url + }; + + normalized.to_string() + } else { + Url::parse(&format!("https://{}", domain)).map_err(|e| { + MethodError::DidCreationFailure(format!("url parse failure {}", e.to_string())) + })?; + domain.clone() + }; + + let mut normalized = valid_url.clone(); + if normalized.ends_with('/') { + normalized = normalized.trim_end_matches('/').to_string() + } + if normalized.ends_with("/did.json") { + normalized = normalized.trim_end_matches("/did.json").to_string() + } + if normalized.ends_with("/.well-known") { + normalized = normalized.trim_end_matches("/.well-known").to_string() + } + + let encoded_domain = normalized.replace(':', "%3A"); + let encoded_domain = encoded_domain.replace('/', ":"); + + let did = format!("did:web:{}", encoded_domain); let verification_method = VerificationMethod { id: format!("{}#key-0", did), - r#type: "JsonWebKey2020".to_string(), + r#type: "JsonWebKey".to_string(), controller: did.clone(), public_key_jwk: public_jwk, }; diff --git a/crates/web5_cli/src/dids/create.rs b/crates/web5_cli/src/dids/create.rs index b46c7352..c98cbf19 100644 --- a/crates/web5_cli/src/dids/create.rs +++ b/crates/web5_cli/src/dids/create.rs @@ -1,6 +1,5 @@ use clap::Subcommand; use std::sync::Arc; -use url::Url; use web5::{ crypto::dsa::ed25519::{Ed25519Generator, Ed25519Signer}, dids::{ @@ -77,48 +76,7 @@ impl Commands { let mut public_jwk = private_jwk.clone(); public_jwk.d = None; - let valid_url = if domain.starts_with("http://") || domain.starts_with("https://") { - let url = Url::parse(domain).expect("Invalid URL"); - - // Ensure "http://" is only allowed for localhost or 127.0.0.1 - if url.scheme() == "http" - && !(url.host_str() == Some("localhost") - || url.host_str() == Some("127.0.0.1")) - { - panic!("Only https is allowed except for localhost or 127.0.0.1 with http"); - } - - // Get the trimmed URL string without the scheme - let trimmed_url = url[url::Position::BeforeHost..].to_string(); - - // Remove the scheme - let normalized = if trimmed_url.starts_with("//") { - &trimmed_url[2..] - } else { - &trimmed_url - }; - - normalized.to_string() - } else { - Url::parse(&format!("https://{}", domain)).expect("Invalid URL"); - domain.clone() - }; - - let mut normalized = valid_url.clone(); - if normalized.ends_with("/") { - normalized = normalized.trim_end_matches("/").to_string() - } - if normalized.ends_with("/did.json") { - normalized = normalized.trim_end_matches("/did.json").to_string() - } - if normalized.ends_with("/.well-known") { - normalized = normalized.trim_end_matches("/.well-known").to_string() - } - - let encoded_domain = normalized.replace(":", "%3A"); - let encoded_domain = encoded_domain.replace("/", ":"); - - let did_web = DidWeb::new(&encoded_domain, public_jwk).unwrap(); + let did_web = DidWeb::new(domain, public_jwk).unwrap(); let portable_did = PortableDid { did_uri: did_web.did.uri, document: did_web.document, From 497eefb085496a76f70c4420d7223dc45315cf32 Mon Sep 17 00:00:00 2001 From: Kendall Weihe Date: Tue, 16 Jul 2024 09:53:25 -0400 Subject: [PATCH 7/7] Fix linting --- crates/web5/src/dids/methods/did_web/mod.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/crates/web5/src/dids/methods/did_web/mod.rs b/crates/web5/src/dids/methods/did_web/mod.rs index dd8a26c3..3ec24d1f 100644 --- a/crates/web5/src/dids/methods/did_web/mod.rs +++ b/crates/web5/src/dids/methods/did_web/mod.rs @@ -25,9 +25,8 @@ impl DidWeb { pub fn new(domain: &str, public_jwk: Jwk) -> Result { let domain = &domain.to_string(); let valid_url = if domain.starts_with("http://") || domain.starts_with("https://") { - let url = Url::parse(domain).map_err(|e| { - MethodError::DidCreationFailure(format!("url parse failure {}", e.to_string())) - })?; + let url = Url::parse(domain) + .map_err(|e| MethodError::DidCreationFailure(format!("url parse failure {}", e)))?; // Ensure "http://" is only allowed for localhost or 127.0.0.1 if url.scheme() == "http" @@ -50,9 +49,8 @@ impl DidWeb { normalized.to_string() } else { - Url::parse(&format!("https://{}", domain)).map_err(|e| { - MethodError::DidCreationFailure(format!("url parse failure {}", e.to_string())) - })?; + Url::parse(&format!("https://{}", domain)) + .map_err(|e| MethodError::DidCreationFailure(format!("url parse failure {}", e)))?; domain.clone() };