1use super::*;
2
3pub type BufferCred = EdhocBuffer<192>; pub type BufferKid = EdhocBuffer<16>; pub type BufferIdCred = EdhocBuffer<192>; pub type BytesKeyAES128 = [u8; 16];
7pub type BytesKeyEC2 = [u8; 32];
8
9#[derive(Clone, Copy, Debug, PartialEq)]
10#[repr(C)]
11pub enum CredentialKey {
12 Symmetric(BytesKeyAES128),
13 EC2Compact(BytesKeyEC2),
14 }
16
17#[derive(Clone, Copy, Debug, PartialEq)]
18#[repr(C)]
19pub enum CredentialType {
20 CCS,
21 #[allow(non_camel_case_types)]
22 CCS_PSK,
23 }
25
26#[derive(Clone, Copy, Debug, PartialEq)]
27pub enum IdCredType {
28 KID = 4,
29 KCCS = 14,
30}
31
32impl From<u8> for IdCredType {
33 fn from(value: u8) -> Self {
34 match value {
35 4 => IdCredType::KID,
36 14 => IdCredType::KCCS,
37 _ => panic!("Invalid IdCredType"),
38 }
39 }
40}
41
42#[derive(Clone, Copy, Debug, Default, PartialEq)]
55#[repr(C)]
56pub struct IdCred {
57 pub bytes: BufferIdCred, }
62
63impl IdCred {
64 pub fn new() -> Self {
65 Self {
66 bytes: BufferIdCred::new(),
67 }
68 }
69
70 pub fn from_full_value(value: &[u8]) -> Result<Self, EDHOCError> {
71 Ok(Self {
72 bytes: BufferIdCred::new_from_slice(value)
73 .map_err(|_| EDHOCError::CredentialTooLongError)?,
74 })
75 }
76
77 pub fn from_encoded_value(value: &[u8]) -> Result<Self, EDHOCError> {
79 let bytes = match value {
80 &[x] if Self::bstr_representable_as_int(x) => {
82 BufferIdCred::new_from_slice(&[0xa1, KID_LABEL, 0x41, x])
83 .map_err(|_| EDHOCError::CredentialTooLongError)? }
85 &[0x40..=0x57, ..] => {
88 let tail = &value[1..];
89 if let &[single_byte] = tail {
90 if Self::bstr_representable_as_int(single_byte) {
91 return Err(EDHOCError::ParsingError);
93 }
94 }
95 if usize::from(value[0] - 0x40) != tail.len() {
96 return Err(EDHOCError::ParsingError);
99 }
100 let mut bytes = BufferIdCred::new_from_slice(&[0xa1, KID_LABEL])
101 .map_err(|_| EDHOCError::CredentialTooLongError)?;
102 bytes
103 .extend_from_slice(value)
104 .map_err(|_| EDHOCError::CredentialTooLongError)?;
105 bytes
106 }
107 &[0xa1, KCCS_LABEL, ..] => BufferIdCred::new_from_slice(value)
109 .map_err(|_| EDHOCError::CredentialTooLongError)?,
110 _ => return Err(EDHOCError::ParsingError),
111 };
112
113 Ok(Self { bytes })
114 }
115
116 pub fn as_full_value(&self) -> &[u8] {
120 self.bytes.as_slice()
121 }
122
123 pub fn as_encoded_value(&self) -> &[u8] {
129 match self.bytes.as_slice() {
130 [0xa1, KID_LABEL, 0x41, x] if (x >> 5) < 2 && (x & 0x1f) < 24 => {
131 &self.bytes.as_slice()[3..]
132 }
133 [0xa1, KID_LABEL, ..] => &self.bytes.as_slice()[2..],
134 _ => self.bytes.as_slice(),
135 }
136 }
137
138 pub fn reference_only(&self) -> bool {
139 [IdCredType::KID].contains(&self.item_type())
140 }
141
142 pub fn item_type(&self) -> IdCredType {
143 self.bytes.as_slice()[1].into()
144 }
145
146 pub fn get_ccs(&self) -> Option<Credential> {
147 if self.item_type() == IdCredType::KCCS {
148 Credential::parse_ccs(&self.bytes.as_slice()[2..]).ok()
149 } else {
150 None
151 }
152 }
153
154 fn bstr_representable_as_int(value: u8) -> bool {
155 (0x0..=0x17).contains(&value) || (0x20..=0x37).contains(&value)
156 }
157}
158
159#[cfg_attr(feature = "python-bindings", pyclass)]
165#[derive(Clone, Copy, Debug, PartialEq)]
166#[repr(C)]
167pub struct Credential {
168 pub bytes: BufferCred,
173 pub key: CredentialKey,
174 pub kid: Option<BufferKid>, pub cred_type: CredentialType,
176}
177
178impl Credential {
179 pub fn new_ccs(bytes: BufferCred, public_key: BytesKeyEC2) -> Self {
181 Self {
182 bytes,
183 key: CredentialKey::EC2Compact(public_key),
184 kid: None,
185 cred_type: CredentialType::CCS,
186 }
187 }
188
189 pub fn new_ccs_symmetric(bytes: BufferCred, symmetric_key: BytesKeyAES128) -> Self {
193 Self {
194 bytes,
195 key: CredentialKey::Symmetric(symmetric_key),
196 kid: None,
197 cred_type: CredentialType::CCS_PSK,
198 }
199 }
200
201 pub fn with_kid(self, kid: BufferKid) -> Self {
202 Self {
203 kid: Some(kid),
204 ..self
205 }
206 }
207
208 pub fn public_key(&self) -> Option<BytesKeyEC2> {
209 match self.key {
210 CredentialKey::EC2Compact(key) => Some(key),
211 _ => None,
212 }
213 }
214
215 pub fn parse_ccs(value: &[u8]) -> Result<Self, EDHOCError> {
220 let mut decoder = CBORDecoder::new(value);
221 let mut x_kid = None;
222 for _ in 0..decoder.map()? {
223 match decoder.u8()? {
224 2 => {
226 let _subject = decoder.str()?;
227 }
228 8 => {
230 if decoder.map()? != 1 {
231 return Err(EDHOCError::ParsingError);
233 }
234
235 if decoder.u8()? != 1 {
236 return Err(EDHOCError::ParsingError);
238 }
239
240 x_kid = Some(Self::parse_cosekey(&mut decoder)?);
241 }
242 _ => {
243 return Err(EDHOCError::ParsingError);
244 }
245 }
246 }
247
248 let Some((x, kid)) = x_kid else {
249 return Err(EDHOCError::ParsingError);
251 };
252
253 if !decoder.finished() {
254 return Err(EDHOCError::ParsingError);
255 }
256
257 Ok(Self {
258 bytes: BufferCred::new_from_slice(value).map_err(|_| EDHOCError::ParsingError)?,
259 key: x,
260 kid,
261 cred_type: CredentialType::CCS,
262 })
263 }
264
265 pub fn parse_ccs_symmetric(value: &[u8]) -> Result<Self, EDHOCError> {
269 const CCS_PREFIX_LEN: usize = 3;
270 const CNF_AND_COSE_KEY_PREFIX_LEN: usize = 8;
271 const COSE_KEY_FIRST_ITEMS_LEN: usize = 3; const SYMMETRIC_KEY_LEN: usize = 16; if value.len()
275 < CCS_PREFIX_LEN
276 + 1
277 + CNF_AND_COSE_KEY_PREFIX_LEN
278 + COSE_KEY_FIRST_ITEMS_LEN
279 + SYMMETRIC_KEY_LEN
280 {
281 Err(EDHOCError::ParsingError)
282 } else {
283 let subject_len = CBORDecoder::info_of(value[2]) as usize;
284
285 let id_cred_offset: usize = CCS_PREFIX_LEN
286 .checked_add(subject_len)
287 .and_then(|x| x.checked_add(CNF_AND_COSE_KEY_PREFIX_LEN))
288 .ok_or(EDHOCError::ParsingError)?;
289
290 let symmetric_key_offset: usize = id_cred_offset
291 .checked_add(COSE_KEY_FIRST_ITEMS_LEN)
292 .ok_or(EDHOCError::ParsingError)?;
293
294 if symmetric_key_offset
295 .checked_add(SYMMETRIC_KEY_LEN)
296 .map_or(false, |end| end <= value.len())
297 {
298 let symmetric_key: [u8; SYMMETRIC_KEY_LEN] = value
299 [symmetric_key_offset..symmetric_key_offset + SYMMETRIC_KEY_LEN]
300 .try_into()
301 .map_err(|_| EDHOCError::ParsingError)?;
302
303 let kid = value[id_cred_offset];
304
305 Ok(Self {
306 bytes: BufferCred::new_from_slice(value)
307 .map_err(|_| EDHOCError::ParsingError)?,
308 key: CredentialKey::Symmetric(symmetric_key),
309 kid: Some(BufferKid::new_from_slice(&[kid]).unwrap()),
310 cred_type: CredentialType::CCS_PSK,
311 })
312 } else {
313 Err(EDHOCError::ParsingError)
314 }
315 }
316 }
317
318 fn parse_cosekey<'data>(
327 decoder: &mut CBORDecoder<'data>,
328 ) -> Result<(CredentialKey, Option<BufferKid>), EDHOCError> {
329 let items = decoder.map()?;
330 let mut x = None;
331 let mut kid = None;
332 for _ in 0..items {
333 match decoder.i8()? {
334 1 => {
336 if decoder.u8()? != 2 {
337 return Err(EDHOCError::ParsingError);
338 }
339 }
340 2 => {
343 kid = Some(
344 BufferKid::new_from_slice(decoder.bytes()?)
345 .map_err(|_| EDHOCError::ParsingError)?,
347 );
348 }
349 -1 => {
351 if decoder.u8()? != 1 {
352 return Err(EDHOCError::ParsingError);
353 }
354 }
355 -2 => {
357 x = Some(CredentialKey::EC2Compact(
358 decoder
359 .bytes()?
360 .try_into()
362 .map_err(|_| EDHOCError::ParsingError)?,
363 ));
364 }
365 -3 => {
367 let _ = decoder.bytes()?;
368 }
369 _ => {
370 return Err(EDHOCError::ParsingError);
371 }
372 }
373 }
374 Ok((x.ok_or(EDHOCError::ParsingError)?, kid))
375 }
376
377 pub fn parse_and_dress_naked_cosekey(cosekey: &[u8]) -> Result<Self, EDHOCError> {
395 let mut decoder = CBORDecoder::new(cosekey);
396 let (key, kid) = Self::parse_cosekey(&mut decoder)?;
397 if !decoder.finished() {
398 return Err(EDHOCError::ParsingError);
399 }
400 let mut bytes = BufferCred::new();
401 bytes
402 .extend_from_slice(&[0xa1, 0x08, 0xa1, 0x01])
403 .expect("Minimal size fits in the buffer");
404 bytes
405 .extend_from_slice(cosekey)
406 .map_err(|_| EDHOCError::CredentialTooLongError)?;
407 Ok(Self {
408 bytes,
409 key,
410 kid,
411 cred_type: CredentialType::CCS,
412 })
413 }
414
415 pub fn by_value(&self) -> Result<IdCred, EDHOCError> {
420 match self.cred_type {
421 CredentialType::CCS => {
422 let mut id_cred = IdCred::new();
423 id_cred
424 .bytes
425 .extend_from_slice(&[CBOR_MAJOR_MAP + 1, KCCS_LABEL])
426 .map_err(|_| EDHOCError::CredentialTooLongError)?;
427 id_cred
428 .bytes
429 .extend_from_slice(self.bytes.as_slice())
430 .unwrap();
431 Ok(id_cred)
432 }
433 CredentialType::CCS_PSK => Err(EDHOCError::UnexpectedCredential),
436 }
437 }
438
439 pub fn by_kid(&self) -> Result<IdCred, EDHOCError> {
446 let Some(kid) = self.kid.as_ref() else {
447 return Err(EDHOCError::MissingIdentity);
448 };
449 let mut id_cred = IdCred::new();
450 id_cred
451 .bytes
452 .extend_from_slice(&[
453 CBOR_MAJOR_MAP + 1,
454 KID_LABEL,
455 CBOR_MAJOR_BYTE_STRING | kid.len() as u8,
456 ])
457 .map_err(|_| EDHOCError::CredentialTooLongError)?;
458 id_cred.bytes.extend_from_slice(kid.as_slice()).unwrap();
459 Ok(id_cred)
460 }
461}
462
463#[cfg(test)]
464mod test {
465 use super::*;
466 use hexlit::hex;
467 use rstest::rstest;
468
469 const CRED_TV: &[u8] = &hex!("a2026b6578616d706c652e65647508a101a501020241322001215820bbc34960526ea4d32e940cad2a234148ddc21791a12afbcbac93622046dd44f02258204519e257236b2a0ce2023f0931f1f386ca7afda64fcde0108c224c51eabf6072");
470 const G_A_TV: &[u8] = &hex!("BBC34960526EA4D32E940CAD2A234148DDC21791A12AFBCBAC93622046DD44F0");
471 const ID_CRED_BY_REF_TV: &[u8] = &hex!("a1044132");
472 const ID_CRED_BY_VALUE_TV: &[u8] = &hex!("A10EA2026B6578616D706C652E65647508A101A501020241322001215820BBC34960526EA4D32E940CAD2A234148DDC21791A12AFBCBAC93622046DD44F02258204519E257236B2A0CE2023F0931F1F386CA7AFDA64FCDE0108C224C51EABF6072");
473 const KID_VALUE_TV: &[u8] = &hex!("32");
474
475 const CRED_PSK: &[u8] =
476 &hex!("A202686D79646F74626F7408A101A30104024132205050930FF462A77A3540CF546325DEA214");
477 const K: &[u8] = &hex!("50930FF462A77A3540CF546325DEA214");
478 const KID_VALUE_PSK: &[u8] = &hex!("32");
479
480 #[test]
481 fn test_new_cred_ccs() {
482 let cred = Credential::new_ccs(CRED_TV.try_into().unwrap(), G_A_TV.try_into().unwrap());
483 assert_eq!(cred.bytes.as_slice(), CRED_TV);
484 }
485
486 #[test]
487 fn test_cred_ccs_by_value_or_reference() {
488 let cred = Credential::new_ccs(CRED_TV.try_into().unwrap(), G_A_TV.try_into().unwrap())
489 .with_kid(KID_VALUE_TV.try_into().unwrap());
490 let id_cred = cred.by_value().unwrap();
491 assert_eq!(id_cred.bytes.as_slice(), ID_CRED_BY_VALUE_TV);
492 assert_eq!(id_cred.item_type(), IdCredType::KCCS);
493 let id_cred = cred.by_kid().unwrap();
494 assert_eq!(id_cred.bytes.as_slice(), ID_CRED_BY_REF_TV);
495 assert_eq!(id_cred.item_type(), IdCredType::KID);
496 }
497
498 #[test]
499 fn test_parse_ccs() {
500 let cred = Credential::parse_ccs(CRED_TV).unwrap();
501 assert_eq!(cred.bytes.as_slice(), CRED_TV);
502 assert_eq!(
503 cred.key,
504 CredentialKey::EC2Compact(G_A_TV.try_into().unwrap())
505 );
506 assert_eq!(cred.kid.unwrap().as_slice(), KID_VALUE_TV);
507 assert_eq!(cred.cred_type, CredentialType::CCS);
508
509 let cred_no_sub = hex!("a108a101a401022001215820f5aeba08b599754ba16f5db80feafdf91e90a5a7ccb2e83178adb51b8c68ea9522582097e7a3fdd70a3a7c0a5f9578c6e4e96d8bc55f6edd0ff64f1caeaac19d37b67d");
511 let cred_no_kid = hex!("a20263666f6f08a101a401022001215820f5aeba08b599754ba16f5db80feafdf91e90a5a7ccb2e83178adb51b8c68ea9522582097e7a3fdd70a3a7c0a5f9578c6e4e96d8bc55f6edd0ff64f1caeaac19d37b67d");
513 for cred in [cred_no_sub.as_slice(), cred_no_kid.as_slice()] {
514 let CredentialKey::EC2Compact(key) = Credential::parse_ccs(&cred).unwrap().key else {
515 panic!("CCS contains unexpected key type.");
516 };
517 assert!(key.as_slice().starts_with(&hex!("f5aeba08b59975")));
518 }
519
520 let cred_exotic = hex!("a2016008a101a401022001215820f5aeba08b599754ba16f5db80feafdf91e90a5a7ccb2e83178adb51b8c68ea9522582097e7a3fdd70a3a7c0a5f9578c6e4e96d8bc55f6edd0ff64f1caeaac19d37b67d");
525 Credential::parse_ccs(&cred_exotic).unwrap_err();
526 }
527
528 #[rstest]
529 #[case(&[0x0D], &[0xa1, 0x04, 0x41, 0x0D])] #[case(&[0x41, 0x18], &[0xa1, 0x04, 0x41, 0x18])] #[case(ID_CRED_BY_VALUE_TV, ID_CRED_BY_VALUE_TV)] fn test_id_cred_from_encoded_plaintext(#[case] input: &[u8], #[case] expected: &[u8]) {
533 assert_eq!(
534 IdCred::from_encoded_value(input).unwrap().as_full_value(),
535 expected
536 );
537 }
538}
539
540#[cfg(test)]
541mod test_experimental {
542 use super::*;
543 use hexlit::hex;
544
545 const CRED_PSK: &[u8] =
546 &hex!("A202686D79646F74626F7408A101A30104024132205050930FF462A77A3540CF546325DEA214");
547 const K: &[u8] = &hex!("50930FF462A77A3540CF546325DEA214");
548 const KID_VALUE_PSK: &[u8] = &hex!("32");
549
550 #[test]
551 fn test_cred_ccs_symmetric_by_value_or_reference() {
552 }
554
555 #[test]
556 fn test_new_cred_ccs_symmetric() {
557 let cred =
558 Credential::new_ccs_symmetric(CRED_PSK.try_into().unwrap(), K.try_into().unwrap());
559 assert_eq!(cred.bytes.as_slice(), CRED_PSK);
560 }
561
562 #[test]
563 fn test_parse_ccs_symmetric() {
564 let cred = Credential::parse_ccs_symmetric(CRED_PSK).unwrap();
565 assert_eq!(cred.bytes.as_slice(), CRED_PSK);
566 assert_eq!(cred.key, CredentialKey::Symmetric(K.try_into().unwrap()));
567 assert_eq!(cred.kid.unwrap().as_slice(), KID_VALUE_PSK);
568 assert_eq!(cred.cred_type, CredentialType::CCS_PSK);
569 }
570}