Hatena::ブログ(Diary)

あどけない話

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よ、早く直して。

2017-02-23

TLS 1.3 開発日記 その12 OCSP と SCT

TLS 1.2では Server Hello 拡張であった OCSP と SCT は、TLS 1.3ではハンドシェイクメッセージである Certificate の拡張となった。

OCSP

証明書は有効期限内であっても、失効している可能性がある。失効しているかを調べる伝統的なやり方は、CRL(Certificate Revocation List)であった。

CRLでは、クライアントが失効リストを取って来て、その中に対象があるかないかを調べる。これに対し、OCSPサーバに問い合わせると、証明書が(いつの時点で)有効か教えてくれるのが OCSP(Online Certificate Status Protocol) である。

TLSクライアントが、OCSPサーバに問い合わせるのは現在では非推奨である。その代わり、TLSサーバが定期的に問い合わせ、証明書と一緒にフレッシュな OCSP Response を送ってくるのが、OCSP stapling である。

証明書に対する OCSP サーバの情報は、証明書の中に入っている。Let's ecnrypt で取得した署名書では、以下のような感じ:

% openssl x509 -text -in cert.pem
Certificate:
    Data:
...
            Authority Information Access: 
                OCSP - URI:http://ocsp.int-x3.letsencrypt.org/
                CA Issuers - URI:http://cert.int-x3.letsencrypt.org/
...

OCSPサーバに問い合わせてみる:

% openssl ocsp \
  -noverify \
  -issuer chain.pem -cert cert.pem -CAfile chain.pem \
  -url http://ocsp.int-x3.letsencrypt.org \
  -header Host ocsp.int-x3.letsencrypt.org \
  -resp_text -respout resp.der
OCSP Response Data:
    OCSP Response Status: successful (0x0)
    Response Type: Basic OCSP Response
    Version: 1 (0x0)
    Responder Id: C = US, O = Let's Encrypt, CN = Let's Encrypt Authority X3
    Produced At: Feb 23 06:37:00 2017 GMT
    Responses:
    Certificate ID:
      Hash Algorithm: sha1
      Issuer Name Hash: 7EE66AE7729AB3FCF8A220646C16A12D6071085D
      Issuer Key Hash: A84A6A63047DDDBAE6D139B7A64565EFF3A8ECA1
      Serial Number: 03AF31CC447C2CEF256FC77839190A70DDE2
    Cert Status: good
    This Update: Feb 23 06:00:00 2017 GMT
    Next Update: Mar  2 06:00:00 2017 GMT
...

便利なラッパーとして fetch-ocsp-responseがある。

SCT

Certificate Transperancyは、(理想的にはすべての)証明書を登録するサービスである。ロガー呼ばれるサーバには、証明書の追加だけができるログを持っている。2つの使い方がある。

  • ある証明書のハッシュを送ると、ログの中に存在しているか確認できる。
  • ロガーから、証明書群の差分を取れる。つまり、ロガーが持っている証明書すべてを取得できるので、不正な証明書が発行されてないか調べられる。たとえば、自分のドメインを名乗る不正なサーバに対する証明書を発見できる。

ロガーには、サーバ名から証明書を検索する機能はない。そういうサービスは、上記二番目の機能を使って外部に作る。たとえば、crt.sh

ロガーは、新規に証明書を登録された場合、SCT (signed certificate timestamp)を返す。これは、ロガーの署名が付いた存在証明である。TLSクライアントは、SCTの署名をロガーの公開鍵で検証すれば、対応する証明書がログに入っていることを確認できる。つまり、ロガーに問い合わせる必要はないし、TLSクライアントがロガーに問い合わせるのは非推奨である。

TLSサーバからTLSクライアントにSCTを渡すには、2つの方法がある。

  • 証明書の拡張に入れる
  • TLS の Server Hello の拡張に入れる

CA から、サーバ管理者に SCT を配布するには、以下の方法がある。

  • 発行する証明書に入れる
  • OCSPの拡張で返す

Let's encrypt の CA は、証明書を発行する際、ct.googleapis.com/pilot と ct.googleapis.com/icarus に証明書を登録するようだ。つまり、Let's encrypt の CA は、SCT を知っているが、残念ながらそれをサーバ管理者には教えてくれない。

  • certbot renew しても SCT ファイルはできない
  • certbot renew して得られる証明書には SCT が入っていない
  • OCSP で問い合わせても SCT 拡張は入っていない

八方塞がりである。

実はロガーは、すでにある証明書が登録されようとした場合も、SCT を返す。この方法を実現してくれるのが、ct-submitである。(もちろん新規登録も可能。)

% ct-submit ct.googleapis.com/pilot < fullchain.pem > sct

2017-02-07

TLS 1.3 開発日記 その11 NSS

NSSをローカルビルドして使う方法。

まず、ソースを取ってくる:

% mkdir nssroot
% cd nssroot
% hg clone https://hg.mozilla.org/projects/nss
% hg clone https://hg.mozilla.org/projects/nspr

ビルドする:

% cd nss
% make nss_build_all

証明書なんかを作る:

% cd tests/ssl_gtests
% export DOMSUF="."
% ./ssl_gtests.sh

NSSサーバの動かし方

共有ライブラリを使っている場合は、適当にパスを加える。nssroot ディレクトリで:

% ./dist/Darwin16.4.0_cc_DBG.OBJ/bin/selfserv -d tests_results/security/localhost.1/ssl_gtests -n rsa -p 443 -V tls1.3:tls1.3 -u -Z
  • -u が PSK を有効にする
  • -Z が 0RTT を有効にする

ヘルプの表示:

% ./dist/Darwin16.4.0_cc_DBG.OBJ/bin/selfserv -h

selfserv のソース:

NSSクライアントの動かし方

共有ライブラリを使っている場合は、適当にパスを加える。nssroot ディレクトリで:

ヘルプの表示:

% ./dist/Darwin16.4.0_cc_DBG.OBJ/bin/tstclnt -\?

フルネゴシエーション

% ./dist/Darwin16.4.0_cc_DBG.OBJ/bin/tstclnt -D -V tls1.3:tls1.3 -h 127.0.0.1 -p 443 -o

PSK:

% ./dist/Darwin16.4.0_cc_DBG.OBJ/bin/tstclnt -D -V tls1.3:tls1.3 -h 127.0.0.1 -p 443 -o -L 2 -A <input_file>

0RTT:

% ./dist/Darwin16.4.0_cc_DBG.OBJ/bin/tstclnt -D -V tls1.3:tls1.3 -h 127.0.0.1 -p 443 -o -L 2 -A <input_file> -Z

入力ファイルの例:

GET / HTTP/1.1
Host: 127.0.0.1
Connection: close

tstclnt のソース:

2017-01-20

TLS 1.3 開発日記 その10 NSSサーバ

TLS 1.3 のテスト用に公開されているNSSサーバは2つあります。

Haskell TLS 1.3 client では、前者とフルハンドシェイクできるのですが、後者は handshake error を返してきます。一方で、Firefox Nightly や Chrome Canary は、後者に問題なくアクセスできます。

必要な拡張が足らないのかと思い、Firefox Nightly が付けている拡張をすべて付けてみたりしましたがダメでした。この相互接続性は長い間の課題だったのですが、他の作業が落ち着いたこともあり、重い腰を上げて真面目に解析してみました。

採った方法は、Firefox Nightlyが出力する Client Hello のバイト列をそのまま送りつけるユーティリティを作り、徐々に拡張などを削っていて、handshake error を返す要因を特定することです。

驚くべきことにTLS 1.3に関係のない拡張を全部削除しても、tls13はServer Hello を返してきました。そこで、TLS 1.3に必須の拡張から値を徐々に消していきました。そして、Signature Scheme から ecdsa_secp384r1_sha384 を消したときに、handshake error になることを突き止めました。

tls13のサーバ証明書は ECDSAだったのです!同じ NSSサーバでも、franziskuskieferではうまくいくのに tls13ではうまくいかない理由が氷解しました。そこで、Haskell TLS 1.3 にECDSAのコードを入れたところ、めでたく tls13とハンドシェクできるようになりました。

NSSサーバは、NewSessionTicketを送ってきませんので、PSKハンドシェイクなどはできないようです。

追記:

Mozillaの人達と話したら、tls13 が NewSessionTicket を返さないのは、現在のNSSの実装では、ECDSA の証明書に対しそれを返せないからだそうです。証明書を RSA に変えて、NewSessionTicket を有効にしていただきました。めでたく、NewSessionTicket は届くようになりましたが、まだ PSK のハンドシェイクに成功していません。

これは mod_nss の問題のようです。ローカルでビルドした NSS とは、4つのハンドシェイク全部でつながることを確認できました。