1use super::{BoxFuture, Context, DynKv, Error, authority};
10use crate::fetch::Fetcher;
11use crate::sig::SenderKey;
12use crate::sig::verification::{self, Policy, PublishedKey, Request};
13use serde_json::{Value, json};
14use std::sync::Arc;
15use std::time::Duration;
16use url::Url;
17
18type KeyFn<D> =
19 Arc<dyn Fn(Context<D>) -> BoxFuture<'static, Result<Option<SenderKey>, Error>> + Send + Sync>;
20
21pub(super) type FetcherFn<D> = Arc<dyn Fn(&D) -> Arc<Fetcher> + Send + Sync>;
22pub(super) type KvFn<D> = Arc<dyn Fn(&D) -> Arc<dyn DynKv> + Send + Sync>;
23pub(super) type KeyFetchedFn<D> =
24 Arc<dyn Fn(Context<D>, Value) -> BoxFuture<'static, ()> + Send + Sync>;
25pub(super) type KnownKeyFn<D> = Arc<
26 dyn Fn(Context<D>, String) -> BoxFuture<'static, Result<Option<KnownKey>, Error>> + Send + Sync,
27>;
28
29pub(super) struct SignedFetch<D> {
30 pub(super) fetcher: Arc<Fetcher>,
31 pub(super) kv: Arc<dyn DynKv>,
32 pub(super) key_ttl: Duration,
33 pub(super) key: KeyFn<D>,
34 pub(super) fetcher_for: Option<FetcherFn<D>>,
35 pub(super) kv_for: Option<KvFn<D>>,
36 pub(super) known_key: Option<KnownKeyFn<D>>,
37 pub(super) key_fetched: Option<KeyFetchedFn<D>>,
38}
39
40impl<D> SignedFetch<D> {
41 pub(super) fn fetcher(&self, data: &D) -> Arc<Fetcher> {
43 match &self.fetcher_for {
44 Some(fetcher) => fetcher(data),
45 None => self.fetcher.clone(),
46 }
47 }
48
49 pub(super) fn kv(&self, data: &D) -> Arc<dyn DynKv> {
51 match &self.kv_for {
52 Some(kv) => kv(data),
53 None => self.kv.clone(),
54 }
55 }
56}
57
58#[derive(Clone, Debug)]
61pub struct KnownKey {
62 pub pem: String,
63 pub actor: Url,
64}
65
66struct Published {
69 key: PublishedKey,
70 actor: Url,
71 document: Option<Value>,
72}
73
74impl Published {
75 fn to_json(&self) -> Value {
76 match &self.key {
77 PublishedKey::RsaPem(pem) => json!({"pem": pem, "actor": self.actor.as_str()}),
78 PublishedKey::Ed25519(bytes) => json!({
79 "ed25519": base64::Engine::encode(&base64::engine::general_purpose::STANDARD, bytes),
80 "actor": self.actor.as_str(),
81 }),
82 }
83 }
84
85 fn from_json(value: &Value) -> Option<Self> {
86 let key = match (value.get("pem"), value.get("ed25519")) {
87 (Some(pem), _) => PublishedKey::RsaPem(pem.as_str()?.to_owned()),
88 (None, Some(bytes)) => PublishedKey::Ed25519(
89 base64::Engine::decode(&base64::engine::general_purpose::STANDARD, bytes.as_str()?)
90 .ok()?
91 .try_into()
92 .ok()?,
93 ),
94 (None, None) => return None,
95 };
96 Some(Self {
97 key,
98 actor: Url::parse(value.get("actor")?.as_str()?).ok()?,
99 document: None,
100 })
101 }
102}
103
104#[derive(Clone, Debug, PartialEq, Eq)]
106pub enum Signing {
107 Unsigned,
109 Verified(Url),
111 Blocked(String),
114 Invalid(String),
116}
117
118pub(super) async fn verify<D: Clone + Send + Sync + 'static>(context: &Context<D>) -> Signing {
119 let signed = context.inner.request.as_ref().is_some_and(|info| {
120 info.headers
121 .iter()
122 .any(|(name, _)| name.eq_ignore_ascii_case("signature"))
123 });
124 if !signed {
125 return Signing::Unsigned;
126 }
127 match authenticate_signed(context, b"").await {
128 Ok(signer) => Signing::Verified(signer),
129 Err(Refusal::Blocked(host)) => Signing::Blocked(host),
130 Err(Refusal::Invalid(why)) => Signing::Invalid(why),
131 }
132}
133
134enum Refusal {
136 Blocked(String),
138 Invalid(String),
139}
140
141impl From<String> for Refusal {
142 fn from(why: String) -> Self {
143 Self::Invalid(why)
144 }
145}
146
147impl From<&str> for Refusal {
148 fn from(why: &str) -> Self {
149 Self::Invalid(why.to_owned())
150 }
151}
152
153pub(super) async fn authenticate<D: Clone + Send + Sync + 'static>(
157 context: &Context<D>,
158 body: &[u8],
159) -> Result<Url, String> {
160 authenticate_signed(context, body)
161 .await
162 .map_err(|refusal| match refusal {
163 Refusal::Blocked(host) => format!("signed with a key on {host}, which is blocked"),
164 Refusal::Invalid(why) => why,
165 })
166}
167
168pub(super) async fn blocked_key_id<D: Clone + Send + Sync + 'static>(
177 context: &Context<D>,
178 body: &[u8],
179) -> Result<Option<String>, Error> {
180 let Some(blocked) = &context.inner.federation.blocked else {
181 return Ok(None);
182 };
183 let Some(info) = context.inner.request.as_ref() else {
184 return Ok(None);
185 };
186 let headers: Vec<(&str, &str)> = info
187 .headers
188 .iter()
189 .map(|(name, value)| (name.as_str(), value.as_str()))
190 .collect();
191 let request = Request {
192 method: &info.method,
193 path_and_query: &info.path_and_query,
194 headers: &headers,
195 body,
196 };
197 let Ok(signature) = verification::parse(&request) else {
198 return Ok(None);
199 };
200 let Some(host) = Url::parse(&signature.key_id)
201 .ok()
202 .and_then(|key| key.host_str().map(str::to_owned))
203 else {
204 return Ok(None);
205 };
206 Ok(blocked(context.clone(), host)
207 .await?
208 .then_some(signature.key_id))
209}
210
211async fn authenticate_signed<D: Clone + Send + Sync + 'static>(
212 context: &Context<D>,
213 body: &[u8],
214) -> Result<Url, Refusal> {
215 let settings = context
216 .inner
217 .federation
218 .signed_fetch
219 .as_ref()
220 .ok_or("signed fetches are not configured")?;
221 let info = context.inner.request.as_ref().ok_or("no request")?;
222 let headers: Vec<(&str, &str)> = info
223 .headers
224 .iter()
225 .map(|(name, value)| (name.as_str(), value.as_str()))
226 .collect();
227 let request = Request {
228 method: &info.method,
229 path_and_query: &info.path_and_query,
230 headers: &headers,
231 body,
232 };
233 let signature = verification::parse(&request).map_err(|error| error.to_string())?;
234 if let (Some(blocked), Some(host)) = (
237 &context.inner.federation.blocked,
238 Url::parse(&signature.key_id)
239 .ok()
240 .and_then(|key| key.host_str().map(str::to_owned)),
241 ) {
242 match blocked(context.clone(), host.clone()).await {
243 Ok(true) => return Err(Refusal::Blocked(host)),
244 Ok(false) => {}
245 Err(error) => {
246 context.report(&error);
247 return Err(format!("could not tell whether {host} is blocked").into());
248 }
249 }
250 }
251 let canonical = authority(context.origin());
252 let hosts = [info.host.as_str(), canonical.as_str()];
253 verification::check(
254 &signature,
255 &request,
256 &Policy::new(&hosts),
257 chrono::Utc::now().timestamp(),
258 )
259 .map_err(|error| error.to_string())?;
260
261 find_key(context, settings, &signature.key_id, |key| {
262 verification::verify(&signature, &request, key.as_key()).is_ok()
263 })
264 .await
265 .map_err(Refusal::from)
266}
267
268async fn find_key<D: Clone + Send + Sync + 'static>(
272 context: &Context<D>,
273 settings: &SignedFetch<D>,
274 key_id: &str,
275 check: impl Fn(&PublishedKey) -> bool,
276) -> Result<Url, String> {
277 if let Some(known) = &settings.known_key {
280 match known(context.clone(), key_id.to_owned()).await {
281 Ok(Some(known)) => {
282 if check(&PublishedKey::RsaPem(known.pem)) {
283 return Ok(known.actor);
284 }
285 }
286 Ok(None) => {}
287 Err(error) => context.report(&error),
288 }
289 }
290
291 let kv = settings.kv(context.data());
292 let cache_key = ["ojak", "key", key_id];
293 match kv.get(&cache_key).await {
294 Ok(Some(cached)) => {
295 if let Some(published) = Published::from_json(&cached)
296 && check(&published.key)
297 {
298 return Ok(published.actor);
299 }
300 }
301 Ok(None) => {}
302 Err(error) => context.report(&Error::from(error)),
303 }
304
305 let published = fetch_key(context, settings, key_id)
306 .await
307 .ok_or_else(|| format!("no key {key_id} published by its actor"))?;
308 let verified = check(&published.key);
309 let kept_by_application = verified
316 && settings.known_key.is_some()
317 && settings.key_fetched.is_some()
318 && published.document.is_some();
319 if !kept_by_application
320 && let Err(error) = kv
321 .set(&cache_key, published.to_json(), Some(settings.key_ttl))
322 .await
323 {
324 context.report(&Error::from(error));
325 }
326 if verified {
327 if let (Some(hook), Some(document)) = (&settings.key_fetched, published.document) {
331 hook(context.clone(), document).await;
332 }
333 Ok(published.actor)
334 } else {
335 Err("the signature does not verify".into())
336 }
337}
338
339pub(super) async fn verify_linked_data<D: Clone + Send + Sync + 'static>(
348 context: &Context<D>,
349 document: &Value,
350 actor: &str,
351) -> Result<(Url, Option<ojak_jsonld::Registry>), String> {
352 use crate::sig::linked_data::Signed;
353
354 let settings = context
355 .inner
356 .federation
357 .signed_fetch
358 .as_ref()
359 .ok_or("signed fetches are not configured")?;
360 let Some(creator) = crate::sig::linked_data::creator(document) else {
361 return Err("no RsaSignature2017".into());
362 };
363 if let (Some(blocked), Some(host)) = (
366 &context.inner.federation.blocked,
367 Url::parse(creator)
368 .ok()
369 .and_then(|key| key.host_str().map(str::to_owned)),
370 ) {
371 match blocked(context.clone(), host.clone()).await {
372 Ok(true) => return Err(format!("signed with a key on {host}, which is blocked")),
373 Ok(false) => {}
374 Err(error) => {
375 context.report(&error);
376 return Err(format!("could not tell whether {host} is blocked"));
377 }
378 }
379 }
380 let registry = match &context.inner.federation.remote_contexts {
383 Some(remote) => {
384 crate::contexts::resolve(&crate::fetch::REGISTRY, document, remote.limits, |iri| {
385 (remote.load)(context.clone(), iri)
386 })
387 .await
388 .map_err(|error| error.to_string())?
389 }
390 None => None,
391 };
392 let signed = Signed::read(
393 registry.as_ref().unwrap_or(&crate::fetch::REGISTRY),
394 document,
395 chrono::Utc::now().timestamp(),
396 )
397 .map_err(|error| error.to_string())?;
398 let signer = find_key(context, settings, signed.creator(), |key| match key {
399 PublishedKey::RsaPem(pem) => signed.verify(pem).is_ok(),
400 PublishedKey::Ed25519(_) => false,
401 })
402 .await?;
403 if signer.as_str() != actor {
404 return Err(format!("signed by {signer}, not {actor}"));
405 }
406 Ok((signer, registry))
407}
408
409pub(super) async fn prove<D: Clone + Send + Sync + 'static>(
413 context: &Context<D>,
414 document: &Value,
415 actor: &str,
416) -> Result<Url, String> {
417 use crate::sig::integrity;
418
419 let settings = context
420 .inner
421 .federation
422 .signed_fetch
423 .as_ref()
424 .ok_or("signed fetches are not configured")?;
425 let proofs: Vec<_> = integrity::integrity_proofs(document, chrono::Utc::now().timestamp())
429 .into_iter()
430 .filter(|(_, _, method)| crate::origin::same_origin(method, actor))
431 .collect();
432 if proofs.is_empty() {
433 return Err(format!(
434 "no usable integrity proof by a key on {actor}'s origin"
435 ));
436 }
437 let actor_url = Url::parse(actor).map_err(|error| error.to_string())?;
438 let key = match (settings.key)(context.clone()).await {
439 Ok(key) => key,
440 Err(error) => {
441 context.report(&error);
442 None
443 }
444 };
445 let fetched = settings
446 .fetcher(context.data())
447 .document(&actor_url, key.as_ref())
448 .await
449 .map_err(|error| error.to_string())?;
450 let mut why = String::new();
451 for (proof, _, method) in proofs {
452 let Some(multibase) = assertion_method(&fetched.json, &method) else {
453 why = format!("{actor} does not list {method} as an assertionMethod");
454 continue;
455 };
456 let verified = integrity::decode_multikey(&multibase).and_then(|public_key| {
457 integrity::verify_object_integrity_proof(document, &proof, &public_key)
458 });
459 match verified {
460 Ok(()) => return Url::parse(&fetched.id).map_err(|error| error.to_string()),
461 Err(error) => why = error.to_string(),
462 }
463 }
464 Err(why)
465}
466
467pub(super) async fn establish<D: Clone + Send + Sync + 'static>(
472 context: &Context<D>,
473 document: &Value,
474 actor: &str,
475) -> Result<(Value, Url), String> {
476 let settings = context
477 .inner
478 .federation
479 .signed_fetch
480 .as_ref()
481 .ok_or("signed fetches are not configured")?;
482 let id = document
483 .get("id")
484 .and_then(Value::as_str)
485 .ok_or("a forwarded activity with no id cannot be fetched")?;
486 let url = Url::parse(id).map_err(|error| format!("{id}: {error}"))?;
487 if !matches!(url.scheme(), "http" | "https") {
488 return Err(format!("{id} cannot be fetched from its origin"));
489 }
490 let key = match (settings.key)(context.clone()).await {
491 Ok(key) => key,
492 Err(error) => {
493 context.report(&error);
494 None
495 }
496 };
497 let fetched = settings
498 .fetcher(context.data())
499 .document(&url, key.as_ref())
500 .await
501 .map_err(|error| format!("{id}: {error}"))?;
502 if fetched.id != id {
503 return Err(format!("{id} is served as {}", fetched.id));
504 }
505 if super::inbox::actor_of(&fetched.json) != Some(actor) {
506 return Err(format!("{id} as its origin serves it is not by {actor}"));
507 }
508 let actor = Url::parse(actor).map_err(|error| error.to_string())?;
509 Ok((fetched.json, actor))
510}
511
512fn assertion_method(actor: &Value, method: &str) -> Option<String> {
514 let methods = match actor.get("assertionMethod")? {
515 Value::Array(items) => items.iter().collect(),
516 item => vec![item],
517 };
518 methods.into_iter().find_map(|item| {
519 (item.get("id").and_then(Value::as_str) == Some(method))
520 .then(|| item.get("publicKeyMultibase")?.as_str().map(str::to_owned))
521 .flatten()
522 })
523}
524
525async fn fetch_key<D: Clone + Send + Sync + 'static>(
528 context: &Context<D>,
529 settings: &SignedFetch<D>,
530 key_id: &str,
531) -> Option<Published> {
532 let owner = Url::parse(verification::key_owner(key_id)).ok()?;
533 let key = match (settings.key)(context.clone()).await {
534 Ok(key) => key,
535 Err(error) => {
536 context.report(&error);
537 None
538 }
539 };
540 let document = settings
541 .fetcher(context.data())
542 .document(&owner, key.as_ref())
543 .await
544 .ok()?;
545 let key = verification::published_key(&document.json, key_id)?;
546 Some(Published {
547 key,
548 actor: Url::parse(&document.id).ok()?,
549 document: Some(document.json),
550 })
551}