diff --git a/lib/src/edhoc.rs b/lib/src/edhoc.rs index b20c1db8..d016b10b 100644 --- a/lib/src/edhoc.rs +++ b/lib/src/edhoc.rs @@ -109,24 +109,28 @@ pub fn r_prepare_message_2( let salt_3e2m = compute_salt_3e2m(crypto, &prk_2e, &th_2); let prk_3e2m = compute_prk_3e2m(crypto, &salt_3e2m, r, &state.g_x); + let id_cred_r = match cred_transfer { + CredentialTransfer::ByValue => { + IdCred::tmp_from_ccs_or_kid(cred_r.value.as_slice(), IdCredType::KCCS as u8)? + } + CredentialTransfer::ByReference => { + IdCred::tmp_from_ccs_or_kid(&[cred_r.kid], IdCredType::KID as u8)? + } + }; + // compute MAC_2 let mac_2 = compute_mac_2( crypto, &prk_3e2m, c_r, - &cred_r.get_id_cred(), + id_cred_r.as_full_value(), cred_r.value.as_slice(), &th_2, ead_2, ); - let id_cred_r = match cred_transfer { - CredentialTransfer::ByValue => IdCred::FullCredential(cred_r.value.as_slice()), - CredentialTransfer::ByReference => IdCred::CompactKid(cred_r.kid), - }; - // compute ciphertext_2 - let plaintext_2 = encode_plaintext_2(c_r, &id_cred_r, &mac_2, &ead_2)?; + let plaintext_2 = encode_plaintext_2(c_r, id_cred_r.encode_for_plaintext(), &mac_2, &ead_2)?; // step is actually from processing of message_3 // but we do it here to avoid storing plaintext_2 in State @@ -156,37 +160,13 @@ pub fn r_parse_message_3( state: &mut WaitM3, crypto: &mut impl CryptoTrait, message_3: &BufferMessage3, -) -> Result<(ProcessingM3, CredentialRPK, Option), EDHOCError> { +) -> Result<(ProcessingM3, IdCred, Option), EDHOCError> { let plaintext_3 = decrypt_message_3(crypto, &state.prk_3e2m, &state.th_3, message_3); if let Ok(plaintext_3) = plaintext_3 { let decoded_p3_res = decode_plaintext_3(&plaintext_3); if let Ok((id_cred_i, mac_3, ead_3)) = decoded_p3_res { - let id_cred_i = match id_cred_i { - IdCred::CompactKid(kid) => CredentialRPK { - value: Default::default(), - public_key: Default::default(), - kid, - }, - IdCred::FullCredential(cred) => { - let Ok(buffer) = EdhocMessageBuffer::new_from_slice(cred) else { - return Err(EDHOCError::ParsingError); - }; - if let Ok(parsed_rpk) = CredentialRPK::new(buffer) { - parsed_rpk - } else { - // This is incomplete, and the application will need to fill in the gaps -- - // just as in the CompactKid case the CredentialRPK is also incomplete. - CredentialRPK { - value: buffer, - public_key: Default::default(), - kid: Default::default(), - } - } - } - }; - Ok(( ProcessingM3 { mac_3, @@ -306,7 +286,7 @@ pub fn i_parse_message_2<'a>( state: &WaitM2, crypto: &mut impl CryptoTrait, message_2: &BufferMessage2, -) -> Result<(ProcessingM2, ConnId, CredentialRPK, Option), EDHOCError> { +) -> Result<(ProcessingM2, ConnId, IdCred, Option), EDHOCError> { let res = parse_message_2(message_2); if let Ok((g_y, ciphertext_2)) = res { let th_2 = compute_th_2(crypto, &g_y, &state.h_message_1); @@ -331,20 +311,6 @@ pub fn i_parse_message_2<'a>( ead_2: ead_2.clone(), // needed for compute_mac_2 }; - let id_cred_r = match id_cred_r { - IdCred::CompactKid(kid) => CredentialRPK { - value: Default::default(), - public_key: Default::default(), - kid, - }, - IdCred::FullCredential(cred) => { - let Ok(buffer) = EdhocMessageBuffer::new_from_slice(cred) else { - return Err(EDHOCError::ParsingError); - }; - CredentialRPK::new(buffer)? - } - }; - Ok((state, c_r_2, id_cred_r, ead_2)) } else { Err(EDHOCError::ParsingError) @@ -409,20 +375,25 @@ pub fn i_prepare_message_3( cred_transfer: CredentialTransfer, ead_3: &Option, // FIXME: make it a list of EADItem ) -> Result<(Completed, BufferMessage3, BytesHashLen), EDHOCError> { + let id_cred_i = match cred_transfer { + CredentialTransfer::ByValue => { + IdCred::tmp_from_ccs_or_kid(cred_i.value.as_slice(), IdCredType::KCCS as u8)? + } + CredentialTransfer::ByReference => { + IdCred::tmp_from_ccs_or_kid(&[cred_i.kid], IdCredType::KID as u8)? + } + }; + let mac_3 = compute_mac_3( crypto, &state.prk_4e3m, &state.th_3, - &cred_i.get_id_cred(), + id_cred_i.as_full_value(), cred_i.value.as_slice(), ead_3, ); - let id_cred_i = match cred_transfer { - CredentialTransfer::ByValue => IdCred::FullCredential(cred_i.value.as_slice()), - CredentialTransfer::ByReference => IdCred::CompactKid(cred_i.kid), - }; - let plaintext_3 = encode_plaintext_3(&id_cred_i, &mac_3, &ead_3)?; + let plaintext_3 = encode_plaintext_3(id_cred_i.encode_for_plaintext(), &mac_3, &ead_3)?; let message_3 = encrypt_message_3(crypto, &state.prk_3e2m, &state.th_3, &plaintext_3); let th_4 = compute_th_4(crypto, &state.th_3, &plaintext_3, cred_i.value.as_slice()); @@ -643,14 +614,17 @@ fn edhoc_kdf( } fn encode_plaintext_3( - id_cred_i: &IdCred, + id_cred_i: &[u8], mac_3: &BytesMac3, ead_3: &Option, ) -> Result { let mut plaintext_3: BufferPlaintext3 = BufferPlaintext3::new(); // plaintext: P = ( ? PAD, ID_CRED_I / bstr / int, Signature_or_MAC_3, ? EAD_3 ) - id_cred_i.write_to_message(&mut plaintext_3)?; + // id_cred_i.write_to_message(&mut plaintext_3)?; + plaintext_3 + .extend_from_slice(id_cred_i) + .or(Err(EDHOCError::EncodingError))?; let offset_cred = plaintext_3.len; plaintext_3.content[offset_cred] = CBOR_MAJOR_BYTE_STRING | MAC_LENGTH_3 as u8; plaintext_3.content[offset_cred + 1..][..mac_3.len()].copy_from_slice(&mac_3[..]); @@ -793,7 +767,7 @@ fn decrypt_message_3( // output must hold id_cred.len() + cred.len() fn encode_kdf_context( c_r: Option, // only present for MAC_2 - id_cred: &BytesIdCred, + id_cred: &[u8], th: &BytesHashLen, cred: &[u8], ead: &Option, @@ -809,7 +783,7 @@ fn encode_kdf_context( } else { 0 // no u8 encoded }; - output[output_len..output_len + id_cred.len()].copy_from_slice(&id_cred[..]); + output[output_len..output_len + id_cred.len()].copy_from_slice(&id_cred); output[output_len + id_cred.len()] = CBOR_BYTE_STRING; output[output_len + id_cred.len() + 1] = SHA256_DIGEST_LEN as u8; output[output_len + id_cred.len() + 2..output_len + id_cred.len() + 2 + th.len()] @@ -835,7 +809,7 @@ fn compute_mac_3( crypto: &mut impl CryptoTrait, prk_4e3m: &BytesHashLen, th_3: &BytesHashLen, - id_cred_i: &BytesIdCred, + id_cred_i: &[u8], cred_i: &[u8], ead_3: &Option, ) -> BytesMac3 { @@ -861,7 +835,7 @@ fn compute_mac_2( crypto: &mut impl CryptoTrait, prk_3e2m: &BytesHashLen, c_r: ConnId, - id_cred_r: &BytesIdCred, + id_cred_r: &[u8], cred_r: &[u8], th_2: &BytesHashLen, ead_2: &Option, @@ -880,7 +854,7 @@ fn compute_mac_2( fn encode_plaintext_2( c_r: ConnId, - id_cred_r: &IdCred, + id_cred_r: &[u8], mac_2: &BytesMac2, ead_2: &Option, ) -> Result { @@ -890,7 +864,10 @@ fn encode_plaintext_2( plaintext_2 .extend_from_slice(c_r) .or(Err(EDHOCError::EncodingError))?; - id_cred_r.write_to_message(&mut plaintext_2)?; + // id_cred_r.write_to_message(&mut plaintext_2)?; + plaintext_2 + .extend_from_slice(id_cred_r) + .or(Err(EDHOCError::EncodingError))?; let offset_cred = plaintext_2.len; plaintext_2.content[offset_cred] = CBOR_MAJOR_BYTE_STRING | MAC_LENGTH_2 as u8; @@ -1395,7 +1372,9 @@ mod tests { let plaintext_2_tv = BufferPlaintext2::from_hex(PLAINTEXT_2_TV); let plaintext_2 = encode_plaintext_2( C_R_TV, - &IdCred::CompactKid(ID_CRED_R_TV[ID_CRED_R_TV.len() - 1]), + IdCred::from_full_value(&ID_CRED_R_TV[..]) + .unwrap() + .encode_for_plaintext(), &MAC_2_TV, &None::, ) @@ -1423,11 +1402,7 @@ mod tests { assert!(plaintext_2.is_ok()); let (c_r, id_cred_r, mac_2, ead_2) = plaintext_2.unwrap(); assert_eq!(c_r, C_R_TV); - let id_cred_r = match id_cred_r { - IdCred::CompactKid(id_cred_r) => id_cred_r, - _ => panic!("Invalid ID_CRED_R"), - }; - assert_eq!(id_cred_r, ID_CRED_R_TV[3]); + assert_eq!(id_cred_r.as_full_value(), ID_CRED_R_TV); assert_eq!(mac_2, MAC_2_TV); assert!(ead_2.is_none()); } @@ -1480,26 +1455,25 @@ mod tests { #[test] fn test_encode_plaintext_3() { let plaintext_3_tv = BufferPlaintext3::from_hex(PLAINTEXT_3_TV); - let kid_tv = ID_CRED_I_TV[ID_CRED_I_TV.len() - 1]; - let plaintext_3 = - encode_plaintext_3(&IdCred::CompactKid(kid_tv), &MAC_3_TV, &None::).unwrap(); + let plaintext_3 = encode_plaintext_3( + IdCred::from_full_value(&ID_CRED_I_TV[..]) + .unwrap() + .encode_for_plaintext(), + &MAC_3_TV, + &None::, + ) + .unwrap(); assert_eq!(plaintext_3, plaintext_3_tv); } #[test] fn test_decode_plaintext_3() { let plaintext_3_tv = BufferPlaintext3::from_hex(PLAINTEXT_3_TV); - let kid_tv = ID_CRED_I_TV[ID_CRED_I_TV.len() - 1]; let (id_cred_i, mac_3, ead_3) = decode_plaintext_3(&plaintext_3_tv).unwrap(); - let kid = match id_cred_i { - IdCred::CompactKid(id_cred_i) => id_cred_i, - _ => panic!("Invalid ID_CRED_I"), - }; - assert_eq!(mac_3, MAC_3_TV); - assert_eq!(kid, kid_tv); + assert_eq!(id_cred_i.as_full_value(), ID_CRED_I_TV); assert!(ead_3.is_none()); } diff --git a/lib/src/lib.rs b/lib/src/lib.rs index b4824bf3..6901915f 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -180,14 +180,7 @@ impl<'a, Crypto: CryptoTrait> EdhocResponderWaitM3 { pub fn parse_message_3( mut self, message_3: &'a BufferMessage3, - ) -> Result< - ( - EdhocResponderProcessingM3, - CredentialRPK, - Option, - ), - EDHOCError, - > { + ) -> Result<(EdhocResponderProcessingM3, IdCred, Option), EDHOCError> { trace!("Enter parse_message_3"); match r_parse_message_3(&mut self.state, &mut self.crypto, message_3) { Ok((state, id_cred_i, ead_3)) => Ok(( @@ -321,7 +314,7 @@ impl<'a, Crypto: CryptoTrait> EdhocInitiatorWaitM2 { ( EdhocInitiatorProcessingM2, ConnId, - CredentialRPK, + IdCred, Option, ), EDHOCError, @@ -471,7 +464,7 @@ pub fn generate_connection_identifier(crypto: &mut Crypto) // Implements auth credential checking according to draft-tiloca-lake-implem-cons pub fn credential_check_or_fetch( cred_expected: Option, - id_cred_received: CredentialRPK, + id_cred_received: IdCred, ) -> Result { trace!("Enter credential_check_or_fetch"); // Processing of auth credentials according to draft-tiloca-lake-implem-cons @@ -481,9 +474,10 @@ pub fn credential_check_or_fetch( // IMPL: compare cred_i_expected with id_cred // IMPL: assume cred_i_expected is well formed let credentials_match = if id_cred_received.reference_only() { - id_cred_received.kid == cred_expected.kid + // FIXME: will be fixed when we update CredentialRPK to Credential + id_cred_received.as_full_value()[3] == cred_expected.kid } else { - id_cred_received.value == cred_expected.value + &id_cred_received.as_full_value()[2..] == cred_expected.value.as_slice() }; // 2. Is this authentication credential still valid? @@ -512,7 +506,9 @@ pub fn credential_check_or_fetch( // Pair it with consistent credential identifiers, for each supported type of credential identifier. assert!(!id_cred_received.reference_only()); - Ok(id_cred_received) + // FIXME: will be fixed when we update CredentialRPK to Credential + CredentialRPK::new(id_cred_received.as_full_value()[2..].try_into().unwrap()) + .map_err(|_| EDHOCError::ParsingError) } // 8. Is this authentication credential good to use in the context of this EDHOC session? @@ -733,7 +729,12 @@ mod test_authz { EDHOCMethod::StatStat, EDHOCSuite::CipherSuite2, ); - let responder = EdhocResponder::new(default_crypto(), R, cred_r); + let responder = EdhocResponder::new( + default_crypto(), + EDHOCMethod::StatStat, + R.try_into().expect("Wrong length of responder private key"), + cred_r.clone(), + ); // ==== initialize ead-authz ==== let device = ZeroTouchDevice::new( diff --git a/shared/src/cred_new.rs b/shared/src/cred_new.rs index fe495c1b..eae0effa 100644 --- a/shared/src/cred_new.rs +++ b/shared/src/cred_new.rs @@ -44,30 +44,58 @@ impl From for IdCredType { /// /// Possible values include key IDs, credentials by value and others. // TODO: rename to just IdCred -pub struct IdCredNew { +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct IdCred { /// The value is always stored in the ID_CRED_x form as a serialized one-element dictionary; /// while this technically wastes two bytes, it has the convenient property of having the full /// value available as a slice. pub bytes: BufferIdCred, // variable size, can contain either the contents of a BufferCred or a BufferKid } -impl IdCredNew { +impl IdCred { pub fn new() -> Self { Self { bytes: BufferIdCred::new(), } } - /// Instantiate an IdCredNew from an encoded value. + pub fn from_full_value(value: &[u8]) -> Result { + Ok(Self { + bytes: BufferIdCred::new_from_slice(value) + .map_err(|_| EDHOCError::CredentialTooLongError)?, + }) + } + + // FIXME: function only used while CredentialRPK is still around + pub fn tmp_from_ccs_or_kid(value: &[u8], label: u8) -> Result { + let mut bytes = BufferIdCred::new_from_slice(&[0xa1, label]) + .map_err(|_| EDHOCError::CredentialTooLongError)?; + if label == IdCredType::KID as u8 { + // the actual value of the kid is always a byte string + bytes + .extend_from_slice(&[0x40 | value.len() as u8]) + .map_err(|_| EDHOCError::CredentialTooLongError)?; + } + bytes + .extend_from_slice(value) + .map_err(|_| EDHOCError::CredentialTooLongError)?; + Ok(Self { bytes }) + } + + pub fn bstr_representable_as_int(value: u8) -> bool { + (0x0..=0x17).contains(&value) || (0x20..=0x37).contains(&value) + } + + /// Instantiate an IdCred from an encoded value. pub fn from_encoded_plaintext(value: &[u8]) -> Result { let bytes = match value { // kid that has been encoded as CBOR integer - &[x] if x < 24 => { + &[x] if Self::bstr_representable_as_int(x) => { BufferIdCred::new_from_slice(&[0xa1, KID_LABEL, 0x41, x]) .map_err(|_| EDHOCError::CredentialTooLongError)? // TODO: make this error handling less verbose? } // kid that has been encoded as CBOR byte string - &[0x41, ..] => { + &[0x41, x, ..] if !Self::bstr_representable_as_int(x) => { let mut bytes = BufferIdCred::new_from_slice(&[0xa1, KID_LABEL]) .map_err(|_| EDHOCError::CredentialTooLongError)?; bytes @@ -75,15 +103,10 @@ impl IdCredNew { .map_err(|_| EDHOCError::CredentialTooLongError)?; bytes } - // credential by value - value => { - let mut bytes = BufferIdCred::new_from_slice(&[0xa1, KCSS_LABEL]) - .map_err(|_| EDHOCError::CredentialTooLongError)?; - bytes - .extend_from_slice(value) - .map_err(|_| EDHOCError::CredentialTooLongError)?; - bytes - } + // CCS by value + &[0xa1, KCSS_LABEL, ..] => BufferIdCred::new_from_slice(value) + .map_err(|_| EDHOCError::CredentialTooLongError)?, + _ => return Err(EDHOCError::ParsingError), }; Ok(Self { bytes }) @@ -107,6 +130,10 @@ impl IdCredNew { } } + pub fn reference_only(&self) -> bool { + [IdCredType::KID].contains(&self.item_type()) + } + pub fn item_type(&self) -> IdCredType { self.bytes.as_slice()[1].into() } @@ -230,10 +257,10 @@ impl Credential { /// /// For example, if the credential is a CCS: /// { /kccs/ 14: bytes } - pub fn by_value(&self) -> Result { + pub fn by_value(&self) -> Result { match self.cred_type { CredentialType::CCS => { - let mut id_cred = IdCredNew::new(); + let mut id_cred = IdCred::new(); id_cred .bytes .extend_from_slice(&[CBOR_MAJOR_MAP + 1, KCSS_LABEL]) @@ -256,11 +283,11 @@ impl Credential { /// { /kid/ 4: kid } /// /// TODO: accept a parameter to specify the type of reference, e.g. kid, x5t, etc. - pub fn by_kid(&self) -> Result { + pub fn by_kid(&self) -> Result { let Some(kid) = self.kid.as_ref() else { return Err(EDHOCError::MissingIdentity); }; - let mut id_cred = IdCredNew::new(); + let mut id_cred = IdCred::new(); id_cred .bytes .extend_from_slice(&[ @@ -349,10 +376,10 @@ mod test { #[rstest] #[case(&[0x0D], &[0xa1, 0x04, 0x41, 0x0D])] // two optimizations: omit kid label and encode as CBOR integer #[case(&[0x41, 0x18], &[0xa1, 0x04, 0x41, 0x18])] // one optimization: omit kid label - #[case(CRED_TV, ID_CRED_BY_VALUE_TV)] // regular credential by value + #[case(ID_CRED_BY_VALUE_TV, ID_CRED_BY_VALUE_TV)] // regular credential by value fn test_id_cred_from_encoded_plaintext(#[case] input: &[u8], #[case] expected: &[u8]) { assert_eq!( - IdCredNew::from_encoded_plaintext(input) + IdCred::from_encoded_plaintext(input) .unwrap() .as_full_value(), expected diff --git a/shared/src/lib.rs b/shared/src/lib.rs index 3fd124c1..37734fd4 100644 --- a/shared/src/lib.rs +++ b/shared/src/lib.rs @@ -545,31 +545,6 @@ impl EADItem { } } -// FIXME: homogenize the two structs below (likey keep only the owned version) -#[derive(Debug, Clone, Copy)] -pub enum IdCred<'a> { - CompactKid(u8), - /// Credential by value. It is required that the credential is a valid deterministic encoding - /// of a CCS. - FullCredential(&'a [u8]), -} - -impl<'a> IdCred<'a> { - pub fn write_to_message(&self, message: &mut EdhocMessageBuffer) -> Result<(), EDHOCError> { - match self { - IdCred::CompactKid(kid) => message.extend_from_slice(&[*kid]), - IdCred::FullCredential(cred) => { - let kccs_map_len = 1; - message - .extend_from_slice(&[CBOR_MAJOR_MAP + kccs_map_len, KCSS_LABEL]) - .map_err(|_| EDHOCError::CredentialTooLongError)?; - message.extend_from_slice(cred) - } - } - .map_err(|_| EDHOCError::CredentialTooLongError) - } -} - mod helpers { use super::*; @@ -768,16 +743,8 @@ mod edhoc_parser { let c_r = ConnId::from_int_raw(decoder.int_raw()?); - // NOTE: if len of bstr is 1, it is a compact kid and therefore should have been encoded as int - let id_cred_r = if CBOR_MAJOR_MAP == CBORDecoder::type_of(decoder.current()?) { - if decoder.map()? == 1 && decoder.u8()? == KCSS_LABEL { - IdCred::FullCredential(decoder.any_as_encoded()?) - } else { - return Err(EDHOCError::ParsingError); - } - } else { - IdCred::CompactKid(decoder.int_raw()?) - }; + // the id_cred may have been encoded as a single int, a byte string, or a map + let id_cred_r = IdCred::from_encoded_plaintext(decoder.any_as_encoded()?)?; mac_2[..].copy_from_slice(decoder.bytes_sized(MAC_LENGTH_2)?); @@ -805,16 +772,8 @@ mod edhoc_parser { let mut decoder = CBORDecoder::new(plaintext_3.as_slice()); - // NOTE: if len of bstr is 1, it is a compact kid and therefore should have been encoded as int - let id_cred_i = if CBOR_MAJOR_MAP == CBORDecoder::type_of(decoder.current()?) { - if decoder.map()? == 1 && decoder.u8()? == KCSS_LABEL { - IdCred::FullCredential(decoder.any_as_encoded()?) - } else { - return Err(EDHOCError::ParsingError); - } - } else { - IdCred::CompactKid(decoder.int_raw()?) - }; + // the id_cred may have been encoded as a single int, a byte string, or a map + let id_cred_i = IdCred::from_encoded_plaintext(decoder.any_as_encoded()?)?; mac_3[..].copy_from_slice(decoder.bytes_sized(MAC_LENGTH_3)?);