Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add CryptoInterface library #6961

Merged
merged 13 commits into from
Apr 29, 2020
555 changes: 555 additions & 0 deletions cores/esp8266/Crypto.cpp

Large diffs are not rendered by default.

848 changes: 848 additions & 0 deletions cores/esp8266/Crypto.h

Large diffs are not rendered by default.

57 changes: 57 additions & 0 deletions cores/esp8266/Esp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -522,6 +522,63 @@ bool EspClass::eraseConfig(void) {
return true;
}

uint8_t *EspClass::random(uint8_t *resultArray, const size_t outputSizeBytes) const
{
/**
* The ESP32 Technical Reference Manual v4.1 chapter 24 has the following to say about random number generation (no information found for ESP8266):
*
* "When used correctly, every 32-bit value the system reads from the RNG_DATA_REG register of the random number generator is a true random number.
* These true random numbers are generated based on the noise in the Wi-Fi/BT RF system.
* When Wi-Fi and BT are disabled, the random number generator will give out pseudo-random numbers.
*
* When Wi-Fi or BT is enabled, the random number generator is fed two bits of entropy every APB clock cycle (normally 80 MHz).
* Thus, for the maximum amount of entropy, it is advisable to read the random register at a maximum rate of 5 MHz.
* A data sample of 2 GB, read from the random number generator with Wi-Fi enabled and the random register read at 5 MHz,
* has been tested using the Dieharder Random Number Testsuite (version 3.31.1).
* The sample passed all tests."
*
* Since ESP32 is the sequal to ESP8266 it is unlikely that the ESP8266 is able to generate random numbers more quickly than 5 MHz when run at a 80 MHz frequency.
* A maximum random number frequency of 0.5 MHz is used here to leave some margin for possibly inferior components in the ESP8266.
* It should be noted that the ESP8266 has no Bluetooth functionality, so turning the WiFi off is likely to cause RANDOM_REG32 to use pseudo-random numbers.
*
* It is possible that yield() must be called on the ESP8266 to properly feed the hardware random number generator new bits, since there is only one processor core available.
* However, no feeding requirements are mentioned in the ESP32 documentation, and using yield() could possibly cause extended delays during nonce generation.
* Thus only delayMicroseconds() is used below.
*/

constexpr uint8_t cooldownMicros = 2;
static uint32_t lastCalledMicros = micros() - cooldownMicros;

uint32_t randomNumber = 0;

for(size_t byteIndex = 0; byteIndex < outputSizeBytes; ++byteIndex)
{
if(byteIndex % 4 == 0)
{
// Old random number has been used up (random number could be exactly 0, so we can't check for that)

uint32_t timeSinceLastCall = micros() - lastCalledMicros;
if(timeSinceLastCall < cooldownMicros)
delayMicroseconds(cooldownMicros - timeSinceLastCall);

randomNumber = RANDOM_REG32;
lastCalledMicros = micros();
}

resultArray[byteIndex] = randomNumber;
randomNumber >>= 8;
}

return resultArray;
}

uint32_t EspClass::random() const
{
union { uint32_t b32; uint8_t b8[4]; } result;
random(result.b8, 4);
return result.b32;
}

uint32_t EspClass::getSketchSize() {
static uint32_t result = 0;
if (result)
Expand Down
3 changes: 3 additions & 0 deletions cores/esp8266/Esp.h
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,9 @@ class EspClass {

bool eraseConfig();

uint8_t *random(uint8_t *resultArray, const size_t outputSizeBytes) const;
uint32_t random() const;

#ifndef CORE_MOCK
inline uint32_t getCycleCount() __attribute__((always_inline));
#else
Expand Down
91 changes: 91 additions & 0 deletions cores/esp8266/TypeConversion.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/*
TypeConversion functionality
Copyright (C) 2019 Anders Löfgren
License (MIT license):
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/

#include <assert.h>
#include "TypeConversion.h"

namespace esp8266
{
namespace TypeConversion
{
const char base36Chars[36] PROGMEM = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'};
const uint8_t base36CharValues[75] PROGMEM {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0, 0, // 0 to 9
10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 0, 0, 0, 0, 0, 0, // Upper case letters
10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35 // Lower case letters
};


String uint8ArrayToHexString(const uint8_t *uint8Array, const uint32_t arrayLength)
{
String hexString;
if (!hexString.reserve(2 * arrayLength)) // Each uint8_t will become two characters (00 to FF)
{
return emptyString;
}

for (uint32_t i = 0; i < arrayLength; ++i)
{
hexString += (char)pgm_read_byte(base36Chars + (uint8Array[i] >> 4));
hexString += (char)pgm_read_byte(base36Chars + uint8Array[i] % 16);
}

return hexString;
}

uint8_t *hexStringToUint8Array(const String &hexString, uint8_t *uint8Array, const uint32_t arrayLength)
{
assert(hexString.length() >= arrayLength * 2); // Each array element can hold two hexString characters

for (uint32_t i = 0; i < arrayLength; ++i)
{
uint8Array[i] = (pgm_read_byte(base36CharValues + hexString.charAt(i * 2) - '0') << 4) + pgm_read_byte(base36CharValues + hexString.charAt(i * 2 + 1) - '0');
}

return uint8Array;
}

uint8_t *uint64ToUint8ArrayBE(const uint64_t value, uint8_t *resultArray)
{
resultArray[7] = value;
resultArray[6] = value >> 8;
resultArray[5] = value >> 16;
resultArray[4] = value >> 24;
resultArray[3] = value >> 32;
resultArray[2] = value >> 40;
resultArray[1] = value >> 48;
resultArray[0] = value >> 56;

return resultArray;
}

uint64_t uint8ArrayToUint64BE(const uint8_t *inputArray)
{
uint64_t result = (uint64_t)inputArray[0] << 56 | (uint64_t)inputArray[1] << 48 | (uint64_t)inputArray[2] << 40 | (uint64_t)inputArray[3] << 32
aerlon marked this conversation as resolved.
Show resolved Hide resolved
| (uint64_t)inputArray[4] << 24 | (uint64_t)inputArray[5] << 16 | (uint64_t)inputArray[6] << 8 | (uint64_t)inputArray[7];

return result;
}
}
}
80 changes: 80 additions & 0 deletions cores/esp8266/TypeConversion.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
TypeConversion functionality
Copyright (C) 2019 Anders Löfgren
License (MIT license):
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/

#ifndef __ESP8266_TYPECONVERSION_H__
#define __ESP8266_TYPECONVERSION_H__

#include <Arduino.h>

namespace esp8266
{
namespace TypeConversion
{
extern const char base36Chars[36];

// Subtract '0' to normalize the char before lookup.
extern const uint8_t base36CharValues[75];

/**
Convert the contents of a uint8_t array to a String in HEX format. The resulting String starts from index 0 of the array.
All array elements will be padded with zeroes to ensure they are converted to 2 String characters each.
@param uint8Array The array to make into a HEX String.
@param arrayLength The size of uint8Array, in bytes.
@return Normally a String containing the HEX representation of the uint8Array. An empty String if the memory allocation for the String failed.
*/
String uint8ArrayToHexString(const uint8_t *uint8Array, const uint32_t arrayLength);

/**
Convert the contents of a String in HEX format to a uint8_t array. Index 0 of the array will represent the start of the String.
There must be 2 String characters for each array element. Use padding with zeroes where required.
@param hexString The HEX String to convert to a uint8_t array. Must contain at least 2*arrayLength characters.
@param uint8Array The array to fill with the contents of the hexString.
@param arrayLength The number of bytes to fill in uint8Array.
@return A pointer to the uint8Array.
*/
uint8_t *hexStringToUint8Array(const String &hexString, uint8_t *uint8Array, const uint32_t arrayLength);

/**
Takes a uint64_t value and stores the bits in a uint8_t array. Assumes index 0 of the array should contain MSB (big endian).
@param value The uint64_t value to convert to a uint8_t array.
@param resultArray A uint8_t array that will hold the result once the function returns. Should have a size of at least 8 bytes.
@return The resultArray.
*/
uint8_t *uint64ToUint8ArrayBE(const uint64_t value, uint8_t *resultArray);

/**
Takes a uint8_t array and converts the first 8 (lowest index) elements to a uint64_t. Assumes index 0 of the array contains MSB (big endian).
@param inputArray A uint8_t array containing the data to convert to a uint64_t. Should have a size of at least 8 bytes.
@return A uint64_t representation of the first 8 bytes of the array.
*/
uint64_t uint8ArrayToUint64BE(const uint8_t *inputArray);
}
}

#endif
96 changes: 96 additions & 0 deletions libraries/esp8266/examples/HelloCrypto/HelloCrypto.ino
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/**
This example demonstrates the usage of the ESP8266 Crypto implementation, which aims to contain easy-to-use cryptographic functions.
Crypto is currently primarily a frontend for the cryptographic library BearSSL which is used by `BearSSL::WiFiClientSecure` and `BearSSL::WiFiServerSecure` in the ESP8266 Arduino Core.
Extensive documentation can be found in the Crypto source code files and on the [BearSSL homepage](https://www.bearssl.org).
*/

#include <ESP8266WiFi.h>
#include <TypeConversion.h>
#include <Crypto.h>

namespace TypeCast = esp8266::TypeConversion;

/**
NOTE: Although we could define the strings below as normal String variables,
here we are using PROGMEM combined with the FPSTR() macro (and also just the F() macro further down in the file).
The reason is that this approach will place the strings in flash memory which will help save RAM during program execution.
Reading strings from flash will be slower than reading them from RAM,
but this will be a negligible difference when printing them to Serial.

More on F(), FPSTR() and PROGMEM:
https://github.com/esp8266/Arduino/issues/1143
https://arduino-esp8266.readthedocs.io/en/latest/PROGMEM.html
*/
constexpr char masterKey[] PROGMEM = "w86vn@rpfA O+S"; // Use 8 random characters or more

void setup() {
// Prevents the flash memory from being worn out, see: https://github.com/esp8266/Arduino/issues/1054 .
// This will however delay node WiFi start-up by about 700 ms. The delay is 900 ms if we otherwise would have stored the WiFi network we want to connect to.
WiFi.persistent(false);

Serial.begin(115200);

Serial.println();
Serial.println();
}

void loop() {
// This serves only to demonstrate the library use. See the header file for a full list of functions.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Up here can do something like:

using namespace experimental::crypto;

Then further down you can use directly, e.g.:

...
getNonceGenerator()(...);
...
SHA256::hash(...);
...
SHA256::hmac(...);

etc. This eases use and reduces the long fully qualified names. It also allows easy remapping of namespaces, because you just change the namespace import at the top instead of having to change each fully qualified name through the code.


String exampleData = F("Hello Crypto World!");
Serial.println(String(F("This is our example data: ")) + exampleData);

uint8_t resultArray[esp8266::experimental::Crypto::SHA256::NATURAL_LENGTH] { 0 };
uint8_t derivedKey[esp8266::experimental::Crypto::ENCRYPTION_KEY_LENGTH] { 0 };

static uint32_t encryptionCounter = 0;


// Generate the salt to use for HKDF
uint8_t hkdfSalt[16] { 0 };
esp8266::experimental::Crypto::getNonceGenerator()(hkdfSalt, sizeof hkdfSalt);

// Generate the key to use for HMAC and encryption
esp8266::experimental::Crypto::HKDF hkdfInstance(FPSTR(masterKey), (sizeof masterKey) - 1, hkdfSalt, sizeof hkdfSalt); // (sizeof masterKey) - 1 removes the terminating null value of the c-string
hkdfInstance.produce(derivedKey, sizeof derivedKey);

// Hash
esp8266::experimental::Crypto::SHA256::hash(exampleData.c_str(), exampleData.length(), resultArray);
Serial.println(String(F("\nThis is the SHA256 hash of our example data, in HEX format:\n")) + TypeCast::uint8ArrayToHexString(resultArray, sizeof resultArray));
Serial.println(String(F("This is the SHA256 hash of our example data, in HEX format, using String output:\n")) + esp8266::experimental::Crypto::SHA256::hash(exampleData));


// HMAC
// Note that HMAC output length is limited
esp8266::experimental::Crypto::SHA256::hmac(exampleData.c_str(), exampleData.length(), derivedKey, sizeof derivedKey, resultArray, sizeof resultArray);
Serial.println(String(F("\nThis is the SHA256 HMAC of our example data, in HEX format:\n")) + TypeCast::uint8ArrayToHexString(resultArray, sizeof resultArray));
Serial.println(String(F("This is the SHA256 HMAC of our example data, in HEX format, using String output:\n")) + esp8266::experimental::Crypto::SHA256::hmac(exampleData, derivedKey, sizeof derivedKey, esp8266::experimental::Crypto::SHA256::NATURAL_LENGTH));


// Authenticated Encryption with Associated Data (AEAD)
String dataToEncrypt = F("This data is not encrypted.");
uint8_t resultingNonce[12] { 0 }; // The nonce is always 12 bytes
uint8_t resultingTag[16] { 0 }; // The tag is always 16 bytes

Serial.println(String(F("\nThis is the data to encrypt: ")) + dataToEncrypt);

// Note that the key must be ENCRYPTION_KEY_LENGTH long.
esp8266::experimental::Crypto::ChaCha20Poly1305::encrypt(dataToEncrypt.begin(), dataToEncrypt.length(), derivedKey, &encryptionCounter, sizeof encryptionCounter, resultingNonce, resultingTag);
Serial.println(String(F("Encrypted data: ")) + dataToEncrypt);

bool decryptionSucceeded = esp8266::experimental::Crypto::ChaCha20Poly1305::decrypt(dataToEncrypt.begin(), dataToEncrypt.length(), derivedKey, &encryptionCounter, sizeof encryptionCounter, resultingNonce, resultingTag);
encryptionCounter++;

if (decryptionSucceeded) {
Serial.print(F("Decryption succeeded. Result: "));
} else {
Serial.print(F("Decryption failed. Result: "));
}

Serial.println(dataToEncrypt);


Serial.println(F("\n##########################################################################################################\n"));

delay(10000);
}
31 changes: 31 additions & 0 deletions libraries/esp8266/keywords.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,18 @@

ESP KEYWORD1

Crypto KEYWORD1
nonceGeneratorType KEYWORD1
MD5 KEYWORD1
SHA1 KEYWORD1
SHA224 KEYWORD1
SHA256 KEYWORD1
SHA384 KEYWORD1
SHA512 KEYWORD1
MD5SHA1 KEYWORD1
HKDF KEYWORD1
ChaCha20Poly1305 KEYWORD1

#######################################
# Methods and Functions (KEYWORD2)
#######################################
Expand Down Expand Up @@ -60,6 +72,21 @@ getResetInfo KEYWORD2
getResetInfoPtr KEYWORD2
eraseConfig KEYWORD2
getCycleCount KEYWORD2
random->KEYWORD2

setCtMinDataLength KEYWORD2
getCtMinDataLength KEYWORD2
setCtMaxDataLength KEYWORD2
getCtMaxDataLength KEYWORD2
setNonceGenerator KEYWORD2
getNonceGenerator KEYWORD2
hash KEYWORD2
hmac KEYWORD2
hmacCT KEYWORD2
init KEYWORD2
produce KEYWORD2
encrypt KEYWORD2
decrypt KEYWORD2

#######################################
# Constants (LITERAL1)
Expand All @@ -79,6 +106,10 @@ WAKE_RF_DISABLED LITERAL1
ADC_VCC LITERAL1
ADC_TOUT LITERAL1

NATURAL_LENGTH LITERAL1
ENCRYPTION_KEY_LENGTH LITERAL1
CT_MAX_DIFF LITERAL1

#######################################
# namespace esp8266
#######################################
Expand Down
Loading