1 // Copyright 2021 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //      http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 //
15 ////////////////////////////////////////////////////////////////////////////////
16 
17 //! CBOR Web Token functionality.
18 
19 use crate::{
20     cbor::value::Value,
21     common::AsCborValue,
22     iana,
23     iana::{EnumI64, WithPrivateRange},
24     util::{cbor_type_error, ValueTryAs},
25     CoseError,
26 };
27 use alloc::{collections::BTreeSet, string::String, vec::Vec};
28 use core::convert::TryInto;
29 
30 #[cfg(test)]
31 mod tests;
32 
33 /// Number of seconds since UNIX epoch.
34 #[derive(Clone, Debug, PartialEq)]
35 pub enum Timestamp {
36     WholeSeconds(i64),
37     FractionalSeconds(f64),
38 }
39 
40 impl AsCborValue for Timestamp {
from_cbor_value(value: Value) -> Result<Self, CoseError>41     fn from_cbor_value(value: Value) -> Result<Self, CoseError> {
42         match value {
43             Value::Integer(i) => Ok(Timestamp::WholeSeconds(i.try_into()?)),
44             Value::Float(f) => Ok(Timestamp::FractionalSeconds(f)),
45             _ => cbor_type_error(&value, "int/float"),
46         }
47     }
to_cbor_value(self) -> Result<Value, CoseError>48     fn to_cbor_value(self) -> Result<Value, CoseError> {
49         Ok(match self {
50             Timestamp::WholeSeconds(t) => Value::Integer(t.into()),
51             Timestamp::FractionalSeconds(f) => Value::Float(f),
52         })
53     }
54 }
55 
56 /// Claim name.
57 pub type ClaimName = crate::RegisteredLabelWithPrivate<iana::CwtClaimName>;
58 
59 /// Structure representing a CWT Claims Set.
60 #[derive(Clone, Debug, Default, PartialEq)]
61 pub struct ClaimsSet {
62     /// Issuer
63     pub issuer: Option<String>,
64     /// Subject
65     pub subject: Option<String>,
66     /// Audience
67     pub audience: Option<String>,
68     /// Expiration Time
69     pub expiration_time: Option<Timestamp>,
70     /// Not Before
71     pub not_before: Option<Timestamp>,
72     /// Issued At
73     pub issued_at: Option<Timestamp>,
74     /// CWT ID
75     pub cwt_id: Option<Vec<u8>>,
76     /// Any additional claims.
77     pub rest: Vec<(ClaimName, Value)>,
78 }
79 
80 impl crate::CborSerializable for ClaimsSet {}
81 
82 const ISS: ClaimName = ClaimName::Assigned(iana::CwtClaimName::Iss);
83 const SUB: ClaimName = ClaimName::Assigned(iana::CwtClaimName::Sub);
84 const AUD: ClaimName = ClaimName::Assigned(iana::CwtClaimName::Aud);
85 const EXP: ClaimName = ClaimName::Assigned(iana::CwtClaimName::Exp);
86 const NBF: ClaimName = ClaimName::Assigned(iana::CwtClaimName::Nbf);
87 const IAT: ClaimName = ClaimName::Assigned(iana::CwtClaimName::Iat);
88 const CTI: ClaimName = ClaimName::Assigned(iana::CwtClaimName::Cti);
89 
90 impl AsCborValue for ClaimsSet {
from_cbor_value(value: Value) -> Result<Self, CoseError>91     fn from_cbor_value(value: Value) -> Result<Self, CoseError> {
92         let m = match value {
93             Value::Map(m) => m,
94             v => return cbor_type_error(&v, "map"),
95         };
96 
97         let mut claims = Self::default();
98         let mut seen = BTreeSet::new();
99         for (n, value) in m.into_iter() {
100             // The `ciborium` CBOR library does not police duplicate map keys, so do it here.
101             let name = ClaimName::from_cbor_value(n)?;
102             if seen.contains(&name) {
103                 return Err(CoseError::DuplicateMapKey);
104             }
105             seen.insert(name.clone());
106             match name {
107                 x if x == ISS => claims.issuer = Some(value.try_as_string()?),
108                 x if x == SUB => claims.subject = Some(value.try_as_string()?),
109                 x if x == AUD => claims.audience = Some(value.try_as_string()?),
110                 x if x == EXP => claims.expiration_time = Some(Timestamp::from_cbor_value(value)?),
111                 x if x == NBF => claims.not_before = Some(Timestamp::from_cbor_value(value)?),
112                 x if x == IAT => claims.issued_at = Some(Timestamp::from_cbor_value(value)?),
113                 x if x == CTI => claims.cwt_id = Some(value.try_as_bytes()?),
114                 name => claims.rest.push((name, value)),
115             }
116         }
117         Ok(claims)
118     }
119 
to_cbor_value(self) -> Result<Value, CoseError>120     fn to_cbor_value(self) -> Result<Value, CoseError> {
121         let mut map = Vec::new();
122         if let Some(iss) = self.issuer {
123             map.push((ISS.to_cbor_value()?, Value::Text(iss)));
124         }
125         if let Some(sub) = self.subject {
126             map.push((SUB.to_cbor_value()?, Value::Text(sub)));
127         }
128         if let Some(aud) = self.audience {
129             map.push((AUD.to_cbor_value()?, Value::Text(aud)));
130         }
131         if let Some(exp) = self.expiration_time {
132             map.push((EXP.to_cbor_value()?, exp.to_cbor_value()?));
133         }
134         if let Some(nbf) = self.not_before {
135             map.push((NBF.to_cbor_value()?, nbf.to_cbor_value()?));
136         }
137         if let Some(iat) = self.issued_at {
138             map.push((IAT.to_cbor_value()?, iat.to_cbor_value()?));
139         }
140         if let Some(cti) = self.cwt_id {
141             map.push((CTI.to_cbor_value()?, Value::Bytes(cti)));
142         }
143         for (label, value) in self.rest {
144             map.push((label.to_cbor_value()?, value));
145         }
146         Ok(Value::Map(map))
147     }
148 }
149 
150 /// Builder for [`ClaimsSet`] objects.
151 #[derive(Default)]
152 pub struct ClaimsSetBuilder(ClaimsSet);
153 
154 impl ClaimsSetBuilder {
155     builder! {ClaimsSet}
156     builder_set_optional! {issuer: String}
157     builder_set_optional! {subject: String}
158     builder_set_optional! {audience: String}
159     builder_set_optional! {expiration_time: Timestamp}
160     builder_set_optional! {not_before: Timestamp}
161     builder_set_optional! {issued_at: Timestamp}
162     builder_set_optional! {cwt_id: Vec<u8>}
163 
164     /// Set a claim name:value pair.
165     ///
166     /// # Panics
167     ///
168     /// This function will panic if it used to set a claim with name from the range [1, 7].
169     #[must_use]
claim(mut self, name: iana::CwtClaimName, value: Value) -> Self170     pub fn claim(mut self, name: iana::CwtClaimName, value: Value) -> Self {
171         if name.to_i64() >= iana::CwtClaimName::Iss.to_i64()
172             && name.to_i64() <= iana::CwtClaimName::Cti.to_i64()
173         {
174             panic!("claim() method used to set core claim"); // safe: invalid input
175         }
176         self.0.rest.push((ClaimName::Assigned(name), value));
177         self
178     }
179 
180     /// Set a claim name:value pair where the `name` is text.
181     #[must_use]
text_claim(mut self, name: String, value: Value) -> Self182     pub fn text_claim(mut self, name: String, value: Value) -> Self {
183         self.0.rest.push((ClaimName::Text(name), value));
184         self
185     }
186 
187     /// Set a claim  where the claim key is a numeric value from the private use range.
188     ///
189     /// # Panics
190     ///
191     /// This function will panic if it is used to set a claim with a key value outside of the
192     /// private use range.
193     #[must_use]
private_claim(mut self, id: i64, value: Value) -> Self194     pub fn private_claim(mut self, id: i64, value: Value) -> Self {
195         assert!(iana::CwtClaimName::is_private(id));
196         self.0.rest.push((ClaimName::PrivateUse(id), value));
197         self
198     }
199 }
200