Skip to content

Add OSCORE Support #47

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 38 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
0c90da9
first oscore functions wrappers & oscore conf struct
ln4c Dec 19, 2024
597ca62
Merge branch 'namib-project:main' into oscore_dev
JonathanBroering Dec 19, 2024
1a9a15b
removed hardcoded oscore conf to read from file instead, added functi…
ln4c Jan 8, 2025
548cf0c
Fix for add_oscore_conf
JonathanBroering Jan 9, 2025
be20cf7
Fix for add_new_oscore_recipient
JonathanBroering Jan 9, 2025
9e4bd76
added functions to read/write the (initial) sequence number from/to n…
ln4c Jan 15, 2025
71c404a
refactor: removed unnecessary argument passing from read_initial_sequ…
ln4c Jan 16, 2025
749ec4a
added function to delete_oscore_recipient (wip)
ln4c Jan 16, 2025
c93bd34
fixed inconsistency in handling the creation of OscoreConf
ln4c Jan 16, 2025
cdea622
fixed lifetimes for adding and removing oscore_recipients
ln4c Jan 22, 2025
c23dc04
added oscore-feature-flag to configure oscore support
ln4c Jan 23, 2025
e4d0ff1
reverted accidental previous mistake
ln4c Jan 23, 2025
ca31f06
Merge remote-tracking branch 'upstream/main' into oscore_dev
ln4c Jan 25, 2025
b34c0f7
started refactoring and saving OscoreRecipients in the CoapContext (wip)
ln4c Jan 30, 2025
0b25d3c
added function to directly create oscore_conf from bytes provided
ln4c Jan 31, 2025
bef83ae
added function to create and add a oscore_conf from bytes provided, a…
ln4c Jan 31, 2025
af1a0d5
refactored some functions & dropped 1 deprecated, changed visibility …
ln4c Feb 5, 2025
545a361
renamed method add_oscore_conf_from_bytes -> add_oscore_conf
ln4c Feb 5, 2025
f86b0e5
removed oscore_conf from context (handled by libcoap)
ln4c Feb 18, 2025
c58a9d0
safety for adding/removing recipients (and handling the raw pointer t…
ln4c Feb 19, 2025
b1ca7ff
adding/removing recipients should only be allowed if proper oscore in…
ln4c Feb 19, 2025
d32d7c7
explicitly handle invalid oscore configs (wip)
ln4c Feb 19, 2025
2ae9002
proper error handling/disclosure, more safety explanations
ln4c Feb 20, 2025
3aafc56
fixed double free() for initial recipient_id (from config)
ln4c Feb 20, 2025
5f9753d
refactor & rename, added std-feature-flag, OscoreConf public again, m…
ln4c Feb 25, 2025
1afb629
fix: imports for std <-> no_std
ln4c Feb 27, 2025
e3cbca2
reverted cloning of OscoreConf raw_conf if favor of a valid-bit to ke…
ln4c Mar 6, 2025
8cf67d3
Improve Comments
JonathanBroering Mar 10, 2025
20b9bdf
removed std-variant of OscoreConf::new
ln4c Mar 20, 2025
6390a42
Merge remote-tracking branch 'upstream/main' into oscore_dev
ln4c Mar 20, 2025
8578d91
small refactor of error handling, improved annotations further
ln4c Mar 20, 2025
4505ffc
fixed typos
ln4c Mar 20, 2025
d8bf64b
fixed linter error
ln4c Mar 20, 2025
262ddfe
fixed typo
ln4c Apr 9, 2025
3d30a2f
added missing license/copyright header
ln4c Apr 16, 2025
5fea7d9
removed OscoreRecipient, improved recipient handling
ln4c Apr 19, 2025
2093521
Merge remote-tracking branch 'upstream/main' into oscore_dev
ln4c May 13, 2025
b9976cf
improve OscoreConf handling (wip)
ln4c May 15, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions libcoap/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ dtls-mbedtls-sys = ["libcoap-sys/dtls-mbedtls-sys"]
dtls-tinydtls-sys = ["libcoap-sys/dtls-tinydtls-sys"]
dtls-openssl-sys-vendored = ["libcoap-sys/dtls-openssl-sys-vendored"]
dtls-tinydtls-sys-vendored = ["libcoap-sys/dtls-tinydtls-sys-vendored"]
oscore = ["libcoap-sys/oscore"]

[dependencies]
libcoap-sys = { version = "^0.2.2", path = "../libcoap-sys", default-features = false, features = ["client", "server"] }
Expand Down
208 changes: 194 additions & 14 deletions libcoap/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,22 @@
//! Module containing context-internal types and traits.

use core::ffi::c_uint;
#[cfg(feature = "dtls-pki")]
use std::ffi::CString;
#[cfg(feature = "dtls")]
use std::ptr::NonNull;
use std::{
any::Any,
ffi::{c_void, CStr},
fmt::Debug,
net::SocketAddr,
ops::Sub,
sync::Once,
time::Duration,
};
#[cfg(all(feature = "dtls-pki", unix))]
use std::{os::unix::ffi::OsStrExt, path::Path};

#[cfg(feature = "dtls-pki")]
use libcoap_sys::coap_context_set_pki_root_cas;
use libcoap_sys::{
Expand All @@ -30,19 +46,13 @@ use libcoap_sys::{
coap_event_t_COAP_EVENT_SESSION_FAILED, coap_event_t_COAP_EVENT_TCP_CLOSED, coap_event_t_COAP_EVENT_TCP_CONNECTED,
coap_event_t_COAP_EVENT_TCP_FAILED, coap_event_t_COAP_EVENT_WS_CLOSED, coap_event_t_COAP_EVENT_WS_CONNECTED,
coap_event_t_COAP_EVENT_WS_PACKET_SIZE, coap_event_t_COAP_EVENT_XMIT_BLOCK_FAIL, coap_free_context,
coap_get_app_data, coap_io_process, coap_new_context, coap_proto_t, coap_proto_t_COAP_PROTO_DTLS,
coap_proto_t_COAP_PROTO_TCP, coap_proto_t_COAP_PROTO_UDP, coap_register_event_handler,
coap_register_response_handler, coap_set_app_data, coap_startup_with_feature_checks, COAP_BLOCK_SINGLE_BODY,
COAP_BLOCK_USE_LIBCOAP, COAP_IO_WAIT,
coap_get_app_data, coap_io_process, coap_join_mcast_group_intf, coap_new_bin_const, coap_new_context, coap_proto_t,
coap_proto_t_COAP_PROTO_DTLS, coap_proto_t_COAP_PROTO_TCP, coap_proto_t_COAP_PROTO_UDP,
coap_register_event_handler, coap_register_response_handler, coap_set_app_data, coap_startup_with_feature_checks,
COAP_BLOCK_SINGLE_BODY, COAP_BLOCK_USE_LIBCOAP, COAP_IO_WAIT,
};
use std::ffi::CStr;
#[cfg(feature = "dtls-pki")]
use std::ffi::CString;
#[cfg(feature = "dtls")]
use std::ptr::NonNull;
use std::{any::Any, ffi::c_void, fmt::Debug, net::SocketAddr, ops::Sub, sync::Once, time::Duration};
#[cfg(all(feature = "dtls-pki", unix))]
use std::{os::unix::ffi::OsStrExt, path::Path};
#[cfg(feature = "oscore")]
use libcoap_sys::{coap_context_oscore_server, coap_delete_oscore_recipient, coap_new_oscore_recipient};

#[cfg(any(feature = "dtls-rpk", feature = "dtls-pki"))]
use crate::crypto::pki_rpk::ServerPkiRpkCryptoContext;
Expand All @@ -56,9 +66,12 @@ use crate::{
session::{session_response_handler, CoapServerSession, CoapSession},
transport::CoapEndpoint,
};

//TODO: New feature?
use libcoap_sys::coap_join_mcast_group_intf;
#[cfg(feature = "oscore")]
use crate::{
error::{OscoreRecipientError, OscoreServerCreationError},
OscoreConf,
};

static COAP_STARTUP_ONCE: Once = Once::new();

Expand All @@ -85,6 +98,13 @@ struct CoapContextInner<'a> {
/// PKI context for encrypted server-side sessions.
#[cfg(any(feature = "dtls-pki", feature = "dtls-rpk"))]
pki_rpk_context: Option<ServerPkiRpkCryptoContext<'a>>,
/// Saves if appropriate oscore information has been added to the raw_context as stated by
/// libcoap to assume before adding/removing additional recipient_id's to it.
#[cfg(feature = "oscore")]
provided_oscore_information: bool,
/// A list of recipients associated with this context.
#[cfg(feature = "oscore")]
recipients: Vec<Box<String>>,
}

/// A CoAP Context — container for general state and configuration information relating to CoAP
Expand Down Expand Up @@ -133,6 +153,10 @@ impl<'a> CoapContext<'a> {
psk_context: None,
#[cfg(any(feature = "dtls-pki", feature = "dtls-rpk"))]
pki_rpk_context: None,
#[cfg(feature = "oscore")]
provided_oscore_information: false,
#[cfg(feature = "oscore")]
recipients: Vec::new(),
});

// SAFETY: We checked that the raw context is not null, the provided function is valid and
Expand Down Expand Up @@ -401,6 +425,158 @@ impl CoapContext<'_> {
self.add_endpoint(addr, coap_proto_t_COAP_PROTO_TCP)
}

/// Set the context's default OSCORE configuration for a server.
///
/// # Errors
/// Will return a [OscoreServerCreationError] if adding the oscore configuration fails.
#[cfg(feature = "oscore")]
pub fn oscore_server(&mut self, oscore_conf: OscoreConf) -> Result<(), OscoreServerCreationError> {
let mut inner_ref = self.inner.borrow_mut();
let result: i32;
let (raw_conf, initial_recipient) = oscore_conf.into_raw_conf();

// SAFETY: Properly initialized CoapContext always has a valid raw_context that is not deleted until
// the CoapContextInner is dropped. OscoreConf raw_conf should be valid, else return an error.
//
// coap_context_oscore_server will also always free the raw_conf, regardless of the result:
// [libcoap docs](https://libcoap.net/doc/reference/4.3.5/group__oscore.html#ga71ddf56bcd6d6650f8235ee252fde47f)
unsafe {
result = coap_context_oscore_server(inner_ref.raw_context, raw_conf);
};

// Check whether adding the config to the context failed.
if result == 0 {
return Err(OscoreServerCreationError::Unknown);
}

// Add the initial_recipient (if present).
if let Some(initial_recipient) = initial_recipient {
inner_ref.recipients.push(Box::new(initial_recipient));
}

// Mark context as valid oscore server.
inner_ref.provided_oscore_information = true;
Ok(())
}

/// Adds a recipient_id to the OscoreContext.
///
/// # Errors
/// Will return a [OscoreRecipientError] if adding the recipient failed.
/// You might want to check the error to determine if adding the recipient failed
/// due to the recipient_id beeing already added to the context.
/// Trying to call this on a CoapContext without appropriate oscore information will
/// also result in an error.
#[cfg(feature = "oscore")]
pub fn new_oscore_recipient(&mut self, recipient_id: &str) -> Result<(), OscoreRecipientError> {
let mut inner_ref = self.inner.borrow_mut();

// Return if context has no appropriate oscore information.
if !inner_ref.provided_oscore_information {
#[cfg(debug_assertions)]
eprintln!("tried adding recipient to context with no appropriate oscore information");
return Err(OscoreRecipientError::NoOscoreContext);
}

let recipient = recipient_id.to_string();

// Return if the recipient is already added as the coap_new_oscore_recipient
// would free the raw struct for duplicate recipients.
// As we can't check why adding the recipient failed we have to filter this
// case to prevent a double free.
if inner_ref.recipients.iter().any(|elem| *elem.as_ref() == recipient) {
return Err(OscoreRecipientError::DuplicateId);
}

let raw_recipient;
let result;

// SAFETY: If adding the recipient to the context fails we drop its underlying raw
// struct manually as it's not handled by libcoap except for when trying to add a duplicate
// recipient, which is filtered out above because libcoap does not offer a proper way to
// determine the cause of the failure.
// The raw_recipient is checked for validity before use and properly initialized
// CoapContext should always have a valid raw_context until CoapContextInner is dropped.
unsafe {
raw_recipient = coap_new_bin_const(recipient.as_ptr(), recipient.len());
if raw_recipient.is_null() {
return Err(OscoreRecipientError::Unknown);
}
result = coap_new_oscore_recipient(inner_ref.raw_context, raw_recipient);
};

// If adding the recipient failed...
if result == 0 {
// ...box the raw_pointer of the recipient to free it.
// SAFETY: The raw_recipient's raw_pointer has to be valid here because libcoap does
// only free the recipient if adding it failed due to a duplicate, which is filtered
// out above because libcoap currently does not offer a proper way to determine the
// cause of the failure as of version 4.3.5.
unsafe {
let _ = Box::from_raw(raw_recipient);
}
return Err(OscoreRecipientError::Unknown);
}

// Else box the recipient and add it to the CoapContext to keep its raw_pointer valid.
inner_ref.recipients.push(Box::new(recipient));

Ok(())
}

/// Removes a recipient_id from the OscoreContext.
///
/// # Errors
/// Will return a [OscoreRecipientError] if removing the recipient failed.
/// You might want to check the error to determine if removing the recipient failed
/// due to the recipient_id beeing not present within the context.
/// Trying to call this on a CoapContext without appropriate oscore information will
/// also result in an error.
#[cfg(feature = "oscore")]
pub fn delete_oscore_recipient(&mut self, recipient_id: &str) -> Result<(), OscoreRecipientError> {
let mut inner_ref = self.inner.borrow_mut();

// Return if context has no appropriate oscore information.
if !inner_ref.provided_oscore_information {
#[cfg(debug_assertions)]
eprintln!("tried removing recipient from context with no appropriate oscore information");
return Err(OscoreRecipientError::NoOscoreContext);
}

let recipient = recipient_id.to_string();

// Search for the recipient and try to remove it if present.
if let Some(index) = inner_ref.recipients.iter().position(|elem| *elem.as_ref() == recipient) {
let result: i32;

// SAFETY: If libcoap successfully removed the raw_recipient from the raw_context
// it is also freed. The recipient's raw_struct should always be valid, as it would
// have been removed from the context once deleting it succeeded.
// The raw_recipient is checked for validity before use and properly initialized
// CoapContext should always have a valid raw_context until CoapContextInner is
// dropped.
unsafe {
let raw_recipient = coap_new_bin_const(recipient.as_ptr(), recipient.len());
if raw_recipient.is_null() {
return Err(OscoreRecipientError::Unknown);
}
result = coap_delete_oscore_recipient(inner_ref.raw_context, raw_recipient);
};

// Check whether removing the recipient from the CoapContext's raw_context failed.
if result == 0 {
return Err(OscoreRecipientError::Unknown);
}

// Remove the recipient from the CoapContext if it has been successfully removed
// from the CoapContext's raw_context. At this point the raw_recipient has been dropped
// by libcoap.
inner_ref.recipients.remove(index);
return Ok(());
}
Err(OscoreRecipientError::NotFound)
}

/// Creates a new DTLS endpoint that is bound to the given address.
///
/// Note that in order to actually connect to DTLS clients, you need to set a crypto provider
Expand Down Expand Up @@ -719,5 +895,9 @@ impl Drop for CoapContextInner<'_> {
unsafe {
coap_free_context(self.raw_context);
}

// Drop all recipients as coap_free_context() has dropped all associated raw_recipients.
#[cfg(feature = "oscore")]
self.recipients.clear();
}
}
56 changes: 56 additions & 0 deletions libcoap/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,52 @@ pub enum EndpointCreationError {
Unknown,
}

#[cfg(feature = "oscore")]
#[derive(Error, Debug, Copy, Clone, Eq, PartialEq)]
pub enum OscoreConfigError {
/// Tried to get oscore config as raw struct which has been invalidated before
#[error("Oscore config error: tried to get oscore config as raw struct which has been invalidated before")]
Invalid,
/// Unknown error inside of libcoap, probably due to missing/invalid entries in your oscore
/// config
#[error("Oscore config error: unknown error in call to libcoap, probably due to missing/invalid entries in your oscore config")]
Unknown,
}

#[cfg(feature = "oscore")]
#[derive(Error, Debug, Copy, Clone, Eq, PartialEq)]
pub enum OscoreServerCreationError {
/// Oscore config seems to be invalid, make sure to use it only once
#[error("Oscore server creation error: oscore config seems to be invalid, make sure to use it only once")]
OscoreConfigInvalid,
/// Unknown error inside of libcoap
#[error("Oscore server creation error: unknown error in call to libcoap")]
Unknown,
}
#[cfg(feature = "oscore")]
impl From<OscoreConfigError> for OscoreServerCreationError {
fn from(_error: OscoreConfigError) -> Self {
OscoreServerCreationError::OscoreConfigInvalid
}
}

#[cfg(feature = "oscore")]
#[derive(Error, Debug, Copy, Clone, Eq, PartialEq)]
pub enum OscoreRecipientError {
/// Method is called on a context without appropriate oscore information
#[error("Oscore recipient error: context is missing appropriate oscore information")]
NoOscoreContext,
/// Tried adding duplicate recipient to context
#[error("Oscore recipient error: tried adding duplicate recipient to context")]
DuplicateId,
/// Tried removing a recipient that is not associated with the context
#[error("Oscore recipient error: tried removing a recipient that is not associated with the context")]
NotFound,
/// Unknown error inside of libcoap, adding/removing a recipient failed
#[error("Oscore recipient error: unknown error in call to libcoap, adding/removing the recipient failed")]
Unknown,
}

#[derive(Error, Debug, Copy, Clone, Eq, PartialEq)]
pub enum ContextConfigurationError {
/// Unknown error inside of libcoap
Expand Down Expand Up @@ -81,6 +127,16 @@ pub enum SessionCreationError {
/// Unknown error inside of libcoap
#[error("CoAP session creation error: unknown error in call to libcoap")]
Unknown,
/// Oscore config seems to be invalid, make sure to use it only once
#[cfg(feature = "oscore")]
#[error("CoAP session creation error: oscore config seems to be invalid, make sure to use it only once")]
OscoreConfigInvalid,
}
#[cfg(feature = "oscore")]
impl From<OscoreConfigError> for SessionCreationError {
fn from(_error: OscoreConfigError) -> Self {
SessionCreationError::OscoreConfigInvalid
}
}

#[derive(Error, Debug, Copy, Clone, Eq, PartialEq)]
Expand Down
4 changes: 4 additions & 0 deletions libcoap/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,8 @@ extern crate core;

pub use context::CoapContext;
pub use event::CoapEventHandler;
#[cfg(feature = "oscore")]
pub use oscore::OscoreConf;
pub use resource::{CoapRequestHandler, CoapResource};

mod context;
Expand All @@ -176,6 +178,8 @@ pub mod error;
mod event;
mod mem;
pub mod message;
#[cfg(feature = "oscore")]
pub mod oscore;
pub mod prng;
pub mod protocol;
mod resource;
Expand Down
Loading
Loading