きっかけ
WireGuardを軽量なVPNとして利用している。
センサー、カメラなどの多数の処理ができ、比較的能力が高いLinux搭載デバイスを、安全に配置するにはどうしたらいいか考えてみた。
WireGuardとは
公式サイトではWireGuard は、最先端の暗号化を利用する、非常にシンプルでありながら高速で最新の VPN と紹介されている。
概要としては以下の通りとなる。
- 鍵共有:Curve 25519
- 鍵導出: HKDF
- 認証暗号:Chacha20-Poly1305
- ハッシュ:BLAKE2s
WireGuardの構造
Raspberry Pi OS bullseyeでWireGuradの構成を確認したところ、下記のようになるようだ。
OSに組み込まれたカーネルモジュールが本体となり、設定はユーザー空間のwireguard-toolsがnetlinkインターフェースを介して行う。
設定ファイルを/etc/wireguard/wg0.confとし、
$ sudo wg-quick up /etc/wireguard/wg0.conf
とすることでwg0ネットワークデバイスが作成され、wireguardモジュールが起動する。
WireGuardの接続プロトコル
公式サイトの記載に、Noise Protocol Frameworkの記載がある。
Noise Protocol Frameworkについて公式Webサイトをもとに概要を調べた。
2つの鍵ペアを使い、DH(Diffie-Hellman)計算を行いながらハンドシェイク通信を行い接続を確立する。
課題
下記はwg0.conf設定ファイルの例となるが、秘密鍵が直接記載されている形式となる。
これを保護できないかと考えた。
[Interface]
PrivateKey = FA2qtPQO3XsjOSR06Sx9Eg/rR8pw2B5cSgdOXKpa8Gs=
Address = 10.0.0.112/32
[Peer]
PublicKey = ty5vAu...KfMBw=
Endpoint = XXX.XXX.XXX.XXX:XXXXX
AllowedIPs = 10.0.0.0/24
PersistentKeepalive = 25
セキュアエレメントについて
セキュアエレメントの機能の主要なものとして、秘密鍵を隠して、なりすましを防ぐという機能がある。
具体的には、
- 秘密鍵を読みだすコマンドがなく、公開鍵を読めるコマンドのみある。
- セキュアエレメント内の秘密鍵に値(ハッシュ)を与えて署名、DH計算など行い結果を返すコマンドがある。
今回この機能をWireGuardに組み込んで、秘密鍵を守れるか実装してみた。
WireGuardが利用するCurve25519に対応する、NXP社SE050(C)を利用して実装していく。
環境準備
Raspberry Pi 3b+ bullseye 環境を用意
NXP SE050 C1をI2C接続。(市販品ではMIKROE Plug&Trust clickが利用可能)
$ sudo raspi-configでI2Cを有効
$ i2cdetect –y 1 コマンドで0x48認識を確認
実装方針
WireGuardの静的秘密鍵とのDH計算を行うステップをターゲットとする。
実装のポイント
1,Linuxユーザー空間のwireguard-toolsの変更
wg genkeyコマンド
出力される秘密鍵を隠すため、SE050の内部の鍵の保管番号であるオブジェクトIDを
代わりに出力する。この値自体は漏れても問題ない
wg pubkeyコマンド
上記オブジェクトIDを引数として、SE050から公開鍵を出力する。
2,Linuxカーネルモジュールのwireguard本体
noise.c
ハンドシェイクを行うコード内で、wg->static_identity.static_private 構造体の中身をSE050の内部の鍵の保管番号であるオブジェクトIDに変更し、ECDH計算のためこれを呼び出す関数をSE050のAPIに置き換える。
NXPのPlug&Trustミドルウェアはユーザー空間用なため、カーネルモジュール用にHAL(Hardware Abstraction Layer)の作成が必要。
ミドルウェアコードもカーネル空間用にリライトが必要。
実装後
1,Linuxユーザー空間のwireguard-toolsの変更
NXP SE050の内部鍵のオブジェクトIDである0x10000009をBase64にした数値になり、秘密鍵を隠せた。
出力される公開鍵は相手側で従来通り登録し、準備する。
$ wg genkey 0x10000009
CQAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
$ wg genkey 0x10000009 | wg pubkey
Yh3rhVp7LfvPC4YWjB7L8mJO2hk2VXMh9sZ9B6tLm2c=
2,Linuxカーネルモジュールのwireguard本体
NXP SE050の内部鍵のオブジェクトIDである0x10000009をBase64にした数値を鍵の値として渡し、SE050の関数でDH計算を行う。
$ sudo cat /etc/wireguard/wg0.conf
[Interface]
PrivateKey = CQAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
Address = 10.0.0.110/32
[Peer]
PublicKey = XXXXX=
Endpoint = XXX.XXX.XXX.XXX:XXXXX
AllowedIPs = 10.0.0.0/24
PersistentKeepalive = 25
ネットワークパフォーマンス
セキュアエレメントの追加がネットワークパフォーマンスに影響があるか確認した。
iperf3 5回実行平均
標準のwireguard
39.28Mbit/sec
セキュアエレメント対応wireguard
39.38Mbit/sec
となり、有意な差はなさそうなことが分かった。
キーローテーションを試す
クライアント側で新しいキー番号で秘密鍵 、公開鍵を作成する。
$ wg genkey 0x10000006
BgAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
$ wg genkey 0x10000006 | wg pubkey
9oGFAiGUSMIzg2gzwwEBasjfKxkULebTwzOEjn7K4Rk=
サーバー側でIPはそのまま、新しい公開鍵を登録。
$ sudo wg set wg0 peer "9oGFAiGUSMIzg2gzwwEBasjfKxkULebTwzOEjn7K4Rk=" allowed-ips 10.0.0.111/32
実行直後に接続が切れる。
続けて下記を実行して古い公開鍵を消す。
$ sudo wg set wg0 peer "ChgKBqwSnkov+rcC2KKUQKMgPH9pfrnWXzLpMzfKpg4=" remove
クライアント側に戻り、wg0.confの秘密鍵の番号を変更する。
$ sudo wg-quick down /etc/wireguard/wg0.conf
$ sudo vi /etc/wireguard/wg0.conf
[Interface]
PrivateKey = BgAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
Address = 10.0.0.111/32
[Peer]
PublicKey = XXXX=
Endpoint = XXX.XXX.XXX.XXX:XXXXX
AllowedIPs = 10.0.0.0/24
PersistentKeepalive = 25
$ sudo wg-quick up /etc/wireguard/wg0.conf
これで鍵が変わった状態で再度リンクアップする。
まとめ
クラウド接続が増加してくるIoTデバイスでのWireguardセキュアエレメントの実装は
- IoTデバイスのメンテナンスネットワーク
- 既存のMQTTなどのプロトコル保護
など、ネットワーク、扱うデータ、デバイスをより信頼できるようになるため、活用の幅が広まるのではないかと思う。
関連コードは下記に配置した。
wireguard-se(github)
wireguard-tools-se(github)
参考資料
作って理解するWireGuard
https://speakerdeck.com/fadis/zuo-tuteli-jie-suruwireguard