diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index dc8d74dc..74601c8c 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -21,15 +21,12 @@ jobs: - name: Check if code is well formatted run: cargo fmt --check - unit-tests: # run before build because it is faster + unit-tests-default: needs: check-style runs-on: ubuntu-latest strategy: fail-fast: false - matrix: - crypto_backend: [lakers-crypto/hacspec, lakers-crypto/psa, lakers-crypto/rustcrypto] - ead: [ead-none, ead-zeroconf] steps: - name: Checkout repo @@ -38,6 +35,21 @@ jobs: - name: Run unit tests with default features run: RUST_BACKTRACE=1 cargo test + + unit-tests: + needs: check-style + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + crypto_backend: [lakers-crypto/hacspec, lakers-crypto/psa, lakers-crypto/rustcrypto] + ead: [ead-none, ead-zeroconf] + + steps: + - name: Checkout repo + uses: actions/checkout@v3 + - name: Run unit tests with feature matrix # note that we only add `--package lakers-ead-zeroconf` when testing with that config run: RUST_BACKTRACE=1 cargo test -p lakers -p lakers-crypto -p lakers-shared ${{ matrix.ead == 'ead-zeroconf' && '-p lakers-ead-zeroconf' || '' }} --no-default-features --features="${{ matrix.crypto_backend }}, ${{ matrix.ead }}" --no-fail-fast -- --test-threads 1 @@ -66,7 +78,6 @@ jobs: generate-fstar: - needs: unit-tests runs-on: ubuntu-latest steps: diff --git a/Cargo.toml b/Cargo.toml index 55edda54..37f549b5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,6 +46,7 @@ lakers-shared = { package = "lakers-shared", path = "shared/", version = "^0.4.1 lakers-ead = { package = "lakers-ead-dispatch", path = "ead/", version = "^0.4.1", default-features = false } lakers-ead-none = { package = "lakers-ead-none", path = "ead/lakers-ead-none/", version = "^0.4.1" } lakers-ead-zeroconf = { package = "lakers-ead-zeroconf", path = "ead/lakers-ead-zeroconf/", version = "^0.4.1" } +lakers-ead-authz = { package = "lakers-ead-authz", path = "ead/lakers-ead-authz/", version = "^0.4.1" } lakers-crypto = { path = "crypto/" } lakers-crypto-cc2538 = { path = "crypto/lakers-crypto-cc2538/" } diff --git a/ead/Cargo.toml b/ead/Cargo.toml index 30100d50..f9b319ef 100644 --- a/ead/Cargo.toml +++ b/ead/Cargo.toml @@ -13,8 +13,10 @@ lakers-shared.workspace = true lakers-ead-none = { workspace = true, optional = true } lakers-ead-zeroconf = { workspace = true, optional = true } +lakers-ead-authz = { workspace = true, optional = true } [features] default = [ "ead-none" ] ead-none = [ "lakers-ead-none" ] ead-zeroconf = [ "lakers-ead-zeroconf" ] +ead-authz = [ "lakers-ead-authz" ] diff --git a/ead/lakers-ead-authz/src/authenticator.rs b/ead/lakers-ead-authz/src/authenticator.rs index fe42d755..efdb8a06 100644 --- a/ead/lakers-ead-authz/src/authenticator.rs +++ b/ead/lakers-ead-authz/src/authenticator.rs @@ -1,5 +1,5 @@ use super::shared::*; -use lakers_shared::{Crypto as CryptoTrait, *}; +use lakers_shared::*; #[derive(Debug, Default)] pub struct ZeroTouchAuthenticator; @@ -113,7 +113,6 @@ fn parse_voucher_response( mod test_authenticator { use super::*; use crate::test_vectors::*; - use lakers_crypto::default_crypto; #[test] fn test_parse_ead_1_value() { @@ -184,7 +183,6 @@ mod test_authenticator { mod test_responder_stateless_operation { use super::*; use crate::test_vectors::*; - use lakers_crypto::default_crypto; #[test] fn test_slo_encode_voucher_request() { diff --git a/ead/lakers-ead-authz/src/device.rs b/ead/lakers-ead-authz/src/device.rs index c3d8e99a..b59da79c 100644 --- a/ead/lakers-ead-authz/src/device.rs +++ b/ead/lakers-ead-authz/src/device.rs @@ -11,6 +11,7 @@ pub struct ZeroTouchDevice { #[derive(Debug)] pub struct ZeroTouchDeviceWaitEAD2 { prk: BytesHashLen, + pub h_message_1: BytesHashLen, } #[derive(Debug)] @@ -43,17 +44,26 @@ impl ZeroTouchDevice { value, }; - (ead_1, ZeroTouchDeviceWaitEAD2 { prk }) + ( + ead_1, + ZeroTouchDeviceWaitEAD2 { + prk, + h_message_1: [0; SHA256_DIGEST_LEN], + }, + ) } } impl ZeroTouchDeviceWaitEAD2 { + pub fn set_h_message_1(&mut self, h_message_1: BytesHashLen) { + self.h_message_1 = h_message_1; + } + pub fn process_ead_2( &self, crypto: &mut Crypto, ead_2: EADItem, cred_v: &[u8], - h_message_1: &BytesHashLen, ) -> Result { if ead_2.label != EAD_ZEROCONF_LABEL || ead_2.value.is_none() { return Err(()); @@ -61,7 +71,7 @@ impl ZeroTouchDeviceWaitEAD2 { let mut ead_2_value: BytesEncodedVoucher = Default::default(); ead_2_value[..].copy_from_slice(&ead_2.value.unwrap().content[..ENCODED_VOUCHER_LEN]); - match verify_voucher(crypto, &ead_2_value, h_message_1, cred_v, &self.prk) { + match verify_voucher(crypto, &ead_2_value, &self.h_message_1, cred_v, &self.prk) { Ok(voucher) => Ok(ZeroTouchDeviceDone { voucher }), Err(_) => Err(()), } @@ -179,13 +189,13 @@ mod test_device { let ead_device = ZeroTouchDeviceWaitEAD2 { prk: PRK_TV.try_into().unwrap(), + h_message_1: H_MESSAGE_1_TV.try_into().unwrap(), }; let res = ead_device.process_ead_2( &mut default_crypto(), ead_2_tv, CRED_V_TV.try_into().unwrap(), - &H_MESSAGE_1_TV.try_into().unwrap(), ); assert!(res.is_ok()); let ead_device = res.unwrap(); diff --git a/ead/lakers-ead-authz/src/lib.rs b/ead/lakers-ead-authz/src/lib.rs index 9e8d7f8e..c58dbca0 100644 --- a/ead/lakers-ead-authz/src/lib.rs +++ b/ead/lakers-ead-authz/src/lib.rs @@ -18,6 +18,7 @@ mod test_authz { test_vectors::*, }; use lakers_crypto::default_crypto; + use lakers_shared::EDHOCError; #[test] fn test_complete_flow() { @@ -35,8 +36,9 @@ mod test_authz { // using .unwrap below since detailed errors are tested in each entity's tests - let (ead_1, device) = + let (ead_1, mut device) = device.prepare_ead_1(&mut default_crypto(), &X_TV.try_into().unwrap(), SS_TV); + device.set_h_message_1(H_MESSAGE_1_TV.try_into().unwrap()); // ead_1 will be transported within message_1 @@ -54,12 +56,34 @@ mod test_authz { // ead_2 will be transported within message_2 - let result = device.process_ead_2( - &mut default_crypto(), - ead_2, - CRED_V_TV, - H_MESSAGE_1_TV.try_into().unwrap(), - ); + let result = device.process_ead_2(&mut default_crypto(), ead_2, CRED_V_TV); assert!(result.is_ok()); } + + #[test] + fn test_complete_flow_unauthorized() { + let device = ZeroTouchDevice::new( + ID_U_TV.try_into().unwrap(), + G_W_TV.try_into().unwrap(), + LOC_W_TV.try_into().unwrap(), + ); + let authenticator = ZeroTouchAuthenticator::default(); + let server = ZeroTouchServer::new( + W_TV.try_into().unwrap(), + CRED_V_TV.try_into().unwrap(), + Some(ACL_INVALID_TV.try_into().unwrap()), + ); + + let (ead_1, mut device) = + device.prepare_ead_1(&mut default_crypto(), &X_TV.try_into().unwrap(), SS_TV); + device.set_h_message_1(H_MESSAGE_1_TV.try_into().unwrap()); + + let (_loc_w, voucher_request, _authenticator) = authenticator + .process_ead_1(&ead_1, &MESSAGE_1_WITH_EAD_TV.try_into().unwrap()) + .unwrap(); + + let voucher_response = + server.handle_voucher_request(&mut default_crypto(), &voucher_request); + assert_eq!(voucher_response.unwrap_err(), EDHOCError::EADError); + } } diff --git a/ead/src/lib.rs b/ead/src/lib.rs index 4c1b1148..5c717db3 100644 --- a/ead/src/lib.rs +++ b/ead/src/lib.rs @@ -5,3 +5,6 @@ pub use lakers_ead_none::*; #[cfg(feature = "ead-zeroconf")] pub use lakers_ead_zeroconf::*; + +#[cfg(feature = "ead-authz")] +pub use lakers_ead_authz::*; diff --git a/examples/coap/src/bin/coapclient.rs b/examples/coap/src/bin/coapclient.rs index be464b64..c40af705 100644 --- a/examples/coap/src/bin/coapclient.rs +++ b/examples/coap/src/bin/coapclient.rs @@ -16,23 +16,24 @@ const CRED_R: &[u8] = &hex!("A2026008A101A5010202410A2001215820BBC34960526EA4D32 const _G_R: &[u8] = &hex!("bbc34960526ea4d32e940cad2a234148ddc21791a12afbcbac93622046dd44f0"); fn main() { + match client_handshake() { + Ok(_) => println!("Handshake completed"), + Err(e) => panic!("Handshake failed with error: {:?}", e), + } +} + +fn client_handshake() -> Result<(), EDHOCError> { let url = "coap://127.0.0.1:5683/.well-known/edhoc"; let timeout = Duration::new(5, 0); println!("Client request: {}", url); - let state = Default::default(); - let initiator = EdhocInitiator::new( - state, - lakers_crypto::default_crypto(), - &I, - &CRED_I, - Some(&CRED_R), - ); + let initiator = + EdhocInitiator::new(lakers_crypto::default_crypto(), &I, &CRED_I, Some(&CRED_R)); // Send Message 1 over CoAP and convert the response to byte let mut msg_1_buf = Vec::from([0xf5u8]); // EDHOC message_1 when transported over CoAP is prepended with CBOR true let c_i = generate_connection_identifier_cbor(&mut lakers_crypto::default_crypto()); - let (initiator, message_1) = initiator.prepare_message_1(c_i).unwrap(); + let (initiator, message_1) = initiator.prepare_message_1(Some(c_i), &None)?; msg_1_buf.extend_from_slice(message_1.as_slice()); println!("message_1 len = {}", msg_1_buf.len()); @@ -43,45 +44,43 @@ fn main() { println!("response_vec = {:02x?}", response.message.payload); println!("message_2 len = {}", response.message.payload.len()); - let m2result = initiator.process_message_2( - &response.message.payload[..] - .try_into() - .expect("wrong length"), - ); + let message_2 = EdhocMessageBuffer::new_from_slice(&response.message.payload[..]).unwrap(); + let (initiator, c_r, id_cred_r, _ead_2) = initiator.parse_message_2(&message_2)?; + let (valid_cred_r, _g_r) = + credential_check_or_fetch(Some(CRED_R.try_into().unwrap()), id_cred_r).unwrap(); + let initiator = initiator.verify_message_2(valid_cred_r.as_slice())?; - if let Ok((initiator, c_r)) = m2result { - let mut msg_3 = Vec::from([c_r]); - let (mut initiator, message_3, prk_out) = initiator.prepare_message_3().unwrap(); - msg_3.extend_from_slice(message_3.as_slice()); - println!("message_3 len = {}", msg_3.len()); + let mut msg_3 = Vec::from([c_r]); + let (mut initiator, message_3, prk_out) = initiator.prepare_message_3(&None)?; + 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(); + // we don't care about the response to message_3 for now - println!("EDHOC exchange successfully completed"); - println!("PRK_out: {:02x?}", prk_out); + println!("EDHOC exchange successfully completed"); + println!("PRK_out: {:02x?}", prk_out); - let mut _oscore_secret = initiator.edhoc_exporter(0u8, &[], 16); // label is 0 - let mut _oscore_salt = initiator.edhoc_exporter(1u8, &[], 8); // label is 1 + let mut oscore_secret = initiator.edhoc_exporter(0u8, &[], 16); // label is 0 + let mut oscore_salt = initiator.edhoc_exporter(1u8, &[], 8); // label is 1 - println!("OSCORE secret: {:02x?}", _oscore_secret); - println!("OSCORE salt: {:02x?}", _oscore_salt); + println!("OSCORE secret: {:02x?}", oscore_secret); + println!("OSCORE salt: {:02x?}", oscore_salt); - // context of key update is a test vector from draft-ietf-lake-traces - let prk_out_new = initiator.edhoc_key_update(&[ - 0xa0, 0x11, 0x58, 0xfd, 0xb8, 0x20, 0x89, 0x0c, 0xd6, 0xbe, 0x16, 0x96, 0x02, 0xb8, - 0xbc, 0xea, - ]); + // context of key update is a test vector from draft-ietf-lake-traces + let prk_out_new = initiator.edhoc_key_update(&[ + 0xa0, 0x11, 0x58, 0xfd, 0xb8, 0x20, 0x89, 0x0c, 0xd6, 0xbe, 0x16, 0x96, 0x02, 0xb8, 0xbc, + 0xea, + ]); - println!("PRK_out after key update: {:02x?}?", prk_out_new); + println!("PRK_out after key update: {:02x?}?", prk_out_new); - // compute OSCORE secret and salt after key update - _oscore_secret = initiator.edhoc_exporter(0u8, &[], 16); // label is 0 - _oscore_salt = initiator.edhoc_exporter(1u8, &[], 8); // label is 1 + // compute OSCORE secret and salt after key update + oscore_secret = initiator.edhoc_exporter(0u8, &[], 16); // label is 0 + oscore_salt = initiator.edhoc_exporter(1u8, &[], 8); // label is 1 - println!("OSCORE secret after key update: {:02x?}", _oscore_secret); - println!("OSCORE salt after key update: {:02x?}", _oscore_salt); - } else { - panic!("Message 2 processing error: {:#?}", m2result); - } + println!("OSCORE secret after key update: {:02x?}", oscore_secret); + println!("OSCORE salt after key update: {:02x?}", oscore_salt); + + Ok(()) } diff --git a/examples/coap/src/bin/coapserver-coaphandler.rs b/examples/coap/src/bin/coapserver-coaphandler.rs index 148dfcf1..31291ee6 100644 --- a/examples/coap/src/bin/coapserver-coaphandler.rs +++ b/examples/coap/src/bin/coapserver-coaphandler.rs @@ -5,7 +5,7 @@ use lakers_crypto::Crypto; use embedded_nal::UdpFullStack; const _ID_CRED_I: &[u8] = &hex!("a104412b"); -const _ID_CRED_R: &[u8] = &hex!("a104410a"); +const ID_CRED_R: &[u8] = &hex!("a104410a"); const CRED_I: &[u8] = &hex!("A2027734322D35302D33312D46462D45462D33372D33322D333908A101A5010202412B2001215820AC75E9ECE3E50BFC8ED60399889522405C47BF16DF96660A41298CB4307F7EB62258206E5DE611388A4B8A8211334AC7D37ECB52A387D257E6DB3C2A93DF21FF3AFFC8"); const _G_I: &[u8] = &hex!("ac75e9ece3e50bfc8ed60399889522405c47bf16df96660a41298cb4307f7eb6"); const _G_I_Y_COORD: &[u8] = @@ -46,7 +46,7 @@ enum EdhocResponse { // take up a slot there anyway) if we make it an enum. OkSend2 { c_r: u8, - responder: EdhocResponderBuildM2<'static, Crypto>, + responder: EdhocResponderProcessedM1<'static, Crypto>, }, Message3Processed, } @@ -60,20 +60,13 @@ impl coap_handler::Handler for EdhocHandler { let starts_with_true = request.payload().get(0) == Some(&0xf5); if starts_with_true { - let state = EdhocState::default(); - - let responder = EdhocResponder::new( - state, - lakers_crypto::default_crypto(), - &R, - &CRED_R, - Some(&CRED_I), - ); + let responder = + EdhocResponder::new(lakers_crypto::default_crypto(), &R, &CRED_R, Some(&CRED_I)); let response = responder .process_message_1(&request.payload()[1..].try_into().expect("wrong length")); - if let Ok(responder) = response { + if let Ok((responder, _ead_1)) = response { let c_r = self.new_c_r(); EdhocResponse::OkSend2 { c_r, responder } } else { @@ -87,9 +80,17 @@ impl coap_handler::Handler for EdhocHandler { .expect("No such C_R found"); println!("Found state with connection identifier {:?}", c_r_rcvd); - let result = responder - .process_message_3(&request.payload()[1..].try_into().expect("wrong length")); + let message_3 = EdhocMessageBuffer::new_from_slice(&request.payload()[1..]).unwrap(); + let result = responder.parse_message_3(&message_3); + let Ok((responder, id_cred_i, _ead_3)) = result else { + println!("EDHOC processing error: {:?}", result); + // FIXME remove state from edhoc_connections + panic!("Handler can't just not respond"); + }; + let (valid_cred_i, _g_i) = + credential_check_or_fetch(Some(CRED_I.try_into().unwrap()), id_cred_i).unwrap(); + let result = responder.verify_message_3(valid_cred_i.as_slice()); let Ok((mut responder, prk_out)) = result else { println!("EDHOC processing error: {:?}", result); // FIXME remove state from edhoc_connections @@ -130,7 +131,9 @@ impl coap_handler::Handler for EdhocHandler { response.set_code(coap_numbers::code::CHANGED.try_into().ok().unwrap()); match req { EdhocResponse::OkSend2 { c_r, responder } => { - let (responder, message_2) = responder.prepare_message_2(c_r).unwrap(); + let kid = IdCred::CompactKid(ID_CRED_R[3]); + let (responder, message_2) = + responder.prepare_message_2(&kid, Some(c_r), &None).unwrap(); self.connections.push((c_r, responder)); response.set_payload(message_2.as_slice()); } diff --git a/examples/coap/src/bin/coapserver.rs b/examples/coap/src/bin/coapserver.rs index 9125a780..efd7591a 100644 --- a/examples/coap/src/bin/coapserver.rs +++ b/examples/coap/src/bin/coapserver.rs @@ -4,7 +4,7 @@ use hexlit::hex; use std::net::UdpSocket; const _ID_CRED_I: &[u8] = &hex!("a104412b"); -const _ID_CRED_R: &[u8] = &hex!("a104410a"); +const ID_CRED_R: &[u8] = &hex!("a104410a"); const CRED_I: &[u8] = &hex!("A2027734322D35302D33312D46462D45462D33372D33322D333908A101A5010202412B2001215820AC75E9ECE3E50BFC8ED60399889522405C47BF16DF96660A41298CB4307F7EB62258206E5DE611388A4B8A8211334AC7D37ECB52A387D257E6DB3C2A93DF21FF3AFFC8"); const _G_I: &[u8] = &hex!("ac75e9ece3e50bfc8ed60399889522405c47bf16df96660a41298cb4307f7eb6"); const _G_I_Y_COORD: &[u8] = @@ -31,9 +31,7 @@ fn main() { println!("Received message from {}", src); // This is an EDHOC message if request.message.payload[0] == 0xf5 { - let state = EdhocState::default(); let responder = EdhocResponder::new( - state, lakers_crypto::default_crypto(), &R, &CRED_R, @@ -46,10 +44,12 @@ fn main() { .expect("wrong length"), ); - if let Ok(responder) = result { + if let Ok((responder, _ead_1)) = result { let c_r = generate_connection_identifier_cbor(&mut lakers_crypto::default_crypto()); - let (responder, message_2) = responder.prepare_message_2(c_r).unwrap(); + let kid = IdCred::CompactKid(ID_CRED_R[3]); + let (responder, message_2) = + responder.prepare_message_2(&kid, Some(c_r), &None).unwrap(); response.message.payload = Vec::from(message_2.as_slice()); // save edhoc connection edhoc_connections.push((c_r, responder)); @@ -62,17 +62,23 @@ fn main() { let responder = take_state(c_r_rcvd, &mut edhoc_connections).unwrap(); println!("Found state with connection identifier {:?}", c_r_rcvd); - let result = responder.process_message_3( - &request.message.payload[1..] - .try_into() - .expect("wrong length"), - ); - let Ok((mut responder, prk_out)) = result else { - println!("EDHOC processing error: {:?}", response); + let message_3 = + EdhocMessageBuffer::new_from_slice(&request.message.payload[1..]).unwrap(); + let Ok((responder, id_cred_i, _ead_3)) = responder.parse_message_3(&message_3) + else { + println!("EDHOC processing error: {:?}", message_3); // We don't get another chance, it's popped and can't be used any further // anyway legally continue; }; + let (valid_cred_i, _g_i) = + credential_check_or_fetch(Some(CRED_I.try_into().unwrap()), id_cred_i).unwrap(); + let Ok((mut responder, prk_out)) = + responder.verify_message_3(valid_cred_i.as_slice()) + else { + println!("EDHOC processing error: {:?}", valid_cred_i); + continue; + }; // send empty ack back response.message.payload = b"".to_vec(); diff --git a/examples/edhoc-rs-no_std/src/main.rs b/examples/edhoc-rs-no_std/src/main.rs index 6328f03f..1e07a531 100644 --- a/examples/edhoc-rs-no_std/src/main.rs +++ b/examples/edhoc-rs-no_std/src/main.rs @@ -61,7 +61,7 @@ fn main() -> ! { use hexlit::hex; const _ID_CRED_I: &[u8] = &hex!("a104412b"); - const _ID_CRED_R: &[u8] = &hex!("a104410a"); + const ID_CRED_R: &[u8] = &hex!("a104410a"); const CRED_I: &[u8] = &hex!("A2027734322D35302D33312D46462D45462D33372D33322D333908A101A5010202412B2001215820AC75E9ECE3E50BFC8ED60399889522405C47BF16DF96660A41298CB4307F7EB62258206E5DE611388A4B8A8211334AC7D37ECB52A387D257E6DB3C2A93DF21FF3AFFC8"); const I: &[u8] = &hex!("fb13adeb6518cee5f88417660841142e830a81fe334380a953406a1305e8706b"); const R: &[u8] = &hex!("72cc4761dbd4c78f758931aa589d348d1ef874a7e303ede2f140dcf3e6aa4aac"); @@ -73,14 +73,8 @@ fn main() -> ! { const _C_R_TV: [u8; 1] = hex!("27"); fn test_new_initiator() { - let state = Default::default(); - let _initiator = EdhocInitiator::new( - state, - lakers_crypto::default_crypto(), - I, - CRED_I, - Some(CRED_R), - ); + let _initiator = + EdhocInitiator::new(lakers_crypto::default_crypto(), I, CRED_I, Some(CRED_R)); } test_new_initiator(); @@ -99,18 +93,12 @@ fn main() -> ! { println!("Test test_p256_keys passed."); fn test_prepare_message_1() { - let state = Default::default(); - let mut initiator = EdhocInitiator::new( - state, - lakers_crypto::default_crypto(), - I, - CRED_I, - Some(CRED_R), - ); + let mut initiator = + EdhocInitiator::new(lakers_crypto::default_crypto(), I, CRED_I, Some(CRED_R)); let c_i: u8 = generate_connection_identifier_cbor(&mut lakers_crypto::default_crypto()).into(); - let message_1 = initiator.prepare_message_1(c_i); + let message_1 = initiator.prepare_message_1(None, &None); assert!(message_1.is_ok()); } @@ -118,39 +106,29 @@ fn main() -> ! { println!("Test test_prepare_message_1 passed."); fn test_handshake() { - let state_initiator = Default::default(); - let mut initiator = EdhocInitiator::new( - state_initiator, - lakers_crypto::default_crypto(), - I, - CRED_I, - Some(CRED_R), - ); - let state_responder = Default::default(); - let responder = EdhocResponder::new( - state_responder, - lakers_crypto::default_crypto(), - R, - CRED_R, - Some(CRED_I), - ); + let mut initiator = + EdhocInitiator::new(lakers_crypto::default_crypto(), I, CRED_I, Some(CRED_R)); + let responder = + EdhocResponder::new(lakers_crypto::default_crypto(), R, CRED_R, Some(CRED_I)); - let c_i: u8 = - generate_connection_identifier_cbor(&mut lakers_crypto::default_crypto()).into(); - let (initiator, message_1) = initiator.prepare_message_1(c_i).unwrap(); // to update the state + let (initiator, message_1) = initiator.prepare_message_1(None, &None).unwrap(); - let responder = responder.process_message_1(&message_1).unwrap(); - - let c_r: u8 = - generate_connection_identifier_cbor(&mut lakers_crypto::default_crypto()).into(); - let (responder, message_2) = responder.prepare_message_2(c_r).unwrap(); - assert!(c_r != 0xff); + let (responder, _ead_1) = responder.process_message_1(&message_1).unwrap(); + let kid = IdCred::CompactKid(ID_CRED_R[3]); + let (responder, message_2) = responder.prepare_message_2(&kid, None, &None).unwrap(); - let (initiator, _c_r) = initiator.process_message_2(&message_2).unwrap(); + let (initiator, c_r, id_cred_r, ead_2) = initiator.parse_message_2(&message_2).unwrap(); + let (valid_cred_r, g_r) = + credential_check_or_fetch(Some(CRED_R.try_into().unwrap()), id_cred_r).unwrap(); + let initiator = initiator.verify_message_2(valid_cred_r.as_slice()).unwrap(); - let (mut initiator, message_3, i_prk_out) = initiator.prepare_message_3().unwrap(); + let (mut initiator, message_3, i_prk_out) = initiator.prepare_message_3(&None).unwrap(); - let (mut responder, r_prk_out) = responder.process_message_3(&message_3).unwrap(); + let (responder, id_cred_i, _ead_3) = responder.parse_message_3(&message_3).unwrap(); + let (valid_cred_i, g_i) = + credential_check_or_fetch(Some(CRED_I.try_into().unwrap()), id_cred_i).unwrap(); + let (mut responder, r_prk_out) = + responder.verify_message_3(valid_cred_i.as_slice()).unwrap(); // check that prk_out is equal at initiator and responder side assert_eq!(i_prk_out, r_prk_out); diff --git a/lib/Cargo.toml b/lib/Cargo.toml index b0ec956b..f7262bff 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -20,6 +20,7 @@ hexlit = "0.5.3" default = [ "lakers-ead/ead-none" ] ead-none = [ "lakers-ead/ead-none" ] ead-zeroconf = [ "lakers-ead/ead-zeroconf" ] +ead-authz = [ "lakers-ead/ead-authz" ] [lib] crate-type = ["rlib"] diff --git a/lib/src/edhoc.rs b/lib/src/edhoc.rs index 94418299..3e6fe60e 100644 --- a/lib/src/edhoc.rs +++ b/lib/src/edhoc.rs @@ -1,9 +1,7 @@ -use core::marker::PhantomData; -use lakers_ead::*; use lakers_shared::{Crypto as CryptoTrait, *}; pub fn edhoc_exporter( - state: &State, + state: &Completed, crypto: &mut impl CryptoTrait, label: u8, context: &BytesMaxContextBuffer, @@ -21,7 +19,7 @@ pub fn edhoc_exporter( } pub fn edhoc_key_update( - state: &mut State, + state: &mut Completed, crypto: &mut impl CryptoTrait, context: &BytesMaxContextBuffer, context_len: usize, @@ -56,47 +54,32 @@ pub fn edhoc_key_update( } pub fn r_process_message_1( - state: State, + state: ResponderStart, crypto: &mut impl CryptoTrait, message_1: &BufferMessage1, -) -> Result, EDHOCError> { +) -> Result<(ProcessingM1, Option), EDHOCError> { // Step 1: decode message_1 // g_x will be saved to the state - let res = parse_message_1(message_1); - - if let Ok((method, suites_i, suites_i_len, g_x, c_i, ead_1)) = res { + if let Ok((method, suites_i, suites_i_len, g_x, c_i, ead_1)) = parse_message_1(message_1) { // verify that the method is supported if method == EDHOC_METHOD { // Step 2: verify that the selected cipher suite is supported if suites_i[suites_i_len - 1] == EDHOC_SUPPORTED_SUITES[0] { - // Step 3: If EAD is present make it available to the application - let ead_success = if let Some(ead_1) = ead_1 { - r_process_ead_1(crypto, &ead_1, message_1).is_ok() - } else { - true - }; - if ead_success { - // hash message_1 and save the hash to the state to avoid saving the whole message - let mut message_1_buf: BytesMaxBuffer = [0x00; MAX_BUFFER_LEN]; - message_1_buf[..message_1.len].copy_from_slice(message_1.as_slice()); - let h_message_1 = crypto.sha256_digest(&message_1_buf, message_1.len); - - let state = State { - current_state: PhantomData, - x_or_y: state.x_or_y, + // hash message_1 and save the hash to the state to avoid saving the whole message + let mut message_1_buf: BytesMaxBuffer = [0x00; MAX_BUFFER_LEN]; + message_1_buf[..message_1.len].copy_from_slice(message_1.as_slice()); + let h_message_1 = crypto.sha256_digest(&message_1_buf, message_1.len); + + Ok(( + ProcessingM1 { + y: state.y, + g_y: state.g_y, c_i, - gy_or_gx: g_x, - prk_3e2m: state.prk_3e2m, - prk_4e3m: state.prk_4e3m, - prk_out: state.prk_out, - prk_exporter: state.prk_exporter, + g_x, h_message_1, - th_3: state.th_3, - }; - Ok(state) - } else { - Err(EDHOCError::EADError) - } + }, + ead_1, + )) } else { Err(EDHOCError::UnsupportedCipherSuite) } @@ -109,35 +92,25 @@ pub fn r_process_message_1( } pub fn r_prepare_message_2( - state: State, + state: ProcessingM1, crypto: &mut impl CryptoTrait, cred_r: &[u8], r: &BytesP256ElemLen, // R's static private DH key - y: BytesP256ElemLen, - g_y: BytesP256ElemLen, c_r: u8, -) -> Result<(State, BufferMessage2), EDHOCError> { + id_cred_r: &IdCred, + ead_2: &Option, +) -> Result<(WaitM3, BufferMessage2), EDHOCError> { // compute TH_2 - let th_2 = compute_th_2(crypto, &g_y, &state.h_message_1); + let th_2 = compute_th_2(crypto, &state.g_y, &state.h_message_1); // compute prk_3e2m - let prk_2e = compute_prk_2e(crypto, &y, &state.gy_or_gx, &th_2); + let prk_2e = compute_prk_2e(crypto, &state.y, &state.g_x, &th_2); let salt_3e2m = compute_salt_3e2m(crypto, &prk_2e, &th_2); - let prk_3e2m = compute_prk_3e2m(crypto, &salt_3e2m, r, &state.gy_or_gx); + let prk_3e2m = compute_prk_3e2m(crypto, &salt_3e2m, r, &state.g_x); // compute MAC_2 let mac_2 = compute_mac_2(crypto, &prk_3e2m, &get_id_cred(cred_r)?, cred_r, &th_2); - let ead_2 = r_prepare_ead_2(); - - let id_cred_r = if ead_2.is_some() { - // NOTE: assume EAD_2 is for zeroconf - IdCred::FullCredential(cred_r) - } else { - let (_g_r, kid) = parse_cred(cred_r)?; - IdCred::CompactKid(kid) - }; - // compute ciphertext_2 let plaintext_2 = encode_plaintext_2(c_r, id_cred_r, &mac_2, &ead_2)?; @@ -152,133 +125,51 @@ pub fn r_prepare_message_2( ct.fill_with_slice(ciphertext_2.as_slice()).unwrap(); // TODO(hax): same as just above. - let message_2 = encode_message_2(&g_y, &ct); - - let state = State { - current_state: PhantomData, - x_or_y: y, - c_i: state.c_i, - gy_or_gx: state.gy_or_gx, - prk_3e2m: prk_3e2m, - prk_4e3m: state.prk_4e3m, - prk_out: state.prk_out, - prk_exporter: state.prk_exporter, - h_message_1: state.h_message_1, - th_3: th_3, - }; + let message_2 = encode_message_2(&state.g_y, &ct); - Ok((state, message_2)) + Ok(( + WaitM3 { + y: state.y, + prk_3e2m: prk_3e2m, + th_3: th_3, + }, + message_2, + )) } // FIXME fetch ID_CRED_I and CRED_I based on kid -pub fn r_process_message_3( - state: &mut State, +pub fn r_parse_message_3( + state: &mut WaitM3, crypto: &mut impl CryptoTrait, message_3: &BufferMessage3, - cred_i_expected: Option<&[u8]>, -) -> Result<(State, BytesHashLen), EDHOCError> { +) -> Result<(ProcessingM3, IdCredOwned, 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 { - // The implementation currently supports the following two cases on handling the credentials: - // 1. R receives a kid and has a corresponding CRED_x passed in as cred_i_expected - // 2. R receives CRED_x by value in the message and uses it - // TODO: add support for fetching CRED_x based on kid received in the message - - let cred_i = credential_check_or_fetch(cred_i_expected, id_cred_i); - // IMPL: stop if credential_check_or_fetch returns Error - - if let Ok((valid_cred_i, g_i)) = cred_i { - // Phase 2: - // - Process EAD_X items that have not been processed yet, and that can be processed before message verification - // IMPL: we are sure valid_cred_i is a full credential - - // Step 3: If EAD is present make it available to the application - let ead_res = if let Some(ead_3) = ead_3 { - // IMPL: if EAD-zeroconf is present, then id_cred must contain a full credential - // at this point, in case of EAD = zeroconf, if it works it means that: - // - the Voucher has been verified - // - the received valid_cred_i (aka cred_i) has been authenticated - r_process_ead_3(ead_3) - } else { - Ok(()) - }; - - if ead_res.is_ok() { - // verify mac_3 - - // compute salt_4e3m - let salt_4e3m = compute_salt_4e3m(crypto, &state.prk_3e2m, &state.th_3); - // TODO compute prk_4e3m - let prk_4e3m = compute_prk_4e3m(crypto, &salt_4e3m, &state.x_or_y, &g_i); - - // compute mac_3 - let expected_mac_3 = compute_mac_3( - crypto, - &prk_4e3m, - &state.th_3, - &get_id_cred(valid_cred_i)?, - valid_cred_i, - ); - - // verify mac_3 - if mac_3 == expected_mac_3 { - let th_4 = compute_th_4(crypto, &state.th_3, &plaintext_3, valid_cred_i); - - let mut th_4_buf: BytesMaxContextBuffer = [0x00; MAX_KDF_CONTEXT_LEN]; - th_4_buf[..th_4.len()].copy_from_slice(&th_4[..]); - // compute prk_out - // PRK_out = EDHOC-KDF( PRK_4e3m, 7, TH_4, hash_length ) - let prk_out_buf = edhoc_kdf( - crypto, - &prk_4e3m, - 7u8, - &th_4_buf, - th_4.len(), - SHA256_DIGEST_LEN, - ); - let mut prk_out: BytesHashLen = Default::default(); - prk_out[..SHA256_DIGEST_LEN] - .copy_from_slice(&prk_out_buf[..SHA256_DIGEST_LEN]); - - // compute prk_exporter from prk_out - // PRK_exporter = EDHOC-KDF( PRK_out, 10, h'', hash_length ) - let prk_exporter_buf = edhoc_kdf( - crypto, - &prk_out, - 10u8, - &[0x00u8; MAX_KDF_CONTEXT_LEN], - 0, - SHA256_DIGEST_LEN, - ); - state.prk_exporter[..SHA256_DIGEST_LEN] - .copy_from_slice(&prk_exporter_buf[..SHA256_DIGEST_LEN]); - - let state = State { - current_state: PhantomData, - x_or_y: state.x_or_y, - c_i: state.c_i, - gy_or_gx: state.gy_or_gx, - prk_3e2m: state.prk_3e2m, - prk_4e3m: state.prk_4e3m, - prk_out: prk_out, - prk_exporter: state.prk_exporter, - h_message_1: state.h_message_1, - th_3: state.th_3, - }; - Ok((state, prk_out)) - } else { - Err(EDHOCError::MacVerificationFailed) - } - } else { - Err(EDHOCError::EADError) + let id_cred_i = match id_cred_i { + IdCred::CompactKid(kid) => IdCredOwned::CompactKid(kid), + IdCred::FullCredential(cred) => { + let Ok(buffer) = EdhocMessageBuffer::new_from_slice(cred) else { + return Err(EDHOCError::ParsingError); + }; + IdCredOwned::FullCredential(buffer) } - } else { - Err(cred_i.unwrap_err()) - } + }; + + Ok(( + ProcessingM3 { + mac_3, + y: state.y, + prk_3e2m: state.prk_3e2m, + th_3: state.th_3, + plaintext_3, // NOTE: this is needed for th_4, which needs valid_cred_i, which is only available at step 'b' + }, + id_cred_i, + ead_3, + )) } else { Err(decoded_p3_res.unwrap_err()) } @@ -288,22 +179,86 @@ pub fn r_process_message_3( } } -pub fn i_prepare_message_1( - state: State, +pub fn r_verify_message_3( + state: &mut ProcessingM3, crypto: &mut impl CryptoTrait, - x: BytesP256ElemLen, - g_x: BytesP256ElemLen, - c_i: u8, -) -> Result<(State, BufferMessage1), EDHOCError> { - // we only support a single cipher suite which is already CBOR-encoded - let mut suites_i: BytesSuites = [0x0; SUITES_LEN]; - let suites_i_len = EDHOC_SUPPORTED_SUITES.len(); - suites_i[0..suites_i_len].copy_from_slice(&EDHOC_SUPPORTED_SUITES[..]); + valid_cred_i: &[u8], +) -> Result<(Completed, BytesHashLen), EDHOCError> { + let (g_i, _kid_i) = parse_cred(valid_cred_i)?; - let ead_1 = i_prepare_ead_1(crypto, &x, suites_i[suites_i_len - 1]); + // compute salt_4e3m + let salt_4e3m = compute_salt_4e3m(crypto, &state.prk_3e2m, &state.th_3); + // TODO compute prk_4e3m + let prk_4e3m = compute_prk_4e3m(crypto, &salt_4e3m, &state.y, &g_i); + // compute mac_3 + let expected_mac_3 = compute_mac_3( + crypto, + &prk_4e3m, + &state.th_3, + &get_id_cred(valid_cred_i)?, + valid_cred_i, + ); + + // verify mac_3 + if state.mac_3 == expected_mac_3 { + let th_4 = compute_th_4(crypto, &state.th_3, &state.plaintext_3, valid_cred_i); + + let mut th_4_buf: BytesMaxContextBuffer = [0x00; MAX_KDF_CONTEXT_LEN]; + th_4_buf[..th_4.len()].copy_from_slice(&th_4[..]); + // compute prk_out + // PRK_out = EDHOC-KDF( PRK_4e3m, 7, TH_4, hash_length ) + let prk_out_buf = edhoc_kdf( + crypto, + &prk_4e3m, + 7u8, + &th_4_buf, + th_4.len(), + SHA256_DIGEST_LEN, + ); + let mut prk_out: BytesHashLen = Default::default(); + prk_out[..SHA256_DIGEST_LEN].copy_from_slice(&prk_out_buf[..SHA256_DIGEST_LEN]); + + // compute prk_exporter from prk_out + // PRK_exporter = EDHOC-KDF( PRK_out, 10, h'', hash_length ) + let prk_exporter_buf = edhoc_kdf( + crypto, + &prk_out, + 10u8, + &[0x00u8; MAX_KDF_CONTEXT_LEN], + 0, + SHA256_DIGEST_LEN, + ); + let mut prk_exporter = BytesHashLen::default(); + prk_exporter[..SHA256_DIGEST_LEN].copy_from_slice(&prk_exporter_buf[..SHA256_DIGEST_LEN]); + + Ok(( + Completed { + prk_out, + prk_exporter, + }, + prk_out, + )) + } else { + Err(EDHOCError::MacVerificationFailed) + } +} + +pub fn i_prepare_message_1( + state: InitiatorStart, + crypto: &mut impl CryptoTrait, + c_i: u8, + 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 - let message_1 = encode_message_1(EDHOC_METHOD, &suites_i, suites_i_len, &g_x, c_i, &ead_1)?; + let message_1 = encode_message_1( + EDHOC_METHOD, + &state.suites_i, + state.suites_i_len, + &state.g_x, + c_i, + ead_1, + )?; let mut message_1_buf: BytesMaxBuffer = [0x00; MAX_BUFFER_LEN]; message_1_buf[..message_1.len].copy_from_slice(message_1.as_slice()); @@ -311,38 +266,27 @@ pub fn i_prepare_message_1( // hash message_1 here to avoid saving the whole message in the state let h_message_1 = crypto.sha256_digest(&message_1_buf, message_1.len); - let state = State { - current_state: PhantomData, - x_or_y: x, - c_i: c_i, - gy_or_gx: state.gy_or_gx, - prk_3e2m: state.prk_3e2m, - prk_4e3m: state.prk_4e3m, - prk_out: state.prk_out, - prk_exporter: state.prk_exporter, - h_message_1: h_message_1, - th_3: state.th_3, - }; - - Ok((state, message_1)) + Ok(( + WaitM2 { + x: state.x, + h_message_1, + }, + message_1, + )) } // returns c_r -pub fn i_process_message_2( - state: State, +pub fn i_parse_message_2<'a>( + state: WaitM2, crypto: &mut impl CryptoTrait, message_2: &BufferMessage2, - cred_r_expected: Option<&[u8]>, - i: &BytesP256ElemLen, // I's static private DH key -) -> Result<(State, u8, u8), EDHOCError> { - let mut kid = 0xffu8; // invalidate kid - +) -> Result<(ProcessingM2, u8, IdCredOwned, 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); // compute prk_2e - let prk_2e = compute_prk_2e(crypto, &state.x_or_y, &g_y, &th_2); + let prk_2e = compute_prk_2e(crypto, &state.x, &g_y, &th_2); let plaintext_2 = encrypt_decrypt_ciphertext_2(crypto, &prk_2e, &th_2, ciphertext_2); @@ -350,73 +294,26 @@ pub fn i_process_message_2( let plaintext_2_decoded = decode_plaintext_2(&plaintext_2); if let Ok((c_r_2, id_cred_r, mac_2, ead_2)) = plaintext_2_decoded { - let c_r = c_r_2; - - let cred_r = credential_check_or_fetch(cred_r_expected, id_cred_r); - // IMPL: stop if credential_check_or_fetch returns Error - if let Ok((valid_cred_r, g_r)) = cred_r { - // Phase 2: - // - Process EAD_X items that have not been processed yet, and that can be processed before message verification - // IMPL: we are sure valid_cred_r is a full credential - - // Step 3: If EAD is present make it available to the application - let ead_res = if let Some(ead_2) = ead_2 { - // IMPL: if EAD-zeroconf is present, then id_cred must contain a full credential - // at this point, in case of EAD = zeroconf, if it works it means that: - // - the Voucher has been verified - // - the received valid_cred_r (aka cred_v) has been authenticated - i_process_ead_2(crypto, ead_2, valid_cred_r, &state.h_message_1) - } else { - Ok(()) - }; - - if ead_res.is_ok() { - // verify mac_2 - let salt_3e2m = compute_salt_3e2m(crypto, &prk_2e, &th_2); - - let prk_3e2m = compute_prk_3e2m(crypto, &salt_3e2m, &state.x_or_y, &g_r); - - let expected_mac_2 = compute_mac_2( - crypto, - &prk_3e2m, - &get_id_cred(valid_cred_r)?, - valid_cred_r, - &th_2, - ); - - if mac_2 == expected_mac_2 { - // step is actually from processing of message_3 - // but we do it here to avoid storing plaintext_2 in State - let th_3 = compute_th_3(crypto, &th_2, &plaintext_2, valid_cred_r); - // message 3 processing - - let salt_4e3m = compute_salt_4e3m(crypto, &prk_3e2m, &th_3); - - let prk_4e3m = compute_prk_4e3m(crypto, &salt_4e3m, i, &g_y); - - let state = State { - current_state: PhantomData, - x_or_y: state.x_or_y, - c_i: state.c_i, - gy_or_gx: state.gy_or_gx, - prk_3e2m: prk_3e2m, - prk_4e3m: prk_4e3m, - prk_out: state.prk_out, - prk_exporter: state.prk_exporter, - h_message_1: state.h_message_1, - th_3: th_3, - }; - - Ok((state, c_r, kid)) - } else { - Err(EDHOCError::MacVerificationFailed) - } - } else { - Err(EDHOCError::EADError) + let state = ProcessingM2 { + mac_2, + prk_2e, + th_2, + g_y, + plaintext_2: plaintext_2, + x: state.x, + }; + + let id_cred_r = match id_cred_r { + IdCred::CompactKid(kid) => IdCredOwned::CompactKid(kid), + IdCred::FullCredential(cred) => { + let Ok(buffer) = EdhocMessageBuffer::new_from_slice(cred) else { + return Err(EDHOCError::ParsingError); + }; + IdCredOwned::FullCredential(buffer) } - } else { - Err(cred_r.unwrap_err()) - } + }; + + Ok((state, c_r_2, id_cred_r, ead_2)) } else { Err(EDHOCError::ParsingError) } @@ -425,17 +322,63 @@ pub fn i_process_message_2( } } +pub fn i_verify_message_2( + state: ProcessingM2, + crypto: &mut impl CryptoTrait, + valid_cred_r: &[u8], // TODO: have a struct to hold credentials to avoid re-computing + i: &BytesP256ElemLen, // I's static private DH key +) -> Result { + // verify mac_2 + let salt_3e2m = compute_salt_3e2m(crypto, &state.prk_2e, &state.th_2); + + let (g_r, _) = parse_cred(valid_cred_r)?; + let prk_3e2m = compute_prk_3e2m(crypto, &salt_3e2m, &state.x, &g_r); + + let expected_mac_2 = compute_mac_2( + crypto, + &prk_3e2m, + &get_id_cred(valid_cred_r)?, + valid_cred_r, + &state.th_2, + ); + + if state.mac_2 == expected_mac_2 { + // step is actually from processing of message_3 + // but we do it here to avoid storing plaintext_2 in State + let th_3 = compute_th_3(crypto, &state.th_2, &state.plaintext_2, valid_cred_r); + // message 3 processing + + let salt_4e3m = compute_salt_4e3m(crypto, &prk_3e2m, &th_3); + + let prk_4e3m = compute_prk_4e3m(crypto, &salt_4e3m, i, &state.g_y); + + let state = ProcessedM2 { + prk_3e2m: prk_3e2m, + prk_4e3m: prk_4e3m, + th_3: th_3, + }; + + Ok(state) + } else { + Err(EDHOCError::MacVerificationFailed) + } +} + pub fn i_prepare_message_3( - state: &mut State, + state: &mut ProcessedM2, crypto: &mut impl CryptoTrait, - id_cred_i: &BytesIdCred, cred_i: &[u8], -) -> Result<(State, BufferMessage3, BytesHashLen), EDHOCError> { - let mac_3 = compute_mac_3(crypto, &state.prk_4e3m, &state.th_3, id_cred_i, cred_i); - - let ead_3 = i_prepare_ead_3(); + ead_3: &Option, // FIXME: make it a list of EADItem +) -> Result<(Completed, BufferMessage3, BytesHashLen), EDHOCError> { + let mac_3 = compute_mac_3( + crypto, + &state.prk_4e3m, + &state.th_3, + &get_id_cred(cred_i)?, + cred_i, + ); - let plaintext_3 = encode_plaintext_3(id_cred_i, &mac_3, &ead_3)?; + let plaintext_3 = encode_plaintext_3(&get_id_cred(cred_i)?, &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); @@ -466,86 +409,17 @@ pub fn i_prepare_message_3( 0, SHA256_DIGEST_LEN, ); - state.prk_exporter[..SHA256_DIGEST_LEN].copy_from_slice(&prk_exporter_buf[..SHA256_DIGEST_LEN]); - - let state = State { - current_state: PhantomData, - x_or_y: state.x_or_y, - c_i: state.c_i, - gy_or_gx: state.gy_or_gx, - prk_3e2m: state.prk_3e2m, - prk_4e3m: state.prk_4e3m, - prk_out: prk_out, - prk_exporter: state.prk_exporter, - h_message_1: state.h_message_1, - th_3: state.th_3, - }; - - Ok((state, message_3, prk_out)) -} - -// Implements auth credential checking according to draft-tiloca-lake-implem-cons -fn credential_check_or_fetch<'a>( - cred_expected: Option<&'a [u8]>, - id_cred_received: IdCred<'a>, -) -> Result<(&'a [u8], BytesP256ElemLen), EDHOCError> { - // Processing of auth credentials according to draft-tiloca-lake-implem-cons - // Comments tagged with a number refer to steps in Section 4.3.1. of draft-tiloca-lake-implem-cons - if let Some(cred_expected) = cred_expected { - // 1. Does ID_CRED_X point to a stored authentication credential? YES - // IMPL: compare cred_i_expected with id_cred - // IMPL: assume cred_i_expected is well formed - let (public_key_expected, kid_expected) = parse_cred(cred_expected)?; - let public_key = public_key_expected; - let credentials_match = match id_cred_received { - IdCred::CompactKid(kid_received) => kid_received == kid_expected, - IdCred::FullCredential(cred_received) => cred_expected == cred_received, - }; - - // 2. Is this authentication credential still valid? - // IMPL,TODO: check cred_r_expected is still valid - - // Continue by considering CRED_X as the authentication credential of the other peer. - // IMPL: ready to proceed, including process ead_2 - - if credentials_match { - Ok((cred_expected, public_key)) - } else { - Err(EDHOCError::UnknownPeer) - } - } else { - // 1. Does ID_CRED_X point to a stored authentication credential? NO - // IMPL: cred_i_expected provided by application is None - // id_cred must be a full credential - if let IdCred::FullCredential(cred_received) = id_cred_received { - // 3. Is the trust model Pre-knowledge-only? NO (hardcoded to NO for now) - - // 4. Is the trust model Pre-knowledge + TOFU? YES (hardcoded to YES for now) - - // 6. Validate CRED_X. Generally a CCS has to be validated only syntactically and semantically, unlike a certificate or a CWT. - // Is the validation successful? - // IMPL: parse_cred(cred_r) and check it is valid - match parse_cred(cred_received) { - Ok((public_key_received, _kid_received)) => { - // 5. Is the authentication credential authorized for use in the context of this EDHOC session? - // IMPL,TODO: we just skip this step for now - - // 7. Store CRED_X as valid and trusted. - // Pair it with consistent credential identifiers, for each supported type of credential identifier. - // IMPL: cred_r = id_cred - let public_key = public_key_received; - Ok((cred_received, public_key)) - } - Err(_) => Err(EDHOCError::UnknownPeer), - } - } else { - // IMPL: should have gotten a full credential - Err(EDHOCError::UnknownPeer) - } - } - - // 8. Is this authentication credential good to use in the context of this EDHOC session? - // IMPL,TODO: we just skip this step for now + let mut prk_exporter: BytesHashLen = Default::default(); + prk_exporter[..SHA256_DIGEST_LEN].copy_from_slice(&prk_exporter_buf[..SHA256_DIGEST_LEN]); + + Ok(( + Completed { + prk_out, + prk_exporter, + }, + message_3, + prk_out, + )) } fn encode_ead_item(ead_1: &EADItem) -> Result { @@ -911,7 +785,7 @@ fn compute_mac_2( fn encode_plaintext_2( c_r: u8, - id_cred_r: IdCred, + id_cred_r: &IdCred, mac_2: &BytesMac2, ead_2: &Option, ) -> Result { @@ -920,7 +794,7 @@ fn encode_plaintext_2( let offset_cred = match id_cred_r { IdCred::CompactKid(kid) => { - plaintext_2.content[1] = kid; + plaintext_2.content[1] = *kid; 2 } IdCred::FullCredential(cred) => { @@ -1102,8 +976,6 @@ mod tests { "0382060258208af6f430ebe18d34184017a9a11bf511c8dff8f834730b96c1b7c8dbca2fc3b63701cccccc"; const MESSAGE_1_WITH_DUMMY_CRITICAL_EAD_TV: &str = "0382060258208af6f430ebe18d34184017a9a11bf511c8dff8f834730b96c1b7c8dbca2fc3b63720cccccc"; - const PLAINTEXT_2_WITH_DUMMY_CRITICAL_EAD_TV: &str = "3248d0d1a594797d0aaf20cccccc"; - const PLAINTEXT_3_WITH_DUMMY_CRITICAL_EAD_TV: &str = "2b48ddf106b86fd22fe420cccccc"; const G_Y_TV: BytesP256ElemLen = hex!("419701d7f00a26c2dc587a36dd752549f33763c893422c8ea0f955a13a4ff5d5"); const C_R_TV: u8 = 0x27; @@ -1115,7 +987,6 @@ mod tests { hex!("356efd53771425e008f3fe3a86c83ff4c6b16e57028ff39d5236c182b202084b"); const TH_3_TV: BytesHashLen = hex!("dfe5b065e64c72d226d500c12d49bee6dc4881ded0965e9bdf89d24a54f2e59a"); - const CIPHERTEXT_3_TV: &str = "473dd16077dd71d65b56e6bd71e7a49d6012"; const TH_4_TV: BytesHashLen = hex!("baf60adbc500fce789af25b108ada2275575056c52c1c2036a2da4a643891cb4"); const PRK_2E_TV: BytesP256ElemLen = @@ -1201,7 +1072,7 @@ mod tests { let decoder = CBORDecoder::new(&message_1_tv.content[1..message_1_tv.len]); let res = parse_suites_i(decoder); assert!(res.is_ok()); - let (suites_i, suites_i_len, _decoder) = res.unwrap(); + let (suites_i, _suites_i_len, _decoder) = res.unwrap(); assert_eq!(suites_i, SUITES_I_TV); let message_1_tv = BufferMessage1::from_hex(MESSAGE_1_TV_SUITE_ONLY_A); @@ -1209,7 +1080,7 @@ mod tests { let decoder = CBORDecoder::new(&message_1_tv.content[1..message_1_tv.len]); let res = parse_suites_i(decoder); assert!(res.is_ok()); - let (suites_i, suites_i_len, _decoder) = res.unwrap(); + let (suites_i, _suites_i_len, _decoder) = res.unwrap(); assert_eq!(suites_i[0], 0x18); // let (suites_i, suites_i_len, raw_suites_len) = @@ -1250,7 +1121,7 @@ mod tests { // first time message_1 parsing let res = parse_message_1(&message_1_tv_first_time); assert!(res.is_ok()); - let (method, suites_i, suites_i_len, g_x, c_i, ead_1) = res.unwrap(); + let (method, suites_i, _suites_i_len, g_x, c_i, ead_1) = res.unwrap(); assert_eq!(method, METHOD_TV_FIRST_TIME); assert_eq!(suites_i, SUITES_I_TV_FIRST_TIME); @@ -1261,7 +1132,7 @@ mod tests { // second time message_1 let res = parse_message_1(&message_1_tv); assert!(res.is_ok()); - let (method, suites_i, suites_i_len, g_x, c_i, ead_1) = res.unwrap(); + let (method, suites_i, _suites_i_len, g_x, c_i, ead_1) = res.unwrap(); assert_eq!(method, METHOD_TV); assert_eq!(suites_i, SUITES_I_TV); @@ -1438,7 +1309,7 @@ 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::CompactKid(ID_CRED_R_TV[ID_CRED_R_TV.len() - 1]), &MAC_2_TV, &None::, ) @@ -1461,7 +1332,6 @@ mod tests { #[test] fn test_decode_plaintext_2() { let plaintext_2_tv = BufferPlaintext2::from_hex(PLAINTEXT_2_TV); - let ead_2_tv = [0x00u8; 0]; let plaintext_2 = decode_plaintext_2(&plaintext_2_tv); assert!(plaintext_2.is_ok()); @@ -1662,6 +1532,7 @@ mod tests { assert_eq!(ead_1.value.unwrap().content, ead_value_tv.content); } + #[test] fn test_compute_prk_out() { let mut prk_out: BytesHashLen = [0x00; SHA256_DIGEST_LEN]; let mut th_4_context: BytesMaxContextBuffer = [0x00; MAX_KDF_CONTEXT_LEN]; diff --git a/lib/src/lib.rs b/lib/src/lib.rs index 4be01b36..4be6c62a 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -15,20 +15,19 @@ //! [EDHOC]: https://datatracker.ietf.org/doc/draft-ietf-lake-edhoc/ #![cfg_attr(not(test), no_std)] -pub use { - lakers_shared::Crypto as CryptoTrait, lakers_shared::State as EdhocState, lakers_shared::*, -}; +pub use {lakers_shared::Crypto as CryptoTrait, lakers_shared::*}; -#[cfg(any(feature = "ead-none", feature = "ead-zeroconf"))] +#[cfg(any(feature = "ead-none", feature = "ead-zeroconf", feature = "ead-authz"))] pub use lakers_ead::*; mod edhoc; use edhoc::*; +// TODO: clean these structs and remove the cred_x whre they are not needed anymore /// Starting point for performing EDHOC in the role of the Initiator. #[derive(Debug)] pub struct EdhocInitiator<'a, Crypto: CryptoTrait> { - state: EdhocState, // opaque state + state: InitiatorStart, // opaque state i: &'a [u8], // private authentication key of I cred_i: &'a [u8], // I's full credential cred_r: Option<&'a [u8]>, // R's full credential (if provided) @@ -37,30 +36,39 @@ pub struct EdhocInitiator<'a, Crypto: CryptoTrait> { #[derive(Debug)] pub struct EdhocInitiatorWaitM2<'a, Crypto: CryptoTrait> { - state: EdhocState, // opaque state - i: &'a [u8], // private authentication key of I - cred_i: &'a [u8], // I's full credential - cred_r: Option<&'a [u8]>, // R's full credential (if provided) + state: WaitM2, // opaque state + i: &'a [u8], // private authentication key of I + cred_i: &'a [u8], // I's full credential + cred_r: Option<&'a [u8]>, // R's full credential (if provided) + crypto: Crypto, +} + +#[derive(Debug)] +pub struct EdhocInitiatorProcessingM2<'a, Crypto: CryptoTrait> { + state: ProcessingM2, // opaque state + i: &'a [u8], // private authentication key of I + cred_i: &'a [u8], // I's full credential + cred_r: Option<&'a [u8]>, // R's full credential (if provided) crypto: Crypto, } #[derive(Debug)] -pub struct EdhocInitiatorBuildM3<'a, Crypto: CryptoTrait> { - state: EdhocState, // opaque state - cred_i: &'a [u8], // I's full credential +pub struct EdhocInitiatorProcessedM2<'a, Crypto: CryptoTrait> { + state: ProcessedM2, // opaque state + cred_i: &'a [u8], // I's full credential crypto: Crypto, } #[derive(Debug)] pub struct EdhocInitiatorDone { - state: EdhocState, + state: Completed, crypto: Crypto, } /// Starting point for performing EDHOC in the role of the Responder. #[derive(Debug)] pub struct EdhocResponder<'a, Crypto: CryptoTrait> { - state: EdhocState, // opaque state + state: ResponderStart, // opaque state r: &'a [u8], // private authentication key of R cred_r: &'a [u8], // R's full credential cred_i: Option<&'a [u8]>, // I's full credential (if provided) @@ -68,39 +76,46 @@ pub struct EdhocResponder<'a, Crypto: CryptoTrait> { } #[derive(Debug)] -pub struct EdhocResponderBuildM2<'a, Crypto: CryptoTrait> { - state: EdhocState, // opaque state - r: &'a [u8], // private authentication key of R - cred_r: &'a [u8], // R's full credential - cred_i: Option<&'a [u8]>, // I's full credential (if provided) +pub struct EdhocResponderProcessedM1<'a, Crypto: CryptoTrait> { + state: ProcessingM1, // opaque state + r: &'a [u8], // private authentication key of R + cred_r: &'a [u8], // R's full credential + cred_i: Option<&'a [u8]>, // I's full credential (if provided) crypto: Crypto, } #[derive(Debug)] pub struct EdhocResponderWaitM3<'a, Crypto: CryptoTrait> { - state: EdhocState, // opaque state - cred_i: Option<&'a [u8]>, // I's full credential (if provided) + state: WaitM3, // opaque state + cred_i: Option<&'a [u8]>, // I's full credential (if provided) + crypto: Crypto, +} + +#[derive(Debug)] +pub struct EdhocResponderProcessingM3<'a, Crypto: CryptoTrait> { + state: ProcessingM3, // opaque state + cred_i: Option<&'a [u8]>, // I's full credential (if provided) crypto: Crypto, } #[derive(Debug)] pub struct EdhocResponderDone { - state: EdhocState, + state: Completed, crypto: Crypto, } impl<'a, Crypto: CryptoTrait> EdhocResponder<'a, Crypto> { pub fn new( - state: EdhocState, - crypto: Crypto, + mut crypto: Crypto, r: &'a [u8], cred_r: &'a [u8], cred_i: Option<&'a [u8]>, ) -> Self { assert!(r.len() == P256_ELEM_LEN); + let (y, g_y) = crypto.p256_generate_key_pair(); EdhocResponder { - state, + state: ResponderStart { y, g_y }, r, cred_r, cred_i, @@ -111,34 +126,42 @@ impl<'a, Crypto: CryptoTrait> EdhocResponder<'a, Crypto> { pub fn process_message_1( mut self, message_1: &BufferMessage1, - ) -> Result, EDHOCError> { - let state = r_process_message_1(self.state, &mut self.crypto, message_1)?; - - Ok(EdhocResponderBuildM2 { - state, - r: self.r, - cred_r: self.cred_r, - cred_i: self.cred_i, - crypto: self.crypto, - }) + ) -> Result<(EdhocResponderProcessedM1<'a, Crypto>, Option), EDHOCError> { + let (state, ead_1) = r_process_message_1(self.state, &mut self.crypto, message_1)?; + + Ok(( + EdhocResponderProcessedM1 { + state, + r: self.r, + cred_r: self.cred_r, + cred_i: self.cred_i, + crypto: self.crypto, + }, + ead_1, + )) } } -impl<'a, Crypto: CryptoTrait> EdhocResponderBuildM2<'a, Crypto> { +impl<'a, Crypto: CryptoTrait> EdhocResponderProcessedM1<'a, Crypto> { pub fn prepare_message_2( mut self, - c_r: u8, + id_cred_r: &IdCred, + c_r: Option, + ead_2: &Option, ) -> Result<(EdhocResponderWaitM3<'a, Crypto>, BufferMessage2), EDHOCError> { - let (y, g_y) = self.crypto.p256_generate_key_pair(); + let c_r = match c_r { + Some(c_r) => c_r, + None => generate_connection_identifier_cbor(&mut self.crypto), + }; match r_prepare_message_2( self.state, &mut self.crypto, self.cred_r, self.r.try_into().expect("Wrong length of private key"), - y, - g_y, c_r, + id_cred_r, + ead_2, ) { Ok((state, message_2)) => Ok(( EdhocResponderWaitM3 { @@ -154,11 +177,38 @@ impl<'a, Crypto: CryptoTrait> EdhocResponderBuildM2<'a, Crypto> { } impl<'a, Crypto: CryptoTrait> EdhocResponderWaitM3<'a, Crypto> { - pub fn process_message_3( + pub fn parse_message_3( mut self, - message_3: &BufferMessage3, + message_3: &'a BufferMessage3, + ) -> Result< + ( + EdhocResponderProcessingM3, + IdCredOwned, + Option, + ), + EDHOCError, + > { + match r_parse_message_3(&mut self.state, &mut self.crypto, message_3) { + Ok((state, id_cred_i, ead_3)) => Ok(( + EdhocResponderProcessingM3 { + state, + crypto: self.crypto, + cred_i: self.cred_i, + }, + id_cred_i, + ead_3, + )), + Err(error) => Err(error), + } + } +} + +impl<'a, Crypto: CryptoTrait> EdhocResponderProcessingM3<'a, Crypto> { + pub fn verify_message_3( + mut self, + cred_i: &[u8], ) -> Result<(EdhocResponderDone, [u8; SHA256_DIGEST_LEN]), EDHOCError> { - match r_process_message_3(&mut self.state, &mut self.crypto, message_3, self.cred_i) { + match r_verify_message_3(&mut self.state, &mut self.crypto, cred_i) { Ok((state, prk_out)) => Ok(( EdhocResponderDone { state, @@ -206,16 +256,25 @@ impl EdhocResponderDone { impl<'a, Crypto: CryptoTrait> EdhocInitiator<'a, Crypto> { pub fn new( - state: EdhocState, - crypto: Crypto, + mut crypto: Crypto, i: &'a [u8], cred_i: &'a [u8], cred_r: Option<&'a [u8]>, ) -> Self { assert!(i.len() == P256_ELEM_LEN); + // we only support a single cipher suite which is already CBOR-encoded + let mut suites_i: BytesSuites = [0x0; SUITES_LEN]; + let suites_i_len = EDHOC_SUPPORTED_SUITES.len(); + suites_i[0..suites_i_len].copy_from_slice(&EDHOC_SUPPORTED_SUITES[..]); + let (x, g_x) = crypto.p256_generate_key_pair(); EdhocInitiator { - state, + state: InitiatorStart { + x, + g_x, + suites_i, + suites_i_len, + }, i, cred_i, cred_r, @@ -225,11 +284,15 @@ impl<'a, Crypto: CryptoTrait> EdhocInitiator<'a, Crypto> { pub fn prepare_message_1( mut self, - c_i: u8, - ) -> Result<(EdhocInitiatorWaitM2<'a, Crypto>, BufferMessage1), EDHOCError> { - let (x, g_x) = self.crypto.p256_generate_key_pair(); - - match i_prepare_message_1(self.state, &mut self.crypto, x, g_x, c_i) { + c_i: Option, + ead_1: &Option, + ) -> Result<(EdhocInitiatorWaitM2<'a, Crypto>, EdhocMessageBuffer), EDHOCError> { + let c_i = match c_i { + Some(c_i) => c_i, + None => generate_connection_identifier_cbor(&mut self.crypto), + }; + + match i_prepare_message_1(self.state, &mut self.crypto, c_i, ead_1) { Ok((state, message_1)) => Ok(( EdhocInitiatorWaitM2 { state, @@ -246,35 +309,63 @@ impl<'a, Crypto: CryptoTrait> EdhocInitiator<'a, Crypto> { } impl<'a, Crypto: CryptoTrait> EdhocInitiatorWaitM2<'a, Crypto> { - pub fn process_message_2( + pub fn parse_message_2( mut self, - message_2: &BufferMessage2, - ) -> Result<(EdhocInitiatorBuildM3<'a, Crypto>, u8), EDHOCError> { - match i_process_message_2( - self.state, - &mut self.crypto, - message_2, - self.cred_r, - self.i - .try_into() - .expect("Wrong length of initiator private key"), - ) { - Ok((state, c_r, _kid)) => Ok(( - EdhocInitiatorBuildM3 { + message_2: &'a BufferMessage2, + ) -> Result< + ( + EdhocInitiatorProcessingM2<'a, Crypto>, + u8, + IdCredOwned, + Option, + ), + EDHOCError, + > { + match i_parse_message_2(self.state, &mut self.crypto, message_2) { + Ok((state, c_r, id_cred_r, ead_2)) => Ok(( + EdhocInitiatorProcessingM2 { state, + i: self.i, cred_i: self.cred_i, + cred_r: self.cred_r, crypto: self.crypto, }, c_r, + id_cred_r, + ead_2, )), Err(error) => Err(error), } } } -impl<'a, Crypto: CryptoTrait> EdhocInitiatorBuildM3<'a, Crypto> { +impl<'a, Crypto: CryptoTrait> EdhocInitiatorProcessingM2<'a, Crypto> { + pub fn verify_message_2( + mut self, + valid_cred_r: &[u8], + ) -> Result, EDHOCError> { + match i_verify_message_2( + self.state, + &mut self.crypto, + valid_cred_r, + self.i + .try_into() + .expect("Wrong length of initiator private key"), + ) { + Ok(state) => Ok(EdhocInitiatorProcessedM2 { + state, + cred_i: self.cred_i, + crypto: self.crypto, + }), + Err(error) => Err(error), + } + } +} + +impl<'a, Crypto: CryptoTrait> EdhocInitiatorProcessedM2<'a, Crypto> { pub fn prepare_message_3( mut self, + ead_3: &Option, ) -> Result< ( EdhocInitiatorDone, @@ -283,12 +374,7 @@ impl<'a, Crypto: CryptoTrait> EdhocInitiatorBuildM3<'a, Crypto> { ), EDHOCError, > { - match i_prepare_message_3( - &mut self.state, - &mut self.crypto, - &get_id_cred(self.cred_i)?, - self.cred_i, - ) { + match i_prepare_message_3(&mut self.state, &mut self.crypto, self.cred_i, ead_3) { Ok((state, message_3, prk_out)) => Ok(( EdhocInitiatorDone { state, @@ -356,6 +442,70 @@ pub fn generate_connection_identifier(crypto: &mut Crypto) conn_id } +// Implements auth credential checking according to draft-tiloca-lake-implem-cons +pub fn credential_check_or_fetch<'a>( + cred_expected: Option, + id_cred_received: IdCredOwned, +) -> Result<(EdhocMessageBuffer, BytesP256ElemLen), EDHOCError> { + // Processing of auth credentials according to draft-tiloca-lake-implem-cons + // Comments tagged with a number refer to steps in Section 4.3.1. of draft-tiloca-lake-implem-cons + if let Some(cred_expected) = cred_expected { + // 1. Does ID_CRED_X point to a stored authentication credential? YES + // IMPL: compare cred_i_expected with id_cred + // IMPL: assume cred_i_expected is well formed + let (public_key_expected, kid_expected) = parse_cred(cred_expected.as_slice())?; + let public_key = public_key_expected; + let credentials_match = match id_cred_received { + IdCredOwned::CompactKid(kid_received) => kid_received == kid_expected, + IdCredOwned::FullCredential(cred_received) => cred_expected == cred_received, + }; + + // 2. Is this authentication credential still valid? + // IMPL,TODO: check cred_r_expected is still valid + + // Continue by considering CRED_X as the authentication credential of the other peer. + // IMPL: ready to proceed, including process ead_2 + + if credentials_match { + Ok((cred_expected, public_key)) + } else { + Err(EDHOCError::UnknownPeer) + } + } else { + // 1. Does ID_CRED_X point to a stored authentication credential? NO + // IMPL: cred_i_expected provided by application is None + // id_cred must be a full credential + if let IdCredOwned::FullCredential(cred_received) = id_cred_received { + // 3. Is the trust model Pre-knowledge-only? NO (hardcoded to NO for now) + + // 4. Is the trust model Pre-knowledge + TOFU? YES (hardcoded to YES for now) + + // 6. Validate CRED_X. Generally a CCS has to be validated only syntactically and semantically, unlike a certificate or a CWT. + // Is the validation successful? + // IMPL: parse_cred(cred_r) and check it is valid + match parse_cred(cred_received.as_slice()) { + Ok((public_key_received, _kid_received)) => { + // 5. Is the authentication credential authorized for use in the context of this EDHOC session? + // IMPL,TODO: we just skip this step for now + + // 7. Store CRED_X as valid and trusted. + // Pair it with consistent credential identifiers, for each supported type of credential identifier. + // IMPL: cred_r = id_cred + let public_key = public_key_received; + Ok((cred_received, public_key)) + } + Err(_) => Err(EDHOCError::UnknownPeer), + } + } else { + // IMPL: should have gotten a full credential + Err(EDHOCError::UnknownPeer) + } + } + + // 8. Is this authentication credential good to use in the context of this EDHOC session? + // IMPL,TODO: we just skip this step for now +} + #[cfg(test)] mod test { use super::*; @@ -369,12 +519,9 @@ mod test { const CRED_I: &[u8] = &hex!("A2027734322D35302D33312D46462D45462D33372D33322D333908A101A5010202412B2001215820AC75E9ECE3E50BFC8ED60399889522405C47BF16DF96660A41298CB4307F7EB62258206E5DE611388A4B8A8211334AC7D37ECB52A387D257E6DB3C2A93DF21FF3AFFC8"); const I: &[u8] = &hex!("fb13adeb6518cee5f88417660841142e830a81fe334380a953406a1305e8706b"); const R: &[u8] = &hex!("72cc4761dbd4c78f758931aa589d348d1ef874a7e303ede2f140dcf3e6aa4aac"); - const G_I: &[u8] = &hex!("ac75e9ece3e50bfc8ed60399889522405c47bf16df96660a41298cb4307f7eb6"); // used const _G_I_Y_COORD: &[u8] = &hex!("6e5de611388a4b8a8211334ac7d37ecb52a387d257e6db3c2a93df21ff3affc8"); // not used const CRED_R: &[u8] = &hex!("A2026008A101A5010202410A2001215820BBC34960526EA4D32E940CAD2A234148DDC21791A12AFBCBAC93622046DD44F02258204519E257236B2A0CE2023F0931F1F386CA7AFDA64FCDE0108C224C51EABF6072"); - const G_R: &[u8] = &hex!("bbc34960526ea4d32e940cad2a234148ddc21791a12afbcbac93622046dd44f0"); - const C_R_TV: [u8; 1] = hex!("27"); const MESSAGE_1_TV_FIRST_TIME: &str = "03065820741a13d7ba048fbb615e94386aa3b61bea5b3d8f65f32620b749bee8d278efa90e"; @@ -383,36 +530,30 @@ mod test { #[test] fn test_new_initiator() { - let state = Default::default(); - let _initiator = EdhocInitiator::new(state, default_crypto(), I, CRED_I, Some(CRED_R)); - let state = Default::default(); - let _initiator = EdhocInitiator::new(state, default_crypto(), I, CRED_I, None); + let _initiator = EdhocInitiator::new(default_crypto(), I, CRED_I, Some(CRED_R)); + let _initiator = EdhocInitiator::new(default_crypto(), I, CRED_I, None); } #[test] fn test_new_responder() { - let state = Default::default(); - let _responder = EdhocResponder::new(state, default_crypto(), R, CRED_R, Some(CRED_I)); - let state = Default::default(); - let _responder = EdhocResponder::new(state, default_crypto(), R, CRED_R, None); + let _responder = EdhocResponder::new(default_crypto(), R, CRED_R, Some(CRED_I)); + let _responder = EdhocResponder::new(default_crypto(), R, CRED_R, None); } #[test] fn test_prepare_message_1() { - let state = Default::default(); - let initiator = EdhocInitiator::new(state, default_crypto(), I, CRED_I, Some(CRED_R)); + let initiator = EdhocInitiator::new(default_crypto(), I, CRED_I, Some(CRED_R)); let c_i = generate_connection_identifier_cbor(&mut default_crypto()); - let message_1 = initiator.prepare_message_1(c_i); - assert!(message_1.is_ok()); + let result = initiator.prepare_message_1(Some(c_i), &None); + assert!(result.is_ok()); } #[test] fn test_process_message_1() { let message_1_tv_first_time = EdhocMessageBuffer::from_hex(MESSAGE_1_TV_FIRST_TIME); let message_1_tv = EdhocMessageBuffer::from_hex(MESSAGE_1_TV); - let state = Default::default(); - let responder = EdhocResponder::new(state, default_crypto(), R, CRED_R, Some(CRED_I)); + let responder = EdhocResponder::new(default_crypto(), R, CRED_R, Some(CRED_I)); // process message_1 first time, when unsupported suite is selected let error = responder.process_message_1(&message_1_tv_first_time); @@ -421,8 +562,7 @@ mod test { // We need to create a new responder -- no message is supposed to be processed twice by a // responder or initiator - let state = Default::default(); - let responder = EdhocResponder::new(state, default_crypto(), R, CRED_R, Some(CRED_I)); + let responder = EdhocResponder::new(default_crypto(), R, CRED_R, Some(CRED_I)); // process message_1 second time let error = responder.process_message_1(&message_1_tv); @@ -438,27 +578,40 @@ mod test { #[cfg(feature = "ead-none")] #[test] fn test_handshake() { - let state_initiator = Default::default(); - let initiator = - EdhocInitiator::new(state_initiator, default_crypto(), I, CRED_I, Some(CRED_R)); - let state_responder = Default::default(); - let responder = - EdhocResponder::new(state_responder, default_crypto(), R, CRED_R, Some(CRED_I)); - - let c_i: u8 = generate_connection_identifier_cbor(&mut default_crypto()); - let (initiator, result) = initiator.prepare_message_1(c_i).unwrap(); // to update the state - - let responder = responder.process_message_1(&result).unwrap(); - - let c_r = generate_connection_identifier_cbor(&mut default_crypto()); - let (responder, message_2) = responder.prepare_message_2(c_r).unwrap(); - - assert!(c_r != 0xff); - let (initiator, _) = initiator.process_message_2(&message_2).unwrap(); - - let (mut initiator, message_3, i_prk_out) = initiator.prepare_message_3().unwrap(); - - let (mut responder, r_prk_out) = responder.process_message_3(&message_3).unwrap(); + let initiator = EdhocInitiator::new(default_crypto(), I, CRED_I, Some(CRED_R)); + let responder = EdhocResponder::new(default_crypto(), R, CRED_R, Some(CRED_I)); + + // ---- begin initiator handling + // if needed: prepare ead_1 + let (initiator, message_1) = initiator.prepare_message_1(None, &None).unwrap(); + // ---- end initiator handling + + // ---- begin responder handling + let (responder, _ead_1) = responder.process_message_1(&message_1).unwrap(); + // if ead_1: process ead_1 + // if needed: prepare ead_2 + let kid = IdCred::CompactKid(ID_CRED_R[3]); + let (responder, message_2) = responder.prepare_message_2(&kid, None, &None).unwrap(); + // ---- end responder handling + + // ---- being initiator handling + let (initiator, _c_r, id_cred_r, _ead_2) = initiator.parse_message_2(&message_2).unwrap(); + let (valid_cred_r, _g_r) = + credential_check_or_fetch(Some(CRED_R.try_into().unwrap()), id_cred_r).unwrap(); + let initiator = initiator.verify_message_2(valid_cred_r.as_slice()).unwrap(); + + // if needed: prepare ead_3 + let (mut initiator, message_3, i_prk_out) = initiator.prepare_message_3(&None).unwrap(); + // ---- end initiator handling + + // ---- begin responder handling + let (responder, id_cred_i, _ead_3) = responder.parse_message_3(&message_3).unwrap(); + let (valid_cred_i, _g_i) = + credential_check_or_fetch(Some(CRED_I.try_into().unwrap()), id_cred_i).unwrap(); + // if ead_3: process ead_3 + let (mut responder, r_prk_out) = + responder.verify_message_3(valid_cred_i.as_slice()).unwrap(); + // ---- end responder handling // check that prk_out is equal at initiator and responder side assert_eq!(i_prk_out, r_prk_out); @@ -498,138 +651,75 @@ mod test { const LOC_W_TV: &[u8] = &hex!("636F61703A2F2F656E726F6C6C6D656E742E736572766572"); // TODO: have a setup_test function that prepares the common objects for the ead tests - #[cfg(feature = "ead-zeroconf")] + #[cfg(feature = "ead-authz")] #[test] - fn test_ead_zeroconf() { + fn test_ead_authz() { // ==== initialize edhoc ==== - let initiator = EdhocInitiator::new(Default::default(), default_crypto(), I, CRED_I, None); - let responder = EdhocResponder::new( - Default::default(), - default_crypto(), - R, - CRED_R, - Some(CRED_I), - ); - - // ==== initialize ead-zeroconf ==== - let id_u: EdhocMessageBuffer = ID_U_TV.try_into().unwrap(); - let g_w: BytesP256ElemLen = G_W_TV.try_into().unwrap(); - let loc_w: EdhocMessageBuffer = LOC_W_TV.try_into().unwrap(); - - ead_initiator_set_global_state(EADInitiatorState::new(id_u, g_w, loc_w)); - let ead_initiator_state = ead_initiator_get_global_state(); - assert_eq!( - ead_initiator_state.protocol_state, - EADInitiatorProtocolState::Start - ); - - ead_responder_set_global_state(EADResponderState::new()); - let ead_responder_state = ead_responder_get_global_state(); - assert_eq!( - ead_responder_state.protocol_state, - EADResponderProtocolState::Start + let initiator = EdhocInitiator::new(default_crypto(), I, CRED_I, Some(CRED_R)); + let responder = EdhocResponder::new(default_crypto(), R, CRED_R, Some(CRED_I)); + + // ==== initialize ead-authz ==== + let device = ZeroTouchDevice::new( + ID_U_TV.try_into().unwrap(), + G_W_TV.try_into().unwrap(), + LOC_W_TV.try_into().unwrap(), ); + let authenticator = ZeroTouchAuthenticator::default(); - let mut acl = EdhocMessageBuffer::new(); - let (_g, kid_i) = parse_cred(CRED_I).unwrap(); - acl.push(kid_i).unwrap(); - mock_ead_server_set_global_state(MockEADServerState::new( - CRED_R, + let acl = EdhocMessageBuffer::new_from_slice(&[ID_CRED_I[3]]).unwrap(); + let server = ZeroTouchServer::new( W_TV.try_into().unwrap(), + CRED_R.try_into().unwrap(), Some(acl), - )); - - let c_i = generate_connection_identifier_cbor(&mut default_crypto()); - let (initiator, message_1) = initiator.prepare_message_1(c_i).unwrap(); - assert_eq!( - ead_initiator_state.protocol_state, - EADInitiatorProtocolState::WaitEAD2 ); - // ==== begin edhoc with ead-zeroconf ==== - let responder = responder.process_message_1(&message_1).unwrap(); - assert_eq!( - ead_responder_state.protocol_state, - EADResponderProtocolState::ProcessedEAD1 - ); + // ==== begin edhoc with ead-authz ==== - let c_r = generate_connection_identifier_cbor(&mut default_crypto()); - let (responder, message_2) = responder.prepare_message_2(c_r).unwrap(); - assert_eq!( - ead_responder_state.protocol_state, - EADResponderProtocolState::Completed + let (ead_1, mut device) = device.prepare_ead_1( + &mut default_crypto(), + &initiator.state.x, // FIXME: avoid accessing private ephemeral key from application code + initiator.state.suites_i[initiator.state.suites_i_len - 1], ); + let (initiator, message_1) = initiator.prepare_message_1(None, &Some(ead_1)).unwrap(); + device.set_h_message_1(initiator.state.h_message_1.clone()); + + let (responder, ead_1) = responder.process_message_1(&message_1).unwrap(); + let ead_2 = if let Some(ead_1) = ead_1 { + let (_loc_w, voucher_request, authenticator) = + authenticator.process_ead_1(&ead_1, &message_1).unwrap(); + + // the line below mocks a request to the server: let voucher_response = auth_client.post(loc_w, voucher_request)? + let voucher_response = server + .handle_voucher_request(&mut default_crypto(), &voucher_request) + .unwrap(); + + let res = authenticator.prepare_ead_2(&voucher_response); + assert!(res.is_ok()); + authenticator.prepare_ead_2(&voucher_response).ok() + } else { + None + }; + let kid = IdCred::CompactKid(ID_CRED_R[3]); + let (responder, message_2) = responder.prepare_message_2(&kid, None, &ead_2).unwrap(); + + let (initiator, _c_r, id_cred_r, ead_2) = initiator.parse_message_2(&message_2).unwrap(); + let (valid_cred_r, _g_r) = + credential_check_or_fetch(Some(CRED_R.try_into().unwrap()), id_cred_r).unwrap(); + if let Some(ead_2) = ead_2 { + let result = device.process_ead_2(&mut default_crypto(), ead_2, CRED_R); + assert!(result.is_ok()); + } + let initiator = initiator.verify_message_2(valid_cred_r.as_slice()).unwrap(); - let (initiator, _) = initiator.process_message_2(&message_2).unwrap(); - - assert_eq!( - ead_initiator_state.protocol_state, - EADInitiatorProtocolState::Completed - ); + let (mut _initiator, message_3, i_prk_out) = initiator.prepare_message_3(&None).unwrap(); - let (initiator, message_3, i_prk_out) = initiator.prepare_message_3().unwrap(); + let (responder, id_cred_i, _ead_3) = responder.parse_message_3(&message_3).unwrap(); + let (valid_cred_i, _g_i) = + credential_check_or_fetch(Some(CRED_I.try_into().unwrap()), id_cred_i).unwrap(); + let (mut _responder, r_prk_out) = + responder.verify_message_3(valid_cred_i.as_slice()).unwrap(); - let (mut responder, r_prk_out) = responder.process_message_3(&message_3).unwrap(); + // check that prk_out is equal at initiator and responder side assert_eq!(i_prk_out, r_prk_out); - assert_eq!( - ead_responder_state.protocol_state, - EADResponderProtocolState::Completed - ); - } - - #[cfg(feature = "ead-zeroconf")] - #[test] - fn test_ead_zeroconf_not_authorized() { - // ==== initialize edhoc ==== - let initiator = EdhocInitiator::new(Default::default(), default_crypto(), I, CRED_I, None); - let responder = EdhocResponder::new( - Default::default(), - default_crypto(), - R, - CRED_R, - Some(CRED_I), - ); - - // ==== initialize ead-zeroconf ==== - let id_u: EdhocMessageBuffer = ID_U_TV.try_into().unwrap(); - let g_w: BytesP256ElemLen = G_W_TV.try_into().unwrap(); - let loc_w: EdhocMessageBuffer = LOC_W_TV.try_into().unwrap(); - - ead_initiator_set_global_state(EADInitiatorState::new(id_u, g_w, loc_w)); - let ead_initiator_state = ead_initiator_get_global_state(); - assert_eq!( - ead_initiator_state.protocol_state, - EADInitiatorProtocolState::Start - ); - - ead_responder_set_global_state(EADResponderState::new()); - let ead_responder_state = ead_responder_get_global_state(); - assert_eq!( - ead_responder_state.protocol_state, - EADResponderProtocolState::Start - ); - - let mut acl = EdhocMessageBuffer::new(); - let (_g, kid_i) = parse_cred(CRED_I).unwrap(); - let invalid_kid = kid_i + 1; - acl.push(invalid_kid).unwrap(); - mock_ead_server_set_global_state(MockEADServerState::new( - CRED_R, - W_TV.try_into().unwrap(), - Some(acl), - )); - - let c_i = generate_connection_identifier_cbor(&mut default_crypto()); - let (initiator, message_1) = initiator.prepare_message_1(c_i).unwrap(); - assert_eq!( - ead_initiator_state.protocol_state, - EADInitiatorProtocolState::WaitEAD2 - ); - - // ==== begin edhoc with ead-zeroconf ==== - assert_eq!( - responder.process_message_1(&message_1).unwrap_err(), - EDHOCError::EADError - ); } } diff --git a/shared/src/lib.rs b/shared/src/lib.rs index 5b5b5417..53fb15d0 100644 --- a/shared/src/lib.rs +++ b/shared/src/lib.rs @@ -9,8 +9,6 @@ //! [lakers-ead-dispatch]: https://docs.rs/lakers-ead-dispatch/latest/lakers_ead_dispatch/ #![no_std] -use core::marker::PhantomData; - pub use cbor::*; pub use cbor_decoder::*; pub use edhoc_parser::*; @@ -97,31 +95,6 @@ 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 -// This is sealed -pub trait EDHOCState: core::fmt::Debug {} -// For both initiator and responder -#[derive(Debug)] -pub struct Start; -impl EDHOCState for Start {} -// For the initiator -#[derive(Debug)] -pub struct WaitMessage2; -impl EDHOCState for WaitMessage2 {} -#[derive(Debug)] -pub struct ProcessedMessage2; -impl EDHOCState for ProcessedMessage2 {} -// For the responder -#[derive(Debug)] -pub struct ProcessedMessage1; -impl EDHOCState for ProcessedMessage1 {} -#[derive(Debug)] -pub struct WaitMessage3; -impl EDHOCState for WaitMessage3 {} -// For both again -#[derive(Debug)] -pub struct Completed; -impl EDHOCState for Completed {} - #[repr(C)] #[derive(PartialEq, Debug)] pub enum EDHOCError { @@ -136,37 +109,80 @@ pub enum EDHOCError { UnknownError = 9, } -#[repr(C)] #[derive(Debug)] -pub struct State { - pub current_state: PhantomData, - pub x_or_y: BytesP256ElemLen, // ephemeral private key of myself - pub c_i: u8, // connection identifier chosen by the initiator - pub gy_or_gx: BytesP256ElemLen, // g_y or g_x, ephemeral public key of the peer +pub struct InitiatorStart { + pub suites_i: BytesSuites, + pub suites_i_len: usize, + pub x: BytesP256ElemLen, // ephemeral private key of myself + pub g_x: BytesP256ElemLen, // ephemeral public key of myself +} + +#[derive(Debug)] +pub struct ResponderStart { + pub y: BytesP256ElemLen, // ephemeral private key of myself + pub g_y: BytesP256ElemLen, // ephemeral public key of myself +} + +#[derive(Debug)] +pub struct ProcessingM1 { + pub y: BytesP256ElemLen, + pub g_y: BytesP256ElemLen, + pub c_i: u8, + pub g_x: BytesP256ElemLen, // ephemeral public key of the initiator + pub h_message_1: BytesHashLen, +} + +#[derive(Debug)] +pub struct WaitM2 { + pub x: BytesP256ElemLen, // ephemeral private key of the initiator + pub h_message_1: BytesHashLen, +} + +#[derive(Debug)] +pub struct WaitM3 { + pub y: BytesP256ElemLen, // ephemeral private key of the responder + pub prk_3e2m: BytesHashLen, + pub th_3: BytesHashLen, +} + +#[derive(Debug)] +pub struct ProcessingM2 { + pub mac_2: BytesMac2, + pub prk_2e: BytesHashLen, + pub th_2: BytesHashLen, + pub x: BytesP256ElemLen, + pub g_y: BytesP256ElemLen, + pub plaintext_2: EdhocMessageBuffer, +} + +#[derive(Debug)] +pub struct ProcessedM2 { pub prk_3e2m: BytesHashLen, pub prk_4e3m: BytesHashLen, - pub prk_out: BytesHashLen, - pub prk_exporter: BytesHashLen, - pub h_message_1: BytesHashLen, pub th_3: BytesHashLen, } -impl Default for State { - fn default() -> Self { - // This is also what `#[derive(Default)]` would do, but we can't limit that to Start. - State { - current_state: Default::default(), - x_or_y: Default::default(), - c_i: Default::default(), - gy_or_gx: Default::default(), - prk_3e2m: Default::default(), - prk_4e3m: Default::default(), - prk_out: Default::default(), - prk_exporter: Default::default(), - h_message_1: Default::default(), - th_3: Default::default(), - } - } +#[derive(Debug)] +pub struct ProcessingM3 { + pub mac_3: BytesMac3, + pub y: BytesP256ElemLen, // ephemeral private key of the responder + pub prk_3e2m: BytesHashLen, + pub th_3: BytesHashLen, + pub plaintext_3: EdhocMessageBuffer, +} + +#[derive(Debug)] +pub struct PreparingM3 { + pub prk_3e2m: BytesHashLen, + pub prk_4e3m: BytesHashLen, + pub th_3: BytesHashLen, + pub mac_3: BytesMac3, +} + +#[derive(Debug)] +pub struct Completed { + pub prk_out: BytesHashLen, + pub prk_exporter: BytesHashLen, } /// An owned u8 vector of a limited length @@ -197,6 +213,15 @@ impl EdhocMessageBuffer { } } + pub fn new_from_slice(slice: &[u8]) -> Result { + let mut buffer = Self::new(); + if buffer.fill_with_slice(slice).is_ok() { + Ok(buffer) + } else { + Err(()) + } + } + pub fn get(self, index: usize) -> Option { self.content.get(index).copied() } @@ -290,12 +315,19 @@ impl EADTrait for EADItem { } } -#[derive(Debug)] +// FIXME: homogenize the two structs below (likey keep only the owned version) +#[derive(Debug, Clone, Copy)] pub enum IdCred<'a> { CompactKid(u8), FullCredential(&'a [u8]), } +#[derive(Debug, Clone, Copy)] +pub enum IdCredOwned { + CompactKid(u8), + FullCredential(EdhocMessageBuffer), +} + // TODO: remove this, once ead-zeroconf is adjusted to use cbor_decoder mod cbor { use super::*;