Skip to content

Commit

Permalink
coap: Enable use of ID_CRED_I by value
Browse files Browse the repository at this point in the history
  • Loading branch information
chrysn committed May 15, 2024
1 parent 86d9665 commit 378c48a
Show file tree
Hide file tree
Showing 3 changed files with 84 additions and 28 deletions.
6 changes: 3 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

58 changes: 46 additions & 12 deletions examples/coap/fauxhoc.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
# /// script
# requires-python = ">= 3.10"
# dependencies = [
# # 0.3.0 is insufficient for --random-identity, but works for the default case
# "lakers-python == 0.3.0",
# "aiocoap[oscore] == 0.4.8",
# "cbor2",
Expand All @@ -25,14 +26,23 @@

import asyncio
import random
import argparse

import cbor2
from aiocoap import oscore, Message, POST, Context, GET
from aiocoap import oscore, Message, POST, Context, GET, error

import lakers

import coap_console

p = argparse.ArgumentParser()
p.add_argument("--random-identity", help="Instead of using the known credential, make one up. Chances are the server will not accept this for privileged operations.", action="store_true")
p.add_argument("peer", help="URI (scheme and host); defaults to the current RIOT-rs default {default}", default="coap://10.42.0.61", nargs="?")
args = p.parse_args()

if args.peer.count("/") != 2:
p.error("Peer should be given as 'coap://[2001:db8:;1]' or similar, without trailing slash.")

# Someone told us that these are the credentials of devices that are our legitimate peers
eligible_responders_ccs = {
bytes.fromhex(
Expand All @@ -48,10 +58,28 @@
# when ID_CRED_R is CRED_R
eligible_responders |= {ccs: ccs for ccs in eligible_responders_ccs}

CRED_I = bytes.fromhex(
"A2027734322D35302D33312D46462D45462D33372D33322D333908A101A5010202412B2001215820AC75E9ECE3E50BFC8ED60399889522405C47BF16DF96660A41298CB4307F7EB62258206E5DE611388A4B8A8211334AC7D37ECB52A387D257E6DB3C2A93DF21FF3AFFC8"
)
I = bytes.fromhex("fb13adeb6518cee5f88417660841142e830a81fe334380a953406a1305e8706b")
if args.random_identity:
KEY_I, public = lakers.p256_generate_key_pair()
# For now, that's all Lakers understands; it doesn't even look into -3 (y)
# b/c it doesn't need it for key derivation, which is fortunate because the
# generator doesn't produce one either. (It's not like this key is going to
# be used for signing or encryption).
cred_i_data = {2: "me", 8: {1: {1: 2, 2: b'\x2b', -1: 1, -2: public, -3: b'0'}}}
# We could slim it down to
# >>> cred_i_data = {8: {1: {1: 2, -1: 1, -2: public}}}
# but even if the peer had the code to process that into a valid
# credential, the Lakers Python API currently doesn't allow creating
# credential through any other code path than through CredentialRPK::parse.
CRED_I = cbor2.dumps(cred_i_data)
cred_i_mode = lakers.CredentialTransfer.ByValue
else:
# Those are currently hardcoded, will late be configurable, and ultimately not needed if using ACE
CRED_I = bytes.fromhex(
"A2027734322D35302D33312D46462D45462D33372D33322D333908A101A5010202412B2001215820AC75E9ECE3E50BFC8ED60399889522405C47BF16DF96660A41298CB4307F7EB62258206E5DE611388A4B8A8211334AC7D37ECB52A387D257E6DB3C2A93DF21FF3AFFC8"
)
KEY_I = bytes.fromhex("fb13adeb6518cee5f88417660841142e830a81fe334380a953406a1305e8706b")
# Because the peer knows, but also because it's just a bit too long to pass around by value
cred_i_mode = lakers.CredentialTransfer.ByReference


class EdhocSecurityContext(
Expand All @@ -61,7 +89,7 @@ def __init__(self, initiator, c_ours, c_theirs):
# initiator could also be responder, and only this line would need to change
# FIXME Only ByReference implemented in edhoc.rs so far
self.message_3, _i_prk_out = initiator.prepare_message_3(
lakers.CredentialTransfer.ByReference, None
cred_i_mode, None
)

if initiator.selected_cipher_suite() == 2:
Expand Down Expand Up @@ -116,7 +144,7 @@ async def main():

msg1 = Message(
code=POST,
uri="coap://10.42.0.61/.well-known/edhoc",
uri=args.peer + "/.well-known/edhoc",
payload=cbor2.dumps(True) + message_1,
# payload=b"".join(cbor2.dumps(x) for x in [True, 3, 2, b'\0' * 32, 0]),
)
Expand All @@ -132,28 +160,34 @@ async def main():

cred_r = eligible_responders[id_cred_r]
initiator.verify_message_2(
I, CRED_I, cred_r
KEY_I, CRED_I, cred_r
) # odd that we provide that here rather than in the next function

oscore_context = EdhocSecurityContext(initiator, c_i, c_r)

ctx.client_credentials["coap://10.42.0.61/*"] = oscore_context
ctx.client_credentials[args.peer + "/*"] = oscore_context

msg3 = Message(
code=GET,
uri="coap://10.42.0.61/.well-known/core",
uri=args.peer + "/.well-known/core",
)

print((await ctx.request(msg3).response_raising).payload.decode("utf8"))

normalrequest = Message(
code=GET,
uri="coap://10.42.0.61/poem",
uri=args.peer + "/poem",
)
print((await ctx.request(normalrequest).response_raising).payload.decode("utf8"))

print("Reading stdiout through OSCORE:")
await coap_console.read_stream_to_console(ctx, "coap://10.42.0.61/stdout")
try:
# pre-flight b/c read_stream_to_console has bad error reporting
await ctx.request(Message(code=GET, uri=args.peer + "/stdout")).response_raising
except error.ResponseWrappingError as e:
print("Received response but no success:", e.coapmessage.code, e.coapmessage.payload.decode('utf8'))
else:
await coap_console.read_stream_to_console(ctx, args.peer + "/stdout")

await ctx.shutdown()

Expand Down
48 changes: 35 additions & 13 deletions examples/coap/src/seccontext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -565,26 +565,48 @@ impl<'a, H: coap_handler::Handler, L: Write> coap_handler::Handler
return Err(Own(CoAPError::bad_request()));
}

// FIXME: Right now this can only do credential-by-value

let cred_i;
let authorization;

if id_cred_i.reference_only() {
writeln!(self.log, "Got reference only, need to upgrade");
match id_cred_i.kid {
43 => {
writeln!(self.log, "Peer indicates use of the one preconfigured key");

use hexlit::hex;
const CRED_I: &[u8] = &hex!("A2027734322D35302D33312D46462D45462D33372D33322D333908A101A5010202412B2001215820AC75E9ECE3E50BFC8ED60399889522405C47BF16DF96660A41298CB4307F7EB62258206E5DE611388A4B8A8211334AC7D37ECB52A387D257E6DB3C2A93DF21FF3AFFC8");

cred_i = lakers::CredentialRPK::new(
CRED_I.try_into().expect("Static credential is too large"),
)
.expect("Static credential is not processable");

// FIXME: learn from CRED_I
authorization = AifStaticRest { may_use_stdout: true };
}
_ => {
// FIXME: send better message
return Err(Own(CoAPError::bad_request()));
},
}
} else {
writeln!(self.log, "Got full credential, need to evaluate");
}
writeln!(self.log, "Got credential by value: {:?}..", &id_cred_i.value.get_slice(0, 5));

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");
cred_i = lakers::CredentialRPK::new(id_cred_i.value)
// FIXME What kind of error do we send here?
.map_err(|_| Own(CoAPError::bad_request()))?;

// FIXME: Do we want to continue at all? At least we don't allow
// stdout, but let's otherwise continue with the privileges of an
// unencrypted peer (allowing opportunistic encryption b/c we have
// enough slots to spare for some low-priority connections)
authorization = AifStaticRest { may_use_stdout: false };
}

let (mut responder, _prk_out) =
responder.verify_message_3(cred_i).map_err(render_error)?;

// FIXME: learn from CRED_I
let authorization = AifStaticRest { may_use_stdout: true };

let oscore_secret = responder.edhoc_exporter(0u8, &[], 16); // label is 0
let oscore_salt = responder.edhoc_exporter(1u8, &[], 8); // label is 1
let oscore_secret = &oscore_secret[..16];
Expand Down

0 comments on commit 378c48a

Please sign in to comment.