SROS2を読み解く（暗号化）

# この記事について
素晴らしき進化を遂げた「SROS2」について，あまり深堀りor噛み砕いだ解説が見つからなかったので，私なりに読み解こうと思う．今回の記事は「暗号化」について．

# SROS2とは？
ROS1の時からSROSというものがあった．ただし，これはROS1のフォークとして「別物」として存在していた．一方でROS2は，DDS-Security規格を活用した互換性の高い方法を採用しており，比較的手軽？！？．．．を目指して作っている様子である．
※現状，ソースコードからのインストールが必要

SROS2については，基本的には以下の公式系記事の通りであるが，要約すると，この2点と言ったところでしょうか．
- 「DDS-Security」と，ROS2の統合を実現するための「サブセット」のことを`SROS2`と呼んでいる
- サブセット=「RCL統合」と「ツール」

## 前提知識
この記事を読む前に，公式もしくは，準公式（中の人）が書いて下さっている記事は，全体像を掴むためには読んでおくことが望ましい．
- [ROS 2 DDS\-Security integration](http://design.ros2.org/articles/ros2_dds_security.html)
- [Robotics security: What is SROS 2? \| Ubuntu](https://ubuntu.com/blog/what-is-sros-2)
- [ROS 2 Access Control Policies](http://design.ros2.org/articles/ros2_access_control_policies.html)

また，規格そのものや，SROS2の実装そのものについてのリンクも次のように示しておく．
DDS-Security規格：[About the DDS Security Specification Version 1\.1](https://www.omg.org/spec/DDS-SECURITY/1.1)
SROS2リポジトリ：[sros2/SROS2\_Linux\.md at master · ros2/sros2](https://github.com/ros2/sros2/blob/master/SROS2_Linux.md)


## DDS-Security概要
各DDS実装（FastDDSやCycloneDDSなど）は，DDS-Security規格に従って「①認証，②認可，③暗号化，④ロギング，⑤データタギング」といったセキュリティ機能を提供してくれる．ただし，すべてのDDS実装が，DDS-Securityをサポートしている訳ではなく，DDS規格の中ではあくまでも「オプショナル」なものである．

## RCL統合とツール
このDDS-Security規格のうち，SROS2は「①認証，②認可，③暗号化」の3つの特徴をカバーしている．この根幹を支えているのが，[公開鍵基盤（PKI）技術 \- Wikipedia](https://ja.wikipedia.org/wiki/%E5%85%AC%E9%96%8B%E9%8D%B5%E5%9F%BA%E7%9B%A4)を拠り所とする，サービス・プラグイン・インターフェイス（SPI）による具体的な各DDS実装である．
上記にて，サブセット=「RCL統合」と「ツール」として記述したが，それぞれについて「PKI」を軸に，少し深く掘り下げる．

### ツール→公開鍵基盤を作る
基本的な役割は次の通り．`ros2 security`コマンドを通して操作される．
- 自己署名証明書によるルート「CA（認証局）」の構築
- このCAを使った各種ファイルや証明書への「署名」

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/96992/62bf6bb8-3143-0f39-cc08-2e2334e00982.png)

### RCL統合→公開鍵基盤を使う
RCL統合では，ユーザ指定の環境変数・引数・設定ファイルに応じて，各DDS実装に対して，RMWを通じてDDS-Securityの設定を伝搬していく．
具体的な設定項目の一例は次の通り．
- マスター「オン／オフ」スイッチ
- セキュリティ戦略の指定
	- Permissive<Enforce（厳密）
- 「ツール」で生成したセキュリティファイル（各種ファイルや証明書）を指定

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/96992/40088441-3078-3352-397e-02bce64ffd5b.png)



# 暗号化
## なぜ，初手で暗号化について書くのか
上記にて，SROS2では，
> DDS-Security規格のうち，SROS2は「①認証，②認可，③暗号化」の3つの特徴をカバー

と書いたわけではあるが，この記事は「③暗号化」について，さらなる深堀りを実施する．正直，どこから順番に読み解くのが適切かは判断が難しい．しいて言えば，「AESとかの熟れた技術であり，直感的に理解しやすい」と思ったからである．（反面，「①認証」及び，付随する「共有秘密の導出」の仕組みはちょっと難しい．興味がない場合は，出来るものとして考えてもいいかも．．．．）

## 概要
「暗号化」については，SROS2（DDS-Security規格）における以下の変換式さえ理解できれば問題ない．
```
C, T = AES-GCM(K, P, AAD, IV)
```

この変換では，PlainTextを暗号化した結果としての`「C」iphertext`，そのデータ保護と認証（完全性確認）を，受信側で確認するための`Authentication 「T」ag（MAC：Message Authentication Code）`が得られる．

以降では，上記式をもう少し読み解いていく．具体的には，K（鍵）やIV（初期ベクトル）生成についての細かなやりとりについて追いかけていきたい．

なお，以降の文章は特に断りが無い限りは，DDS-Security v1.1のPDF仕様書における章番号や図表番号を引用した解説が中心となる．
[About the DDS Security Specification Version 1\.1](https://www.omg.org/spec/DDS-SECURITY/1.1)

# AES-GCMによる認証/暗号化演算手法（送信側の処理）
DDS-Securityでは，AES in Galois Counter Mode (AES-GCM)を用いて平文から暗号文を計算する。→「9.5 組み込みの暗号 DDS:Crypto:AES-GCM-GMAC」より

- AES：共通鍵暗号アルゴリズムの一つ，ブロック暗号の一つ
	- [Advanced Encryption Standard \- Wikipedia](https://ja.wikipedia.org/wiki/Advanced_Encryption_Standard)
- GCM：暗号利用モード→CTR（カウンターモード）＋Galois（ガロア認証）
	- [Galois/Counter Mode \- Wikipedia](https://ja.wikipedia.org/wiki/Galois/Counter_Mode)

暗号化は，指定された KeyHandle に対応する `SessionKey` を用いて，カウンタモードで AES-GCM アルゴリズムによる暗号化処理を行い，平文入力を暗号文に変換するものである．

4つの入力を受けて、2つの出力を生成する変換する式
```
C, T = AES-GCM(K, P, AAD, IV)
```

表64 - AES-GCM変換の入力内容
| Input        | 説明                                           |
| ------------ | ---------------------------------------------- |
| K            | AES-128ブロック暗号で使用する128ビット鍵<br>またはAES-256ブロック暗号で使用する256ビット鍵 |
| P            | 平文です。暗号化・認証対象となるデータです<br>データ認証のみを行う場合は空欄でもよい |
| AAD          | 追加の認証データ<br>暗号化されていない |
| IV           | 初期化ベクトル<br>96 ビットの NONCE であり、同一キーで繰り返して使用してはならない |

表65 - AES-GCM変換の出力内容
| Input        | 説明                                           |
| ------------ | ---------------------------------------------- |
| C            | 暗号文<br>平文 "P "を暗号化したもの            |
| T            | 暗号文（C）と追加認証データ（AAD）の認証を行うためのメッセージ認証コード（MAC）である |


## Kについて
前述のAES-GCM処理で使用するとされている，「K（鍵）」は，`SessionKey`を指している．そして，`SessionKey`と`SessionReceiverSpecificKey`は、`MasterKey`、`MasterSalt`、`SessionId`から算出される。

```
SessionKey := HMAC256(MasterKey,"SessionKey" | MasterSalt | SessionId)
SessionReceiverSpecificKey 
           := HMAC256(MasterReceiverSpecificKey,
                      "SessionReceiverKey" | MasterSalt | SessionId)
```
- ※SessionKey(256bit)
- ※SessionReceiverSpecificKey(256bit)
- ※HMAC256は、HMAC-SHA256である→「9.5.3.3.3 Computation of SessionKey and SessionReceiverSpecificKey」より
	- ※HMACsha256(key, data)：'data' に対して、第一引数で指定された鍵と RFC2104で定義された SHA256 ハッシュを使用して、ハッシュベースのメッセージ認証コード（MAC）を計算する→「Table. 68」より
- ※`MasterKey`と`MasterSalt`について
	- `MasterKey`は，IETF RFC 5869 [50]で推奨されている **HMAC-Based Key Derivation (HKDF)** を使って，認証処理で取得した `SharedSecret` から派生したものである．→「9.5.2.1.2章 鍵素材について」より
		- [RFC 5869 \- HMAC\-based Extract\-and\-Expand Key Derivation Function \(HKDF\) 日本語訳](https://tex2e.github.io/rfc-translater/html/rfc5869.html)
	- `MasterSalt`は，`MasterKey`と同様の手順（ **HMAC-Based Key Derivation (HKDF)** ）を使って生成している（FastDDSの場合）

### HMAC-Based Key Derivation (HKDF)による鍵の生成
セッション毎に，認証処理で取得した `SharedSecret`から派生した「新しい鍵」を作るための方法．`SharedSecret`に対して，`challenge1` 及び `challenge2` を追加することで派生させる．この方法を使って以下の属性値を決定する．前述までの`MasterKey`は，「`master_sender_key`」を指していると捉えて問題ない．

表 67 - BuiltinParticipantVolatileMessageSecureWriter および BuiltinParticipantVolatileMessageSecureReader 用の KeyMaterial_AES_GCM_GMAC構造体（抜粋）より
| Attribute name   | Attribute value                                  |
| ---------------- | ------------------------------------------------ |
| master_salt      | `HMACsha256 ( sha256(Challenge1 \| KxSaltCookie \| Challenge2) , SharedSecret)` <br><br> 上記機能に対するパラメータを表 68 に定義する。transformation_kindがCRYPTO_TRANSFORMATION_KIND_AES128_GCMの場合、これは最初の128ビットに切り詰められます。 |
| master_sender_key | `HMACsha256 ( sha256(Challenge2 \| KxKeyCookie \| Challenge1) , SharedSecret )` <br><br> 上記機能に対するパラメータを表 68 に定義する。transformation_kindがCRYPTO_TRANSFORMATION_KIND_AES128_GCMの場合、これは最初の128ビットに切り詰められます。 |

※`SharedSecret`：認証処理で取得した，共有秘密と呼ばれる「ノードインスタンス参加者」のみが知る情報
※KeyMaterial_AES_GCM_GMAC構造体は，`CryptoToken`クラスの属性値「dds.cryp.keymat」を指す．

実装側（FastDDS）では次の部分で処理している：
[Fast\-DDS/AESGCMGMAC\_KeyFactory\.cpp at master · eProsima/Fast\-DDS](https://github.com/eProsima/Fast-DDS/blob/master/src/cpp/security/cryptography/AESGCMGMAC_KeyFactory.cpp#L48)
```cpp
bool create_kx_key(
        std::array<uint8_t, 32>& out_data,
        const std::vector<uint8_t>* first_data,
        const char* cookie,
        const std::vector<uint8_t>* second_data,
        const std::vector<uint8_t>* shared_secret)
```

[Fast\-DDS/AESGCMGMAC\_KeyFactory\.cpp at master · eProsima/Fast\-DDS](https://github.com/eProsima/Fast-DDS/blob/master/src/cpp/security/cryptography/AESGCMGMAC_KeyFactory.cpp#L300)
```cpp
if (!create_kx_key(buffer.master_salt, challenge_1, "keyexchange salt", challenge_2, shared_secret_ss))
```
[Fast\-DDS/AESGCMGMAC\_KeyFactory\.cpp at master · eProsima/Fast\-DDS](https://github.com/eProsima/Fast-DDS/blob/master/src/cpp/security/cryptography/AESGCMGMAC_KeyFactory.cpp#L307)
```cpp
if (!create_kx_key(buffer.master_sender_key, challenge_2, "key exchange key", challenge_1, shared_secret_ss))
```


## IVについて
暗号化操作には、次のように構成された96ビットの初期化ベクトル（`InitializationVector`）を使用する。
**通信時，絶対に「同じIV」を使いまわしてはならない**→鍵が導出されてしまうため．DDS-Securityの場合は，派生させて使っているので，大本の共有秘密を求めるのは難しいが，セッションが乗っ取られる可能性はある．
[本当は怖いAES-GCMの話
](https://jovi0608.hatenablog.com/entry/20160524/1464054882)

```
InitializationVector = SessionId | InitializationVectorSuffix
```

- ※InitializationVector(96bit) = SessionId(4×8=32bit) | InitializationVectorSuffix(8×8=64bit)
- 同じ `InitializationVector` が、特定のセンダーに関連付けられた「すべてのセッション鍵」に関連付けられる。それらの鍵のいずれかが`MAC（Message Authentication Code）`を使用されるたびに、`InitializationVector`はインクリメントされなければならない。
	- ※全てのセッション鍵：`SessionKey`とすべての`SessionReceiverSpecificKeys`
- 生成される暗号電文には、`SessionId`と`InitializationVectorSuffix`を示す`CryptoHeader`が付きます。
	- **受信側でMACを検証することが出来る**


# MACを使ったメッセージ保護（受信側の処理）
前述の通り，データ保護と認証（完全性確認）を，受信側で確認するための`Authentication 「T」ag（MAC：Message Authentication Code）`を用いる．
DDS-Securityで使用される暗号電文には，いくつかの構造が存在するが，一番ベーシックなものは次の構造（`CryptoContent`構造体を使ったもの）を使用してやりとりする．

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/96992/a8a88dec-9406-8706-f240-1a9fb8812f95.png)
図 12 - RTPS メッセージ内の encode_serialized_payload() による変換 より

```
0...2...........8...............16.............24...............32
+---------------+---------------+---------------+---------------+
~     CryptoHeader                                              ~
+---------------+---------------+---------------+---------------+
~     CryptoContent                                             ~
|        crypto_content = Encrypt(SerializedPayload)            |
+---------------+---------------+---------------+---------------+
~     CryptoFooter                                              ~
+---------------+---------------+---------------+---------------+
```

各DDS実装は、サブメッセージの種類がDataおよびDataFragであるすべての発信「RTPSサブメッセ ージ」について、この暗号化（***encode_serialized_payload()***）を呼び出す。各DDS実装は、RTPSサブメッセージ内のSerializedPayloadサブメッセージエレメントを、生成されたCryptoContentで置換しなければならない。
CryptoContentを生成する際に，シリアライズされた本文に対して，「ヘッダ追加（`CryptoHeader`）」「フッタ追加（`CryptoFooter`）」及び「暗号化」が実施されている．この追加されたヘッダとフッタを用いて，MACの検証を行う．

`CryptoHeader`は次のような構造となっている．この中には，先述の`InitializationVector`を導出する際に利用したパラメータである「SessionId」及び「InitializationVectorSuffix」が含まれている．

```
0...2...........8...............16.............24...............32
+---------------+---------------+---------------+---------------+
+     CryptoHeader:                                             +
+             CryptoTransformIdentifier transformation_id       +
|             octet[4] transformation_id.transformation_kind    |
|             octet[4] transformation_id.transformation_key_id  |
+     - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
+    plugin_sec_prefix:                                         +
|             octet[4] plugin_sec_prefix.session_id             |
~             octet[8] plugin_sec_prefix.init_vector_suffix     ~
+---------------+---------------+---------------+---------------+
```

`CryptoFooter`は次のような構造となっている．この中には，先述の暗号化と同時に導出された「MAC」が`common_mac`という形で付与される．なお，この暗号化（encode_serialized_payload()）では`receiver_specific_macs`は使用しない．（1 つの受信機とのみ共有される受信機固有の鍵を使用する場合のみ使用する．）

```
0...2...........8...............16.............24...............32
+---------------+---------------+---------------+---------------+
+ CryptoFooter ( = plugin_sec_tag):                             +
~    octet[16] plugin_sec_tag.common_mac                        ~
+    - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  +
+    plugin_sec_tag.receiver_specific_macs:                     +
|       long plugin_sec_tag.receiver_specific_macs.length = N   |
|       - - - - - - - - - - - - - - - - - - - - - - - - - - -   |
|       octet[4] receiver_specific_macs[0].receiver_mac_key_id  |
|       octet[16] receiver_specific_macs[0].receiver_mac        ~
+       - - - - - - - - - - - - - - - - - - - - - - - - - - -   +
+       . . .                                                   +
+       - - - - - - - - - - - - - - - - - - - - - - - - - - -   +
|       octet[4] receiver_specific_macs[N-1].receiver_mac_key_id|
~       octet[16] receiver_specific_macs[N-1].receiver_mac      ~
+---------------+---------------+---------------+---------------+
```

要約すると，次の情報を使ってMACを計算して，たった今受信した`CryptoFooter.common_mac`との差異が無いことを確認している．

- たった今受信した：`SessionId`と`InitializationVectorSuffix`
- 認証時に取得した「共有秘密」：`transformation_id`で管理されているもの
	- SessionKey→MasterKey，MasterSalt，SessionIdから導出される


# おわりに
ざっと書きましたが，AESによる暗号化は熟れているので特別言及することが少なかったかもしれません．それよりも，「①認証」とそれに付随する「共有秘密」の確立，それらをやり取りするための組み込みトピックである「DCPSParticipantVolatileMessageSecure」の確立が肝になる感じがやんわり伝わったようであれば幸いです．
次は，認証について書きたい．．．せめて共有秘密確立まで．．．．


# お知らせ
SROS2のメリットを存分に活かすことが出来る，[OSS](https://github.com/rdbox-intec/rdbox/tree/insiders)を現在公開中です．
その詳細については，2022/10/19に実施される，[ROSConJP 2022](https://roscon.jp/#program)にてAM11:00より`「SROS2 with OIDC(OpenID Connect) :ロボットと人を安全に繋ぐ技術」`と題して発表予定です．乞うご期待．