| @@ -29,7 +29,8 @@ | |||
| static const size_t MAC_LEN = 8; | |||
| 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 confirm[] = "confirm"; | |||
| @@ -60,25 +61,55 @@ _strobe_state_size() | |||
| 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_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 */ | |||
| 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; | |||
| return 1; | |||
| } | |||
| #define CONFIRMED_STR_BASE "confirmed" | |||
| @@ -128,7 +159,7 @@ badmsg: | |||
| ret = 0; | |||
| switch (sess->cs_state) { | |||
| case COMMS_WAIT_REQUEST: | |||
| case COMMS_WAIT_REQUEST_SHARED: | |||
| if (msglen != 24 || memcmp(reqreset, &buf[16], | |||
| sizeof reqreset - 1) != 0) | |||
| goto badmsg; | |||
| @@ -143,6 +174,55 @@ badmsg: | |||
| sess->cs_state = COMMS_WAIT_CONFIRM; | |||
| 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: | |||
| if (msglen != 7 || memcmp(confirm, buf, | |||
| sizeof confirm - 1) != 0) | |||
| @@ -227,20 +307,14 @@ retmsg: | |||
| if (cs->cs_pending.cs_state == COMMS_PROCESS_MSGS) { | |||
| /* new active state */ | |||
| 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; | |||
| } | |||
| /* pending session didn't work, maybe new */ | |||
| struct comms_session tmpsess; | |||
| tmpsess = (struct comms_session){ | |||
| .cs_crypto = cs->cs_start, | |||
| .cs_state = COMMS_WAIT_REQUEST, | |||
| }; | |||
| tmpsess = cs->cs_start; | |||
| pbouttmp = *pbout; | |||
| _comms_process_session(cs, &tmpsess, pbin, &pbouttmp); | |||
| @@ -28,6 +28,7 @@ | |||
| #include <stdint.h> | |||
| #include <strobe.h> | |||
| #include <x25519.h> | |||
| #define COMMS_MAXMSG 64 | |||
| @@ -40,7 +41,8 @@ struct pktbuf { | |||
| typedef void (*process_msgfunc_t)(struct pktbuf, struct pktbuf *); | |||
| enum comm_state { | |||
| COMMS_WAIT_REQUEST = 1, | |||
| COMMS_WAIT_REQUEST_SHARED = 1, | |||
| COMMS_WAIT_REQUEST_ECDHE, | |||
| COMMS_WAIT_CONFIRM, | |||
| COMMS_PROCESS_MSGS, | |||
| }; | |||
| @@ -72,7 +74,11 @@ struct comms_state { | |||
| struct comms_session cs_active; /* current active 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; | |||
| @@ -86,5 +92,5 @@ struct comms_state { | |||
| size_t _strobe_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 *); | |||
| @@ -929,7 +929,7 @@ class TestLORANode(unittest.IsolatedAsyncioTestCase): | |||
| shared_key = os.urandom(32) | |||
| # 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 | |||
| tsd = SyncDatagram() | |||
| @@ -996,6 +996,103 @@ class TestLORANode(unittest.IsolatedAsyncioTestCase): | |||
| 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) | |||
| async def test_ccode(self): | |||
| _self = self | |||
| @@ -1064,7 +1161,7 @@ class TestLORANode(unittest.IsolatedAsyncioTestCase): | |||
| shared_key = os.urandom(32) | |||
| # 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 | |||
| tsd = CCodeSD() | |||
| @@ -1193,7 +1290,7 @@ class TestLORANode(unittest.IsolatedAsyncioTestCase): | |||
| shared_key = os.urandom(32) | |||
| # 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 | |||
| tsd = CCodeSD1() | |||
| @@ -1276,7 +1373,7 @@ class TestLoRaNodeMulticast(unittest.IsolatedAsyncioTestCase): | |||
| shared_key = os.urandom(32) | |||
| # 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 | |||
| msd = MulticastSyncDatagram(self.maddr) | |||
| @@ -83,12 +83,21 @@ class CommsSession(Structure,StructureRepr): | |||
| ('cs_state', c_int), | |||
| ] | |||
| EC_PUBLIC_BYTES = 32 | |||
| EC_PRIVATE_BYTES = 32 | |||
| class CommsState(Structure,StructureRepr): | |||
| _fields_ = [ | |||
| # The alignment of these may be off | |||
| ('cs_active', 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_prevmsg', PktBuf), | |||
| @@ -98,9 +107,6 @@ class CommsState(Structure,StructureRepr): | |||
| ('cs_prevmsgrespbuf', c_uint8 * 64), | |||
| ] | |||
| EC_PUBLIC_BYTES = 32 | |||
| EC_PRIVATE_BYTES = 32 | |||
| if _lib is not None: | |||
| _lib._comms_state_size.restype = c_size_t | |||
| _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') | |||
| 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))), | |||
| ('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)), | |||