diff --git a/Cargo.lock b/Cargo.lock index be5c96000..61cfb5a0e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2207,7 +2207,7 @@ dependencies = [ [[package]] name = "lakers" version = "0.5.1" -source = "git+https://github.com/openwsn-berkeley/lakers/#c0d74ac59b2fc68ebedf7882975ef0ce546cc2c5" +source = "git+https://github.com/openwsn-berkeley/lakers/#7c54187adedb607392435eb0ab832af68403a356" dependencies = [ "lakers-shared", ] @@ -2215,7 +2215,7 @@ dependencies = [ [[package]] name = "lakers-crypto-rustcrypto" version = "0.5.1" -source = "git+https://github.com/openwsn-berkeley/lakers/#c0d74ac59b2fc68ebedf7882975ef0ce546cc2c5" +source = "git+https://github.com/openwsn-berkeley/lakers/#7c54187adedb607392435eb0ab832af68403a356" dependencies = [ "aead", "aes", @@ -2230,7 +2230,7 @@ dependencies = [ [[package]] name = "lakers-shared" version = "0.5.1" -source = "git+https://github.com/openwsn-berkeley/lakers/#c0d74ac59b2fc68ebedf7882975ef0ce546cc2c5" +source = "git+https://github.com/openwsn-berkeley/lakers/#7c54187adedb607392435eb0ab832af68403a356" [[package]] name = "lalrpop" diff --git a/examples/coap/fauxhoc.py b/examples/coap/fauxhoc.py index 3cf024f21..11cf3d65f 100755 --- a/examples/coap/fauxhoc.py +++ b/examples/coap/fauxhoc.py @@ -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", @@ -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( @@ -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( @@ -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: @@ -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]), ) @@ -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() diff --git a/examples/coap/src/seccontext.rs b/examples/coap/src/seccontext.rs index e69664925..a2fd704d7 100644 --- a/examples/coap/src/seccontext.rs +++ b/examples/coap/src/seccontext.rs @@ -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];