1use defmt_or_log::trace;
7
8use crate::error::{CredentialError, CredentialErrorDetail};
9
10use crate::helpers::COwn;
11
12pub(crate) const OWN_NONCE_LEN: usize = 8;
14
15const MAX_SUPPORTED_PEER_NONCE_LEN: usize = 16;
17
18const MAX_SUPPORTED_ACCESSTOKEN_LEN: usize = 256;
20const MAX_SUPPORTED_ENCRYPT_PROTECTED_LEN: usize = 32;
22
23#[cfg_attr(feature = "defmt", derive(defmt::Format))]
28#[derive(minicbor::Decode, minicbor::Encode, Default)]
29#[cbor(map)]
30#[non_exhaustive]
31struct AceCbor<'a> {
32 #[cbor(b(1), with = "minicbor::bytes")]
33 access_token: Option<&'a [u8]>,
34 #[cbor(b(40), with = "minicbor::bytes")]
35 nonce1: Option<&'a [u8]>,
36 #[cbor(b(42), with = "minicbor::bytes")]
37 nonce2: Option<&'a [u8]>,
38 #[cbor(b(43), with = "minicbor::bytes")]
39 ace_client_recipientid: Option<&'a [u8]>,
40 #[cbor(b(44), with = "minicbor::bytes")]
41 ace_server_recipientid: Option<&'a [u8]>,
42}
43
44type UnprotectedAuthzInfoPost<'a> = AceCbor<'a>;
52
53#[cfg_attr(feature = "defmt", derive(defmt::Format))]
58#[derive(minicbor::Decode)]
59#[cbor(map)]
60#[non_exhaustive]
61pub struct HeaderMap<'a> {
62 #[n(1)]
63 pub(crate) alg: Option<i32>,
65 #[cbor(b(5), with = "minicbor::bytes")]
66 pub(crate) iv: Option<&'a [u8]>,
67}
68
69impl HeaderMap<'_> {
70 fn updated_with(&self, other: Self) -> Self {
72 Self {
73 alg: self.alg.or(other.alg),
74 iv: self.iv.or(other.iv),
75 }
76 }
77}
78
79#[cfg_attr(feature = "defmt", derive(defmt::Format))]
87#[derive(minicbor::Decode, Debug)]
88#[allow(
89 dead_code,
90 reason = "Presence of the item makes CBOR derive tolerate the item"
91)]
92#[cbor(map)]
93#[non_exhaustive]
94pub(crate) struct CoseKey<'a> {
95 #[n(1)]
96 pub(crate) kty: i32, #[cbor(b(2), with = "minicbor::bytes")]
98 pub(crate) kid: Option<&'a [u8]>,
99 #[n(3)]
100 pub(crate) alg: Option<i32>, #[n(-1)]
103 pub(crate) crv: Option<i32>, #[cbor(b(-2), with = "minicbor::bytes")]
105 pub(crate) x: Option<&'a [u8]>,
106 #[cbor(b(-3), with = "minicbor::bytes")]
107 pub(crate) y: Option<&'a [u8]>, }
109
110#[cfg_attr(feature = "defmt", derive(defmt::Format))]
112#[derive(minicbor::Decode)]
113#[cbor(tag(16))]
114#[non_exhaustive]
115struct CoseEncrypt0<'a> {
116 #[cbor(b(0), with = "minicbor::bytes")]
117 protected: &'a [u8],
118 #[b(1)]
119 unprotected: HeaderMap<'a>,
120 #[cbor(b(2), with = "minicbor::bytes")]
121 encrypted: &'a [u8],
122}
123
124impl CoseEncrypt0<'_> {
125 fn prepare_decryption<'t>(
132 &self,
133 buffer: &'t mut heapless::Vec<u8, MAX_SUPPORTED_ACCESSTOKEN_LEN>,
134 ) -> Result<(HeaderMap<'_>, impl AsRef<[u8]>, &'t mut [u8]), CredentialError> {
135 trace!("Preparing decryption of {:?}", self);
136
137 let protected: HeaderMap = minicbor::decode(self.protected)?;
140 trace!("Protected decoded as header map: {:?}", protected);
141 let headers = self.unprotected.updated_with(protected);
142
143 #[derive(minicbor::Encode)]
144 struct Encrypt0<'a> {
145 #[n(0)]
146 context: &'static str,
147 #[cbor(b(1), with = "minicbor::bytes")]
148 protected: &'a [u8],
149 #[cbor(b(2), with = "minicbor::bytes")]
150 external_aad: &'a [u8],
151 }
152 let aad = Encrypt0 {
153 context: "Encrypt0",
154 protected: self.protected,
155 external_aad: &[],
156 };
157 const AADSIZE: usize = 1 + 1 + 8 + 1 + MAX_SUPPORTED_ENCRYPT_PROTECTED_LEN + 1;
158 let mut aad_encoded = heapless::Vec::<u8, AADSIZE>::new();
159 minicbor::encode(&aad, minicbor_adapters::WriteToHeapless(&mut aad_encoded))
160 .map_err(|_| CredentialErrorDetail::ConstraintExceeded)?;
161 trace!("Serialized AAD: {:02x}", aad_encoded); buffer.clear();
164 buffer
168 .extend_from_slice(self.encrypted)
169 .map_err(|_| CredentialErrorDetail::ConstraintExceeded)?;
170
171 Ok((headers, aad_encoded, buffer))
172 }
173}
174
175type EncryptedCwt<'a> = CoseEncrypt0<'a>;
176
177#[cfg_attr(feature = "defmt", derive(defmt::Format))]
179#[derive(minicbor::Decode)]
180#[cbor(tag(18))]
181#[non_exhaustive]
182struct CoseSign1<'a> {
183 #[cbor(b(0), with = "minicbor::bytes")]
184 protected: &'a [u8],
185 #[b(1)]
186 unprotected: HeaderMap<'a>,
187 #[cbor(b(2), with = "minicbor::bytes")]
189 payload: &'a [u8],
190 #[cbor(b(3), with = "minicbor::bytes")]
191 signature: &'a [u8],
192}
193
194type SignedCwt<'a> = CoseSign1<'a>;
195
196#[derive(minicbor::Decode, Debug)]
201#[allow(
202 dead_code,
203 reason = "Presence of the item makes CBOR derive tolerate the item"
204)]
205#[cbor(map)]
206#[non_exhaustive]
207pub struct CwtClaimsSet<'a> {
208 #[n(3)]
209 pub(crate) aud: Option<&'a str>,
210 #[n(4)]
211 pub(crate) exp: u64,
212 #[n(6)]
213 pub(crate) iat: u64,
214 #[b(8)]
215 cnf: Cnf<'a>,
216 #[cbor(b(9), with = "minicbor::bytes")]
217 pub(crate) scope: &'a [u8],
218}
219
220#[derive(minicbor::Decode, Debug)]
235#[cbor(map)]
236#[non_exhaustive]
237struct Cnf<'a> {
238 #[b(4)]
239 osc: Option<OscoreInputMaterial<'a>>,
240 #[b(1)]
241 cose_key: Option<minicbor_adapters::WithOpaque<'a, CoseKey<'a>>>,
242}
243
244#[cfg_attr(feature = "defmt", derive(defmt::Format))]
252#[derive(minicbor::Decode, Debug)]
253#[allow(
254 dead_code,
255 reason = "Presence of the item makes CBOR derive tolerate the item"
256)]
257#[cbor(map)]
258#[non_exhaustive]
259struct OscoreInputMaterial<'a> {
260 #[cbor(b(0), with = "minicbor::bytes")]
261 id: &'a [u8],
262 #[cbor(b(2), with = "minicbor::bytes")]
263 ms: &'a [u8],
264}
265
266impl OscoreInputMaterial<'_> {
267 fn derive(
268 &self,
269 nonce1: &[u8],
270 nonce2: &[u8],
271 sender_id: &[u8],
272 recipient_id: &[u8],
273 ) -> Result<liboscore::PrimitiveContext, CredentialError> {
274 let hkdf = liboscore::HkdfAlg::from_number(5).expect("Default algorithm is supported");
276 let aead = liboscore::AeadAlg::from_number(10).expect("Default algorithm is supported");
277
278 const { assert!(OWN_NONCE_LEN < 256) };
281 const { assert!(MAX_SUPPORTED_PEER_NONCE_LEN < 256) };
282 let mut combined_salt =
283 heapless::Vec::<u8, { 1 + 2 + MAX_SUPPORTED_PEER_NONCE_LEN + 2 + OWN_NONCE_LEN }>::new(
284 );
285 let mut encoder =
286 minicbor::Encoder::new(minicbor_adapters::WriteToHeapless(&mut combined_salt));
287 encoder
289 .bytes(b"")
290 .and_then(|encoder| encoder.bytes(nonce1))
291 .and_then(|encoder| encoder.bytes(nonce2))?;
292
293 let immutables = liboscore::PrimitiveImmutables::derive(
294 hkdf,
295 self.ms,
296 &combined_salt,
297 None, aead,
299 sender_id,
300 recipient_id,
301 )
302 .map_err(|_| CredentialErrorDetail::UnsupportedAlgorithm)?;
304
305 Ok(liboscore::PrimitiveContext::new_from_fresh_material(
307 immutables,
308 ))
309 }
310}
311
312pub struct AceCborAuthzInfoResponse {
318 nonce2: [u8; OWN_NONCE_LEN],
319 ace_server_recipientid: COwn,
320}
321
322impl AceCborAuthzInfoResponse {
323 pub(crate) fn render<M: coap_message::MutableWritableMessage>(
324 &self,
325 message: &mut M,
326 ) -> Result<(), M::UnionError> {
327 let full = AceCbor {
328 nonce2: Some(&self.nonce2),
329 ace_server_recipientid: Some(self.ace_server_recipientid.as_slice()),
330 ..Default::default()
331 };
332
333 use coap_message::Code;
334 message.set_code(M::Code::new(coap_numbers::code::CHANGED)?);
335
336 const { assert!(OWN_NONCE_LEN < 256) };
337 const { assert!(COwn::MAX_SLICE_LEN < 256) };
338 let required_len = 1 + 2 + 2 + OWN_NONCE_LEN + 2 + 2 + COwn::MAX_SLICE_LEN;
339 let payload = message.payload_mut_with_len(required_len)?;
340
341 let mut cursor = minicbor::encode::write::Cursor::new(payload);
342 minicbor::encode(full, &mut cursor).expect("Sufficient size was requested");
343 let written = cursor.position();
344 message.truncate(written)?;
345
346 Ok(())
347 }
348}
349
350pub(crate) fn process_acecbor_authz_info<GC: crate::GeneralClaims>(
375 payload: &[u8],
376 authorities: &impl crate::seccfg::ServerSecurityConfig<GeneralClaims = GC>,
377 nonce2: [u8; OWN_NONCE_LEN],
378 server_recipient_id: impl FnOnce(&[u8]) -> COwn,
379) -> Result<(AceCborAuthzInfoResponse, liboscore::PrimitiveContext, GC), CredentialError> {
380 trace!("Processing authz_info {=[u8]:02x}", payload); let decoded: UnprotectedAuthzInfoPost = minicbor::decode(payload)?;
383 let AceCbor {
386 access_token: Some(access_token),
387 nonce1: Some(nonce1),
388 ace_client_recipientid: Some(ace_client_recipientid),
389 ..
390 } = decoded
391 else {
392 return Err(CredentialErrorDetail::ProtocolViolation.into());
393 };
394
395 trace!(
396 "Decodeded authz_info as application/ace+cbor: {:?}",
397 decoded
398 );
399
400 let encrypt0: EncryptedCwt = minicbor::decode(access_token)?;
401
402 let mut buffer = Default::default();
403 let (headers, aad_encoded, buffer) = encrypt0.prepare_decryption(&mut buffer)?;
404
405 if headers.alg != Some(31) {
410 return Err(CredentialErrorDetail::UnsupportedAlgorithm.into());
411 }
412
413 let (processed, parsed) =
414 authorities.decrypt_symmetric_token(&headers, aad_encoded.as_ref(), buffer)?;
415
416 let Cnf {
421 osc: Some(osc),
422 cose_key: None,
423 } = parsed.cnf
424 else {
425 return Err(CredentialErrorDetail::InconsistentDetails.into());
426 };
427
428 let ace_server_recipientid = server_recipient_id(ace_client_recipientid);
429
430 let derived = osc.derive(
431 nonce1,
432 &nonce2,
433 ace_client_recipientid,
434 ace_server_recipientid.as_slice(),
435 )?;
436
437 let response = AceCborAuthzInfoResponse {
438 nonce2,
439 ace_server_recipientid,
440 };
441
442 Ok((response, derived, processed))
443}
444
445pub(crate) fn process_edhoc_token<GeneralClaims>(
446 ead3: &[u8],
447 authorities: &impl crate::seccfg::ServerSecurityConfig<GeneralClaims = GeneralClaims>,
448) -> Result<(lakers::Credential, GeneralClaims), CredentialError> {
449 let mut buffer = heapless::Vec::<u8, MAX_SUPPORTED_ACCESSTOKEN_LEN>::new();
450
451 let (processed, parsed) = if let Ok(encrypt0) = minicbor::decode::<EncryptedCwt>(ead3) {
455 let (headers, aad_encoded, buffer) = encrypt0.prepare_decryption(&mut buffer)?;
456
457 authorities.decrypt_symmetric_token(&headers, aad_encoded.as_ref(), buffer)?
458 } else if let Ok(sign1) = minicbor::decode::<SignedCwt>(ead3) {
459 let protected: HeaderMap = minicbor::decode(sign1.protected)?;
460 trace!(
461 "Decoded protected header map {:?} inside sign1 container {:?}",
462 &protected,
463 &sign1
464 );
465 let headers = sign1.unprotected.updated_with(protected);
466
467 #[derive(minicbor::Encode)]
468 struct SigStructureForSignature1<'a> {
469 #[n(0)]
470 context: &'static str,
471 #[cbor(b(1), with = "minicbor::bytes")]
472 body_protected: &'a [u8],
473 #[cbor(b(2), with = "minicbor::bytes")]
474 external_aad: &'a [u8],
475 #[cbor(b(3), with = "minicbor::bytes")]
476 payload: &'a [u8],
477 }
478 let aad = SigStructureForSignature1 {
479 context: "Signature1",
480 body_protected: sign1.protected,
481 external_aad: &[],
482 payload: sign1.payload,
483 };
484 buffer = heapless::Vec::new();
485 minicbor::encode(&aad, minicbor_adapters::WriteToHeapless(&mut buffer))?;
486 trace!("Serialized AAD: {:#02x}", buffer);
487
488 authorities.verify_asymmetric_token(&headers, &buffer, sign1.signature, sign1.payload)?
489 } else {
490 return Err(CredentialErrorDetail::UnsupportedExtension.into());
491 };
492
493 let Cnf {
494 osc: None,
495 cose_key: Some(cose_key),
496 } = parsed.cnf
497 else {
498 return Err(CredentialErrorDetail::InconsistentDetails.into());
499 };
500
501 let mut prefixed = lakers::BufferCred::new();
502 prefixed
504 .extend_from_slice(&[0xa1, 0x08, 0xa1, 0x01])
505 .unwrap();
506 prefixed
507 .extend_from_slice(&cose_key.opaque)
508 .map_err(|_| CredentialErrorDetail::ConstraintExceeded)?;
509 let credential = lakers::Credential::new_ccs(
510 prefixed,
511 cose_key
512 .parsed
513 .x
514 .ok_or(CredentialErrorDetail::InconsistentDetails)?
515 .try_into()
516 .map_err(|_| CredentialErrorDetail::InconsistentDetails)?,
517 );
518
519 Ok((credential, processed))
520}