puppy/bin/server/src/sig.rs

150 lines
5.4 KiB
Rust
Raw Normal View History

//! 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<B>(&self, req: &Request<B>) -> 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<VerificationKey, FetchError> {
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,
}