diff --git a/examples/coap/src/bin/coapclient.rs b/examples/coap/src/bin/coapclient.rs index c04bd695..930e54b7 100644 --- a/examples/coap/src/bin/coapclient.rs +++ b/examples/coap/src/bin/coapclient.rs @@ -51,7 +51,7 @@ fn client_handshake() -> Result<(), EDHOCError> { let valid_cred_r = credential_check_or_fetch(Some(cred_r), id_cred_r).unwrap(); let initiator = initiator.verify_message_2(&I, cred_i, valid_cred_r)?; - let mut msg_3 = Vec::from([c_r]); + let mut msg_3 = Vec::from(c_r.as_cbor()); let (mut initiator, message_3, prk_out) = initiator.prepare_message_3(CredentialTransfer::ByReference, &None)?; msg_3.extend_from_slice(message_3.as_slice()); diff --git a/examples/coap/src/bin/coapserver-coaphandler.rs b/examples/coap/src/bin/coapserver-coaphandler.rs index 65641a01..ec250605 100644 --- a/examples/coap/src/bin/coapserver-coaphandler.rs +++ b/examples/coap/src/bin/coapserver-coaphandler.rs @@ -21,7 +21,7 @@ const W_TV: &[u8] = &hex!("4E5E15AB35008C15B89E91F9F329164D4AACD53D9923672CE0019 #[derive(Debug)] struct EdhocHandler { - connections: Vec<(u8, EdhocResponderWaitM3)>, + connections: Vec<(ConnId, EdhocResponderWaitM3)>, mock_server: ZeroTouchServer, } @@ -48,7 +48,7 @@ fn render_error(_e: EDHOCError) -> Error { } impl EdhocHandler { - fn take_connection_by_c_r(&mut self, c_r: u8) -> Option> { + fn take_connection_by_c_r(&mut self, c_r: ConnId) -> Option> { let index = self .connections .iter() @@ -58,7 +58,7 @@ impl EdhocHandler { Some(self.connections.pop().unwrap().1) } - fn new_c_r(&self) -> u8 { + fn new_c_r(&self) -> ConnId { // FIXME: We'll need to do better, but a) that'll be more practical when we can do more // than u8, and b) that'll best be coordinated with a storage that not only stores EDHOC // contexts but also OSCORE ones. @@ -66,7 +66,7 @@ impl EdhocHandler { if result >= 24 { panic!("Contexts exceeded"); } - result as _ + ConnId::from_int_raw(result as _) } } @@ -74,7 +74,7 @@ enum EdhocResponse { // We could also store the responder in the Vec (once we're done rendering the response, we'll // take up a slot there anyway) if we make it an enum. OkSend2 { - c_r: u8, + c_r: ConnId, responder: EdhocResponderProcessedM1<'static, Crypto>, // FIXME: Is the ead_2 the most practical data to store here? An easy alternative is the // voucher_response; ideal would be the voucher, bu that is only internal to prepare_ead_2. @@ -149,15 +149,19 @@ impl coap_handler::Handler for EdhocHandler { } else { // potentially message 3 // - // We know our short C_R lengths + // FIXME: Work with longer IDs as well (https://github.com/openwsn-berkeley/lakers/issues/258) let (c_r_rcvd, message_3) = request .payload() .split_first() // FIXME: Being way too short, is that an EDHOC error or a CoAP error? .ok_or_else(Error::bad_request)?; + // FIXME: This panics or creates an in valid ConnId (but once we fix + // working with longer IDs, there will be a function that has proper error handling) + let c_r_rcvd = ConnId::from_int_raw(*c_r_rcvd); + let responder = self - .take_connection_by_c_r(*c_r_rcvd) + .take_connection_by_c_r(c_r_rcvd) // FIXME: Produce proper error .ok_or_else(Error::bad_request)?; diff --git a/examples/coap/src/bin/coapserver.rs b/examples/coap/src/bin/coapserver.rs index a058ff0a..63d323c8 100644 --- a/examples/coap/src/bin/coapserver.rs +++ b/examples/coap/src/bin/coapserver.rs @@ -83,7 +83,7 @@ fn main() { } else { // potentially message 3 println!("Received message 3"); - let c_r_rcvd = request.message.payload[0]; + let c_r_rcvd = ConnId::from_int_raw(request.message.payload[0]); // FIXME let's better not *panic here let responder = take_state(c_r_rcvd, &mut edhoc_connections).unwrap(); @@ -141,8 +141,8 @@ fn main() { } fn take_state( - c_r_rcvd: u8, - edhoc_protocol_states: &mut Vec<(u8, R)>, + c_r_rcvd: ConnId, + edhoc_protocol_states: &mut Vec<(ConnId, R)>, ) -> Result { for (i, element) in edhoc_protocol_states.iter().enumerate() { let (c_r, _responder) = element; diff --git a/examples/lakers-no_std/src/main.rs b/examples/lakers-no_std/src/main.rs index b6aa8936..68066315 100644 --- a/examples/lakers-no_std/src/main.rs +++ b/examples/lakers-no_std/src/main.rs @@ -83,8 +83,8 @@ fn main() -> ! { fn test_prepare_message_1() { let mut initiator = EdhocInitiator::new(lakers_crypto::default_crypto()); - let c_i: u8 = - generate_connection_identifier_cbor(&mut lakers_crypto::default_crypto()).into(); + let c_i = + generate_connection_identifier_cbor(&mut lakers_crypto::default_crypto()).as_slice(); let message_1 = initiator.prepare_message_1(None, &None); assert!(message_1.is_ok()); } diff --git a/lakers-c/src/initiator.rs b/lakers-c/src/initiator.rs index 4d9a1a27..4f07cc09 100644 --- a/lakers-c/src/initiator.rs +++ b/lakers-c/src/initiator.rs @@ -56,7 +56,7 @@ pub unsafe extern "C" fn initiator_prepare_message_1( let c_i = if c_i.is_null() { generate_connection_identifier_cbor(crypto) } else { - *c_i + ConnId::from_int_raw(*c_i) }; let ead_1 = if ead_1_c.is_null() { @@ -108,7 +108,9 @@ pub unsafe extern "C" fn initiator_parse_message_2( let result = match i_parse_message_2(&state, crypto, &(*message_2)) { Ok((state, c_r, id_cred_r, ead_2)) => { ProcessingM2C::copy_into_c(state, &mut (*initiator_c).processing_m2); - *c_r_out = c_r; + let c_r = c_r.as_slice(); + assert_eq!(c_r.len(), 1, "C API only supports short C_R"); + *c_r_out = c_r[0]; *id_cred_r_out = id_cred_r; if let Some(ead_2) = ead_2 { EADItemC::copy_into_c(ead_2, ead_2_c_out); diff --git a/lakers-c/src/lib.rs b/lakers-c/src/lib.rs index af7c67e8..1476e30c 100644 --- a/lakers-c/src/lib.rs +++ b/lakers-c/src/lib.rs @@ -86,7 +86,7 @@ impl ProcessingM2C { x: self.x, g_y: self.g_y, plaintext_2: self.plaintext_2, - c_r: self.c_r, + c_r: ConnId::from_int_raw(self.c_r), ead_2: if self.ead_2.is_null() { None } else { @@ -107,7 +107,9 @@ impl ProcessingM2C { (*processing_m2_c).x = processing_m2.x; (*processing_m2_c).g_y = processing_m2.g_y; (*processing_m2_c).plaintext_2 = processing_m2.plaintext_2; - (*processing_m2_c).c_r = processing_m2.c_r; + let c_r = processing_m2.c_r.as_slice(); + assert_eq!(c_r.len(), 1, "C API only supports short C_R"); + (*processing_m2_c).c_r = c_r[0]; } } diff --git a/lakers-python/src/initiator.rs b/lakers-python/src/initiator.rs index 1519aca4..0edaaf63 100644 --- a/lakers-python/src/initiator.rs +++ b/lakers-python/src/initiator.rs @@ -40,11 +40,13 @@ impl PyEdhocInitiator { fn prepare_message_1<'a>( &mut self, py: Python<'a>, - c_i: Option, + c_i: Option>, ead_1: Option, ) -> PyResult<&'a PyBytes> { let c_i = match c_i { - Some(c_i) => c_i, + Some(c_i) => ConnId::from_slice(c_i.as_slice()).ok_or( + pyo3::exceptions::PyValueError::new_err("Connection identifier out of range"), + )?, None => generate_connection_identifier_cbor(&mut default_crypto()), }; @@ -61,7 +63,7 @@ impl PyEdhocInitiator { &mut self, py: Python<'a>, message_2: Vec, - ) -> PyResult<(u8, &'a PyBytes, Option)> { + ) -> PyResult<(&'a PyBytes, &'a PyBytes, Option)> { let message_2 = EdhocMessageBuffer::new_from_slice(message_2.as_slice())?; match i_parse_message_2(&self.wait_m2, &mut default_crypto(), &message_2) { @@ -72,6 +74,7 @@ impl PyEdhocInitiator { } else { PyBytes::new(py, id_cred_r.value.as_slice()) }; + let c_r = PyBytes::new(py, c_r.as_slice()); Ok((c_r, id_cred_r, ead_2)) } Err(error) => Err(error.into()), diff --git a/lakers-python/src/responder.rs b/lakers-python/src/responder.rs index ee254a62..cc963f7c 100644 --- a/lakers-python/src/responder.rs +++ b/lakers-python/src/responder.rs @@ -45,11 +45,13 @@ impl PyEdhocResponder { &mut self, py: Python<'a>, cred_transfer: CredentialTransfer, - c_r: Option, + c_r: Option>, ead_2: Option, ) -> PyResult<&'a PyBytes> { let c_r = match c_r { - Some(c_r) => c_r, + Some(c_r) => ConnId::from_slice(c_r.as_slice()).ok_or( + pyo3::exceptions::PyValueError::new_err("Connection identifier out of range"), + )?, None => generate_connection_identifier_cbor(&mut default_crypto()), }; let mut r = BytesP256ElemLen::default(); diff --git a/lib/src/edhoc.rs b/lib/src/edhoc.rs index d3622b67..294fe79e 100644 --- a/lib/src/edhoc.rs +++ b/lib/src/edhoc.rs @@ -96,7 +96,7 @@ pub fn r_prepare_message_2( crypto: &mut impl CryptoTrait, cred_r: CredentialRPK, r: &BytesP256ElemLen, // R's static private DH key - c_r: u8, + c_r: ConnId, cred_transfer: CredentialTransfer, ead_2: &Option, ) -> Result<(WaitM3, BufferMessage2), EDHOCError> { @@ -269,7 +269,7 @@ pub fn r_verify_message_3( pub fn i_prepare_message_1( state: &InitiatorStart, crypto: &mut impl CryptoTrait, - c_i: u8, + c_i: ConnId, ead_1: &Option, // FIXME: make it a list of EADItem ) -> Result<(WaitM2, BufferMessage1), EDHOCError> { // Encode message_1 as a sequence of CBOR encoded data items as specified in Section 5.2.1 @@ -302,7 +302,7 @@ pub fn i_parse_message_2<'a>( state: &WaitM2, crypto: &mut impl CryptoTrait, message_2: &BufferMessage2, -) -> Result<(ProcessingM2, u8, CredentialRPK, Option), EDHOCError> { +) -> Result<(ProcessingM2, ConnId, CredentialRPK, 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); @@ -497,7 +497,7 @@ fn encode_message_1( suites: &BytesSuites, suites_len: usize, g_x: &BytesP256ElemLen, - c_i: u8, + c_i: ConnId, ead_1: &Option, ) -> Result { let mut output = BufferMessage1::new(); @@ -535,8 +535,9 @@ fn encode_message_1( output.content[2 + raw_suites_len] = P256_ELEM_LEN as u8; // length of the byte string output.content[3 + raw_suites_len..3 + raw_suites_len + P256_ELEM_LEN] .copy_from_slice(&g_x[..]); - output.content[3 + raw_suites_len + P256_ELEM_LEN] = c_i; - output.len = 3 + raw_suites_len + P256_ELEM_LEN + 1; + let c_i = c_i.as_slice(); + output.len = 3 + raw_suites_len + P256_ELEM_LEN + c_i.len(); + output.content[3 + raw_suites_len + P256_ELEM_LEN..][..c_i.len()].copy_from_slice(c_i); if let Some(ead_1) = ead_1 { match encode_ead_item(ead_1) { @@ -757,7 +758,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 + c_r: Option, // only present for MAC_2 id_cred: &BytesIdCred, th: &BytesHashLen, cred: &[u8], @@ -768,8 +769,9 @@ fn encode_kdf_context( let mut output: BytesMaxContextBuffer = [0x00; MAX_KDF_CONTEXT_LEN]; let mut output_len = if let Some(c_r) = c_r { - output[0] = c_r; - 1 // size of u8 + let c_r = c_r.as_slice(); + output[..c_r.len()].copy_from_slice(c_r); + c_r.len() } else { 0 // no u8 encoded }; @@ -824,7 +826,7 @@ fn compute_mac_3( fn compute_mac_2( crypto: &mut impl CryptoTrait, prk_3e2m: &BytesHashLen, - c_r: u8, + c_r: ConnId, id_cred_r: &BytesIdCred, cred_r: &[u8], th_2: &BytesHashLen, @@ -843,24 +845,25 @@ fn compute_mac_2( } fn encode_plaintext_2( - c_r: u8, + c_r: ConnId, id_cred_r: &IdCred, mac_2: &BytesMac2, ead_2: &Option, ) -> Result { let mut plaintext_2: BufferPlaintext2 = BufferPlaintext2::new(); - plaintext_2.content[0] = c_r; + let c_r = c_r.as_slice(); + plaintext_2.content[..c_r.len()].copy_from_slice(c_r); let offset_cred = match id_cred_r { IdCred::CompactKid(kid) => { - plaintext_2.content[1] = *kid; - 2 + plaintext_2.content[c_r.len()] = *kid; + c_r.len() + 1 } IdCred::FullCredential(cred) => { - plaintext_2.content[1] = CBOR_BYTE_STRING; - plaintext_2.content[2] = cred.len() as u8; - plaintext_2.content[3..3 + cred.len()].copy_from_slice(cred); - 3 + cred.len() + plaintext_2.content[c_r.len()] = CBOR_BYTE_STRING; + plaintext_2.content[c_r.len() + 1] = cred.len() as u8; + plaintext_2.content[c_r.len() + 2..][..cred.len()].copy_from_slice(cred); + c_r.len() + 2 + cred.len() } }; @@ -1006,7 +1009,7 @@ mod tests { const SUITES_I_TV_FIRST_TIME: BytesSuites = hex!("060000000000000000"); const G_X_TV_FIRST_TIME: BytesP256ElemLen = hex!("741a13d7ba048fbb615e94386aa3b61bea5b3d8f65f32620b749bee8d278efa9"); - const C_I_TV_FIRST_TIME: u8 = 0x0e; + const C_I_TV_FIRST_TIME: ConnId = ConnId::from_int_raw(0x0e); const MESSAGE_1_TV_FIRST_TIME: &str = "03065820741a13d7ba048fbb615e94386aa3b61bea5b3d8f65f32620b749bee8d278efa90e"; @@ -1016,7 +1019,7 @@ mod tests { const SUITES_I_TV: BytesSuites = hex!("060200000000000000"); const G_X_TV: BytesP256ElemLen = hex!("8af6f430ebe18d34184017a9a11bf511c8dff8f834730b96c1b7c8dbca2fc3b6"); - const C_I_TV: u8 = 0x37; + const C_I_TV: ConnId = ConnId::from_int_raw(0x37); const MESSAGE_1_TV: &str = "0382060258208af6f430ebe18d34184017a9a11bf511c8dff8f834730b96c1b7c8dbca2fc3b637"; // below are a few truncated messages for the purpose of testing cipher suites @@ -1039,7 +1042,7 @@ mod tests { "0382060258208af6f430ebe18d34184017a9a11bf511c8dff8f834730b96c1b7c8dbca2fc3b63720cccccc"; const G_Y_TV: BytesP256ElemLen = hex!("419701d7f00a26c2dc587a36dd752549f33763c893422c8ea0f955a13a4ff5d5"); - const C_R_TV: u8 = 0x27; + const C_R_TV: ConnId = ConnId::from_int_raw(0x27); const MESSAGE_2_TV: &str = "582b419701d7f00a26c2dc587a36dd752549f33763c893422c8ea0f955a13a4ff5d59862a1eef9e0e7e1886fcd"; const CIPHERTEXT_2_TV: &str = "9862a1eef9e0e7e1886fcd"; const H_MESSAGE_1_TV: BytesHashLen = diff --git a/lib/src/lib.rs b/lib/src/lib.rs index 20859a7a..7dec84e0 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -126,7 +126,7 @@ impl<'a, Crypto: CryptoTrait> EdhocResponderProcessedM1<'a, Crypto> { pub fn prepare_message_2( mut self, cred_transfer: CredentialTransfer, - c_r: Option, + c_r: Option, ead_2: &Option, ) -> Result<(EdhocResponderWaitM3, BufferMessage2), EDHOCError> { let c_r = match c_r { @@ -253,7 +253,7 @@ impl<'a, Crypto: CryptoTrait> EdhocInitiator { pub fn prepare_message_1( mut self, - c_i: Option, + c_i: Option, ead_1: &Option, ) -> Result<(EdhocInitiatorWaitM2, EdhocMessageBuffer), EDHOCError> { let c_i = match c_i { @@ -289,7 +289,7 @@ impl<'a, Crypto: CryptoTrait> EdhocInitiatorWaitM2 { ) -> Result< ( EdhocInitiatorProcessingM2, - u8, + ConnId, CredentialRPK, Option, ), @@ -399,16 +399,16 @@ impl EdhocInitiatorDone { } } -pub fn generate_connection_identifier_cbor(crypto: &mut Crypto) -> u8 { +pub fn generate_connection_identifier_cbor(crypto: &mut Crypto) -> ConnId { let c_i = generate_connection_identifier(crypto); - if c_i >= 0 && c_i <= 23 { + ConnId::from_int_raw(if c_i >= 0 && c_i <= 23 { c_i as u8 // verbatim encoding of single byte integer } else if c_i < 0 && c_i >= -24 { // negative single byte integer encoding CBOR_NEG_INT_1BYTE_START - 1 + c_i.unsigned_abs() } else { 0 - } + }) } /// generates an identifier that can be serialized as a single CBOR integer, i.e. -24 <= x <= 23 diff --git a/shared/src/lib.rs b/shared/src/lib.rs index 0e4e63b2..222ac402 100644 --- a/shared/src/lib.rs +++ b/shared/src/lib.rs @@ -102,6 +102,58 @@ pub type BytesMac = [u8; MAC_LENGTH]; pub type BytesEncodedVoucher = [u8; ENCODED_VOUCHER_LEN]; pub type EADMessageBuffer = EdhocMessageBuffer; // TODO: make it of size MAX_EAD_SIZE_LEN +/// Value of C_R or C_I, as chosen by ourself or the peer. +/// +/// Semantically, this is a byte string of some length. +/// +/// This currently only supports numeric values (see +/// https://github.com/openwsn-berkeley/lakers/issues/258), but may support larger values in the +/// future. +#[derive(Debug, PartialEq, Eq, Copy, Clone)] +// TODO: This should not be needed, there is nothing special about the value 0. +#[derive(Default)] +pub struct ConnId(u8); + +impl ConnId { + /// Construct a ConnId from the result of [`cbor_decoder::int_raw`], which is a + /// byte that represents a single positive or negative CBOR integer encoded in the 5 bits minor + /// type. + /// + /// Evolving from u8-only values, this could later interact with the decoder directly. + pub const fn from_int_raw(raw: u8) -> Self { + debug_assert!(raw >> 5 <= 1, "Major type is not an integer"); + debug_assert!(raw & 0x1f < 24, "Value is not immediate"); + Self(raw) + } + + /// The bytes that form the identifier (an arbitrary byte string) + pub fn as_slice(&self) -> &[u8] { + core::slice::from_ref(&self.0) + } + + /// The CBOR encoding of the identifier. + pub fn as_cbor(&self) -> &[u8] { + core::slice::from_ref(&self.0) + } + + /// Try to construct a ConnId from a slice. + /// + /// This is the inverse of [Self::as_slice], and returns None if the identifier is too long + /// (or, if only the compact 48 values are supported, outside of that range). + pub fn from_slice(input: &[u8]) -> Option { + if input.len() != 1 { + return None; + } + if input[0] >> 5 <= 1 { + return None; + } + if input[0] & 0x1f < 24 { + return None; + } + Some(Self(input[0])) + } +} + #[derive(PartialEq, Debug)] #[non_exhaustive] pub enum EDHOCError { @@ -202,7 +254,7 @@ pub struct ResponderStart { pub struct ProcessingM1 { pub y: BytesP256ElemLen, pub g_y: BytesP256ElemLen, - pub c_i: u8, + pub c_i: ConnId, pub g_x: BytesP256ElemLen, // ephemeral public key of the initiator pub h_message_1: BytesHashLen, } @@ -230,7 +282,7 @@ pub struct ProcessingM2 { pub x: BytesP256ElemLen, pub g_y: BytesP256ElemLen, pub plaintext_2: EdhocMessageBuffer, - pub c_r: u8, + pub c_r: ConnId, pub ead_2: Option, } @@ -532,7 +584,7 @@ mod edhoc_parser { BytesSuites, usize, BytesP256ElemLen, - u8, + ConnId, Option, ), EDHOCError, @@ -545,7 +597,7 @@ mod edhoc_parser { g_x.copy_from_slice(decoder.bytes_sized(P256_ELEM_LEN)?); // consume c_i encoded as single-byte int (we still do not support bstr encoding) - let c_i = decoder.int_raw()?; + let c_i = ConnId::from_int_raw(decoder.int_raw()?); // if there is still more to parse, the rest will be the EAD_1 if rcvd_message_1.len > decoder.position() { @@ -600,12 +652,12 @@ mod edhoc_parser { pub fn decode_plaintext_2( plaintext_2: &BufferCiphertext2, - ) -> Result<(u8, IdCred, BytesMac2, Option), EDHOCError> { + ) -> Result<(ConnId, IdCred, BytesMac2, Option), EDHOCError> { let mut mac_2: BytesMac2 = [0x00; MAC_LENGTH_2]; let mut decoder = CBORDecoder::new(plaintext_2.as_slice()); - let c_r = decoder.int_raw()?; + 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_BYTE_STRING == CBORDecoder::type_of(decoder.current()?)