1mod hashlink;
12mod uri;
13
14pub use hashlink::{Hashlink, Media};
15pub use uri::*;
16
17use crate::sig::did::{did_key, did_key_method, resolve_did_key};
18use crate::sig::integrity::{self, PublicKey};
19use serde_json::Value;
20use std::fmt;
21use std::future::Future;
22use std::pin::Pin;
23use std::sync::Arc;
24
25pub const MAX_GATEWAYS: usize = 5;
30
31pub type Error = Box<dyn std::error::Error + Send + Sync>;
33
34type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
35
36pub trait DidResolver: Send + Sync + 'static {
38 fn resolve<'a>(&'a self, did_url: &'a str) -> BoxFuture<'a, Result<Option<PublicKey>, Error>>;
41}
42
43#[derive(Debug)]
45pub enum PortableError {
46 NotPortable,
48 NoProof,
50 ForeignMethod { method: String, did: String },
52 Unresolved { method: String, why: String },
54 Invalid(String),
56 NoGateways,
58 InvalidGateway(String),
61}
62
63impl fmt::Display for PortableError {
64 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
65 match self {
66 Self::NotPortable => f.write_str("not a portable object"),
67 Self::NoProof => f.write_str("no usable integrity proof"),
68 Self::ForeignMethod { method, did } => {
69 write!(f, "proof key {method} is not under {did}")
70 }
71 Self::Unresolved { method, why } => write!(f, "{method} did not resolve: {why}"),
72 Self::Invalid(why) => write!(f, "proof does not verify: {why}"),
73 Self::NoGateways => f.write_str("portable actor lists no gateways"),
74 Self::InvalidGateway(gateway) => {
75 write!(f, "portable actor lists {gateway}, which is not an origin")
76 }
77 }
78 }
79}
80
81impl std::error::Error for PortableError {}
82
83pub async fn resolve(
89 did_url: &str,
90 resolver: Option<&dyn DidResolver>,
91) -> Result<PublicKey, PortableError> {
92 let unresolved = |why: String| PortableError::Unresolved {
93 method: did_url.to_owned(),
94 why,
95 };
96 if did_url.starts_with("did:key:") {
97 return resolve_did_key(did_url).map_err(|error| unresolved(error.to_string()));
98 }
99 let resolver = resolver.ok_or_else(|| unresolved("no resolver for its method".into()))?;
100 match resolver.resolve(did_url).await {
101 Ok(Some(key)) => Ok(key),
102 Ok(None) => Err(unresolved("unknown to the resolver".into())),
103 Err(error) => Err(unresolved(error.to_string())),
104 }
105}
106
107pub async fn verify_by(
114 document: &Value,
115 did: &str,
116 resolver: Option<&dyn DidResolver>,
117) -> Result<(), PortableError> {
118 let mut last = PortableError::NoProof;
119 for (proof, _, method) in integrity::integrity_proofs(document, chrono::Utc::now().timestamp())
120 {
121 if did_of(&method) != Some(did) {
122 last = PortableError::ForeignMethod {
123 method,
124 did: did.to_owned(),
125 };
126 continue;
127 }
128 let key = match resolve(&method, resolver).await {
129 Ok(key) => key,
130 Err(error) => {
131 last = error;
132 continue;
133 }
134 };
135 match integrity::verify_object_integrity_proof(document, &proof, &key) {
136 Ok(()) => return Ok(()),
137 Err(error) => last = PortableError::Invalid(error.to_string()),
138 }
139 }
140 Err(last)
141}
142
143pub async fn verify(
151 document: &Value,
152 resolver: Option<&dyn DidResolver>,
153) -> Result<ApUri, PortableError> {
154 let id = document
155 .get("id")
156 .and_then(Value::as_str)
157 .and_then(ApUri::parse)
158 .ok_or(PortableError::NotPortable)?;
159 if is_actor(document) {
160 let listed = listed_gateways(document);
164 if let Some(invalid) = listed
165 .iter()
166 .find(|gateway| gateway.as_str().is_none_or(|gateway| !is_gateway(gateway)))
167 {
168 return Err(PortableError::InvalidGateway(match invalid {
169 Value::String(gateway) => gateway.clone(),
170 other => other.to_string(),
171 }));
172 }
173 if listed.is_empty() {
174 return Err(PortableError::NoGateways);
175 }
176 }
177 verify_by(document, id.did(), resolver).await?;
178 Ok(id)
179}
180
181#[must_use]
189pub fn gateways(actor: &Value) -> Vec<String> {
190 let mut gateways: Vec<String> = Vec::new();
191 for gateway in listed_gateways(actor).into_iter().filter_map(Value::as_str) {
192 let gateway = gateway.strip_suffix('/').unwrap_or(gateway);
193 if is_gateway(gateway) && !gateways.iter().any(|seen| seen == gateway) {
194 gateways.push(gateway.to_owned());
195 }
196 }
197 gateways
198}
199
200fn listed_gateways(actor: &Value) -> Vec<&Value> {
201 match actor.get("gateways") {
202 Some(Value::Array(items)) => items.iter().collect(),
203 Some(Value::Object(list)) => match list.get("@list") {
204 Some(Value::Array(items)) => items.iter().collect(),
205 _ => Vec::new(),
206 },
207 Some(item) => vec![item],
208 None => Vec::new(),
209 }
210}
211
212fn is_actor(document: &Value) -> bool {
213 const ACTORS: [&str; 5] = ["Application", "Group", "Organization", "Person", "Service"];
214 has_type(document, &ACTORS)
215}
216
217pub(crate) fn has_type(document: &Value, types: &[&str]) -> bool {
219 match document.get("type") {
220 Some(Value::String(kind)) => types.contains(&kind.as_str()),
221 Some(Value::Array(kinds)) => kinds
222 .iter()
223 .filter_map(Value::as_str)
224 .any(|kind| types.contains(&kind)),
225 _ => false,
226 }
227}
228
229pub trait ProofSigner: Send + Sync + 'static {
233 fn verification_method(&self) -> &str;
236
237 fn prove<'a>(&'a self, document: &'a Value) -> BoxFuture<'a, Result<Value, Error>>;
239}
240
241impl<S: ProofSigner + ?Sized> ProofSigner for Arc<S> {
242 fn verification_method(&self) -> &str {
243 (**self).verification_method()
244 }
245
246 fn prove<'a>(&'a self, document: &'a Value) -> BoxFuture<'a, Result<Value, Error>> {
247 (**self).prove(document)
248 }
249}
250
251#[derive(Clone)]
253pub struct Ed25519Signer {
254 seed: [u8; 32],
255 did: String,
256 method: String,
257}
258
259impl fmt::Debug for Ed25519Signer {
260 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
261 f.debug_struct("Ed25519Signer")
262 .field("did", &self.did)
263 .finish_non_exhaustive()
264 }
265}
266
267impl Ed25519Signer {
268 #[must_use]
270 pub fn new(seed: [u8; 32]) -> Self {
271 let did = did_key(&integrity::ed25519_public_key(&seed));
272 let method = did_key_method(&did);
273 Self { seed, did, method }
274 }
275
276 #[must_use]
278 pub fn generate() -> Self {
279 Self::new(integrity::generate_ed25519_seed(&mut rand_core::UnwrapErr(
280 getrandom::SysRng,
281 )))
282 }
283
284 pub fn from_pem(pem: &str) -> Result<Self, Error> {
290 let (seed, _) = integrity::parse_ed25519_key(pem).map_err(|error| error.to_string())?;
291 Ok(Self::new(seed))
292 }
293
294 #[must_use]
296 pub fn did(&self) -> &str {
297 &self.did
298 }
299}
300
301impl ProofSigner for Ed25519Signer {
302 fn verification_method(&self) -> &str {
303 &self.method
304 }
305
306 fn prove<'a>(&'a self, document: &'a Value) -> BoxFuture<'a, Result<Value, Error>> {
307 Box::pin(async move {
308 integrity::sign_object_integrity_proof(
309 document,
310 &self.method,
311 &self.seed,
312 chrono::Utc::now().timestamp(),
313 )
314 .map_err(|error| error.to_string().into())
315 })
316 }
317}
318
319#[cfg(test)]
320mod tests {
321 use super::*;
322 use serde_json::json;
323
324 fn actor(signer: &Ed25519Signer) -> Value {
325 json!({
326 "@context": [
327 "https://www.w3.org/ns/activitystreams",
328 "https://w3id.org/security/data-integrity/v1",
329 "https://w3id.org/fep/ef61"
330 ],
331 "type": "Person",
332 "id": format!("ap://{}/actor", signer.did()),
333 "inbox": format!("ap://{}/actor/inbox", signer.did()),
334 "gateways": ["https://server1.example"]
335 })
336 }
337
338 #[tokio::test]
339 async fn what_its_key_signed_is_authentic() {
340 let signer = Ed25519Signer::generate();
341 let signed = signer.prove(&actor(&signer)).await.unwrap();
342 let id = verify(&signed, None).await.unwrap();
343 assert_eq!(id.did(), signer.did());
344 assert_eq!(gateways(&signed), ["https://server1.example"]);
345 }
346
347 #[tokio::test]
348 async fn a_proof_that_verifies_is_found_among_ones_that_do_not() {
349 let owner = Ed25519Signer::generate();
350 let other = Ed25519Signer::generate();
351 let unsigned = actor(&owner);
352 let by_owner = owner.prove(&unsigned).await.unwrap();
353 let by_other = other.prove(&unsigned).await.unwrap();
354 let mut tampered = by_owner["proof"].clone();
355 tampered["created"] = json!("2000-01-01T00:00:00Z");
356 let mut signed = unsigned;
357 signed["proof"] = json!([by_other["proof"], tampered, by_owner["proof"]]);
358 assert_eq!(
359 integrity::integrity_proofs(&signed, chrono::Utc::now().timestamp()).len(),
360 3
361 );
362
363 let id = verify(&signed, None).await.unwrap();
364 assert_eq!(id.did(), owner.did());
365
366 signed["proof"] = json!([by_other["proof"], tampered]);
367 assert!(matches!(
368 verify(&signed, None).await,
369 Err(PortableError::Invalid(_))
370 ));
371 }
372
373 #[tokio::test]
374 async fn another_key_does_not_vouch_for_it() {
375 let owner = Ed25519Signer::generate();
376 let other = Ed25519Signer::generate();
377 let signed = other.prove(&actor(&owner)).await.unwrap();
378 assert!(matches!(
379 verify(&signed, None).await,
380 Err(PortableError::ForeignMethod { .. })
381 ));
382 }
383
384 #[tokio::test]
385 async fn an_actor_lists_only_gateways() {
386 let signer = Ed25519Signer::generate();
387 let mut unsigned = actor(&signer);
388 unsigned["gateways"] = json!(["https://server1.example/", "https://server2.example"]);
389 let signed = signer.prove(&unsigned).await.unwrap();
390 verify(&signed, None).await.unwrap();
391 assert_eq!(
392 gateways(&signed),
393 ["https://server1.example", "https://server2.example"]
394 );
395
396 for listed in [
397 json!(["https://server1.example", "https://server2.example/path"]),
398 json!(["https://server1.example", 1]),
399 json!("ap://did:key:z6Mk/actor"),
400 ] {
401 unsigned["gateways"] = listed;
402 let signed = signer.prove(&unsigned).await.unwrap();
403 assert!(matches!(
404 verify(&signed, None).await,
405 Err(PortableError::InvalidGateway(_))
406 ));
407 }
408 unsigned["gateways"] = json!([]);
409 let signed = signer.prove(&unsigned).await.unwrap();
410 assert!(matches!(
411 verify(&signed, None).await,
412 Err(PortableError::NoGateways)
413 ));
414 }
415
416 #[tokio::test]
417 async fn a_changed_document_does_not_verify() {
418 let signer = Ed25519Signer::generate();
419 let mut signed = signer.prove(&actor(&signer)).await.unwrap();
420 signed["gateways"] = json!(["https://attacker.example"]);
421 assert!(matches!(
422 verify(&signed, None).await,
423 Err(PortableError::Invalid(_))
424 ));
425 }
426
427 #[tokio::test]
428 async fn an_actor_needs_a_gateway_and_a_proof() {
429 let signer = Ed25519Signer::generate();
430 let mut unsigned = actor(&signer);
431 assert!(matches!(
432 verify(&unsigned, None).await,
433 Err(PortableError::NoProof)
434 ));
435 unsigned.as_object_mut().unwrap().remove("gateways");
436 let signed = signer.prove(&unsigned).await.unwrap();
437 assert!(matches!(
438 verify(&signed, None).await,
439 Err(PortableError::NoGateways)
440 ));
441 let https = json!({"id": "https://a.example/users/alice", "type": "Person"});
442 assert!(matches!(
443 verify(&https, None).await,
444 Err(PortableError::NotPortable)
445 ));
446 }
447
448 #[tokio::test]
449 async fn other_methods_need_a_resolver() {
450 struct Web(PublicKeyBytes);
451 struct PublicKeyBytes([u8; 32]);
452 impl DidResolver for Web {
453 fn resolve<'a>(
454 &'a self,
455 did_url: &'a str,
456 ) -> BoxFuture<'a, Result<Option<PublicKey>, Error>> {
457 let key = (did_url == "did:web:a.example#key")
458 .then(|| PublicKey::Ed25519(Box::new(self.0.0)));
459 Box::pin(async move { Ok(key) })
460 }
461 }
462
463 let seed = [9u8; 32];
464 let public = integrity::ed25519_public_key(&seed);
465 let document = json!({
466 "@context": "https://www.w3.org/ns/activitystreams",
467 "id": "ap://did:web:a.example/objects/1",
468 "type": "Note"
469 });
470 let signed = integrity::sign_object_integrity_proof(
471 &document,
472 "did:web:a.example#key",
473 &seed,
474 chrono::Utc::now().timestamp(),
475 )
476 .unwrap();
477 assert!(matches!(
478 verify(&signed, None).await,
479 Err(PortableError::Unresolved { .. })
480 ));
481 let resolver = Web(PublicKeyBytes(public));
482 verify(&signed, Some(&resolver)).await.unwrap();
483 }
484}