LoRa + MQTT + OpenRemote によるIoTシステムの構築

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を再利用します。
  • 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秒で失効。