実装の流れ
目指す形
本書では、TLS 1.3クライアントを1つのGoモジュールとして実装します。ルートにtls13パッケージを置き、TLSに関する処理をすべてこのパッケージに集約します。
Go標準のcrypto/tlsと同じように、複雑なTLSの処理を意識せず、Dialで対象へ接続するだけでTLS接続を確立し、その後はWriteやReadを呼び出すだけで暗号化通信を行えるパッケージを目指します。 Dialが返すConnはio.Writerとio.Readerを満たすので、fmt.Fprintfやio.Copyにそのまま渡せます。
go
conn, err := tls13.Dial("tcp", "example.com:443", &tls13.Config{
ServerName: "example.com",
})
if err != nil {
return err
}
defer conn.Close()
fmt.Fprintf(conn, "GET / HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n\r\n")
io.Copy(os.Stdout, conn)完成したライブラリで公開するのは、Dial関数とConfig構造体、Dialが返すConnだけです。それ以外の型、関数、定数は、すべて小文字で始まる名前にしてパッケージの中に閉じ込めます。
ただし、本書では実装の途中でcmd/main.goから動作を確かめるため、動作確認に使う型や関数、定数だけは一時的に公開します。そのため、途中のコードでは大文字始まりの名前になっているものがあります。一時的に公開したものは、公開APIを整えるのページでまとめて非公開にします。
最終的なファイルツリー
完成時のファイルツリーは以下のようになります。
.
├── cmd/
│ └── main.go tls13パッケージを使ったコードを書く場所
├── go.mod
├── common.go 定数(ContentType、HandshakeType など)
├── bytes.go バイト列の読み書きヘルパ
├── record.go TLSPlaintext / TLSCiphertext
├── handshake.go Handshakeメッセージの共通部分
├── extensions.go ClientHelloに付ける拡張
├── client_hello.go ClientHello
├── server_hello.go ServerHello
├── key_schedule.go HKDFによる鍵導出
├── aead.go AES-GCMの初期化
├── record_protection.go レコードの暗号化と復号
├── certificate.go Certificate / CertificateVerify とその検証
├── alert.go Alert
├── handshake_client.go ハンドシェイクの状態機械
├── conn.go Conn(レコードとメッセージの読み書き、Write / Read / Close)
└── client.go Dial