From b9e4d70a6ec6b4004a5ecf3f6423c19bfb3487bc Mon Sep 17 00:00:00 2001 From: chrysn Date: Fri, 19 Apr 2024 16:54:30 +0200 Subject: [PATCH] coap: Functional request/response --- examples/coap/Cargo.toml | 7 +- examples/coap/fauxhoc.py | 5 +- examples/coap/src/seccontext.rs | 222 +++++++++++++++++++++++++------- 3 files changed, 183 insertions(+), 51 deletions(-) diff --git a/examples/coap/Cargo.toml b/examples/coap/Cargo.toml index eded6bfa5..9359f1ea5 100644 --- a/examples/coap/Cargo.toml +++ b/examples/coap/Cargo.toml @@ -28,13 +28,16 @@ coap-request-implementations = "0.1.0-alpha.4" lakers = { version = "0.5.1", default-features = false } lakers-crypto-rustcrypto = "0.5.1" coap-handler = "0.2.0" -coap-message-utils = "0.3.1" +coap-message-utils = "0.3.2" coap-handler-implementations = "0.5.0" hexlit = "0.5.5" coap-numbers = "0.2.3" minicbor = "0.23.0" -liboscore = { git = "https://gitlab.com/oscore/liboscore/", branch = "rust-backends" } +liboscore.git = "https://gitlab.com/oscore/liboscore/" +liboscore-msgbackend = { git = "https://gitlab.com/oscore/liboscore/", features = [ "alloc" ] } +coap-message-implementations = "0.1.1" +static-alloc = "0.2.5" [features] default = [ "proto-ipv4" ] # shame diff --git a/examples/coap/fauxhoc.py b/examples/coap/fauxhoc.py index f24a526ef..25c829848 100644 --- a/examples/coap/fauxhoc.py +++ b/examples/coap/fauxhoc.py @@ -99,12 +99,9 @@ async def main(): msg3 = Message( code=GET, - uri="coap://10.42.0.61/hello", + uri="coap://10.42.0.61/.well-known/core", ) -# object_security=b"foobar\x00", # works with how the peer parses it right now -# edhoc=True, - print(await ctx.request(msg3).response_raising) await ctx.shutdown() diff --git a/examples/coap/src/seccontext.rs b/examples/coap/src/seccontext.rs index d3aa7fbe3..bd5621738 100644 --- a/examples/coap/src/seccontext.rs +++ b/examples/coap/src/seccontext.rs @@ -5,6 +5,12 @@ use coap_message::{ use coap_message_utils::{Error as CoAPError, OptionsExt as _}; use core::borrow::Borrow; +extern crate alloc; +use static_alloc::Bump; + +#[global_allocator] +static A: Bump<[u8; 1 << 16]> = Bump::uninit(); + // If this exceeds 47, COwn will need to be extended. const MAX_CONTEXTS: usize = 4; @@ -122,6 +128,7 @@ enum SecContextState { c_r: COwn, }, + // FIXME: Also needs a flag for whether M4 was received; if not, it's GC'able Oscore(liboscore::PrimitiveContext), } @@ -181,11 +188,16 @@ impl<'a, H: coap_handler::Handler> OscoreEdhocHandler<'a, H> { } } -pub enum EdhocResponse { +pub enum EdhocResponse { // Taking a small state here: We already have a slot in the pool, storing the big data there OkSend2(usize), // Could have a state Message3Processed -- but do we really want to implement that? (like, just // use the EDHOC option) + OscoreRequest { + slot: usize, + correlation: liboscore::raw::oscore_requestid_t, + extracted: I, + }, } // FIXME: It'd be tempting to implement Drop for Response to set the slot back to Empty -- but @@ -241,7 +253,8 @@ impl RenderableOnMinimal for OrI } impl<'a, H: coap_handler::Handler> coap_handler::Handler for OscoreEdhocHandler<'a, H> { - type RequestData = OrInner; + type RequestData = + OrInner>, H::RequestData>; type ExtractRequestError = OrInner; type BuildResponseError = @@ -363,12 +376,11 @@ impl<'a, H: coap_handler::Handler> coap_handler::Handler for OscoreEdhocHandler< // This whole loop-and-tree could become a single take_responder_wait3 method? let cown = COwn::from_kid(&[kid]); let mut pool_lock = self.pool.0.borrow_mut(); - let matched = pool_lock + let (slot, matched) = pool_lock .iter_mut() - .filter(|c| c.corresponding_cown() == cown) - .next(); - println!("Corresponding secctx is {:?}", matched); - let matched = matched + .enumerate() + .filter(|(slot, c)| c.corresponding_cown() == cown) + .next() // following RFC8613 Section 8.2 item 2.2 // FIXME unauthorized (unreleased in coap-message-utils) .ok_or_else(CoAPError::bad_request)?; @@ -390,16 +402,12 @@ impl<'a, H: coap_handler::Handler> coap_handler::Handler for OscoreEdhocHandler< // isn't processable, it's unlikely that another one would come up and be. let mut taken = core::mem::replace(matched, Default::default()); - if let SecContextState::EdhocResponderSentM2 { - responder, - c_r, - } = taken - { + if let SecContextState::EdhocResponderSentM2 { responder, c_r } = taken { let msg_3 = lakers::EdhocMessageBuffer::new_from_slice(&payload[..cutoff]) .map_err(|e| Own(too_small(e)))?; - let (responder, id_cred_i, ead_3) = responder.parse_message_3(&msg_3) - .map_err(render_error)?; + let (responder, id_cred_i, ead_3) = + responder.parse_message_3(&msg_3).map_err(render_error)?; if ead_3.is_some_and(|e| e.is_critical) { // FIXME: send error message @@ -415,15 +423,20 @@ impl<'a, H: coap_handler::Handler> coap_handler::Handler for OscoreEdhocHandler< use hexlit::hex; const CRED_I: &[u8] = &hex!("A2027734322D35302D33312D46462D45462D33372D33322D333908A101A5010202412B2001215820AC75E9ECE3E50BFC8ED60399889522405C47BF16DF96660A41298CB4307F7EB62258206E5DE611388A4B8A8211334AC7D37ECB52A387D257E6DB3C2A93DF21FF3AFFC8"); - let cred_i = lakers::CredentialRPK::new(CRED_I.try_into().expect("Static credential is too large")).expect("Static credential is not processable"); + let cred_i = lakers::CredentialRPK::new( + CRED_I.try_into().expect("Static credential is too large"), + ) + .expect("Static credential is not processable"); let (mut responder, _prk_out) = responder.verify_message_3(cred_i).map_err(render_error)?; let oscore_secret = responder.edhoc_exporter(0u8, &[], 16); // label is 0 let oscore_salt = responder.edhoc_exporter(1u8, &[], 8); // label is 1 - println!("OSCORE secret: {:?}", &oscore_secret[..5]); - println!("OSCORE salt: {:?}", &oscore_salt[..5]); + let oscore_secret = &oscore_secret[..16]; + let oscore_salt = &oscore_salt[..8]; + println!("OSCORE secret: {:?}...", &oscore_secret[..5]); + println!("OSCORE salt: {:?}", &oscore_salt); let sender_id = 0x08; // FIXME: lakers can't export that? let recipient_id = kid; @@ -441,17 +454,15 @@ impl<'a, H: coap_handler::Handler> coap_handler::Handler for OscoreEdhocHandler< // FIXME need KID form (but for all that's supported that works still) &[sender_id], &[recipient_id], - ) - // FIXME convert error - .unwrap(); + ) + // FIXME convert error + .unwrap(); - let context = liboscore::PrimitiveContext::new_from_fresh_material( - immutables, - ); + let context = + liboscore::PrimitiveContext::new_from_fresh_material(immutables); *matched = SecContextState::Oscore(context); } else { - println!("Odd state: {:?}", taken); // Return the state. Best bet is that it was already advanced to an OSCORE // state, and the peer sent message 3 with multiple concurrent in-flight // messages. We're ignoring the EDHOC value and continue with OSCORE @@ -469,25 +480,57 @@ impl<'a, H: coap_handler::Handler> coap_handler::Handler for OscoreEdhocHandler< return Err(Own(CoAPError::bad_request())); }; - // FIXME: Pass on a message to OSCORE, which will really not even need this mapped - // but needs the offset as an input to its constructed message - let oscore_message = &payload[front_trim_payload..]; - - println!("Process with ctx {:?}", oscore_context); - println!("Message on to OSCORE from {}: {:?}", front_trim_payload, oscore_message); - - // FIXME CONTINUE HERE: To call - // let (mut correlation, extracted)) = liboscore::unprotect_request( - // &mut request, - // oscore_option, - // context, - // |request| handler.extract_request_data(request), - // )? - // we need to wrap the message in a strip-edhoc data type - - // Result may need somethig inside Own that again is H::RequestData - // todo!() - Err(Own(CoAPError::internal_server_error())) + let mut allocated_message = coap_message_implementations::heap::HeapMessage::new(); + // This works from +WithSortedOptions into MinimalWritableMessage, but not from + // ReadableMessage to MutableWritableMessage + allows-random-access: + // allocated_message.set_from_message(request); + // + // The whole workaround is messy; not trying to enhance it b/c the whole alloc mess + // is temporary. + allocated_message.set_code(request.code().into()); + let mut oscore_option = None; + for opt in request.options() { + if opt.number() == coap_numbers::option::EDHOC { + continue; + } + // it's infallible, but we don't have irrefutable patterns yet + allocated_message + .add_option(opt.number(), opt.value()) + .unwrap(); + + if opt.number() == coap_numbers::option::OSCORE { + oscore_option = Some( + heapless::Vec::<_, 16>::try_from(opt.value()) + .map_err(|_| CoAPError::bad_option(opt.number()))?, + ); + } + } + // We know this to not fail b/c we only got here due to its presence + let oscore_option = oscore_option.unwrap(); + let oscore_option = liboscore::OscoreOption::parse(&oscore_option) + .map_err(|_| CoAPError::bad_option(coap_numbers::option::OSCORE))?; + allocated_message + .set_payload(&payload[front_trim_payload..]) + .unwrap(); + + let Ok((correlation, extracted)) = liboscore::unprotect_request( + allocated_message, + oscore_option, + oscore_context, + |request| { + self.inner.extract_request_data(request) + }, + ) else { + // FIXME is that the righ tcode? + println!("Decryption failure"); + return Err(Own(CoAPError::unauthorized())); + }; + + Ok(Own(EdhocResponse::OscoreRequest { + slot, + correlation, + extracted, + })) } } } @@ -505,12 +548,11 @@ impl<'a, H: coap_handler::Handler> coap_handler::Handler for OscoreEdhocHandler< use OrInner::{Inner, Own}; Ok(match req { - Own(req) => { + Own(EdhocResponse::OkSend2(slot)) => { // FIXME: Why does the From not do the map_err? response.set_code( M::Code::new(coap_numbers::code::CHANGED).map_err(|x| Own(x.into()))?, ); - let EdhocResponse::OkSend2(slot) = req; let pool = &mut self.pool.0.borrow_mut(); let SecContextState::EdhocResponderProcessedM1(responder) = @@ -534,6 +576,96 @@ impl<'a, H: coap_handler::Handler> coap_handler::Handler for OscoreEdhocHandler< .set_payload(message_2.as_slice()) .map_err(|x| Own(x.into()))?; } + Own(EdhocResponse::OscoreRequest { + slot, + mut correlation, + extracted, + }) => { + response.set_code( + M::Code::new(coap_numbers::code::CHANGED).map_err(|x| Own(x.into()))?, + ); + + let pool = &mut self.pool.0.borrow_mut(); + let SecContextState::Oscore(ref mut oscore_context) = &mut pool[slot] else { + // FIXME render late error (it'd help if CoAPError also offered a type that unions it + // with an arbitrary other error). As it is, depending on the CoAP stack, there may be + // DoS if a peer can send many requests before the server starts rendering responses. + panic!("State vanished before respone was built."); + }; + + // Almost-but-not: This'd require 'static on Message which we can't have b/c the + // type may be shortlived for good reason. + /* + let response: &mut dyn core::any::Any = response; + let response: &mut coap_message_implementations::inmemory_write::Message = response.downcast_mut() + .expect("libOSCORE currently only works with CoAP stacks whose response messages are inmemory_write"); + */ + // FIXME! + let response: &mut M = response; + let response: &mut coap_message_implementations::inmemory_write::Message = + unsafe { core::mem::transmute(response) }; + + response.set_code(coap_numbers::code::CHANGED); + + use crate::println; + + if liboscore::protect_response( + response, + // SECURITY BIG FIXME: How do we make sure that our correlation is really for + // what we find in the pool and not for what wound up there by the time we send + // the response? (Can't happen with the current stack, but conceptually there + // should be a tie; carry the OSCORE context in an owned way?). + oscore_context, + &mut correlation, + |response| match extracted { + Ok(extracted) => match self.inner.build_response(response, extracted) { + Ok(()) => { + println!("All fine"); + }, + // One attempt to render rendering errors + // FIXME rewind message + Err(e) => match e.render(response) { + Ok(()) => { + println!("Rendering error to successful extraction shown"); + }, + Err(_) => { + println!("Rendering error to successful extraction failed"); + // FIXME rewind message + response.set_code(coap_numbers::code::INTERNAL_SERVER_ERROR); + } + }, + }, + Err(inner_request_error) => { + match inner_request_error.render(response) { + Ok(()) => { + println!("Extraction failed, inner error rendered successfully"); + }, + Err(e) => { + // Two attempts to render extraction errors + // FIXME rewind message + match e.render(response) { + Ok(()) => { + println!("Extraction failed, inner error rendered through fallback"); + }, + Err(_) => { + println!("Extraction failed, inner error rendering failed"); + // FIXME rewind message + response.set_code( + coap_numbers::code::INTERNAL_SERVER_ERROR, + ); + } + } + } + } + } + }, + ) + .is_err() + { + println!("Oups, responding with weird state"); + // todo!("Thanks to the protect API we've lost access to our response"); + } + } Inner(i) => self.inner.build_response(response, i).map_err(Inner)?, }) }