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