セキュアエレメントによるWireGuard保護

## きっかけ

WireGuardを軽量なVPNとして利用している。
センサー、カメラなどの多数の処理ができ、比較的能力が高いLinux搭載デバイスを、安全に配置するにはどうしたらいいか考えてみた。

## WireGuardとは

[公式サイト](https://www.wireguard.com/)ではWireGuard は、最先端の暗号化を利用する、非常にシンプルでありながら高速で最新の VPN と紹介されている。
概要としては以下の通りとなる。

* 鍵共有：Curve 25519
* 鍵導出： HKDF
* 認証暗号：Chacha20-Poly1305
* ハッシュ：BLAKE2s

## WireGuardの構造

Raspberry Pi OS bullseyeでWireGuradの構成を確認したところ、下記のようになるようだ。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/387653/0927db1e-80a4-a7a2-7288-cfa13c49abc1.png)

OSに組み込まれたカーネルモジュールが本体となり、設定はユーザー空間のwireguard-toolsがnetlinkインターフェースを介して行う。

設定ファイルを/etc/wireguard/wg0.confとし、

```
$ sudo wg-quick up /etc/wireguard/wg0.conf
```

とすることでwg0ネットワークデバイスが作成され、wireguardモジュールが起動する。

## WireGuardの接続プロトコル

[公式サイト](https://www.wireguard.com/)の記載に、Noise Protocol Frameworkの記載がある。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/387653/c86315d9-9cfb-8a49-d180-3c913255ca11.png)

Noise Protocol Frameworkについて[公式Webサイト](http://www.noiseprotocol.org/)をもとに概要を調べた。
2つの鍵ペアを使い、DH(Diffie-Hellman)計算を行いながらハンドシェイク通信を行い接続を確立する。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/387653/0b92be33-c8d2-960d-10bd-06d407a3bedd.png)

## 課題

下記は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
```


## セキュアエレメントについて

セキュアエレメントの機能の主要なものとして、秘密鍵を隠して、なりすましを防ぐという機能がある。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/387653/eb5e7e99-1d28-a112-ee4f-2593bbe20b46.png)

具体的には、
* 秘密鍵を読みだすコマンドがなく、公開鍵を読めるコマンドのみある。
* セキュアエレメント内の秘密鍵に値（ハッシュ）を与えて署名、DH計算など行い結果を返すコマンドがある。

今回この機能をWireGuardに組み込んで、秘密鍵を守れるか実装してみた。

WireGuardが利用するCurve25519に対応する、[NXP社SE050](https://www.nxp.jp/products/security-and-authentication/authentication/edgelock-se050-plug-trust-secure-element-family-enhanced-iot-security-with-high-flexibility:SE050)(C)を利用して実装していく。

## 環境準備
Raspberry Pi 3b＋ bullseye 環境を用意
NXP SE050 C1をI2C接続。（市販品では[MIKROE Plug&Trust click](https://www.mikroe.com/plugtrust-click)が利用可能）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/387653/2195c354-99f3-0956-1a8b-24dec6dff5bc.png)

$ sudo raspi-configでI2Cを有効 
$ i2cdetect –y 1 コマンドで0x48認識を確認
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/387653/a22fb46a-9ce5-3bcc-572d-078aa37a7be0.png)

## 実装方針
WireGuardの静的秘密鍵とのDH計算を行うステップをターゲットとする。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/387653/e22e48ea-f8f9-269d-c5f0-1c80a9373bd5.png)

## 実装のポイント

1,Linuxユーザー空間のwireguard-toolsの変更

:::note info 
wg genkeyコマンド
	出力される秘密鍵を隠すため、SE050の内部の鍵の保管番号であるオブジェクトIDを
代わりに出力する。この値自体は漏れても問題ない
:::

:::note info 
wg pubkeyコマンド
	上記オブジェクトIDを引数として、SE050から公開鍵を出力する。　
:::

2,Linuxカーネルモジュールのwireguard本体

:::note info 
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)](https://github.com/kmwebnet/wireguard-se)
[wireguard-tools-se(github)](https://github.com/kmwebnet/wireguard-tools-se)

## 参考資料

作って理解するWireGuard
https://speakerdeck.com/fadis/zuo-tuteli-jie-suruwireguard