golangで作るTLS1.3プロトコル
はじめに
前回までの記事でTLS1.2プロトコルスタックを自作してみました。
ただ皆さんご存知の通り、TLS1.2の脆弱性の対策やQUICなど新しいプロトコルへの対応を考慮して設計したTLS1.3が2018年にリリースされ普及が進んでいます。
使用率ではまだTLS1.2が一般的ですが今後は1.3へと置き換えが進んでいくと、どこかの時点で逆転するのでしょう。そのときに慌てて学ぶよりも、今1.3も実装して学ぶことにします😊
まぁ1.2作れたしイケるでしょう(死亡フラグ😇😇😇)
今回の実装方針です。
-
crypto/tls
は一切使わずTLS1.3のフルハンドシェイクをオレオレで実装する - 使える部分は前回までのTLS1.2の実装を再利用する
- cipher suitesは
TLS_CHACHA20_POLY1305_SHA256
を使う - TLS1.3でハンドシェイクしてからnginxにHTTPリクエストを送りhtmlが返ってきたらOK
今回も基本RFCと参考文献、Wiresharkeでパケットを見ながら実装していきます。
- RFC 8446 - The Transport Layer Security (TLS) Protocol Version 1.3 日本語訳
- RFC 8448 - Example Handshake Traces for TLS 1.3 日本語訳
- プロフェッショナルSSL/TLS
- TLS暗号設定ガイドライン
実装を中心に説明していくので、一般的なTLS1.3の解説にはなっておりません。その辺は適時参考文献などで補ってください。
また都度ググって調べながら、実装してるので変なコードや説明、認識違いなどあると思いますが、ご指摘頂ければと。
コード自体は以下にあります。
はじめてのTLS1.3
とりあえず1.2と何が違うのよと、Wiresharkeでパケットを見て観察します。
1.2のときに使ったクライアントとサーバのコードの tls.Config
の MinVersion
と MaxVersion
を tls.VersionTLS13
にして実行します。
ClientHelloから始まりSeverHello, Certificate...が返ってくるのは1.2と同じですが、1.3では1往復したら直ちに暗号化が開始されます。
1.2では暗号化まで、2往復必要でした。ざっくり言うと1.2では1往復目で暗号化方式を合意して、2往復目でClientKeyExchangeが送られてからクライアントとサーバでApplicationDataの暗号と復号に使用する鍵が生成されます。
1.3では1往復目で暗号化方式の合意とTLS Extensionsに公開鍵をセットして交換することで、暗号化方式が決まれば直ちに暗号化を開始することができます。
暗号化方式が決まらないと、HelloRetryになるのですが、今回は合意されることを前提にするので実装&説明は割愛します。
ClientHelloとSeverHelloで公開鍵を交換してしまえばクライアントとサーバは同じ鍵を生成してお互いに暗号と復号ができることになるので、SeverHelloの次にChangeCipherSpecが送られます。
このためServerCertificateなど1.2では平文で送られていたハンドシェイクメッセージも暗号化されてクライアントに送られます。
また予めPSK(PreSharedKey)をクライアントが持っていれば1往復目から暗号化したApplicationDataをサーバに送れます。
1つ往復が減るのは通信速度や量といったところでメリットでかいですよね(小並感)
次に異なるのが鍵の生成方法です。1.2ではPRFという関数で鍵を生成していたのですが、1.3ではHKDFというHMACを利用した関数になります。
HKDFの処理自体は hkdfパッケージの Expand
と Extract
をサンプルのまま使えば鍵を作れるのですが面倒くさいのが作らないといけない鍵の数です。
1.3ではクライアントとサーバのハンドシェイク暗号化用、クライアントとサーバのアプリ暗号化用と用途と使う側に応じてそれぞれ鍵を生成していく必要があります。
1.2ではアプリケーションデータの暗号用の鍵をクライアントとサーバを合計2つ作るだけでしたが、1.3ではハンドシェイクを行うために最低でも4つ作る必要があります。
4つ作るためにまずEarly Secretを作ったら次にHandshake Secretを作って...と芋づる式に作成していく必要があります。
1.3の鍵作成方法は次で詳しく説明します。
ひとまず1.2の実装に追加して必要なのは
- ServerHelloのTLS Extensionsで共通鍵を送るように拡張する
- HKDFで鍵を生成する
となりますかね🤔
ServerHelloとかClientHello, Certificateメッセージは1.2と互換性を保っているので、メッセージ作ったり証明書を検証したりなど1.2の実装で作った部分はそのまま使えます。
面倒くさいのが鍵生成の処理ですね😫😫😫
TLS1.3の鍵生成の流れ
クライアントとサーバの鍵を生成していく流れ(鍵スケジュール)は、RFC8446の7.1. Key Scheduleに記載されています。
関数の定義はいいとして、最初に見て全く意味がわからないのが、将軍家の家系図みたいな↓これです。
以下で↑の家系図を説明していきます。
- Early Secretの作成
まず最初にEarly Secretを作らないと何も始まりません。
ここの箇所です。
0
|
v
PSK -> HKDF-Extract = Early Secret
↑の見かたですが、HKDF-Extractは、上部からSalt引数を、左側からIKM引数を取り、その出力を下部に、出力の名前を右側に取るように描画されます。
と書かれています。
つまり、PSKと0を引数にしてHKDF-Extract関数を呼ぶと、Early Secretが生成されます。初めてハンドシェイクする時はPSK(Presharedkey)はないので、0を入れます。
コードっぽく書くとこんな感じでしょうか。実際のコードは後で試してみます。
early_secret = HKDF-Extract(0,0)
- Derive Secretの作成
次にDerive Secretを作ります。家系図を下に進んで、ここです。
v
Derive-Secret(., "derived", "")
Derive-Secret関数の第一引数、"." になっている部分には "v"の矢印の方向、この場合上から来るのが入ります。ここでは最初に生成されたEarly Secretです。
つまりコードっぽく書くとこんな感じです。
derive_secret = Derive-Secret(early_secret, "derived", "")
- DHE共通鍵と2.で作成したderive_secretを引数にHandshake Secretを作る
(EC)DHE -> HKDF-Extract = Handshake Secret
↑はこうです。↓
handshake_secret = HKDF-Extract(dhe, derive_secret)
DHEは鍵交換で生成された共通のものです。
クライアント側だと、クライアントの秘密鍵とServerHelloのTLS Extensionsで受け取った公開鍵から計算した値です。
-
handshake_secretからderive_secretを作る
-
と同じ処理を3.で作成したhandshake_secretを引数で行います。
derive_secret = Derive-Secret(handshake_secret, "derived", "")
- master secretを作る
4.で作成したderive_secretを引数にmaster secretを作成します。
0 -> HKDF-Extract = Master Secret
↑はこういう意味です↓
master_secret = HKDF-Extract(0, derive_secret)
こうして、Early Secret → Handshake Secret → Master Secret を作成しました。家康→秀忠→家光みたいな感じでしょうかw(違う)
とりあえず上から下へ説明しましたが、実際には右横に伸びてるDerive-Secret関数の処理も必要です、↓こういうの。
(EC)DHE -> HKDF-Extract = Handshake Secret
|
ここ → +-----> Derive-Secret(., "c hs traffic",
| ClientHello...ServerHello)
| = client_handshake_traffic_secret
↑はこういう意味です。↓
client_handshake_traffic_secret = Derive-Secret(handshake_secret, "c hs traffic", ClientHello+ServerHelloメッセージ)
Early Secret → Handshake Secret → Master Secretを作ったら、横に伸びてるDerive-Secret関数を呼んで、ハンドシェイクに必要な以下4つを生成します。
- client_handshake_traffic_secret
- server_handshake_traffic_secret
- client_application_traffic_secret_0
- server_application_traffic_secret_0
↑の4つ以外はPSKで使ったりするやつですかね、ちょっとまだよくわかってません汗
鍵スケジュールで気をつけないといけないのが、生成するタイミングです。
handshake_traffic_secretはClientHello+ServerHelloメッセージを受信したら生成できるのですが、application_traffic_secretはServerのFinishedメッセージまでを含む必要があります。
つまり以下のように2回に分けます。
- Early SecretからMaster Secretまでと、handshake_traffic_secretを作成する
ClientHello→
←ServerHello
-
1で作成したserver_handshake_traffic_secretで以下4つのメッセージを復号する。
復号したらapplication_traffic_secretを作成する←EncryptedExtensions
Certificate
CertificateVerify
Finished
tlsパッケージだと、クライアントはこの辺, サーバはこの辺で鍵スケジュールの処理をしていますね。
TLS1.3の鍵生成の処理
鍵スケジュールの流れがわかったら処理を書いてみます。
RFCで関数は以下のように書かれています。
HKDF-Expand-Label(Secret, Label, Context, Length) =
HKDF-Expand(Secret, HkdfLabel, Length)
struct {
uint16 length = Length;
opaque label<7..255> = "tls13 " + Label;
opaque context<0..255> = Context;
} HkdfLabel;
Derive-Secret(Secret, Label, Messages) =
HKDF-Expand-Label(Secret, Label,
Transcript-Hash(Messages), Hash.length)
Derive-SecretはHKDF-Expand-Labelのラッパー関数で、HKDF-Expand-LabelはHKDF-Expandのラッパー関数となっています。
goで同じように書いてみました。
func hkdfExpand(secret, hkdflabel []byte, length int) []byte {
hash := sha256.New
expand := hkdf.Expand(hash, secret, hkdflabel)
b := make([]byte, length)
io.ReadFull(expand, b)
return b
}
func hkdfExpandLabel(secret, label, ctx []byte, length int) []byte {
// labelを作成
tlslabel := []byte(`tls13 `)
tlslabel = append(tlslabel, label...)
// lengthをセット
hkdflabel := uintTo2byte(uint16(length))
hkdflabel = append(hkdflabel, byte(len(tlslabel)))
hkdflabel = append(hkdflabel, tlslabel...)
hkdflabel = append(hkdflabel, byte(len(ctx)))
hkdflabel = append(hkdflabel, ctx...)
return hkdfExpand(secret, hkdflabel, length)
}
func deriveSecret(secret, label, messages_byte []byte) []byte {
return hkdfExpandLabel(secret, label, messages_byte, 32)
}
extractはこんな形に
func hkdfExtract(secret, salt []byte) []byte {
hash := sha256.New
return hkdf.Extract(hash, secret, salt)
}
goのtlsパッケージでは、src/crypto/tls/key_schedule.goにhkdfの処理があります。僕の実装より、そっちのほうがはるかに読みやすく正しいですw
↓が実装した処理でMaster Secretとhandshake_traffic_secretと鍵を作る関数です。
handshake_traffic_secretが出来たらlabelが key
と iv
で Expand-Label関数を呼ぶとkeyとivがそれぞれできます。
7.3. Traffic Key Calculationに書かれているところです。
トラフィックキーイングマテリアルは、次を使用して入力トラフィックシークレット値から生成されます。
[sender]_write_key = HKDF-Expand-Label(Secret, "key", "", key_length)
[sender]_write_iv = HKDF-Expand-Label(Secret, "iv", "", iv_length)
[送信者]は送信側を示します。 各レコードタイプのSecretの値を次の表に示します。
+-------------------+---------------------------------------+
| Record Type | Secret |
+-------------------+---------------------------------------+
| 0-RTT Application | client_early_traffic_secret |
| | |
| Handshake | [sender]_handshake_traffic_secret |
| | |
| Application Data | [sender]_application_traffic_secret_N |
+-------------------+---------------------------------------+
生成したクライアントのkeyとivはFinishedMessageを送るときの暗号化に使います。
サーバのkeyとivは、ServerHello,ChangeCipherSpec以後の暗号化されたハンドシェイクメッセージの復号に使います。
func keyscheduleToMasterSecret(sharedkey, handshake_messages []byte) KeyBlockTLS13 {
zero := noRandomByte(32)
zerohash := writeHash(nil)
// 0からearly secretを作成する
earlySecret := hkdfExtract(zero, zero)
// {client} derive secret for handshake "tls13 derived"
derivedSecretForhs := deriveSecret(earlySecret, DerivedLabel, zerohash)
fmt.Printf("derivedSecretForhs %x\n", derivedSecretForhs)
// {client} extract secret "handshake":
handshake_secret := hkdfExtract(sharedkey, derivedSecretForhs)
fmt.Printf("handshake_secret is : %x\n", handshake_secret)
hash_messages := writeHash(handshake_messages)
fmt.Printf("hashed messages is %x\n", hash_messages)
// {client} derive secret "tls13 c hs traffic":
chstraffic := deriveSecret(handshake_secret, ClienthsTraffic, hash_messages)
fmt.Printf("CLIENT_HANDSHAKE_TRAFFIC_SECRET %x %x\n", zero, chstraffic)
// Finished message用のキー
clientfinkey := deriveSecret(chstraffic, FinishedLabel, nil)
//fmt.Printf("clientfinkey is : %x\n", clientfinkey)
// {client} derive secret "tls13 s hs traffic":
shstraffic := deriveSecret(handshake_secret, ServerhsTraffic, hash_messages)
fmt.Printf("SERVER_HANDSHAKE_TRAFFIC_SECRET %x %x\n", zero, shstraffic)
// Finished message用のキー
serverfinkey := deriveSecret(shstraffic, FinishedLabel, nil)
//fmt.Printf("serverfinkey is : %x\n", serverfinkey)
derivedSecretFormaster := deriveSecret(handshake_secret, DerivedLabel, zerohash)
fmt.Printf("derivedSecretFormaster is : %x\n", derivedSecretFormaster)
extractSecretMaster := hkdfExtract(zero, derivedSecretFormaster)
fmt.Printf("extractSecretMaster is : %x\n", extractSecretMaster)
// {client} derive write traffic keys for handshake data from server hs traffic:
// 7.3. トラフィックキーの計算
clienttraffickey := hkdfExpandLabel(chstraffic, []byte(`key`), nil, 32)
fmt.Printf("client traffic key is : %x\n", clienttraffickey)
clienttrafficiv := hkdfExpandLabel(chstraffic, []byte(`iv`), nil, 12)
fmt.Printf("client traffic iv is : %x\n", clienttrafficiv)
servertraffickey := hkdfExpandLabel(shstraffic, []byte(`key`), nil, 32)
fmt.Printf("server traffic key is : %x\n", servertraffickey)
servertrafficiv := hkdfExpandLabel(shstraffic, []byte(`iv`), nil, 12)
fmt.Printf("server traffic iv is : %x\n", servertrafficiv)
return KeyBlockTLS13{
handshakeSecret: handshake_secret,
clientHandshakeSecret: chstraffic,
clientHandshakeKey: clienttraffickey,
clientHandshakeIV: clienttrafficiv,
clientFinishedKey: clientfinkey,
serverHandshakeSecret: shstraffic,
serverHandshakeKey: servertraffickey,
serverHandshakeIV: servertrafficiv,
serverFinishedKey: serverfinkey,
masterSecret: extractSecretMaster,
}
}
keyscheduleToMasterSecret関数をテストしてみましょう。
RFC8448はTLS1.3のハンドシェイクのサンプルが書かれています。
これと同じ計算結果が出力されれば、実装は正しいといえます。
例として記載されている、クライアントの秘密鍵、サーバの公開鍵、ClientHello、ServerHelloメッセージをコピペします。
stringをbyteに変換して鍵スケジュールの関数呼びます。
func main() {
// private key (32 octets): 49 af 42 ba 7f 79 94 85 2d 71 3e f2 78 4b cb ca a7 91 1d e2 6a dc 56 42 cb 63 45 40 e7 ea 50 05
clientPrivateKey, _ := hex.DecodeString("49af42ba7f7994852d713ef2784bcbcaa7911de26adc5642cb634540e7ea5005")
// public key (32 octets): c9 82 88 76 11 20 95 fe 66 76 2b db f7 c6 72 e1 56 d6 cc 25 3b 83 3d f1 dd 69 b1 b0 4e 75 1f 0f
serverPublickKey, _ := hex.DecodeString("c9828876112095fe66762bdbf7c672e156d6cc253b833df1dd69b1b04e751f0f")
// {client} construct a ClientHello handshake message:
clienthello := "010000c00303cb34ecb1e78163ba1c38c6dacb196a6dffa21a8d9912ec18a2ef6283024dece7000006130113031302010000910000000b0009000006736572766572ff01000100000a00140012001d0017001800190100010101020103010400230000003300260024001d002099381de560e4bd43d23d8e435a7dbafeb3c06e51c13cae4d5413691e529aaf2c002b0003020304000d0020001e040305030603020308040805080604010501060102010402050206020202002d00020101001c00024001"
// {server} construct a ServerHello handshake message:
sererhello := "020000560303a6af06a4121860dc5e6e60249cd34c95930c8ac5cb1434dac155772ed3e2692800130100002e00330024001d0020c9828876112095fe66762bdbf7c672e156d6cc253b833df1dd69b1b04e751f0f002b00020304"
clientserverhello, _ := hex.DecodeString(clienthello + sererhello)
sharedkey, _ := curve25519.X25519(clientPrivateKey, serverPublickKey)
keyscheduleToMasterSecret(sharedkey, clientserverhello)
}
実行すると以下のように出力しました。
$ go run .
derivedSecretForhs 6f2615a108c702c5678f54fc9dbab69716c076189c48250cebeac3576c3611ba
handshake_secret is : 1dc826e93606aa6fdc0aadc12f741b01046aa6b99f691ed221a9f0ca043fbeac
hashed messages is 860c06edc07858ee8e78f0e7428c58edd6b43f2ca3e6e95f02ed063cf0e1cad8
CLIENT_HANDSHAKE_TRAFFIC_SECRET 0000000000000000000000000000000000000000000000000000000000000000 b3eddb126e067f35a780b3abf45e2d8f3b1a950738f52e9600746a0e27a55a21
SERVER_HANDSHAKE_TRAFFIC_SECRET 0000000000000000000000000000000000000000000000000000000000000000 b67b7d690cc16c4e75e54213cb2d37b4e9c912bcded9105d42befd59d391ad38
serverfinkey is : 008d3b66f816ea559f96b537e885c31fc068bf492c652f01f288a1d8cdc19fc8
derivedSecretFormaster is : 43de77e0c77713859a944db9db2590b53190a65b3ee2e4f12dd7a0bb7ce254b4
extractSecretMaster is : 18df06843d13a08bf2a449844c5f8a478001bc4d4c627984d5a41da8d0402919
client traffic key is : 73bfffe9212112f34b54106f2be9617a394d95c8f360452bd4ef2be66b9d8392
client traffic iv is : 5bd3c71b836e0b76bb73265f
server traffic key is : ac70443f7fe3bdaf568b1dcdb0a7f3fea098bca189c3455ba41fcd9d488348a4
server traffic iv is : 5d313eb2671276ee13000b30
左が出力、右がRFC8448の例です。
derivedSecretForhs 6f2615a108c702c5678f54fc9dbab69716c076189c48250cebeac3576c3611ba
→ expanded (32 octets): 6f 26 15 a1 08 c7 02 c5 67 8f 54 fc 9d ba b6 97 16 c0 76 18 9c 48 25 0c eb ea c3 57 6c 36 11 ba
handshake_secret is : 1dc826e93606aa6fdc0aadc12f741b01046aa6b99f691ed221a9f0ca043fbeac
→ secret (32 octets): 1d c8 26 e9 36 06 aa 6f dc 0a ad c1 2f 74 1b 01 04 6a a6 b9 9f 69 1e d2 21 a9 f0 ca 04 3f be ac
b3eddb126e067f35a780b3abf45e2d8f3b1a950738f52e9600746a0e27a55a21
→ expanded (32 octets): b3 ed db 12 6e 06 7f 35 a7 80 b3 ab f4 5e 2d 8f 3b 1a 95 07 38 f5 2e 96 00 74 6a 0e 27 a5 5a 21
b67b7d690cc16c4e75e54213cb2d37b4e9c912bcded9105d42befd59d391ad38
→ expanded (32 octets): b6 7b 7d 69 0c c1 6c 4e 75 e5 42 13 cb 2d 37 b4 e9 c9 12 bc de d9 10 5d 42 be fd 59 d3 91 ad 38
derivedSecretFormaster is : 43de77e0c77713859a944db9db2590b53190a65b3ee2e4f12dd7a0bb7ce254b4
→ expanded (32 octets): 43 de 77 e0 c7 77 13 85 9a 94 4d b9 db 25 90 b5 31 90 a6 5b 3e e2 e4 f1 2d d7 a0 bb 7c e2 54 b4
extractSecretMaster is : 18df06843d13a08bf2a449844c5f8a478001bc4d4c627984d5a41da8d0402919
→ secret (32 octets): 18 df 06 84 3d 13 a0 8b f2 a4 49 84 4c 5f 8a 47 80 01 bc 4d 4c 62 79 84 d5 a4 1d a8 d0 40 29 19
↑という風に鍵スケジュールの計算結果が一致しているのがわかるので、これで大丈夫そうです。
さて途中で出てくるFinished message用のキーって何でしょうか?鍵スケジュールの家系図にはなかったですよね。
// Finished message用のキー
clientfinkey := deriveSecret(chstraffic, FinishedLabel, nil)
// Finished message用のキー
serverfinkey := deriveSecret(shstraffic, FinishedLabel, nil)
Finished messageはこれまでやり取りしたメッセージが改ざんされてないことを確認するための verify_data
を含んだメッセージです。
1.2と1.3では生成方法が変わりました。
- 1.2
7.4.9. Finished
verify_data
PRF(master_secret, finished_label, Hash(handshake_messages))
[0..verify_data_length-1];
- 1.3
4.4.4. Finished
verify_data =
HMAC(finished_key,
Transcript-Hash(Handshake Context,
Certificate*, CertificateVerify*))
1.2ではPRF関数を使用していましたが、1.3ではHMAC関数で署名をするようになりました。この署名で使われるのがFinished message用のキーです。
Finishedメッセージの計算に使用されるキーは、HKDFを使用してセクション4.4で定義されたベースキーから計算されます(セクション7.1を参照)。 具体的には:
finished_key =
HKDF-Expand-Label(BaseKey, "finished", "", Hash.length)
ベースキーとは[sender]_handshake_traffic_secretのことを指しています。
はー、マジで1.3面倒くせぇ...🫠🫠🫠
追加の処理
ここからは追加で実装した処理を説明していきます。
ClientHelloの作成
まずClientHelloを作る部分です、1.3だったら1.3用のTLS Extensionsをセットするように分岐させます。
if bytes.Equal(tlsversion, TLS1_2) {
handshake.Extensions = setTLSExtenstions()
tlsinfo.MasterSecretInfo.ClientRandom = handshake.Random
} else {
// TLS1.3のextensionをセット
handshake.Extensions, tlsinfo.ECDHEKeys = setTLS13Extension()
}
setTLS13Extension
ではパケットキャプチャしたのをそのままセットしてるだけなのですがw, key_share
で共有鍵をセットしています。
func setTLS13Extension() ([]byte, ECDHEKeys) {
var tlsExtension []byte
// set length
tlsExtension = append(tlsExtension, []byte{0x00, 0x6F}...)
// status_reqeust
tlsExtension = append(tlsExtension, []byte{0x00, 0x05, 0x00, 0x05, 0x01, 0x00, 0x00, 0x00, 0x00}...)
// supported_groups
tlsExtension = append(tlsExtension, []byte{0x00, 0x0a, 0x00, 0x04, 0x00, 0x02, 0x00, 0x1d}...)
// ec_point_formats
tlsExtension = append(tlsExtension, []byte{0x00, 0x0b, 0x00, 0x02, 0x01, 0x00}...)
// signature_algorithms
tlsExtension = append(tlsExtension, []byte{
0x00, 0x0d, 0x00, 0x1a, 0x00, 0x18, 0x08, 0x04,
0x04, 0x03, 0x08, 0x07, 0x08, 0x05, 0x08, 0x06,
0x04, 0x01, 0x05, 0x01, 0x06, 0x01, 0x05, 0x03,
0x06, 0x03, 0x02, 0x01, 0x02, 0x03,
}...)
// renagotiation_info
tlsExtension = append(tlsExtension, []byte{0xff, 0x01, 0x00, 0x01, 0x00}...)
// signed_certificate_timestamp
tlsExtension = append(tlsExtension, []byte{0x00, 0x12, 0x00, 0x00}...)
// supported_versions
tlsExtension = append(tlsExtension, []byte{0x00, 0x2b, 0x00, 0x03, 0x02, 0x03, 0x04}...)
// 公開鍵を生成する
clientkey := genrateClientECDHEKey()
// key_share, DHEの公開鍵を送る
tlsExtension = append(tlsExtension, []byte{0x00, 0x33, 0x00, 0x26, 0x00, 0x24}...)
tlsExtension = append(tlsExtension, uintTo2byte(uint16(tls.X25519))...)
// keyのLength = 32byte
tlsExtension = append(tlsExtension, []byte{0x00, 0x20}...)
// 公開鍵を追加
tlsExtension = append(tlsExtension, clientkey.publicKey...)
return tlsExtension, clientkey
}
鍵の生成はcurve25519の楕円曲線を使ってます。
debug用に秘密鍵は0にしています。
func genrateClientECDHEKey() ECDHEKeys {
// 秘密鍵となる32byteの乱数をセット
//clientPrivateKey := randomByte(curve25519.ScalarSize)
clientPrivateKey := noRandomByte(32)
// ClientKeyExchangeでサーバに送る公開鍵を生成
clientPublicKey, _ := curve25519.X25519(clientPrivateKey, curve25519.Basepoint)
return ECDHEKeys{
privateKey: clientPrivateKey,
publicKey: clientPublicKey,
}
}
ServerからのHandshakeメッセージのパース
Handshakeメッセージをパースする部分です。
1.3のServerHelloをパースするように1.2と分けています。
サーバのDHE公開鍵を拾わないといけないからですね。
else {
hello := ServerHello{
HandshakeType: packet[0:1],
Length: packet[1:4],
Version: packet[4:6],
Random: packet[6:38],
SessionIDLength: packet[38:39],
SessionID: packet[39:71],
CipherSuites: packet[71:73],
CompressionMethod: packet[73:74],
ExtensionLength: packet[74:76],
}
// supported_versions
hello.TLSExtensions = append(hello.TLSExtensions, TLSExtensions{
Type: packet[76:78],
Length: packet[78:80],
Value: packet[80:82],
})
//key_share
hello.TLSExtensions = append(hello.TLSExtensions, TLSExtensions{
Type: packet[82:84],
Length: packet[84:86],
Value: map[string]interface{}{
"Group": packet[86:88],
"KeyExchangeLength": packet[88:90],
"KeyExchange": packet[90:122],
},
})
i = hello
}
パースする4つのハンドシェイクメッセージのCaseを追加しました。
// TLS1.3用に追加
case HandshakeTypeEncryptedExtensions:
i = EncryptedExtensions{
HandshakeType: packet[0:1],
Length: packet[1:4],
ExtensionLength: packet[4:6],
}
fmt.Printf("EncryptedExtensions : %+v\n", i)
// TLS1.3用に追加
case HandshakeTypeCertificateVerify:
i = CertificateVerify{
HandshakeType: packet[0:1],
Length: packet[1:4],
SignatureHashAlgorithms: packet[4:6],
SignatureLength: packet[6:8],
Signature: packet[8:],
}
fmt.Printf("CertificateVerify : %+v\n", i)
// TLS1.3用に追加
case HandshakeTypeFinished:
i = FinishedMessage{
HandshakeType: packet[0:1],
Length: packet[1:4],
VerifyData: packet[4:],
}
fmt.Printf("FinishedMessage : %+v\n", i)
// TLS1.3用に追加
case HandshakeTypeNewSessionTicket:
ticketLength := binary.BigEndian.Uint16(packet[21:23]) + 23
i = SessionTicket{
HandshakeType: packet[0:1],
Length: packet[1:4],
TicketLifeTime: packet[4:8],
TicketAgeAdd: packet[8:12],
TicketNonceLength: packet[12:13],
TicketNonce: packet[13:21],
TicketLength: packet[21:23],
Ticket: packet[23:ticketLength],
TicketExtensions: packet[ticketLength:],
}
fmt.Printf("SessionTicket : %+v\n", i)
}
CertificateVerifyの対応
ServerCertificateの次にCertificateVerifyが送られます。
サーバはClientHello〜ServerCertificateまでのメッセージを秘密鍵で署名して送ってきます。
ServerCertificateに入っている証明書の公開鍵で署名を検証できれば、正しく秘密鍵を保持していることが証明されます。
クライアント認証で送っていたのと目的は同じですね。
ただメッセージの構造が変わりました。1.2だとその直前までのHandshakeメッセージのハッシュしたものだったのですが↓のようになりました。
4.4.3. Certificate Verify
デジタル署名は、次の連結を介して計算されます。
- 64回繰り返されるオクテット32(0x20)で構成される文字列
- コンテキスト文字列
- セパレータとして機能する単一の0バイト
- 署名するコンテンツ
こうなればいいらしいです。
The context string for a server signature is "TLS 1.3, server CertificateVerify". The context string for a client signature is "TLS 1.3, client CertificateVerify". It is used to provide separation between signatures made in different contexts, helping against potential cross-protocol attacks.
For example, if the transcript hash was 32 bytes of 01 (this length would make sense for SHA-256), the content covered by the digital signature for a server CertificateVerify would be:
2020202020202020202020202020202020202020202020202020202020202020
2020202020202020202020202020202020202020202020202020202020202020
544c5320312e332c20736572766572204365727469666963617465566572696679
00
0101010101010101010101010101010101010101010101010101010101010101
なので、Handshakeメッセージのハッシュ値の前に↓の3つを入れています。
- 64回繰り返されるオクテット32(0x20)で構成される文字列
- コンテキスト文字列 = "TLS 1.3, server CertificateVerify"
- セパレータとして機能する単一の0バイト
func verifyServerCertificate(pubkey *rsa.PublicKey, signature, handshake_messages []byte) {
hash_messages := writeHash(handshake_messages)
hasher := sha256.New()
// 64回繰り返されるオクテット32(0x20)で構成される文字列
hasher.Write(strtoByte(str0x20x64))
// コンテキスト文字列 = "TLS 1.3, server CertificateVerify"
hasher.Write(serverCertificateContextString)
// セパレータとして機能する単一の0バイト
hasher.Write([]byte{0x00})
hasher.Write(hash_messages)
signed := hasher.Sum(nil)
fmt.Printf("hash_messages is %x, signed is %x\n", hash_messages, signed)
signOpts := &rsa.PSSOptions{SaltLength: rsa.PSSSaltLengthEqualsHash}
err := rsa.VerifyPSS(pubkey, crypto.SHA256, signed, signature, signOpts)
if err != nil {
log.Fatal(err)
}
fmt.Println("Server Certificate Verify is OK !!")
}
メッセージの暗号化
chacha20poly1305を使います。
サンプルにあるように、使用するHandshake用かApplicationData用かの鍵で New
→ Seal
を呼んで、5byteのレコードヘッダ後ろに暗号化された文字列をくっつけます。
5.3. Per-Record Nonceに 2.埋め込まれたシーケンス番号は、静的なclient_write_ivまたはserver_write_iv(役割に応じて)とXORされます。
と書いているので、
ivとシーケンス番号をxorした12byteがSealで使うnonceになります。
tlsパッケージでは暗号と復号はこの辺でやってます(AEADの場合)
SealとOpenの前後にxorの演算が挟まんでますね。
func encryptChacha20(message []byte, tlsinfo TLSInfo) []byte {
var key, iv, nonce []byte
// Finishedメッセージを送るとき
if tlsinfo.State == ContentTypeHandShake {
key = tlsinfo.KeyBlockTLS13.clientHandshakeKey
iv = tlsinfo.KeyBlockTLS13.clientHandshakeIV
nonce = getNonce(tlsinfo.ClientHandshakeSeq, 8)
} else {
// Application Dataを送る時
key = tlsinfo.KeyBlockTLS13.clientAppKey
iv = tlsinfo.KeyBlockTLS13.clientAppIV
nonce = getNonce(tlsinfo.ClientAppSeq, 8)
}
fmt.Printf("key is %x, iv is %x\n", key, iv)
aead, err := chacha20poly1305.New(key)
if err != nil {
log.Fatal(err)
}
// ivとnonceをxorのbit演算をする
// 5.3. レコードごとのノンス
// 2.埋め込まれたシーケンス番号は、静的なclient_write_ivまたはserver_write_iv(役割に応じて)とXORされます。
xornonce := getXORNonce(nonce, iv)
header := strtoByte("170303")
// 平文→暗号化したときのOverHeadを足す
totalLength := len(message) + 16
header = append(header, uintTo2byte(uint16(totalLength))...)
fmt.Printf("encrypt now nonce is %x xornonce is %x, plaintext is %x, add is %x\n", nonce, xornonce, message, header)
ciphertext := aead.Seal(header, xornonce, message, header)
return ciphertext
}
復号は使用するkeyとivをServerのものにしてaead.Seal
が Open
になるだけなので省略します。
オレオレTLS1.3 with オレオレHTTP、その結末は
ひと通り材料が揃ったので、TLS1.3のnginxにHTTPリクエストを送ってみます。
nginx.confはこんな感じです。
events{
}
http{
server {
server_name localhost;
listen 443 ssl;
ssl_certificate /etc/certs/my-tls.pem;
ssl_certificate_key /etc/certs/my-tls-key.pem;
ssl_protocols TLSV1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
ssl_ciphers AES128-GCM-SHA256,ECDHE-RSA-AES128-GCM-SHA256,ECDHE-RSA-CHACHA20-POLY1305;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
}
}
}
mainの処理を上からなぞっていきます。
まずClientHelloメッセージを作成して送信します。サーバからServerHello〜Finishedまで受信します。
func main() {
sock := NewSockStreemSocket()
addr := setSockAddrInet4(iptobyte(LOCALIP), LOCALPORT)
err := syscall.Connect(sock, &addr)
if err != nil {
log.Fatalf("connect err : %v\n", err)
}
var hello ClientHello
// ClientHelloメッセージを作成
tlsinfo, hellobyte := hello.NewClientHello(TLS1_3)
// メッセージを送信
syscall.Write(sock, hellobyte)
var packet []byte
// ServerHello, ChangeCipherSpec, EncryptedExtensions, Certificate, CertificateVerify, Finishedを受信する
for {
recvBuf := make([]byte, 2000)
n, _, err := syscall.Recvfrom(sock, recvBuf, 0)
if err != nil {
log.Fatalf("read err : %v", err)
}
packet = recvBuf[0:n]
break
}
最初のServerHelloメッセージをパースして、TLS Extensionsの中から公開鍵を取り出します。
取り出したサーバの公開鍵とクライアントの秘密鍵から共通鍵を生成します。
共通鍵を作成したらMasterSecretまで鍵スケジュールを行い、Handshake用のSecret、Keyを作成します。
// read ServerHello
length := binary.BigEndian.Uint16(packet[3:5]) + 5
serverhello := parseTLSHandshake(packet[5:length], TLS1_3).(ServerHello)
serverkeyshare := serverhello.TLSExtensions[1].Value.(map[string]interface{})["KeyExchange"]
// Serverhelloをmessageに入れておく
tlsinfo.Handshakemessages = append(tlsinfo.Handshakemessages, packet[5:length]...)
tlsinfo.State = ContentTypeHandShake
fmt.Printf("server key share is %x\n", serverkeyshare.([]byte))
//クライアントの秘密鍵とサーバの公開鍵で共通鍵を生成する
sharedkey, _ := curve25519.X25519(tlsinfo.ECDHEKeys.privateKey, serverkeyshare.([]byte))
fmt.Printf("sharedkey is %x\n", sharedkey)
tlsinfo.KeyBlockTLS13 = keyscheduleToMasterSecret(sharedkey, tlsinfo.Handshakemessages)
ChangeCipherSpecを読み取ってから、暗号化されているメッセージを↑で作ったておいたサーバのkeyで復号します。
復号したらHandshakeメッセージになるので、それぞれパースします。
ServerCertificateから公開鍵を取り出したら、次のCertificateVerifyで署名の検証をします。
FinishedMessageでは作成しておいたFinishedKeyとこれまでのHandshakeメッセージのハッシュ値で検証します。
検証結果が一致すれば、次の処理に進みます。
copy(packet, packet[length:])
// read ChangeCipherSpec
changecipherspec := packet[0:6]
fmt.Printf("read ChangeCipherSpec is %x, これから暗号化やでー\n", changecipherspec)
copy(packet, packet[6:])
hanshake := bytes.Split(packet, []byte{0x17, 0x03, 0x03})
var pubkey *rsa.PublicKey
exit_loop:
for _, v := range hanshake {
if len(v) != 0 {
v = append([]byte{0x17, 0x03, 0x03}, v...)
length := binary.BigEndian.Uint16(v[3:5]) + 5
plaintext := decryptChacha20(v[0:length], tlsinfo)
i := parseTLSHandshake(plaintext[0:len(plaintext)-1], TLS1_3)
switch proto := i.(type) {
case ServerCertificate:
pubkey = proto.Certificates[0].PublicKey.(*rsa.PublicKey)
case CertificateVerify:
verifyServerCertificate(pubkey, proto.Signature, tlsinfo.Handshakemessages)
case FinishedMessage:
key := tlsinfo.KeyBlockTLS13.serverFinishedKey
mac := hmac.New(sha256.New, key)
mac.Write(writeHash(tlsinfo.Handshakemessages))
verifydata := mac.Sum(nil)
if bytes.Equal(verifydata, plaintext[4:len(plaintext)-1]) {
fmt.Println("Server Verify data is correct !!")
tlsinfo.ServerHandshakeSeq++
tlsinfo.Handshakemessages = append(tlsinfo.Handshakemessages, plaintext[0:len(plaintext)-1]...)
break exit_loop
} else {
// 4.4.4. Finished
// 本当はdecrypt_errorを送る必要があるのでほんとはだめ
log.Fatalf("Server Verify data is incorrect! Handshake is stop!")
}
}
tlsinfo.ServerHandshakeSeq++
tlsinfo.Handshakemessages = append(tlsinfo.Handshakemessages, plaintext[0:len(plaintext)-1]...)
}
}
サーバのFinishedメッセージまで処理が出来たら、ApplicationDataの暗号化用のキーを作ります。
ChangeCipherSpecメッセージを作ったら、ここまでのHandshakeメッセージからクライアント側のFinishedメッセージを作成してサーバに送ります。
// App用のキーを作る
tlsinfo = keyscheduleToAppTraffic(tlsinfo)
// ChangeCipherSpecメッセージを作る
changeCipher := NewChangeCipherSpec()
key := tlsinfo.KeyBlockTLS13.clientFinishedKey
mac := hmac.New(sha256.New, key)
mac.Write(writeHash(tlsinfo.Handshakemessages))
verifydata := mac.Sum(nil)
finMessage := []byte{HandshakeTypeFinished}
finMessage = append(finMessage, uintTo3byte(uint32(len(verifydata)))...)
finMessage = append(finMessage, verifydata...)
finMessage = append(finMessage, ContentTypeHandShake)
fmt.Printf("fin message %x\n", finMessage)
encryptFinMessage := encryptChacha20(finMessage, tlsinfo)
fmt.Printf("fin message %x\n", encryptFinMessage)
var all []byte
all = append(all, changeCipher...)
all = append(all, encryptFinMessage...)
// Finished messageを送る
syscall.Write(sock, all)
fmt.Println("send finished message")
Finishedメッセージを送った後は、ApplicationDataを送ります。
nginxサーバに対して、HTTPリクエストをTLS1.3で暗号化してHTTPSとして送ります。
HTTPリクエスト自体は以前作ったものです。
tlsinfo.State = ContentTypeApplicationData
// HTTPリクエストを作成する
req := NewHttpGetRequest("/", fmt.Sprintf("%s:%d", LOCALIP, LOCALPORT))
appData := req.reqtoByteArr(req)
appData = append(appData, ContentTypeApplicationData)
encAppData := encryptChacha20(appData, tlsinfo)
// HTTPSリクエストを送る
syscall.Write(sock, encAppData)
tlsinfo.ClientAppSeq++
fmt.Println("send Application data")
for {
recvBuf := make([]byte, 2000)
_, _, err := syscall.Recvfrom(sock, recvBuf, 0)
if err != nil {
log.Fatalf("read err : %v", err)
}
length := binary.BigEndian.Uint16(recvBuf[3:5])
plaintext := decryptChacha20(recvBuf[0:length+5], tlsinfo)
// Alert(Close notify)が来たらbreakして終了
if bytes.Equal(plaintext[len(plaintext)-1:], []byte{ContentTypeAlert}) {
break
} else if bytes.Equal(plaintext[len(plaintext)-1:], []byte{ContentTypeApplicationData}) {
fmt.Printf("\nplaintext is %s\n", string(plaintext[0:len(plaintext)-1]))
}
tlsinfo.ServerAppSeq++
}
}
ではこれを実行します。
$ go run .
ServerHello : {HandshakeType:[2] Length:[0 0 118] Version:[3 3] Random:[35 61 131 243 101 157 71 42 222 75 113 187 84 155 144 248 244 165 243 155 128 229 217 18 60 225 179 58 63 23 38 44] SessionIDLength:[32] SessionID:[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] CipherSuites:[19 3] CompressionMethod:[0] ExtensionLength:[0 46] TLSExtensions:[{Type:[0 43] Length:[0 2] Value:[3 4]} {Type:[0 51] Length:[0 36] Value:map[Group:[0 29] KeyExchange:[178 35 114 157 228 46 96 208 162 74 160 117 98 51 183 196 51 82 184 186 151 34 80 96 201 233 118 159 141 234 111 127] KeyExchangeLength:[0 32]]}]}
server key share is b223729de42e60d0a24aa0756233b7c43352b8ba97225060c9e9769f8dea6f7f
sharedkey is a960f4a1b75f08e25243949761a2eac93f5bd46686d45d10a7b7b7869a368a08
derivedSecretForhs 6f2615a108c702c5678f54fc9dbab69716c076189c48250cebeac3576c3611ba
handshake_secret is : 4f4d481f3c9bb75e1d5ab6f39189264eaa80fc306dfb00b83749ea719cb53bbf
hashed messages is b6ea7f03ad2b83b8837eaa3576c61c65a9678613caf9adb1e14f5066998e5844
CLIENT_HANDSHAKE_TRAFFIC_SECRET 0000000000000000000000000000000000000000000000000000000000000000 7feb0aaad0d6c30aff2f655e0a6837447fc22191009a726a7bc417c1f1e07512
SERVER_HANDSHAKE_TRAFFIC_SECRET 0000000000000000000000000000000000000000000000000000000000000000 00d875582c4a8b66e47bccd66f0bb1dccfc5df4d02f434cffe224650b470c01c
serverfinkey is : 39380bcaa3f23a50807d23a4cfa275d73fc78c212ef02a14b6b33c29a1eb751f
derivedSecretFormaster is : cf9443ee4f87a97fc67fd241cb1a4b342c33140895567d8a8605ef3227632b53
extractSecretMaster is : f82ebbadd8f7975874817aaedd420f16be5d22fa69d3fa369bef01ea3ce63c9b
client traffic key is : e150b9107770675165b6a8bd61dc6b513bd7c4851b2cc7bb49b524a841d9236d
client traffic iv is : 292cfde70da79c45a6e9f61b
server traffic key is : 309db84b0b432c9477c12156048ae242e9046c3fde7f7f35b20282928d92c8c2
server traffic iv is : 6f45a0b388fd72baba335d75
read ChangeCipherSpec is 140303000101, これから暗号化するんやでー
EncryptedExtensions : {HandshakeType:[8] Length:[0 0 2] ExtensionLength:[0 0] TLSExtensions:[]}
証明書マジ正しい!
Certificate : {HandshakeType:[11] Length:[0 4 36] CertificatesRequestContextLength:[0] CertificatesLength:[0 4 32] Certificates:[0xc0000c0580]}
CertificateVerify : {HandshakeType:[15] Length:[0 1 4] SignatureHashAlgorithms:[8 4] SignatureLength:[1 0] Signature:[21 176 138 110 163 212 253 176 10 186 173 37 214 164 220 254 97 28 241 249 5 192 17 102 85 100 26 217 42 132 127 184 89 93 67 66 168 231 164 227 166 7 228 189 81 53 51 76 236 61 231 247 187 156 86 53 221 232 6 99 245 177 188 169 173 249 62 104 236 16 89 205 5 123 213 30 40 234 161 252 174 218 253 142 126 238 53 242 224 93 131 66 166 67 27 137 223 184 213 224 211 163 181 183 70 156 170 128 52 5 202 219 73 63 244 220 91 148 101 110 58 22 199 107 129 202 112 127 127 179 184 89 18 9 98 249 27 133 240 88 189 217 139 35 181 162 165 243 30 250 64 35 49 235 214 59 38 124 72 86 135 90 48 72 195 181 190 151 49 41 135 132 224 209 6 27 99 239 156 201 208 61 234 153 183 230 74 66 142 143 109 75 200 214 69 68 78 150 90 175 147 69 99 20 225 252 6 160 189 195 207 123 46 44 128 229 50 135 98 162 8 71 4 167 25 62 249 165 186 98 142 66 120 136 120 156 242 31 203 214 86 165 12 93 168 243 61 38 215 163 190 119 39 148 48 188]}
hash_messages is 1d5b4e95a77fe7b9034661011ab2b5d422276ecb35e213d1a3bc90cf9bb560d6, signed is 40e694b6014cf16b2eaaff84ece2bdb618539e49c5a6a21f5a54e2f3680adbbd
Server Certificate Verify is OK !!
FinishedMessage : {HandshakeType:[20] Length:[0 0 32] VerifyData:[136 69 176 231 172 96 48 241 81 67 31 26 142 156 142 61 135 237 214 6 98 237 192 70 206 159 246 124 30 230 137 59]}
Server Verify data is correct !!
hashed messages is 331399637c93f0afc9bb5d451e036cd6431f9aa718f16af5d82743be450dbb39
CLIENT_TRAFFIC_SECRET_0 0000000000000000000000000000000000000000000000000000000000000000 ec06d4e6b3b9fedaa0125867a6809dcf8c76e362db3d34036d58bd5909e7bea5
SERVER_TRAFFIC_SECRET_0 0000000000000000000000000000000000000000000000000000000000000000 b385a5a205f3fe1ecf504f005e97ab1c3401b7416948b66bc4f6293595d7c0dd
clientAppKey and IV is : 8209132454ead35bea0b7101c0fa7c4c15e6ed758ada375f488dbd6ffe4ac0f2, 7b999ad753d76b5fa809166e
serverAppkey and IV is : 709b44afe0ffa5cb0d5ccc1f869c9dd98b6b062f2a61c46e645df6df002597c2, de68a2db65332b5ee87fcec9
fin message 14000020e24aa09b482fc3bfec10b8ef7c97af853f05966198fa67cb01a328673a268c3e16
key is e150b9107770675165b6a8bd61dc6b513bd7c4851b2cc7bb49b524a841d9236d, iv is 292cfde70da79c45a6e9f61b
encrypt now nonce is 0000000000000000 xornonce is 292cfde70da79c45a6e9f61b, plaintext is 14000020e24aa09b482fc3bfec10b8ef7c97af853f05966198fa67cb01a328673a268c3e16, add is 1703030035
fin message 1703030035b273a2e486118aa4787d1c00c499ee4311351be3ab94a2fcbc494b393443aa2c3e9271579f5513802423d6a901f933880f4a09caab
send finished message
key is 8209132454ead35bea0b7101c0fa7c4c15e6ed758ada375f488dbd6ffe4ac0f2, iv is 7b999ad753d76b5fa809166e
encrypt now nonce is 0000000000000000 xornonce is 7b999ad753d76b5fa809166e, plaintext is 474554202f20485454502f312e310d0a486f73743a203132372e302e302e313a383434330d0a557365722d4167656e743a206375726c2f372e36382e300d0a4163636570743a202a2f2a0d0a436f6e6e656374696f6e3a20636c6f73650d0a0d0a17, add is 1703030072
send Application data
plaintext is HTTP/1.1 200 OK
Server: nginx/1.21.6
Date: Fri, 06 May 2022 11:43:57 GMT
Content-Type: text/html
Content-Length: 615
Last-Modified: Tue, 25 Jan 2022 15:03:52 GMT
Connection: close
ETag: "61f01158-267"
Accept-Ranges: bytes
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
ちゃんとnginxのTopページが返ってきました!!!
Wiresharkで見ても正しくTLS1.3でフルハンドシェイクをしてから、HTTPSリクエストが送られています。
おわりに
というわけで無事なんとかTLS1.3のハンドシェイクが出来ました、GWがほぼ無くなりましたけどね🫠🫠🫠
実装しながらむちゃくちゃ面倒くさい!!!!!!と思いましたけど、まぁ面倒くさいってことはそれだけ攻撃者が盗聴するのが難しいってわけで、利用者としては安心して使えるってことですよね。
最初に1.2を作ったので、1.3のパケットを見るとすぐに「なるほど、だからTLS1.3は1-RTTだけで暗号化できるのか」と仕様がわかりました。
こういうのって自分でやってみたからこそわかることですよね😊😊
というわけでぜひ皆さんもTLSを自作してみてはいかがでしょうか。
俺達のプロトコル開発はこれからだ!!次回作にご期待ください。
Discussion