//! Verification of HTTP signatures. use http::Request; use puppy::fetch::{ signatures::{Private, Public, Signature, SigningKey, VerificationKey, Key}, FetchError, }; use serde_json::{json, Value}; use puppy::config::Config; /// Checks request signatures. #[derive(Clone)] pub struct Verifier { actor_id: String, key_id: String, private: Private, public: Public, } const VERIFIER_PATH: &str = "/s/request-verifier"; /// The path at which the request verification actor will present itself. pub const VERIFIER_MOUNT: &[&str] = &["s", "request-verifier"]; /// A "verdict" about a signed request, passed by a [`Verifier`]. pub enum Verdict { /// The signature checks out. Verified(Signer), /// The signature does not contain a signature header. This may be intentional, or a client error. Unsigned, /// The signature failed to verify due to an error related to the signature itself. Rejected { signature_str: String, reason: String, }, } impl Verifier { /// Get the JSON-LD representation of the verifier actor. pub fn to_json_ld(&self) -> Value { json!({ "@context": [ "https://www.w3.org/ns/activitystreams", "https://w3id.org/security/v1", ], "id": self.actor_id, "name": "Public key fetcher", "publicKey": { "id": self.key_id, "owner": self.actor_id, "publicKeyPem": self.public.encode_pem() }, "type": "Service", }) } /// Load the server's verifier actor. /// /// Each server has one special actor for fetching public keys. Unlike all other objects, /// acquiring that actor's JSON-LD representation does not require a request signature. /// /// It doesn't have any data in the data store. Due to its exceptional nature, we just put /// the private key in the [`state_dir`][Config::state_dir]. The very first time you load /// the verifier, it generates the required private keys. pub fn load(cfg: &Config) -> Verifier { let Config { ap_domain, state_dir, .. } = cfg; let key_path = format!("{state_dir}/fetcher.pem"); // Read the private key from the state directory, or generate a new one if it couldn't // be read. let private = Private::load(&key_path).unwrap_or_else(|| { let (private, _) = Private::gen(); private.save(key_path); private }); Verifier { actor_id: format!("https://{ap_domain}{VERIFIER_PATH}"), key_id: format!("https://{ap_domain}{VERIFIER_PATH}#sig-key"), public: private.get_public(), private, } } /// Does the HTTP signature verification process, and returns a "proof" of the signature in the form /// of the [`Signer`], which contains information about who signed a particular request. pub async fn verify(&self, req: &Request) -> Verdict { // TODO: implement the whole verification thing as a middleware so we can intercept requests // like these, instead of coupling this tightly with the router. if req.uri().path() == VERIFIER_PATH { // HACK: Allow access to the request verifier actor without checking the signature. return Verdict::Unsigned; } let Some(header) = req.headers().get("signature") else { return Verdict::Unsigned; }; let signature_str = header .to_str() .expect("signature header value should be valid ascii") .to_string(); let sig = match Signature::derive(&req) { Err(error) => return Verdict::Rejected { signature_str, reason: error }, Ok(signature) => signature, }; // Fetch the signer's public key using our private key. let fetch_result = self.fetch_public_key(sig.key_id()).await; let public_key = match fetch_result { Ok(public_key) => public_key, Err(err) => { return Verdict::Rejected { reason: format!("could not fetch public key: {err}"), signature_str, } } }; // TODO: verify digest also if let Err(error) = public_key.verify(&sig) { Verdict::Rejected { signature_str, reason: error } } else { Verdict::Verified(Signer { ap_id: public_key.owner }) } } /// Send a request to get the public key from an ID. This request will be signed with the /// verifier actor's public key. async fn fetch_public_key(&self, uri: &str) -> Result { let json = puppy::fetch::resolve(&self.signing_key(), uri).await?; let Some(key) = Key::from_json(json) else { return Err(FetchError::BadJson( "invalid public key structure".to_string(), )); }; Ok(key.upgrade()) } /// Get the key that the verification actor signs requests with. fn signing_key(&self) -> SigningKey { Key { id: self.key_id.clone(), owner: self.actor_id.clone(), inner: self.private.clone(), } } } /// An ActivityPub actor that signed a request. pub struct Signer { /// The ActivityPub ID (a URL) of the signer of the request. pub ap_id: String, }