mirror of
https://github.com/alexhopeoconnor/firmware.git
synced 2026-10-14 23:41:29 +10:00
* Allow key verification to work for unknown nodes. * trunk * More reliable admin key decryption * Add admin key fallback tests * Actually check haveRemoteKey * Logging cleanup * Address review feedback - Persist the committed key + manually-verified flag in commitVerifiedRemoteNode via saveToDisk(SEGMENT_NODEDATABASE), replacing the "todo: initiate save" - Guard the CryptoEngine pending-key slot with a dedicated internal lock; the Router reads it while already holding the non-recursive cryptLock, so the accessors cannot reuse that lock - Draw the security number from the hardware RNG (CryptRNG fallback) under cryptLock instead of random(); on nRF52 the entropy fill toggles the same CC310 the BLE task's packet crypto uses - Return true after fully handling the hash2 response (restores develop behavior; consistent with the hash1 branch) - Take uint32_t in the number-picker callbacks so 8-digit hex nodenums can't truncate through int - Trim over-long comments flagged by review * feat(tests): add deterministic tests for admin session-key behavior --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
132 lines
5.4 KiB
C++
132 lines
5.4 KiB
C++
#pragma once
|
|
#include "AES.h"
|
|
#include "CTR.h"
|
|
#include "concurrency/LockGuard.h"
|
|
#include "configuration.h"
|
|
#include "mesh-pb-constants.h"
|
|
#include <Arduino.h>
|
|
#include <memory>
|
|
|
|
extern concurrency::Lock *cryptLock;
|
|
|
|
struct CryptoKey {
|
|
uint8_t bytes[32];
|
|
|
|
/// # of bytes, or -1 to mean "invalid key - do not use"
|
|
int8_t length;
|
|
};
|
|
|
|
/**
|
|
* see docs/software/crypto.md for details.
|
|
*
|
|
*/
|
|
|
|
#define MAX_BLOCKSIZE 256
|
|
#define TEST_CURVE25519_FIELD_OPS // Exposes Curve25519::isWeakPoint() for testing keys
|
|
#define XEDDSA_SIGNATURE_SIZE 64
|
|
// Encoded size the signature adds to the Data protobuf: 1 tag byte (field 10 < 16) +
|
|
// 1 length byte (64 < 128) + 64 signature bytes. test_packet_signing asserts this stays exact.
|
|
#define XEDDSA_SIGNATURE_FIELD_BYTES (XEDDSA_SIGNATURE_SIZE + 2)
|
|
|
|
class CryptoEngine
|
|
{
|
|
public:
|
|
#if !(MESHTASTIC_EXCLUDE_PKI)
|
|
uint8_t public_key[32] = {0};
|
|
#endif
|
|
|
|
virtual ~CryptoEngine() {}
|
|
#if !(MESHTASTIC_EXCLUDE_PKI)
|
|
#if !(MESHTASTIC_EXCLUDE_PKI_KEYGEN)
|
|
virtual void generateKeyPair(uint8_t *pubKey, uint8_t *privKey);
|
|
virtual bool regeneratePublicKey(uint8_t *pubKey, uint8_t *privKey);
|
|
virtual bool ensurePkiKeys(meshtastic_Config_SecurityConfig &security, meshtastic_User &user);
|
|
#endif
|
|
#if !(MESHTASTIC_EXCLUDE_XEDDSA)
|
|
bool xeddsa_sign(uint32_t fromNode, uint32_t packetId, uint32_t portnum, const uint8_t *payload, size_t payloadLen,
|
|
uint8_t *signature);
|
|
bool xeddsa_verify(const uint8_t *pubKey, uint32_t fromNode, uint32_t packetId, uint32_t portnum, const uint8_t *payload,
|
|
size_t payloadLen, const uint8_t *signature);
|
|
#endif
|
|
void setDHPrivateKey(uint8_t *_private_key);
|
|
// The remotePublic key parameter takes the public_key bytes container from
|
|
// a stored node header. NodeInfoLite is the on-device storage type since
|
|
// the slim refactor flattened UserLite into it.
|
|
virtual bool encryptCurve25519(uint32_t toNode, uint32_t fromNode, meshtastic_NodeInfoLite_public_key_t remotePublic,
|
|
uint64_t packetNum, size_t numBytes, const uint8_t *bytes, uint8_t *bytesOut);
|
|
virtual bool decryptCurve25519(uint32_t fromNode, meshtastic_NodeInfoLite_public_key_t remotePublic, uint64_t packetNum,
|
|
size_t numBytes, const uint8_t *bytes, uint8_t *bytesOut);
|
|
virtual bool setDHPublicKey(uint8_t *publicKey);
|
|
virtual void hash(uint8_t *bytes, size_t numBytes);
|
|
|
|
// Temporary holder for a peer's not-yet-verified public key, learned in-band during an
|
|
// in-progress key-verification handshake before it is committed to NodeDB. Lets the Router
|
|
// run the DH handshake to encode/decode the follow-on PKI packet. Single slot is enough:
|
|
// only one verification runs at a time. Discarded when the handshake ends (resetToIdle).
|
|
// Internally guarded by pendingKeyLock, not cryptLock: the Router calls the getter while
|
|
// already holding the non-recursive cryptLock; KeyVerificationModule writes from elsewhere.
|
|
void setPendingPublicKey(uint32_t node, const uint8_t *key);
|
|
void clearPendingPublicKey();
|
|
// Fills `out` (size set to 32) and returns true iff a pending key is held for `node`.
|
|
bool getPendingPublicKey(uint32_t node, meshtastic_NodeInfoLite_public_key_t &out);
|
|
|
|
virtual void aesSetKey(const uint8_t *key, size_t key_len);
|
|
|
|
virtual void aesEncrypt(uint8_t *in, uint8_t *out);
|
|
std::unique_ptr<AESSmall256> aes = nullptr;
|
|
|
|
#endif
|
|
|
|
/**
|
|
* Set the key used for encrypt, decrypt.
|
|
*
|
|
* As a special case: If all bytes are zero, we assume _no encryption_ and send all data in cleartext.
|
|
*
|
|
* @param numBytes must be 16 (AES128), 32 (AES256) or 0 (no crypt)
|
|
* @param bytes a _static_ buffer that will remain valid for the life of this crypto instance (i.e. this class will cache the
|
|
* provided pointer)
|
|
*/
|
|
virtual void setKey(const CryptoKey &k);
|
|
|
|
/**
|
|
* Encrypt a packet
|
|
*
|
|
* @param bytes is updated in place
|
|
*/
|
|
virtual void encryptPacket(uint32_t fromNode, uint64_t packetId, size_t numBytes, uint8_t *bytes);
|
|
virtual void decrypt(uint32_t fromNode, uint64_t packetId, size_t numBytes, uint8_t *bytes);
|
|
virtual void encryptAESCtr(CryptoKey key, uint8_t *nonce, size_t numBytes, uint8_t *bytes);
|
|
#ifndef PIO_UNIT_TESTING
|
|
protected:
|
|
#endif
|
|
/** Our per packet nonce */
|
|
uint8_t nonce[16] = {0};
|
|
CryptoKey key = {};
|
|
#if !(MESHTASTIC_EXCLUDE_PKI)
|
|
uint8_t shared_key[32] = {0};
|
|
uint8_t private_key[32] = {0};
|
|
uint32_t pendingKeyVerificationNode = 0;
|
|
uint8_t pendingKeyVerificationPublicKey[32] = {0};
|
|
bool hasPendingKeyVerificationKey = false;
|
|
concurrency::Lock pendingKeyLock;
|
|
#if !(MESHTASTIC_EXCLUDE_XEDDSA)
|
|
uint8_t xeddsa_public_key[32] = {0};
|
|
uint8_t xeddsa_private_key[32] = {0};
|
|
void curve_to_ed_pub(const uint8_t *curve_pubkey, uint8_t *ed_pubkey);
|
|
// Single-entry cache for curve_to_ed_pub conversion (avoids expensive field inversion per packet)
|
|
uint8_t cached_curve_pubkey[32] = {0};
|
|
uint8_t cached_ed_pubkey[32] = {0};
|
|
#endif
|
|
#endif
|
|
/**
|
|
* Init our 128 bit nonce for a new packet
|
|
*
|
|
* The NONCE is constructed by concatenating (from MSB to LSB):
|
|
* a 64 bit packet number (stored in little endian order)
|
|
* a 32 bit sending node number (stored in little endian order)
|
|
* a 32 bit block counter (starts at zero)
|
|
*/
|
|
void initNonce(uint32_t fromNode, uint64_t packetId, uint32_t extraNonce = 0);
|
|
};
|
|
|
|
extern CryptoEngine *crypto; |