Skip to main content

actpub_httpsig/
lib.rs

1//! Dual-stack HTTP message signatures for `ActivityPub`.
2//!
3//! Provides signing and verification for both:
4//!
5//! - [Cavage draft-12][cavage] — the de-facto Fediverse standard (Mastodon,
6//!   Pleroma, Lemmy, Misskey, …)
7//! - [RFC 9421][rfc9421] — the finalized IETF HTTP Message Signatures standard
8//!   (Mastodon 4.5+ accepts both)
9//!
10//! Algorithms supported out of the box:
11//!
12//! - `rsa-sha256` (2048–8192-bit modulus) — legacy main-key format,
13//!   required for interop with current Mastodon; [`RsaBits`] exposes
14//!   the conventional 2048 and 4096 presets for generation, and
15//!   [`RsaSigningKey::from_pkcs8_der`] accepts any byte-aligned width
16//!   in the full range
17//! - `ed25519` — FEP-521a Multikey, recommended for new deployments
18//!
19//! All cryptographic primitives are backed by [aws-lc-rs], a memory-safe,
20//! constant-time, FIPS 140-3 validated library maintained by AWS. This crate
21//! is therefore **not** affected by [RUSTSEC-2023-0071] (Marvin Attack) that
22//! impacts the pure-Rust `rsa` crate.
23//!
24//! The crate is HTTP-framework agnostic: it operates on [`http::Request`]
25//! values and leaves transport to the caller.
26//!
27//! # Example — Cavage signing
28//!
29//! ```
30//! # use actpub_httpsig::{SigningKey, CavageSigner, sha256_digest_header};
31//! # use http::{Request, Method};
32//! let key = SigningKey::generate_ed25519();
33//! let body: Vec<u8> = br#"{"type":"Follow"}"#.to_vec();
34//! let mut req = Request::builder()
35//!     .method(Method::POST)
36//!     .uri("https://example.com/inbox")
37//!     .header("host", "example.com")
38//!     .header("date", "Sun, 05 Jan 2014 21:31:40 GMT")
39//!     .header("digest", sha256_digest_header(&body))
40//!     .header("content-type", "application/activity+json")
41//!     .body(body)
42//!     .unwrap();
43//!
44//! let signer = CavageSigner::new(&key, "https://example.com/users/alice#main-key");
45//! signer.sign(&mut req).unwrap();
46//! assert!(req.headers().contains_key("signature"));
47//! ```
48//!
49//! [cavage]: https://datatracker.ietf.org/doc/html/draft-cavage-http-signatures-12
50//! [rfc9421]: https://www.rfc-editor.org/rfc/rfc9421.html
51//! [aws-lc-rs]: https://docs.rs/aws-lc-rs
52//! [RUSTSEC-2023-0071]: https://rustsec.org/advisories/RUSTSEC-2023-0071.html
53#![cfg_attr(docsrs, feature(doc_cfg))]
54#![allow(
55    clippy::error_impl_error,
56    reason = "`Error` is the idiomatic name for the crate's top-level error enum, matching the `thiserror` convention used pervasively in the Rust ecosystem"
57)]
58#![cfg_attr(
59    test,
60    allow(
61        clippy::indexing_slicing,
62        clippy::panic,
63        clippy::unwrap_used,
64        reason = "JSON / byte field indexing via `[\"key\"]` or `[0]` is ergonomic inside tests, and `panic!` / `unwrap()` are the idiomatic way to assert expectations with a failure message when a fixture is wrong"
65    )
66)]
67
68mod cavage;
69mod content_digest;
70mod digest;
71mod error;
72mod http_shared;
73mod key;
74mod policy;
75mod rfc9421;
76mod verify;
77
78use bytes as _;
79use pkcs8 as _;
80#[cfg(test)]
81use tokio as _;
82use tracing as _;
83use url as _;
84
85pub use self::cavage::{
86    CavageHeaderParams, CavageHeaderSet, CavageSigner, CavageVerified, DEFAULT_HEADER_SET,
87    SIGNATURE_HEADER, cavage_verify, cavage_verify_with_policy,
88};
89pub use self::content_digest::{
90    CONTENT_DIGEST_HEADER, DigestAlgorithm, content_digest_header, content_digest_header_with,
91    verify_any_content_digest_header, verify_content_digest_header,
92};
93pub use self::digest::{SHA256_DIGEST_PREFIX, sha256_digest_header, verify_digest_header};
94pub use self::error::Error;
95pub use self::key::{
96    Algorithm, Ed25519PublicKey, Ed25519SigningKey, Multikey, RsaBits, RsaPublicKey, RsaSigningKey,
97    SigningKey, VerifyingKey,
98};
99pub use self::policy::{CAVAGE_REQUIRED_HEADERS, VerifyPolicy};
100pub use self::rfc9421::{
101    Component, DEFAULT_COMPONENTS as RFC9421_DEFAULT_COMPONENTS, Rfc9421Signer, Rfc9421Verified,
102    SIGNATURE_INPUT_HEADER, SignatureInput, parse_signature_dict, parse_signature_input_dict,
103    rfc9421_verify, rfc9421_verify_with_policy, serialise_signature_dict,
104    serialise_signature_input_dict,
105};
106pub use self::verify::{REDACTED_HEADERS_DEFAULT, Verified, verify, verify_with_policy};
107
108/// Crate [`Result`] alias with the default error type set to [`Error`].
109pub type Result<T, E = Error> = core::result::Result<T, E>;