| @@ -29,7 +29,8 @@ | |||||
| static const size_t MAC_LEN = 8; | static const size_t MAC_LEN = 8; | ||||
| static const size_t CHALLENGE_LEN = 16; | static const size_t CHALLENGE_LEN = 16; | ||||
| static const uint8_t domain[] = "com.funkthat.lora.irrigation.shared.v0.0.1"; | |||||
| static const uint8_t shared_domain[] = "com.funkthat.lora.irrigation.shared.v0.0.1"; | |||||
| static const uint8_t ecdhe_domain[] = "com.funkthat.lora.irrigation.ecdhe.v0.0.1"; | |||||
| static const uint8_t reqreset[] = "reqreset"; | static const uint8_t reqreset[] = "reqreset"; | ||||
| static const uint8_t confirm[] = "confirm"; | static const uint8_t confirm[] = "confirm"; | ||||
| @@ -60,25 +61,55 @@ _strobe_state_size() | |||||
| return sizeof(strobe_s); | return sizeof(strobe_s); | ||||
| } | } | ||||
| void | |||||
| comms_init(struct comms_state *cs, process_msgfunc_t pmf, struct pktbuf *shared) | |||||
| int | |||||
| comms_init(struct comms_state *cs, process_msgfunc_t pmf, | |||||
| struct pktbuf *shared, struct pktbuf *resp_key, struct pktbuf *init_pubkey) | |||||
| { | { | ||||
| unsigned char buf[EC_PUBLIC_BYTES * 2]; | |||||
| strobe_s *crypt; | |||||
| *cs = (struct comms_state){ | *cs = (struct comms_state){ | ||||
| .cs_procmsg = pmf, | .cs_procmsg = pmf, | ||||
| }; | }; | ||||
| strobe_init(&cs->cs_start, domain, sizeof domain - 1); | |||||
| crypt = &cs->cs_start.cs_crypto; | |||||
| if (shared != NULL) { | |||||
| strobe_init(crypt, shared_domain, sizeof shared_domain - 1); | |||||
| strobe_key(crypt, SYM_KEY, shared->pkt, shared->pktlen); | |||||
| cs->cs_start.cs_state = COMMS_WAIT_REQUEST_SHARED; | |||||
| } else if (resp_key != NULL && init_pubkey != NULL) { | |||||
| strobe_init(crypt, ecdhe_domain, sizeof ecdhe_domain - 1); | |||||
| if (resp_key->pktlen != EC_PRIVATE_BYTES || | |||||
| init_pubkey->pktlen != EC_PUBLIC_BYTES) | |||||
| return 0; | |||||
| /* store our private key */ | |||||
| memcpy(cs->cs_respkey, resp_key->pkt, resp_key->pktlen); | |||||
| x25519_base(cs->cs_resppubkey, resp_key->pkt, 1); | |||||
| /* store the public key to associate with */ | |||||
| memcpy(cs->cs_initpubkey, init_pubkey->pkt, init_pubkey->pktlen); | |||||
| /* public keys */ | |||||
| memcpy(&buf[0], cs->cs_initpubkey, EC_PUBLIC_BYTES); | |||||
| memcpy(&buf[EC_PUBLIC_BYTES], cs->cs_resppubkey, EC_PUBLIC_BYTES); | |||||
| strobe_key(crypt, SYM_KEY, buf, EC_PUBLIC_BYTES * 2); | |||||
| if (shared != NULL) | |||||
| strobe_key(&cs->cs_start, SYM_KEY, shared->pkt, shared->pktlen); | |||||
| cs->cs_start.cs_state = COMMS_WAIT_REQUEST_ECDHE; | |||||
| } else { | |||||
| return 0; | |||||
| } | |||||
| /* copy starting state over to initial state */ | /* copy starting state over to initial state */ | ||||
| cs->cs_active = (struct comms_session){ | |||||
| .cs_crypto = cs->cs_start, | |||||
| .cs_state = COMMS_WAIT_REQUEST, | |||||
| }; | |||||
| cs->cs_active = cs->cs_start; | |||||
| cs->cs_pending = cs->cs_active; | cs->cs_pending = cs->cs_active; | ||||
| return 1; | |||||
| } | } | ||||
| #define CONFIRMED_STR_BASE "confirmed" | #define CONFIRMED_STR_BASE "confirmed" | ||||
| @@ -128,7 +159,7 @@ badmsg: | |||||
| ret = 0; | ret = 0; | ||||
| switch (sess->cs_state) { | switch (sess->cs_state) { | ||||
| case COMMS_WAIT_REQUEST: | |||||
| case COMMS_WAIT_REQUEST_SHARED: | |||||
| if (msglen != 24 || memcmp(reqreset, &buf[16], | if (msglen != 24 || memcmp(reqreset, &buf[16], | ||||
| sizeof reqreset - 1) != 0) | sizeof reqreset - 1) != 0) | ||||
| goto badmsg; | goto badmsg; | ||||
| @@ -143,6 +174,55 @@ badmsg: | |||||
| sess->cs_state = COMMS_WAIT_CONFIRM; | sess->cs_state = COMMS_WAIT_CONFIRM; | ||||
| break; | break; | ||||
| case COMMS_WAIT_REQUEST_ECDHE: | |||||
| { | |||||
| unsigned char ephkey[EC_PRIVATE_BYTES]; | |||||
| unsigned char ephpubkey[EC_PUBLIC_BYTES]; | |||||
| unsigned char keybuf[EC_PUBLIC_BYTES * 2]; | |||||
| if (msglen != (EC_PUBLIC_BYTES + 8) || memcmp(reqreset, &buf[EC_PUBLIC_BYTES], | |||||
| sizeof reqreset - 1) != 0) | |||||
| goto badmsg; | |||||
| /* DH(s, re) */ | |||||
| memcpy(&keybuf[0], cs->cs_resppubkey, EC_PUBLIC_BYTES); | |||||
| if (x25519(&keybuf[0], cs->cs_respkey, buf, 1) != 0) | |||||
| goto badmsg; | |||||
| /* DH(s, rs) */ | |||||
| memcpy(&keybuf[EC_PUBLIC_BYTES], cs->cs_resppubkey, EC_PUBLIC_BYTES); | |||||
| if (x25519(&keybuf[EC_PUBLIC_BYTES], cs->cs_respkey, cs->cs_initpubkey, 1) != 0) | |||||
| goto badmsg; | |||||
| /* key(DH() + DH()) */ | |||||
| strobe_key(&sess->cs_crypto, SYM_KEY, keybuf, EC_PUBLIC_BYTES * 2); | |||||
| /* generate ephemeral */ | |||||
| bare_strobe_randomize(ephkey, EC_PRIVATE_BYTES); | |||||
| if (x25519_base(ephpubkey, ephkey, 1) != 0) | |||||
| goto badmsg; | |||||
| ret = strobe_put(&sess->cs_crypto, APP_CIPHERTEXT, ephpubkey, | |||||
| EC_PUBLIC_BYTES); | |||||
| ret += strobe_put(&sess->cs_crypto, MAC, NULL, MAC_LEN); | |||||
| /* DH(e, re) */ | |||||
| memcpy(&keybuf[0], ephpubkey, EC_PUBLIC_BYTES); | |||||
| if (x25519(&keybuf[0], ephkey, buf, 1) != 0) | |||||
| goto badmsg; | |||||
| /* DH(e, rs) */ | |||||
| memcpy(&keybuf[EC_PUBLIC_BYTES], ephpubkey, EC_PUBLIC_BYTES); | |||||
| if (x25519(&keybuf[EC_PUBLIC_BYTES], ephkey, cs->cs_initpubkey, 1) != 0) | |||||
| goto badmsg; | |||||
| /* key(DH() + DH()) */ | |||||
| strobe_key(&sess->cs_crypto, SYM_KEY, keybuf, EC_PUBLIC_BYTES * 2); | |||||
| sess->cs_state = COMMS_WAIT_CONFIRM; | |||||
| break; | |||||
| } | |||||
| case COMMS_WAIT_CONFIRM: | case COMMS_WAIT_CONFIRM: | ||||
| if (msglen != 7 || memcmp(confirm, buf, | if (msglen != 7 || memcmp(confirm, buf, | ||||
| sizeof confirm - 1) != 0) | sizeof confirm - 1) != 0) | ||||
| @@ -227,20 +307,14 @@ retmsg: | |||||
| if (cs->cs_pending.cs_state == COMMS_PROCESS_MSGS) { | if (cs->cs_pending.cs_state == COMMS_PROCESS_MSGS) { | ||||
| /* new active state */ | /* new active state */ | ||||
| cs->cs_active = cs->cs_pending; | cs->cs_active = cs->cs_pending; | ||||
| cs->cs_pending = (struct comms_session){ | |||||
| .cs_crypto = cs->cs_start, | |||||
| .cs_state = COMMS_WAIT_REQUEST, | |||||
| }; | |||||
| cs->cs_pending = cs->cs_start; | |||||
| goto retmsg; | goto retmsg; | ||||
| } | } | ||||
| /* pending session didn't work, maybe new */ | /* pending session didn't work, maybe new */ | ||||
| struct comms_session tmpsess; | struct comms_session tmpsess; | ||||
| tmpsess = (struct comms_session){ | |||||
| .cs_crypto = cs->cs_start, | |||||
| .cs_state = COMMS_WAIT_REQUEST, | |||||
| }; | |||||
| tmpsess = cs->cs_start; | |||||
| pbouttmp = *pbout; | pbouttmp = *pbout; | ||||
| _comms_process_session(cs, &tmpsess, pbin, &pbouttmp); | _comms_process_session(cs, &tmpsess, pbin, &pbouttmp); | ||||
| @@ -28,6 +28,7 @@ | |||||
| #include <stdint.h> | #include <stdint.h> | ||||
| #include <strobe.h> | #include <strobe.h> | ||||
| #include <x25519.h> | |||||
| #define COMMS_MAXMSG 64 | #define COMMS_MAXMSG 64 | ||||
| @@ -40,7 +41,8 @@ struct pktbuf { | |||||
| typedef void (*process_msgfunc_t)(struct pktbuf, struct pktbuf *); | typedef void (*process_msgfunc_t)(struct pktbuf, struct pktbuf *); | ||||
| enum comm_state { | enum comm_state { | ||||
| COMMS_WAIT_REQUEST = 1, | |||||
| COMMS_WAIT_REQUEST_SHARED = 1, | |||||
| COMMS_WAIT_REQUEST_ECDHE, | |||||
| COMMS_WAIT_CONFIRM, | COMMS_WAIT_CONFIRM, | ||||
| COMMS_PROCESS_MSGS, | COMMS_PROCESS_MSGS, | ||||
| }; | }; | ||||
| @@ -72,7 +74,11 @@ struct comms_state { | |||||
| struct comms_session cs_active; /* current active session */ | struct comms_session cs_active; /* current active session */ | ||||
| struct comms_session cs_pending; /* current pending session */ | struct comms_session cs_pending; /* current pending session */ | ||||
| strobe_s cs_start; /* special starting state cache */ | |||||
| unsigned char cs_respkey[EC_PRIVATE_BYTES]; /* private key for device */ | |||||
| unsigned char cs_resppubkey[EC_PUBLIC_BYTES]; /* public key for device */ | |||||
| unsigned char cs_initpubkey[EC_PUBLIC_BYTES]; /* public key for initiator */ | |||||
| struct comms_session cs_start; /* special starting state cache */ | |||||
| process_msgfunc_t cs_procmsg; | process_msgfunc_t cs_procmsg; | ||||
| @@ -86,5 +92,5 @@ struct comms_state { | |||||
| size_t _strobe_state_size(); | size_t _strobe_state_size(); | ||||
| size_t _comms_state_size(); | size_t _comms_state_size(); | ||||
| void comms_init(struct comms_state *, process_msgfunc_t, struct pktbuf *); | |||||
| int comms_init(struct comms_state *, process_msgfunc_t, struct pktbuf *, struct pktbuf *, struct pktbuf *); | |||||
| void comms_process(struct comms_state *, struct pktbuf, struct pktbuf *); | void comms_process(struct comms_state *, struct pktbuf, struct pktbuf *); | ||||
| @@ -929,7 +929,7 @@ class TestLORANode(unittest.IsolatedAsyncioTestCase): | |||||
| shared_key = os.urandom(32) | shared_key = os.urandom(32) | ||||
| # Initialize everything | # Initialize everything | ||||
| lora_comms.comms_init(commstate, cb, make_pktbuf(shared_key)) | |||||
| lora_comms.comms_init(commstate, cb, make_pktbuf(shared_key), None, None) | |||||
| # Create test fixture, only use it to init crypto state | # Create test fixture, only use it to init crypto state | ||||
| tsd = SyncDatagram() | tsd = SyncDatagram() | ||||
| @@ -996,6 +996,103 @@ class TestLORANode(unittest.IsolatedAsyncioTestCase): | |||||
| self.assertFalse(out) | self.assertFalse(out) | ||||
| @timeout(2) | |||||
| async def test_ccode_ecdhe(self): | |||||
| _self = self | |||||
| from ctypes import c_uint8 | |||||
| # seed the RNG | |||||
| prngseed = b'abc123' | |||||
| lora_comms.strobe_seed_prng((c_uint8 * | |||||
| len(prngseed))(*prngseed), len(prngseed)) | |||||
| # Create the state for testing | |||||
| commstate = lora_comms.CommsState() | |||||
| # These are the expected messages and their arguments | |||||
| exptmsgs = [ | |||||
| (CMD_WAITFOR, [ 30 ]), | |||||
| (CMD_RUNFOR, [ 1, 50 ]), | |||||
| (CMD_PING, [ ]), | |||||
| (CMD_TERMINATE, [ ]), | |||||
| ] | |||||
| def procmsg(msg, outbuf): | |||||
| msgbuf = msg._from() | |||||
| cmd = msgbuf[0] | |||||
| args = [ int.from_bytes(msgbuf[x:x + 4], | |||||
| byteorder='little') for x in range(1, len(msgbuf), | |||||
| 4) ] | |||||
| if exptmsgs[0] == (cmd, args): | |||||
| exptmsgs.pop(0) | |||||
| outbuf[0].pkt[0] = cmd | |||||
| outbuf[0].pktlen = 1 | |||||
| else: #pragma: no cover | |||||
| raise RuntimeError('cmd not found') | |||||
| # wrap the callback function | |||||
| cb = lora_comms.process_msgfunc_t(procmsg) | |||||
| class CCodeSD(MockSyncDatagram): | |||||
| async def runner(self): | |||||
| for expectlen in [ 40, 17, 9, 9, 9, 9 ]: | |||||
| # get message | |||||
| inmsg = await self.get() | |||||
| # process the test message | |||||
| out = lora_comms.comms_process_wrap( | |||||
| commstate, inmsg) | |||||
| # make sure the reply matches length | |||||
| _self.assertEqual(expectlen, len(out)) | |||||
| # save what was originally replied | |||||
| origmsg = out | |||||
| # pretend that the reply didn't make it | |||||
| out = lora_comms.comms_process_wrap( | |||||
| commstate, inmsg) | |||||
| # make sure that the reply matches | |||||
| # the previous | |||||
| _self.assertEqual(origmsg, out) | |||||
| # pass the reply back | |||||
| await self.put(out) | |||||
| # Generate keys | |||||
| initkey = X25519.gen() | |||||
| respkey = X25519.gen() | |||||
| # Initialize everything | |||||
| lora_comms.comms_init(commstate, cb, None, make_pktbuf(respkey.getpriv()), make_pktbuf(initkey.getpub())) | |||||
| # Create test fixture | |||||
| tsd = CCodeSD() | |||||
| l = LORANode(tsd, init_key=initkey, resp_pub=respkey.getpub()) | |||||
| # Send various messages | |||||
| await l.start() | |||||
| await l.waitfor(30) | |||||
| await l.runfor(1, 50) | |||||
| await l.ping() | |||||
| await l.terminate() | |||||
| await tsd.drain() | |||||
| # Make sure all messages have been processed | |||||
| self.assertTrue(tsd.sendq.empty()) | |||||
| self.assertTrue(tsd.recvq.empty()) | |||||
| # Make sure all the expected messages have been | |||||
| # processed. | |||||
| self.assertFalse(exptmsgs) | |||||
| #_debprint('done') | |||||
| @timeout(2) | @timeout(2) | ||||
| async def test_ccode(self): | async def test_ccode(self): | ||||
| _self = self | _self = self | ||||
| @@ -1064,7 +1161,7 @@ class TestLORANode(unittest.IsolatedAsyncioTestCase): | |||||
| shared_key = os.urandom(32) | shared_key = os.urandom(32) | ||||
| # Initialize everything | # Initialize everything | ||||
| lora_comms.comms_init(commstate, cb, make_pktbuf(shared_key)) | |||||
| lora_comms.comms_init(commstate, cb, make_pktbuf(shared_key), None, None) | |||||
| # Create test fixture | # Create test fixture | ||||
| tsd = CCodeSD() | tsd = CCodeSD() | ||||
| @@ -1193,7 +1290,7 @@ class TestLORANode(unittest.IsolatedAsyncioTestCase): | |||||
| shared_key = os.urandom(32) | shared_key = os.urandom(32) | ||||
| # Initialize everything | # Initialize everything | ||||
| lora_comms.comms_init(commstate, cb, make_pktbuf(shared_key)) | |||||
| lora_comms.comms_init(commstate, cb, make_pktbuf(shared_key), None, None) | |||||
| # Create test fixture | # Create test fixture | ||||
| tsd = CCodeSD1() | tsd = CCodeSD1() | ||||
| @@ -1276,7 +1373,7 @@ class TestLoRaNodeMulticast(unittest.IsolatedAsyncioTestCase): | |||||
| shared_key = os.urandom(32) | shared_key = os.urandom(32) | ||||
| # Initialize everything | # Initialize everything | ||||
| lora_comms.comms_init(commstate, cb, make_pktbuf(shared_key)) | |||||
| lora_comms.comms_init(commstate, cb, make_pktbuf(shared_key), None, None) | |||||
| # create the object we are testing | # create the object we are testing | ||||
| msd = MulticastSyncDatagram(self.maddr) | msd = MulticastSyncDatagram(self.maddr) | ||||
| @@ -83,12 +83,21 @@ class CommsSession(Structure,StructureRepr): | |||||
| ('cs_state', c_int), | ('cs_state', c_int), | ||||
| ] | ] | ||||
| EC_PUBLIC_BYTES = 32 | |||||
| EC_PRIVATE_BYTES = 32 | |||||
| class CommsState(Structure,StructureRepr): | class CommsState(Structure,StructureRepr): | ||||
| _fields_ = [ | _fields_ = [ | ||||
| # The alignment of these may be off | # The alignment of these may be off | ||||
| ('cs_active', CommsSession), | ('cs_active', CommsSession), | ||||
| ('cs_pending', CommsSession), | ('cs_pending', CommsSession), | ||||
| ('cs_start', c_uint64 * _strobe_state_u64_cnt), | |||||
| ('cs_respkey', c_uint8 * EC_PRIVATE_BYTES), | |||||
| ('cs_resppubkey', c_uint8 * EC_PUBLIC_BYTES), | |||||
| ('cs_initpubkey', c_uint8 * EC_PUBLIC_BYTES), | |||||
| ('cs_start', CommsSession), | |||||
| ('cs_procmsg', process_msgfunc_t), | ('cs_procmsg', process_msgfunc_t), | ||||
| ('cs_prevmsg', PktBuf), | ('cs_prevmsg', PktBuf), | ||||
| @@ -98,9 +107,6 @@ class CommsState(Structure,StructureRepr): | |||||
| ('cs_prevmsgrespbuf', c_uint8 * 64), | ('cs_prevmsgrespbuf', c_uint8 * 64), | ||||
| ] | ] | ||||
| EC_PUBLIC_BYTES = 32 | |||||
| EC_PRIVATE_BYTES = 32 | |||||
| if _lib is not None: | if _lib is not None: | ||||
| _lib._comms_state_size.restype = c_size_t | _lib._comms_state_size.restype = c_size_t | ||||
| _lib._comms_state_size.argtypes = () | _lib._comms_state_size.argtypes = () | ||||
| @@ -111,8 +117,8 @@ if _lib is not None: | |||||
| X25519_BASE_POINT = (c_uint8 * (256//8)).in_dll(_lib, 'X25519_BASE_POINT') | X25519_BASE_POINT = (c_uint8 * (256//8)).in_dll(_lib, 'X25519_BASE_POINT') | ||||
| for func, ret, args in [ | for func, ret, args in [ | ||||
| ('comms_init', None, (POINTER(CommsState), process_msgfunc_t, | |||||
| POINTER(PktBuf))), | |||||
| ('comms_init', c_int, (POINTER(CommsState), process_msgfunc_t, | |||||
| POINTER(PktBuf), POINTER(PktBuf), POINTER(PktBuf))), | |||||
| ('comms_process', None, (POINTER(CommsState), PktBuf, POINTER(PktBuf))), | ('comms_process', None, (POINTER(CommsState), PktBuf, POINTER(PktBuf))), | ||||
| ('strobe_seed_prng', None, (POINTER(c_uint8), c_ssize_t)), | ('strobe_seed_prng', None, (POINTER(c_uint8), c_ssize_t)), | ||||
| ('x25519', c_int, (c_uint8 * EC_PUBLIC_BYTES, c_uint8 * EC_PRIVATE_BYTES, c_uint8 * EC_PUBLIC_BYTES, c_int)), | ('x25519', c_int, (c_uint8 * EC_PUBLIC_BYTES, c_uint8 * EC_PRIVATE_BYTES, c_uint8 * EC_PUBLIC_BYTES, c_int)), | ||||