1use crate::{
2 capability::Capabilities,
3 crypto::did::DidParser,
4 serde::{Base64Encode, DagJson},
5 time::now,
6};
7use anyhow::{anyhow, Result};
8use base64::Engine;
9use cid::{
10 multihash::{Code, MultihashDigest},
11 Cid,
12};
13use libipld_core::{codec::Codec, raw::RawCodec};
14use serde::{Deserialize, Serialize};
15use serde_json::Value;
16use std::{collections::BTreeMap, convert::TryFrom, str::FromStr};
17
18pub const UCAN_VERSION: &str = "0.10.0-canary";
19
20pub type FactsMap = BTreeMap<String, Value>;
21
22#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
23pub struct UcanHeader {
24 pub alg: String,
25 pub typ: String,
26}
27
28#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
29pub struct UcanPayload {
30 pub ucv: String,
31 pub iss: String,
32 pub aud: String,
33 pub exp: Option<u64>,
34 #[serde(skip_serializing_if = "Option::is_none")]
35 pub nbf: Option<u64>,
36 #[serde(skip_serializing_if = "Option::is_none")]
37 pub nnc: Option<String>,
38 pub cap: Capabilities,
39 #[serde(skip_serializing_if = "Option::is_none")]
40 pub fct: Option<FactsMap>,
41 #[serde(skip_serializing_if = "Option::is_none")]
42 pub prf: Option<Vec<String>>,
43}
44
45#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
46pub struct Ucan {
47 header: UcanHeader,
48 payload: UcanPayload,
49 signed_data: Vec<u8>,
50 signature: Vec<u8>,
51}
52
53impl Ucan {
54 pub fn new(
55 header: UcanHeader,
56 payload: UcanPayload,
57 signed_data: Vec<u8>,
58 signature: Vec<u8>,
59 ) -> Self {
60 Ucan {
61 signed_data,
62 header,
63 payload,
64 signature,
65 }
66 }
67
68 pub async fn validate<'a>(
70 &self,
71 now_time: Option<u64>,
72 did_parser: &mut DidParser,
73 ) -> Result<()> {
74 if self.is_expired(now_time) {
75 return Err(anyhow!("Expired"));
76 }
77
78 if self.is_too_early() {
79 return Err(anyhow!("Not active yet (too early)"));
80 }
81
82 self.check_signature(did_parser).await
83 }
84
85 pub async fn check_signature<'a>(&self, did_parser: &mut DidParser) -> Result<()> {
87 let key = did_parser.parse(&self.payload.iss)?;
88 key.verify(&self.signed_data, &self.signature).await
89 }
90
91 pub fn encode(&self) -> Result<String> {
94 let header = self.header.jwt_base64_encode()?;
95 let payload = self.payload.jwt_base64_encode()?;
96 let signature =
97 base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(self.signature.as_slice());
98
99 Ok(format!("{header}.{payload}.{signature}"))
100 }
101
102 pub fn is_expired(&self, now_time: Option<u64>) -> bool {
104 if let Some(exp) = self.payload.exp {
105 exp < now_time.unwrap_or_else(now)
106 } else {
107 false
108 }
109 }
110
111 pub fn signed_data(&self) -> &[u8] {
113 &self.signed_data
114 }
115
116 pub fn signature(&self) -> &[u8] {
117 &self.signature
118 }
119
120 pub fn is_too_early(&self) -> bool {
122 match self.payload.nbf {
123 Some(nbf) => nbf > now(),
124 None => false,
125 }
126 }
127
128 pub fn lifetime_begins_before(&self, other: &Ucan) -> bool {
133 match (self.payload.nbf, other.payload.nbf) {
134 (Some(nbf), Some(other_nbf)) => nbf <= other_nbf,
135 (Some(_), None) => false,
136 _ => true,
137 }
138 }
139
140 pub fn lifetime_ends_after(&self, other: &Ucan) -> bool {
142 match (self.payload.exp, other.payload.exp) {
143 (Some(exp), Some(other_exp)) => exp >= other_exp,
144 (Some(_), None) => false,
145 (None, _) => true,
146 }
147 }
148
149 pub fn lifetime_encompasses(&self, other: &Ucan) -> bool {
151 self.lifetime_begins_before(other) && self.lifetime_ends_after(other)
152 }
153
154 pub fn algorithm(&self) -> &str {
155 &self.header.alg
156 }
157
158 pub fn issuer(&self) -> &str {
159 &self.payload.iss
160 }
161
162 pub fn audience(&self) -> &str {
163 &self.payload.aud
164 }
165
166 pub fn proofs(&self) -> &Option<Vec<String>> {
167 &self.payload.prf
168 }
169
170 pub fn expires_at(&self) -> &Option<u64> {
171 &self.payload.exp
172 }
173
174 pub fn not_before(&self) -> &Option<u64> {
175 &self.payload.nbf
176 }
177
178 pub fn nonce(&self) -> &Option<String> {
179 &self.payload.nnc
180 }
181
182 #[deprecated(since = "0.4.0", note = "use `capabilities()`")]
183 pub fn attenuation(&self) -> &Capabilities {
184 self.capabilities()
185 }
186
187 pub fn capabilities(&self) -> &Capabilities {
188 &self.payload.cap
189 }
190
191 pub fn facts(&self) -> &Option<FactsMap> {
192 &self.payload.fct
193 }
194
195 pub fn version(&self) -> &str {
196 &self.payload.ucv
197 }
198
199 pub fn to_cid(&self, hasher: Code) -> Result<Cid> {
200 let codec = RawCodec;
201 let token = self.encode()?;
202 let encoded = codec.encode(token.as_bytes())?;
203 Ok(Cid::new_v1(codec.into(), hasher.digest(&encoded)))
204 }
205}
206
207impl<'a> TryFrom<&'a str> for Ucan {
209 type Error = anyhow::Error;
210
211 fn try_from(ucan_token: &str) -> Result<Self, Self::Error> {
212 Ucan::from_str(ucan_token)
213 }
214}
215
216impl TryFrom<String> for Ucan {
218 type Error = anyhow::Error;
219
220 fn try_from(ucan_token: String) -> Result<Self, Self::Error> {
221 Ucan::from_str(ucan_token.as_str())
222 }
223}
224
225impl FromStr for Ucan {
227 type Err = anyhow::Error;
228
229 fn from_str(ucan_token: &str) -> Result<Self, Self::Err> {
230 let signed_data = ucan_token
232 .split('.')
233 .take(2)
234 .map(String::from)
235 .reduce(|l, r| format!("{l}.{r}"))
236 .ok_or_else(|| anyhow!("Could not parse signed data from token string"))?;
237
238 let mut parts = ucan_token.split('.').map(|str| {
239 base64::engine::general_purpose::URL_SAFE_NO_PAD
240 .decode(str)
241 .map_err(|error| anyhow!(error))
242 });
243
244 let header = parts
245 .next()
246 .ok_or_else(|| anyhow!("Missing UCAN header in token part"))?
247 .map(|decoded| UcanHeader::from_dag_json(&decoded))
248 .map_err(|e| e.context("Could not decode UCAN header base64"))?
249 .map_err(|e| e.context("Could not parse UCAN header JSON"))?;
250
251 let payload = parts
252 .next()
253 .ok_or_else(|| anyhow!("Missing UCAN payload in token part"))?
254 .map(|decoded| UcanPayload::from_dag_json(&decoded))
255 .map_err(|e| e.context("Could not decode UCAN payload base64"))?
256 .map_err(|e| e.context("Could not parse UCAN payload JSON"))?;
257
258 let signature = parts
259 .next()
260 .ok_or_else(|| anyhow!("Missing UCAN signature in token part"))?
261 .map_err(|e| e.context("Could not parse UCAN signature base64"))?;
262
263 Ok(Ucan::new(
264 header,
265 payload,
266 signed_data.as_bytes().into(),
267 signature,
268 ))
269 }
270}