Skip to content

Commit

Permalink
coap: Functional request/response
Browse files Browse the repository at this point in the history
  • Loading branch information
chrysn committed Apr 19, 2024
1 parent 138193a commit b9e4d70
Show file tree
Hide file tree
Showing 3 changed files with 183 additions and 51 deletions.
7 changes: 5 additions & 2 deletions examples/coap/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 1 addition & 4 deletions examples/coap/fauxhoc.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
222 changes: 177 additions & 45 deletions examples/coap/src/seccontext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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),
}

Expand Down Expand Up @@ -181,11 +188,16 @@ impl<'a, H: coap_handler::Handler> OscoreEdhocHandler<'a, H> {
}
}

pub enum EdhocResponse {
pub enum EdhocResponse<I> {
// 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
Expand Down Expand Up @@ -241,7 +253,8 @@ impl<O: RenderableOnMinimal, I: RenderableOnMinimal> RenderableOnMinimal for OrI
}

impl<'a, H: coap_handler::Handler> coap_handler::Handler for OscoreEdhocHandler<'a, H> {
type RequestData = OrInner<EdhocResponse, H::RequestData>;
type RequestData =
OrInner<EdhocResponse<Result<H::RequestData, H::ExtractRequestError>>, H::RequestData>;

type ExtractRequestError = OrInner<CoAPError, H::ExtractRequestError>;
type BuildResponseError<M: MinimalWritableMessage> =
Expand Down Expand Up @@ -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)?;
Expand All @@ -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
Expand All @@ -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;
Expand All @@ -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
Expand All @@ -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,
}))
}
}
}
Expand All @@ -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<O> 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) =
Expand All @@ -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)?,
})
}
Expand Down

0 comments on commit b9e4d70

Please sign in to comment.