Skip to content

Commit

Permalink
Merge pull request #1 from meshtastic/master
Browse files Browse the repository at this point in the history
Update
  • Loading branch information
Dafeman committed May 12, 2020
2 parents c4a1fe0 + 1bf9d05 commit 125035d
Show file tree
Hide file tree
Showing 20 changed files with 420 additions and 237 deletions.
2 changes: 1 addition & 1 deletion docs/faq.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ This project is still pretty young but moving at a pretty good pace. Not all fea
Most of these problems should be solved by the beta release (within three months):

- We don't make these devices and they haven't been tested by UL or the FCC. If you use them you are experimenting and we can't promise they won't burn your house down ;-)
- Encryption is turned off for now
- The encryption [implementation](software/crypto.md) has not been reviewed by an expert. (Are you an expert? Please help us)
- A number of (straightforward) software work items have to be completed before battery life matches our measurements, currently battery life is about three days. Join us on chat if you want the spreadsheet of power measurements/calculations.
- The Android API needs to be documented better
- No one has written an iOS app yet. But some good souls [are talking about it](https://github.com/meshtastic/Meshtastic-esp32/issues/14) ;-)
Expand Down
1 change: 0 additions & 1 deletion docs/software/TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ fetches the fresh nodedb.
- rx signal measurements -3 marginal, -9 bad, 10 great, -10 means almost unusable. So scale this into % signal strength. preferably as a graph, with an X indicating loss of comms.
- assign every "channel" a random shared 8 bit sync word (per 4.2.13.6 of datasheet) - use that word to filter packets before even checking CRC. This will ensure our CPU will only wake for packets on our "channel"
- Note: we do not do address filtering at the chip level, because we might need to route for the mesh
- add basic crypto - https://github.com/chegewara/esp32-mbedtls-aes-test/blob/master/main/main.c https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation - use ECB at first (though it is shit) because it doesn't require us to send 16 bytes of IV with each packet. Then OFB per example. Possibly do this crypto at the data payload level only, so that all of the packet routing metadata
is in cleartext (so that nodes will route for other radios that are cryptoed with a key we don't know)
- add frequency hopping, dependent on the gps time, make the switch moment far from the time anyone is going to be transmitting
- share channel settings over Signal (or qr code) by embedding an an URL which is handled by the MeshUtil app.
Expand Down
38 changes: 38 additions & 0 deletions docs/software/cypto.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Encryption in Meshtastic

Cryptography is tricky, so we've tried to 'simply' apply standard crypto solutions to our implementation. However,
the project developers are not cryptography experts. Therefore we ask two things:

- If you are a cryptography expert, please review these notes and our questions below. Can you help us by reviewing our
notes below and offering advice? We will happily give as much or as little credit as you wish as our thanks ;-).
- Consider our existing solution 'alpha' and probably fairly secure against an not very aggressive adversary. But until
it is reviewed by someone smarter than us, assume it might have flaws.

## Notes on implementation

- We do all crypto at the SubPacket (payload) level only, so that all meshtastic nodes will route for others - even those channels which are encrypted with a different key.
- Mostly based on reading [Wikipedia](<https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Counter_(CTR)>) and using the modes the ESP32 provides support for in hardware.
- We use AES256-CTR as a stream cypher (with zero padding on the last BLOCK) because it is well supported with hardware acceleration.

Parameters for our CTR implementation:

- Our AES key is 256 bits, shared as part of the 'Channel' specification.
- Each SubPacket will be sent as a series of 16 byte BLOCKS.
- The node number concatenated with the packet number is used as the NONCE. This counter will be stored in flash in the device and should essentially never repeat. If the user makes a new 'Channel' (i.e. picking a new random 256 bit key), the packet number will start at zero. The packet number is sent
in cleartext with each packet. The node number can be derived from the "from" field of each packet.
- Each BLOCK for a packet has an incrementing COUNTER. COUNTER starts at zero for the first block of each packet.
- The IV for each block is constructed by concatenating the NONCE as the upper 96 bits of the IV and the COUNTER as the bottom 32 bits. Note: since our packets are small counter will really never be higher than 32 (five bits).

```
You can encrypt separate messages by dividing the nonce_counter buffer in two areas: the first one used for a per-message nonce, handled by yourself, and the second one updated by this function internally.
For example, you might reserve the first 12 bytes for the per-message nonce, and the last 4 bytes for internal use. In that case, before calling this function on a new message you need to set the first 12 bytes of nonce_counter to your chosen nonce value, the last 4 to 0, and nc_off to 0 (which will cause stream_block to be ignored). That way, you can encrypt at most 2**96 messages of up to 2**32 blocks each with the same key.
The per-message nonce (or information sufficient to reconstruct it) needs to be communicated with the ciphertext and must be unique. The recommended way to ensure uniqueness is to use a message counter. An alternative is to generate random nonces, but this limits the number of messages that can be securely encrypted: for example, with 96-bit random nonces, you should not encrypt more than 2**32 messages with the same key.
Note that for both stategies, sizes are measured in blocks and that an AES block is 16 bytes.
```

## Remaining todo

- Make the packet numbers 32 bit
- Implement for NRF52 [NRF52](https://infocenter.nordicsemi.com/topic/com.nordic.infocenter.sdk5.v15.0.0/lib_crypto_aes.html#sub_aes_ctr)
1 change: 1 addition & 0 deletions docs/software/nrf52-TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ Needed to be fully functional at least at the same level of the ESP32 boards. At

Nice ideas worth considering someday...

- use the Jumper simulator to run meshes of simulated hardware: https://docs.jumper.io/docs/install.html
- make/find a multithread safe debug logging class (include remote logging and timestamps and levels). make each log event atomic.
- turn on freertos stack size checking
- Currently we use Nordic's vendor ID, which is apparently okay: https://devzone.nordicsemi.com/f/nordic-q-a/44014/using-nordic-vid-and-pid-for-nrf52840 and I just picked a PID of 0x4403
Expand Down
2 changes: 1 addition & 1 deletion proto
Submodule proto updated from b35e7f to 5e2df6
83 changes: 83 additions & 0 deletions src/esp32/ESP32CryptoEngine.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
#include "CryptoEngine.h"
#include "configuration.h"

#include "crypto/includes.h"

#include "crypto/common.h"

// #include "esp_system.h"

#include "crypto/aes.h"
#include "crypto/aes_wrap.h"
#include "mbedtls/aes.h"

#define MAX_BLOCKSIZE 256

class ESP32CryptoEngine : public CryptoEngine
{

mbedtls_aes_context aes;

/// How many bytes in our key
uint8_t keySize = 0;

public:
ESP32CryptoEngine() { mbedtls_aes_init(&aes); }

~ESP32CryptoEngine() { mbedtls_aes_free(&aes); }

/**
* Set the key used for encrypt, decrypt.
*
* As a special case: If all bytes are zero, we assume _no encryption_ and send all data in cleartext.
*
* @param numBytes must be 16 (AES128), 32 (AES256) or 0 (no crypt)
* @param bytes a _static_ buffer that will remain valid for the life of this crypto instance (i.e. this class will cache the
* provided pointer)
*/
virtual void setKey(size_t numBytes, uint8_t *bytes)
{
keySize = numBytes;
DEBUG_MSG("Installing AES%d key!\n", numBytes * 8);
if (numBytes != 0) {
auto res = mbedtls_aes_setkey_enc(&aes, bytes, numBytes * 8);
assert(!res);
}
}

/**
* Encrypt a packet
*
* @param bytes is updated in place
*/
virtual void encrypt(uint32_t fromNode, uint64_t packetNum, size_t numBytes, uint8_t *bytes)
{
if (keySize != 0) {
uint8_t stream_block[16];
static uint8_t scratch[MAX_BLOCKSIZE];
size_t nc_off = 0;

// DEBUG_MSG("ESP32 encrypt!\n");
initNonce(fromNode, packetNum);
assert(numBytes <= MAX_BLOCKSIZE);
memcpy(scratch, bytes, numBytes);
memset(scratch + numBytes, 0,
sizeof(scratch) - numBytes); // Fill rest of buffer with zero (in case cypher looks at it)

auto res = mbedtls_aes_crypt_ctr(&aes, numBytes, &nc_off, nonce, stream_block, scratch, bytes);
assert(!res);
}
}

virtual void decrypt(uint32_t fromNode, uint64_t packetNum, size_t numBytes, uint8_t *bytes)
{
// DEBUG_MSG("ESP32 decrypt!\n");

// For CTR, the implementation is the same
encrypt(fromNode, packetNum, numBytes, bytes);
}

private:
};

CryptoEngine *crypto = new ESP32CryptoEngine();
8 changes: 3 additions & 5 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,6 @@ const char *getDeviceName()
return name;
}

static MeshRadio *radio = NULL;

static uint32_t ledBlinker()
{
static bool ledOn;
Expand Down Expand Up @@ -231,10 +229,10 @@ void setup()
#else
new SimRadio();
#endif
radio = new MeshRadio(rIf);
router.addInterface(&radio->radioIf);

if (radio && !radio->init())
router.addInterface(rIf);

if (!rIf->init())
recordCriticalError(ErrNoRadio);

// This must be _after_ service.init because we need our preferences loaded from flash to have proper timeout values
Expand Down
32 changes: 32 additions & 0 deletions src/mesh/CryptoEngine.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#include "CryptoEngine.h"
#include "configuration.h"

void CryptoEngine::setKey(size_t numBytes, uint8_t *bytes)
{
DEBUG_MSG("WARNING: Using stub crypto - all crypto is sent in plaintext!\n");
}

/**
* Encrypt a packet
*
* @param bytes is updated in place
*/
void CryptoEngine::encrypt(uint32_t fromNode, uint64_t packetNum, size_t numBytes, uint8_t *bytes)
{
DEBUG_MSG("WARNING: noop encryption!\n");
}

void CryptoEngine::decrypt(uint32_t fromNode, uint64_t packetNum, size_t numBytes, uint8_t *bytes)
{
DEBUG_MSG("WARNING: noop decryption!\n");
}

/**
* Init our 128 bit nonce for a new packet
*/
void CryptoEngine::initNonce(uint32_t fromNode, uint64_t packetNum)
{
memset(nonce, 0, sizeof(nonce));
*((uint64_t *)&nonce[0]) = packetNum;
*((uint32_t *)&nonce[8]) = fromNode;
}
50 changes: 50 additions & 0 deletions src/mesh/CryptoEngine.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#pragma once

#include <Arduino.h>

/**
* see docs/software/crypto.md for details.
*
*/

class CryptoEngine
{
protected:
/** Our per packet nonce */
uint8_t nonce[16];

public:
virtual ~CryptoEngine() {}

/**
* Set the key used for encrypt, decrypt.
*
* As a special case: If all bytes are zero, we assume _no encryption_ and send all data in cleartext.
*
* @param numBytes must be 16 (AES128), 32 (AES256) or 0 (no crypt)
* @param bytes a _static_ buffer that will remain valid for the life of this crypto instance (i.e. this class will cache the
* provided pointer)
*/
virtual void setKey(size_t numBytes, uint8_t *bytes);

/**
* Encrypt a packet
*
* @param bytes is updated in place
*/
virtual void encrypt(uint32_t fromNode, uint64_t packetNum, size_t numBytes, uint8_t *bytes);
virtual void decrypt(uint32_t fromNode, uint64_t packetNum, size_t numBytes, uint8_t *bytes);

protected:
/**
* Init our 128 bit nonce for a new packet
*
* The NONCE is constructed by concatenating (from MSB to LSB):
* a 64 bit packet number (stored in little endian order)
* a 32 bit sending node number (stored in little endian order)
* a 32 bit block counter (starts at zero)
*/
void initNonce(uint32_t fromNode, uint64_t packetNum);
};

extern CryptoEngine *crypto;
113 changes: 0 additions & 113 deletions src/mesh/MeshRadio.cpp

This file was deleted.

0 comments on commit 125035d

Please sign in to comment.