@@ -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)), | ||||