Use general Did type intsead of DidPkh in identity proofs

This commit is contained in:
silverpill 2022-11-08 21:59:45 +00:00
parent 4e53a5c4e4
commit dae6e9437b
12 changed files with 135 additions and 64 deletions

View file

@ -11,7 +11,7 @@ use crate::ethereum::identity::{
verify_eip191_identity_proof,
};
use crate::frontend::get_subscription_page_url;
use crate::identity::did_pkh::DidPkh;
use crate::identity::did::Did;
use crate::models::profiles::types::{
ExtraField,
IdentityProof,
@ -45,13 +45,14 @@ pub fn parse_identity_proof(
if proof_type != ETHEREUM_EIP191_PROOF {
return Err(ValidationError("unknown proof type"));
};
let did = attachment.name.parse::<DidPkh>()
let did = attachment.name.parse::<Did>()
.map_err(|_| ValidationError("invalid did"))?;
let Did::Pkh(ref did_pkh) = did;
let signature = attachment.signature_value.as_ref()
.ok_or(ValidationError("missing signature"))?;
verify_eip191_identity_proof(
actor_id,
&did,
did_pkh,
signature,
).map_err(|_| ValidationError("invalid identity proof"))?;
let proof = IdentityProof {

View file

@ -9,6 +9,7 @@ use crate::http_signatures::verify::{
verify_http_signature,
HttpSignatureVerificationError as HttpSignatureError,
};
use crate::identity::did::Did;
use crate::json_signatures::verify::{
get_json_signature,
verify_jcs_rsa_signature,
@ -136,8 +137,9 @@ pub async fn verify_signed_activity(
verify_jcs_rsa_signature(&signature_data, &public_key)?;
actor_profile
},
JsonSigner::DidPkh(ref signer) => {
let mut profiles: Vec<_> = search_profiles_by_did_only(db_client, signer)
JsonSigner::DidPkh(did_pkh) => {
let did = Did::Pkh(did_pkh.clone());
let mut profiles: Vec<_> = search_profiles_by_did_only(db_client, &did)
.await?.into_iter()
// Exclude local profiles
.filter(|profile| !profile.is_local())
@ -150,7 +152,7 @@ pub async fn verify_signed_activity(
};
if let Some(profile) = profiles.pop() {
verify_jcs_eip191_signature(
signer,
&did_pkh,
&signature_data.message,
&signature_data.signature,
)?;

79
src/identity/did.rs Normal file
View file

@ -0,0 +1,79 @@
/// https://www.w3.org/TR/did-core/
use std::fmt;
use std::str::FromStr;
use regex::Regex;
use serde::{
Deserialize, Deserializer, Serialize, Serializer,
de::Error as DeserializerError,
};
use super::did_pkh::DidPkh;
const DID_RE: &str = r"did:(?P<method>\w+):.+";
#[derive(Clone, Debug, PartialEq)]
pub enum Did {
Pkh(DidPkh),
}
#[derive(thiserror::Error, Debug)]
#[error("DID parse error")]
pub struct DidParseError;
impl FromStr for Did {
type Err = DidParseError;
fn from_str(value: &str) -> Result<Self, Self::Err> {
let did_re = Regex::new(DID_RE).unwrap();
let caps = did_re.captures(value).ok_or(DidParseError)?;
let did = match &caps["method"] {
"pkh" => {
let did_pkh = DidPkh::from_str(value)?;
Self::Pkh(did_pkh)
},
_ => return Err(DidParseError),
};
Ok(did)
}
}
impl fmt::Display for Did {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
let did_str = match self {
Self::Pkh(did_pkh) => did_pkh.to_string(),
};
write!(formatter, "{}", did_str)
}
}
impl<'de> Deserialize<'de> for Did {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where D: Deserializer<'de>
{
let did_str: String = Deserialize::deserialize(deserializer)?;
did_str.parse().map_err(DeserializerError::custom)
}
}
impl Serialize for Did {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where S: Serializer
{
let did_str = self.to_string();
serializer.serialize_str(&did_str)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_did_string_conversion() {
let did_str = "did:pkh:eip155:1:0xb9c5714089478a327f09197987f16f9e5d936e8a";
let did: Did = did_str.parse().unwrap();
assert!(matches!(did, Did::Pkh(_)));
assert_eq!(did.to_string(), did_str);
}
}

View file

@ -3,13 +3,10 @@ use std::convert::TryInto;
use std::str::FromStr;
use regex::Regex;
use serde::{
Deserialize, Deserializer, Serialize, Serializer,
de::Error as DeserializerError,
};
use crate::utils::caip2::ChainId;
use crate::utils::currencies::Currency;
use super::did::DidParseError;
#[derive(Clone, Debug, PartialEq)]
pub struct DidPkh {
@ -43,10 +40,6 @@ impl ToString for DidPkh {
}
}
#[derive(thiserror::Error, Debug)]
#[error("DID parse error")]
pub struct DidParseError;
// https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-10.md#syntax
const DID_PKH_RE: &str = r"did:pkh:(?P<network>[-a-z0-9]{3,8}):(?P<chain>[-a-zA-Z0-9]{1,32}):(?P<address>[a-zA-Z0-9]{1,64})";
@ -67,30 +60,12 @@ impl FromStr for DidPkh {
}
}
impl Serialize for DidPkh {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where S: Serializer
{
let did_str = self.to_string();
serializer.serialize_str(&did_str)
}
}
impl<'de> Deserialize<'de> for DidPkh {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where D: Deserializer<'de>
{
let did_str: String = Deserialize::deserialize(deserializer)?;
did_str.parse().map_err(DeserializerError::custom)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_did_string_conversion() {
fn test_did_pkh_string_conversion() {
let address = "0xB9C5714089478a327F09197987f16f9E5d936E8a";
let ethereum = Currency::Ethereum;
let did = DidPkh::from_address(&ethereum, address);

View file

@ -1 +1,2 @@
pub mod did;
pub mod did_pkh;

View file

@ -5,6 +5,7 @@ use serde::{Deserialize, Serialize};
use uuid::Uuid;
use crate::errors::ValidationError;
use crate::identity::did::Did;
use crate::mastodon_api::pagination::PageSize;
use crate::mastodon_api::uploads::{UploadError, save_validated_b64_file};
use crate::models::profiles::types::{
@ -78,12 +79,13 @@ impl Account {
let mut identity_proofs = vec![];
for proof in profile.identity_proofs.clone().into_inner() {
let Did::Pkh(did_pkh) = proof.issuer;
// Skip proof if it doesn't map to field name
if let Some(currency) = proof.issuer.currency() {
if let Some(currency) = did_pkh.currency() {
let field_name = currency.field_name();
let field = AccountField {
name: field_name,
value: proof.issuer.address,
value: did_pkh.address,
// Use current time because DID proofs are always valid
verified_at: Some(Utc::now()),
};

View file

@ -25,7 +25,7 @@ use crate::ethereum::identity::{
create_identity_claim,
verify_eip191_identity_proof,
};
use crate::identity::did_pkh::DidPkh;
use crate::identity::did::Did;
use crate::json_signatures::{
canonicalization::canonicalize_object,
create::{add_integrity_proof, IntegrityProof},
@ -249,7 +249,7 @@ async fn send_signed_update(
) -> Result<HttpResponse, HttpError> {
let db_client = &mut **get_database_client(&db_pool).await?;
let current_user = get_current_user(db_client, auth.token()).await?;
let signer = data.signer.parse::<DidPkh>()
let signer = data.signer.parse::<Did>()
.map_err(|_| ValidationError("invalid DID"))?;
if !current_user.profile.identity_proofs.any(&signer) {
return Err(ValidationError("unknown signer").into());
@ -261,6 +261,7 @@ 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);
@ -290,7 +291,7 @@ 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 = query_params.did.parse::<DidPkh>()
let Did::Pkh(did) = query_params.did.parse::<Did>()
.map_err(|_| ValidationError("invalid DID"))?;
let claim = create_identity_claim(&actor_id, &did)
.map_err(|_| HttpError::InternalError)?;
@ -308,9 +309,10 @@ async fn create_identity_proof(
let db_client = &**get_database_client(&db_pool).await?;
let mut current_user = get_current_user(db_client, auth.token()).await?;
let actor_id = current_user.profile.actor_id(&config.instance_url());
let did = proof_data.did.parse::<DidPkh>()
let did = proof_data.did.parse::<Did>()
.map_err(|_| ValidationError("invalid DID"))?;
if did.chain_id != ChainId::ethereum_mainnet() {
let Did::Pkh(ref did_pkh) = did;
if did_pkh.chain_id != ChainId::ethereum_mainnet() {
// DID must point to Ethereum Mainnet because it is a valid
// identifier on any Ethereum chain
return Err(ValidationError("unsupported chain ID").into());
@ -319,7 +321,7 @@ async fn create_identity_proof(
current_user.public_wallet_address(&Currency::Ethereum);
if let Some(address) = maybe_public_address {
// Do not allow to add more than one address proof
if did.address != address {
if did_pkh.address != address {
return Err(ValidationError("DID doesn't match current identity").into());
};
};
@ -336,7 +338,7 @@ async fn create_identity_proof(
};
verify_eip191_identity_proof(
&actor_id,
&did,
did_pkh,
&proof_data.signature,
)?;
let proof = IdentityProof {
@ -399,7 +401,7 @@ async fn search_by_did(
query_params: web::Query<SearchDidQueryParams>,
) -> Result<HttpResponse, HttpError> {
let db_client = &**get_database_client(&db_pool).await?;
let did: DidPkh = query_params.did.parse()
let did: Did = query_params.did.parse()
.map_err(|_| ValidationError("invalid DID"))?;
let profiles = search_profiles_by_did(db_client, &did, false).await?;
let accounts: Vec<Account> = profiles.into_iter()

View file

@ -11,7 +11,7 @@ use crate::activitypub::fetcher::helpers::{
};
use crate::config::Config;
use crate::errors::{ValidationError, HttpError};
use crate::identity::did_pkh::DidPkh;
use crate::identity::did::Did;
use crate::mastodon_api::accounts::types::Account;
use crate::mastodon_api::statuses::helpers::build_status_list;
use crate::mastodon_api::statuses::types::Tag;
@ -33,7 +33,7 @@ enum SearchQuery {
TagQuery(String),
Url(String),
WalletAddress(String),
Did(DidPkh),
Did(Did),
Unknown,
}
@ -66,7 +66,7 @@ fn parse_tag_query(query: &str) -> Result<String, ValidationError> {
fn parse_search_query(search_query: &str) -> SearchQuery {
let search_query = search_query.trim();
// DID is a valid URI so it should be tried before Url::parse
if let Ok(did) = DidPkh::from_str(search_query) {
if let Ok(did) = Did::from_str(search_query) {
return SearchQuery::Did(did);
};
if Url::parse(search_query).is_ok() {

View file

@ -4,7 +4,7 @@ use uuid::Uuid;
use crate::database::catch_unique_violation;
use crate::database::query_macro::query;
use crate::errors::DatabaseError;
use crate::identity::did_pkh::DidPkh;
use crate::identity::{did::Did, did_pkh::DidPkh};
use crate::models::cleanup::{
find_orphaned_files,
find_orphaned_ipfs_objects,
@ -402,7 +402,7 @@ pub async fn search_profiles(
pub async fn search_profiles_by_did_only(
db_client: &impl GenericClient,
did: &DidPkh,
did: &Did,
) -> Result<Vec<DbActorProfile>, DatabaseError> {
let rows = db_client.query(
"
@ -425,12 +425,17 @@ pub async fn search_profiles_by_did_only(
pub async fn search_profiles_by_did(
db_client: &impl GenericClient,
did: &DidPkh,
did: &Did,
prefer_verified: bool,
) -> Result<Vec<DbActorProfile>, DatabaseError> {
let did_str = did.to_string();
let verified = search_profiles_by_did_only(db_client, did).await?;
let unverified = if let Some(currency) = did.currency() {
let maybe_currency_address = match did {
Did::Pkh(did_pkh) => {
did_pkh.currency()
.map(|currency| (currency, did_pkh.address.clone()))
},
};
let unverified = if let Some((currency, address)) = maybe_currency_address {
// If currency is Ethereum,
// search over extra fields must be case insensitive.
let value_op = match currency {
@ -457,9 +462,8 @@ pub async fn search_profiles_by_did(
let field_name = currency.field_name();
let query = query!(
&statement,
did=did_str,
field_name=field_name,
field_value=did.address,
field_value=address,
)?;
let rows = db_client.query(query.sql(), query.parameters()).await?;
let unverified = rows.iter()
@ -487,7 +491,8 @@ pub async fn search_profiles_by_wallet_address(
wallet_address: &str,
prefer_verified: bool,
) -> Result<Vec<DbActorProfile>, DatabaseError> {
let did = DidPkh::from_address(currency, wallet_address);
let did_pkh = DidPkh::from_address(currency, wallet_address);
let did = Did::Pkh(did_pkh);
search_profiles_by_did(db_client, &did, prefer_verified).await
}
@ -723,7 +728,7 @@ mod tests {
async fn test_search_profiles_by_wallet_address_identity_proof() {
let db_client = &mut create_test_database().await;
let identity_proof = IdentityProof {
issuer: DidPkh::from_address(&ETHEREUM, "0x1234abcd"),
issuer: Did::Pkh(DidPkh::from_address(&ETHEREUM, "0x1234abcd")),
proof_type: "ethereum".to_string(),
value: "13590013185bdea963".to_string(),
};

View file

@ -14,7 +14,7 @@ use crate::activitypub::actors::types::{Actor, ActorAddress};
use crate::activitypub::identifiers::local_actor_id;
use crate::database::json_macro::{json_from_sql, json_to_sql};
use crate::errors::{ConversionError, ValidationError};
use crate::identity::did_pkh::DidPkh;
use crate::identity::did::Did;
use crate::utils::caip2::ChainId;
use super::validators::{
validate_username,
@ -25,7 +25,7 @@ use super::validators::{
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct IdentityProof {
pub issuer: DidPkh,
pub issuer: Did,
pub proof_type: String,
pub value: String,
}
@ -41,7 +41,7 @@ impl IdentityProofs {
/// Returns true if identity proof list contains at least one proof
/// created by a given DID.
pub fn any(&self, issuer: &DidPkh) -> bool {
pub fn any(&self, issuer: &Did) -> bool {
let Self(identity_proofs) = self;
identity_proofs.iter().any(|proof| proof.issuer == *issuer)
}
@ -430,7 +430,8 @@ 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();
assert_eq!(proof.issuer.address, "0xb9c5714089478a327f09197987f16f9e5d936e8a");
let Did::Pkh(ref did_pkh) = proof.issuer;
assert_eq!(did_pkh.address, "0xb9c5714089478a327f09197987f16f9e5d936e8a");
let serialized = serde_json::to_string(&proof).unwrap();
assert_eq!(serialized, json_data);
}

View file

@ -3,7 +3,7 @@ use uuid::Uuid;
use crate::database::catch_unique_violation;
use crate::errors::DatabaseError;
use crate::identity::did_pkh::DidPkh;
use crate::identity::{did::Did, did_pkh::DidPkh};
use crate::models::profiles::queries::create_profile;
use crate::models::profiles::types::{DbActorProfile, ProfileCreateData};
use crate::utils::currencies::Currency;
@ -184,7 +184,7 @@ pub async fn get_user_by_login_address(
pub async fn get_user_by_did(
db_client: &impl GenericClient,
did: &DidPkh,
did: &Did,
) -> Result<User, DatabaseError> {
// DIDs must be locally unique
let maybe_row = db_client.query_opt(
@ -212,7 +212,8 @@ pub async fn get_user_by_public_wallet_address(
currency: &Currency,
wallet_address: &str,
) -> Result<User, DatabaseError> {
let did = DidPkh::from_address(currency, wallet_address);
let did_pkh = DidPkh::from_address(currency, wallet_address);
let did = Did::Pkh(did_pkh);
get_user_by_did(db_client, &did).await
}

View file

@ -4,6 +4,7 @@ use regex::Regex;
use uuid::Uuid;
use crate::errors::ValidationError;
use crate::identity::did::Did;
use crate::models::profiles::types::DbActorProfile;
use crate::utils::currencies::Currency;
@ -48,11 +49,12 @@ impl User {
/// Returns wallet address if it is verified
pub fn public_wallet_address(&self, currency: &Currency) -> Option<String> {
for proof in self.profile.identity_proofs.clone().into_inner() {
let Did::Pkh(did_pkh) = proof.issuer;
// Return the first matching address, because only
// one proof per currency is allowed.
if let Some(ref address_currency) = proof.issuer.currency() {
if let Some(ref address_currency) = did_pkh.currency() {
if address_currency == currency {
return Some(proof.issuer.address);
return Some(did_pkh.address);
};
};
};