SipHashとnonceを導入(アクチュエータノード)
通信仕様の変更
- UARTの下りフレーム(ESP32→STM32)を、7バイトから19バイトに変更しました。形式は
CC 33 target(2) type value nonce(4) tag(8) CRC8です。CRC8の対象はtargetからtagまでです。 - LoRaの
CmdPacketを、11バイトから19バイトに変更。tag[8]を追加し、cmdSeq欄にはActuatorが発行したnonceを入れます。 - LoRaの
StatusPacketを、15バイトから19バイトに変更。nonce(4バイト)を追加しました。 - 署名の対象は、
magic0からcmdValueまでの10バイトです(リトルエンディアン)。アルゴリズムは SipHash-2-4 で、タグは8バイトです。
新規ファイル
siphash24.hを、esp32_gateway/src/とstm32_dx-lr30/src/の両方に追加(ヘッダのみの実装です)。stm32_dx-lr30/src/mesh_secret.hを追加。STM32側の鍵MESH_KEY[16]を置くファイルで、コミットしません。.gitignoreに追記。gen_mesh_key.pyを追加。乱数で鍵を作り、mesh_secret.hを書き出し、ESP32に入れる32文字のhexを表示します。
STM32:mesh_protocol.h
siphash24.hとstddef.hをinclude。CmdPacketにtag[8]を追加。StatusPacketにnonceを追加。meshCmdVerify()を追加。タグを、定数時間で比較して検証します。meshCmdJudge()を追加。結果はMCV_REJECT、MCV_APPLY、MCV_RESEND_STATUSのいずれかです。meshNonceMix()を追加。前回のnonce、カウンタ、micros()、RSSI、ADCを、鍵つきでハッシュしてnonceを作ります。0xFFFFFFFFは予約値で、直前と同じ値も避けます。
STM32:mesh_main.cpp
Actuator
actNonceと、関連の変数を追加。actRotateNonce()を追加。actSendStatus()は、sp.nonceを設定して送信します。loopActuator()のコマンド受信を、meshCmdJudge()の結果で分岐するように変更。- タグが不正なら破棄。
- nonceが一致すれば、リレーを適用します。
- nonceが古い(使用済みの再送や、再生されたもの)場合は、再適用せずに、ステータスだけを返します。返信は500 msに1回までに制限。
- 次の3つのタイミングで、nonceを更新。
- コマンドの適用後(結果のステータスと同時)
- 60秒ごとの定期ステータスの直前
- 起動時の最初のステータスの直前
Gateway
- UARTの下りの受信を、16バイトの本体を貯める方式に書き換え。
PendingCmdに、nonceとタグを持たせました。sendCommandToActuator()を、PendingCmdを受け取る形に変更。
その他
mesh_secret.hをinclude。CMD_CONFIRM_TIMEOUT_MSを、400から600に変更。
ESP32:main.cpp
StatusPacketの構造体に、nonceを追加。- nonceの管理表を追加(8スロット、ミューテックスで保護)。
- ステータスを受け取るたびに、Actuatorごとの最新nonceを覚えます(
nonce_on_status)。 - コマンドは、nonceがあればすぐに署名して送り、なければ保留します(最新の1件だけ、最長70秒)。
- 送信後に、3秒たっても新しいステータスが来なければ、コマンドが届かなかったとみなし、同じnonceを再利用します。
- ステータスを受け取るたびに、Actuatorごとの最新nonceを覚えます(
cmd_sign()とsend_signed_cmd()を追加。19バイトのUARTフレームを組み立てます。- MQTTの
cmd/relayの処理を、nonce_submit_cmd()の呼び出しに置き換え。 - 鍵の読み込み(
cmd_auth_load_key())を、gw_config_load()の直後で行います。未設定、または不正なら、リレーコマンドを無効にして、ログに案内を出力。
ESP32:ポータルと設定(key-portal.patch)
gw_prov.hのGwConfigに、mesh_key_hex[33]を追加。gw_prov.cpp:NVSのキーmeshkeyに、読み書きします。- ポータルに「Relay command key」の欄(パスワード欄)を追加。空欄なら保存済みの値を維持し、32文字のhex以外は拒否。
- 保存時のログには、鍵そのものは出さず、
setかNOT setだけを出力。
動作として得られるもの
- ESP32が署名。Actuatorが、タグとnonceを検証。
- 盗聴したコマンドを再送しても、nonceが更新されているため、適用されません。
- 署名済みで、届かなかったコマンドは、最長60秒で失効。
