Hatena::ブログ(Diary)

あどけない話

2017-04-26

TLS 1.3 開発日記 その17 AEAD

TLS 1.2とTLS 1.3のAEAD の違いについて、AEADの一つであるAES 128 GCMを例にとって説明する。

TLS 1.2のAEAD

以下の3つのRFCをよーく読まないといけない。

RFC 5246 の 6.2.3.3 節では、AEADで暗号化されたレコードが以下のように定義してある。

struct {
    opaque nonce_explicit[SecurityParameters.record_iv_length];
    aead-ciphered struct {
        opaque content[TLSCompressed.length];
    };
} GenericAEADCipher;

aead-ciphered の部分は、以下の関数で生成される。

AEADEncrypted = AEAD-Encrypt(write_key, nonce, plaintext, additional_data)

賢明な読者なら「authentication tag はどこにいった?」と疑問に思うだろう。RFC 5116の5.1節には、こう書いてある。

The AEAD_AES_128_GCM ciphertext is formed by appending the authentication tag provided as an output to the GCM encryption operation to the ciphertext that is output by that operation.

つまり、AEAD-Encryptの中身はこんな感じ:

AEAD-Encrypt(write_key, nonce, plaintext, additional_data) {
    (cipher, auth_tag) = aead-encrypt(write_key, nonce, plaintext, additional_data);
    return (cipher + auth_tag);
}

一方、AEAD-Decryptのインターフェイスは、こう定義してある:

TLSCompressed.fragment = AEAD-Decrypt(write_key, nonce, AEADEncrypted, additional_data)

中身はこんな感じになる:

AEAD-Decrypt(write_key, nonce, AEADEncrypted, additional_data) {
    (cipher, auth_tag1) = split(AEADEncrypted);
    (plaintext, auth_tag2) = aead_decrypt(write_key, nonce, cipher, additional_data);
    if (auth_tag1 != auth_tag2) throw_error();
    return plaintext;
}

引数のadditonal_dataも、RFC 5246に定義してある:

additional_data = seq_num + TLSCompressed.type + TLSCompressed.version + TLSCompressed.length;

この後半の3つであるが、TLSCompressedレコードの定義をみると、なんのことはない、レコードヘッダだと分かるだろう。

struct {
    ContentType type;       /* same as TLSPlaintext.type */
    ProtocolVersion version;/* same as TLSPlaintext.version */
    uint16 length;
    opaque fragment[TLSCompressed.length];
} TLSCompressed;

つまり、こういうことである。

additional_data = seq_num + レコードのヘッダ

最後は nonce。RFC 5116の5.1節には、"N_MIN and N_MAX are both 12 octets" と書いてある。最小値も最大値も12バイトだから、nonceは12バイトであると分かる。RFC 5288は、こう書かれている:

          struct {
                opaque salt[4];
                opaque nonce_explicit[8];
             } GCMNonce;

   The salt is the "implicit" part of the nonce and is not sent in the
   packet.  Instead, the salt is generated as part of the handshake
   process: it is either the client_write_IV (when the client is
   sending) or the server_write_IV (when the server is sending).  The
   salt length (SecurityParameters.fixed_iv_length) is 4 octets.

というわけで、4バイトのsaltにはclient_write_IVかserver_write_IVを入れればよい。nonce_explicitについては:

The nonce_explicit MAY be the 64-bit sequence number.

なので、seq_numでよいと分かる。

TLS 1.2 を実装するの、嫌になったでしょ?

TLS 1.3のAEAD

TLS 1.3のAEADを理解するには、ドラフトを読めばよい。

the additional data input is empty (zero length)

というわけで、additional data には空文字列を渡す。

nonce は、

  • The 64-bit record sequence number is encoded in network byte order and padded to the left with zeros to iv_length.
  • The padded sequence number is XORed with the static client_write_iv or server_write_iv, depending on the role.

のように作ればよい。

2017-04-25

TLS 1.3 開発日記 その16 Wireshark

Wiresharkはv2.3.0からTLS 1.3 ID 19に対応する。めでたい。すぐに使いたい人は、Nightlyビルドをとってくるとよい。

使ってみる

ポート13443で起動しているHaskellサーバとpicotlsクライアント通信tcpdumpでキャプチャしたファイルを"pico.pcap"とする。これを表示してみよう。

% tshark -dtcp.port==13443,ssl -Y ssl -r pico.pcap -V
Secure Sockets Layer
    TLSv1 Record Layer: Handshake Protocol: Client Hello
        Content Type: Handshake (22)
        Version: TLS 1.0 (0x0301)
        Length: 178
        Handshake Protocol: Client Hello
            Handshake Type: Client Hello (1)
            Length: 174
            Version: TLS 1.2 (0x0303)
            Random: c61e4f47e9d8f0c6e713bd872bd488c1a8c9bb855b8ccb4a...
                GMT Unix Time: May  1, 2075 03:39:03.000000000 JST
                Random Bytes: e9d8f0c6e713bd872bd488c1a8c9bb855b8ccb4a2bfad94d...
            Session ID Length: 0
            Cipher Suites Length: 2
            Cipher Suites (1 suite)
                Cipher Suite: TLS_AES_128_GCM_SHA256 (0x1301)
            Compression Methods Length: 1
            Compression Methods (1 method)
                Compression Method: null (0)
...
            Extension: key_share (len=71)
                Type: key_share (40)
                Length: 71
                Key Share extension
                    Client Key Share Length: 69
                    Key Share Entry: Group: secp256r1, Key Exchange length: 65
                        Group: secp256r1 (23)
                        Key Exchange Length: 65
                        Key Exchange: 046ed5a9aca26248b0fc322e218e778ebd17f4b47add1a7a...

ClientHello のkey_shareが表示できてる。やったね!

オプションの意味:

  • "-dtcp.port==13443,ssl": ポート13443をTLSとして解析
  • "-Y ssl": TLSだけを表示
  • "-r pico.pcap": 入力ファイルは "pico.pcap"
  • "-V":詳細表示

復号化する

picotlsでは、cliに"-l"でファイルを指定すると、セッションキーをそのファイルに書き出す。ここでは、ファイル名を"pico.keys"とする。内容はこんな感じ:

% cat pico.keys
SERVER_HANDSHAKE_TRAFFIC_SECRET 秘密の鍵1
CLIENT_HANDSHAKE_TRAFFIC_SECRET 秘密の鍵2
SERVER_TRAFFIC_SECRET_0 秘密の鍵3
CLIENT_TRAFFIC_SECRET_0 秘密の鍵4

このファイルをtsharkに指定すると、暗号化されている部分が復号化できる。

% tshark -ossl.keylog_file:pico.keys -dtcp.port==13443,ssl -Y ssl -r pico.pcap
    4   0.028292 IPアドレスA → IPアドレスB TLSv1 249 Client Hello
    6   0.060786 IPアドレスB → IPアドレスA TLSv1.3 1514 Server Hello, Encrypted Extensions
    7   0.060844 IPアドレスB → IPアドレスA TLSv1.3 1514 Certificate [TCP segment of a reassembled P
DU]
    8   0.060847 IPアドレスB → IPアドレスA TLSv1.3 163 Certificate Verify, Finished
   11   0.062084 IPアドレスA → IPアドレスB TLSv1.3 124 Finished
   12   0.062843 IPアドレスB → IPアドレスA TLSv1.3 368 New Session Ticket
   15   4.281915 IPアドレスA → IPアドレスB TLSv1.3 89 Application Data
   17   4.307195 IPアドレスB → IPアドレスA TLSv1.3 229 Application Data
   19   4.307612 IPアドレスB → IPアドレスA TLSv1.3 90 Alert (Level: Warning, Description: Close Not
ify)

やっほー!

オプションの意味:

  • "-ossl.keylog_file:pico.keys": 鍵のファイルは "pico.keys"

Macでの戦い

しかし、Macでは復号化できなかった。教えてもらった"-ossl.debug_file:ssl-debug.txt "というデバッグオプションを付けてみると:

% cat ssl-debug.txt
...
Libgcrypt version: 1.5.0
...
Libgcrypt is older than 1.6, unable to verify auth tag!

というわけで、libgcryptが古いのが原因だった。"-ossl.ignore_ssl_mac_failed:TRUE"を付けると見れるようになると教えてもらった。そのうち解決されるだろう。

2017-04-07

TLS 1.3 開発日記 その15 RSAPSSとX25519

開発日記 その8に書いた「拡張の再利用」の問題に進展があったので、記録しておく。

これまでの方針

拡張が再利用されていても、TLS 1.2 と TLS 1.3 では異なる拡張として扱う。

生じた問題

SignatureSchemeに対する問題:

OpenSSL サーバがこのような挙動をする。

NamedGroupに対する問題:

www.google.com がこのような挙動をする。

対処

SignatureSchemeに関しては、TLS 1.3 のドラフトには以下のように書かれている。

Implementations that advertise support for RSASSA-PSS (which is mandatory in TLS 1.3), MUST be prepared to accept a signature using that scheme even when TLS 1.2 is negotiated. In TLS 1.2, RSASSA-PSS is used with RSA cipher suites.

NamedGroupに関しては、RFC 4492 bis で、X25519 と X448 が追加されている。

つまり、このオプションを独立に扱うのは筋が悪い。TLS 1.3 と TLS 1.2 の両方から、同じ拡張のコードを使うべき。

というわけで、まず Haskell tls ライブラリの master ブランチに、RSA PSS SHA256 たちと X25519 たちをサポートする pull request を出した。

TLS 1.3 ブランチは、マージされた後に rebase する(たぶん大変)。

2017-03-27

TLS 1.3 開発日記 その14 TLS 1.3 ID19

IETF 98 ChicagoHackathon に向けて、Haskell TLS ライブラリTLS 1.3 ID19 に対応させた話。僕は Hackathon には遠隔参加。

ID19に一番乗りしたのは OpenSSL。辻川さんがテストサーバを上げてくれた。

Full と PSK

  • Add pre-extract Derive-Secret stages to key schedule

7.1節の key schedule が変わったので、変更すると OpenSSL と full & PSK ハンドシェイクができた。

0RTT

  • Consolidate "ticket_early_data_info" and "early_data" into a single extension
  • Change end_of_early_data to be a handshake message

ticket_early_data_info と early_data 拡張を1つの拡張とし、end_of_early_data をアラートからハンドシェイクへ変更する。ID18 では、end_of_early_data は early_data を送った直後に送ってよかったが、ID19 からは Server Finished を受け取ってから送る。このせいでコードが汚くなった。

OpenSSLサーバHaskellクライアントの間で0RTTができないので、原因を調べたところ2つの問題があった。

  • そもそもID18でも0RTTできなかった。OpenSSLでは、チケットの検証が厳しくこれにひっかかっていたので、Haskell側を修正。これでID18で、0RTTできるようになった。
  • ID19 の仕様では Client Finished の計算に EndOfEarlyData を含めないように書かれていた。Haskellの実装はそれに忠実。OpenSSLの実装は EndOfEarlyData を計算に含めていた。EndOfEarlyDataを計算に含めないと、EndOfEarlyDataを守れないので、ハンドシェイクに変更した意味がない。結局、仕様のバグであると合意に至り、Haskell を修正後、OpenSSLと0RTTできるようになった。

HRR

  • Hash ClientHello1 in the transcript when HRR is used. This reduces the state that needs to be carried in cookies.

ID18 ではハンドシェイクメッセージをその都度ハッシュにくべると transcript hashが計算できたが、ID19からは最初のClientHelloのハッシュ値ハッシュにくべなければならなくなった。面倒であったが、結果的には簡潔に実装できた。

まとめ

ID19の仕様のバグも発見したし、上げたテストサーバクライアントバイナリも利用されたようなので、役に立ってよかった。

2017-03-01

[TLS][Haskell]TLS 1.3 開発日記 その13 TLS 1.3を遮断する中継装置

経緯

原因

  • TLS 1.3は、TLS 1.2と区別のつかないClient Helloを送る。拡張でバージョンが1.3だと教える。
  • TLSの実装は、知らない拡張は単に無視してエラーにしてはいけないという鉄則により、TLSバージョンアップは順調にいくはずだと信じられていた。
  • BlueCoatは、サーバからのCertificateの中身を見て、中継するか否かを決めるらしい。
  • TLS 1.3を知らないBlueCoatには、Client HelloがTLS 1.2に見える。
  • しかし、サーバから戻って来たハンドシェイクは暗号化されているから中身が読めない。
  • TLS 1.2じゃない」と言って、コネクションを遮断。

考察

  • 世の中にはいろんな実装がある。はぁ。
  • Firefoxは、TLS 1.2にフォールバックするらしい。
  • ChromeでもTLS 1.2にフォールバックすれば一応問題解決
  • しかし、TLS 1.2でも1.3でもうまくいくようにClient Helloは設計されているので、TLS 1.2にフォールバックするのは、なんだかなぁという感じ。
  • BlueCoatよ、早く直して。