diff --git a/identity_credential/src/credential/linked_domain_service.rs b/identity_credential/src/credential/linked_domain_service.rs index cf78f9b333..c6efbae255 100644 --- a/identity_credential/src/credential/linked_domain_service.rs +++ b/identity_credential/src/credential/linked_domain_service.rs @@ -11,6 +11,7 @@ use identity_document::service::ServiceEndpoint; use indexmap::map::IndexMap; use crate::error::Result; +use crate::utils::url_only_includes_origin; use crate::Error; use crate::Error::DomainLinkageError; @@ -97,6 +98,11 @@ impl LinkedDomainService { if endpoint.scheme() != "https" { Err(DomainLinkageError("domain does not include `https` scheme".into()))?; } + if !url_only_includes_origin(endpoint) { + Err(DomainLinkageError( + "domain must not contain any path, query or fragment".into(), + ))?; + } Ok(()) } ServiceEndpoint::Set(_) => Err(DomainLinkageError( @@ -114,6 +120,11 @@ impl LinkedDomainService { if origin.scheme() != "https" { return Err(DomainLinkageError("domain does not include `https` scheme".into())); } + if !url_only_includes_origin(origin) { + Err(DomainLinkageError( + "domain must not contain any path, query or fragment".into(), + ))?; + } } Ok(()) } diff --git a/identity_credential/src/domain_linkage/domain_linkage_configuration.rs b/identity_credential/src/domain_linkage/domain_linkage_configuration.rs index 2e4eaf3493..667c70276d 100644 --- a/identity_credential/src/domain_linkage/domain_linkage_configuration.rs +++ b/identity_credential/src/domain_linkage/domain_linkage_configuration.rs @@ -111,6 +111,7 @@ impl DomainLinkageConfiguration { mod __fetch_configuration { use crate::domain_linkage::DomainLinkageConfiguration; use crate::error::Result; + use crate::utils::url_only_includes_origin; use crate::Error::DomainLinkageError; use futures::StreamExt; use identity_core::common::Url; @@ -128,6 +129,11 @@ mod __fetch_configuration { if domain.scheme() != "https" { return Err(DomainLinkageError("domain` does not use `https` protocol".into())); } + if !url_only_includes_origin(&domain) { + return Err(DomainLinkageError( + "domain must not include any path, query or fragment".into(), + )); + } domain.set_path(".well-known/did-configuration.json"); let client: Client = reqwest::ClientBuilder::new() diff --git a/identity_credential/src/domain_linkage/domain_linkage_credential_builder.rs b/identity_credential/src/domain_linkage/domain_linkage_credential_builder.rs index 96e3323718..726e9b0cb6 100644 --- a/identity_credential/src/domain_linkage/domain_linkage_credential_builder.rs +++ b/identity_credential/src/domain_linkage/domain_linkage_credential_builder.rs @@ -14,6 +14,8 @@ use identity_core::common::Url; use identity_did::CoreDID; use identity_did::DID; +use crate::utils::url_only_includes_origin; + /// Convenient builder to create a spec compliant Domain Linkage Credential. /// /// See: @@ -74,6 +76,11 @@ impl DomainLinkageCredentialBuilder { "origin must be a domain with http(s) scheme".into(), )); } + if !url_only_includes_origin(&origin) { + return Err(Error::DomainLinkageError( + "origin must not contain any path, query or fragment".into(), + )); + } let mut properties: Object = Object::new(); properties.insert("origin".into(), origin.into_string().into()); @@ -139,6 +146,26 @@ mod tests { .unwrap_err(); assert!(matches!(err, Error::DomainLinkageError(_))); } + #[test] + fn test_builder_origin_is_a_url() { + let urls = [ + "https://example.com/foo?bar=420#baz", + "https://example.com/?bar=420", + "https://example.com/#baz", + "https://example.com/?bar=420#baz", + ]; + let issuer: CoreDID = "did:example:issuer".parse().unwrap(); + for url in urls { + let err: Error = DomainLinkageCredentialBuilder::new() + .issuance_date(Timestamp::now_utc()) + .expiration_date(Timestamp::now_utc()) + .issuer(issuer.clone()) + .origin(Url::parse(url).unwrap()) + .build() + .unwrap_err(); + assert!(matches!(err, Error::DomainLinkageError(_))); + } + } #[test] fn test_builder_no_issuer() { diff --git a/identity_credential/src/domain_linkage/domain_linkage_validator.rs b/identity_credential/src/domain_linkage/domain_linkage_validator.rs index aa2fd8e656..24969c1c65 100644 --- a/identity_credential/src/domain_linkage/domain_linkage_validator.rs +++ b/identity_credential/src/domain_linkage/domain_linkage_validator.rs @@ -18,6 +18,7 @@ use identity_verification::jws::JwsVerifier; use crate::validator::DecodedJwtCredential; use super::DomainLinkageValidationResult; +use crate::utils::url_only_includes_origin; /// A validator for a Domain Linkage Configuration and Credentials. @@ -190,6 +191,12 @@ impl JwtDomainLinkageValidator { source: Some(Box::new(other_error)), }), }?; + if !url_only_includes_origin(&origin_url) { + return Err(DomainLinkageValidationError { + cause: DomainLinkageValidationErrorCause::InvalidSubjectOrigin, + source: None, + }); + } if origin_url.origin() != domain.origin() { return Err(DomainLinkageValidationError { cause: DomainLinkageValidationErrorCause::OriginMismatch, diff --git a/identity_credential/src/lib.rs b/identity_credential/src/lib.rs index 79f85044a8..1901dd0ca5 100644 --- a/identity_credential/src/lib.rs +++ b/identity_credential/src/lib.rs @@ -23,6 +23,7 @@ pub mod error; pub mod presentation; #[cfg(feature = "revocation-bitmap")] pub mod revocation; +mod utils; #[cfg(feature = "validator")] pub mod validator; diff --git a/identity_credential/src/utils.rs b/identity_credential/src/utils.rs new file mode 100644 index 0000000000..4f6c4d4830 --- /dev/null +++ b/identity_credential/src/utils.rs @@ -0,0 +1,22 @@ +// Copyright 2020-2023 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use identity_core::common::Url; + +pub(crate) fn url_only_includes_origin(url: &Url) -> bool { + url.path() == "/" && url.query().is_none() && url.fragment().is_none() +} + +#[cfg(test)] +mod tests { + use super::url_only_includes_origin; + use identity_core::common::Url; + + #[test] + fn empty_path_and_root_are_valid_origins() { + let urls = ["https://example.com", "https://example.com/"]; + for url in urls { + assert!(url_only_includes_origin(&Url::parse(url).unwrap())); + } + } +} diff --git a/identity_storage/src/key_storage/memstore.rs b/identity_storage/src/key_storage/memstore.rs index 7dd92fc3c2..f101af4759 100644 --- a/identity_storage/src/key_storage/memstore.rs +++ b/identity_storage/src/key_storage/memstore.rs @@ -186,7 +186,7 @@ enum MemStoreKeyType { } impl JwkMemStore { - const ED25519_KEY_TYPE_STR: &str = "Ed25519"; + const ED25519_KEY_TYPE_STR: &'static str = "Ed25519"; /// The Ed25519 key type. pub const ED25519_KEY_TYPE: KeyType = KeyType::from_static_str(Self::ED25519_KEY_TYPE_STR); }