diff --git a/ead/edhoc-ead-zeroconf/src/lib.rs b/ead/edhoc-ead-zeroconf/src/lib.rs index 6d252fa8..70bfab20 100644 --- a/ead/edhoc-ead-zeroconf/src/lib.rs +++ b/ead/edhoc-ead-zeroconf/src/lib.rs @@ -3,7 +3,8 @@ use edhoc_consts::*; use edhoc_crypto::*; -// initiator side +// ---- initiator side (device) + #[derive(Default, PartialEq, Copy, Clone, Debug)] pub enum EADInitiatorProtocolState { #[default] @@ -228,7 +229,8 @@ fn encode_ead_1(loc_w: &EdhocMessageBuffer, enc_id: &EdhocMessageBuffer) -> Edho output } -// responder side +// ---- responder side (authenticator) + #[derive(Default, PartialEq, Copy, Clone, Debug)] pub enum EADResponderProtocolState { #[default] @@ -264,7 +266,7 @@ pub fn ead_responder_set_global_state(new_state: EADResponderState) { } } -// FIXME: receive opaque_state as parameter, but that requires changing the r_process_message_1 function signature +// FIXME: do some plumbing to receive opaque_state as parameter use hexlit::hex; const OPAQUE_STATE_TV: &[u8] = &hex!("827819666538303a3a623833343a643630623a373936663a38646530198bed"); @@ -286,14 +288,15 @@ pub fn r_process_ead_1(ead_1: &EADItem, message_1: &BufferMessage1) -> Result<() Ok(()) } -pub fn r_prepare_ead_2() -> Option { +pub fn r_prepare_ead_2(voucher_response: &EdhocMessageBuffer) -> Option { let mut ead_2 = EADItem::new(); - // add the label to the buffer (non-critical) + // FIXME: we probably don't want to parse the voucher response here, but rather receive only the 'voucher' already parsed + let (_message_1, voucher, _opaque_state) = parse_voucher_response(voucher_response).unwrap(); + ead_2.label = EAD_ZEROCONF_LABEL; ead_2.is_critical = true; - - // TODO: append Voucher (H(message_1), CRED_V) to the buffer + ead_2.value = Some(voucher); // NOTE: see the note in lib.rs::test_ead // state.protocol_state = EADResponderProtocolState::WaitMessage3; @@ -312,6 +315,43 @@ pub fn r_process_ead_3(_ead_3: EADItem) -> Result<(), ()> { Ok(()) } +fn parse_voucher_response( + voucher_response: &EdhocMessageBuffer, +) -> Result<(EdhocMessageBuffer, EdhocMessageBuffer, EdhocMessageBuffer), ()> { + let mut message_1 = EdhocMessageBuffer::new(); + let mut voucher = EdhocMessageBuffer::new(); + let mut opaque_state = EdhocMessageBuffer::new(); + + let array_size = voucher_response.content[0] - CBOR_MAJOR_ARRAY; + + if !(array_size == 2 || array_size == 3) || voucher_response.content[1] != CBOR_BYTE_STRING { + return Err(()); + } + + message_1.len = voucher_response.content[2] as usize; + message_1.content[..message_1.len] + .copy_from_slice(&voucher_response.content[3..3 + message_1.len]); + + if voucher_response.content[3 + message_1.len] != CBOR_BYTE_STRING { + return Err(()); + } + voucher.len = voucher_response.content[4 + message_1.len] as usize; + voucher.content[..voucher.len].copy_from_slice( + &voucher_response.content[5 + message_1.len..5 + message_1.len + voucher.len], + ); + + if voucher_response.content[5 + message_1.len + voucher.len] != CBOR_BYTE_STRING { + return Err(()); + } + opaque_state.len = voucher_response.content[6 + message_1.len + voucher.len] as usize; + opaque_state.content[..opaque_state.len].copy_from_slice( + &voucher_response.content + [7 + message_1.len + voucher.len..7 + message_1.len + voucher.len + opaque_state.len], + ); + + Ok((message_1, voucher, opaque_state)) +} + fn parse_ead_1_value( ead_1_value: &Option, ) -> Result<(EdhocMessageBuffer, EdhocMessageBuffer), ()> { @@ -347,7 +387,8 @@ pub fn encode_voucher_request( output } -// enrollment server +// ---- enrollment server side + fn handle_voucher_request( vreq: &EdhocMessageBuffer, cred_v: &EdhocMessageBuffer, @@ -514,6 +555,10 @@ mod test_vectors { &hex!("d99c86cf666f614d82cc3cfd0fb53cfa393f463f42ece49e38b056808ad5dfc9"); pub const VOUCHER_TV: &[u8] = &hex!("5820d99c86cf666f614d82cc3cfd0fb53cfa393f463f42ece49e38b056808ad5dfc9"); + + // EAD_2 + pub const EAD2_VALUE_TV: &[u8] = + &hex!("5820d99c86cf666f614d82cc3cfd0fb53cfa393f463f42ece49e38b056808ad5dfc9"); } #[cfg(test)] @@ -619,6 +664,38 @@ mod test_responder { EADResponderProtocolState::ProcessedEAD1 ); } + + #[test] + fn test_parse_voucher_response() { + let voucher_response_tv: EdhocMessageBuffer = VOUCHER_RESPONSE_TV.try_into().unwrap(); + let message_1_tv: EdhocMessageBuffer = MESSAGE_1_WITH_EAD_TV.try_into().unwrap(); + let voucher_tv: EdhocMessageBuffer = VOUCHER_TV.try_into().unwrap(); + let opaque_state_tv: EdhocMessageBuffer = OPAQUE_STATE_TV.try_into().unwrap(); + + let res = parse_voucher_response(&voucher_response_tv); + assert!(res.is_ok()); + let (message_1, voucher, opaque_state) = res.unwrap(); + assert_eq!(message_1.content, message_1_tv.content); + assert_eq!(voucher.content, voucher_tv.content); + assert_eq!(opaque_state.content, opaque_state_tv.content); + } + + #[test] + fn test_r_prepare_ead_2() { + let voucher_response_tv: EdhocMessageBuffer = VOUCHER_RESPONSE_TV.try_into().unwrap(); + let ead_2_value_tv: EdhocMessageBuffer = EAD2_VALUE_TV.try_into().unwrap(); + + ead_responder_set_global_state(EADResponderState::new()); + + let ead_2 = r_prepare_ead_2(&voucher_response_tv).unwrap(); + assert_eq!( + ead_responder_get_global_state().protocol_state, + EADResponderProtocolState::Completed + ); + assert_eq!(ead_2.label, EAD_ZEROCONF_LABEL); + assert_eq!(ead_2.is_critical, true); + assert_eq!(ead_2.value.unwrap().content, ead_2_value_tv.content); + } } #[cfg(test)] diff --git a/examples/traces-zeroconf.ipynb b/examples/traces-zeroconf.ipynb index fabd733a..83da9e38 100644 --- a/examples/traces-zeroconf.ipynb +++ b/examples/traces-zeroconf.ipynb @@ -15,7 +15,7 @@ }, { "cell_type": "code", - "execution_count": 69, + "execution_count": 94, "metadata": {}, "outputs": [], "source": [ @@ -38,7 +38,7 @@ }, { "cell_type": "code", - "execution_count": 70, + "execution_count": 95, "metadata": {}, "outputs": [ { @@ -171,9 +171,31 @@ }, { "cell_type": "code", - "execution_count": 71, + "execution_count": 104, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + ".\n", + "----------------------------------------------------------------------\n", + "Ran 1 test in 0.002s\n", + "\n", + "OK\n" + ] + }, + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 104, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "def p256_ecdh(d_hex, x_hex, y_hex):\n", " private_key = ec.derive_private_key(int(d_hex, 16), ec.SECP256R1(), default_backend())\n", @@ -192,7 +214,19 @@ " return hkdf.hkdf_expand(unhexlify(prk), unhexlify(info), length, hash=hashlib.sha256).hex()\n", "\n", "def aes_ccm_encrypt_tag_8(key, iv, enc_structure, plaintext):\n", - " return aead.AESCCM(unhexlify(key), tag_length=8).encrypt(unhexlify(iv), unhexlify(plaintext), unhexlify(enc_structure)).hex()\n" + " return aead.AESCCM(unhexlify(key), tag_length=8).encrypt(unhexlify(iv), unhexlify(plaintext), unhexlify(enc_structure)).hex()\n", + "\n", + "def sha256_digest(message):\n", + " return hashlib.sha256(unhexlify(message)).hexdigest()\n", + "\n", + "import unittest\n", + "class Test(unittest.TestCase):\n", + " def test_ecdh(self):\n", + " self.assertEqual(\n", + " p256_ecdh(keys_tv[\"ephemeral_keys\"][\"X\"], keys_tv[\"static_keys\"][\"G_W\"], keys_tv[\"static_keys\"][\"G_W_y\"]), \n", + " p256_ecdh(keys_tv[\"static_keys\"][\"W\"], keys_tv[\"ephemeral_keys\"][\"G_X\"], keys_tv[\"ephemeral_keys\"][\"G_X_y\"]), \n", + " )\n", + "unittest.main(argv=[''], exit=False)" ] }, { @@ -206,7 +240,7 @@ }, { "cell_type": "code", - "execution_count": 72, + "execution_count": 97, "metadata": {}, "outputs": [ { @@ -329,7 +363,7 @@ }, { "cell_type": "code", - "execution_count": 73, + "execution_count": 98, "metadata": {}, "outputs": [ { @@ -384,7 +418,7 @@ }, { "cell_type": "code", - "execution_count": 89, + "execution_count": 99, "metadata": {}, "outputs": [ { @@ -413,9 +447,6 @@ } ], "source": [ - "def sha256_digest(message):\n", - " return hashlib.sha256(unhexlify(message)).hexdigest()\n", - "\n", "def add_voucher_response(tv):\n", " h_message_1 = sha256_digest(tv[\"input\"][\"MESSAGE_1_WITH_EAD\"])\n", " voucher_input = (cbor2.dumps(unhexlify(h_message_1)) + cbor2.dumps(unhexlify(tv[\"input\"][\"CRED_V\"]))).hex()\n", @@ -463,47 +494,48 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### Unit tests" + "### EAD_2 traces\n", + "\n", + "This one is rather unecessary, sinde EAD_2 = Voucher.\n", + "\n", + "See https://www.ietf.org/archive/id/draft-selander-lake-authz-03.html#name-voucher" ] }, { "cell_type": "code", - "execution_count": 75, + "execution_count": 105, "metadata": {}, "outputs": [ { - "name": "stderr", + "name": "stdout", "output_type": "stream", "text": [ - ".\n", - "----------------------------------------------------------------------\n", - "Ran 1 test in 0.001s\n", "\n", - "OK\n" + "# input\n", + "const VOUCHER_TV: &[u8] = &hex!(\"5820d99c86cf666f614d82cc3cfd0fb53cfa393f463f42ece49e38b056808ad5dfc9\");\n", + "\n", + "# ead2\n", + "const EAD2_VALUE_TV: &[u8] = &hex!(\"5820d99c86cf666f614d82cc3cfd0fb53cfa393f463f42ece49e38b056808ad5dfc9\");\n" ] - }, - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 75, - "metadata": {}, - "output_type": "execute_result" } ], "source": [ - "import unittest\n", + "def add_ead2(tv):\n", + " tv.update({\n", + " \"ead2\": {\n", + " \"ead2_value\": tv[\"input\"][\"VOUCHER\"],\n", + " }\n", + " })\n", + " return tv\n", "\n", - "class Test(unittest.TestCase):\n", - " def test_ead_1(self):\n", - " self.assertEqual(\n", - " p256_ecdh(keys_tv[\"ephemeral_keys\"][\"X\"], keys_tv[\"static_keys\"][\"G_W\"], keys_tv[\"static_keys\"][\"G_W_y\"]), \n", - " p256_ecdh(keys_tv[\"static_keys\"][\"W\"], keys_tv[\"ephemeral_keys\"][\"G_X\"], keys_tv[\"ephemeral_keys\"][\"G_X_y\"]), \n", - " )\n", + "ead2_tv = {\n", + " \"input\": {\n", + " \"VOUCHER\": voucher_tv[\"voucher_response\"][\"voucher\"],\n", + " }\n", + "}\n", + "ead2_tv = add_ead2(ead2_tv)\n", "\n", - "unittest.main(argv=[''], exit=False)" + "format_tv(ead2_tv, \"rust\")" ] } ], diff --git a/lib/src/edhoc.rs b/lib/src/edhoc.rs index 42e8113a..c414061e 100644 --- a/lib/src/edhoc.rs +++ b/lib/src/edhoc.rs @@ -681,7 +681,7 @@ fn is_cbor_bstr_1byte_prefix(byte: u8) -> bool { return byte >= CBOR_MAJOR_BYTE_STRING && byte <= CBOR_MAJOR_BYTE_STRING_MAX; } -/// Check for: a bstr denoted by two bytes, onr for type the other for content length +/// Check for: a bstr denoted by two bytes, one for type the other for content length #[inline(always)] fn is_cbor_bstr_2bytes_prefix(byte: u8) -> bool { return byte == CBOR_BYTE_STRING;