Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
10 changes: 9 additions & 1 deletion .github/workflows/build-and-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,16 @@ jobs:
uses: actions/checkout@v3
- name: Install Rust formatter
run: rustup component add rustfmt
- name: Checkout RIOT
uses: actions/checkout@v3
with:
repository: RIOT-OS/RIOT
ref: "2023.10"
path: examples/coap-riot/RIOT
- name: Check if code is well formatted
run: cargo fmt --check
run: |
sed -i -E "s/rust_riotmodules.*//g" examples/coap-riot/Cargo.toml # avoid `cargo fmt` issues in examples/coap-riot
cargo fmt --check

unit-tests: # run before build because it is faster
needs: check-style
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,5 @@ cscope*
.DS_Store
.vscode
*.fst

bin
7 changes: 7 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ members = [
"examples/coap",
"examples/edhoc-rs-no_std",
"examples/edhoc-rs-cc2538",
"examples/coap-riot",
]

# reduced "default-members", should include only packages that can be built and
Expand Down Expand Up @@ -62,3 +63,9 @@ hacspec-sha256 = { git = "https://github.com/malishav/hacspec", branch = "aesccm
hacspec-aes = { git = "https://github.com/malishav/hacspec", branch = "aesccm" }
hacspec-aes-ccm = { git = "https://github.com/malishav/hacspec", branch = "aesccm" }
psa-crypto = { git = "https://github.com/malishav/rust-psa-crypto", branch = "baremetal" }


# Fixes ... some build failures
riot-sys.git = "https://github.com/riot-os/rust-riot-sys"
# Provides RIOT's RNG
riot-wrappers = { git = "https://github.com/riot-os/rust-riot-wrappers", branch = "rand" }
1 change: 1 addition & 0 deletions examples/coap-riot/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
RIOT
52 changes: 52 additions & 0 deletions examples/coap-riot/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
[package]
name = "edhoc-coap-riot"
version = "0.1.0"
edition = "2021"
resolver = "2"

[lib]
crate-type = ["staticlib"]

[profile.release]
# Setting the panic mode has little effect on the built code (as Rust on RIOT
# supports no unwinding), but setting it allows builds on native without using
# the nightly-only lang_items feature.
panic = "abort"
# This is a typical set of options that helps Rust binaries stay small
lto = true
codegen-units = 1
opt-level = "s"

[dependencies]
riot-wrappers = { version = "^0.8", features = [ "set_panic_handler", "panic_handler_format", "with_embedded_nal" ] }

coap-handler-implementations = "0.4"

edhoc-rs = { path = "../../lib", default-features = false }
edhoc-crypto = { path = "../../crypto", default-features = false, optional = true }
edhoc-crypto-rustcrypto = { path = "../../crypto/edhoc-crypto-rustcrypto/", optional = true }
hexlit = "0.5.3"
heapless = "0.7"

embedded-nal-minimal-coapserver = "0.3.1"
embedded-nal = "0.6"
coap-message = "0.2.3"
coap-handler = "0.1.5"
coap-numbers = "0.2.3"

# not used until hacspec backend is enabled
# embedded-alloc = "0.5.0"

# While currently this exmple does not use any RIOT modules implemented in
# Rust, that may change; it is best practice for any RIOT application that has
# its own top-level Rust crate to include rust_riotmodules from inside
# RIOTBASE.
rust_riotmodules = { path = "./RIOT/sys/rust_riotmodules/" }

[features]
default = [ "crypto-rustcrypto", "ead-none" ]
crypto-rustcrypto = [ "edhoc-crypto-rustcrypto" ] # not going through edhoc-crypto b/c there it requires std
crypto-psa = [ "edhoc-crypto/psa-baremetal" ]
crypto-cryptocell310 = [ "edhoc-crypto/cryptocell310" ]
ead-none = [ "edhoc-rs/ead-none" ]
ead-zeroconf = [ "edhoc-rs/ead-zeroconf" ]
61 changes: 61 additions & 0 deletions examples/coap-riot/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# name of your application
APPLICATION = edhoc_coap_riot

# If no BOARD is found in the environment, use this default:
BOARD ?= native

# While RIOT is flexible here, our rust_riotmodules go through Cargo.toml, and that lacks the same flexibility.
RIOTBASE = $(CURDIR)/RIOT/

CFLAGS += -DTHREAD_STACKSIZE_MAIN=20000

# Basic networking, and gcoap
USEMODULE += gcoap # just here because it pulls some useful depdenencies that enable riot_wrappers::socket_embedded_nal
# USEMODULE += netdev_default
USEMODULE += usbus_cdc_ecm
USEMODULE += auto_init_gnrc_netif
USEMODULE += gnrc_ipv6_default
USEMODULE += gnrc_icmpv6_echo
USEMODULE += sock_aux_local

USEMODULE += ztimer
USEMODULE += ztimer_usec
USEMODULE += ztimer_msec
USEMODULE += ztimer_sec

USEMODULE += vfs
USEMODULE += constfs

USEMODULE += prng_sha256prng

# crypto backend for edhoc-rs
EDHOC_RS_DIR ?= $(CURDIR)/../..
INCLUDES += -I$(EDHOC_RS_DIR)/crypto/edhoc-crypto-cryptocell310-sys/vendor/nrf_cc310/include
ARCHIVES += $(EDHOC_RS_DIR)/crypto/edhoc-crypto-cryptocell310-sys/vendor/nrf_cc310/lib/cortex-m4/hard-float/no-interrupts/libnrf_cc310_0.9.13.a

# Comment this out to disable code in RIOT that does safety checking
# which is not needed in a production environment but helps in the
# development process:
DEVELHELP ?= 1

# Change this to 0 show compiler invocation lines by default:
QUIET ?= 1

# Add 3k extra stack: The Rust examples take more of it than gcoap expects,
# presumably because the example use the standard library's sting formatting
# instead of one of the more optimized formatters.
CFLAGS += -DGCOAP_STACK_SIZE='(THREAD_STACKSIZE_DEFAULT+DEBUG_EXTRA_STACKSIZE+sizeof(coap_pkt_t)+1024)'

# The name of crate (as per Cargo.toml package name, but with '-' replaced with '_')
APPLICATION_RUST_MODULE = edhoc_coap_riot
BASELIBS += $(APPLICATION_RUST_MODULE).module

FEATURES_REQUIRED += rust_target

CARGO_CHANNEL ?= nightly

# Currently unknown, something related to the LED_PORT definition that doesn't
# pass C2Rust's transpilation
BOARD_BLACKLIST := ek-lm4f120xl

include $(RIOTBASE)/Makefile.include
10 changes: 10 additions & 0 deletions examples/coap-riot/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# edhoc-rs with coap in riot

To compile, flash, and attach serial connection:

```bash
git clone https://github.com/RIOT-OS/RIOT
make BOARD=nrf52840dk flash term
```

(As an alternative to the clone, you may crate a symlink to your RIOT checkout at `./RIOT`)
199 changes: 199 additions & 0 deletions examples/coap-riot/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
#![no_std]

use edhoc_rs::*;
use hexlit::hex;

use core::ffi::c_char;
use embedded_nal::UdpFullStack;
use riot_wrappers::{println, riot_main};

// Would be needed by hacspec backend
// use embedded_alloc::Heap;
//
// #[global_allocator]
// static HEAP: Heap = Heap::empty();

extern "C" {
pub fn mbedtls_memory_buffer_alloc_init(buf: *mut c_char, len: usize);
}

const _ID_CRED_I: &[u8] = &hex!("a104412b");
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] =
&hex!("6e5de611388a4b8a8211334ac7d37ecb52a387d257e6db3c2a93df21ff3affc8");
const CRED_R: &[u8] = &hex!("A2026008A101A5010202410A2001215820BBC34960526EA4D32E940CAD2A234148DDC21791A12AFBCBAC93622046DD44F02258204519E257236B2A0CE2023F0931F1F386CA7AFDA64FCDE0108C224C51EABF6072");
const R: &[u8] = &hex!("72cc4761dbd4c78f758931aa589d348d1ef874a7e303ede2f140dcf3e6aa4aac");

type Crypto = edhoc_crypto_rustcrypto::Crypto<riot_wrappers::random::Random>;

#[derive(Default, Debug)]
struct EdhocHandler {
connections: heapless::Vec<(u8, EdhocResponderWaitM3<'static, Crypto>), 3>,
}

impl EdhocHandler {
fn take_connection_by_c_r(&mut self, c_r: u8) -> Option<EdhocResponderWaitM3<'static, Crypto>> {
let index = self
.connections
.iter()
.position(|(current_c_r, _)| current_c_r == &c_r)?;
let last = self.connections.len() - 1;
self.connections.swap(index, last);
Some(self.connections.pop().unwrap().1)
}

fn new_c_r(&self) -> u8 {
// FIXME: We'll need to do better, but a) that'll be more practical when we can do more
// than u8, and b) that'll best be coordinated with a storage that not only stores EDHOC
// contexts but also OSCORE ones.
let result = self.connections.len();
if result >= 24 {
panic!("Contexts exceeded");
}
result as _
}
}

enum EdhocResponse {
// We could also store the responder in the Vec (once we're done rendering the response, we'll
// take up a slot there anyway) if we make it an enum.
OkSend2 {
c_r: u8,
responder: EdhocResponderBuildM2<'static, Crypto>,
},
Message3Processed,
}

impl coap_handler::Handler for EdhocHandler {
type RequestData = EdhocResponse;
fn extract_request_data(
&mut self,
request: &impl coap_message::ReadableMessage,
) -> Self::RequestData {
let starts_with_true = request.payload().get(0) == Some(&0xf5);

if starts_with_true {
let state = EdhocState::default();

let responder = EdhocResponder::new(
state,
edhoc_crypto_rustcrypto::Crypto::new(riot_wrappers::random::Random::new()),
&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 {
let c_r = self.new_c_r();
EdhocResponse::OkSend2 { c_r, responder }
} else {
panic!("How to respond to non-OK?")
}
} else {
// potentially message 3
let c_r_rcvd = request.payload()[0];
let responder = self
.take_connection_by_c_r(c_r_rcvd)
.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 Ok((mut responder, prk_out)) = result else {
println!("EDHOC processing error: {:?}", result);
// FIXME remove state from edhoc_connections
panic!("Handler can't just not respond");
};

println!("EDHOC exchange successfully completed");
println!("PRK_out: {:02x?}", prk_out);

let mut _oscore_secret = responder.edhoc_exporter(0u8, &[], 16); // label is 0
println!("OSCORE secret: {:02x?}", _oscore_secret);
let mut _oscore_salt = responder.edhoc_exporter(1u8, &[], 8); // label is 1
println!("OSCORE salt: {:02x?}", _oscore_salt);

// context of key update is a test vector from draft-ietf-lake-traces
let prk_out_new = responder.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);

_oscore_secret = responder.edhoc_exporter(0u8, &[], 16); // label is 0
println!("OSCORE secret after key update: {:02x?}", _oscore_secret);
_oscore_salt = responder.edhoc_exporter(1u8, &[], 8); // label is 1
println!("OSCORE salt after key update: {:02x?}", _oscore_salt);

EdhocResponse::Message3Processed
}
}
fn estimate_length(&mut self, _: &Self::RequestData) -> usize {
200
}
fn build_response(
&mut self,
response: &mut impl coap_message::MutableWritableMessage,
req: Self::RequestData,
) {
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();
self.connections.push((c_r, responder));
response.set_payload(message_2.as_slice());
}
EdhocResponse::Message3Processed => (), // "send empty ack back"?
};
}
}

fn build_handler() -> impl coap_handler::Handler {
use coap_handler_implementations::{HandlerBuilder, ReportingHandlerBuilder};

let edhoc: EdhocHandler = Default::default();

coap_handler_implementations::new_dispatcher()
.at_with_attributes(&[".well-known", "edhoc"], &[], edhoc)
.with_wkc()
}

fn main_on_stack<S: UdpFullStack>(stack: &mut S) {
let mut sock = stack.socket().expect("Can't create a socket");

let mut handler = build_handler();

stack.bind(&mut sock, 5683).expect("Can't bind to port");

loop {
match embedded_nal_minimal_coapserver::poll(stack, &mut sock, &mut handler) {
Err(embedded_nal::nb::Error::WouldBlock) => {
// See <https://github.com/rust-embedded-community/embedded-nal/issues/47>
riot_wrappers::ztimer::Clock::msec().sleep_ticks(50);
}
e => e.expect("UDP error during send/receive"),
}
}
}

riot_main!(main);

fn main() {
#[cfg(feature = "crypto-psa")]
{
// Memory buffer for mbedtls
let mut buffer: [c_char; 4096 * 2] = [0; 4096 * 2];
unsafe {
mbedtls_memory_buffer_alloc_init(buffer.as_mut_ptr(), buffer.len());
}
}

let mut stack = riot_wrappers::socket_embedded_nal::Stack::<1>::new();
stack.run(|mut s| main_on_stack(&mut s));
}
Loading