From fda3cbc60a39b4912819177be926742301178dec Mon Sep 17 00:00:00 2001 From: Francisco Molina Date: Fri, 5 Mar 2021 12:21:52 +0100 Subject: [PATCH] tests/pkg_edhoc: initial import --- tests/pkg_edhoc_c/Makefile | 39 ++ tests/pkg_edhoc_c/Makefile.ci | 51 +++ tests/pkg_edhoc_c/README.md | 418 ++++++++++++++++++ tests/pkg_edhoc_c/common.c | 141 ++++++ tests/pkg_edhoc_c/edhoc_keys.h | 175 ++++++++ tests/pkg_edhoc_c/initiator.c | 294 ++++++++++++ tests/pkg_edhoc_c/main.c | 103 +++++ tests/pkg_edhoc_c/responder.c | 152 +++++++ tests/pkg_edhoc_c/tests-with-config/01-run.py | 98 ++++ 9 files changed, 1471 insertions(+) create mode 100644 tests/pkg_edhoc_c/Makefile create mode 100644 tests/pkg_edhoc_c/Makefile.ci create mode 100644 tests/pkg_edhoc_c/README.md create mode 100644 tests/pkg_edhoc_c/common.c create mode 100644 tests/pkg_edhoc_c/edhoc_keys.h create mode 100644 tests/pkg_edhoc_c/initiator.c create mode 100644 tests/pkg_edhoc_c/main.c create mode 100644 tests/pkg_edhoc_c/responder.c create mode 100755 tests/pkg_edhoc_c/tests-with-config/01-run.py diff --git a/tests/pkg_edhoc_c/Makefile b/tests/pkg_edhoc_c/Makefile new file mode 100644 index 0000000000..760b57c07f --- /dev/null +++ b/tests/pkg_edhoc_c/Makefile @@ -0,0 +1,39 @@ +include ../Makefile.tests_common + +# Edhoc related packages +USEPKG += edhoc-c +USEMODULE += edhoc-c_crypto_tinycrypt +# USEMODULE += edhoc-c_crypto_wolfssl +USEMODULE += edhoc-c_cbor_nanocbor + +# Include packages that pull up and auto-init the link layer. +# NOTE: 6LoWPAN will be included if IEEE802.15.4 devices are present +USEMODULE += gnrc_netdev_default + +USEMODULE += auto_init_gnrc_netif +# Specify the mandatory networking modules for IPv6 and UDP +USEMODULE += gnrc_ipv6_router_default +USEMODULE += sock_udp +# Additional networking modules that can be dropped if not needed +USEMODULE += gnrc_icmpv6_echo +USEMODULE += nanocoap_sock + +# include this for printing IP addresses +USEMODULE += shell +USEMODULE += shell_commands +USEMODULE += ps +USEMODULE += xtimer + +# This is an optimized stack value based on testing, if you observe +# a segmentation fault please increase this stack size. +CFLAGS += -DTHREAD_STACKSIZE_MAIN=3*THREAD_STACKSIZE_LARGE + +# Include responder code +CONFIG_INITIATOR ?= 1 +CFLAGS += -DCONFIG_INITIATOR=$(CONFIG_INITIATOR) +# Include responder code +CONFIG_RESPONDER ?= 1 +CFLAGS += -DCONFIG_RESPONDER=$(CONFIG_RESPONDER) + +include $(RIOTBASE)/Makefile.include +include $(RIOTMAKE)/default-radio-settings.inc.mk diff --git a/tests/pkg_edhoc_c/Makefile.ci b/tests/pkg_edhoc_c/Makefile.ci new file mode 100644 index 0000000000..9ac65fe348 --- /dev/null +++ b/tests/pkg_edhoc_c/Makefile.ci @@ -0,0 +1,51 @@ +BOARD_INSUFFICIENT_MEMORY := \ + airfy-beacon \ + b-l072z-lrwan1 \ + blackpill \ + blackpill-128kib \ + bluepill \ + bluepill-128kib \ + bluepill-stm32f030c8 \ + calliope-mini \ + cc1350-launchpad \ + cc2650-launchpad \ + cc2650stk \ + e104-bt5010a-tb \ + e104-bt5011a-tb \ + hifive1 \ + hifive1b \ + i-nucleo-lrwan1 \ + im880b \ + lsn50 \ + maple-mini \ + microbit \ + nrf51dk \ + nrf51dongle \ + nrf6310 \ + nucleo-f030r8 \ + nucleo-f031k6 \ + nucleo-f042k6 \ + nucleo-f070rb \ + nucleo-f072rb \ + nucleo-f103rb \ + nucleo-f302r8 \ + nucleo-f303k8 \ + nucleo-f334r8 \ + nucleo-l011k4 \ + nucleo-l031k6 \ + nucleo-l053r8 \ + nucleo-l073rz \ + olimexino-stm32 \ + opencm904 \ + samd10-xmini \ + saml10-xpro \ + saml11-xpro \ + slstk3400a \ + spark-core \ + stk3200 \ + stm32f030f4-demo \ + stm32f0discovery \ + stm32l0538-disco \ + stm32mp157c-dk2 \ + yunjia-nrf51822 \ + # diff --git a/tests/pkg_edhoc_c/README.md b/tests/pkg_edhoc_c/README.md new file mode 100644 index 0000000000..cf5aec6f2c --- /dev/null +++ b/tests/pkg_edhoc_c/README.md @@ -0,0 +1,418 @@ +# EDHOC-C test application + +This test application sets up a RIOT node that can run as an EDHOC handshake +initiator and/or responder. The handshake can be run between two RIOT nodes or +between a RIOT node and a Linux host or for testing purposes the node can +perform an auto-handshake. + +In this example credentials based on LAKE IETF WG are used. These are RPK keys. +Normally different credentials should be used depending on the authentication +method, but currently no validation is done so the same credentials can be +used for any method. + +## EDHOC handshake between host and RIOT node + +### Pre-requisites + +- install [py-edhoc](https://github.com/openwsn-berkeley/py-edhoc): + +``` +$ pip install edhoc +``` + +This will install two cli utils `edhoc-responder` and `edhoc-initiator` to +facilitate testing. + +#### `native` + +- if using native set up a tap interface: + +``` +$ sudo ip tuntap add tap0 mode tap user ${USER} +$ sudo ip link set tap0 up +``` + +#### physical `BOARD` + +- If using any other (non-emulated) `BOARD` then in one terminal: + +``` + $ sudo dist/tools/ethos/setup_network.sh riot0 2001:db8::/64 +``` + +This will create a tap interface called `riot0`, owned by the user. It will +also run an instance of uhcpcd, which starts serving the prefix +`2001:db8::/64`. Keep the shell open as long as you need the network. +Make sure to exit the "make term" instance from the next section *before* +exiting this, as otherwise the "riot0" interface doesn't get cleaned up +properly. + +### Responder + +Find out the ipv6 address of the device is by running the `ifconfig` +command in the shell. + +``` +ifconfig +Starting the shell +> ifconfig +ifconfig +Iface 5 HWaddr: 0A:94:29:80:74:23 + L2-PDU:1500 MTU:1500 HL:64 RTR + RTR_ADV + Source address length: 6 + Link type: wired + inet6 addr: fe80::894:29ff:fe80:7423 scope: link VAL + inet6 group: ff02::2 + inet6 group: ff02::1 + inet6 group: ff02::1:ff80:7423 +``` + +In this case its `fe80::894:29ff:fe80:7423` and since we setup `tap0` as +the tap interface the device is reachable at `[fe80::894:29ff:fe80:7423%tap0]`. + +Initiate the handshake by running the `edhoc-initiator` cli tool: + +``` +$ edhoc-initiator fe80::894:29ff:fe80:7423%tap0] +INFO:root:POST (EdhocState.MSG_1_SENT) b'\x01\x00X \x89\x8f\xf7\x9a\x02\x06z\x16\xea\x1e\xcc\xb9\x0f\xa5"F\xf5\xaaM\xd6\xec\x07k\xba\x02Y\xd9\x04\xb7\xec\x8b\x0c@' +INFO:root:CHANGED (EdhocState.MSG_1_SENT) b'X q\xa3\xd5\x99\xc2\x1d\xa1\x89\x02\xa1\xae\xa8\x10\xb2\xb68,\xcd\x8d_\x9b\xf0\x19R\x81uL^\xbc\xaf0\x1e\x13XP\x99\xd58\x01\xa7%\xbf\xd6\xa4\xe7\x1d\x04\x84\xb7U\xec8=\xf7z\x91n\xc0\xdb\xc0+\xba|!\xa2\x00\x80{OX_r\x8bg\x1a\xd6x\xa4:\xac\xd3;x\xeb\xd5f\xcd\x00O\xc6\xf1\xd4\x06\xf0\x1d\x97\x04\xe7\x05\xb2\x15R\xa9\xeb(\xea1j\xb6P7\xd7\x17\x86.' +INFO:root:POST (EdhocState.MSG_3_SENT) b'\x01\x00X \x89\x8f\xf7\x9a\x02\x06z\x16\xea\x1e\xcc\xb9\x0f\xa5"F\xf5\xaaM\xd6\xec\x07k\xba\x02Y\xd9\x04\xb7\xec\x8b\x0c@' +INFO:root:EDHOC key exchange successfully completed: +INFO:root: - connection IDr: b'+' +INFO:root: - connection IDi: b'' +INFO:root: - aead algorithm: AES_CCM_16_64_128 +INFO:root: - hash algorithm: SHA_256 +INFO:root: - OSCORE secret : b'Y!1k\xae\x12\xc9\xc4\xd2\xb9\xfb \xcc\x1a\x12\xdd' +INFO:root: - OSCORE salt : b'\xad\xf9\xfd\xbed\x98\xa3\x02' +``` + +And on the device (responder): + +``` +> [responder]: received an EDHOC message (len 37) +0x01 0x00 0x58 0x20 0x89 0x8f 0xf7 0x9a +0x02 0x06 0x7a 0x16 0xea 0x1e 0xcc 0xb9 +0x0f 0xa5 0x22 0x46 0xf5 0xaa 0x4d 0xd6 +0xec 0x07 0x6b 0xba 0x02 0x59 0xd9 0x04 +0xb7 0xec 0x8b 0x0c 0x40 + +[responder]: sending msg2 (117 bytes) +0x58 0x20 0x71 0xa3 0xd5 0x99 0xc2 0x1d +0xa1 0x89 0x02 0xa1 0xae 0xa8 0x10 0xb2 +0xb6 0x38 0x2c 0xcd 0x8d 0x5f 0x9b 0xf0 +0x19 0x52 0x81 0x75 0x4c 0x5e 0xbc 0xaf +0x30 0x1e 0x13 0x58 0x50 0x99 0xd5 0x38 +0x01 0xa7 0x25 0xbf 0xd6 0xa4 0xe7 0x1d +0x04 0x84 0xb7 0x55 0xec 0x38 0x3d 0xf7 +0x7a 0x91 0x6e 0xc0 0xdb 0xc0 0x2b 0xba +0x7c 0x21 0xa2 0x00 0x80 0x7b 0x4f 0x58 +0x5f 0x72 0x8b 0x67 0x1a 0xd6 0x78 0xa4 +0x3a 0xac 0xd3 0x3b 0x78 0xeb 0xd5 0x66 +0xcd 0x00 0x4f 0xc6 0xf1 0xd4 0x06 0xf0 +0x1d 0x97 0x04 0xe7 0x05 0xb2 0x15 0x52 +0xa9 0xeb 0x28 0xea 0x31 0x6a 0xb6 0x50 +0x37 0xd7 0x17 0x86 0x2e + +[responder]: received an EDHOC message (len 91) +0x13 0x58 0x58 0x2d 0x88 0xff 0x86 0xda +0x47 0x48 0x2c 0x0d 0xfa 0x55 0x9a 0xc8 +0x24 0xa4 0xa7 0x83 0xd8 0x70 0xc9 0xdb +0xa4 0x78 0x05 0xe8 0xaa 0xfb 0xad 0x69 +0x74 0xc4 0x96 0x46 0x58 0x65 0x03 0xfa +0x9b 0xbf 0x3e 0x00 0x01 0x2c 0x03 0x7e +0xaf 0x56 0xe4 0x5e 0x30 0x19 0x20 0x83 +0x9b 0x81 0x3a 0x53 0xf6 0xd4 0xc5 0x57 +0x48 0x0f 0x6c 0x79 0x7d 0x5b 0x76 0xf0 +0xe4 0x62 0xf5 0xf5 0x7a 0x3d 0xb6 0xd2 +0xb5 0x0c 0x32 0x31 0x9f 0x34 0x0f 0x4a +0xc5 0xaf 0x9a + +[responder]: finalize exchange +[responder]: handshake successfully completed +``` + +### Initiator + +First find out the local ipv6 address of the tap interface: + +``` +ifconfig tap0 +tap0: flags=4163 mtu 1500 + inet6 fe80::894:29ff:fe80:7422 prefixlen 64 scopeid 0x20 + ether 0a:94:29:80:74:22 txqueuelen 1000 (Ethernet) + RX packets 68 bytes 5962 (5.9 KB) + RX errors 0 dropped 0 overruns 0 frame 0 + TX packets 542 bytes 55480 (55.4 KB) + TX errors 0 dropped 1 overruns 0 carrier 0 collisions 0 +``` + +In this case its `fe80::894:29ff:fe80:7422`. Next start the `py-edhoc` +based responder. + +``` +$ edhoc-reponder +INFO:root:Booting CoAP server +INFO:root:Initializing 'core' resource +INFO:root:Initializing 'edhoc' resource +``` + +Now from the RIOT shell initiate the handshake: + +``` +Starting the shell +> ini handshake fe80::894:29ff:fe80:7422 5683 +init handshake fe80::894:29ff:fe80:7422 5683 +[initiator]: sending message (37 bytes) +0x01 0x00 0x58 0x20 0x71 0xa3 0xd5 0x99 +0xc2 0x1d 0xa1 0x89 0x02 0xa1 0xae 0xa8 +0x10 0xb2 0xb6 0x38 0x2c 0xcd 0x8d 0x5f +0x9b 0xf0 0x19 0x52 0x81 0x75 0x4c 0x5e +0xbc 0xaf 0x30 0x1e 0x13 + +[initiator]: send 37 bytes to fe80::894:29ff:fe80:7422 on port 5683 + +[initiator]: received a message (124 bytes): +0x58 0x20 0x71 0xa3 0xd5 0x99 0xc2 0x1d +0xa1 0x89 0x02 0xa1 0xae 0xa8 0x10 0xb2 +0xb6 0x38 0x2c 0xcd 0x8d 0x5f 0x9b 0xf0 +0x19 0x52 0x81 0x75 0x4c 0x5e 0xbc 0xaf +0x30 0x1e 0x13 0x58 0x50 0x99 0xe1 0x3b +0xa4 0x65 0x44 0x44 0xe8 0xb6 0xd4 0x04 +0x01 0x1e 0x01 0xa3 0x29 0xa3 0x26 0x05 +0x45 0x99 0x33 0x95 0xf9 0x34 0x2b 0x43 +0xa7 0x54 0xf9 0xe1 0x8b 0x0f 0xdc 0x46 +0xc2 0xcc 0x4e 0x25 0x24 0x77 0xe0 0x83 +0x52 0x0b 0xf4 0x36 0x74 0x53 0xb6 0x2b +0xbf 0x3e 0x14 0xb7 0xb0 0xea 0x0e 0xee +0x84 0xc5 0x5b 0x9e 0x64 0xfc 0x03 0x97 +0xc0 0x45 0x18 0x6d 0x14 0xdb 0x88 0x8c +0x73 0x2f 0x95 0x52 0xf5 0x68 0xd1 0x61 +0x56 0x6c 0xd1 0x61 + +[initiator]: sending message (91 bytes) +0x13 0x58 0x58 0x49 0x7a 0x3e 0x46 0xac +0xa1 0x36 0xbf 0xff 0xb9 0x5c 0x00 0x46 +0x89 0x69 0x68 0x4a 0xe8 0x2d 0x83 0xf0 +0xe5 0xc5 0xe3 0x3f 0x8f 0x17 0xdf 0x7c +0x72 0xe1 0xf2 0x9e 0x7a 0x2a 0xe8 0x88 +0x75 0x16 0xd2 0x6a 0xe3 0xa7 0x73 0x76 +0xe8 0xe5 0x22 0x14 0x43 0x6d 0xb0 0x37 +0xb8 0x48 0x31 0xf3 0xa9 0xb3 0xfc 0x82 +0x9c 0x4a 0x92 0x19 0x2c 0x3e 0x4a 0xfe +0x42 0x6c 0x11 0x39 0x6c 0x48 0x48 0x06 +0x6b 0xf0 0xed 0x2e 0xff 0x16 0x91 0x08 +0xf4 0xee 0x6e + +[initiator]: send 91 bytes to fe80::894:29ff:fe80:7422 on port 5683 + +[initiator]: handshake successfully completed +``` + +And on the `edhoc-responder`: + +``` +INFO:root:CHANGED (EdhocState.MSG_2_SENT) b'X q\xa3\xd5\x99\xc2\x1d\xa1\x89\x02\xa1\xae\xa8\x10\xb2\xb68,\xcd\x8d_\x9b\xf0\x19R\x81uL^\xbc\xaf0\x1e\x13XP\x99\xe1;\xa4eDD\xe8\xb6\xd4\x04\x01\x1e\x01\xa3)\xa3&\x05E\x993\x95\xf94+C\xa7T\xf9\xe1\x8b\x0f\xdcF\xc2\xccN%$w\xe0\x83R\x0b\xf46tS\xb6+\xbf>\x14\xb7\xb0\xea\x0e\xee\x84\xc5[\x9ed\xfc\x03\x97\xc0E\x18m\x14\xdb\x88\x8cs/\x95R\xf5' +INFO:root:POST (EdhocState.MSG_2_SENT) b'\x13XXIz>F\xac\xa16\xbf\xff\xb9\\\x00F\x89ihJ\xe8-\x83\xf0\xe5\xc5\xe3?\x8f\x17\xdf|r\xe1\xf2\x9ez*\xe8\x88u\x16\xd2j\xe3\xa7sv\xe8\xe5"\x14Cm\xb07\xb8H1\xf3\xa9\xb3\xfc\x82\x9cJ\x92\x19,>J\xfeBl\x119lHH\x06k\xf0\xed.\xff\x16\x91\x08\xf4\xeen' +INFO:root:EDHOC key exchange successfully completed: +INFO:root: - connection IDr: b'+' +INFO:root: - connection IDi: b'+' +INFO:root: - aead algorithm: AES_CCM_16_64_128 +INFO:root: - hash algorithm: SHA_256 +INFO:root: - OSCORE secret : b'\xd8\x1e\xa3@\xec\xe3?3\xe1\xfe\x8a\x1d\x0c|\xd0\xbe' +INFO:root: - OSCORE salt : b'\x87\xf9J\xf7\x82Tq\xa3' +``` + +Congratulations you have performed an EDHOC handshake fo your HOST to +a RIOT node running as the initiator and responder. In all cases you can +now derive symmetric encryption keys from the shared master secret, in this +case a shell command `initiator/responder oscore` is available that derives +keys that could be used for an OSCORE context: + +``` +> ini oscore +init oscore +OSCORE secret: +0x43 0x83 0x97 0xe7 0xa8 0x3b 0xd7 0x35 +0xdf 0x0d 0x47 0xdc 0x45 0x44 0xa4 0x63 + +OSCORE salt: +0x22 0x3b 0x3c 0xb1 0x03 0xc8 0xa3 0xd0 +``` + +## EDHOC handshake between two RIOT nodes + +### Pre-requisites + +#### `native` + +- if using `native` `BOARD`'s then create two tap interfaces linked +through a bridge: + +``` +sudo dist/tools/tapsetup/tapsetup -c 2 +``` + +- bootstrap the `BOARD`s and specify the tap interface to use for each + +``` +PORT=tap0 make -C tests/pkg_edhoc_c all term +PORT=tap1 make -C tests/pkg_edhoc_c all term +``` + +#### physical `BOARD`s + +- for other `BOARD`s make sure the chosen `BOARD`s has a netdev +through which they will be able to communicate. + +- bootstrap the `BOARD`s + +``` +make -C tests/pkg_edhoc_c flash term +``` + +### Perform the handshake + +One of the devices shall be the initiator and the other one the responder. +Both are already setup to listen for the first message. + +In the shell of the node that will act as the responder identify its ipv6 +address: + +``` +Starting the shell +> ifconfig +ifconfig +Iface 5 HWaddr: D6:76:BB:62:F2:AE + L2-PDU:1500 MTU:1500 HL:64 RTR + RTR_ADV + Source address length: 6 + Link type: wired + inet6 addr: fe80::d476:bbff:fe62:f2ae scope: link VAL + inet6 group: ff02::2 + inet6 group: ff02::1 + inet6 group: ff02::1:ff62:f2ae +``` + +In this case `fe80::d476:bbff:fe62:f2ae`. + +From the initiator now start the handshake: + +``` +initiator handshake fe80::d476:bbff:fe62:f2ae 5683 +``` + +Yo should see the different messages being exchanged on both nodes, and +now both can derive OSCORE keys as well with the `responder/initiator oscore` +command: + +- initiator: + +``` +> ini handshake fe80::d476:bbff:fe62:f2ae 5683 +init handshake fe80::d476:bbff:fe62:f2ae 5683 +[initiator]: sending message (37 bytes) +0x01 0x00 0x58 0x20 0x71 0xa3 0xd5 0x99 +0xc2 0x1d 0xa1 0x89 0x02 0xa1 0xae 0xa8 +0x10 0xb2 0xb6 0x38 0x2c 0xcd 0x8d 0x5f +0x9b 0xf0 0x19 0x52 0x81 0x75 0x4c 0x5e +0xbc 0xaf 0x30 0x1e 0x13 +[initiator]: send 37 bytes to fe80::d476:bbff:fe62:f2ae on port 5683 + +[initiator]: received a message (126 bytes): +0x58 0x20 0x71 0xa3 0xd5 0x99 0xc2 0x1d +0xa1 0x89 0x02 0xa1 0xae 0xa8 0x10 0xb2 +0xb6 0x38 0x2c 0xcd 0x8d 0x5f 0x9b 0xf0 +0x19 0x52 0x81 0x75 0x4c 0x5e 0xbc 0xaf +0x30 0x1e 0x13 0x58 0x50 0x99 0xe1 0x3b +0xa4 0x65 0x44 0x44 0xe8 0xb6 0xd4 0x04 +0x01 0x1e 0x01 0xa3 0x29 0xa3 0x26 0x05 +0x45 0x99 0x33 0x95 0xf9 0x34 0x2b 0x43 +0xa7 0x54 0xf9 0xe1 0x8b 0x0f 0xdc 0x46 +0xc2 0xcc 0x4e 0x25 0x24 0x77 0xe0 0x83 +0x52 0x0b 0xf4 0x36 0x74 0x53 0xb6 0x2b +0xbf 0x3e 0x14 0xb7 0xb0 0xea 0x0e 0xee +0x84 0xc5 0x5b 0x9e 0x64 0xfc 0x03 0x97 +0xc0 0x45 0x18 0x6d 0x14 0xdb 0x88 0x8c +0x73 0x2f 0x95 0x52 0xf5 0x60 0x56 0x6c +0xa1 0x60 0x56 0x70 0xa1 0x60 +[initiator]: sending message (91 bytes) +0x13 0x58 0x58 0x49 0x7a 0x3e 0x46 0xac +0xa1 0x36 0xbf 0xff 0xb9 0x5c 0x00 0x46 +0x89 0x69 0x68 0x4a 0xe8 0x2d 0x83 0xf0 +0xe5 0xc5 0xe3 0x3f 0x8f 0x17 0xdf 0x7c +0x72 0xe1 0xf2 0x9e 0x7a 0x2a 0xe8 0x88 +0x75 0x16 0xd2 0x6a 0xe3 0xa7 0x73 0x76 +0xe8 0xe5 0x22 0x14 0x43 0x6d 0xb0 0x37 +0xb8 0x48 0x31 0xf3 0xa9 0xb3 0xfc 0x82 +0x9c 0x4a 0x92 0x19 0x2c 0x3e 0x4a 0xfe +0x42 0x6c 0x11 0x39 0x6c 0x48 0x48 0x06 +0x6b 0xf0 0xed 0x2e 0xff 0x16 0x91 0x08 +0xf4 0xee 0x6e +[initiator]: send 91 bytes to fe80::d476:bbff:fe62:f2ae on port 5683 + +[initiator]: handshake successfully completed +``` + +- responder: + +``` +> [responder]: received an EDHOC message (len 37) +0x01 0x00 0x58 0x20 0x71 0xa3 0xd5 0x99 +0xc2 0x1d 0xa1 0x89 0x02 0xa1 0xae 0xa8 +0x10 0xb2 0xb6 0x38 0x2c 0xcd 0x8d 0x5f +0x9b 0xf0 0x19 0x52 0x81 0x75 0x4c 0x5e +0xbc 0xaf 0x30 0x1e 0x13 +[responder]: sending msg2 (117 bytes) +0x58 0x20 0x71 0xa3 0xd5 0x99 0xc2 0x1d +0xa1 0x89 0x02 0xa1 0xae 0xa8 0x10 0xb2 +0xb6 0x38 0x2c 0xcd 0x8d 0x5f 0x9b 0xf0 +0x19 0x52 0x81 0x75 0x4c 0x5e 0xbc 0xaf +0x30 0x1e 0x13 0x58 0x50 0x99 0xe1 0x3b +0xa4 0x65 0x44 0x44 0xe8 0xb6 0xd4 0x04 +0x01 0x1e 0x01 0xa3 0x29 0xa3 0x26 0x05 +0x45 0x99 0x33 0x95 0xf9 0x34 0x2b 0x43 +0xa7 0x54 0xf9 0xe1 0x8b 0x0f 0xdc 0x46 +0xc2 0xcc 0x4e 0x25 0x24 0x77 0xe0 0x83 +0x52 0x0b 0xf4 0x36 0x74 0x53 0xb6 0x2b +0xbf 0x3e 0x14 0xb7 0xb0 0xea 0x0e 0xee +0x84 0xc5 0x5b 0x9e 0x64 0xfc 0x03 0x97 +0xc0 0x45 0x18 0x6d 0x14 0xdb 0x88 0x8c +0x73 0x2f 0x95 0x52 0xf5 +[responder]: received an EDHOC message (len 91) +0x13 0x58 0x58 0x49 0x7a 0x3e 0x46 0xac +0xa1 0x36 0xbf 0xff 0xb9 0x5c 0x00 0x46 +0x89 0x69 0x68 0x4a 0xe8 0x2d 0x83 0xf0 +0xe5 0xc5 0xe3 0x3f 0x8f 0x17 0xdf 0x7c +0x72 0xe1 0xf2 0x9e 0x7a 0x2a 0xe8 0x88 +0x75 0x16 0xd2 0x6a 0xe3 0xa7 0x73 0x76 +0xe8 0xe5 0x22 0x14 0x43 0x6d 0xb0 0x37 +0xb8 0x48 0x31 0xf3 0xa9 0xb3 0xfc 0x82 +0x9c 0x4a 0x92 0x19 0x2c 0x3e 0x4a 0xfe +0x42 0x6c 0x11 0x39 0x6c 0x48 0x48 0x06 +0x6b 0xf0 0xed 0x2e 0xff 0x16 0x91 0x08 +0xf4 0xee 0x6e +[responder]: finalize exchange +[responder]: handshake successfully completed +``` + +- oscore keys: + +``` +> ini oscore +init oscore +OSCORE secret: +0x43 0x83 0x97 0xe7 0xa8 0x3b 0xd7 0x35 +0xdf 0x0d 0x47 0xdc 0x45 0x44 0xa4 0x63 + +OSCORE salt: +0x22 0x3b 0x3c 0xb1 0x03 0xc8 0xa3 0xd0 +``` + +## EDHOC automatic test + +As long as a BOARD with a netdev interface is used is as simple as: + +``` +$ make -C tests/pkg_edhoc_c flash test-with-config +``` diff --git a/tests/pkg_edhoc_c/common.c b/tests/pkg_edhoc_c/common.c new file mode 100644 index 0000000000..a242eceeb7 --- /dev/null +++ b/tests/pkg_edhoc_c/common.c @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2021 Inria + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup tests + * @{ + * + * @file + * @brief EDHOC initiator/responder common setup code + * + * @author Timothy Claeys + * @author Francisco Molina + */ + +#include +#include + +#include "kernel_defines.h" +#include "edhoc/edhoc.h" +#include "edhoc_keys.h" + +#define ENABLE_DEBUG 0 +#include "debug.h" + +#define CRED_DB_SIZE ARRAY_SIZE(cred_db) + +int _cred_cb(const uint8_t *k, size_t k_len, const uint8_t **o, size_t *o_len) +{ + for (uint8_t i = 0; i < (uint8_t)CRED_DB_SIZE; i++) { + if (cred_db[i].id_len == k_len) { + if (memcmp(cred_db[i].id, k, k_len) == 0) { + *o = cred_db[i].cred; + *o_len = cred_db[i].cred_len; + return 0; + } + } + } + *o = NULL; + *o_len = 0; + return EDHOC_ERR_INVALID_CRED_ID; +} + +void print_bstr(const uint8_t *bstr, size_t bstr_len) +{ + for (size_t i = 0; i < bstr_len; i++) { + if ((i + 1) % 8 == 0) { + printf("0x%02x \n", bstr[i]); + } + else { + printf("0x%02x ", bstr[i]); + } + } + printf("\n"); +} + +int edhoc_setup(edhoc_ctx_t *ctx, edhoc_conf_t *conf, edhoc_role_t role, + cose_key_t *auth_key, cred_id_t *cred_id, rpk_t *rpk, + void *hash_ctx) +{ + /* clear/init context and configuration */ + edhoc_ctx_init(ctx); + edhoc_conf_init(conf); + cred_id_init(cred_id); + cred_rpk_init(rpk); + cose_key_init(auth_key); + + /* only for testing load preset keys for role */ + const uint8_t *cbor_auth_key = NULL; + const uint8_t *cbor_rpk = NULL; + const uint8_t *cbor_rpk_id = NULL; + size_t cbor_auth_key_len = 0; + size_t cbor_rpk_len = 0; + size_t cbor_rpk_id_len; + + if (role == EDHOC_IS_RESPONDER) { + DEBUG_PUTS("[edhoc]: setting up responder"); + cbor_auth_key = resp_cbor_auth_key; + cbor_auth_key_len = sizeof(resp_cbor_auth_key); + cbor_rpk = resp_cbor_rpk; + cbor_rpk_len = sizeof(resp_cbor_rpk); + cbor_rpk_id = resp_cbor_rpk_id; + cbor_rpk_id_len = sizeof(resp_cbor_rpk_id); + } + else { + DEBUG_PUTS("[edhoc]: setting up initiator"); + cbor_auth_key = init_cbor_auth_key; + cbor_auth_key_len = sizeof(init_cbor_auth_key); + cbor_rpk = init_cbor_rpk; + cbor_rpk_len = sizeof(init_cbor_rpk); + cbor_rpk_id = init_cbor_rpk_id; + cbor_rpk_id_len = sizeof(init_cbor_rpk_id); + } + + DEBUG_PUTS("[edhoc]: load private authentication key"); + if (cose_key_from_cbor(auth_key, cbor_auth_key, cbor_auth_key_len) != 0) { + return -1; + } + + DEBUG_PUTS("[edhoc]: load and set CBOR RPK"); + if (cred_rpk_from_cbor(rpk, cbor_rpk, cbor_rpk_len) != 0) { + return -1; + } + + DEBUG_PUTS("[edhoc]: load credential identifier information"); + if (cred_id_from_cbor(cred_id, cbor_rpk_id, cbor_rpk_id_len) != 0) { + return -1; + } + + DEBUG_PUTS("[edhoc]: set up EDHOC callbacks and role"); + edhoc_conf_setup_ad_callbacks(conf, NULL, NULL, NULL); + if (edhoc_conf_setup_role(conf, role) != 0) { + return -1; + } + + DEBUG_PUTS("[edhoc]: set up EDHOC credentials"); + if (edhoc_conf_setup_credentials(conf, auth_key, CRED_TYPE_RPK, rpk, cred_id, _cred_cb) != 0) { + return -1; + } + + DEBUG_PUTS("[edhoc]: EDHOC context setup"); + edhoc_ctx_setup(ctx, conf, hash_ctx); + + return 0; +} + +void edhoc_oscore_exporter(edhoc_ctx_t *ctx, uint8_t *secret, size_t secret_len, + uint8_t *salt, size_t salt_len) +{ + edhoc_exporter(ctx, "OSCORE secret", secret_len, secret, secret_len); + edhoc_exporter(ctx, "OSCORE salt", salt_len, salt, salt_len); + + puts("OSCORE secret:"); + print_bstr(secret, secret_len); + puts("OSCORE salt:"); + print_bstr(salt, salt_len); +} diff --git a/tests/pkg_edhoc_c/edhoc_keys.h b/tests/pkg_edhoc_c/edhoc_keys.h new file mode 100644 index 0000000000..1178df7bcd --- /dev/null +++ b/tests/pkg_edhoc_c/edhoc_keys.h @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2021 Inria + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup tests + * @{ + * + * @file + * @brief Certificates and keys for the edhoc example. This values + * are taken from the IETF lake WG test vectors, specifically + * test vector 34900, see: + * https://github.com/lake-wg/edhoc/blob/5ef58e6ee998f4b9aca4b53b35e87375ca356f32/test-vectors-05/vectors.txt + * + * @author Timothy Claeys + * @author Francisco Molina + * + * @} + */ + +#ifndef EDHOC_KEYS_H +#define EDHOC_KEYS_H + +#include + +#include "kernel_defines.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* initiator CBOR-encoded authentication key */ +static const uint8_t init_cbor_auth_key[] = { + 0xa4, 0x01, 0x01, 0x20, 0x06, 0x21, 0x58, 0x20, + 0x2c, 0x44, 0x0c, 0xc1, 0x21, 0xf8, 0xd7, 0xf2, + 0x4c, 0x3b, 0x0e, 0x41, 0xae, 0xda, 0xfe, 0x9c, + 0xaa, 0x4f, 0x4e, 0x7a, 0xbb, 0x83, 0x5e, 0xc3, + 0x0f, 0x1d, 0xe8, 0x8a, 0xdb, 0x96, 0xff, 0x71, + 0x23, 0x58, 0x20, 0x2b, 0xbe, 0xa6, 0x55, 0xc2, + 0x33, 0x71, 0xc3, 0x29, 0xcf, 0xbd, 0x3b, 0x1f, + 0x02, 0xc6, 0xc0, 0x62, 0x03, 0x38, 0x37, 0xb8, + 0xb5, 0x90, 0x99, 0xa4, 0x43, 0x6f, 0x66, 0x60, + 0x81, 0xb0, 0x8e +}; + +/* initiator CBOR-encoded ephemeral key */ +static const uint8_t init_cbor_eph_key[] = { + 0xa4, 0x01, 0x01, 0x20, 0x04, 0x21, 0x58, 0x20, + 0x8d, 0x3e, 0xf5, 0x6d, 0x1b, 0x75, 0x0a, 0x43, + 0x51, 0xd6, 0x8a, 0xc2, 0x50, 0xa0, 0xe8, 0x83, + 0x79, 0x0e, 0xfc, 0x80, 0xa5, 0x38, 0xa4, 0x44, + 0xee, 0x9e, 0x2b, 0x57, 0xe2, 0x44, 0x1a, 0x7c, + 0x23, 0x58, 0x20, 0xae, 0x11, 0xa0, 0xdb, 0x86, + 0x3c, 0x02, 0x27, 0xe5, 0x39, 0x92, 0xfe, 0xb8, + 0xf5, 0x92, 0x4c, 0x50, 0xd0, 0xa7, 0xba, 0x6e, + 0xea, 0xb4, 0xad, 0x1f, 0xf2, 0x45, 0x72, 0xf4, + 0xf5, 0x7c, 0xfa +}; + +/* initiator CBOR-encoded RPK */ +static const uint8_t init_cbor_rpk[] = { + 0xa4, 0x01, 0x01, 0x20, 0x04, 0x21, 0x58, 0x20, + 0x2c, 0x44, 0x0c, 0xc1, 0x21, 0xf8, 0xd7, 0xf2, + 0x4c, 0x3b, 0x0e, 0x41, 0xae, 0xda, 0xfe, 0x9c, + 0xaa, 0x4f, 0x4e, 0x7a, 0xbb, 0x83, 0x5e, 0xc3, + 0x0f, 0x1d, 0xe8, 0x8a, 0xdb, 0x96, 0xff, 0x71, + 0x6c, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, + 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x60 +}; + +/* initiator CBOR-encoded rpk identifier */ +static const uint8_t init_cbor_rpk_id[] = { + 0xa1, 0x04, 0x41, 0x23 +}; + +/* initiator CBOR-encoded rpk identifier */ +static const uint8_t init_cbor_rpk_id_value[] = { + 0x23 +}; + +/* initiator session identifier preset */ +static const uint8_t init_cid[] = { + 0x16 +}; + +/* responder CBOR-encoded authentication key */ +static const uint8_t resp_cbor_auth_key[] = { + 0xa4, 0x01, 0x01, 0x20, 0x06, 0x21, 0x58, 0x20, + 0xa3, 0xff, 0x26, 0x35, 0x95, 0xbe, 0xb3, 0x77, + 0xd1, 0xa0, 0xce, 0x1d, 0x04, 0xda, 0xd2, 0xd4, + 0x09, 0x66, 0xac, 0x6b, 0xcb, 0x62, 0x20, 0x51, + 0xb8, 0x46, 0x59, 0x18, 0x4d, 0x5d, 0x9a, 0x32, + 0x23, 0x58, 0x20, 0xbb, 0x50, 0x1a, 0xac, 0x67, + 0xb9, 0xa9, 0x5f, 0x97, 0xe0, 0xed, 0xed, 0x6b, + 0x82, 0xa6, 0x62, 0x93, 0x4f, 0xbb, 0xfc, 0x7a, + 0xd1, 0xb7, 0x4c, 0x1f, 0xca, 0xd6, 0x6a, 0x07, + 0x94, 0x22, 0xd0 +}; + +/* responder CBOR-encoded ephemeral key */ +static const uint8_t resp_cbor_eph_key[] = { + 0xa4, 0x01, 0x01, 0x20, 0x04, 0x21, 0x58, 0x20, + 0x52, 0xfb, 0xa0, 0xbd, 0xc8, 0xd9, 0x53, 0xdd, + 0x86, 0xce, 0x1a, 0xb2, 0xfd, 0x7c, 0x05, 0xa4, + 0x65, 0x8c, 0x7c, 0x30, 0xaf, 0xdb, 0xfc, 0x33, + 0x01, 0x04, 0x70, 0x69, 0x45, 0x1b, 0xaf, 0x35, + 0x23, 0x58, 0x20, 0xc6, 0x46, 0xcd, 0xdc, 0x58, + 0x12, 0x6e, 0x18, 0x10, 0x5f, 0x01, 0xce, 0x35, + 0x05, 0x6e, 0x5e, 0xbc, 0x35, 0xf4, 0xd4, 0xcc, + 0x51, 0x07, 0x49, 0xa3, 0xa5, 0xe0, 0x69, 0xc1, + 0x16, 0x16, 0x9a +}; + +/* responder CBOR-encoded RPK */ +static const uint8_t resp_cbor_rpk[] = { + 0xa4, 0x01, 0x01, 0x20, 0x04, 0x21, 0x58, 0x20, + 0xa3, 0xff, 0x26, 0x35, 0x95, 0xbe, 0xb3, 0x77, + 0xd1, 0xa0, 0xce, 0x1d, 0x04, 0xda, 0xd2, 0xd4, + 0x09, 0x66, 0xac, 0x6b, 0xcb, 0x62, 0x20, 0x51, + 0xb8, 0x46, 0x59, 0x18, 0x4d, 0x5d, 0x9a, 0x32, + 0x6c, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, + 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x60 +}; + + +/* responder CBOR-encoded rpk identifier */ +static const uint8_t resp_cbor_rpk_id[] = { + 0xa1, 0x04, 0x41, 0x05 +}; + +/* responder CBOR-encoded rpk identifier */ +static const uint8_t resp_cbor_rpk_id_value[] = { + 0x05 +}; + +/* responder session identifier preset */ +static const uint8_t resp_cid[] = { + 0x00 +}; + +/** + * @brief Credential database entry + */ +typedef struct { + const uint8_t *id; /**< credential id pointer */ + size_t id_len; /**< credential id length */ + const uint8_t *cred; /**< credential pointer */ + size_t cred_len; /**< credential length */ +} cred_db_entry_t; + +/* credential database */ +static const cred_db_entry_t cred_db[] = { + { + resp_cbor_rpk_id_value, + sizeof(resp_cbor_rpk_id_value), + resp_cbor_rpk, + sizeof(resp_cbor_rpk) + }, + { + init_cbor_rpk_id_value, + sizeof(init_cbor_rpk_id_value), + init_cbor_rpk, + sizeof(init_cbor_rpk) + }, +}; + +#ifdef __cplusplus +} +#endif + +#endif /* EDHOC_KEYS_H */ diff --git a/tests/pkg_edhoc_c/initiator.c b/tests/pkg_edhoc_c/initiator.c new file mode 100644 index 0000000000..ecca88ff57 --- /dev/null +++ b/tests/pkg_edhoc_c/initiator.c @@ -0,0 +1,294 @@ +/* + * Copyright (C) 2021 Inria + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup tests + * @{ + * + * @file + * @brief EDHOC coap initiator implementation + * + * @author Timothy Claeys + * @author Francisco Molina + */ + + +#include + +#include "net/ipv6.h" +#include "net/nanocoap_sock.h" +#include "shell.h" + +#include "edhoc/edhoc.h" +#include "edhoc_keys.h" + +#if IS_USED(MODULE_WOLFSSL) +#include "wolfssl/wolfcrypt/sha256.h" +#elif IS_USED(MODULE_TINYCRYPT) +#include "tinycrypt/sha256.h" +#endif + +#define ENABLE_DEBUG 0 +#include "debug.h" + +#define COAP_BUF_SIZE (256U) + +#if IS_ACTIVE(CONFIG_INITIATOR) + +extern void print_bstr(const uint8_t *bstr, size_t bstr_len); +extern int edhoc_setup(edhoc_ctx_t *ctx, edhoc_conf_t *conf, edhoc_role_t role, + cose_key_t *auth_key, cred_id_t *cred_id, rpk_t *rpk, + void *hash_ctx); +extern void edhoc_oscore_exporter(edhoc_ctx_t *ctx, uint8_t *secret, size_t secret_len, + uint8_t *salt, size_t salt_len); + +static edhoc_ctx_t _ctx; +static edhoc_conf_t _conf; +static rpk_t _rpk; +static cred_id_t _cred_id; +static cose_key_t _auth_key; +#if IS_USED(MODULE_WOLFSSL) +static wc_Sha256 _sha_i; +#elif IS_USED(MODULE_TINYCRYPT) +struct tc_sha256_state_struct _sha_i; +#endif +static uint8_t _method; +static uint8_t _suite; + +static ssize_t _send(coap_pkt_t *pkt, size_t len, char *addr_str, uint16_t port) +{ + ipv6_addr_t addr; + sock_udp_ep_t remote; + + remote.family = AF_INET6; + remote.port = port; + + /* parse for interface */ + char *iface = ipv6_addr_split_iface(addr_str); + if (!iface) { + if (gnrc_netif_numof() == 1) { + /* assign the single interface found in gnrc_netif_numof() */ + remote.netif = (uint16_t)gnrc_netif_iter(NULL)->pid; + } + else { + remote.netif = SOCK_ADDR_ANY_NETIF; + } + } + else { + int pid = atoi(iface); + if (gnrc_netif_get_by_pid(pid) == NULL) { + puts("[initiator]: interface not valid"); + return 0; + } + remote.netif = pid; + } + + /* parse destination address */ + if (ipv6_addr_from_str(&addr, addr_str) == NULL) { + puts("[initiator]: unable to parse destination address"); + return 0; + } + if ((remote.netif == SOCK_ADDR_ANY_NETIF) && ipv6_addr_is_link_local(&addr)) { + puts("[initiator]: must specify interface for link local target"); + return 0; + } + memcpy(&remote.addr.ipv6[0], &addr.u8[0], sizeof(addr.u8)); + + return nanocoap_request(pkt, NULL, &remote, len); +} + +static ssize_t _build_coap_pkt(coap_pkt_t *pkt, uint8_t *buf, ssize_t buflen, + uint8_t *payload, ssize_t payload_len) +{ + uint8_t token[2] = { 0xDA, 0xEC }; + ssize_t len = 0; + + /* set pkt buffer */ + pkt->hdr = (coap_hdr_t *)buf; + /* build header, confirmed message always post */ + ssize_t hdrlen = coap_build_hdr(pkt->hdr, COAP_TYPE_CON, token, + sizeof(token), COAP_METHOD_POST, 1); + coap_pkt_init(pkt, buf, buflen, hdrlen); + coap_opt_add_string(pkt, COAP_OPT_URI_PATH, "/.well-known/edhoc", '/'); + coap_opt_add_uint(pkt, COAP_OPT_CONTENT_FORMAT, COAP_FORMAT_OCTET); + len = coap_opt_finish(pkt, COAP_OPT_FINISH_PAYLOAD); + /* copy msg payload */ + pkt->payload_len = payload_len; + memcpy(pkt->payload, payload, payload_len); + len += pkt->payload_len; + return len; +} + +int _handshake_cmd(int argc, char **argv) +{ + uint8_t buf[COAP_BUF_SIZE] = { 0 }; + coap_pkt_t pkt; + ssize_t len = 0; + uint16_t port = COAP_PORT; + uint8_t msg[COAP_BUF_SIZE]; + ssize_t msg_len = 0; + + /* correlation value is transport specific */ + corr_t corr = CORR_1_2; + + if (argc < 2) { + printf("usage: %s [%%iface] \n", argv[0]); + return -1; + } + if (argc == 3) { + port = atoi(argv[2]); + } + + /* reset state */ + _ctx.state = EDHOC_WAITING; + + if ((msg_len = edhoc_create_msg1(&_ctx, corr, _method, _suite, msg, sizeof(msg))) > 0) { + printf("[initiator]: sending msg1 (%d bytes):\n", (int) msg_len); + print_bstr(msg, msg_len); + _build_coap_pkt(&pkt, buf, sizeof(buf), msg, msg_len); + len = _send(&pkt, COAP_BUF_SIZE, argv[1], port); + } + else { + puts("[initiator]: failed to create msg1"); + return -1; + } + if (len < 0) { + puts("[initiator]: failed to send msg1"); + return -1; + } + + printf("[initiator]: received a message (%d bytes):\n", pkt.payload_len); + print_bstr(pkt.payload, pkt.payload_len); + + if ((msg_len = edhoc_create_msg3(&_ctx, pkt.payload, pkt.payload_len, msg, sizeof(msg))) > 0) { + printf("[initiator]: sending msg3 (%d bytes):\n", (int) msg_len); + print_bstr(msg, msg_len); + _build_coap_pkt(&pkt, buf, sizeof(buf), msg, msg_len); + len = _send(&pkt, COAP_BUF_SIZE, argv[1], port); + } + else { + puts("[initiator]: failed to create msg3"); + return -1; + } + + if (edhoc_init_finalize(&_ctx)) { + puts("[initiator]: handshake failed"); + return -1; + } + + puts("[initiator]: handshake successfully completed"); + + printf("[initiator]: Transcript hash 4 (%d bytes):\n", EDHOC_DIGEST_SIZE); + print_bstr(_ctx.session.th4, EDHOC_DIGEST_SIZE); + + _ctx.state = EDHOC_FINALIZED; + + return 0; +} + +static void _print_initiator_usage(void) +{ + puts("Usage:"); + puts("\tinit set method :" + " choose authentication method [initiator-responder]"); + puts("\tinit set suite <0|1>: choose cypher-suit, only 0 and 1 support"); + puts("\tinit handshake [%%iface] : start handshake"); + puts("\tinit oscore: derive OSCORE secret and salt"); +} + +static int _set_cmd(int argc, char **argv) +{ + if (argc < 2) { + _print_initiator_usage(); + return -1; + } + + if (!strcmp(argv[1], "method")) { + if (!strcmp(argv[2], "sgn-sgn")) { + _method = EDHOC_AUTH_SIGN_SIGN; + } + else if (!strcmp(argv[2], "sgn-stat")) { + _method = EDHOC_AUTH_SIGN_STATIC; + } + else if (!strcmp(argv[2], "stat-sgn")) { + _method = EDHOC_AUTH_STATIC_SIGN; + } + else if (!strcmp(argv[2], "stat-stat")) { + _method = EDHOC_AUTH_STATIC_STATIC; + } + else { + printf("error: invalid method %s, sgn-sgn|sgn-stat|stat-sgn|stat-stat\n", argv[1]); + return -1; + } + } + + if (!strcmp(argv[1], "suite")) { + uint8_t suite = atoi(argv[2]); + if (suite > 1) { + puts("error: only cypher suits 0 and 1 are supported"); + return -1; + } + _suite = suite; + } + + return 0; +} + +int initiator_cmd(int argc, char **argv) +{ + if (argc < 2) { + _print_initiator_usage(); + return -1; + } + + if (!strcmp(argv[1], "set")) { + return _set_cmd(argc - 1, &argv[1]); + } + + if (!strcmp(argv[1], "handshake")) { + return _handshake_cmd(argc - 1, &argv[1]); + } + + if (!strcmp(argv[1], "oscore")) { + if (_ctx.state == EDHOC_FINALIZED) { + uint8_t secret[16]; + uint8_t salt[8]; + edhoc_oscore_exporter(&_ctx, secret, sizeof(secret), salt, sizeof(salt)); + } + else { + puts("error: perform edhoc handshake first"); + } + } + + return 0; +} + +int initiator_cli_init(void) +{ + /* default to static-static (method 3) since we are using RPK keys */ + _method = EDHOC_AUTH_STATIC_STATIC; + _suite = EDHOC_CIPHER_SUITE_0; + if (edhoc_setup(&_ctx, &_conf, EDHOC_IS_INITIATOR, &_auth_key, &_cred_id, + &_rpk, &_sha_i)) { + puts("[initiator]: error during setup"); + return -1; + } + /* use fixed values only for testing purposes */ + puts("[initiator]: load ephemeral key: ONLY FOR TESTING"); + if (edhoc_load_ephkey(&_ctx, init_cbor_eph_key, sizeof(init_cbor_eph_key)) != 0) { + return -1; + } + puts("[initiator]: preset cid: ONLY FOR TESTING"); + if (edhoc_session_preset_cidi(&_ctx, init_cid, sizeof(init_cid)) != 0) { + return -1; + } + + return 0; +} + +#endif /* CONFIG_INITIATOR */ diff --git a/tests/pkg_edhoc_c/main.c b/tests/pkg_edhoc_c/main.c new file mode 100644 index 0000000000..fa478b43bb --- /dev/null +++ b/tests/pkg_edhoc_c/main.c @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2021 Inria + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup tests + * @{ + * + * @file + * @brief EDHOC handhshake over COAP using EDHOC-C + * + * @author Timothy Claeys + * @author Francisco Molina + */ + +#include + +#include "net/nanocoap_sock.h" +#include "shell.h" +#include "thread.h" + +#include "edhoc/edhoc.h" +#include "edhoc_keys.h" + +#define MAIN_QUEUE_SIZE (4) +static msg_t _main_msg_queue[MAIN_QUEUE_SIZE]; + +#if IS_ACTIVE(CONFIG_RESPONDER) +static char _nanocoap_server_stack[THREAD_STACKSIZE_MAIN]; +#define NANOCOAP_SERVER_QUEUE_SIZE (4) +static msg_t _nanocoap_server_msg_queue[NANOCOAP_SERVER_QUEUE_SIZE]; +#define NANOCOAP_BUF_SIZE (512U) +extern int responder_cli_init(void); +extern int responder_cmd(int argc, char **argv); +#endif + +#if IS_ACTIVE(CONFIG_INITIATOR) +extern int initiator_cmd(int argc, char **argv); +extern int initiator_cli_init(void); +#endif + +static const shell_command_t shell_commands[] = { + +#if IS_ACTIVE(CONFIG_INITIATOR) + { "init", "EDHOC Initiator cli", initiator_cmd }, +#endif +#if IS_ACTIVE(CONFIG_RESPONDER) + { "resp", "EDHOC Responder cli", responder_cmd }, +#endif + { NULL, NULL, NULL } +}; + +#if IS_ACTIVE(CONFIG_RESPONDER) +static void *_nanocoap_server_thread(void *arg) +{ + (void)arg; + + /* nanocoap_server uses gnrc sock which uses gnrc which needs a msg queue */ + msg_init_queue(_nanocoap_server_msg_queue, NANOCOAP_SERVER_QUEUE_SIZE); + + /* initialize nanocoap server instance */ + uint8_t buf[NANOCOAP_BUF_SIZE]; + sock_udp_ep_t local = { .port = COAP_PORT, .family = AF_INET6 }; + nanocoap_server(&local, buf, sizeof(buf)); + + return NULL; +} +#endif + +int main(void) +{ +#if IS_ACTIVE(CONFIG_INITIATOR) + if (initiator_cli_init()) { + return -1; + } +#endif +#if IS_ACTIVE(CONFIG_RESPONDER) + if (responder_cli_init()) { + return -1; + } + + /* start nanocoap server thread */ + thread_create(_nanocoap_server_stack, sizeof(_nanocoap_server_stack), + THREAD_PRIORITY_MAIN - 1, + THREAD_CREATE_STACKTEST, + _nanocoap_server_thread, NULL, "nanocoap server"); +#endif + + /* the shell contains commands that receive packets via GNRC and thus + needs a msg queue */ + msg_init_queue(_main_msg_queue, MAIN_QUEUE_SIZE); + + puts("Starting the shell"); + char line_buf[SHELL_DEFAULT_BUFSIZE]; + shell_run(shell_commands, line_buf, SHELL_DEFAULT_BUFSIZE); + + /* should be never reached */ + return 0; +} diff --git a/tests/pkg_edhoc_c/responder.c b/tests/pkg_edhoc_c/responder.c new file mode 100644 index 0000000000..860571e4ac --- /dev/null +++ b/tests/pkg_edhoc_c/responder.c @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2021 Inria + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup tests + * @{ + * + * @file + * @brief EDHOC coap responder implementation + * + * @author Timothy Claeys + * @author Francisco Molina + * + */ + +#include + +#include "net/ipv6.h" +#include "net/nanocoap_sock.h" +#include "shell.h" + +#include "edhoc/edhoc.h" +#include "edhoc_keys.h" + +#if IS_USED(MODULE_WOLFSSL) +#include "wolfssl/wolfcrypt/sha256.h" +#elif IS_USED(MODULE_TINYCRYPT) +#include "tinycrypt/sha256.h" +#endif + +#define ENABLE_DEBUG 0 +#include "debug.h" + +#define COAP_BUF_SIZE (256U) + +#if IS_ACTIVE(CONFIG_RESPONDER) + +extern void print_bstr(const uint8_t *bstr, size_t bstr_len); +extern int edhoc_setup(edhoc_ctx_t *ctx, edhoc_conf_t *conf, edhoc_role_t role, + cose_key_t *auth_key, cred_id_t *cred_id, rpk_t *rpk, + void *hash_ctx); +extern void edhoc_oscore_exporter(edhoc_ctx_t *ctx, uint8_t *secret, size_t secret_len, + uint8_t *salt, size_t salt_len); + +static edhoc_ctx_t _ctx; +static edhoc_conf_t _conf; +static rpk_t _rpk; +static cred_id_t _cred_id; +static cose_key_t _auth_key; +#if IS_USED(MODULE_WOLFSSL) +static wc_Sha256 _sha_r; +#elif IS_USED(MODULE_TINYCRYPT) +struct tc_sha256_state_struct _sha_r; +#endif + +ssize_t _edhoc_handler(coap_pkt_t *pkt, uint8_t *buf, size_t len, void *context) +{ + (void)context; + ssize_t msg_len = 0; + + printf("[responder]: received an EDHOC message (len %d):\n", pkt->payload_len); + print_bstr(pkt->payload, pkt->payload_len); + + if (_ctx.state == EDHOC_FINALIZED || _ctx.state == EDHOC_FAILED) { + _ctx.state = EDHOC_WAITING; + } + + if (_ctx.state == EDHOC_WAITING) { + uint8_t msg[COAP_BUF_SIZE]; + if ((msg_len = + edhoc_create_msg2(&_ctx, pkt->payload, pkt->payload_len, msg, sizeof(msg))) >= 0) { + printf("[responder]: sending msg2 (%d bytes):\n", (int) msg_len); + print_bstr(msg, msg_len); + msg_len = coap_reply_simple(pkt, COAP_CODE_204, buf, len, COAP_FORMAT_OCTET, msg, + msg_len); + } + else { + puts("[responder]: failed to create msg2"); + coap_reply_simple(pkt, COAP_CODE_404, buf, len, COAP_FORMAT_TEXT, NULL, 0); + return -1; + } + } + else if (_ctx.state == EDHOC_SENT_MESSAGE_2) { + puts("[responder]: finalize exchange"); + edhoc_resp_finalize(&_ctx, pkt->payload, pkt->payload_len, false, NULL, 0); + msg_len = coap_reply_simple(pkt, COAP_CODE_204, buf, len, COAP_FORMAT_OCTET, NULL, 0); + } + + if (_ctx.state == EDHOC_FINALIZED) { + puts("[responder]: handshake successfully completed"); + } + + return msg_len; +} + +/* must be sorted by path (ASCII order) */ +const coap_resource_t coap_resources[] = { + COAP_WELL_KNOWN_CORE_DEFAULT_HANDLER, + { "/.well-known/edhoc", COAP_POST, _edhoc_handler, NULL }, +}; + +const unsigned coap_resources_numof = ARRAY_SIZE(coap_resources); + +int responder_cmd(int argc, char **argv) +{ + if (argc < 2) { + puts("Usage:"); + puts("\tresp oscore: derive OSCORE secret and salt"); + return -1; + } + + if (!strcmp(argv[1], "oscore")) { + if (_ctx.state == EDHOC_FINALIZED) { + uint8_t secret[16]; + uint8_t salt[8]; + edhoc_oscore_exporter(&_ctx, secret, sizeof(secret), salt, sizeof(salt)); + } + else { + puts("error: perform edhoc handshake first"); + } + } + + return 0; +} + +int responder_cli_init(void) +{ + if (edhoc_setup(&_ctx, &_conf, EDHOC_IS_RESPONDER, &_auth_key, &_cred_id, + &_rpk, &_sha_r)) { + puts("[responder]: error during setup"); + return -1; + } + + /* use fixed values only for testing purposes */ + puts("[responder]: load ephemeral key: ONLY FOR TESTING"); + if (edhoc_load_ephkey(&_ctx, resp_cbor_eph_key, sizeof(resp_cbor_eph_key)) != 0) { + return -1; + } + puts("[responder]: preset cid: ONLY FOR TESTING"); + if (edhoc_session_preset_cidr(&_ctx, resp_cid, sizeof(resp_cid)) != 0) { + return -1; + } + + return 0; +} + +#endif /* CONFIG_RESPONDER */ diff --git a/tests/pkg_edhoc_c/tests-with-config/01-run.py b/tests/pkg_edhoc_c/tests-with-config/01-run.py new file mode 100755 index 0000000000..680fec0058 --- /dev/null +++ b/tests/pkg_edhoc_c/tests-with-config/01-run.py @@ -0,0 +1,98 @@ +#!/usr/bin/env python3 + +# Copyright (C) 2021 Inria +# +# This file is subject to the terms and conditions of the GNU Lesser +# General Public License v2.1. See the file LICENSE in the top level +# directory for more details. + +import os +import sys + +from testrunner import run + +# Default COAP port on which the edhoc responder is running +COAP_PORT = int(os.getenv("COAP_PORT", "5683")) + +LAKE_WG_EDHOC_TV_34900_MSG1 = \ + "0x0d 0x00 0x58 0x20 0x8d 0x3e 0xf5 0x6d\n" + \ + "0x1b 0x75 0x0a 0x43 0x51 0xd6 0x8a 0xc2\n" + \ + "0x50 0xa0 0xe8 0x83 0x79 0x0e 0xfc 0x80\n" + \ + "0xa5 0x38 0xa4 0x44 0xee 0x9e 0x2b 0x57\n" + \ + "0xe2 0x44 0x1a 0x7c 0x21" + +LAKE_WG_EDHOC_TV_34900_MSG2 = \ + "0x58 0x20 0x52 0xfb 0xa0 0xbd 0xc8 0xd9\n" + \ + "0x53 0xdd 0x86 0xce 0x1a 0xb2 0xfd 0x7c\n" + \ + "0x05 0xa4 0x65 0x8c 0x7c 0x30 0xaf 0xdb\n" + \ + "0xfc 0x33 0x01 0x04 0x70 0x69 0x45 0x1b\n" + \ + "0xaf 0x35 0x37 0x4a 0xa3 0xf1 0xbd 0x5d\n" + \ + "0x02 0x8d 0x19 0xcf 0x3c 0x99" + +LAKE_WG_EDHOC_TV_34900_MSG3 = \ + "0x37 0x52 0xd5 0x53 0x5f 0x31 0x47 0xe8\n" + \ + "0x5f 0x1c 0xfa 0xcd 0x9e 0x78 0xab 0xf9\n" + \ + "0xe0 0xa8 0x1b 0xbf" + +LAKE_WG_EDHOC_TV_34900_TH4 = \ + "0x7c 0xcf 0xde 0xdc 0x2c 0x10 0xca 0x03\n" + \ + "0x56 0xe9 0x57 0xb9 0xf6 0xa5 0x92 0xe0\n" + \ + "0xfa 0x74 0xdb 0x2a 0xb5 0x4f 0x59 0x24\n" + \ + "0x40 0x96 0xf9 0xa2 0xac 0x56 0xd2 0x07" + +LAKE_WG_EDHOC_TV_34900_OSCORE_SECRET = \ + "0x5b 0xb2 0xae 0xe2 0x5b 0x16 0x0e 0x7c\n" + \ + "0x6d 0x26 0x12 0xb0 0xa6 0x01 0x09 0x16" + +LAKE_WG_EDHOC_TV_34900_OSCORE_SALT = \ + "0x8e 0x44 0x92 0x10 0xe0 0x3b 0xc2 0x9d" + + +def get_ipv6_addr(child): + child.expect_exact('>') + child.sendline('ifconfig') + # Get device local address + child.expect( + r"inet6\s+addr:\s+(?P[0-9a-fA-F:]+:[A-Fa-f:0-9]+)" + " scope: link VAL" + ) + + return child.match.group("lladdr").lower() + + +def testfunc(child): + child.sendline("init handshake {} {}".format( + get_ipv6_addr(child), COAP_PORT)) + child.expect_exact("[initiator]: sending msg1 (37 bytes):") + for line in LAKE_WG_EDHOC_TV_34900_MSG1.split('\n'): + child.expect_exact(line) + child.expect_exact("[responder]: received an EDHOC message (len 37):") + child.expect_exact("[responder]: sending msg2 (46 bytes):") + for line in LAKE_WG_EDHOC_TV_34900_MSG2.split('\n'): + child.expect_exact(line) + child.expect_exact("[initiator]: received a message (46 bytes):") + child.expect_exact("[initiator]: sending msg3 (20 bytes)") + for line in LAKE_WG_EDHOC_TV_34900_MSG3.split('\n'): + child.expect_exact(line) + child.expect_exact("[responder]: finalize exchange") + child.expect_exact("[responder]: handshake successfully completed") + child.expect_exact("[initiator]: handshake successfully completed") + child.expect_exact("[initiator]: Transcript hash 4 (32 bytes):") + for line in LAKE_WG_EDHOC_TV_34900_TH4.split('\n'): + child.expect_exact(line) + child.sendline("init oscore") + child.expect_exact("OSCORE secret:") + for line in LAKE_WG_EDHOC_TV_34900_OSCORE_SECRET.split('\n'): + child.expect_exact(line) + child.expect_exact("OSCORE salt:") + child.expect_exact(LAKE_WG_EDHOC_TV_34900_OSCORE_SALT) + child.sendline("resp oscore") + child.expect_exact("OSCORE secret:") + for line in LAKE_WG_EDHOC_TV_34900_OSCORE_SECRET.split('\n'): + child.expect_exact(line) + child.expect_exact("OSCORE salt:") + child.expect_exact(LAKE_WG_EDHOC_TV_34900_OSCORE_SALT) + + +if __name__ == "__main__": + sys.exit(run(testfunc))