Browse Source

implement the C (responder) side of ecdhe...

main
John-Mark Gurney 3 years ago
parent
commit
6f74cee4e6
4 changed files with 215 additions and 32 deletions
  1. +93
    -19
      comms.c
  2. +9
    -3
      comms.h
  3. +101
    -4
      lora.py
  4. +12
    -6
      lora_comms.py

+ 93
- 19
comms.c View File

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


+ 9
- 3
comms.h View File

@@ -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 *);

+ 101
- 4
lora.py View File

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


+ 12
- 6
lora_comms.py View File

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


Loading…
Cancel
Save