noosphere_core/data/
authority.rs1use anyhow::Result;
2use cid::Cid;
3use libipld_cbor::DagCborCodec;
4use std::{hash::Hash, str::FromStr};
5use ucan::{crypto::KeyMaterial, store::UcanJwtStore, Ucan};
6
7use noosphere_storage::{base64_decode, base64_encode, BlockStore, UcanStore};
8use serde::{Deserialize, Serialize};
9
10use super::{DelegationsIpld, Link, RevocationsIpld};
11
12#[cfg(docs)]
13use crate::data::SphereIpld;
14
15#[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)]
18pub struct AuthorityIpld {
19 pub delegations: Link<DelegationsIpld>,
22 pub revocations: Link<RevocationsIpld>,
26}
27
28impl AuthorityIpld {
29 pub async fn empty<S: BlockStore>(store: &mut S) -> Result<Self> {
33 let delegations_ipld = DelegationsIpld::empty(store).await?;
34 let delegations = store
35 .save::<DagCborCodec, _>(delegations_ipld)
36 .await?
37 .into();
38 let revocations_ipld = RevocationsIpld::empty(store).await?;
39 let revocations = store
40 .save::<DagCborCodec, _>(revocations_ipld)
41 .await?
42 .into();
43
44 Ok(AuthorityIpld {
45 delegations,
46 revocations,
47 })
48 }
49}
50
51#[cfg(doc)]
52use crate::data::Jwt;
53
54#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Clone, Serialize, Deserialize, Hash)]
59pub struct DelegationIpld {
60 pub name: String,
62 pub jwt: Cid,
64}
65
66impl DelegationIpld {
67 pub async fn register<S: BlockStore>(name: &str, jwt: &str, store: &S) -> Result<Self> {
70 let mut store = UcanStore(store.clone());
71 let cid = store.write_token(jwt).await?;
72
73 Ok(DelegationIpld {
74 name: name.to_string(),
75 jwt: cid,
76 })
77 }
78
79 pub async fn resolve_ucan<S: BlockStore>(&self, store: &S) -> Result<Ucan> {
82 let store = UcanStore(store.clone());
83 let jwt = store.require_token(&self.jwt).await?;
84
85 Ucan::from_str(&jwt)
86 }
87}
88
89#[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize, Hash)]
92pub struct RevocationIpld {
93 pub iss: String,
95 pub revoke: String,
98 pub challenge: String,
101}
102
103impl RevocationIpld {
104 pub async fn revoke<K: KeyMaterial>(cid: &Cid, issuer: &K) -> Result<Self> {
107 Ok(RevocationIpld {
108 iss: issuer.get_did().await?,
109 revoke: cid.to_string(),
110 challenge: base64_encode(&issuer.sign(&Self::make_challenge_payload(cid)).await?)?,
111 })
112 }
113
114 pub async fn verify<K: KeyMaterial + ?Sized>(&self, claimed_issuer: &K) -> Result<()> {
117 let cid = Cid::try_from(self.revoke.as_str())?;
118 let challenge_payload = Self::make_challenge_payload(&cid);
119 let signature = base64_decode(&self.challenge)?;
120
121 claimed_issuer
122 .verify(&challenge_payload, &signature)
123 .await?;
124
125 Ok(())
126 }
127
128 fn make_challenge_payload(cid: &Cid) -> Vec<u8> {
129 format!("REVOKE:{cid}").as_bytes().to_vec()
130 }
131}
132
133#[cfg(test)]
134mod tests {
135 use noosphere_storage::{MemoryStore, UcanStore};
136 use ucan::{builder::UcanBuilder, crypto::KeyMaterial, store::UcanJwtStore};
137
138 use crate::authority::generate_ed25519_key;
139
140 use super::{DelegationIpld, RevocationIpld};
141
142 #[cfg(target_arch = "wasm32")]
143 use wasm_bindgen_test::wasm_bindgen_test;
144
145 #[cfg(target_arch = "wasm32")]
146 wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
147
148 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
149 #[cfg_attr(not(target_arch = "wasm32"), tokio::test)]
150 async fn it_stores_a_registerd_jwt() {
151 let store = MemoryStore::default();
152 let key = generate_ed25519_key();
153
154 let ucan_jwt = UcanBuilder::default()
155 .issued_by(&key)
156 .for_audience(&key.get_did().await.unwrap())
157 .with_lifetime(128)
158 .build()
159 .unwrap()
160 .sign()
161 .await
162 .unwrap()
163 .encode()
164 .unwrap();
165
166 let delegation = DelegationIpld::register("foobar", &ucan_jwt, &store)
167 .await
168 .unwrap();
169
170 assert_eq!(
171 UcanStore(store).read_token(&delegation.jwt).await.unwrap(),
172 Some(ucan_jwt)
173 );
174 }
175
176 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
177 #[cfg_attr(not(target_arch = "wasm32"), tokio::test)]
178 async fn it_can_verify_that_a_key_issued_a_revocation() {
179 let store = MemoryStore::default();
180 let key = generate_ed25519_key();
181 let other_key = generate_ed25519_key();
182
183 let ucan_jwt = UcanBuilder::default()
184 .issued_by(&key)
185 .for_audience(&key.get_did().await.unwrap())
186 .with_lifetime(128)
187 .build()
188 .unwrap()
189 .sign()
190 .await
191 .unwrap()
192 .encode()
193 .unwrap();
194
195 let delegation = DelegationIpld::register("foobar", &ucan_jwt, &store)
196 .await
197 .unwrap();
198
199 let revocation = RevocationIpld::revoke(&delegation.jwt, &key).await.unwrap();
200
201 assert!(revocation.verify(&key).await.is_ok());
202 assert!(revocation.verify(&other_key).await.is_err());
203 }
204}