/*- * Copyright 2021 John-Mark Gurney. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * */ #include #include 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 reqreset[] = "reqreset"; static const uint8_t confirm[] = "confirm"; static int comms_pktbuf_equal(struct pktbuf a, struct pktbuf b); /* returns 1 if equal, 0 if not equal */ static int comms_pktbuf_equal(struct pktbuf a, struct pktbuf b) { if (a.pktlen != b.pktlen) return 0; return memcmp(a.pkt, b.pkt, a.pktlen) == 0; } size_t _comms_state_size() { return sizeof(struct comms_state); } size_t _strobe_state_size() { return sizeof(strobe_s); } void comms_init(struct comms_state *cs, process_msgfunc_t pmf, struct pktbuf *shared) { *cs = (struct comms_state){ .cs_procmsg = pmf, }; strobe_init(&cs->cs_start, domain, sizeof domain - 1); if (shared != NULL) strobe_key(&cs->cs_start, SYM_KEY, shared->pkt, shared->pktlen); /* 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_pending = cs->cs_active; } #define CONFIRMED_STR_BASE "confirmed" #define CONFIRMED_STR ((const uint8_t *)CONFIRMED_STR_BASE) #define CONFIRMED_STR_LEN (sizeof(CONFIRMED_STR_BASE) - 1) static void _comms_process_session(struct comms_state *cs, struct comms_session *sess, struct pktbuf pbin, struct pktbuf *pbout) { strobe_s tmp; uint8_t buf[64] = {}; struct pktbuf pbmsg, pbrep; ssize_t cnt, ret, msglen; /* save the state incase the message is bad */ tmp = sess->cs_crypto; strobe_attach_buffer(&sess->cs_crypto, pbin.pkt, pbin.pktlen); /* if the packet is too short, ignore */ if (pbin.pktlen < MAC_LEN) goto badmsg; cnt = strobe_get(&sess->cs_crypto, APP_CIPHERTEXT, buf, pbin.pktlen - MAC_LEN); msglen = cnt; cnt = strobe_get(&sess->cs_crypto, MAC, pbin.pkt + (pbin.pktlen - MAC_LEN), MAC_LEN); /* MAC check failed */ if (cnt == -1) { badmsg: /* restore the previous state */ sess->cs_crypto = tmp; pbout->pktlen = 0; return; } /* * if we have arrived here, MAC has been verified, and buf now * contains the data to operate upon. */ /* attach the buffer for output */ strobe_attach_buffer(&sess->cs_crypto, pbout->pkt, pbout->pktlen); ret = 0; switch (sess->cs_state) { case COMMS_WAIT_REQUEST: if (msglen != 24 || memcmp(reqreset, &buf[16], sizeof reqreset - 1) != 0) goto badmsg; bare_strobe_randomize(buf, CHALLENGE_LEN); ret = strobe_put(&sess->cs_crypto, APP_CIPHERTEXT, buf, CHALLENGE_LEN); ret += strobe_put(&sess->cs_crypto, MAC, NULL, MAC_LEN); strobe_operate(&sess->cs_crypto, RATCHET, NULL, 32); sess->cs_state = COMMS_WAIT_CONFIRM; break; case COMMS_WAIT_CONFIRM: if (msglen != 7 || memcmp(confirm, buf, sizeof confirm - 1) != 0) goto badmsg; ret = strobe_put(&sess->cs_crypto, APP_CIPHERTEXT, CONFIRMED_STR, CONFIRMED_STR_LEN); ret += strobe_put(&sess->cs_crypto, MAC, NULL, MAC_LEN); sess->cs_state = COMMS_PROCESS_MSGS; break; case COMMS_PROCESS_MSGS: { uint8_t repbuf[pbout->pktlen - MAC_LEN]; memset(repbuf, '\x00', sizeof repbuf); pbmsg.pkt = buf; pbmsg.pktlen = msglen; pbrep.pkt = repbuf; pbrep.pktlen = sizeof repbuf; cs->cs_procmsg(pbmsg, &pbrep); ret = strobe_put(&sess->cs_crypto, APP_CIPHERTEXT, repbuf, pbrep.pktlen); ret += strobe_put(&sess->cs_crypto, MAC, NULL, MAC_LEN); break; } } /* set the output buffer length */ pbout->pktlen = ret; } /* * encrypted data to be processed is passed in via pbin. * * The pktbuf pointed to by pbout contains the buffer that a [encrypted] * response will be written to. The length needs to be updated, where 0 * means no reply. */ void comms_process(struct comms_state *cs, struct pktbuf pbin, struct pktbuf *pbout) { struct pktbuf pbouttmp; /* if the current msg matches the previous */ if (comms_pktbuf_equal(pbin, cs->cs_prevmsg)) { /* send the previous response */ pbout->pktlen = cs->cs_prevmsgresp.pktlen; memcpy(pbout->pkt, cs->cs_prevmsgresp.pkt, pbout->pktlen); return; } /* try to use the active session */ pbouttmp = *pbout; _comms_process_session(cs, &cs->cs_active, pbin, &pbouttmp); if (pbouttmp.pktlen != 0) { retmsg: /* we accepted a new message store it */ *pbout = pbouttmp; /* store the req */ cs->cs_prevmsg.pkt = cs->cs_prevmsgbuf; cs->cs_prevmsg.pktlen = pbin.pktlen; memcpy(cs->cs_prevmsg.pkt, pbin.pkt, pbin.pktlen); /* store the response */ cs->cs_prevmsgresp.pkt = cs->cs_prevmsgrespbuf; cs->cs_prevmsgresp.pktlen = pbout->pktlen; memcpy(cs->cs_prevmsgresp.pkt, pbout->pkt, pbout->pktlen); } else { /* active session didn't work, try cs_pending */ pbouttmp = *pbout; _comms_process_session(cs, &cs->cs_pending, pbin, &pbouttmp); 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, }; 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, }; pbouttmp = *pbout; _comms_process_session(cs, &tmpsess, pbin, &pbouttmp); if (tmpsess.cs_state == COMMS_WAIT_CONFIRM) { /* new request for session */ cs->cs_pending = tmpsess; *pbout = pbouttmp; } else { /* no packet to reply with */ pbout->pktlen = 0; } } }