diff --git a/src/activitypub/actors/attachments.rs b/src/activitypub/actors/attachments.rs index b7e5b72..f498b99 100644 --- a/src/activitypub/actors/attachments.rs +++ b/src/activitypub/actors/attachments.rs @@ -47,7 +47,10 @@ pub fn parse_identity_proof( }; let did = attachment.name.parse::() .map_err(|_| ValidationError("invalid did"))?; - let Did::Pkh(ref did_pkh) = did; + let did_pkh = match did { + Did::Pkh(ref did_pkh) => did_pkh, + _ => return Err(ValidationError("invalid proof issuer")), + }; let signature = attachment.signature_value.as_ref() .ok_or(ValidationError("missing signature"))?; verify_eip191_identity_proof( diff --git a/src/identity/did.rs b/src/identity/did.rs index 4e14a0f..7a8e166 100644 --- a/src/identity/did.rs +++ b/src/identity/did.rs @@ -8,12 +8,14 @@ use serde::{ de::Error as DeserializerError, }; +use super::did_key::DidKey; use super::did_pkh::DidPkh; const DID_RE: &str = r"did:(?P\w+):.+"; #[derive(Clone, Debug, PartialEq)] pub enum Did { + Key(DidKey), Pkh(DidPkh), } @@ -28,6 +30,10 @@ impl FromStr for Did { let did_re = Regex::new(DID_RE).unwrap(); let caps = did_re.captures(value).ok_or(DidParseError)?; let did = match &caps["method"] { + "key" => { + let did_key = DidKey::from_str(value)?; + Self::Key(did_key) + }, "pkh" => { let did_pkh = DidPkh::from_str(value)?; Self::Pkh(did_pkh) @@ -41,6 +47,7 @@ impl FromStr for Did { impl fmt::Display for Did { fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { let did_str = match self { + Self::Key(did_key) => did_key.to_string(), Self::Pkh(did_pkh) => did_pkh.to_string(), }; write!(formatter, "{}", did_str) diff --git a/src/identity/did_key.rs b/src/identity/did_key.rs new file mode 100644 index 0000000..8cee6d5 --- /dev/null +++ b/src/identity/did_key.rs @@ -0,0 +1,46 @@ +/// https://w3c-ccg.github.io/did-method-key/ +use std::fmt; +use std::str::FromStr; + +use regex::Regex; + +use super::did::DidParseError; + +const DID_KEY_RE: &str = r"did:key:(?Pz[a-km-zA-HJ-NP-Z1-9]+)"; + +#[derive(Clone, Debug, PartialEq)] +pub struct DidKey { + pub key: String, +} + +impl FromStr for DidKey { + type Err = DidParseError; + + fn from_str(value: &str) -> Result { + let did_key_re = Regex::new(DID_KEY_RE).unwrap(); + let caps = did_key_re.captures(value).ok_or(DidParseError)?; + let did_key = Self { + key: caps["key"].to_string(), + }; + Ok(did_key) + } +} + +impl fmt::Display for DidKey { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + let did_str = format!("did:key:{}", self.key); + write!(formatter, "{}", did_str) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_did_key_string_conversion() { + let did_str = "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK"; + let did_key: DidKey = did_str.parse().unwrap(); + assert_eq!(did_key.to_string(), did_str); + } +} diff --git a/src/identity/did_pkh.rs b/src/identity/did_pkh.rs index f3e42c7..66d20ef 100644 --- a/src/identity/did_pkh.rs +++ b/src/identity/did_pkh.rs @@ -49,14 +49,14 @@ impl FromStr for DidPkh { fn from_str(value: &str) -> Result { let did_pkh_re = Regex::new(DID_PKH_RE).unwrap(); let caps = did_pkh_re.captures(value).ok_or(DidParseError)?; - let did = Self { + let did_pkh = Self { chain_id: ChainId { namespace: caps["network"].to_string(), reference: caps["chain"].to_string(), }, address: caps["address"].to_string(), }; - Ok(did) + Ok(did_pkh) } } diff --git a/src/identity/mod.rs b/src/identity/mod.rs index 71660b9..42499cf 100644 --- a/src/identity/mod.rs +++ b/src/identity/mod.rs @@ -1,2 +1,3 @@ pub mod did; +pub mod did_key; pub mod did_pkh; diff --git a/src/mastodon_api/accounts/types.rs b/src/mastodon_api/accounts/types.rs index 4292cf9..c2e9d0b 100644 --- a/src/mastodon_api/accounts/types.rs +++ b/src/mastodon_api/accounts/types.rs @@ -79,7 +79,10 @@ impl Account { let mut identity_proofs = vec![]; for proof in profile.identity_proofs.clone().into_inner() { - let Did::Pkh(did_pkh) = proof.issuer; + let did_pkh = match proof.issuer { + Did::Pkh(did_pkh) => did_pkh, + _ => continue, + }; // Skip proof if it doesn't map to field name if let Some(currency) = did_pkh.currency() { let field_name = currency.field_name(); diff --git a/src/mastodon_api/accounts/views.rs b/src/mastodon_api/accounts/views.rs index 7245915..daad253 100644 --- a/src/mastodon_api/accounts/views.rs +++ b/src/mastodon_api/accounts/views.rs @@ -261,10 +261,14 @@ async fn send_signed_update( ).map_err(|_| HttpError::InternalError)?; let canonical_json = canonicalize_object(&activity) .map_err(|_| HttpError::InternalError)?; - let Did::Pkh(signer) = signer; - verify_jcs_eip191_signature(&signer, &canonical_json, &data.signature) - .map_err(|_| ValidationError("invalid signature"))?; - let proof = IntegrityProof::jcs_eip191(&signer, &data.signature); + let proof = match signer { + Did::Key(_) => return Err(ValidationError("unsupported DID type").into()), + Did::Pkh(signer) => { + verify_jcs_eip191_signature(&signer, &canonical_json, &data.signature) + .map_err(|_| ValidationError("invalid signature"))?; + IntegrityProof::jcs_eip191(&signer, &data.signature) + }, + }; let mut activity_value = serde_json::to_value(activity) .map_err(|_| HttpError::InternalError)?; add_integrity_proof(&mut activity_value, proof) @@ -291,8 +295,12 @@ async fn get_identity_claim( let db_client = &**get_database_client(&db_pool).await?; let current_user = get_current_user(db_client, auth.token()).await?; let actor_id = current_user.profile.actor_id(&config.instance_url()); - let Did::Pkh(did) = query_params.did.parse::() + let did = query_params.did.parse::() .map_err(|_| ValidationError("invalid DID"))?; + let did = match did { + Did::Key(_) => return Err(ValidationError("unsupported DID type").into()), + Did::Pkh(did_pkh) => did_pkh, + }; let claim = create_identity_claim(&actor_id, &did) .map_err(|_| HttpError::InternalError)?; let response = IdentityClaim { claim }; @@ -311,7 +319,10 @@ async fn create_identity_proof( let actor_id = current_user.profile.actor_id(&config.instance_url()); let did = proof_data.did.parse::() .map_err(|_| ValidationError("invalid DID"))?; - let Did::Pkh(ref did_pkh) = did; + let did_pkh = match did { + Did::Key(_) => return Err(ValidationError("unsupported DID type").into()), + Did::Pkh(ref did_pkh) => did_pkh, + }; if did_pkh.chain_id != ChainId::ethereum_mainnet() { // DID must point to Ethereum Mainnet because it is a valid // identifier on any Ethereum chain diff --git a/src/models/profiles/queries.rs b/src/models/profiles/queries.rs index f30acad..2486d7b 100644 --- a/src/models/profiles/queries.rs +++ b/src/models/profiles/queries.rs @@ -434,6 +434,7 @@ pub async fn search_profiles_by_did( did_pkh.currency() .map(|currency| (currency, did_pkh.address.clone())) }, + _ => None, }; let unverified = if let Some((currency, address)) = maybe_currency_address { // If currency is Ethereum, diff --git a/src/models/profiles/types.rs b/src/models/profiles/types.rs index 5913994..6617694 100644 --- a/src/models/profiles/types.rs +++ b/src/models/profiles/types.rs @@ -430,7 +430,10 @@ mod tests { fn test_identity_proof_serialization() { let json_data = r#"{"issuer":"did:pkh:eip155:1:0xb9c5714089478a327f09197987f16f9e5d936e8a","proof_type":"ethereum-eip191-00","value":"dbfe"}"#; let proof: IdentityProof = serde_json::from_str(json_data).unwrap(); - let Did::Pkh(ref did_pkh) = proof.issuer; + let did_pkh = match proof.issuer { + Did::Pkh(ref did_pkh) => did_pkh, + _ => panic!("unexpected did method"), + }; assert_eq!(did_pkh.address, "0xb9c5714089478a327f09197987f16f9e5d936e8a"); let serialized = serde_json::to_string(&proof).unwrap(); assert_eq!(serialized, json_data); diff --git a/src/models/users/types.rs b/src/models/users/types.rs index 0d316f8..db95f89 100644 --- a/src/models/users/types.rs +++ b/src/models/users/types.rs @@ -49,7 +49,10 @@ impl User { /// Returns wallet address if it is verified pub fn public_wallet_address(&self, currency: &Currency) -> Option { for proof in self.profile.identity_proofs.clone().into_inner() { - let Did::Pkh(did_pkh) = proof.issuer; + let did_pkh = match proof.issuer { + Did::Pkh(did_pkh) => did_pkh, + _ => continue, + }; // Return the first matching address, because only // one proof per currency is allowed. if let Some(ref address_currency) = did_pkh.currency() {