mirror of
https://github.com/meshtastic/firmware.git
synced 2026-09-29 17:55:25 -04:00
* Bound remote-initiated key verification sessions Opening a session raises a 30 second banner and a client notification and occupies the only verification slot, all before the peer has authenticated anything, and nothing limited how often that could happen. Adds an absolute session cap that incoming packets cannot refresh, a cooldown between remote-initiated sessions measured from when the previous one ended, and refreshes the idle deadline only when the protocol actually advances rather than on any arriving packet. The busy path now sets ignoreRequest so it no longer answers with a NAK. Also replaces the getTime() - 60 timeout comparison, which underflowed before the clock passed 60. * Use elapsed-time comparison for the session timeout and condense comments
105 lines
5.8 KiB
C++
105 lines
5.8 KiB
C++
#pragma once
|
|
|
|
#include "ProtobufModule.h"
|
|
#include "SinglePortModule.h"
|
|
|
|
enum KeyVerificationState {
|
|
KEY_VERIFICATION_IDLE,
|
|
KEY_VERIFICATION_SENDER_HAS_INITIATED,
|
|
KEY_VERIFICATION_SENDER_AWAITING_NUMBER,
|
|
KEY_VERIFICATION_SENDER_AWAITING_USER,
|
|
KEY_VERIFICATION_RECEIVER_AWAITING_USER,
|
|
KEY_VERIFICATION_RECEIVER_AWAITING_HASH1,
|
|
};
|
|
|
|
// KeyVerification Module overview
|
|
// This module allows for two useful functions. First, it implements a 2sv process that can manually verify a trustworthy
|
|
// connection with another node. It specifically verifies that the other node holds the correct private key for its public key, so
|
|
// it is resistant to MitM attacks. Second, it can be used to bootstrap trust in a new node by carrying the public key in the
|
|
// initial unencrypted message (in the hash1 field of the KeyVerification protobuf). This allows a user to manually verify a new
|
|
// node even if they don't have that node in the local nodeDB at all.
|
|
|
|
// The handshake process is as follows (NodeA = initiator, NodeB = responder):
|
|
// 1. NodeA sends a KeyVerification message containing a random nonce and its own public key (in the
|
|
// hash1 field) to NodeB. Implemented in sendInitialRequest(). It is PKI-encrypted if NodeA already
|
|
// holds NodeB's key, otherwise channel-encrypted (the bootstrap case).
|
|
//
|
|
// 2. NodeB replies (allocReply()) with its own public key (hash1 field) and hash2. NodeB generates a
|
|
// random 6-digit security number and stashes NodeA's public key (as a pending key if not already in
|
|
// the nodeDB). It computes hash1 = SHA256(securityNumber, nonce, NodeA_num, NodeB_num, PK_A, PK_B),
|
|
// then hash2 = SHA256(nonce, hash1). The reply is PKI-encrypted only if NodeB already held NodeA's
|
|
// key; in the bootstrap case it is channel-encrypted so NodeA can read NodeB's key from hash1.
|
|
//
|
|
// 3. NodeA receives the reply (handleReceivedProtobuf()), checks the nonce, stashes NodeB's public key,
|
|
// and prompts the user to enter the security number. The security number is never sent over the mesh
|
|
// and must be communicated over a secondary channel. processSecurityNumber() recomputes hash1 from
|
|
// the entered number and verifies SHA256(nonce, hash1) matches the received hash2. NodeA then sends
|
|
// its hash1 back to NodeB in a PKI-encrypted KeyVerification message (the follow-on PKI packet) and
|
|
// shows the KeyVerificationFinalPrompt menu, displaying 8 characters derived from hash1.
|
|
//
|
|
// 4. NodeB receives NodeA's hash1 (handleReceivedProtobuf(); required to be PKI-encrypted), checks it
|
|
// matches the hash1 NodeB generated, and shows the same 8-character code for final confirmation.
|
|
//
|
|
// The final on-screen code comparison is the actual manual verification: the user confirms the codes
|
|
// match on both devices, proving the two nodes agree on the same public keys (no MitM substitution).
|
|
// PKI-encrypting the follow-on packet additionally proves each node holds the private key for the
|
|
// agreed public key.
|
|
|
|
class KeyVerificationModule : public ProtobufModule<meshtastic_KeyVerification> //, private concurrency::OSThread //
|
|
{
|
|
// CallbackObserver<KeyVerificationModule, const meshtastic::Status *> nodeStatusObserver =
|
|
// CallbackObserver<KeyVerificationModule, const meshtastic::Status *>(this, &KeyVerificationModule::handleStatusUpdate);
|
|
|
|
public:
|
|
KeyVerificationModule();
|
|
/* : concurrency::OSThread("KeyVerification"),
|
|
ProtobufModule("KeyVerification", meshtastic_PortNum_KEY_VERIFICATION_APP, &meshtastic_KeyVerification_msg)
|
|
{
|
|
nodeStatusObserver.observe(&nodeStatus->onNewStatus);
|
|
setIntervalFromNow(setStartDelay()); // Wait until NodeInfo is sent
|
|
}*/
|
|
virtual bool wantUIFrame() { return false; };
|
|
bool sendInitialRequest(NodeNum remoteNode);
|
|
void generateVerificationCode(char *); // fills char with the user readable verification code
|
|
uint32_t getCurrentRemoteNode() { return currentRemoteNode; }
|
|
void commitVerifiedRemoteNode(); // Commit a pending key to NodeDB and mark the node manually verified
|
|
|
|
protected:
|
|
/* Called to handle a particular incoming message
|
|
@return true if you've guaranteed you've handled this message and no other handlers should be considered for it
|
|
*/
|
|
virtual bool handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_KeyVerification *p);
|
|
// virtual meshtastic_MeshPacket *allocReply() override;
|
|
|
|
// rather than add to the craziness that is the admin module, just handle those requests here.
|
|
virtual AdminMessageHandleResult handleAdminMessageForModule(const meshtastic_MeshPacket &mp,
|
|
meshtastic_AdminMessage *request,
|
|
meshtastic_AdminMessage *response) override;
|
|
/*
|
|
* Send our Telemetry into the mesh
|
|
*/
|
|
bool sendMetrics();
|
|
virtual meshtastic_MeshPacket *allocReply() override;
|
|
|
|
private:
|
|
uint64_t currentNonce = 0;
|
|
uint32_t currentNonceTimestamp = 0;
|
|
// millis() the session opened, never refreshed, so a peer cannot hold the slot open indefinitely.
|
|
uint32_t sessionStartedMs = 0;
|
|
// millis() a remote-initiated session last ended. Spacing sessions bounds the slot DoS and the
|
|
// banner spam; stamped at the end rather than the start so the cooldown is a real gap.
|
|
uint32_t lastRemoteSessionMs = 0;
|
|
bool sessionFromRemote = false;
|
|
NodeNum currentRemoteNode = 0;
|
|
uint32_t currentSecurityNumber = 0;
|
|
KeyVerificationState currentState = KEY_VERIFICATION_IDLE;
|
|
uint8_t hash1[32] = {0}; //
|
|
uint8_t hash2[32] = {0}; //
|
|
char message[40] = {0};
|
|
|
|
void processSecurityNumber(uint32_t);
|
|
void updateState(bool resetTimer = true); // check the timeouts and maybe reset the state to idle
|
|
void resetToIdle(); // Zero out module state
|
|
};
|
|
|
|
extern KeyVerificationModule *keyVerificationModule; |