Chapter 16
通信のセキュリティ
この章がなぜ必要なのか——「TLS を使っています」は、何も保証しないから.
組み込み機器の TLS 実装で、証明書検証が無効化されている事例は、 驚くほど多い。理由はいつも同じである。
「つながらなかったので、検証を切ったら動いた。」
これで、中間者攻撃に対する防御が完全に失われる。 TLS は「使えば安全」ではなく、「正しく使えば安全」である。
この章で使う既出の用語(定義は各リンク先). リプレイ(06 章 6 節)、AES-CCM(08 章 6 節)、必要(09 章 6 節)、確実(14 章 6 節)、廃止(15 章 8 節)、強力(15 章 4 節)、運用(15 章 8 節)
1. 通信で守るもの
| 性質 | 内容 | 手段 |
|---|---|---|
| 機密性 | 内容を盗聴されない | 暗号化 |
| 完全性 | 改ざんされない | MAC / AEAD |
| サーバ認証 | 接続先が本物である | サーバ証明書の検証 |
| クライアント認証 | 接続元が正規の機器である | クライアント証明書(12 章) |
| 前方秘匿性 (PFS) | 後から鍵が漏れても、過去の通信は復号されない | ECDHE などの鍵交換 |
| リプレイ防止 | 過去のメッセージを再送されない | シーケンス番号、ノンス |
「前方秘匿性」が組み込みでは特に重要である.
機器は 10〜20 年動く。その間に鍵が漏れる可能性は無視できない。
PFS がないと、攻撃者は通信を記録しておいて、 後で鍵を入手したときに全部復号できる(Harvest now, decrypt later)。
ECDHE を使う(RSA 鍵交換を使わない)ことで防げる。 TLS 1.3 では静的な RSA 鍵交換が廃止され、常に PFS がある。
2. TLS の正しい使い方
最低限のチェックリスト
| # | 項目 | よくある失敗 |
|---|---|---|
| 1 | サーバ証明書を検証しているか | VERIFY_NONE にしている |
| 2 | ホスト名を検証しているか | 証明書は検証しているが、CN/SAN を見ていない |
| 3 | 有効期限を検証しているか | 時刻が合っていないので実質無効 |
| 4 | 信頼するルート CA を限定しているか | 数百個のパブリック CA を全部信頼している |
| 5 | TLS 1.2 以上か | 1.0 / 1.1 を許容している |
| 6 | 強い暗号スイートに限定しているか | RC4、3DES、NULL 暗号が有効 |
| 7 | クライアント認証をしているか | サーバが機器を認証していない |
| 8 | 失効を確認しているか | CRL / OCSP を見ていない |
| 9 | エラーを握り潰していないか | 検証失敗でも接続を継続している |
失敗 1: 証明書検証の無効化
/* ★ 絶対にやってはいけない */
mbedtls_ssl_conf_authmode(&conf, MBEDTLS_SSL_VERIFY_NONE);これで TLS は「盗聴されない通信路」ですらなくなる。 中間者が自分の証明書を提示すれば、機器はそれを受け入れて接続する。 以後、すべての通信が中間者に読まれ、改ざんされる。
/* ★ 正しい */
mbedtls_ssl_conf_authmode(&conf, MBEDTLS_SSL_VERIFY_REQUIRED);
mbedtls_ssl_conf_ca_chain(&conf, &cacert, NULL);
mbedtls_ssl_set_hostname(&ssl, "api.example.com"); /* ← ホスト名検証に必須 */
mbedtls_ssl_set_hostname()を呼び忘れると、ホスト名検証が行われない.証明書チェーンは検証されるが、「その証明書が接続先のものか」は確認されない。 攻撃者が正規の CA から取得した別のドメインの証明書を提示すれば通ってしまう。
これは非常に多い実装ミスである。
失敗 2: 時刻が合っていない
証明書の有効期限を検証するには、正しい時刻が必要である。
| 対策 | 内容 |
|---|---|
| RTC + バックアップ電池 | 最も確実。だがコストと電池寿命 |
| NTP / SNTP で同期 | だが NTP 自体が認証されていない(攻撃者が時刻を操作できる) |
| NTS(Network Time Security、RFC 8915) | 認証付きの時刻同期 |
| TLS のハンドシェイクから時刻を得る | 卵と鶏の問題があるが、実用的な妥協 |
| ビルド時刻を下限にする | 「ファームウェアのビルド日より前ではない」ことだけ確認する |
| サーバから安全に時刻を配る | 署名付きの時刻をアプリ層で受け取る |
「ビルド時刻を下限にする」は実用的な妥協である.
/* 完全ではないが、何もないよりずっとよい */ if (current_time < BUILD_TIMESTAMP) { current_time = BUILD_TIMESTAMP; /* 少なくとも過去の証明書は弾ける */ }これで「有効期限の切れた古い証明書を使った中間者攻撃」は防げる。 完全な時刻検証の代わりにはならないが、検証を切るよりは遥かによい。
失敗 3: 信頼する CA が広すぎる
組み込み機器は、特定のサーバとしか通信しない。 なのにブラウザと同じ数百個のパブリック CA を信頼する必要はない。
/* 自社のサーバとしか通信しないなら、自社の CA だけを信頼する */
mbedtls_x509_crt_parse(&cacert, my_company_root_ca, sizeof(my_company_root_ca));| 手法 | 内容 | 長所 | 短所 |
|---|---|---|---|
| プライベート CA のみ信頼 | 自社の CA だけ | 攻撃面が最小 | CA の運用が必要 |
| 証明書ピンニング | 特定の証明書だけを受け入れる | 最強 | 証明書更新時に文鎮化する危険 |
| 公開鍵ピンニング | 公開鍵をピン止め(証明書更新に強い) | バランスがよい | 鍵の更新には対応が要る |
| パブリック CA を限定 | 使う CA だけをバンドル | 運用が楽 | その CA が侵害されると危険 |
ピンニングは諸刃の剣である.
証明書を更新したら、全機器が接続できなくなる——という事故が実際に起きている。
安全な運用:
- 複数のピンを持つ(現行 + 次期の予備)
- バックアップ CA を用意する
- ピンの更新を OTA で配れるようにする
- 有効期限に余裕を持つ
ピンニングを入れるなら、更新のシナリオを必ず設計すること。
3. リソース制約への対応
TLS は組み込みには重い。現実的な削り方を知っておく。
| 項目 | 削減方法 |
|---|---|
| RAM | 証明書チェーンのバッファ、レコードサイズ(MBEDTLS_SSL_MAX_CONTENT_LEN)を削る |
| フラッシュ | 使わない暗号スイート・機能をコンパイル時に除去 |
| ハンドシェイクの時間 | セッション再開(TLS 1.3 の PSK / 0-RTT、TLS 1.2 のセッションチケット) |
| 証明書のサイズ | ECC 証明書を使う(RSA の 1/4 以下) |
| 公開鍵演算 | ハードウェアアクセラレータ(08 章) |
TLS 1.3 の利点
| 利点 | 内容 |
|---|---|
| 1-RTT ハンドシェイク | TLS 1.2 の 2-RTT より速い |
| 0-RTT 再開 | 再接続が非常に速い(ただしリプレイに注意) |
| 暗号スイートの整理 | 危険な選択肢が削除された。設定ミスが起きにくい |
| 常に PFS | 静的 RSA 鍵交換が廃止された |
| ハンドシェイクの暗号化 | 証明書も暗号化される(プライバシー向上) |
0-RTT は便利だが、リプレイ攻撃を許す.
0-RTT で送ったデータは再送されうる(サーバ側で防ぐのが難しい)。 冪等な操作(GET など)にのみ使うこと。 「ドアを開ける」のような操作に使ってはいけない。
DTLS — UDP 上の TLS
| 用途 | 内容 |
|---|---|
| CoAP over DTLS | CoAP(UDP 上で動く HTTP 相当の軽量プロトコル)と DTLS の標準的な組み合わせ |
| LwM2M | DTLS を前提としている |
| リアルタイム性が要る通信 | TCP の再送遅延を避けられる |
DTLS 1.3(RFC 9147)では、コネクション ID により IP アドレスが変わってもセッションを維持できる。 NAT の背後にいる機器や、モバイル機器で有用である。
さらに軽量な選択肢
| 方式 | 内容 |
|---|---|
| OSCORE(RFC 8613) | CoAP のメッセージ単位で暗号化。プロキシを越えて End-to-End |
| EDHOC(RFC 9528) | 極めて軽量な鍵交換。OSCORE と組み合わせる |
| アプリ層の暗号 | 独自プロトコル。強く非推奨だが、極小デバイスでは選択肢 |
独自プロトコルは避けること.
「TLS が重いから独自に作る」は、ほぼ確実に失敗する。 リプレイ防止、鍵更新、ダウングレード防止、エラー処理—— TLS が長年かけて解決してきた問題を、全部作り直すことになる。
どうしても軽くしたいなら、OSCORE + EDHOC のような 標準化された軽量プロトコルを検討すること。
4. 無線プロトコルのセキュリティ
BLE
| 項目 | 内容 |
|---|---|
| ペアリング方式 | Just Works(認証なし)/ Passkey / Numeric Comparison / OOB |
| LE Secure Connections | BLE 4.2 以降。ECDH による鍵交換。必須にすべき |
| レガシーペアリング | BLE 4.0/4.1。中間者攻撃に弱い。無効にする |
| プライバシー | Resolvable Private Address で追跡を防ぐ |
Just Works は「認証なし」である.
画面もボタンもない機器では Just Works になりがちだが、 ペアリング時に中間者がいたら防げない。
対策:
- OOB(Out Of Band)を使う。NFC や QR コードで鍵情報を渡す
- ペアリング可能な時間を極小にする(ボタンを押した 30 秒だけなど)
- アプリ層で追加の認証を行う(証明書ベース)
最後の手段が実務的には最も確実である。 BLE のリンク層セキュリティを信頼しきらず、アプリ層で認証する。
Wi-Fi
| 項目 | 内容 |
|---|---|
| WPA3 | SAE による鍵交換。辞書攻撃に強い。可能なら必須に |
| WPA2 | 現在も広く使われる。KRACK 対策済みのスタックを使うこと |
| WPS | 無効にする。PIN 方式に既知の脆弱性がある |
| オープンネットワーク | 使わない。使うなら必ずアプリ層で暗号化 |
| プロビジョニング | SoftAP / BLE 経由で SSID とパスワードを渡す。この経路を暗号化する |
Wi-Fi のプロビジョニング経路が盲点である.
「機器を AP モードにして、スマホから SSID とパスワードを送る」—— この通信が平文なら、近くにいる誰でも Wi-Fi パスワードを盗める。
- SoftAP に WPA2/WPA3 をかける
- アプリ層で公開鍵暗号を使う(機器の公開鍵で暗号化して送る)
- QR コードで機器の公開鍵を渡す(DPP / Wi-Fi Easy Connect が標準化している)
その他
| プロトコル | セキュリティの要点 |
|---|---|
| Zigbee / Thread | AES-CCM によるリンク層暗号。ネットワークキーの配布が要点 |
| Matter | 証明書ベースの機器認証(DAC)、CASE/PASE。設計が最も現代的 |
| LoRaWAN | AppKey / NwkKey による認証と暗号。鍵のプロビジョニングが要点 |
| NB-IoT / LTE-M | SIM による認証。アプリ層の暗号は別途必要 |
| Modbus / CAN | 認証も暗号もない。上位で守るか、ネットワーク分離 |
産業プロトコル(Modbus、CAN、BACnet)には、そもそもセキュリティがない.
これらは「閉じたネットワークで使う」前提で設計されている。
- ネットワーク分離(01 章の Jeep の事例)
- ゲートウェイでの認証・フィルタ
- CAN では侵入検知(異常なメッセージの検出)
- Modbus/TCP なら TLS でトンネルする(Modbus/TCP Security)
IEC 62443 のゾーンとコンジットの考え方が、まさにこれである。
5. アプリケーション層
TLS の上でも、設計は必要である。
| 項目 | 内容 |
|---|---|
| 認可 | 認証(誰か)と認可(何ができるか)は別。機器ごとに権限を絞る |
| 入力検証 | サーバから来るデータも検証する(サーバが侵害される可能性) |
| レート制限 | 機器側でも異常な頻度の指令を弾く |
| コマンドの署名 | 重要な操作(OTA、工場出荷リセット)は個別に署名する |
| リプレイ防止 | シーケンス番号やタイムスタンプ |
| ログ | 何が起きたかを記録する。改ざんされない形で |
「クラウドは信頼できる」という前提を置かないこと.
クラウド側が侵害されたとき、 全機器に「工場出荷リセット」や「悪意あるファームウェア」を送れるなら、 被害は壊滅的になる。
対策:
- ファームウェアの署名鍵は、クラウドサーバに置かない(13 章の TUF の原則)
- 破壊的な操作には、追加の署名検証を要求する
- 機器側でサニティチェックをする(「全機器同時にリセット」は異常)
6. MQTT の設定
MQTT は、ブローカ(中継サーバ)を介して「トピック」単位でメッセージを 出版/購読(Pub/Sub)する軽量プロトコルで、IoT で最も広く使われる。だから個別に触れる。
| 項目 | 推奨 |
|---|---|
| ポート | 8883(MQTT over TLS)。1883(平文)は使わない |
| クライアント認証 | X.509 クライアント証明書(X.509 は TLS で使う証明書の標準形式。12 章)。ユーザ名/パスワードより強い |
| クライアント ID | 証明書のサブジェクトと紐づける。任意の ID を名乗らせない |
| トピックの認可 | 機器ごとに、読み書きできるトピックを限定する |
| ワイルドカード購読 | 機器には許可しない(他機器のデータが見える) |
| Last Will | 切断検知に使える |
7. 通信設定の検証
TLS 設定をチェックする
設定項目を切り替えて、どの攻撃が成立するかを確かめられる。
実機に対して確認する方法:
# サーバ側の設定確認
$ openssl s_client -connect api.example.com:8883 -showcerts
$ testssl.sh api.example.com:8883 # 包括的な診断
# 機器側の挙動確認(中間者を立てて、検証しているか確かめる)
# → 自己署名証明書を提示して、機器が接続を拒否するか確認する「機器が不正な証明書を拒否するか」を必ず実機で確認すること.
コードレビューだけでは不十分である。 意図的に不正な証明書を提示して、接続が失敗することを試験項目に入れる。
- 自己署名証明書 → 拒否されるか
- 有効期限切れの証明書 → 拒否されるか
- 別のホスト名の正規証明書 → 拒否されるか(ホスト名検証)
- 失効した証明書 → 拒否されるか
これは出荷前試験の必須項目である。
8. この章のまとめ
| ポイント | 内容 |
|---|---|
| 最大の失敗 | 証明書検証の無効化。「つながらないから切った」 |
| ホスト名検証 | set_hostname() を忘れると、別ドメインの正規証明書で通る |
| 時刻 | 合っていないと期限検証が機能しない。ビルド時刻を下限にするだけでも効く |
| 信頼する CA | 自社の CA だけに限定する。パブリック CA を全部信頼しない |
| ピンニング | 強力だが証明書更新で文鎮化する。更新シナリオを必ず設計する |
| 前方秘匿性 | ECDHE を使う。長寿命機器では特に重要 |
| TLS 1.3 | 1-RTT、常に PFS、危険な選択肢の削除。0-RTT はリプレイに注意 |
| 軽量化 | ECC 証明書、セッション再開、OSCORE + EDHOC。独自プロトコルは避ける |
| BLE | Just Works は認証なし。アプリ層で認証するのが確実 |
| Wi-Fi | プロビジョニング経路が盲点。DPP / 公開鍵暗号を使う |
| 産業プロトコル | Modbus / CAN にはセキュリティがない。分離とゲートウェイで守る |
| MQTT | X.509 クライアント認証 + 機器ごとのトピック認可。ワイルドカード購読を禁止 |
| クラウドの前提 | クラウドも侵害されうる。破壊的操作には追加の署名を要求する |
| 検証 | 不正な証明書を提示して拒否されるかを実機で試験する |
ここまでで一般論は終わりである。 次章から第 VI 部——各社の SoC が、これらをどう実装しているかを見る。