QUICのRFCを説明する(InitialPacket暗号化編)

# 注意
現在記事を工事中です。記述がおかしかったりするかもしれませんがご了承下さい。

# はじめに
[前回](https://qiita.com/on-keyday/items/552ed005b7c5e239e0f1)からの続きです。前回の記事の内容を理解した上でお読みください。

# 3. Initial Packetの暗号化
`RFC9001 5.4.1. Header Protection Application`から図を引用する。
```
Initial Packet {
  Header Form (1) = 1,
  Fixed Bit (1) = 1,
  Long Packet Type (2) = 0,
  Reserved Bits (2),         # Protected
  Packet Number Length (2),  # Protected
  Version (32),
  DCID Len (8),
  Destination Connection ID (0..160),
  SCID Len (8),
  Source Connection ID (0..160),
  Token Length (i),
  Token (..),
  Length (i),
  Packet Number (8..32),     # Protected
  Protected Payload (0..24), # Skipped Part
  Protected Payload (128),   # Sampled Part
  Protected Payload (..)     # Remainder
}
```
`# Protected`と書かれている部分がヘッダ保護で暗号化される部分だ。
ヘッダ保護、並びに`Packet Payload`の暗号化の手順は以下の通りだ。

1. クライアントが最初に送信するInitialパケットの`Destination Connection ID`の値からinitial_secretを求める。
1. initial_secretからclient_initial_secret/server_initial_secretを求める
1. client_initial_secret/server_initial_secretからkey,hp,ivを生成する。
1. パケット番号とivをxorしてnonceを生成する。 
1. ヘッダとkeyとnonceを使用してペイロードを暗号化する
1. 暗号化したペイロードから16バイトをサンプリングする。
1. サンプリングしたペイロードとhpを使用してヘッダ保護のためのマスクを生成する。
1. `Reserved Bits`,`Packet Number Length`,`Packet Number`にヘッダ保護をかける。

順番に説明しよう。

#### 1. クライアントが最初に送信したInitialパケットの`Destination Connection ID`の値からinitial_secretを求める。
まず、これを説明する前に`HKDF-Extract`と`HKDF-Expand-label`という操作を説明する。

TLSでは鍵導出関数(KDF)というものが使われており、これを使って暗号化のための鍵が導出(作成)されている。
KDFの操作は２つあり一つは鍵の素(Input Key Material:IKM)から固定長の疑似乱数鍵(Pseduo-Random Key:PRK)を生成する`Extract`、
もう一つは生成されたPRKから更に別のPRKを生成する`Expand`である。

TLS1.3ではHMAC(Hash Based Message Authentication Code)を基礎としたKDFであるHKDFというものを使う。

そして上記の定義を使って説明すると、
`HKDF-Extract`はHKDFにおける`Extract`のことであり、
`HKDF-Expand-label`というのはHKDFにおける`Expand`(`HKDF-Expand`)に追加のパラメータを加えたものである(後述)

ここでは、HKDFの実装の詳細については触れない。
ぶっちゃけ筆者は人に説明できるほど詳しくないのでHKDFの中身のアルゴリズムやなぜそれで鍵が導出できるのかは他の文献をあたっていただきたい。

https://github.com/tsuyopon/memo/blob/master/TLS/Cipher/HKDF.md

[RFC5896 HKDF](https://tex2e.github.io/rfc-translater/html/rfc5869.html)

さて、ではinitial_secretの求め方について説明していく。
HKDF-Extractは次のような関数と考えることができる。
```
PRK = HKDF-Extract(salt,IKM,hash=SHA256)
```
`IKM`は先ほど説明した鍵の素であり、`salt`というのはより暗号学的に強い鍵をつくるために使われるものである。`hash`というのはHKDF-Extract/HKDF-Expandの内部で使われるハッシュ関数だが筆者は詳しくないので割愛する。とりあえずQUICバージョン1ではSHA256というものが使われる。
そしてこれの戻り値の`PRK`がinitial_secretにあたるものである。

InitialPacketのためのinitial_secretをつくるためには
`IKM`として`クライアントが最初に送信するInitialPacketのDestination Connection IDの値`、
`salt`としては[RFC9001](https://tex2e.github.io/rfc-translater/html/rfc9001.html)の`5.2 Initial Secrets`に指定される`38762cf7f55934b34d179ae6a4c80cadccbb7f0a`というバイト列を使用する。

ここで、`クライアントが最初に送信するInitialPacketのDestination Connection IDの値`について話をしよう。
コネクションIDについては軽く説明したのだが、実はクライアントが最初にInitialPacketを送信するときにはサーバーのコネクションIDはわからない。では`Destination Connection ID`にはなんの値が入るのかというと、RFCにはクライアントはランダムな8バイト以上の値を`Destination Connection ID`に設定しなければならないと書かれているため[^8]、ランダムな値が入っているのである。

#### 2. initial_secretからclient_initial_secret/server_initial_secretを生成する。
さて、initial_secretを生成したあとそこからclient_initial_secretとserver_initial_secretというものを生成しなければならない。
この後に生成するkey,hp,ivというものはこのclient_initial_secretとserver_initial_secretから導出される。

まず、HKDF-Expandは次のような関数として表せる。
```
PRK = HKDF-Expand(secret,info,hash=SHA256)
```
`secret`はもととなるPRK、`info`はオプショナルなアプリケーションの情報などである。
今回は`secret`に`initial_secret`を使う。
また`info`には<バイト,データの内容>で表すと
```
<2byte,出力長><1byte,6+ラベル長><6byte,"tls13 "><ラベル長byte,ラベル>
```
という形式のバイナリデータを入力する。

これは[RFC8446 7.1. Key Schedule](https://tex2e.github.io/rfc-translater/html/rfc8446.html#7-1--Key-Schedule)に書かれている`HkdfLabel`構造に等しい。

これを関数化すると
```
# 擬似コード
HKDF-Expand-label(outlen,secret,label,hash=SHA256):
  info = uint16(outlen).to_bytes() + 
         uint8(6+len(label)).to_bytes()+
         "tls13 ".to_bytes() +
         label.to_bytes()
  return HKDF-Expand(secret,info,hash)
```
というようにHKDF-Expand-labelというのが定義される。
`label`には導出する鍵ごとに定義されているラベル(文字列)を指定する。

さて、まずclient_initial_secretの導出をしよう。
client_initial_secretというのはクライアントが暗号化するときに使われるkey,hp,ivを導出するのに使われる。復号化するときも同じものを使う。
以下のように導出される。
```
client_initial_secret = HKDF-Expand-label(32,initial_secret,"client in")
```
そして、server_initial_secretはサーバーが暗号化するときに使われるkey,hp,ivを導出するのに使われる。以下のように導出される。
```
server_initial_secret = HKDF-Expand-label(32,initial_secret,"server in")
```
さて、これでclient_initial_secretとserver_initial_secretが導出できた。

#### 3. client_initial_secret/server_initial_secretからkey,hp,ivを生成する。
ここからはclientが暗号化するということで説明していく。
サーバー側を実装するには適宜client_initial_secretをserver_initial_secretに読み替えてほしい。

とはいっても、導出は先程定義した、HKDF-Expand-labelを使い、
```
key = HKDF-Expand-label(16,client_initial_secret,"quic key")
hp = HKDF-Expand-label(16,client_initial_secret,"quic hp")
iv = HKDF-Expand-label(12,client_initial_secret,"quic iv")
```
これで完了である。

#### 4. パケット番号とivをxorしてnonceを生成する。 
さてここでivについて説明しよう。
ivとはinitialization vectorの略で[AEAD](https://ja.wikipedia.org/wiki/%E8%AA%8D%E8%A8%BC%E4%BB%98%E3%81%8D%E6%9A%97%E5%8F%B7)(後述)の入力に使われる。これを使うことで得られる暗号文が毎回異なるものになり安全性が増すのである。[^9]
そして、ややこしいのだがQUICで実際にivとして渡されるのは上で生成した`iv`ではなく、パケット番号とと`iv`をxorした`nonce`である。

ここでパケット番号と`Packet Number`フィールドの値の関係について説明しておこう。
実は`Packet Number`フィールドの値はもとのパケット番号の値を[RFC9000 A.2 Sample Packet Number Encoding Algorithm](https://tex2e.github.io/rfc-translater/html/rfc9000.html#A-2--Sample-Packet-Number-Encoding-Algorithm)に示されるアルゴリズムでエンコードしたものである。
具体的には、もともとのパケット番号は62bit(つまり可変長整数表現で表現できる)でそれの上位バイト部分を切り捨てて1から4バイトに格納しているのである。
ここでxorするのはInitial Packetの`Packet Number`フィールドの値ではなく、もとの62bitの値の方なのでそこを間違えないようにしよう。

以下の擬似コードで説明する。
```
# ^はxor
# パケット番号を12byteに展開する
# 例えばもとが0x9293100001だったら
# [0,0,0,0,0,0,0,0x92,0x93,0x10,0x00,0x01]
# のように展開する
packet_number_bytes = packet_number.to_bytes(12)
nonce = iv
for i in range(12):
    nonce[i]^=packet_number_bytes[i]
```
これでnonceが生成できた。

#### 5. ヘッダとkeyとnonceを使用してペイロードを暗号化する
さあいよいよペイロードを暗号化する。
暗号化にはAEADというものを使う。
[RFC9001 5.3 AEAD Usage](https://tex2e.github.io/rfc-translater/html/rfc9001.html#5-3--AEAD-Usage)
に詳細が載っているがペイロードの暗号化では
+ `key`
+ `nonce`
+ ヘッダー(AAD)
+ 平文ペイロード(`Packet Payload`)

を入力にとり
+ 暗号ペイロード (`Protected Payload`)
+ 認証タグ(MAC)

を出力する。
InitialPacketの暗号化には`AEAD_AES_128_GCM`を使う。もう少し具体的に言うと、AEAD方式[AES](https://ja.wikipedia.org/wiki/Advanced_Encryption_Standard)アルゴリズムをブロック長さ128bitで[ガロアカウンターモード(GCM)](https://ja.wikipedia.org/wiki/Galois/Counter_Mode)で利用するというものである。
ちなみに筆者はこれを聞いてもなんのことだかさっぱりなので、
詳しいことは自分で調べてほしい。

https://4engineer.net/cipher/aes-gcm/

また、このとき渡されるヘッダーはまだヘッダー保護されていないことに注意しよう。
だいぶ端折るが、とりあえず利用するときには
```
protected_Payload,tag = AES-GCM(plain_payload,key,nonce,AAD=header)
```
というようなイメージである。

ここで前回述べた、InitialPacketの`Length`フィールドの話をしようと思う。
このペイロードの暗号化で平文と同じ長さの`protected_payload`と暗号化方式に応じた`tag`(TLS1.3の暗号化方式では16バイト[^11])が生成されるが、実際にパケットに格納するときには、`protected_payload`の後ろに`tag`をくっつけたものを格納する。
つまり`Length`フィールドの値は`Packet Number Length`+`平文ペイロードの長さ`+`tagの長さ`となるのである。
さらに、実はクライアントが送るInitialPacketは最低1200バイトの長さでなければならない[^10]のでそのためのパディングの長さを計算して、`Length`フィールドに足す必要もある。(パディングの中身は0x00である[^12])
暗号化する前の段階でこれらを考慮して、`Length`フィールドの値を決める必要があるのである。

ちなみに、これについては[RFC 9001 A.2 Client Initial](https://tex2e.github.io/rfc-translater/html/rfc9001.html#A-2--Client-Initial)に書いてある以下の記述をもとに考えられる。
```
The unprotected header indicates a length of 1182 bytes: 
the 4-byte packet number, 1162 bytes of frames, and the 16-byte
authentication tag. 
The header includes the connection ID and a packet number of 2:

   c300000001088394c8f03e5157080000449e00000002
                                   ^^^^   
```
ここで`^`で示した部分が`Length`フィールドにあたり、可変長整数としてデコードすると、0x49E=1182バイトとなり、`Packet Number`が4バイトと認証タグの16バイト、ペイロードの1162バイトを足した値となるというわけである。

#### 6. 暗号化したペイロードから16バイトをサンプリングする。
ヘッダー保護のために、暗号化したペイロードからサンプリングを行う。
なぜ、暗号化したペイロードを使うかというと、
暗号化したペイロードは
1. それ自体が乱数のようなものであり
1. 毎回内容が異なる

という特徴を持っていてnonceとして最適だからである。(下記記事等参照)

https://eng-blog.iij.ad.jp/archives/11018

```
Length (i),
Packet Number (8..32),     # Protected
Protected Payload (0..24), # Skipped Part
Protected Payload (128),   # Sampled Part
```
再びこの図を見てみよう。
`# Sampled Part`と書かれている部分がサンプリングされる部分である。
サンプリングされる部分は暗号化されたペイロードの先頭部分にあたる。
`# Skipped Part`は`Packet Number`フィールドと重ならないようにとられる
オフセットだ。
実のところ、Protected Payloadのサンプリングされる部分は`Length`フィールドから4バイトの位置のところと考えれば問題ない。
つまり下のようなイメージである。
```
PはPacket Numberフィールド
kは# Skipped Part
Sは# Sampled Part
LはLengthフィールド
それぞれ`Packet Number Length`1から4のパターン
         |>ここから16byte　
L|P|k|k|k|S|S|S|S|....
L|P|P|k|k|S|S|S|S|....
L|P|P|P|k|S|S|S|S|....
L|P|P|P|P|S|S|S|S|....
```
ちなみに前回ペイロードが20バイト以上なければならないと書いたのはこれが理由である。

#### 7. サンプリングしたペイロードとhpを使用してヘッダ保護のためのマスクを生成する。
先程サンプリングしたバイト列とhp(header protection key:ヘッダー保護鍵)を使用してマスクを生成する。
マスクの生成にはAES-ECN(128bit)を使う。AES-ECNとは大雑把に言うと先程出てきたAES-GCMの仲間である。くわしい説明は省くが、おおよそ以下のようなイメージである。
```
sample=サンプリングしたバイト列
mask=AES-GCM(hp,sample)
```
このmaskはsampleの長さと等しくなる。

#### 8. `Reserved Bits`,`Packet Number Length`,`Packet Number`にヘッダ保護をかける。
先程のmaskのうち最初の1byteをQUICヘッダの最初の1byteのマスクに使い、
2byte目から5byte目までを`Packet Number`フィールドのマスクに使う。
[RFC9001 5.4.1 Header Protection Application
](https://tex2e.github.io/rfc-translater/html/rfc9001.html#5-4-1--Header-Protection-Application)から引用する。
```
# 注:packetはパケットのバイナリ表現
pn_length = (packet[0] & 0x03) + 1
if (packet[0] & 0x80) == 0x80:
  # Long header: 4 bits masked
  packet[0] ^= mask[0] & 0x0f
else:
  # Short header: 5 bits masked
  packet[0] ^= mask[0] & 0x1f

# pn_offset is the start of the Packet Number field.
packet[pn_offset:pn_offset+pn_length] ^= mask[1:1+pn_length]
```
一番最後の行は`packet`の`pn_offset`から`pn_offset+pn_length`の範囲に`mask`の`1`から`1+pn_length`の値をxorマスクしていくという意味である。

さて、これでInitial Packetの暗号化は完了した。

# Initial Packetの暗号化...?
ここまでInitial Packetの暗号化について書いてきたが、実のところInitial Packetの暗号化というのは通信の保護という面では全くもって意味をなさないということを書いておく。
考えればわかることだが、そもそも手順1に示した通り、initial_secretは`Destination Connection ID`の値から生成され、さらには`Destination Connection ID`は暗号化されていないため、パケットを解析されれば簡単に解読できてしまうのである。

まあ、実際Initial Packetには重要な情報は入らないのでこれは心配する必要はない。

ここまでInitial Packetの暗号化について書いてきたが、基本的な暗号化手順は他の種類のパケットでも同じである。差異については別の記事で取り上げる。

# 次回予告

さて、とりあえず、暗号化はできた。次回は復号化の前にInitial Packetで使用するフレームについて説明する。

[^8]: [RFC9000 7.2. Negotiating Connection IDs](https://tex2e.github.io/rfc-translater/html/rfc9000.html#7-2--Negotiating-Connection-IDs)を参照して欲しい。
[^9]: https://it-trend.jp/encryption/article/64-0091 などで調べてほしい
[^10]: [RFC9000 8.1  Address Validation during Connection Establishment](https://tex2e.github.io/rfc-translater/html/rfc9000.html#8-1--Address-Validation-during-Connection-Establishment)を参照。なお、正確には「InitialPacketを含むUDPデータグラムのペイロードが1200バイト以上でなければならない」であるが説明を簡単にするためInitialPacketがと言っている。
[^11]: [RFC9001 5.3 AEAD Usage](https://tex2e.github.io/rfc-translater/html/rfc9001.html#5-3--AEAD-Usage)参照。