use actix_web::HttpRequest; use serde_json::Value; use crate::config::Config; use crate::database::{DatabaseClient, DatabaseError}; use crate::http_signatures::verify::{ parse_http_signature, verify_http_signature, HttpSignatureVerificationError as HttpSignatureError, }; use crate::identity::{did::Did, signatures::SignatureType}; use crate::json_signatures::verify::{ get_json_signature, verify_ed25519_json_signature, verify_eip191_json_signature, verify_rsa_json_signature, JsonSignatureVerificationError as JsonSignatureError, JsonSigner, }; use crate::models::profiles::queries::get_profile_by_remote_actor_id; use crate::models::profiles::types::DbActorProfile; use crate::utils::crypto_rsa::deserialize_public_key; use super::fetcher::helpers::get_or_import_profile_by_actor_id; use super::receiver::HandlerError; #[derive(thiserror::Error, Debug)] pub enum AuthenticationError { #[error(transparent)] HttpSignatureError(#[from] HttpSignatureError), #[error("no HTTP signature")] NoHttpSignature, #[error(transparent)] JsonSignatureError(#[from] JsonSignatureError), #[error("no JSON signature")] NoJsonSignature, #[error("invalid JSON signature type")] InvalidJsonSignatureType, #[error("invalid key ID")] InvalidKeyId(#[from] url::ParseError), #[error("database error")] DatabaseError(#[from] DatabaseError), #[error("{0}")] ImportError(String), #[error("{0}")] ActorError(&'static str), #[error("invalid public key")] InvalidPublicKey(#[from] rsa::pkcs8::Error), #[error("actor and request signer do not match")] UnexpectedSigner, } fn key_id_to_actor_id(key_id: &str) -> Result { let key_url = url::Url::parse(key_id)?; // Strip #main-key (works with most AP servers) let actor_id = &key_url[..url::Position::BeforeQuery]; // GoToSocial compat let actor_id = actor_id.trim_end_matches("/main-key"); Ok(actor_id.to_string()) } async fn get_signer( config: &Config, db_client: &impl DatabaseClient, signer_id: &str, no_fetch: bool, ) -> Result { let signer = if no_fetch { // Avoid fetching (e.g. if signer was deleted) get_profile_by_remote_actor_id(db_client, signer_id).await? } else { match get_or_import_profile_by_actor_id( db_client, &config.instance(), &config.media_dir(), signer_id, ).await { Ok(profile) => profile, Err(HandlerError::DatabaseError(error)) => return Err(error.into()), Err(other_error) => { return Err(AuthenticationError::ImportError(other_error.to_string())); }, } }; Ok(signer) } /// Verifies HTTP signature and returns signer pub async fn verify_signed_request( config: &Config, db_client: &impl DatabaseClient, request: &HttpRequest, no_fetch: bool, ) -> Result { let signature_data = match parse_http_signature( request.method(), request.uri(), request.headers(), ) { Ok(signature_data) => signature_data, Err(HttpSignatureError::NoSignature) => { return Err(AuthenticationError::NoHttpSignature); }, Err(other_error) => return Err(other_error.into()), }; let signer_id = key_id_to_actor_id(&signature_data.key_id)?; let signer = get_signer(config, db_client, &signer_id, no_fetch).await?; let signer_actor = signer.actor_json.as_ref() .expect("request should be signed by remote actor"); let signer_key = deserialize_public_key(&signer_actor.public_key.public_key_pem)?; verify_http_signature(&signature_data, &signer_key)?; Ok(signer) } /// Verifies JSON signature and returns signer pub async fn verify_signed_activity( config: &Config, db_client: &impl DatabaseClient, activity: &Value, no_fetch: bool, ) -> Result { let signature_data = match get_json_signature(activity) { Ok(signature_data) => signature_data, Err(JsonSignatureError::NoProof) => { return Err(AuthenticationError::NoJsonSignature); }, Err(other_error) => return Err(other_error.into()), }; // Signed activities must have `actor` property, to avoid situations // where signer is identified by DID but there is no matching // identity proof in the local database. let actor_id = activity["actor"].as_str() .ok_or(AuthenticationError::ActorError("unknown actor"))?; let actor_profile = get_signer(config, db_client, actor_id, no_fetch).await?; match signature_data.signer { JsonSigner::ActorKeyId(ref key_id) => { if signature_data.signature_type != SignatureType::JcsRsaSignature { return Err(AuthenticationError::InvalidJsonSignatureType); }; let signer_id = key_id_to_actor_id(key_id)?; if signer_id != actor_id { return Err(AuthenticationError::UnexpectedSigner); }; let signer_actor = actor_profile.actor_json.as_ref() .expect("activity should be signed by remote actor"); let signer_key = deserialize_public_key(&signer_actor.public_key.public_key_pem)?; verify_rsa_json_signature(&signature_data, &signer_key)?; }, JsonSigner::Did(did) => { if !actor_profile.identity_proofs.any(&did) { return Err(AuthenticationError::UnexpectedSigner); }; match signature_data.signature_type { SignatureType::JcsEd25519Signature => { let did_key = match did { Did::Key(did_key) => did_key, _ => return Err(AuthenticationError::InvalidJsonSignatureType), }; verify_ed25519_json_signature( &did_key, &signature_data.message, &signature_data.signature, )?; }, SignatureType::JcsEip191Signature => { let did_pkh = match did { Did::Pkh(did_pkh) => did_pkh, _ => return Err(AuthenticationError::InvalidJsonSignatureType), }; verify_eip191_json_signature( &did_pkh, &signature_data.message, &signature_data.signature, )?; }, _ => return Err(AuthenticationError::InvalidJsonSignatureType), }; }, }; // Signer is actor Ok(actor_profile) } #[cfg(test)] mod tests { use super::*; #[test] fn test_key_id_to_actor_id() { let key_id = "https://myserver.org/actor#main-key"; let actor_id = key_id_to_actor_id(key_id).unwrap(); assert_eq!(actor_id, "https://myserver.org/actor"); let key_id = "https://myserver.org/actor/main-key"; let actor_id = key_id_to_actor_id(key_id).unwrap(); assert_eq!(actor_id, "https://myserver.org/actor"); } }