Skip to content

Commit

Permalink
merge file read fix
Browse files Browse the repository at this point in the history
  • Loading branch information
Consti10 committed Jan 5, 2024
2 parents 9d037db + c22c564 commit 147860e
Show file tree
Hide file tree
Showing 14 changed files with 453 additions and 77 deletions.
4 changes: 2 additions & 2 deletions WBLib.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ target_sources(wifibroadcast PRIVATE
${CMAKE_CURRENT_LIST_DIR}/src/external/radiotap/radiotap.c
${CMAKE_CURRENT_LIST_DIR}/src/external/fec/fec_base.cpp

${CMAKE_CURRENT_LIST_DIR}/src/encryption/Key.hpp
${CMAKE_CURRENT_LIST_DIR}/src/encryption/KeyPairTxRx.hpp
${CMAKE_CURRENT_LIST_DIR}/src/encryption/KeyPair.h
${CMAKE_CURRENT_LIST_DIR}/src/encryption/KeyPair.cpp
${CMAKE_CURRENT_LIST_DIR}/src/encryption/Encryptor.cpp
${CMAKE_CURRENT_LIST_DIR}/src/encryption/Encryption.cpp
${CMAKE_CURRENT_LIST_DIR}/src/encryption/EncryptionFsUtils.cpp
Expand Down
12 changes: 11 additions & 1 deletion executables/unit_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,9 @@ static void test_encrypt_decrypt_validate(const bool use_key_from_file,bool mess
const std::string KEY_FILENAME="../example_key/txrx.key";
wb::KeyPairTxRx keyPairTxRx{};
if(use_key_from_file){
keyPairTxRx=wb::read_keypair_from_file(KEY_FILENAME);
auto tmp=wb::read_keypair_from_file(KEY_FILENAME);
assert(tmp.has_value());
keyPairTxRx=tmp.value();
}else{
const auto before=std::chrono::steady_clock::now();
keyPairTxRx=wb::generate_keypair_from_bind_phrase("openhd");
Expand Down Expand Up @@ -247,6 +249,13 @@ static void test_encrypt_decrypt_validate(const bool use_key_from_file,bool mess
}
fmt::print("Test {} with {} passed\n",TEST_TYPE,TEST_KEY_TYPE);
}
static void test_encryption_serialize(){
auto keypair1=wb::generate_keypair_from_bind_phrase("openhd");
auto raw=wb::KeyPairTxRx::as_raw(keypair1);
auto serialized_deserialized=wb::KeyPairTxRx::from_raw(raw);
assert(keypair1==serialized_deserialized);
fmt::print("Serialize / Deserialize test passed\n");
}


int main(int argc, char *argv[]) {
Expand Down Expand Up @@ -281,6 +290,7 @@ int main(int argc, char *argv[]) {
}
if (test_mode == 0 || test_mode == 2) {
std::cout << "Testing Encryption"<<std::endl;
test_encryption_serialize();
test_encrypt_decrypt_validate(false, false);
test_encrypt_decrypt_validate(false, true);
test_encrypt_decrypt_validate(true, false);
Expand Down
9 changes: 8 additions & 1 deletion executables/wfb_keygen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,5 +55,12 @@ int main(int argc, char *const *argv) {
keyPairTxRx=wb::generate_keypair_random();
}
//auto keypair=wb::generate_keypair_from_bind_phrase("openhd");
return wb::write_keypair_to_file(keyPairTxRx,"txrx.key");
auto res= wb::write_keypair_to_file(keyPairTxRx, "txrx.key");
if(res){
std::cout<<"Wrote keypair to file"<<std::endl;
return 0;
}else{
std::cout<<"Cannot write keypair to file"<<std::endl;
return -1;
}
}
169 changes: 169 additions & 0 deletions src/Encryption.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
//
// Created by consti10 on 13.08.23.
//

#include "Encryption.h"

#include <cassert>
#include <cstring>
#include <iostream>

#include <spdlog/spdlog.h>
#include "wifibroadcast_spdlog.h"

wb::KeyPairTxRx wb::generate_keypair_random() {
KeyPairTxRx ret{};
crypto_box_keypair(ret.key_1.public_key.data(), ret.key_1.secret_key.data());
crypto_box_keypair(ret.key_2.public_key.data(), ret.key_2.secret_key.data());
return ret;
}

// Salts generated once using https://www.random.org/cgi-bin/randbyte?nbytes=16&format=d
// We want deterministic seed from a pw, and are only interested in making it impossible to reverse the process (even though the salt is plain text)
static constexpr std::array<uint8_t,crypto_pwhash_SALTBYTES> OHD_SALT_AIR{192,189,216,102,56,153,154,92,228,26,49,209,157,7,128,207};
static constexpr std::array<uint8_t,crypto_pwhash_SALTBYTES> OHD_SALT_GND{179,30,150,20,17,200,225,82,48,64,18,130,89,62,83,234};

std::array<uint8_t, crypto_box_SEEDBYTES>
wb::create_seed_from_password_openhd_salt(const std::string& pw,
bool use_salt_air) {
const auto salt = use_salt_air ? OHD_SALT_AIR : OHD_SALT_GND;
std::array<uint8_t , crypto_box_SEEDBYTES> key{};
if (crypto_pwhash(key.data(), key.size(), pw.c_str(), pw.length(), salt.data(),
crypto_pwhash_OPSLIMIT_INTERACTIVE, crypto_pwhash_MEMLIMIT_INTERACTIVE,
crypto_pwhash_ALG_DEFAULT) != 0) {
std::cerr<<"ERROR: cannot create_seed_from_password_openhd_salt"<<std::endl;
assert(false);
// out of memory
}
return key;
}

wb::KeyPairTxRx wb::generate_keypair_from_bind_phrase(
const std::string& bind_phrase) {
const auto seed_air=
create_seed_from_password_openhd_salt(bind_phrase, true);
const auto seed_gnd=
create_seed_from_password_openhd_salt(bind_phrase, false);
KeyPairTxRx ret{};
crypto_box_seed_keypair(ret.key_1.public_key.data(), ret.key_1.secret_key.data(),seed_air.data());
crypto_box_seed_keypair(ret.key_2.public_key.data(), ret.key_2.secret_key.data(),seed_gnd.data());
return ret;
}

std::array<uint8_t, crypto_aead_chacha20poly1305_KEYBYTES> wb::create_onetimeauth_subkey(
const uint64_t& nonce, const std::array<uint8_t, 32U>& session_key) {
// sub-key for this packet
std::array<uint8_t, 32> subkey{};
// We only have an 8 byte nonce, this should be enough entropy
std::array<uint8_t,16> nonce_buf{0};
memcpy(nonce_buf.data(),(uint8_t*)&nonce,8);
crypto_core_hchacha20(subkey.data(),nonce_buf.data(),session_key.data(), nullptr);
return subkey;
}

void wb::Encryptor::makeNewSessionKey(
std::array<uint8_t, 24U>& sessionKeyNonce,
std::array<uint8_t, 32U + 16U>& sessionKeyData) {
randombytes_buf(session_key.data(), sizeof(session_key));
randombytes_buf(sessionKeyNonce.data(), sizeof(sessionKeyNonce));
if (crypto_box_easy(sessionKeyData.data(), session_key.data(), sizeof(session_key),
sessionKeyNonce.data(), rx_publickey.data(), tx_secretkey.data()) != 0) {
throw std::runtime_error("Unable to make session key!");
}
}

int wb::Encryptor::authenticate_and_encrypt(const uint64_t& nonce,
const uint8_t* src, int src_len,
uint8_t* dest) {
if(!m_encrypt_data){ // Only sign message
memcpy(dest,src, src_len);
uint8_t* sign=dest+src_len;
const auto sub_key=wb::create_onetimeauth_subkey(nonce,session_key);
crypto_onetimeauth(sign,src,src_len,sub_key.data());
return src_len+crypto_onetimeauth_BYTES;
}
// sign and encrypt all together
long long unsigned int ciphertext_len;
crypto_aead_chacha20poly1305_encrypt(dest, &ciphertext_len,
src, src_len,
(uint8_t *)nullptr, 0,
nullptr,
(uint8_t *) &nonce, session_key.data());
return (int)ciphertext_len;
}

std::shared_ptr<std::vector<uint8_t>>
wb::Encryptor::authenticate_and_encrypt_buff(const uint64_t& nonce,
const uint8_t* src,
std::size_t src_len) {
auto ret=std::make_shared<std::vector<uint8_t>>(src_len + ENCRYPTION_ADDITIONAL_VALIDATION_DATA);
const auto size=authenticate_and_encrypt(nonce, src, src_len, ret->data());
assert(size==ret->size());
return ret;
}

wb::Decryptor::Decryptor(wb::Key key1)
:rx_secretkey(key1.secret_key),tx_publickey(key1.public_key){
memset(session_key.data(), 0, sizeof(session_key));
}


int wb::Decryptor::onNewPacketSessionKeyData(
const std::array<uint8_t, 24U>& sessionKeyNonce,
const std::array<uint8_t, 32U + 16U>& sessionKeyData) {
std::array<uint8_t, sizeof(session_key)> new_session_key{};
if (crypto_box_open_easy(new_session_key.data(),
sessionKeyData.data(), sessionKeyData.size(),
sessionKeyNonce.data(),
tx_publickey.data(), rx_secretkey.data()) != 0) {
// this basically should just never happen, and is an error
wifibroadcast::log::get_default()->warn("unable to decrypt session key");
return SESSION_NOT_VALID;
}
if (memcmp(session_key.data(), new_session_key.data(), sizeof(session_key)) != 0) {
wifibroadcast::log::get_default()->info("Decryptor-New session detected");
session_key = new_session_key;
m_has_valid_session= true;
return SESSION_VALID_NEW;
}
// this is NOT an error, the same session key is sent multiple times !
return SESSION_VALID_NOT_NEW;
}

bool wb::Decryptor::authenticate_and_decrypt(const uint64_t& nonce,
const uint8_t* encrypted,
int encrypted_size,
uint8_t* dest) {
if(!m_encrypt_data){
const auto payload_size=encrypted_size-crypto_onetimeauth_BYTES;
assert(payload_size>0);
const uint8_t* sign=encrypted+payload_size;
//const int res=crypto_auth_hmacsha256_verify(sign,msg,payload_size,session_key.data());
const auto sub_key=wb::create_onetimeauth_subkey(nonce,session_key);
const int res=crypto_onetimeauth_verify(sign,encrypted,payload_size,sub_key.data());
if(res!=-1){
memcpy(dest,encrypted,payload_size);
return true;
}
return false;
}
unsigned long long mlen;
int res=crypto_aead_chacha20poly1305_decrypt(dest, &mlen,
nullptr,
encrypted, encrypted_size,
nullptr,0,
(uint8_t *) (&nonce), session_key.data());
return res!=-1;
}

std::shared_ptr<std::vector<uint8_t>>
wb::Decryptor::authenticate_and_decrypt_buff(const uint64_t& nonce,
const uint8_t* encrypted,
int encrypted_size) {
auto ret=std::make_shared<std::vector<uint8_t>>(encrypted_size - crypto_aead_chacha20poly1305_ABYTES);
const auto res=authenticate_and_decrypt(nonce, encrypted, encrypted_size, ret->data());
if(res){
return ret;
}
return nullptr;
}
156 changes: 156 additions & 0 deletions src/Encryption.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@

#ifndef ENCRYPTION_HPP
#define ENCRYPTION_HPP

#include <sodium.h>

#include <array>
#include <memory>
#include <optional>
#include <string>
#include <vector>

// Namespace that can be used to add encryption+packet validation
// (Or packet validation only to save CPU resources)
// to a lossy unidirectional link
// Packet validation is quite important, to make sure only openhd packets (and not standard wifi packets) are used in OpenHD
// The Encryption / Decryption name(s) are legacy -
// The more difficult part is dealing with the session key stuff, and this class makes it a bit easier to use

// one time authentication and encryption nicely are really similar
static_assert(crypto_onetimeauth_BYTES==crypto_aead_chacha20poly1305_ABYTES);
// Encryption (or packet validation) adds this many bytes to the end of the message
static constexpr auto ENCRYPTION_ADDITIONAL_VALIDATION_DATA=crypto_aead_chacha20poly1305_ABYTES;

namespace wb{



/**
* Generates a new keypair. Non-deterministic, 100% secure.
*/
KeyPairTxRx generate_keypair_random();

/**
* See https://libsodium.gitbook.io/doc/password_hashing
* Deterministic seed from password, but hides password itself (non-reversible)
* Uses a pre-defined salt to be deterministic
*/
std::array<uint8_t , crypto_box_SEEDBYTES>
create_seed_from_password_openhd_salt(const std::string& pw,bool use_salt_air);

// We always use the same bind phrase by default
static constexpr auto DEFAULT_BIND_PHRASE="openhd";
/**
* Generates 2 new (deterministic) tx rx keys, using the seed created from the pw.
* @param bind_phrase the password / bind phrase
*/
KeyPairTxRx generate_keypair_from_bind_phrase(const std::string& bind_phrase=DEFAULT_BIND_PHRASE);

/**
* https://libsodium.gitbook.io/doc/key_derivation
* UINT16SeqNrHelper since we both support encryption and one time validation to save cpu performance
*/
std::array<uint8_t,32> create_onetimeauth_subkey(const uint64_t& nonce,const std::array<uint8_t, crypto_aead_chacha20poly1305_KEYBYTES>& session_key);

class Encryptor {
public:
/**
*
* @param key1 encryption key, otherwise enable a default deterministic encryption key by using std::nullopt
* @param DISABLE_ENCRYPTION_FOR_PERFORMANCE only validate, do not encrypt (less CPU usage)
*/
explicit Encryptor(wb::Key key1)
: tx_secretkey(key1.secret_key),
rx_publickey(key1.public_key){
}
/**
* Creates a new session key, simply put, the data we can send publicly
* @param sessionKeyNonce filled with public nonce
* @param sessionKeyData filled with public data
*/
void makeNewSessionKey(std::array<uint8_t, crypto_box_NONCEBYTES> &sessionKeyNonce,
std::array<uint8_t,crypto_aead_chacha20poly1305_KEYBYTES + crypto_box_MACBYTES> &sessionKeyData);
/**
* Encrypt the given message of size @param src_len
* (Or if encryption is disabled, only calculate the message sign)
* and write the (encrypted) data appended by the validation data into dest
* @param nonce: needs to be different for every packet
* @param src @param src_len message to encrypt
* @param dest needs to point to a memory region at least @param src_len + 16 bytes big
* Returns written data size (msg payload plus sign data)
*/
int authenticate_and_encrypt(const uint64_t& nonce,const uint8_t *src,int src_len,uint8_t* dest);

/**
* For easy use - returns a buffer including (encrypted) payload plus validation data
*/
std::shared_ptr<std::vector<uint8_t>> authenticate_and_encrypt_buff(const uint64_t& nonce,const uint8_t *src,std::size_t src_len);
/**
* Disables encryption (to save cpu performance) but keeps packet validation functionality
* @param encryption_enabled
*/
void set_encryption_enabled(bool encryption_enabled){
m_encrypt_data =encryption_enabled;
}
private:
// tx->rx keypair
const std::array<uint8_t, crypto_box_SECRETKEYBYTES> tx_secretkey{};
const std::array<uint8_t, crypto_box_PUBLICKEYBYTES> rx_publickey{};
std::array<uint8_t, crypto_aead_chacha20poly1305_KEYBYTES> session_key{};
// use this one if you are worried about CPU usage when using encryption
bool m_encrypt_data= true;
};

class Decryptor {
public:
// enable a default deterministic encryption key by using std::nullopt
// else, pass path to file with encryption keys
explicit Decryptor(wb::Key key1);
static constexpr auto SESSION_VALID_NEW=0;
static constexpr auto SESSION_VALID_NOT_NEW=1;
static constexpr auto SESSION_NOT_VALID=-1;
/**
* Returns 0 if the session is a valid session in regards to the key-pairs AND the session is a new session
* Returns 1 if the session is a valid session in regards to the key-pairs but it is not a new session
* Returns -1 if the session is not a valid session in regards to the key-pairs
*
*/
int onNewPacketSessionKeyData(const std::array<uint8_t, crypto_box_NONCEBYTES> &sessionKeyNonce,
const std::array<uint8_t,crypto_aead_chacha20poly1305_KEYBYTES+ crypto_box_MACBYTES> &sessionKeyData);
/**
* Decrypt (or validate only if encryption is disabled) the given message
* and writes the original message content into dest.
* Returns true on success, false otherwise (false== the message is not a valid message)
* @param dest needs to be at least @param encrypted - 16 bytes big.
*/
bool authenticate_and_decrypt(const uint64_t& nonce,const uint8_t* encrypted,int encrypted_size,uint8_t* dest);

/**
* Easier to use, but usage might require memcpy
*/
std::shared_ptr<std::vector<uint8_t>> authenticate_and_decrypt_buff(const uint64_t& nonce,const uint8_t* encrypted,int encrypted_size);
/**
* Disables encryption (to save cpu performance) but keeps packet validation functionality
* @param encryption_enabled
*/
void set_encryption_enabled(bool encryption_enabled){
m_encrypt_data =encryption_enabled;
}
// Set to true as soon as a valid session has been detected
bool has_valid_session() const{
return m_has_valid_session;
}
private:
// use this one if you are worried about CPU usage when using encryption
bool m_encrypt_data= true;
const std::array<uint8_t, crypto_box_SECRETKEYBYTES> rx_secretkey{};
const std::array<uint8_t, crypto_box_PUBLICKEYBYTES> tx_publickey{};
std::array<uint8_t, crypto_aead_chacha20poly1305_KEYBYTES> session_key{};
bool m_has_valid_session= false;
};

} // namespace wb end


#endif //ENCRYPTION_HPP
2 changes: 1 addition & 1 deletion src/encryption/Decryptor.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
#include <memory>
#include <vector>

#include "Key.hpp"
#include "KeyPair.h"

namespace wb {
class Decryptor {
Expand Down
Loading

0 comments on commit 147860e

Please sign in to comment.