From f9e3a229119c28e10dedb6beaca58f43602097c8 Mon Sep 17 00:00:00 2001 From: attermann Date: Sun, 8 Oct 2023 23:59:43 -0600 Subject: [PATCH] WIP update Implemented AES. Implemented AES-CBC. Implemented HKDF. Implemented HMAC. Implemented PKCS7. Implemented Fernet. Added extensive logging and successfully tested ability to encrypt and decrypt a packet. --- platformio.ini | 4 +- src/Bytes.cpp | 42 ++++- src/Bytes.h | 46 ++++-- src/Cryptography/AES.h | 50 ++++++ src/Cryptography/CBC.cpp | 171 ++++++++++++++++++++ src/Cryptography/CBC.h | 66 ++++++++ src/Cryptography/Fernet.cpp | 130 +++++++++++++++- src/Cryptography/Fernet.h | 25 ++- src/Cryptography/HKDF.cpp | 28 ++++ src/Cryptography/HKDF.h | 9 ++ src/Cryptography/HMAC.h | 108 +++++++++++++ src/Cryptography/Hashes.cpp | 1 - src/Cryptography/PKCS7.h | 61 ++++++++ src/Cryptography/Random.h | 16 ++ src/Cryptography/X25519.h | 24 ++- src/Destination.cpp | 132 +++++++++++----- src/Destination.h | 73 ++++++++- src/Identity.cpp | 195 ++++++++++------------- src/Identity.h | 55 ++++++- src/Log.cpp | 1 + src/Packet.cpp | 302 +++++++++++++++++++++++++++--------- src/Packet.h | 31 +++- src/Test/Test.cpp | 4 +- src/Test/Test.h | 2 + src/Test/TestBytes.cpp | 117 ++++++++------ src/Test/TestCrypto.cpp | 105 +++++++++++++ src/Test/TestReference.cpp | 2 + src/main.cpp | 15 +- 28 files changed, 1487 insertions(+), 328 deletions(-) create mode 100644 src/Cryptography/AES.h create mode 100644 src/Cryptography/CBC.cpp create mode 100644 src/Cryptography/CBC.h create mode 100644 src/Cryptography/HKDF.cpp create mode 100644 src/Cryptography/HKDF.h create mode 100644 src/Cryptography/HMAC.h create mode 100644 src/Cryptography/PKCS7.h create mode 100644 src/Cryptography/Random.h diff --git a/platformio.ini b/platformio.ini index 18fc182..5c932f9 100644 --- a/platformio.ini +++ b/platformio.ini @@ -28,7 +28,7 @@ build_flags = -Isrc -DNATIVE lib_deps = -; operatorfoundation/Crypto@^0.4.0 +; rweather/Crypto@^0.4.0 lib_compat_mode = off [env:ttgo-t-beam] @@ -43,4 +43,4 @@ build_flags = -Wno-format -Isrc lib_deps = - operatorfoundation/Crypto@^0.4.0 + rweather/Crypto@^0.4.0 diff --git a/src/Bytes.cpp b/src/Bytes.cpp index 1a0d818..3be5399 100644 --- a/src/Bytes.cpp +++ b/src/Bytes.cpp @@ -34,15 +34,19 @@ int8_t Bytes::compare(const Bytes &bytes) const { else if (*_data < *(bytes._data)) { return -1; } - else { + else if (*_data > *(bytes._data)) { return 1; } + else { + return 0; + } } void Bytes::assignHex(const char* hex) { // if assignment is empty then clear data and don't bother creating new if (hex == nullptr || hex[0] == 0) { _data = nullptr; + _owner = true; return; } newData(); @@ -66,8 +70,21 @@ void Bytes::appendHex(const char* hex) { } } -char const hex_upper_chars[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; -char const hex_lower_chars[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; +const char hex_upper_chars[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; +const char hex_lower_chars[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; + +std::string RNS::hexFromByte(uint8_t byte, bool upper /*= true*/) { + std::string hex; + if (upper) { + hex += hex_upper_chars[ (byte & 0xF0) >> 4]; + hex += hex_upper_chars[ (byte & 0x0F) >> 0]; + } + else { + hex += hex_lower_chars[ (byte & 0xF0) >> 4]; + hex += hex_lower_chars[ (byte & 0x0F) >> 0]; + } + return hex; +} std::string Bytes::toHex(bool upper /*= true*/) const { if (!_data) { @@ -87,12 +104,21 @@ std::string Bytes::toHex(bool upper /*= true*/) const { return hex; } -Bytes Bytes::mid(size_t pos, size_t len) const { - if (!_data || pos >= size()) { +// mid +Bytes Bytes::mid(size_t beginpos, size_t len) const { + if (!_data || beginpos >= size()) { return NONE; } - if ((pos + len) >= size()) { - len = (size() - pos); + if ((beginpos + len) >= size()) { + len = (size() - beginpos); } - return {data() + pos, len}; + return {data() + beginpos, len}; +} + +// to end +Bytes Bytes::mid(size_t beginpos) const { + if (!_data || beginpos >= size()) { + return NONE; + } + return {data() + beginpos, size() - beginpos}; } diff --git a/src/Bytes.h b/src/Bytes.h index 73ecdf2..eb5bb7b 100644 --- a/src/Bytes.h +++ b/src/Bytes.h @@ -104,6 +104,11 @@ namespace RNS { void ownData(); public: + inline void clear() { + _data = nullptr; + _owner = true; + } + inline void assign(const Bytes& bytes) { #ifdef COW _data = bytes.shareData(); @@ -112,6 +117,7 @@ namespace RNS { // if assignment is empty then clear data and don't bother creating new if (bytes.size() <= 0) { _data = nullptr; + _owner = true; return; } newData(); @@ -122,6 +128,7 @@ namespace RNS { // if assignment is empty then clear data and don't bother creating new if (chunk == nullptr || size <= 0) { _data = nullptr; + _owner = true; return; } newData(); @@ -131,6 +138,7 @@ namespace RNS { // if assignment is empty then clear data and don't bother creating new if (string == nullptr || string[0] == 0) { _data = nullptr; + _owner = true; return; } newData(); @@ -170,6 +178,20 @@ namespace RNS { } void appendHex(const char* hex); + inline void resize(size_t newsize) { + // if size is unchanged then do nothing + if (newsize == size()) { + return; + } + ownData(); + _data->resize(newsize); + } + + inline uint8_t *writable(size_t size) { + newData(size); + return _data->data(); + } + public: int8_t compare(const Bytes &bytes) const; inline size_t size() const { if (!_data) return 0; return _data->size(); } @@ -179,15 +201,11 @@ namespace RNS { inline std::string toString() const { if (!_data) return ""; return {(const char*)data(), size()}; } std::string toHex(bool upper = true) const; - Bytes mid(size_t pos, size_t len) const; + Bytes mid(size_t beginpos, size_t len) const; + Bytes mid(size_t beginpos) const; inline Bytes left(size_t len) const { if (!_data) return NONE; if (len > size()) len = size(); return {data(), len}; } inline Bytes right(size_t len) const { if (!_data) return NONE; if (len > size()) len = size(); return {data() + (size() - len), len}; } - inline uint8_t *writable(size_t size) { - newData(size); - return _data->data(); - } - private: SharedData _data; mutable bool _owner = true; @@ -195,14 +213,16 @@ namespace RNS { }; // following array function doesn't work without size since it's past as a pointer to the array sizeof() is of the pointer - //static inline Bytes bytesFromArray(const uint8_t arr[]) { return Bytes(arr, sizeof(arr)); } - //static inline Bytes bytesFromChunk(const uint8_t *ptr, size_t len) { return Bytes(ptr, len); } - static inline Bytes bytesFromChunk(const uint8_t *ptr, size_t len) { return {ptr, len}; } - //static inline Bytes bytesFromString(const char *str) { return Bytes((uint8_t*)str, strlen(str)); } - static inline Bytes bytesFromString(const char *str) { return {(uint8_t*)str, strlen(str)}; } - //zstatic inline Bytes bytesFromInt(const int) { return {(uint8_t*)str, strlen(str)}; } + //inline Bytes bytesFromArray(const uint8_t arr[]) { return Bytes(arr, sizeof(arr)); } + //inline Bytes bytesFromChunk(const uint8_t *ptr, size_t len) { return Bytes(ptr, len); } + inline Bytes bytesFromChunk(const uint8_t *ptr, size_t len) { return {ptr, len}; } + //inline Bytes bytesFromString(const char *str) { return Bytes((uint8_t*)str, strlen(str)); } + inline Bytes bytesFromString(const char *str) { return {(uint8_t*)str, strlen(str)}; } + //zinline Bytes bytesFromInt(const int) { return {(uint8_t*)str, strlen(str)}; } - static inline std::string stringFromBytes(const Bytes& bytes) { return {(const char*)bytes.data(), bytes.size()}; } + inline std::string stringFromBytes(const Bytes& bytes) { return bytes.toString(); } + inline std::string hexFromBytes(const Bytes& bytes) { return bytes.toHex(); } + std::string hexFromByte(uint8_t byte, bool upper = true); } diff --git a/src/Cryptography/AES.h b/src/Cryptography/AES.h new file mode 100644 index 0000000..c245e9d --- /dev/null +++ b/src/Cryptography/AES.h @@ -0,0 +1,50 @@ +#pragma once + +#include "CBC.h" + +#include "../Bytes.h" + +#include + +namespace RNS { namespace Cryptography { + + class AES_128_CBC { + + public: + static inline Bytes encrypt(const Bytes &plaintext, const Bytes &key, const Bytes &iv) { + CBC cbc; + cbc.setKey(key.data(), key.size()); + cbc.setIV(iv.data(), iv.size()); + Bytes ciphertext; + cbc.encrypt(ciphertext.writable(plaintext.size()), plaintext.data(), plaintext.size()); + return ciphertext; + } + + static inline Bytes decrypt(const Bytes &ciphertext, const Bytes &key, const Bytes &iv) { + CBC cbc; + cbc.setKey(key.data(), key.size()); + cbc.setIV(iv.data(), iv.size()); + Bytes plaintext; + cbc.decrypt(plaintext.writable(ciphertext.size()), ciphertext.data(), ciphertext.size()); + return plaintext; + } + + // EXPERIMENTAL - overwrites passed buffer + static inline void inplace_encrypt(Bytes &plaintext, const Bytes &key, const Bytes &iv) { + CBC cbc; + cbc.setKey(key.data(), key.size()); + cbc.setIV(iv.data(), iv.size()); + cbc.encrypt((uint8_t*)plaintext.data(), plaintext.data(), plaintext.size()); + } + + // EXPERIMENTAL - overwrites passed buffer + static inline void inplace_decrypt(Bytes &ciphertext, const Bytes &key, const Bytes &iv) { + CBC cbc; + cbc.setKey(key.data(), key.size()); + cbc.setIV(iv.data(), iv.size()); + cbc.decrypt((uint8_t*)ciphertext.data(), ciphertext.data(), ciphertext.size()); + } + + }; + +} } diff --git a/src/Cryptography/CBC.cpp b/src/Cryptography/CBC.cpp new file mode 100644 index 0000000..8419e0e --- /dev/null +++ b/src/Cryptography/CBC.cpp @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2015 Southern Storm Software, Pty Ltd. + * + * 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 "CBC.h" +#include "Crypto.h" +#include + +/** + * \class CBCCommon CBC.h + * \brief Concrete base class to assist with implementing CBC for + * 128-bit block ciphers. + * + * Reference: http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation + * + * \sa CBC + */ + +/** + * \brief Constructs a new cipher in CBC mode. + * + * This constructor should be followed by a call to setBlockCipher(). + */ +CBCCommon::CBCCommon() + : blockCipher(0) + , posn(16) +{ +} + +/** + * \brief Destroys this cipher object after clearing sensitive information. + */ +CBCCommon::~CBCCommon() +{ + clean(iv); + clean(temp); +} + +size_t CBCCommon::keySize() const +{ + return blockCipher->keySize(); +} + +size_t CBCCommon::ivSize() const +{ + return 16; +} + +bool CBCCommon::setKey(const uint8_t *key, size_t len) +{ + // Verify the cipher's block size, just in case. + if (blockCipher->blockSize() != 16) + return false; + + // Set the key on the underlying block cipher. + return blockCipher->setKey(key, len); +} + +bool CBCCommon::setIV(const uint8_t *iv, size_t len) +{ + if (len != 16) + return false; + memcpy(this->iv, iv, 16); + posn = 16; + return true; +} + +void CBCCommon::encrypt(uint8_t *output, const uint8_t *input, size_t len) +{ + uint8_t posn; + while (len >= 16) { + for (posn = 0; posn < 16; ++posn) + iv[posn] ^= *input++; + blockCipher->encryptBlock(iv, iv); + for (posn = 0; posn < 16; ++posn) + *output++ = iv[posn]; + len -= 16; + } +} + +void CBCCommon::decrypt(uint8_t *output, const uint8_t *input, size_t len) +{ + uint8_t posn; + while (len >= 16) { + blockCipher->decryptBlock(temp, input); + for (posn = 0; posn < 16; ++posn) { + uint8_t in = *input++; + *output++ = temp[posn] ^ iv[posn]; + iv[posn] = in; + } + len -= 16; + } +} + +void CBCCommon::clear() +{ + blockCipher->clear(); + clean(iv); + clean(temp); + posn = 16; +} + +/** + * \fn void CBCCommon::setBlockCipher(BlockCipher *cipher) + * \brief Sets the block cipher to use for this CBC object. + * + * \param cipher The block cipher to use to implement CBC mode, + * which must have a block size of 16 bytes (128 bits). + */ + +/** + * \class CBC CBC.h + * \brief Implementation of the Cipher Block Chaining (CBC) mode for + * 128-bit block ciphers. + * + * The template parameter T must be a concrete subclass of BlockCipher + * indicating the specific block cipher to use. T must have a block size + * of 16 bytes (128 bits). + * + * For example, the following creates a CBC object using AES192 as the + * underlying cipher: + * + * \code + * CBC cbc; + * cbc.setKey(key, 24); + * cbc.setIV(iv, 16); + * cbc.encrypt(output, input, len); + * \endcode + * + * Decryption is similar: + * + * \code + * CBC cbc; + * cbc.setKey(key, 24); + * cbc.setIV(iv, 16); + * cbc.decrypt(output, input, len); + * \endcode + * + * The size of the ciphertext will always be the same as the size of + * the plaintext. Also, the length of the plaintext/ciphertext must be a + * multiple of 16. Extra bytes are ignored and not encrypted. The caller + * is responsible for padding the underlying data to a multiple of 16 + * using an appropriate padding scheme for the application. + * + * Reference: http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation + * + * \sa CTR, CFB, OFB + */ + +/** + * \fn CBC::CBC() + * \brief Constructs a new CBC object for the block cipher T. + */ \ No newline at end of file diff --git a/src/Cryptography/CBC.h b/src/Cryptography/CBC.h new file mode 100644 index 0000000..884568e --- /dev/null +++ b/src/Cryptography/CBC.h @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2015 Southern Storm Software, Pty Ltd. + * + * 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 CRYPTO_CBC_h +#define CRYPTO_CBC_h + +#include "Cipher.h" +#include "BlockCipher.h" + +class CBCCommon : public Cipher +{ +public: + virtual ~CBCCommon(); + + size_t keySize() const; + size_t ivSize() const; + + bool setKey(const uint8_t *key, size_t len); + bool setIV(const uint8_t *iv, size_t len); + + void encrypt(uint8_t *output, const uint8_t *input, size_t len); + void decrypt(uint8_t *output, const uint8_t *input, size_t len); + + void clear(); + +protected: + CBCCommon(); + void setBlockCipher(BlockCipher *cipher) { blockCipher = cipher; } + +private: + BlockCipher *blockCipher; + uint8_t iv[16]; + uint8_t temp[16]; + uint8_t posn; +}; + +template +class CBC : public CBCCommon +{ +public: + CBC() { setBlockCipher(&cipher); } + +private: + T cipher; +}; + +#endif \ No newline at end of file diff --git a/src/Cryptography/Fernet.cpp b/src/Cryptography/Fernet.cpp index 6c91056..1f64c85 100644 --- a/src/Cryptography/Fernet.cpp +++ b/src/Cryptography/Fernet.cpp @@ -1,14 +1,138 @@ #include "Fernet.h" +#include "HMAC.h" +#include "PKCS7.h" +#include "AES.h" #include "../Log.h" +#include +#include + +using namespace RNS; using namespace RNS::Cryptography; -Fernet::Fernet() { - log("Fernet object created", LOG_EXTREME); +Fernet::Fernet(const Bytes &key) { + + if (!key) { + throw std::invalid_argument("Fernet key cannot be None"); + } + + if (key.size() != 32) { + throw std::invalid_argument("Fernet key must be 32 bytes, not " + std::to_string(key.size())); + } + + // Python array indexing + // [8:16] + // pos 8 to pos 16 + // mid(8, 8) + // [:16] + // start to pos 16 (same as first 16) + // left(16) + // [16:] + // pos 16 to end + // mid(16) + // [-16:] + // last 16 + // right(16) + // [:-16] + // all except the last 16 + // left(size()-16) + // mid(0, size()-16) + // [-1] + // last element + // [-2] + // seocnd to last element + + //self._signing_key = key[:16] + _signing_key = key.left(16); + //self._encryption_key = key[16:] + _encryption_key = key.mid(16); + + extreme("Fernet object created"); } Fernet::~Fernet() { - log("Fernet object destroyed", LOG_EXTREME); + extreme("Fernet object destroyed"); } +bool Fernet::verify_hmac(const Bytes &token) { + + if (token.size() <= 32) { + throw std::invalid_argument("Cannot verify HMAC on token of only " + std::to_string(token.size()) + " bytes"); + } + + //received_hmac = token[-32:] + Bytes received_hmac = token.right(32); + debug("Fernet::verify_hmac: received_hmac: " + received_hmac.toHex()); + //expected_hmac = HMAC.new(self._signing_key, token[:-32]).digest() + Bytes expected_hmac = HMAC::generate(_signing_key, token.left(token.size()-32))->digest(); + debug("Fernet::verify_hmac: expected_hmac: " + expected_hmac.toHex()); + + return (received_hmac == expected_hmac); +} + +Bytes Fernet::encrypt(const Bytes &data) { + + debug("Fernet::encrypt: plaintext length: " + std::to_string(data.size())); + Bytes iv = random(16); + //time_t current_time = time(nullptr); + extreme("Fernet::encrypt: iv: " + iv.toHex()); + + extreme("Fernet::encrypt: plaintext: " + data.toHex()); + Bytes ciphertext = AES_128_CBC::encrypt( + PKCS7::pad(data), + _encryption_key, + iv + ); + debug("Fernet::encrypt: padded ciphertext length: " + std::to_string(ciphertext.size())); + extreme("Fernet::encrypt: ciphertext: " + ciphertext.toHex()); + + Bytes signed_parts = iv + ciphertext; + + //return signed_parts + HMAC::generate(_signing_key, signed_parts)->digest(); + Bytes sig(HMAC::generate(_signing_key, signed_parts)->digest()); + extreme("Fernet::encrypt: sig: " + sig.toHex()); + Bytes token(signed_parts + sig); + debug("Fernet::encrypt: token length: " + std::to_string(token.size())); + return token; +} + + +Bytes Fernet::decrypt(const Bytes &token) { + + debug("Fernet::decrypt: token length: " + std::to_string(token.size())); + if (token.size() < 48) { + throw std::invalid_argument("Cannot decrypt token of only " + std::to_string(token.size()) + " bytes"); + } + + if (!verify_hmac(token)) { + throw std::invalid_argument("Fernet token HMAC was invalid"); + } + + //iv = token[:16] + Bytes iv = token.left(16); + extreme("Fernet::decrypt: iv: " + iv.toHex()); + + //ciphertext = token[16:-32] + Bytes ciphertext = token.mid(16, token.size()-48); + extreme("Fernet::decrypt: ciphertext: " + ciphertext.toHex()); + + try { + Bytes plaintext = PKCS7::unpad( + AES_128_CBC::decrypt( + ciphertext, + _encryption_key, + iv + ) + ); + debug("Fernet::encrypt: unpadded plaintext length: " + std::to_string(plaintext.size())); + extreme("Fernet::decrypt: plaintext: " + plaintext.toHex()); + + debug("Fernet::decrypt: plaintext length: " + std::to_string(plaintext.size())); + return plaintext; + } + catch (std::exception &e) { + warning("Could not decrypt Fernet token"); + throw std::runtime_error("Could not decrypt Fernet token"); + } +} \ No newline at end of file diff --git a/src/Cryptography/Fernet.h b/src/Cryptography/Fernet.h index 9ec869b..a8a8a1d 100644 --- a/src/Cryptography/Fernet.h +++ b/src/Cryptography/Fernet.h @@ -1,18 +1,41 @@ #pragma once +#include "Random.h" +#include "../Bytes.h" + #include namespace RNS { namespace Cryptography { + /* + This class provides a slightly modified implementation of the Fernet spec + found at: https://github.com/fernet/spec/blob/master/Spec.md + + According to the spec, a Fernet token includes a one byte VERSION and + eight byte TIMESTAMP field at the start of each token. These fields are + not relevant to Reticulum. They are therefore stripped from this + implementation, since they incur overhead and leak initiator metadata. + */ class Fernet { public: static const uint8_t FERNET_OVERHEAD = 48; // Bytes public: - Fernet(); + static inline Bytes generate_key() { return random(32); } + + public: + Fernet(const Bytes &key); ~Fernet(); + public: + bool verify_hmac(const Bytes &token); + Bytes encrypt(const Bytes &data); + Bytes decrypt(const Bytes &token); + + private: + Bytes _signing_key; + Bytes _encryption_key; }; } } diff --git a/src/Cryptography/HKDF.cpp b/src/Cryptography/HKDF.cpp new file mode 100644 index 0000000..eaba240 --- /dev/null +++ b/src/Cryptography/HKDF.cpp @@ -0,0 +1,28 @@ +#include "HKDF.h" + +#include +#include + +using namespace RNS; + +Bytes RNS::Cryptography::hkdf(size_t length, const Bytes &derive_from, const Bytes &salt /*= Bytes::NONE*/, const Bytes &context /*= Bytes::NONE*/) { + + if (length <= 0) { + throw std::invalid_argument("Invalid output key length"); + } + + if (!derive_from) { + throw std::invalid_argument("Cannot derive key from empty input material"); + } + + HKDF hkdf; + if (salt) { + hkdf.setKey(derive_from.data(), derive_from.size(), salt.data(), salt.size()); + } + else { + hkdf.setKey(derive_from.data(), derive_from.size()); + } + Bytes derived; + hkdf.extract(derived.writable(length), length); + return derived; +} diff --git a/src/Cryptography/HKDF.h b/src/Cryptography/HKDF.h new file mode 100644 index 0000000..7510d31 --- /dev/null +++ b/src/Cryptography/HKDF.h @@ -0,0 +1,9 @@ +#pragma once + +#include "../Bytes.h" + +namespace RNS { namespace Cryptography { + + Bytes hkdf(size_t length, const Bytes &derive_from, const Bytes &salt = Bytes::NONE, const Bytes &context = Bytes::NONE); + +} } diff --git a/src/Cryptography/HMAC.h b/src/Cryptography/HMAC.h new file mode 100644 index 0000000..e59acd0 --- /dev/null +++ b/src/Cryptography/HMAC.h @@ -0,0 +1,108 @@ +#pragma once + +#include "../Bytes.h" + +#include +#include +#include +#include +#include + +namespace RNS { namespace Cryptography { + + class HMAC { + + public: + enum Digest { + DIGEST_NONE, + DIGEST_SHA256, + DIGEST_SHA512, + }; + + using Ptr = std::shared_ptr; + + public: + /* + Create a new HMAC object. + key: bytes or buffer, key for the keyed hash object. + msg: bytes or buffer, Initial input for the hash or None. + digest: The underlying hash algorithm to use + */ + HMAC(const Bytes &key, const Bytes &msg = Bytes::NONE, Digest digest = DIGEST_SHA256) { + + if (digest == DIGEST_NONE) { + throw std::invalid_argument("Cannot derive key from empty input material"); + } + + switch (digest) { + case DIGEST_SHA256: + _hash = std::unique_ptr(new SHA256()); + break; + case DIGEST_SHA512: + _hash = std::unique_ptr(new SHA512()); + break; + default: + throw std::invalid_argument("Unknown ior unsuppored digest"); + } + + _key = key; + _hash->resetHMAC(key.data(), key.size()); + + if (msg) { + update(msg); + } + } + + /* + Feed data from msg into this hashing object. + */ + void update(const Bytes &msg) { + assert(_hash); + _hash->update(msg.data(), msg.size()); + } + + /* + Return the hash value of this hashing object. + This returns the hmac value as bytes. The object is + not altered in any way by this function; you can continue + updating the object after calling this function. + */ + Bytes digest() { + assert(_hash); + Bytes result; + _hash->finalizeHMAC(_key.data(), _key.size(), result.writable(_hash->hashSize()), _hash->hashSize()); + return result; + } + + /* + Create a new hashing object and return it. + key: bytes or buffer, The starting key for the hash. + msg: bytes or buffer, Initial input for the hash, or None. + digest: The underlying hash algorithm to use. + You can now feed arbitrary bytes into the object using its update() + method, and can ask for the hash value at any time by calling its digest() + method. + */ + static inline Ptr generate(const Bytes &key, const Bytes &msg = Bytes::NONE, Digest digest = DIGEST_SHA256) { + return Ptr(new HMAC(key, msg, digest)); + } + + private: + Bytes _key; + std::unique_ptr _hash; + + }; + + /* + Fast inline implementation of HMAC. + key: bytes or buffer, The key for the keyed hash object. + msg: bytes or buffer, Input message. + digest: The underlying hash algorithm to use. + */ + inline Bytes digest(const Bytes &key, const Bytes &msg, HMAC::Digest digest = HMAC::DIGEST_SHA256) { + HMAC hmac(key, msg, digest); + hmac.update(msg); + return hmac.digest(); + } + +} } diff --git a/src/Cryptography/Hashes.cpp b/src/Cryptography/Hashes.cpp index 5229989..e656fb6 100644 --- a/src/Cryptography/Hashes.cpp +++ b/src/Cryptography/Hashes.cpp @@ -1,6 +1,5 @@ #include "Hashes.h" -#include "../Log.h" #include "../Bytes.h" #include diff --git a/src/Cryptography/PKCS7.h b/src/Cryptography/PKCS7.h new file mode 100644 index 0000000..a03f7ee --- /dev/null +++ b/src/Cryptography/PKCS7.h @@ -0,0 +1,61 @@ +#pragma once + +#include "../Bytes.h" +//#include "../Log.h" + +#include + +namespace RNS { namespace Cryptography { + + class PKCS7 { + + public: + + static const size_t BLOCKSIZE = 16; + + static inline Bytes pad(const Bytes &data, size_t bs = BLOCKSIZE) { + Bytes padded(data); + inplace_pad(padded, bs); + return padded; + } + + static inline Bytes unpad(const Bytes &data, size_t bs = BLOCKSIZE) { + Bytes unpadded(data); + inplace_unpad(unpadded, bs); + return unpadded; + } + + // updates passed buffer + static inline void inplace_pad(Bytes &data, size_t bs = BLOCKSIZE) { + size_t len = data.size(); + //debug("PKCS7::pad: len: " + std::to_string(len)); + size_t padlen = bs - (len % bs); + //debug("PKCS7::pad: pad len: " + std::to_string(padlen)); + // create byte array of size n? + //v = bytes([padlen]) + uint8_t pad[padlen] = {0}; + pad[padlen-1] = (uint8_t)padlen; + //return data+v*padlen + data.append(pad, padlen); + //debug("PKCS7::pad: data size: " + std::to_string(data.size())); + } + + // updates passed buffer + static inline void inplace_unpad(Bytes &data, size_t bs = BLOCKSIZE) { + size_t len = data.size(); + //debug("PKCS7::unpad: len: " + std::to_string(len)); + // last byte is pad length + //pad = data[-1] + size_t padlen = (size_t)data.data()[data.size()-1]; + //debug("PKCS7::unpad: pad len: " + std::to_string(padlen)); + if (padlen > bs) { + throw std::runtime_error("Cannot unpad, invalid padding length of " + std::to_string(padlen) + " bytes"); + } + //return data[:len-padlen] + data.resize(len - padlen); + //debug("PKCS7::unpad: data size: " + std::to_string(data.size())); + } + + }; + +} } diff --git a/src/Cryptography/Random.h b/src/Cryptography/Random.h new file mode 100644 index 0000000..1d64fc7 --- /dev/null +++ b/src/Cryptography/Random.h @@ -0,0 +1,16 @@ +#pragma once + +#include "../Bytes.h" + +#include +#include + +namespace RNS { namespace Cryptography { + + inline Bytes random(size_t length) { + Bytes rand; + RNG.rand(rand.writable(length), length); + return rand; + } + +} } diff --git a/src/Cryptography/X25519.h b/src/Cryptography/X25519.h index cdd558d..92cebf1 100644 --- a/src/Cryptography/X25519.h +++ b/src/Cryptography/X25519.h @@ -1,10 +1,12 @@ #pragma once #include "Bytes.h" +#include "Log.h" #include #include +#include #include namespace RNS { namespace Cryptography { @@ -167,11 +169,29 @@ namespace RNS { namespace Cryptography { } */ inline Bytes exchange(const Bytes &peer_public_key) { - Bytes sharedKey(peer_public_key); - Curve25519::dh2(sharedKey.writable(32), _privateKey.writable(32)); + debug("X25519PublicKey::exchange: public key: " + _publicKey.toHex()); + debug("X25519PublicKey::exchange: peer public key: " + peer_public_key.toHex()); + debug("X25519PublicKey::exchange: pre private key: " + _privateKey.toHex()); + Bytes sharedKey; + if (!Curve25519::eval(sharedKey.writable(32), _privateKey.data(), peer_public_key.data())) { + throw std::runtime_error("Peer key is invalid"); + } + debug("X25519PublicKey::exchange: shared key: " + sharedKey.toHex()); + debug("X25519PublicKey::exchange: post private key: " + _privateKey.toHex()); return sharedKey; } + inline bool verify(const Bytes &peer_public_key) { + debug("X25519PublicKey::exchange: public key: " + _publicKey.toHex()); + debug("X25519PublicKey::exchange: peer public key: " + peer_public_key.toHex()); + debug("X25519PublicKey::exchange: pre private key: " + _privateKey.toHex()); + Bytes sharedKey(peer_public_key); + bool success = Curve25519::dh2(sharedKey.writable(32), _privateKey.writable(32)); + debug("X25519PublicKey::exchange: shared key: " + sharedKey.toHex()); + debug("X25519PublicKey::exchange: post private key: " + _privateKey.toHex()); + return success; + } + private: //Bytes _a; Bytes _privateKey; diff --git a/src/Destination.cpp b/src/Destination.cpp index 06c7f78..ecbdf8d 100644 --- a/src/Destination.cpp +++ b/src/Destination.cpp @@ -7,7 +7,6 @@ #include #include -#include #include using namespace RNS; @@ -42,13 +41,12 @@ Destination::Destination(const Identity &identity, const directions direction, c // Generate the destination address hash debug("Destination::Destination: creating hash..."); _object->_hash = hash(_object->_identity, app_name, fullaspects.c_str()); - debug("Destination::Destination: hash: " + _object->_hash.toHex()); + _object->_hexhash = _object->_hash.toHex(); + debug("Destination::Destination: hash: " + _object->_hash.toHex()); // CBA TEST CRASH debug("Destination::Destination: creating name hash..."); _object->_name_hash = Identity::truncated_hash(expand_name(Identity::NONE, app_name, fullaspects.c_str())); debug("Destination::Destination: name hash: " + _object->_name_hash.toHex()); - _object->_hexhash = _object->_hash.toHex(); - debug("Destination::Destination: hexhash: " + _object->_hexhash); debug("Destination::Destination: calling register_destination"); Transport::register_destination(*this); @@ -178,7 +176,7 @@ Packet Destination::announce(const Bytes &app_data, bool path_response, Interfac } else { Bytes destination_hash = _object->_hash; - //zBytes random_hash = Identity::get_random_hash()[0:5] << int(time.time()).to_bytes(5, "big"); + //random_hash = Identity::get_random_hash()[0:5] << int(time.time()).to_bytes(5, "big") Bytes random_hash; Bytes new_app_data(app_data); @@ -187,19 +185,19 @@ Packet Destination::announce(const Bytes &app_data, bool path_response, Interfac } Bytes signed_data; - debug("Destination::announce: hash: " + _object->_hash.toHex()); - debug("Destination::announce: identity public key: " + _object->_identity.get_public_key().toHex()); - debug("Destination::announce: name hash: " + _object->_name_hash.toHex()); - debug("Destination::announce: random hash: " + random_hash.toHex()); + debug("Destination::announce: hash: " + _object->_hash.toHex()); + debug("Destination::announce: public key: " + _object->_identity.get_public_key().toHex()); + debug("Destination::announce: name hash: " + _object->_name_hash.toHex()); + debug("Destination::announce: random hash: " + random_hash.toHex()); debug("Destination::announce: new app data: " + new_app_data.toHex()); signed_data << _object->_hash << _object->_identity.get_public_key() << _object->_name_hash << random_hash; if (new_app_data) { signed_data << new_app_data; } - debug("Destination::announce: signed data: " + signed_data.toHex()); + debug("Destination::announce: signed data: " + signed_data.toHex()); Bytes signature(_object->_identity.sign(signed_data)); - debug("Destination::announce: signature: " + signature.toHex()); + debug("Destination::announce: signature: " + signature.toHex()); announce_data << _object->_identity.get_public_key() << _object->_name_hash << random_hash << signature; @@ -209,7 +207,7 @@ Packet Destination::announce(const Bytes &app_data, bool path_response, Interfac _object->_path_responses.insert({tag, {time(nullptr), announce_data}}); } - debug("Destination::announce: announce_data: " + announce_data.toHex()); + debug("Destination::announce: announce_data:" + announce_data.toHex()); Packet::context_types announce_context = Packet::CONTEXT_NONE; if (path_response) { @@ -217,49 +215,99 @@ Packet Destination::announce(const Bytes &app_data, bool path_response, Interfac } debug("Destination::announce: creating announce packet..."); - Packet announce_packet(*this, announce_data, Packet::ANNOUNCE, announce_context,Transport::BROADCAST, Packet::HEADER_1, nullptr, attached_interface); + //announce_packet = RNS.Packet(self, announce_data, RNS.Packet.ANNOUNCE, context = announce_context, attached_interface = attached_interface) + //Packet announce_packet(*this, announce_data, Packet::ANNOUNCE, announce_context, Transport::BROADCAST, Packet::HEADER_1, nullptr, attached_interface); + Packet announce_packet(*this, announce_data, Packet::DATA, announce_context, Transport::BROADCAST, Packet::HEADER_1, nullptr, attached_interface); + extreme("Destination::announce: pre announce packet: " + announce_packet.toString()); if (send) { announce_packet.send(); - return Packet::NONE; + extreme("Destination::announce: post announce packet: " + announce_packet.toString()); + // CBA temporarily returning copy of sent packet for testing purposes + //return Packet::NONE; + return announce_packet; } else { return announce_packet; } } -/* - Registers a function to be called when a link has been established to - this destination. - :param callback: A function or method with the signature *callback(link)* to be called when a new link is established with this destination. +/* +Registers a request handler. + +:param path: The path for the request handler to be registered. +:param response_generator: A function or method with the signature *response_generator(path, data, request_id, link_id, remote_identity, requested_at)* to be called. Whatever this funcion returns will be sent as a response to the requester. If the function returns ``None``, no response will be sent. +:param allow: One of ``RNS.Destination.ALLOW_NONE``, ``RNS.Destination.ALLOW_ALL`` or ``RNS.Destination.ALLOW_LIST``. If ``RNS.Destination.ALLOW_LIST`` is set, the request handler will only respond to requests for identified peers in the supplied list. +:param allowed_list: A list of *bytes-like* :ref:`RNS.Identity` hashes. +:raises: ``ValueError`` if any of the supplied arguments are invalid. */ -void Destination::set_link_established_callback(Callbacks::link_established callback) { +/* +void Destination::register_request_handler(const Bytes &path, response_generator = None, request_policies allow = ALLOW_NONE, allowed_list = None) { + if path == None or path == "": + raise ValueError("Invalid path specified") + elif not callable(response_generator): + raise ValueError("Invalid response generator specified") + elif not allow in Destination.request_policies: + raise ValueError("Invalid request policy") + else: + path_hash = RNS.Identity.truncated_hash(path.encode("utf-8")) + request_handler = [path, response_generator, allow, allowed_list] + self.request_handlers[path_hash] = request_handler +} +*/ + +/* +Deregisters a request handler. + +:param path: The path for the request handler to be deregistered. +:returns: True if the handler was deregistered, otherwise False. +*/ +/* +bool Destination::deregister_request_handler(const Bytes &path) { + path_hash = RNS.Identity.truncated_hash(path.encode("utf-8")) + if path_hash in self.request_handlers: + self.request_handlers.pop(path_hash) + return True + else: + return False +} +*/ + +void Destination::receive(const Packet &packet) { assert(_object); - _object->_callbacks._link_established = callback; + if (packet._packet_type == Packet::LINKREQUEST) { + Bytes plaintext(packet._data); + incoming_link_request(plaintext, packet); + } + else { + // CBA TEST determine why packet._data is being used instead of packet._raw for incoming packets + //Bytes plaintext(decrypt(packet._data)); + Bytes plaintext(decrypt(packet._raw.mid(19))); + extreme("Destination::receive: decrypted data: " + plaintext.toHex()); + if (plaintext) { + if (packet._packet_type == RNS::Packet::DATA) { + if (_object->_callbacks._packet) { + try { + _object->_callbacks._packet(plaintext, packet); + } + catch (std::exception &e) { + debug("Error while executing receive callback from " + toString() + ". The contained exception was: " + e.what()); + } + } + } + } + } } -/* - Registers a function to be called when a packet has been received by - this destination. - - :param callback: A function or method with the signature *callback(data, packet)* to be called when this destination receives a packet. -*/ -void Destination::set_packet_callback(Callbacks::packet callback) { +void Destination::incoming_link_request(const Bytes &data, const Packet &packet) { assert(_object); - _object->_callbacks._packet = callback; -} - -/* - Registers a function to be called when a proof has been requested for - a packet sent to this destination. Allows control over when and if - proofs should be returned for received packets. - - :param callback: A function or method to with the signature *callback(packet)* be called when a packet that requests a proof is received. The callback must return one of True or False. If the callback returns True, a proof will be sent. If it returns False, a proof will not be sent. -*/ -void Destination::set_proof_requested_callback(Callbacks::proof_requested callback) { - assert(_object); - _object->_callbacks._proof_requested = callback; + if (_object->_accept_link_requests) { + //zlink = RNS::Link::validate_request(data, packet); + //zif (link) { + //z _links.append(link); + //z} + } } /* @@ -270,7 +318,7 @@ Encrypts information for ``RNS.Destination.SINGLE`` or ``RNS.Destination.GROUP`` */ Bytes Destination::encrypt(const Bytes &data) { assert(_object); - debug("Destination::encrypt: encrypting bytes"); + debug("Destination::encrypt: encrypting data..."); if (_object->_type == Destination::PLAIN) { return data; @@ -304,7 +352,7 @@ Decrypts information for ``RNS.Destination.SINGLE`` or ``RNS.Destination.GROUP`` */ Bytes Destination::decrypt(const Bytes &data) { assert(_object); - debug("Destination::decrypt: decrypting bytes"); + debug("Destination::decrypt: decrypting data..."); if (_object->_type == Destination::PLAIN) { return data; diff --git a/src/Destination.h b/src/Destination.h index e1108f3..aea8e34 100644 --- a/src/Destination.h +++ b/src/Destination.h @@ -4,12 +4,13 @@ #include "Identity.h" #include "Bytes.h" -#include #include #include #include #include #include +#include +#include namespace RNS { @@ -36,14 +37,15 @@ namespace RNS { public: class Callbacks { public: - using link_established = void(*)(Link *link); + using link_established = void(*)(const Link &link); //using packet = void(*)(uint8_t *data, uint16_t data_len, Packet *packet); - using packet = void(*)(const Bytes &data, Packet *packet); - using proof_requested = void(*)(Packet *packet); - + using packet = void(*)(const Bytes &data, const Packet &packet); + using proof_requested = void(*)(const Packet &packet); + public: link_established _link_established = nullptr; packet _packet = nullptr; proof_requested _proof_requested = nullptr; + friend class Detination; }; //typedef std::pair Response; @@ -106,11 +108,64 @@ namespace RNS { public: Packet announce(const Bytes &app_data = {}, bool path_response = false, Interface *attached_interface = nullptr, const Bytes &tag = {}, bool send = true); + + /* + Set or query whether the destination accepts incoming link requests. + + :param accepts: If ``True`` or ``False``, this method sets whether the destination accepts incoming link requests. If not provided or ``None``, the method returns whether the destination currently accepts link requests. + :returns: ``True`` or ``False`` depending on whether the destination accepts incoming link requests, if the *accepts* parameter is not provided or ``None``. + */ inline void set_accepts_links(bool accepts) { assert(_object); _object->_accept_link_requests = accepts; } inline bool get_accepts_links() { assert(_object); return _object->_accept_link_requests; } - void set_link_established_callback(Callbacks::link_established callback); - void set_packet_callback(Callbacks::packet callback); - void set_proof_requested_callback(Callbacks::proof_requested callback); + + /* + Registers a function to be called when a link has been established to + this destination. + + :param callback: A function or method with the signature *callback(link)* to be called when a new link is established with this destination. + */ + inline void set_link_established_callback(Callbacks::link_established callback) { + assert(_object); + _object->_callbacks._link_established = callback; + } + /* + Registers a function to be called when a packet has been received by + this destination. + + :param callback: A function or method with the signature *callback(data, packet)* to be called when this destination receives a packet. + */ + inline void set_packet_callback(Callbacks::packet callback) { + assert(_object); + _object->_callbacks._packet = callback; + } + /* + Registers a function to be called when a proof has been requested for + a packet sent to this destination. Allows control over when and if + proofs should be returned for received packets. + + :param callback: A function or method to with the signature *callback(packet)* be called when a packet that requests a proof is received. The callback must return one of True or False. If the callback returns True, a proof will be sent. If it returns False, a proof will not be sent. + */ + inline void set_proof_requested_callback(Callbacks::proof_requested callback) { + assert(_object); + _object->_callbacks._proof_requested = callback; + } + + /* + Sets the destinations proof strategy. + + :param proof_strategy: One of ``RNS.Destination.PROVE_NONE``, ``RNS.Destination.PROVE_ALL`` or ``RNS.Destination.PROVE_APP``. If ``RNS.Destination.PROVE_APP`` is set, the `proof_requested_callback` will be called to determine whether a proof should be sent or not. + */ + inline void set_proof_strategy(proof_strategies proof_strategy) { + assert(_object); + //if (proof_strategy <= PROOF_NONE) { + // throw throw std::invalid_argument("Unsupported proof strategy"); + //} + _object->_proof_strategy = proof_strategy; + } + + void receive(const Packet &packet); + void incoming_link_request(const Bytes &data, const Packet &packet); + Bytes encrypt(const Bytes &data); Bytes decrypt(const Bytes &data); Bytes sign(const Bytes &message); @@ -124,6 +179,8 @@ namespace RNS { inline uint16_t mtu() const { assert(_object); return _object->_mtu; } inline void mtu(uint16_t mtu) { assert(_object); _object->_mtu = mtu; } + inline std::string toString() const { assert(_object); return "{Destination:" + _object->_hash.toHex() + "}"; } + private: class Object { public: diff --git a/src/Identity.cpp b/src/Identity.cpp index c144268..1ee8e8e 100644 --- a/src/Identity.cpp +++ b/src/Identity.cpp @@ -3,8 +3,10 @@ #include "Reticulum.h" #include "Packet.h" #include "Log.h" -#include "Cryptography/Hashes.h" #include "Cryptography/X25519.h" +#include "Cryptography/HKDF.h" +#include "Cryptography/Fernet.h" +#include "Cryptography/Random.h" #include @@ -21,18 +23,22 @@ Identity::Identity(bool create_keys) : _object(new Object()) { void Identity::createKeys() { assert(_object); + // CRYPTO: create encryption private keys _object->_prv = Cryptography::X25519PrivateKey::generate(); _object->_prv_bytes = _object->_prv->private_bytes(); debug("Identity::createKeys: prv bytes: " + _object->_prv_bytes.toHex()); - _object->_sig_prv = Cryptography::Ed25519PrivateKey::generate(); - _object->_sig_prv_bytes = _object->_sig_prv->private_bytes(); - debug("Identity::createKeys: sig prv bytes: " + _object->_sig_prv_bytes.toHex()); - + // CRYPTO: create encryption public keys _object->_pub = _object->_prv->public_key(); _object->_pub_bytes = _object->_pub->public_bytes(); debug("Identity::createKeys: pub bytes: " + _object->_pub_bytes.toHex()); + // CRYPTO: create signature private keys + _object->_sig_prv = Cryptography::Ed25519PrivateKey::generate(); + _object->_sig_prv_bytes = _object->_sig_prv->private_bytes(); + debug("Identity::createKeys: sig prv bytes: " + _object->_sig_prv_bytes.toHex()); + + // CRYPTO: create signature public keys _object->_sig_pub = _object->_sig_prv->public_key(); _object->_sig_pub_bytes = _object->_sig_pub->public_bytes(); debug("Identity::createKeys: sig pub bytes: " + _object->_sig_pub_bytes.toHex()); @@ -42,43 +48,6 @@ void Identity::createKeys() { verbose("Identity keys created for " + _object->_hash.toHex()); } -/* -:returns: The public key as *bytes* -*/ -Bytes Identity::get_public_key() { - assert(_object); - return _object->_pub_bytes + _object->_sig_pub_bytes; -} - -void Identity::update_hashes() { - assert(_object); - _object->_hash = truncated_hash(get_public_key()); - debug("Identity::update_hashes: hash: " + _object->_hash.toHex()); - _object->_hexhash = _object->_hash.toHex(); - debug("Identity::update_hashes: hexhash: " + _object->_hexhash); -}; - - -/* -Get a SHA-256 hash of passed data. - -:param data: Data to be hashed as *bytes*. -:returns: SHA-256 hash as *bytes* -*/ -/*static*/ Bytes Identity::full_hash(const Bytes &data) { - return Cryptography::sha256(data); -} - -/* -Get a truncated SHA-256 hash of passed data. - -:param data: Data to be hashed as *bytes*. -:returns: Truncated SHA-256 hash as *bytes* -*/ -/*static*/ Bytes Identity::truncated_hash(const Bytes &data) { - return full_hash(data).right(TRUNCATED_HASHLENGTH/8); -} - /* Encrypts information for the identity. @@ -89,31 +58,34 @@ Encrypts information for the identity. */ Bytes Identity::encrypt(const Bytes &plaintext) { assert(_object); - if (_object->_pub) { - Cryptography::X25519PrivateKey::Ptr ephemeral_key = Cryptography::X25519PrivateKey::generate(); - Bytes ephemeral_pub_bytes = ephemeral_key->public_key()->public_bytes(); - -/* - Bytes shared_key = ephemeral_key->exchange(_object->_pub); - - Bytes derived_key = RNS.Cryptography.hkdf( - length=32, - derive_from=shared_key, - salt=get_salt(), - context=get_context(), - ) - - fernet = Fernet(derived_key) - ciphertext = fernet.encrypt(plaintext) - - return ephemeral_pub_bytes + ciphertext; -*/ - // MOCK - return Bytes::NONE; - } - else { + debug("Identity::encrypt: encrypting data..."); + if (!_object->_pub) { throw std::runtime_error("Encryption failed because identity does not hold a public key"); } + Cryptography::X25519PrivateKey::Ptr ephemeral_key = Cryptography::X25519PrivateKey::generate(); + Bytes ephemeral_pub_bytes = ephemeral_key->public_key()->public_bytes(); + debug("Identity::encrypt: ephemeral public key: " + ephemeral_pub_bytes.toHex()); + + // CRYPTO: create shared key for key exchange using own public key + //shared_key = ephemeral_key.exchange(self.pub) + Bytes shared_key = ephemeral_key->exchange(_object->_pub_bytes); + debug("Identity::encrypt: shared key: " + shared_key.toHex()); + + Bytes derived_key = Cryptography::hkdf( + 32, + shared_key, + get_salt(), + get_context() + ); + debug("Identity::encrypt: derived key: " + derived_key.toHex()); + + Cryptography::Fernet fernet(derived_key); + debug("Identity::encrypt: Fernet encrypting data of length " + std::to_string(plaintext.size())); + extreme("Identity::encrypt: plaintext: " + plaintext.toHex()); + Bytes ciphertext = fernet.encrypt(plaintext); + extreme("Identity::encrypt: ciphertext: " + ciphertext.toHex()); + + return ephemeral_pub_bytes + ciphertext; } @@ -126,42 +98,49 @@ Decrypts information for the identity. */ Bytes Identity::decrypt(const Bytes &ciphertext_token) { assert(_object); - if (_object->_prv) { - if (ciphertext_token.size() > Identity::KEYSIZE/8/2) { - Bytes plaintext; - try { - Bytes peer_pub_bytes = ciphertext_token.right(Identity::KEYSIZE/8/2); - Cryptography::X25519PublicKey::Ptr peer_pub = Cryptography::X25519PublicKey::from_public_bytes(peer_pub_bytes); - -/* - Bytes shared_key = _object->_prv->exchange(peer_pub); - - Bytes derived_key = RNS.Cryptography.hkdf( - length=32, - derive_from=shared_key, - salt=get_salt(), - context=get_context(), - ) - - fernet = Fernet(derived_key) - ciphertext = ciphertext_token[Identity.KEYSIZE//8//2:] - plaintext = fernet.decrypt(ciphertext) -*/ - } - catch (std::exception &e) { - debug("Decryption by " + _object->_hash.toHex() + " failed: " + e.what()); - } - - return plaintext; - } - else { - debug("Decryption failed because the token size was invalid."); - return Bytes::NONE; - } - } - else { + debug("Identity::decrypt: decrypting data..."); + if (!_object->_prv) { throw std::runtime_error("Decryption failed because identity does not hold a private key"); } + if (ciphertext_token.size() <= Identity::KEYSIZE/8/2) { + debug("Decryption failed because the token size " + std::to_string(ciphertext_token.size()) + " was invalid."); + return Bytes::NONE; + } + Bytes plaintext; + try { + //peer_pub_bytes = ciphertext_token[:Identity.KEYSIZE//8//2] + Bytes peer_pub_bytes = ciphertext_token.left(Identity::KEYSIZE/8/2); + //peer_pub = X25519PublicKey.from_public_bytes(peer_pub_bytes) + //Cryptography::X25519PublicKey::Ptr peer_pub = Cryptography::X25519PublicKey::from_public_bytes(peer_pub_bytes); + debug("Identity::decrypt: peer public key: " + peer_pub_bytes.toHex()); + + // CRYPTO: create shared key for key exchange using peer public key + //shared_key = _object->_prv->exchange(peer_pub); + Bytes shared_key = _object->_prv->exchange(peer_pub_bytes); + debug("Identity::decrypt: shared key: " + shared_key.toHex()); + + Bytes derived_key = Cryptography::hkdf( + 32, + shared_key, + get_salt(), + get_context() + ); + debug("Identity::decrypt: derived key: " + derived_key.toHex()); + + Cryptography::Fernet fernet(derived_key); + //ciphertext = ciphertext_token[Identity.KEYSIZE//8//2:] + Bytes ciphertext(ciphertext_token.mid(Identity::KEYSIZE/8/2)); + debug("Identity::decrypt: Fernet decrypting data of length " + std::to_string(ciphertext.size())); + extreme("Identity::decrypt: ciphertext: " + ciphertext.toHex()); + plaintext = fernet.decrypt(ciphertext); + extreme("Identity::decrypt: plaintext: " + plaintext.toHex()); + debug("Identity::decrypt: Fernet decrypted data of length " + std::to_string(plaintext.size())); + } + catch (std::exception &e) { + debug("Decryption by " + toString() + " failed: " + e.what()); + } + + return plaintext; } /* @@ -173,18 +152,16 @@ Signs information by the identity. */ Bytes Identity::sign(const Bytes &message) { assert(_object); - if (_object->_sig_prv) { - try { - return _object->_sig_prv->sign(message); - } - catch (std::exception &e) { - error("The identity " + toString() + " could not sign the requested message. The contained exception was: " + e.what()); - throw e; - } - } - else { + if (!_object->_sig_prv) { throw std::runtime_error("Signing failed because identity does not hold a private key"); } + try { + return _object->_sig_prv->sign(message); + } + catch (std::exception &e) { + error("The identity " + toString() + " could not sign the requested message. The contained exception was: " + e.what()); + throw e; + } } /* diff --git a/src/Identity.h b/src/Identity.h index 264332f..f61f570 100644 --- a/src/Identity.h +++ b/src/Identity.h @@ -5,9 +5,10 @@ //#include "Destination.h" #include "Log.h" #include "Bytes.h" -#include "Cryptography/Fernet.h" -#include "Cryptography/X25519.h" +#include "Cryptography/Hashes.h" #include "Cryptography/Ed25519.h" +#include "Cryptography/X25519.h" +#include "Cryptography/Fernet.h" #include @@ -66,11 +67,51 @@ namespace RNS { public: void createKeys(); - Bytes get_public_key(); - void update_hashes(); + /* + :returns: The public key as *bytes* + */ + inline Bytes get_public_key() { + assert(_object); + return _object->_pub_bytes + _object->_sig_pub_bytes; + } + inline void update_hashes() { + assert(_object); + _object->_hash = truncated_hash(get_public_key()); + debug("Identity::update_hashes: hash: " + _object->_hash.toHex()); + _object->_hexhash = _object->_hash.toHex(); + }; - static Bytes full_hash(const Bytes &data); - static Bytes truncated_hash(const Bytes &data); + /* + Get a SHA-256 hash of passed data. + + :param data: Data to be hashed as *bytes*. + :returns: SHA-256 hash as *bytes* + */ + static inline Bytes full_hash(const Bytes &data) { + return Cryptography::sha256(data); + } + /* + Get a truncated SHA-256 hash of passed data. + + :param data: Data to be hashed as *bytes*. + :returns: Truncated SHA-256 hash as *bytes* + */ + static inline Bytes truncated_hash(const Bytes &data) { + //return Identity.full_hash(data)[:(Identity.TRUNCATED_HASHLENGTH//8)] + return full_hash(data).left(TRUNCATED_HASHLENGTH/8); + } + /* + Get a random SHA-256 hash. + + :param data: Data to be hashed as *bytes*. + :returns: Truncated SHA-256 hash of random data as *bytes* + */ + static inline Bytes get_random_hash() { + return truncated_hash(Cryptography::random(Identity::TRUNCATED_HASHLENGTH/8)); + } + + inline Bytes get_salt() { assert(_object); return _object->_hash; } + inline Bytes get_context() { return Bytes::NONE; } Bytes encrypt(const Bytes &plaintext); Bytes decrypt(const Bytes &ciphertext_token); @@ -87,7 +128,7 @@ namespace RNS { inline Bytes hash() const { assert(_object); return _object->_hash; } inline std::string hexhash() const { assert(_object); return _object->_hexhash; } - inline std::string toString() const { assert(_object); return _object->_hash.toHex(); } + inline std::string toString() const { assert(_object); return "{Identity:" + _object->_hash.toHex() + "}"; } private: class Object { diff --git a/src/Log.cpp b/src/Log.cpp index d250be3..1850360 100644 --- a/src/Log.cpp +++ b/src/Log.cpp @@ -45,6 +45,7 @@ void RNS::doLog(const char* msg, LogLevel level) { Serial.print(getLevelName(level)); Serial.print(" "); Serial.println(msg); + Serial.flush(); #else printf("%s: %s\n", getLevelName(level), msg); #endif diff --git a/src/Packet.cpp b/src/Packet.cpp index 2995998..5a0864f 100644 --- a/src/Packet.cpp +++ b/src/Packet.cpp @@ -8,10 +8,10 @@ using namespace RNS; -//Packet::Packet(const Destination &destination, const Bytes &data, types packet_type, context_types context, Transport::types transport_type, header_types header_type, const uint8_t *transport_id, Interface *attached_interface, bool create_receipt) : _object(new Object(destination)) { -Packet::Packet(const Destination &destination, const Interface &attached_interface, const Bytes &data, types packet_type /*= DATA*/, context_types context /*= CONTEXT_NONE*/, Transport::types transport_type /*= Transport::BROADCAST*/, header_types header_type /*= HEADER_1*/, const uint8_t *transport_id /*= nullptr*/, bool create_receipt /*= true*/) : _object(new Object(destination, attached_interface)) { +Packet::Packet(const Destination &destination, const Interface &attached_interface, const Bytes &data, types packet_type /*= DATA*/, context_types context /*= CONTEXT_NONE*/, Transport::types transport_type /*= Transport::BROADCAST*/, header_types header_type /*= HEADER_1*/, const Bytes &transport_id /*= Bytes::NONE*/, bool create_receipt /*= true*/) : _object(new Object(destination, attached_interface)) { if (_object->_destination) { + extreme("Creating packet with detination..."); // CBA TODO handle NONE if (transport_type == -1) { transport_type = Transport::BROADCAST; @@ -23,31 +23,37 @@ Packet::Packet(const Destination &destination, const Interface &attached_interfa _transport_type = transport_type; _context = context; - //transport_id = transport_id; - //setTransportId(transport_id); - if (transport_id != nullptr) { - memcpy(_transport_id, transport_id, Reticulum::DESTINATION_LENGTH); - } + _transport_id = transport_id; - //data = data; - //setData(data); + _data = data; + if (_data.size() > MDU) { + _truncated = true; + _data.resize(MDU); + } +/* if (data) { + // data is plaintext if (data.size() > MDU) { _truncated = true; // CBA TODO add method to truncate //zdata_len = MDU; } + _data = _raw + Reticulum::HEADER_MAXSIZE; memcpy(_data, data.data(), data.size()); } +*/ _flags = get_packed_flags(); _create_receipt = create_receipt; } else { - //_raw = data; + extreme("Creating packet without detination..."); + _raw = data; +/* if (data) { memcpy(_raw, data.data(), data.size()); } +*/ _packed = true; _fromPacked = true; _create_receipt = false; @@ -60,57 +66,6 @@ Packet::~Packet() { } -/* -void Packet::setTransportId(const uint8_t* transport_id) { - if (_transport_id == nullptr) { - delete[] _transport_id; - _transport_id = nullptr; - } - if (transport_id != nullptr) { - _transport_id = new uint8_t[Reticulum::ADDRESS_LENGTH]; - memcpy(_transport_id, transport_id, Reticulum::ADDRESS_LENGTH); - } -} - -void Packet::setTransportId(const uint8_t* header) { - if (_header == nullptr) { - delete[] _header; - _header = nullptr; - } - if (header != nullptr) { - _header = new uint8_t[HEADER_MAXSIZE]; - memcpy(_header, header, HEADER_MAXSIZE); - } -} - -void Packet::setRaw(const uint8_t* raw, uint16_t len) { - if (_raw == nullptr) { - delete[] _raw; - _raw = nullptr; - _raw_len = 0; - } - if (raw != nullptr) { - _raw = new uint8_t[_MTU]; - memcpy(_raw, raw, len); - _raw_len = len; - } -} - -void Packet::setData(const uint8_t* data, uint16_t len) { - if (_data == nullptr) { - delete[] _data; - _data = nullptr; - _data_len = 0; - } - if (data != nullptr) { - _data = new uint8_t[MDU]; - memcpy(_data, data, len); - _data_len = len; - } -} -*/ - - uint8_t Packet::get_packed_flags() { uint8_t packed_flags = 0; if (_context == LRPROOF) { @@ -122,6 +77,13 @@ uint8_t Packet::get_packed_flags() { return packed_flags; } +void Packet::unpack_flags(uint8_t flags) { + _header_type = static_cast((flags & 0b01000000) >> 6); + _transport_type = static_cast((flags & 0b00110000) >> 4); + _destination_type = static_cast((flags & 0b00001100) >> 2); + _packet_type = static_cast(flags & 0b00000011); +} + /* == Reticulum Wire Format ====== @@ -287,20 +249,22 @@ but excluding any interface access codes. // | ...destination_2 | context | data ... | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +/* void Packet::pack() { assert(_object); debug("Packet::pack: packing packet..."); + extreme("Packet::pack: pre hops: " + std::to_string(_hops)); //memcpy(_destination_hash, _destination->_hash.data(), Reticulum::DESTINATION_LENGTH); memcpy(_destination_hash, _object->_destination.hash().data(), _object->_destination.hash().size()); - _header[0] = _flags; - _header[1] = _hops; - //uint8_t *ciphertext; if (_context == LRPROOF) { // write header - //memcpy(_header+2, _destination->_link_id, Reticulum::DESTINATION_LENGTH); + _header = _data - Reticulum::HEADER_MINSIZE; + _header[0] = _flags; + _header[1] = _hops; + //memcpy(header+2, _destination->_link_id, Reticulum::DESTINATION_LENGTH); debug("Packet::pack: destination link id: " + _object->_destination.link_id().toHex() ); memcpy(_header+2, _object->_destination.link_id().data(), _object->_destination.link_id().size()); _header[Reticulum::DESTINATION_LENGTH+2] = _context; @@ -311,7 +275,10 @@ void Packet::pack() { else { if (_header_type == HEADER_1) { // write header - //memcpy(_header+2, _destination->_hash.data(), Reticulum::DESTINATION_LENGTH); + _header = _data - Reticulum::HEADER_MINSIZE; + _header[0] = _flags; + _header[1] = _hops; + //memcpy(header+2, _destination->_hash.data(), Reticulum::DESTINATION_LENGTH); debug("Packet::pack: destination hash: " + _object->_destination.hash().toHex() ); memcpy(_header+2, _object->_destination.hash().data(), _object->_destination.hash().size()); _header[Reticulum::DESTINATION_LENGTH+2] = _context; @@ -359,8 +326,9 @@ void Packet::pack() { //memcpy(data, _data, _data_len); //_data_len = _destination->encrypt(_data, data, _data_len); Bytes plaintext(_data, _data_len); - Bytes bytes = _object->_destination.encrypt(plaintext); - _data_len = bytes.size(); + Bytes ciphertext = _object->_destination.encrypt(plaintext); + memcpy(_data, ciphertext.data(), ciphertext.size()); + _data_len = ciphertext.size(); } } else if (_header_type == HEADER_2) { @@ -368,8 +336,11 @@ void Packet::pack() { throw std::invalid_argument("Packet with header type 2 must have a transport ID"); } // write header + _header = _data - Reticulum::HEADER_MAXSIZE; + _header[0] = _flags; + _header[1] = _hops; memcpy(_header+2, _transport_id, Reticulum::DESTINATION_LENGTH); - //memcpy(_header+Reticulum::DESTINATION_LENGTH+2, _destination->_hash.data(), Reticulum::DESTINATION_LENGTH); + //memcpy(header+Reticulum::DESTINATION_LENGTH+2, _destination->_hash.data(), Reticulum::DESTINATION_LENGTH); debug("Packet::pack: destination hash: " + _object->_destination.hash().toHex() ); memcpy(_header+Reticulum::DESTINATION_LENGTH+2, _object->_destination.hash().data(), _object->_destination.hash().size()); _header[2*Reticulum::DESTINATION_LENGTH+2] = _context; @@ -390,12 +361,104 @@ void Packet::pack() { _packed = true; update_hash(); + extreme("Packet::pack: post hops: " + std::to_string(_hops)); +} +*/ +void Packet::pack() { + assert(_object); + debug("Packet::pack: packing packet..."); + extreme("Packet::pack: pre hops: " + std::to_string(_hops)); + + _destination_hash = _object->_destination.hash(); + + _raw.clear(); + + _raw << _flags; + _raw << _hops; + + if (_context == LRPROOF) { + debug("Packet::pack: destination link id: " + _object->_destination.link_id().toHex() ); + _raw << _object->_destination.link_id(); + _raw << (uint8_t)_context; + _raw << _data; + } + else { + if (_header_type == HEADER_1) { + debug("Packet::pack: destination hash: " + _object->_destination.hash().toHex() ); + _raw << _object->_destination.hash(); + _raw << (uint8_t)_context; + + if (_packet_type == ANNOUNCE) { + // Announce packets are not encrypted + _raw << _data; + } + else if (_packet_type == LINKREQUEST) { + // Link request packets are not encrypted + _raw << _data; + } + else if (_packet_type == PROOF && _context == RESOURCE_PRF) { + // Resource proofs are not encrypted + _raw << _data; + } + else if (_packet_type == PROOF && _object->_destination.type() == Destination::LINK) { + // Packet proofs over links are not encrypted + _raw << _data; + } + else if (_context == RESOURCE) { + // A resource takes care of encryption + // by itself + _raw << _data; + } + else if (_context == KEEPALIVE) { + // Keepalive packets contain no actual + // data + _raw << _data; + } + else if (_context == CACHE_REQUEST) { + // Cache-requests are not encrypted + _raw << _data; + } + else { + // In all other cases, we encrypt the packet + // with the destination's encryption method + _raw << _object->_destination.encrypt(_data); + } + } + else if (_header_type == HEADER_2) { + if (!_transport_id) { + throw std::invalid_argument("Packet with header type 2 must have a transport ID"); + } + debug("Packet::pack: transport id: " + _transport_id.toHex() ); + debug("Packet::pack: destination hash: " + _object->_destination.hash().toHex() ); + _raw << _transport_id; + _raw << _object->_destination.hash(); + _raw << (uint8_t)_context; + + if (_packet_type == ANNOUNCE) { + // Announce packets are not encrypted + _raw << _data; + } + } + } + + if (_raw.size() > _mtu) { + throw std::length_error("Packet size of " + std::to_string(_raw.size()) + " exceeds MTU of " + std::to_string(_mtu) +" bytes"); + } + + _packed = true; + update_hash(); + + extreme("Packet::pack: post hops: " + std::to_string(_hops)); } +/* bool Packet::unpack() { assert(_object); debug("Packet::unpack: unpacking packet..."); + extreme("Packet::unpack: pre hops: " + std::to_string(_hops)); try { + + // read header _flags = _raw[0]; _hops = _raw[1]; @@ -431,6 +494,58 @@ bool Packet::unpack() { return false; } + extreme("Packet::unpack: post hops: " + std::to_string(_hops)); + return true; +} +*/ +bool Packet::unpack() { + assert(_object); + debug("Packet::unpack: unpacking packet..."); + extreme("Packet::unpack: pre hops: " + std::to_string(_hops)); + try { + if (_raw.size() < Reticulum::HEADER_MINSIZE) { + throw std::length_error("Packet size of " + std::to_string(_raw.size()) + " does not meet minimum header size of " + std::to_string(Reticulum::HEADER_MINSIZE) +" bytes"); + } + + const uint8_t *raw = _raw.data(); + + // read header + _flags = raw[0]; + _hops = raw[1]; + + unpack_flags(_flags); + + // CBA TODO detect invalid flags and throw error + if (false) { + log("Received malformed packet, dropping it."); + return false; + } + + if (_header_type == HEADER_2) { + if (_raw.size() < Reticulum::HEADER_MAXSIZE) { + throw std::length_error("Packet size of " + std::to_string(_raw.size()) + " does not meet minimum header size of " + std::to_string(Reticulum::HEADER_MAXSIZE) +" bytes"); + } + _transport_id.assign(raw+2, Reticulum::DESTINATION_LENGTH); + _destination_hash.assign(raw+Reticulum::DESTINATION_LENGTH+2, Reticulum::DESTINATION_LENGTH); + _context = static_cast(raw[2*Reticulum::DESTINATION_LENGTH+2]); + _data.assign(raw+2*Reticulum::DESTINATION_LENGTH+3, _raw.size()-(2*Reticulum::DESTINATION_LENGTH+3)); + } + else { + _transport_id.clear(); + _destination_hash.assign(raw+2, Reticulum::DESTINATION_LENGTH); + _context = static_cast(raw[Reticulum::DESTINATION_LENGTH+2]); + _data.assign(raw+Reticulum::DESTINATION_LENGTH+3, _raw.size()-(Reticulum::DESTINATION_LENGTH+3)); + } + + _packed = false; + update_hash(); + } + catch (std::exception& e) { + error(std::string("Received malformed packet, dropping it. The contained exception was: ") + e.what()); + return false; + } + + extreme("Packet::unpack: post hops: " + std::to_string(_hops)); return true; } @@ -525,8 +640,15 @@ Bytes Packet::getTruncatedHash() { Bytes Packet::get_hashable_part() { assert(_object); Bytes hashable_part; - hashable_part << (uint8_t)(_raw[0] & 0b00001111); - hashable_part.append(_data-Reticulum::DESTINATION_LENGTH-1, _data_len+Reticulum::DESTINATION_LENGTH+1); + hashable_part << (uint8_t)(_raw.data()[0] & 0b00001111); + if (_header_type == Packet::HEADER_2) { + //hashable_part += self.raw[(RNS.Identity.TRUNCATED_HASHLENGTH//8)+2:] + hashable_part << _raw.mid((Identity::TRUNCATED_HASHLENGTH/8)+2); + } + else { + //hashable_part += self.raw[2:]; + hashable_part << _raw.mid(2); + } return hashable_part; } @@ -536,3 +658,41 @@ Bytes Packet::get_hashable_part() { // return ProofDestination(); //} + +std::string Packet::toString() { + if (_packed) { + //unpack(); + } + std::string dump; + dump = "\n--------------------\n"; + dump += "flags: " + hexFromByte(_flags) + "\n"; + dump += " header_type: " + std::to_string(_header_type) + "\n"; + dump += " transport_type: " + std::to_string(_transport_type) + "\n"; + dump += " destination_type: " + std::to_string(_destination_type) + "\n"; + dump += " packet_type: " + std::to_string(_packet_type) + "\n"; + dump += "hops: " + std::to_string(_hops) + "\n"; + dump += "transport: " + _transport_id.toHex() + "\n"; + dump += "destination: " + _destination_hash.toHex() + "\n"; + dump += "context_type: " + std::to_string(_header_type) + "\n"; + dump += "plaintext: " + _data.toHex() + "\n"; + dump += " length: " + std::to_string(_data.size()) + "\n"; + dump += "raw: " + _raw.toHex() + "\n"; + dump += " length: " + std::to_string(_raw.size()) + "\n"; + if (_raw.size() > 0) { + size_t header_len = Reticulum::HEADER_MINSIZE; + if (_header_type == HEADER_2) { + header_len = Reticulum::HEADER_MAXSIZE; + } + dump += " header: " + _raw.left(header_len).toHex() + "\n"; + dump += " key: " + _raw.mid(header_len, Identity::KEYSIZE/8/2).toHex() + "\n"; + Bytes ciphertext(_raw.mid(header_len+Identity::KEYSIZE/8/2)); + dump += " ciphertext: " + ciphertext.toHex() + "\n"; + dump += " length: " + std::to_string(ciphertext.size()) + "\n"; + dump += " iv: " + ciphertext.left(16).toHex() + "\n"; + dump += " sig: " + ciphertext.right(32).toHex() + "\n"; + dump += " aes ciphertext: " + ciphertext.mid(16, ciphertext.size()-48).toHex() + "\n"; + dump += " length: " + std::to_string(ciphertext.size()-48) + "\n"; + } + dump += "--------------------\n"; + return dump; +} diff --git a/src/Packet.h b/src/Packet.h index cf80452..cbd2778 100644 --- a/src/Packet.h +++ b/src/Packet.h @@ -85,8 +85,8 @@ namespace RNS { uint8_t EMPTY_DESTINATION[Reticulum::DESTINATION_LENGTH] = {0}; public: - Packet(const Destination &destination, const Interface &attached_interface, const Bytes &data, types packet_type = DATA, context_types context = CONTEXT_NONE, Transport::types transport_type = Transport::BROADCAST, header_types header_type = HEADER_1, const uint8_t *transport_id = nullptr, bool create_receipt = true); - Packet(const Destination &destination, const Bytes &data, types packet_type = DATA, context_types context = CONTEXT_NONE, Transport::types transport_type = Transport::BROADCAST, header_types header_type = HEADER_1, const uint8_t *transport_id = nullptr, bool create_receipt = true) : Packet(destination, Interface::NONE, data, DATA, CONTEXT_NONE, Transport::BROADCAST, HEADER_1, nullptr, create_receipt) { + Packet(const Destination &destination, const Interface &attached_interface, const Bytes &data, types packet_type = DATA, context_types context = CONTEXT_NONE, Transport::types transport_type = Transport::BROADCAST, header_types header_type = HEADER_1, const Bytes &transport_id = Bytes::NONE, bool create_receipt = true); + Packet(const Destination &destination, const Bytes &data, types packet_type = DATA, context_types context = CONTEXT_NONE, Transport::types transport_type = Transport::BROADCAST, header_types header_type = HEADER_1, const Bytes &transport_id = Bytes::NONE, bool create_receipt = true) : Packet(destination, Interface::NONE, data, packet_type, context, transport_type, header_type, transport_id, create_receipt) { } Packet(NoneConstructor none) { extreme("Packet NONE object created"); @@ -115,6 +115,7 @@ namespace RNS { public: uint8_t get_packed_flags(); + void unpack_flags(uint8_t flags); void pack(); bool unpack(); bool send(); @@ -128,12 +129,14 @@ namespace RNS { // getters/setters inline const Interface& receiving_interface() const { assert(_object); return _object->_receiving_interface; } + std::string toString(); + public: - types _packet_type; - header_types _header_type; - context_types _context; - Transport::types _transport_type; - Destination::types _destination_type; + header_types _header_type = HEADER_1; + Transport::types _transport_type = Transport::BROADCAST; + Destination::types _destination_type = Destination::SINGLE; + types _packet_type = DATA; + context_types _context = CONTEXT_NONE; uint8_t _flags = 0; uint8_t _hops = 0; @@ -153,13 +156,25 @@ namespace RNS { //uint8_t _packet_hash[Reticulum::HASHLENGTH] = {0}; Bytes _packet_hash; +/* uint8_t _destination_hash[Reticulum::DESTINATION_LENGTH] = {0}; uint8_t _transport_id[Reticulum::DESTINATION_LENGTH] = {0}; + // universal packet buffer uint8_t _raw[Reticulum::MTU]; - uint8_t _header[Reticulum::HEADER_MAXSIZE]; + // header pointer into universal packet buffer + uint8_t *_header = _raw; + uint16_t _header_len = 0; + // data pointer into universal packet buffer uint8_t *_data = _raw + Reticulum::HEADER_MAXSIZE; uint16_t _data_len = 0; + uint8_t _raw[Reticulum::MTU]; +*/ + Bytes _destination_hash; + Bytes _transport_id; + + Bytes _raw; // header + ciphertext + Bytes _data; // plaintext private: class Object { diff --git a/src/Test/Test.cpp b/src/Test/Test.cpp index 9858bf9..8e69688 100644 --- a/src/Test/Test.cpp +++ b/src/Test/Test.cpp @@ -7,14 +7,16 @@ void test() { //RNS::LogLevel loglevel = RNS::loglevel(); //RNS::loglevel(RNS::LOG_WARNING); - testMap(); testBytes(); testCowBytes(); testBytesConversion(); + testMap(); testReference(); testCrypto(); + testHMAC(); + testPKCS7(); //RNS::loglevel(loglevel); diff --git a/src/Test/Test.h b/src/Test/Test.h index dc209b2..ad96306 100644 --- a/src/Test/Test.h +++ b/src/Test/Test.h @@ -9,3 +9,5 @@ void testBytesConversion(); void testReference(); void testCrypto(); +void testHMAC(); +void testPKCS7(); diff --git a/src/Test/TestBytes.cpp b/src/Test/TestBytes.cpp index 779a8af..7e544cc 100644 --- a/src/Test/TestBytes.cpp +++ b/src/Test/TestBytes.cpp @@ -4,54 +4,7 @@ #include "Log.h" #include - -void testMap() -{ - const uint8_t prestr[] = "Hello"; - const uint8_t poststr[] = "World"; - - RNS::Bytes prebuf(prestr, 5); - assert(prebuf.size() == 5); - assert(memcmp(prebuf.data(), "Hello", prebuf.size()) == 0); - - RNS::Bytes postbuf(poststr, 5); - assert(postbuf.size() == 5); - assert(memcmp(postbuf.data(), "World", postbuf.size()) == 0); - - std::map map; - map.insert({prebuf, "hello"}); - map.insert({postbuf, "world"}); - assert(map.size() == 2); - - auto preit = map.find(prebuf); - assert(preit != map.end()); - assert((*preit).second.compare("hello") == 0); - if (preit != map.end()) { - RNS::extreme(std::string("found prebuf: ") + (*preit).second); - } - - auto postit = map.find(postbuf); - assert(postit != map.end()); - assert((*postit).second.compare("world") == 0); - if (postit != map.end()) { - RNS::extreme(std::string("found postbuf: ") + (*postit).second); - } - - const uint8_t newstr[] = "World"; - RNS::Bytes newbuf(newstr, 5); - assert(newbuf.size() == 5); - assert(memcmp(newbuf.data(), "World", newbuf.size()) == 0); - auto newit = map.find(newbuf); - assert(newit != map.end()); - assert((*newit).second.compare("world") == 0); - if (newit != map.end()) { - RNS::extreme(std::string("found newbuf: ") + (*newit).second); - } - - std::string str = map["World"]; - assert(str.size() == 5); - assert(str.compare("world") == 0); -} +#include void testBytes() { @@ -154,6 +107,22 @@ void testBytes() { assert(mid.size() == 8); assert(memcmp(mid.data(), "lo World", mid.size()) == 0); } + // test mid to end variant + { + RNS::Bytes mid(bytes.mid(3)); + RNS::extreme("end mid: " + mid.toString()); + assert(mid.size() == 8); + assert(memcmp(mid.data(), "lo World", mid.size()) == 0); + } + + // test resize + { + RNS::Bytes shrink(bytes); + shrink.resize(5); + RNS::extreme("shrink: " + shrink.toString()); + assert(shrink.size() == 5); + assert(memcmp(shrink.data(), "Hello", shrink.size()) == 0); + } // stream into empty bytes { @@ -220,6 +189,8 @@ void testBytes() { assert(bytes.data() == nullptr); } + // TODO test comparison + } void testCowBytes() { @@ -311,14 +282,62 @@ void testBytesConversion() { } +void testMap() +{ + const uint8_t prestr[] = "Hello"; + const uint8_t poststr[] = "World"; + + RNS::Bytes prebuf(prestr, 5); + assert(prebuf.size() == 5); + assert(memcmp(prebuf.data(), "Hello", prebuf.size()) == 0); + + RNS::Bytes postbuf(poststr, 5); + assert(postbuf.size() == 5); + assert(memcmp(postbuf.data(), "World", postbuf.size()) == 0); + + std::map map; + map.insert({prebuf, "hello"}); + map.insert({postbuf, "world"}); + assert(map.size() == 2); + + auto preit = map.find(prebuf); + assert(preit != map.end()); + assert((*preit).second.compare("hello") == 0); + if (preit != map.end()) { + RNS::extreme(std::string("found prebuf: ") + (*preit).second); + } + + auto postit = map.find(postbuf); + assert(postit != map.end()); + assert((*postit).second.compare("world") == 0); + if (postit != map.end()) { + RNS::extreme(std::string("found postbuf: ") + (*postit).second); + } + + const uint8_t newstr[] = "World"; + RNS::Bytes newbuf(newstr, 5); + assert(newbuf.size() == 5); + assert(memcmp(newbuf.data(), "World", newbuf.size()) == 0); + auto newit = map.find(newbuf); + assert(newit != map.end()); + assert((*newit).second.compare("world") == 0); + if (newit != map.end()) { + RNS::extreme(std::string("found newbuf: ") + (*newit).second); + } + + std::string str = map["World"]; + assert(str.size() == 5); + assert(str.compare("world") == 0); +} + /* int main(void) { UNITY_BEGIN(); - RUN_TEST(testMap); RUN_TEST(testBytes); RUN_TEST(testCowBytes); RUN_TEST(testBytesConversion); + RUN_TEST(testMap); return UNITY_END(); } */ diff --git a/src/Test/TestCrypto.cpp b/src/Test/TestCrypto.cpp index 7589f37..cbfc02e 100644 --- a/src/Test/TestCrypto.cpp +++ b/src/Test/TestCrypto.cpp @@ -5,6 +5,12 @@ #include "Destination.h" #include "Packet.h" #include "Bytes.h" +#include "Log.h" + +#include "Cryptography/HMAC.h" +#include "Cryptography/PKCS7.h" + +#include void testCrypto() { @@ -21,6 +27,105 @@ void testCrypto() { //Packet packet = destination.announce("appdata"); } +void testHMAC() { + + { + const char keystr[] = "key"; + const char datastr[] = "The quick brown fox jumps over the lazy dog"; + const uint8_t hasharr[] = { + 0xf7, 0xbc, 0x83, 0xf4, 0x30, 0x53, 0x84, 0x24, + 0xb1, 0x32, 0x98, 0xe6, 0xaa, 0x6f, 0xb1, 0x43, + 0xef, 0x4d, 0x59, 0xa1, 0x49, 0x46, 0x17, 0x59, + 0x97, 0x47, 0x9d, 0xbc, 0x2d, 0x1a, 0x3c, 0xd8 + }; + RNS::Bytes key(keystr); + RNS::Bytes data(datastr); + RNS::Bytes hash(hasharr, sizeof(hasharr)); + RNS::extreme("expected hash: " + hash.toHex()); + RNS::Cryptography::HMAC hmac(key, data); + RNS::Bytes result = hmac.digest(); + RNS::extreme("result hash: " + result.toHex()); + assert(memcmp(hash.data(), result.data(), result.size()) == 0); + } +} + +void testPKCS7() { + + const uint8_t str[] = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"; + + // test buffer of half blocksize + { + size_t len = RNS::Cryptography::PKCS7::BLOCKSIZE / 2; + RNS::Bytes bytes(str, len); + bytes = RNS::Cryptography::PKCS7::pad(bytes); + assert(bytes.size() == RNS::Cryptography::PKCS7::BLOCKSIZE); + + bytes = RNS::Cryptography::PKCS7::unpad(bytes); + assert(bytes.size() == len); + assert(memcmp(bytes.data(), str, len) == 0); + } + + // test buffer of one less blocksize + { + size_t len = RNS::Cryptography::PKCS7::BLOCKSIZE - 1; + RNS::Bytes bytes(str, len); + bytes = RNS::Cryptography::PKCS7::pad(bytes); + assert(bytes.size() == RNS::Cryptography::PKCS7::BLOCKSIZE); + + bytes = RNS::Cryptography::PKCS7::unpad(bytes); + assert(bytes.size() == len); + assert(memcmp(bytes.data(), str, len) == 0); + } + + // test buffer of blocksize + { + size_t len = RNS::Cryptography::PKCS7::BLOCKSIZE; + RNS::Bytes bytes(str, len); + bytes = RNS::Cryptography::PKCS7::pad(bytes); + assert(bytes.size() == RNS::Cryptography::PKCS7::BLOCKSIZE * 2); + + bytes = RNS::Cryptography::PKCS7::unpad(bytes); + assert(bytes.size() == len); + assert(memcmp(bytes.data(), str, len) == 0); + } + + // test inplace buffer of half blocksize + { + size_t len = RNS::Cryptography::PKCS7::BLOCKSIZE / 2; + RNS::Bytes bytes(str, len); + RNS::Cryptography::PKCS7::inplace_pad(bytes); + assert(bytes.size() == RNS::Cryptography::PKCS7::BLOCKSIZE); + + RNS::Cryptography::PKCS7::inplace_unpad(bytes); + assert(bytes.size() == len); + assert(memcmp(bytes.data(), str, len) == 0); + } + + // test inplace buffer of one less blocksize + { + size_t len = RNS::Cryptography::PKCS7::BLOCKSIZE - 1; + RNS::Bytes bytes(str, len); + RNS::Cryptography::PKCS7::inplace_pad(bytes); + assert(bytes.size() == RNS::Cryptography::PKCS7::BLOCKSIZE); + + RNS::Cryptography::PKCS7::inplace_unpad(bytes); + assert(bytes.size() == len); + assert(memcmp(bytes.data(), str, len) == 0); + } + + // test inplace buffer of blocksize + { + size_t len = RNS::Cryptography::PKCS7::BLOCKSIZE; + RNS::Bytes bytes(str, len); + RNS::Cryptography::PKCS7::inplace_pad(bytes); + assert(bytes.size() == RNS::Cryptography::PKCS7::BLOCKSIZE * 2); + + RNS::Cryptography::PKCS7::inplace_unpad(bytes); + assert(bytes.size() == len); + assert(memcmp(bytes.data(), str, len) == 0); + } +} + /* int main(void) { diff --git a/src/Test/TestReference.cpp b/src/Test/TestReference.cpp index 0d840b0..2a41c55 100644 --- a/src/Test/TestReference.cpp +++ b/src/Test/TestReference.cpp @@ -3,6 +3,8 @@ #include "Reticulum.h" #include "Bytes.h" +#include + void testReference() { RNS::Reticulum reticulum_default; diff --git a/src/main.cpp b/src/main.cpp index 42d4a5c..0fa7125 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -19,8 +19,6 @@ #include //#include -#include - // Let's define an app name. We'll use this for all // destinations we create. Since this basic example // is part of a range of example utilities, we'll put @@ -68,9 +66,20 @@ void setup() { //destination.announce(RNS::bytesFromString(fruits[rand() % 7])); // test path - destination.announce(RNS::bytesFromString(fruits[rand() % 7]), true, nullptr, RNS::bytesFromString("test_tag")); + //destination.announce(RNS::bytesFromString(fruits[rand() % 7]), true, nullptr, RNS::bytesFromString("test_tag")); + // test packet send + RNS::Packet send_packet = destination.announce(RNS::bytesFromString(fruits[rand() % 7])); + //RNS::Packet packet = destination.announce(RNS::bytesFromString(fruits[rand() % 7]), false, nullptr, RNS::Bytes::NONE, false); // 23.9% (+0.8%) + RNS::extreme("Test send_packet packet: " + send_packet.toString()); + + // test packet receive + RNS::Packet recv_packet(RNS::Destination::NONE, send_packet._raw); + RNS::extreme("Test recv_packet packet: " + recv_packet.toString()); + + destination.receive(recv_packet); + //zdestination.set_proof_strategy(RNS::Destination::PROVE_ALL); //zannounce_handler = ExampleAnnounceHandler(