diff --git a/.gitignore b/.gitignore index bf6e3d0a..8648d64b 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,5 @@ cscope* .vscode *.log + +results/ \ No newline at end of file diff --git a/examples/coap/src/bin/coapclient.rs b/examples/coap/src/bin/coapclient.rs index ae80c7e8..c52403ca 100644 --- a/examples/coap/src/bin/coapclient.rs +++ b/examples/coap/src/bin/coapclient.rs @@ -65,8 +65,14 @@ fn client_handshake() -> Result<(), EDHOCError> { msg_3.extend_from_slice(message_3.as_slice()); println!("message_3 len = {}", msg_3.len()); - let _response = CoAPClient::post_with_timeout(url, msg_3, timeout).unwrap(); - // we don't care about the response to message_3 for now + let response = CoAPClient::post_with_timeout(url, msg_3, timeout).unwrap(); + if response.get_status() != &ResponseType::Changed { + panic!("Message 3 response error: {:?}", response.get_status()); + } + println!("response_vec = {:02x?}", response.message.payload); + println!("message_3 len = {}", response.message.payload.len()); + let message_4 = EdhocMessageBuffer::new_from_slice(&response.message.payload[..]).unwrap(); + let (mut initiator, ead_4) = initiator.process_message_4(&message_4).unwrap(); println!("EDHOC exchange successfully completed"); println!("PRK_out: {:02x?}", prk_out); diff --git a/examples/coap/src/bin/coapserver-coaphandler.rs b/examples/coap/src/bin/coapserver-coaphandler.rs index 895982ec..6982e4c9 100644 --- a/examples/coap/src/bin/coapserver-coaphandler.rs +++ b/examples/coap/src/bin/coapserver-coaphandler.rs @@ -182,12 +182,12 @@ impl coap_handler::Handler for EdhocHandler { .expect("Static credential is not processable"); let valid_cred_i = credential_check_or_fetch(Some(cred_i), id_cred_i).map_err(render_error)?; - let (mut responder, prk_out) = - responder.verify_message_3(valid_cred_i).map_err(|e| { - println!("EDHOC processing error: {:?}", e); - render_error(e) - })?; + let (responder, prk_out) = responder.verify_message_3(valid_cred_i).map_err(|e| { + println!("EDHOC processing error: {:?}", e); + render_error(e) + })?; + let (mut responder, message_4) = responder.prepare_message_4(&None).unwrap(); println!("EDHOC exchange successfully completed"); println!("PRK_out: {:02x?}", prk_out); diff --git a/examples/coap/src/bin/coapserver.rs b/examples/coap/src/bin/coapserver.rs index a114dc67..1fa8fd23 100644 --- a/examples/coap/src/bin/coapserver.rs +++ b/examples/coap/src/bin/coapserver.rs @@ -112,9 +112,9 @@ fn main() { println!("EDHOC error at verify_message_3: {:?}", valid_cred_i); continue; }; - + let (mut responder, message_4) = responder.prepare_message_4(&None).unwrap(); // send empty ack back - response.message.payload = b"".to_vec(); + response.message.payload = Vec::from(message_4.as_slice()); println!("EDHOC exchange successfully completed"); println!("PRK_out: {:02x?}", prk_out); diff --git a/examples/lakers-no_std/src/main.rs b/examples/lakers-no_std/src/main.rs index 9df603a4..8d32440f 100644 --- a/examples/lakers-no_std/src/main.rs +++ b/examples/lakers-no_std/src/main.rs @@ -132,13 +132,16 @@ fn main() -> ! { .unwrap(); // exposing own identity only after validating cred_r let initiator = initiator.verify_message_2(valid_cred_r).unwrap(); - let (mut initiator, message_3, i_prk_out) = initiator + let (initiator, message_3, i_prk_out) = initiator .prepare_message_3(CredentialTransfer::ByReference, &None) .unwrap(); let (responder, id_cred_i, _ead_3) = responder.parse_message_3(&message_3).unwrap(); let valid_cred_i = credential_check_or_fetch(Some(cred_i), id_cred_i).unwrap(); - let (mut responder, r_prk_out) = responder.verify_message_3(valid_cred_i).unwrap(); + let (responder, r_prk_out) = responder.verify_message_3(valid_cred_i).unwrap(); + + let mut initiator = initiator.completed_without_message_4().unwrap(); + let mut responder = responder.completed_without_message_4().unwrap(); // check that prk_out is equal at initiator and responder side assert_eq!(i_prk_out, r_prk_out); diff --git a/examples/lakers-nrf52840/src/bin/initiator.rs b/examples/lakers-nrf52840/src/bin/initiator.rs index 1171b6c0..f5465a90 100644 --- a/examples/lakers-nrf52840/src/bin/initiator.rs +++ b/examples/lakers-nrf52840/src/bin/initiator.rs @@ -81,13 +81,15 @@ async fn main(spawner: Spawner) { let (initiator, message_1) = initiator.prepare_message_1(Some(c_i), &None).unwrap(); let pckt_1 = common::Packet::new_from_slice(message_1.as_slice(), Some(0xf5)) .expect("Buffer not long enough"); - let rcvd = common::transmit_and_wait_response(&mut radio, pckt_1, Some(0xf5)).await; match rcvd { Ok(pckt_2) => { + info!("Received message_2"); let message_2: EdhocMessageBuffer = + // starts in 1 to consider only the content and not the metadata pckt_2.pdu[1..pckt_2.len].try_into().expect("wrong length"); + info!("message_2 :{:?}", message_2.content); let (initiator, c_r, id_cred_r, ead_2) = initiator.parse_message_2(&message_2).unwrap(); let valid_cred_r = credential_check_or_fetch(Some(cred_r), id_cred_r).unwrap(); let initiator = initiator.verify_message_2(valid_cred_r).unwrap(); @@ -95,15 +97,27 @@ async fn main(spawner: Spawner) { let (mut initiator, message_3, i_prk_out) = initiator .prepare_message_3(CredentialTransfer::ByReference, &None) .unwrap(); - - common::transmit_without_response( - &mut radio, + let pckt_3 = common::Packet::new_from_slice(message_3.as_slice(), Some(c_r.as_slice()[0])) - .unwrap(), - ) - .await; - - info!("Handshake completed. prk_out = {:X}", i_prk_out); + .expect("Buffer not long enough"); + info!("Send message_3 and wait message_4"); + let rcvd = + common::transmit_and_wait_response(&mut radio, pckt_3, Some(c_r.as_slice()[0])) + .await; + + info!("Sent message_3"); + match rcvd { + Ok(pckt_4) => { + info!("Received message_4"); + let message_4: EdhocMessageBuffer = + pckt_4.pdu[1..pckt_4.len].try_into().expect("wrong length"); + + let (initiator, ead_4) = initiator.process_message_4(&message_4).unwrap(); + + info!("Handshake completed. prk_out = {:X}", i_prk_out); + } + Err(_) => panic!("parsing error"), + } } Err(_) => panic!("parsing error"), } diff --git a/examples/lakers-nrf52840/src/bin/responder.rs b/examples/lakers-nrf52840/src/bin/responder.rs index 9463364f..dda1ebdd 100644 --- a/examples/lakers-nrf52840/src/bin/responder.rs +++ b/examples/lakers-nrf52840/src/bin/responder.rs @@ -119,24 +119,34 @@ async fn main(spawner: Spawner) { // anyway legally continue; }; - - let cred_i = + let cred_i: Credential = Credential::parse_ccs(common::CRED_I.try_into().unwrap()).unwrap(); let valid_cred_i = credential_check_or_fetch(Some(cred_i), id_cred_i).unwrap(); - - let Ok((responder, prk_out)) = responder.verify_message_3(valid_cred_i) + let Ok((responder, r_prk_out)) = responder.verify_message_3(valid_cred_i) else { - info!("EDHOC error at verify_message_3"); + info!("EDHOC error at parse_message_3"); continue; }; - info!("Handshake completed. prk_out: {:X}", prk_out); - - unwrap!(spawner.spawn(example_application_task(prk_out))); + info!("Prepare message_4"); + let ead_4 = None; + let (responder, message_4) = responder.prepare_message_4(&ead_4).unwrap(); + + info!("Send message_4"); + common::transmit_without_response( + &mut radio, + common::Packet::new_from_slice( + message_4.as_slice(), + Some(c_r.unwrap().as_slice()[0]), + ) + .unwrap(), + ) + .await; + + info!("Handshake completed. prk_out = {:X}", r_prk_out); } else { info!("Another packet interrupted the handshake."); - continue; } } Err(PacketError::TimeoutError) => info!("Timeout while waiting for message_3!"), diff --git a/lakers-c/src/initiator.rs b/lakers-c/src/initiator.rs index 3d3802b3..6a2719d6 100644 --- a/lakers-c/src/initiator.rs +++ b/lakers-c/src/initiator.rs @@ -15,6 +15,7 @@ pub struct EdhocInitiator { pub wait_m2: WaitM2, pub processing_m2: ProcessingM2C, pub processed_m2: ProcessedM2, + pub wait_m4: WaitM4, pub cred_i: *mut CredentialC, pub completed: Completed, } @@ -181,7 +182,7 @@ pub unsafe extern "C" fn initiator_prepare_message_3( &ead_3, ) { Ok((state, msg_3, prk_out)) => { - (*initiator_c).completed = state; + (*initiator_c).wait_m4 = state; *message_3 = msg_3; *prk_out_c = prk_out; 0 @@ -190,6 +191,56 @@ pub unsafe extern "C" fn initiator_prepare_message_3( } } +#[no_mangle] +pub unsafe extern "C" fn initiator_process_message_4( + // input params + initiator_c: *mut EdhocInitiator, + message_4: *const EdhocMessageBuffer, + // output params + ead_4_c_out: *mut EADItemC, +) -> i8 { + // this is a parsing function, so all output parameters are mandatory + if initiator_c.is_null() || message_4.is_null() || ead_4_c_out.is_null() { + return -1; + } + let crypto = &mut default_crypto(); + + let mut state = core::ptr::read(&(*initiator_c).wait_m4); + + let result = match i_process_message_4(&mut state, crypto, &(*message_4)) { + Ok((state, ead_4)) => { + (*initiator_c).completed = state; + if let Some(ead_4) = ead_4 { + EADItemC::copy_into_c(ead_4, ead_4_c_out); + } + + 0 + } + Err(err) => err as i8, + }; + + result +} + +#[no_mangle] +pub unsafe extern "C" fn completed_without_message_4( + // input params + initiator_c: *mut EdhocInitiator, +) -> i8 { + if initiator_c.is_null() { + return -1; + } + let state = core::ptr::read(&(*initiator_c).wait_m4); + + match i_complete_without_message_4(&state) { + Ok(state) => { + (*initiator_c).completed = state; + 0 + } + Err(err) => err as i8, + } +} + #[no_mangle] pub unsafe extern "C" fn initiator_compute_ephemeral_secret( initiator_c: *const EdhocInitiator, diff --git a/lakers-python/README.md b/lakers-python/README.md index abc0eabd..c0fac05e 100644 --- a/lakers-python/README.md +++ b/lakers-python/README.md @@ -42,9 +42,8 @@ To deploy: ln -s ../examples ./examples MATURIN_PYPI_TOKEN= maturin publish ``` - +.take().ok_or(StateMismatch)?, ## Requirements - The maturin executable must be available. The recommended way is to install and use it in a virtual environment: ``` diff --git a/lakers-python/src/initiator.rs b/lakers-python/src/initiator.rs index 93760982..61238df1 100644 --- a/lakers-python/src/initiator.rs +++ b/lakers-python/src/initiator.rs @@ -15,6 +15,7 @@ pub struct PyEdhocInitiator { wait_m2: Option, processing_m2: Option, processed_m2: Option, + wait_m4: Option, completed: Option, } @@ -39,6 +40,7 @@ impl PyEdhocInitiator { wait_m2: None, processing_m2: None, processed_m2: None, + wait_m4: None, completed: None, } } @@ -131,7 +133,7 @@ impl PyEdhocInitiator { &ead_3, ) { Ok((state, message_3, prk_out)) => { - self.completed = Some(state); + self.wait_m4 = Some(state); Ok(( PyBytes::new_bound(py, message_3.as_slice()), PyBytes::new_bound(py, prk_out.as_slice()), @@ -141,6 +143,36 @@ impl PyEdhocInitiator { } } + pub fn completed_without_message_4<'a>(&mut self, py: Python<'a>) -> PyResult<()> { + match i_complete_without_message_4(&self.wait_m4.take().ok_or(StateMismatch)?) { + Ok(state) => { + self.completed = Some(state); + Ok(()) + } + Err(error) => Err(error.into()), + } + } + + pub fn process_message_4<'a>( + &mut self, + py: Python<'a>, + message_4: Vec, + ) -> PyResult> { + let message_4 = EdhocMessageBuffer::new_from_slice(message_4.as_slice())?; + + match i_process_message_4( + &mut self.wait_m4.take().ok_or(StateMismatch)?, + &mut default_crypto(), + &message_4, + ) { + Ok((state, ead_4)) => { + self.completed = Some(state); + Ok(ead_4) + } + Err(error) => Err(error.into()), + } + } + pub fn edhoc_exporter<'a>( &mut self, py: Python<'a>, diff --git a/lakers-python/src/responder.rs b/lakers-python/src/responder.rs index f70e430e..bb218bd1 100644 --- a/lakers-python/src/responder.rs +++ b/lakers-python/src/responder.rs @@ -13,6 +13,7 @@ pub struct PyEdhocResponder { processing_m1: Option, wait_m3: Option, processing_m3: Option, + processed_m3: Option, completed: Option, } @@ -36,6 +37,7 @@ impl PyEdhocResponder { processing_m1: None, wait_m3: None, processing_m3: None, + processed_m3: None, completed: None, }) } @@ -125,13 +127,42 @@ impl PyEdhocResponder { valid_cred_i, ) { Ok((state, prk_out)) => { - self.completed = Some(state); + self.processed_m3 = Some(state); Ok(PyBytes::new_bound(py, prk_out.as_slice())) } Err(error) => Err(error.into()), } } + #[pyo3(signature = (ead_4=None))] + fn prepare_message_4<'a>( + &mut self, + py: Python<'a>, + ead_4: Option, + ) -> PyResult> { + match r_prepare_message_4( + &self.processed_m3.take().ok_or(StateMismatch)?, + &mut default_crypto(), + &ead_4, + ) { + Ok((state, message_4)) => { + self.completed = Some(state); + Ok(PyBytes::new_bound(py, message_4.as_slice())) + } + Err(error) => Err(error.into()), + } + } + + pub fn completed_without_message_4<'a>(&mut self, py: Python<'a>) -> PyResult<()> { + match r_complete_without_message_4(&self.processed_m3.take().ok_or(StateMismatch)?) { + Ok(state) => { + self.completed = Some(state); + Ok(()) + } + Err(error) => Err(error.into()), + } + } + pub fn edhoc_exporter<'a>( &mut self, py: Python<'a>, diff --git a/lakers-python/test/test_lakers.py b/lakers-python/test/test_lakers.py index 9dc93715..281987ac 100644 --- a/lakers-python/test/test_lakers.py +++ b/lakers-python/test/test_lakers.py @@ -66,9 +66,13 @@ def _test_handshake(cred_r_transfer, cred_i_transfer): assert ead_3 == None valid_cred_i = lakers.credential_check_or_fetch(id_cred_i, CRED_I) r_prk_out = responder.verify_message_3(valid_cred_i) + message_4 = responder.prepare_message_4(None) assert i_prk_out == r_prk_out + # initiator + ead_4 = initiator.process_message_4(message_4) + i_oscore_secret = initiator.edhoc_exporter(0, [], 16) i_oscore_salt = initiator.edhoc_exporter(1, [], 8) r_oscore_secret = responder.edhoc_exporter(0, [], 16) diff --git a/lib/src/edhoc.rs b/lib/src/edhoc.rs index 0b53c586..730aee24 100644 --- a/lib/src/edhoc.rs +++ b/lib/src/edhoc.rs @@ -184,7 +184,7 @@ pub fn r_verify_message_3( state: &mut ProcessingM3, crypto: &mut impl CryptoTrait, valid_cred_i: Credential, -) -> Result<(Completed, BytesHashLen), EDHOCError> { +) -> Result<(ProcessedM3, BytesHashLen), EDHOCError> { // compute salt_4e3m let salt_4e3m = compute_salt_4e3m(crypto, &state.prk_3e2m, &state.th_3); @@ -243,9 +243,11 @@ pub fn r_verify_message_3( prk_exporter[..SHA256_DIGEST_LEN].copy_from_slice(&prk_exporter_buf[..SHA256_DIGEST_LEN]); Ok(( - Completed { - prk_out, - prk_exporter, + ProcessedM3 { + prk_4e3m: prk_4e3m, + th_4: th_4, + prk_out: prk_out, + prk_exporter: prk_exporter, }, prk_out, )) @@ -254,6 +256,31 @@ pub fn r_verify_message_3( } } +pub fn r_prepare_message_4( + state: &ProcessedM3, + crypto: &mut impl CryptoTrait, + ead_4: &Option, // FIXME: make it a list of EADItem +) -> Result<(Completed, BufferMessage4), EDHOCError> { + // compute ciphertext_4 + let plaintext_4 = encode_plaintext_4(&ead_4)?; + let message_4 = encrypt_message_4(crypto, &state.prk_4e3m, &state.th_4, &plaintext_4); + + Ok(( + Completed { + prk_out: state.prk_out, + prk_exporter: state.prk_exporter, + }, + message_4, + )) +} + +pub fn r_complete_without_message_4(state: &ProcessedM3) -> Result { + Ok(Completed { + prk_out: state.prk_out, + prk_exporter: state.prk_exporter, + }) +} + pub fn i_prepare_message_1( state: &InitiatorStart, crypto: &mut impl CryptoTrait, @@ -377,7 +404,7 @@ pub fn i_prepare_message_3( cred_i: Credential, cred_transfer: CredentialTransfer, ead_3: &Option, // FIXME: make it a list of EADItem -) -> Result<(Completed, BufferMessage3, BytesHashLen), EDHOCError> { +) -> Result<(WaitM4, BufferMessage3, BytesHashLen), EDHOCError> { let id_cred_i = match cred_transfer { CredentialTransfer::ByValue => cred_i.by_value()?, CredentialTransfer::ByReference => cred_i.by_kid()?, @@ -427,15 +454,45 @@ pub fn i_prepare_message_3( prk_exporter[..SHA256_DIGEST_LEN].copy_from_slice(&prk_exporter_buf[..SHA256_DIGEST_LEN]); Ok(( - Completed { - prk_out, - prk_exporter, + WaitM4 { + prk_4e3m: state.prk_4e3m, + th_4: th_4, + prk_out: prk_out, + prk_exporter: prk_exporter, }, message_3, prk_out, )) } +pub fn i_process_message_4( + state: &mut WaitM4, + crypto: &mut impl CryptoTrait, + message_4: &BufferMessage4, +) -> Result<(Completed, Option), EDHOCError> { + let plaintext_4 = decrypt_message_4(crypto, &state.prk_4e3m, &state.th_4, &message_4)?; + let decoded_p4_res = decode_plaintext_4(&plaintext_4); + + if let Ok(ead_4) = decoded_p4_res { + Ok(( + Completed { + prk_out: state.prk_out, + prk_exporter: state.prk_exporter, + }, + ead_4, + )) + } else { + Err(decoded_p4_res.unwrap_err()) + } +} + +pub fn i_complete_without_message_4(state: &WaitM4) -> Result { + Ok(Completed { + prk_out: state.prk_out, + prk_exporter: state.prk_exporter, + }) +} + fn encode_ead_item(ead_1: &EADItem) -> Result { let mut output = EdhocMessageBuffer::new(); @@ -642,6 +699,22 @@ fn encode_plaintext_3( } } +fn encode_plaintext_4(ead_4: &Option) -> Result { + let mut plaintext_4: BufferPlaintext4 = BufferPlaintext4::new(); + + if let Some(ead_4) = ead_4 { + match encode_ead_item(ead_4) { + Ok(ead_4) => plaintext_4 + .extend_from_slice(ead_4.as_slice()) + .and(Ok(plaintext_4)) + .or(Err(EDHOCError::EadTooLongError)), + Err(e) => Err(e), + } + } else { + Ok(plaintext_4) + } +} + fn encode_enc_structure(th_3: &BytesHashLen) -> BytesEncStructureLen { let mut encrypt0: Bytes8 = [0x00; 8]; encrypt0[0] = 0x45u8; // 'E' @@ -694,6 +767,33 @@ fn compute_k_3_iv_3( (k_3, iv_3) } +fn compute_k_4_iv_4( + crypto: &mut impl CryptoTrait, + prk_4e3m: &BytesHashLen, + th_4: &BytesHashLen, +) -> (BytesCcmKeyLen, BytesCcmIvLen) { + // K_4 = EDHOC-KDF( PRK_4e3m, ?? , TH_4, key_length ) + let mut k_4: BytesCcmKeyLen = [0x00; AES_CCM_KEY_LEN]; + let mut th_4_buf: BytesMaxContextBuffer = [0x00; MAX_KDF_CONTEXT_LEN]; + th_4_buf[..th_4.len()].copy_from_slice(&th_4[..]); + let k_4_buf = edhoc_kdf( + crypto, + prk_4e3m, + 8u8, // FIXME + &th_4_buf, + th_4.len(), + AES_CCM_KEY_LEN, + ); + k_4[..].copy_from_slice(&k_4_buf[..AES_CCM_KEY_LEN]); + + // IV_3 = EDHOC-KDF( PRK_4e3m, ?? , TH_4, iv_length ) + let mut iv_4: BytesCcmIvLen = [0x00; AES_CCM_IV_LEN]; + let iv_4_buf = edhoc_kdf(crypto, prk_4e3m, 9u8, &th_4_buf, th_4.len(), AES_CCM_IV_LEN); + iv_4[..].copy_from_slice(&iv_4_buf[..AES_CCM_IV_LEN]); + + (k_4, iv_4) +} + // calculates ciphertext_3 wrapped in a cbor byte string fn encrypt_message_3( crypto: &mut impl CryptoTrait, @@ -763,6 +863,74 @@ fn decrypt_message_3( crypto.aes_ccm_decrypt_tag_8(&k_3, &iv_3, &enc_structure, &ciphertext_3) } +fn encrypt_message_4( + crypto: &mut impl CryptoTrait, + prk_4e3m: &BytesHashLen, + th_4: &BytesHashLen, + plaintext_4: &BufferPlaintext4, +) -> BufferMessage4 { + let mut output: BufferMessage4 = BufferMessage4::new(); + let bytestring_length = plaintext_4.len + AES_CCM_TAG_LEN; + let prefix_length; + // FIXME: Reuse CBOR encoder + if bytestring_length < 24 { + output.content[0] = CBOR_MAJOR_BYTE_STRING | (bytestring_length) as u8; + prefix_length = 1; + } else { + // FIXME: Assumes we don't exceed 256 bytes which is the current buffer size + output.content[0] = CBOR_MAJOR_BYTE_STRING | 24; + output.content[1] = bytestring_length as _; + prefix_length = 2; + }; + output.len = prefix_length + bytestring_length; + // FIXME: Make the function fallible, especially with the prospect of algorithm agility + assert!( + output.len <= MAX_MESSAGE_SIZE_LEN, + "Tried to encode a message that is too large." + ); + + let enc_structure = encode_enc_structure(th_4); + + let (k_4, iv_4) = compute_k_4_iv_4(crypto, prk_4e3m, th_4); + + let ciphertext_4 = crypto.aes_ccm_encrypt_tag_8(&k_4, &iv_4, &enc_structure[..], plaintext_4); + + output.content[prefix_length..][..ciphertext_4.len].copy_from_slice(ciphertext_4.as_slice()); + + output +} + +fn decrypt_message_4( + crypto: &mut impl CryptoTrait, + prk_4e3m: &BytesHashLen, + th_4: &BytesHashLen, + message_4: &BufferMessage4, +) -> Result { + // decode message_4 + let bytestring_length: usize; + let prefix_length; + // FIXME: Reuse CBOR decoder + if (0..=23).contains(&(message_4.content[0] ^ CBOR_MAJOR_BYTE_STRING)) { + bytestring_length = (message_4.content[0] ^ CBOR_MAJOR_BYTE_STRING).into(); + prefix_length = 1; + } else { + // FIXME: Assumes we don't exceed 256 bytes which is the current buffer size + bytestring_length = message_4.content[1].into(); + prefix_length = 2; + } + + let mut ciphertext_4: BufferCiphertext4 = BufferCiphertext4::new(); + ciphertext_4.len = bytestring_length; + ciphertext_4.content[..bytestring_length] + .copy_from_slice(&message_4.content[prefix_length..][..bytestring_length]); + + let (k_4, iv_4) = compute_k_4_iv_4(crypto, prk_4e3m, th_4); + + let enc_structure = encode_enc_structure(th_4); + + crypto.aes_ccm_decrypt_tag_8(&k_4, &iv_4, &enc_structure, &ciphertext_4) +} + // output must hold id_cred.len() + cred.len() fn encode_kdf_context( c_r: Option, // only present for MAC_2 @@ -1069,6 +1237,11 @@ mod tests { const MESSAGE_3_TV: &str = "52e562097bc417dd5919485ac7891ffd90a9fc"; const PRK_4E3M_TV: BytesP256ElemLen = hex!("81cc8a298e357044e3c466bb5c0a1e507e01d49238aeba138df94635407c0ff7"); + const MESSAGE_4_TV: &str = "4828c966b7ca304f83"; + const CIPHERTEXT_4_TV: &str = "28c966b7ca304f83"; + const PLAINTEXT_4_TV: &str = ""; + const K_4_TV: BytesCcmKeyLen = hex!("d3c77872b6eeb508911bdbd308b2e6a0"); + const IV_4_TV: BytesCcmIvLen = hex!("04ff0f44456e96e217853c3601"); const CRED_I_TV : [u8; 107] = hex!("a2027734322d35302d33312d46462d45462d33372d33322d333908a101a5010202412b2001215820ac75e9ece3e50bfc8ed60399889522405c47bf16df96660a41298cb4307f7eb62258206e5de611388a4b8a8211334ac7d37ecb52a387d257e6db3c2a93df21ff3affc8"); const ID_CRED_R_TV: BytesIdCred = hex!("a1044132"); const CRED_R_TV : [u8; 95] = hex!("a2026b6578616d706c652e65647508a101a501020241322001215820bbc34960526ea4d32e940cad2a234148ddc21791a12afbcbac93622046dd44f02258204519e257236b2a0ce2023f0931f1f386ca7afda64fcde0108c224c51eabf6072"); @@ -1434,6 +1607,48 @@ mod tests { } } + #[test] + fn test_decode_plaintext_4() { + let plaintext_4_tv = BufferPlaintext2::from_hex(PLAINTEXT_4_TV); + + let plaintext_4 = decode_plaintext_4(&plaintext_4_tv); + assert!(plaintext_4.is_ok()); + let (ead_4) = plaintext_4.unwrap(); + assert!(ead_4.is_none()); + } + + #[test] + fn test_encrypt_message_4() { + let plaintext_4_tv = BufferPlaintext4::from_hex(PLAINTEXT_4_TV); + let message_4_tv = BufferMessage4::from_hex(MESSAGE_4_TV); + + let message_4 = encrypt_message_4( + &mut default_crypto(), + &PRK_4E3M_TV, + &TH_4_TV, + &plaintext_4_tv, + ); + assert_eq!(message_4, message_4_tv); + } + + #[test] + fn test_decrypt_message_4() { + let plaintext_4_tv = BufferPlaintext4::from_hex(PLAINTEXT_4_TV); + let message_4_tv = BufferMessage3::from_hex(MESSAGE_4_TV); + + let plaintext_4 = + decrypt_message_4(&mut default_crypto(), &PRK_4E3M_TV, &TH_4_TV, &message_4_tv); + assert!(plaintext_4.is_ok()); + assert_eq!(plaintext_4.unwrap(), plaintext_4_tv); + } + + #[test] + fn test_compute_k_4_iv_4() { + let (k_4, iv_4) = compute_k_4_iv_4(&mut default_crypto(), &PRK_4E3M_TV, &TH_4_TV); + assert_eq!(k_4, K_4_TV); + assert_eq!(iv_4, IV_4_TV); + } + #[test] fn test_compute_prk_4e3m() { let prk_4e3m = compute_prk_4e3m(&mut default_crypto(), &SALT_4E3M_TV, &SK_I_TV, &G_Y_TV); diff --git a/lib/src/lib.rs b/lib/src/lib.rs index 4e0a8139..2162fe94 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -56,6 +56,12 @@ pub struct EdhocInitiatorProcessedM2 { crypto: Crypto, } +#[derive(Debug)] +pub struct EdhocInitiatorWaitM4 { + state: WaitM4, // opaque state + crypto: Crypto, +} + #[derive(Debug)] pub struct EdhocInitiatorDone { state: Completed, @@ -91,6 +97,12 @@ pub struct EdhocResponderProcessingM3 { crypto: Crypto, } +#[derive(Debug)] +pub struct EdhocResponderProcessedM3 { + state: ProcessedM3, // opaque state + crypto: Crypto, +} + #[derive(Debug)] pub struct EdhocResponderDone { state: Completed, @@ -197,11 +209,11 @@ impl<'a, Crypto: CryptoTrait> EdhocResponderProcessingM3 { pub fn verify_message_3( mut self, cred_i: Credential, - ) -> Result<(EdhocResponderDone, [u8; SHA256_DIGEST_LEN]), EDHOCError> { + ) -> Result<(EdhocResponderProcessedM3, [u8; SHA256_DIGEST_LEN]), EDHOCError> { trace!("Enter verify_message_3"); match r_verify_message_3(&mut self.state, &mut self.crypto, cred_i) { Ok((state, prk_out)) => Ok(( - EdhocResponderDone { + EdhocResponderProcessedM3 { state, crypto: self.crypto, }, @@ -212,6 +224,36 @@ impl<'a, Crypto: CryptoTrait> EdhocResponderProcessingM3 { } } +impl EdhocResponderProcessedM3 { + pub fn prepare_message_4( + mut self, + ead_4: &Option, + ) -> Result<(EdhocResponderDone, BufferMessage4), EDHOCError> { + trace!("Enter prepare_message_4"); + match r_prepare_message_4(&self.state, &mut self.crypto, ead_4) { + Ok((state, message_4)) => Ok(( + EdhocResponderDone { + state, + crypto: self.crypto, + }, + message_4, + )), + Err(error) => Err(error), + } + } + + pub fn completed_without_message_4(self) -> Result, EDHOCError> { + trace!("Enter completed"); + match r_complete_without_message_4(&self.state) { + Ok(state) => Ok(EdhocResponderDone { + state, + crypto: self.crypto, + }), + Err(error) => Err(error), + } + } +} + impl EdhocResponderDone { pub fn edhoc_exporter( &mut self, @@ -374,7 +416,7 @@ impl<'a, Crypto: CryptoTrait> EdhocInitiatorProcessedM2 { ead_3: &Option, ) -> Result< ( - EdhocInitiatorDone, + EdhocInitiatorWaitM4, BufferMessage3, [u8; SHA256_DIGEST_LEN], ), @@ -392,7 +434,7 @@ impl<'a, Crypto: CryptoTrait> EdhocInitiatorProcessedM2 { ead_3, ) { Ok((state, message_3, prk_out)) => Ok(( - EdhocInitiatorDone { + EdhocInitiatorWaitM4 { state, crypto: self.crypto, }, @@ -404,6 +446,36 @@ impl<'a, Crypto: CryptoTrait> EdhocInitiatorProcessedM2 { } } +impl<'a, Crypto: CryptoTrait> EdhocInitiatorWaitM4 { + pub fn process_message_4( + mut self, + message_4: &'a BufferMessage4, + ) -> Result<(EdhocInitiatorDone, Option), EDHOCError> { + trace!("Enter parse_message_4"); + match i_process_message_4(&mut self.state, &mut self.crypto, message_4) { + Ok((state, ead_4)) => Ok(( + EdhocInitiatorDone { + state: state, + crypto: self.crypto, + }, + ead_4, + )), + Err(error) => Err(error), + } + } + + pub fn completed_without_message_4(self) -> Result, EDHOCError> { + trace!("Enter completed"); + match i_complete_without_message_4(&self.state) { + Ok(state) => Ok(EdhocResponderDone { + state, + crypto: self.crypto, + }), + Err(error) => Err(error), + } + } +} + impl EdhocInitiatorDone { pub fn edhoc_exporter( &mut self, @@ -656,10 +728,15 @@ mod test { // ---- begin responder handling let (responder, id_cred_i, _ead_3) = responder.parse_message_3(&message_3).unwrap(); let valid_cred_i = credential_check_or_fetch(Some(cred_i), id_cred_i).unwrap(); - // if ead_3: process ead_3 let (mut responder, r_prk_out) = responder.verify_message_3(valid_cred_i).unwrap(); + + // Send message_4 + let (mut responder, message_4) = responder.prepare_message_4(&None).unwrap(); // ---- end responder handling + let (mut initiator, ead_4) = initiator.process_message_4(&message_4).unwrap(); + // ---- end initiator handling + // check that prk_out is equal at initiator and responder side assert_eq!(i_prk_out, r_prk_out); @@ -793,18 +870,19 @@ mod test_authz { .unwrap(); let initiator = initiator.verify_message_2(valid_cred_r).unwrap(); - let (mut _initiator, message_3, i_prk_out) = initiator + let (mut initiator, message_3, i_prk_out) = initiator .prepare_message_3(CredentialTransfer::ByReference, &None) .unwrap(); - + let _initiator = initiator.completed_without_message_4(); let (responder, id_cred_i, _ead_3) = responder.parse_message_3(&message_3).unwrap(); let valid_cred_i = if id_cred_i.reference_only() { mock_fetch_cred_i(id_cred_i).unwrap() } else { id_cred_i.get_ccs().unwrap() }; - let (mut _responder, r_prk_out) = responder.verify_message_3(valid_cred_i).unwrap(); + let (mut responder, r_prk_out) = responder.verify_message_3(valid_cred_i).unwrap(); + let mut _responder = responder.completed_without_message_4(); // check that prk_out is equal at initiator and responder side assert_eq!(i_prk_out, r_prk_out); } diff --git a/shared/cbindgen.toml b/shared/cbindgen.toml index ff11b562..04a17b74 100644 --- a/shared/cbindgen.toml +++ b/shared/cbindgen.toml @@ -21,6 +21,6 @@ include = [ "EdhocBuffer", "BufferKid", "BufferCred", "BufferIdCred", "CredentialKey", "CredentialType", "IdCred", "CredentialTransfer", - "InitiatorStart", "WaitM2", "Completed", "ProcessedM2", + "InitiatorStart", "WaitM2", "Completed", "ProcessedM2", "WaitM4", "EdhocInitiatorC", "EdhocInitiatorWaitM2C", "EdhocInitiatorProcessingM2C", "EdhocInitiatorProcessedM2C", "EdhocInitiatorDoneC", ] diff --git a/shared/src/lib.rs b/shared/src/lib.rs index d900924f..b7155f0f 100644 --- a/shared/src/lib.rs +++ b/shared/src/lib.rs @@ -111,12 +111,15 @@ pub type BytesCcmKeyLen = [u8; AES_CCM_KEY_LEN]; pub type BytesCcmIvLen = [u8; AES_CCM_IV_LEN]; pub type BufferPlaintext2 = EdhocMessageBuffer; pub type BufferPlaintext3 = EdhocMessageBuffer; +pub type BufferPlaintext4 = EdhocMessageBuffer; pub type BytesMac2 = [u8; MAC_LENGTH_2]; pub type BytesMac3 = [u8; MAC_LENGTH_3]; pub type BufferMessage1 = EdhocMessageBuffer; pub type BufferMessage3 = EdhocMessageBuffer; +pub type BufferMessage4 = EdhocMessageBuffer; pub type BufferCiphertext2 = EdhocMessageBuffer; pub type BufferCiphertext3 = EdhocMessageBuffer; +pub type BufferCiphertext4 = EdhocMessageBuffer; pub type BytesHashLen = [u8; SHA256_DIGEST_LEN]; pub type BytesP256ElemLen = [u8; P256_ELEM_LEN]; pub type BufferMessage2 = EdhocMessageBuffer; @@ -472,7 +475,7 @@ pub struct ProcessingM3 { pub ead_3: Option, } -#[derive(Debug)] +#[derive(Default, Debug)] pub struct PreparingM3 { pub prk_3e2m: BytesHashLen, pub prk_4e3m: BytesHashLen, @@ -480,6 +483,23 @@ pub struct PreparingM3 { pub mac_3: BytesMac3, } +#[derive(Default, Debug)] +pub struct ProcessedM3 { + pub prk_4e3m: BytesHashLen, + pub th_4: BytesHashLen, + pub prk_out: BytesHashLen, + pub prk_exporter: BytesHashLen, +} + +#[derive(Default, Debug)] +#[repr(C)] +pub struct WaitM4 { + pub prk_4e3m: BytesHashLen, + pub th_4: BytesHashLen, + pub prk_out: BytesHashLen, + pub prk_exporter: BytesHashLen, +} + #[derive(Default, Debug)] #[repr(C)] pub struct Completed { @@ -876,6 +896,27 @@ mod edhoc_parser { Err(EDHOCError::ParsingError) } } + + pub fn decode_plaintext_4( + plaintext_4: &BufferPlaintext4, + ) -> Result, EDHOCError> { + trace!("Enter decode_plaintext_4"); + let decoder = CBORDecoder::new(plaintext_4.as_slice()); + + if plaintext_4.len > decoder.position() { + // assume only one EAD item + let ead_res = parse_ead(decoder.remaining_buffer()?); + if let Ok(ead_4) = ead_res { + Ok(ead_4) + } else { + Err(ead_res.unwrap_err()) + } + } else if decoder.finished() { + Ok(None) + } else { + Err(EDHOCError::ParsingError) + } + } } mod cbor_decoder {