はじめに
本稿を読む前に、拙稿「SSL3.0, TLS1.0~1.2のハンドシェイクを復習する」を読むことをお勧めします。
プロトコルバージョン別・主要な変更点
SSL3.0→TLS1.0
【新】 拡張という概念がRFC3546 (Google翻訳)で追加されました。
ではSSL3.0で拡張は使えなかったのかというと、その辺りはちょっと微妙なので別項で説明します。
【新】 マンダトリ(必須)サイファースイートという概念が導入されました。
これはクライアントとサーバで共通の暗号方式が一つもなく、暗号通信が確立しなくなる事態を防ぐための措置です。TLS1.0のマンダトリ・サイファースイートはTLS_DHE_DSS_WITH_3DES_EDE_CBC_SHAです。
【廃】 FORTEZZA鍵交換が廃止されました。
FORTEZZAとは、かつて米国で使われていた暗号装置(ハードウェア)です。
【改】 master_secret, key_block, Finishedハッシュの計算式が変わりました。
PRF(疑似乱数関数)が導入されてアルゴリズムが整理・強化されました。
Finishedハッシュは36バイトから12バイトへと短くなりました。
master_secretは48バイトで長さは変わりません。
【改】 ClientVerifyの計算方式がちょっと変わりました。
【改】 CBCモードが変わりました。
パディング領域の内容が厳密に定義されるようになりました。SSL3.0がPOODLE攻撃に弱く、TLS1.0以降は強い理由はこれです。
TLS1.0→TLS1.1
【廃】 輸出用暗号が廃止されました。
【改】 マンダトリ・サイファースイートが変わりました。
今度はTLS_RSA_WITH_3DES_EDE_CBC_SHAです。なんだか前より弱くなったような気がしますが気にしないということで。
【改】 CBCモードがまた変わりました。
IVをkey_blockから算出するのではなく、別途生成してTLSレコードレイヤで伝送するようになりました。また、パディングエラーの取り扱いがちょっと変わりました。
TLS1.0以前では選択平文攻撃を応用した中間者攻撃が可能であると指摘されており、その対策がこれです。
【改】 些細な変更ですが、 session resumption (Session IDやSessionTicket拡張を利用したセッション再開)できる条件がちょっと緩和されました。
TLS1.1→TLS1.2
【改】 プロトコルのそこかしこで使われていたMD5, SHA-1の大半が消えました。
この変更は広範囲に及んでおり、結果としてプロトコル全体が複雑になってしまいました。
【改】 上述のMD5, SHA-1が消えた余波で、ServerKeyExchangeとClientVerifyが変わりました。
TLS1.1以前は署名方式(RSAかDSAか)によってMD5とSHA-1を使い分けていましたが、TLS1.2ではプロトコル内にハッシュ方式を明示的に指定するフィールドがあります。
【改】 PRFのアルゴリズムが変わりました。
ハッシュ方式をサイファースイートで指定できることになりました。デフォルトはSHA-256です。
【新】 signature_algorithms拡張が追加されました。
これは署名方式(署名方式とハッシュ方式のペア)を指定するもので、複数種類を指定できます。
この拡張はServerCertificate, ServerKeyExchange, ClientKeyExchange, ClientVerifyに影響します。サーバ証明書や中間CA証明書の署名を含む全ての署名はsignature_algorithmsリストに含まれる方式を使わなければなりません。
【改】 CAがサーバ証明書に署名するときの署名方式の解釈が変わりました。
TLS1.1まではCAが証明書に署名するときの署名方式をcipher suiteで指定していましたが、signature_algorithms拡張の導入に伴い、この仕様がなくなりました。
TLS1.1までは、TLSサーバの認証方式がRSAならCAはサーバ証明書をRSAで署名しなければならず、TLSサーバの認証方式がDSSならCAはサーバ証明書をDSSで署名しなければなりませんでしたが、TLS1.2ではこれが関係なくなり、TLSサーバの認証方式がRSAでもCAはサーバ証明書をDSSで署名したり、TLSサーバの認証方式がDSSでもCAはサーバ証明書をRSAで署名したりしてもよくなりました。
また、DH_RSAとDH_DSSの区別、ECDH_RSAとECDH_DSSの区別が、それぞれなくなりました。
【新】 CBCに加えて、GCM,CCMモードが使えるようになりました。
【新】 Finishedハッシュの長さをサイファースイートで指定できるようになりました。
デフォルトは従来どおり12バイトです。
【改】 マンダトリ・サイファースイートがまた変わりました。
今度はTLS_RSA_WITH_AES_128_CBC_SHAです。
解説・Q&A
SSL3.0に拡張はあるのか。
書類上の仕様としては、SSL3.0に拡張はありません。しかし実装上は使えることもあります。
拡張は、TLS1.0の拡張はRFC3545、TLS1.1の拡張はRFC4346という具合にプロトコル本体の定義に後付けする別のRFCとして定義されてきました。TLS1.2でやっと本体定義に取り込まれました。表にまとめるとこんな感じです。
プロトコル | プロトコルの定義 | 拡張の定義 |
---|---|---|
SSL3.0 | RFC6101 (Google翻訳) | N/A |
TLS1.0 | RFC2246 (Google翻訳) | RFC3546 (Google翻訳) |
TLS1.1 | RFC4346 (Google翻訳) | RFC4366 (Google翻訳) |
TLS1.2 | RFC5246 (Google翻訳) | 同左 |
そんなわけで、「SSL3.0の拡張」は__書類上__は存在しないのです。しかしSSL3.0にもTLS1.0と同様に「後付けで拡張を追加できる余地」がありますので、TLS1.0と同じ方式の拡張をSSL3.0に後付けすることは可能で、一部では実際に行われているというわけです。
TLS1.2のcipher suiteは1.1以前と何が変わったのか。
PRFアルゴルズムとFinishedハッシュ長をcipher suiteで指定できるようになった、とRFCには書いてあるのです。しかし、これを書いている2016年5月時点で、IANAのcipher suite一覧のどこを見ても、「PRFアルゴリズム欄」や「Finishedハッシュ長さ欄」のあるcipher suiteなんて一つもありません。どうも企画倒れなんじゃないかという気がします。
ちなみに無指定の場合、PRFはP_SHA256, Finishedハッシュ長は12バイトがTLS1.2のデフォルトです。
DSAはセキュアなのか。
RSAと並ぶデジタル署名の双璧、DSA。ところがここに案外へなちょこな弱点がありまして、RSAであればPKCS#1のDigestInfo (RFC3447のAppendix A.2.4) に相当するフォーマットがDSAに用意されておらず、ハッシュ関数の種類という、たった1~数バイト程度の情報をセキュアに送る手段がありません。そのため、非常にレアな可能性ではあるものの、中間者攻撃でDSA-SHA-2署名をDSA-SHA-1にフォールバックさせられてしまう可能性があるのです。
そんなわけで、今すぐではありませんが、将来的にはsignature_algorithms拡張を使ってDSA/ECDSAとSHA-1の組み合わせを明示的に禁止することが推奨になるかもしれません。
資料編
用語・凡例
「+」記号 : 加算ではなく連結を表します。例えば"abc"+"def"
は"abcdef"
です。
pad_1, pad_2 : SSL3.0で使われるパディングで、定義は以下のとおりです。TLS1.0以降では使われません。
pad_1: The character 0x36 repeated 48 times for MD5 or 40 times for SHA.
pad_2: The character 0x5c repeated 48 times for MD5 or 40 times for SHA.
数式
P_hash(secret, seed)
共有鍵とシード(種)から理論上無限長の疑似乱数列を生成する関数です。後述するPRFの定義に使われます。SSL3.0にはありません。
P_hash(secret, seed) = HMAC_hash(secret, A(1) + seed) +
HMAC_hash(secret, A(2) + seed) +
HMAC_hash(secret, A(3) + seed) + ...
A() is defined as:
A(0) = seed
A(i) = HMAC_hash(secret, A(i-1))
P_hash
のhash
部分にはハッシュ関数が入ります。例えばP_SHA-1, P_SHA256のように使います。
PRF(secret, label, seed)
PRFは、master_secretやkey_blockなどの算出に使う、何かと便利な関数です。これもSSL3.0にはない概念です。label引数は後述の固定文字列が入ります。
PRF(secret, label, seed) = P_MD5(S1, label + seed) XOR
P_SHA-1(S2, label + seed);
S1
とS2
は何かというと、引数secret
を前半と後半に二分割し、前半部がS1
、後半部がS2
です。
PRF(secret, label, seed) = P_<hash>(secret, label + seed)
<hash>
は、デフォルトではSHA-256です。デフォルト以外は、cipher suiteで指定します。
master_secret
master_secret =
MD5(pre_master_secret + SHA('A' + pre_master_secret +
ClientHello.random + ServerHello.random)) +
MD5(pre_master_secret + SHA('BB' + pre_master_secret +
ClientHello.random + ServerHello.random)) +
MD5(pre_master_secret + SHA('CCC' + pre_master_secret +
ClientHello.random + ServerHello.random));
16バイトのMD5ハッシュを3個つなぎ合わせて48バイトとします。
master_secret = PRF(pre_master_secret, "master secret",
ClientHello.random + ServerHello.random)
[0..47];
SSL3.0と違ってPRFがありますから定義が楽ちん!
key_block
key_block =
MD5(master_secret + SHA(`A' + master_secret +
ServerHello.random +
ClientHello.random)) +
MD5(master_secret + SHA(`BB' + master_secret +
ServerHello.random +
ClientHello.random)) +
MD5(master_secret + SHA(`CCC' + master_secret +
ServerHello.random +
ClientHello.random)) + [...];
key_blockの長さは理論上無限長ですから、A
、BB
、CCC
の次はDDDD
、EEEEE
、FFFFFF
…と続くことになります。Z
(26字)まで行きついたらその先はどうなるんでしょう。未定義です。まあ、そこまで行くことはないのでしょう。
key_block = PRF(SecurityParameters.master_secret,
"key expansion",
SecurityParameters.server_random +
SecurityParameters.client_random);
PRFが導入されてすっきりしました。
ServerKeyExchange, ClientVerifyの署名関数
署名といえばRSAかDSAですが、それと組み合わせて使うハッシュ関数がTLS1.1以前とTLS1.2で違います。
enum { anonymous, rsa, dsa } SignatureAlgorithm;
digitally-signed struct {
select(SignatureAlgorithm) {
case anonymous: struct { };
case rsa:
opaque md5_hash[16];
opaque sha_hash[20];
case dsa:
opaque sha_hash[20];
};
} Signature;
署名方式がRSAかDSAかでハッシュ方式が変わります。RSAの場合はMD5とSHA-1の組み合わせで、DSAの場合はSHA-1です。
このハッシュ値をそのまま署名します。RSAの場合でもRFC3447 Appendix A.2.4形式は使いません。
struct {
SignatureAndHashAlgorithm algorithm;
opaque signature<0..2^16-1>;
} DigitallySigned;
SignatureAndHashAlgorithm
は、IANAの署名番号表とハッシュ関数番号表から一つずつ選んで指定します。デフォルトという概念はなく、必ず何か指定しなければなりません。
RSAの場合は、TLS1.1以前と異なり、ハッシュ値をRFC3447 Appendix A.2.4のDigestInfo形式でカプセル化し、DERエンコードしてからRSA署名します。DSAの場合はハッシュ値をそのまま署名します。
MAC
hash(MAC_write_secret + pad_2 +
hash(MAC_write_secret + pad_1 + seq_num +
SSLCompressed.type + SSLCompressed.length +
SSLCompressed.fragment));
seq_numは、このメッセージのシーケンス番号です。
SSL3.0のMACはRFC2104 (Google翻訳)のHMACではなく独自の定義です。SSL3.0が公開された1995年には、RFC2104はありませんでした。時代を感じさせます。
MAC(MAC_write_key, seq_num +
TLSCompressed.type +
TLSCompressed.version +
TLSCompressed.length +
TLSCompressed.fragment);
TLS1.0以降のMACは、RFC2104 (Google翻訳)のHMACです。
key_blockからのパラメータ取り出し
client_write_MAC_secret[SecurityParameters.hash_size]
server_write_MAC_secret[SecurityParameters.hash_size]
client_write_key[SecurityParameters.key_material_length]
server_write_key[SecurityParameters.key_material_length]
client_write_IV[SecurityParameters.IV_size]
server_write_IV[SecurityParameters.IV_size]
先頭から順に、MACの秘密鍵、暗号通信の秘密鍵、CBCのIVです。
client_write_MAC_secret[SecurityParameters.hash_size]
server_write_MAC_secret[SecurityParameters.hash_size]
client_write_key[SecurityParameters.key_material_length]
server_write_key[SecurityParameters.key_material_length]
CBCの仕様が変わった影響で、client_write_IV
とserver_write_IV
がなくなりました。IVはレコードレイヤに新設されたフィールドを使って伝送します。
client_write_MAC_key[SecurityParameters.mac_key_length]
server_write_MAC_key[SecurityParameters.mac_key_length]
client_write_key[SecurityParameters.enc_key_length]
server_write_key[SecurityParameters.enc_key_length]
client_write_IV[SecurityParameters.fixed_iv_length]
server_write_IV[SecurityParameters.fixed_iv_length]
client_write_IV
とserver_write_IV
は、TLS1.0のCBCのIVが復活したのではなく、AEAD(認証つき暗号)で使うIVです。
輸出用暗号に使う秘密鍵
TLS1.1以降は輸出用暗号が廃止されたため、この定義はありません。SSL3.0とTLS1.0にだけあります。
final_client_write_key = MD5(client_write_key +
ClientHello.random +
ServerHello.random);
final_server_write_key = MD5(server_write_key +
ServerHello.random +
ClientHello.random);
client_write_IV = MD5(ClientHello.random + ServerHello.random);
server_write_IV = MD5(ServerHello.random + ClientHello.random);
暗号化に使う秘密鍵とCBCのIVです。MACは暗号化ではないので長い鍵でも輸出規制違反にならないらしく、key_blockから取り出した値をそのまま使います。
final_client_write_key =
PRF(SecurityParameters.client_write_key,
"client write key",
SecurityParameters.client_random +
SecurityParameters.server_random);
final_server_write_key =
PRF(SecurityParameters.server_write_key,
"server write key",
SecurityParameters.client_random +
SecurityParameters.server_random);
考えてみればCBCも暗号化ではないから輸出規制は関係ないよね、ということらしく、TLS1.0では輸出用の短いIVが廃止されました。key_blockから取り出した長いIVをそのまま使います。
ハンドシェイクプロトコル詳細
ここでは、SSL3.0~TLS1.2で変更のあったメッセージのみ説明します。
ServerKeyExchange
ServerKeyExchangeは、一時鍵Diffie-Hellman方式 (DHE, ECDHE) のほか、FORTEZZA(SSL3.0のみ)、RSA_EXPORTの一時鍵RSA (SSL3.0, TLS1.0) で使われます。改ざんを防ぐため、サーバ鍵による署名が付与されます。
struct {
select (KeyExchangeAlgorithm) {
case diffie_hellman:
ServerDHParams params;
Signature signed_params;
case rsa:
ServerRSAParams params;
Signature signed_params;
case fortezza_kea:
ServerFortezzaParams params;
};
} ServerKeyExchange;
paramsに対応する署名がsigned_paramsです。DH_anonの場合は、署名はつきません。
struct {
select (KeyExchangeAlgorithm) {
case diffie_hellman:
ServerDHParams params;
Signature signed_params;
case rsa:
ServerRSAParams params;
Signature signed_params;
};
} ServerKeyExchange;
FORTEZZAが廃止されました。
struct {
select (KeyExchangeAlgorithm) {
case diffie_hellman:
ServerDHParams params;
Signature signed_params;
case rsa:
ServerRSAParams params;
Signature signed_params;
};
} ServerKeyExchange;
TLS1.0と変わっておりません。しかし、RFC4346には確かにこの通り書いてあるのですが、輸出用暗号が廃止されたはずのTLS1.1で、KeyExchangeAlgorithmがrsaの場合なんてあるんでしょうか。ここはよく分かりません。case rsa:
のくだりは無視していいのかもしれません。
struct {
select (KeyExchangeAlgorithm) {
case dh_anon:
ServerDHParams params;
case dhe_dss:
case dhe_rsa:
ServerDHParams params;
digitally-signed struct {
opaque client_random[32];
opaque server_random[32];
ServerDHParams params;
} signed_params;
case rsa:
case dh_dss:
case dh_rsa:
struct {} ;
/* message is omitted for rsa, dh_dss, and dh_rsa */
/* may be extended, e.g., for ECDH -- see [TLSECC] */
};
} ServerKeyExchange;
signed_paramsの中身がちょっと変わり、client_randomとserver_randomを付け加えてから署名することになりました。また、TLS1.2のdigitally-signed
には署名方式とハッシュ方式を表すフィールドがつきます(署名関数のところで説明済みですね)。
それ以外は、書きっぷりこそ大幅に変わりましたが中身は変わっておりません。
CertificateRequest
struct {
ClientCertificateType certificate_types<1..2^8-1>;
DistinguishedName certificate_authorities<3..2^16-1>;
} CertificateRequest;
証明書種別とDNのリストです。証明書種別の指定はIANAの番号表を使います。クライアントは、この条件に合致した証明書を送らなければなりません。
ところで、IANAの番号表をよくみると、rsa_ephemeral_dh_RESERVED, dss_ephemeral_dh_RESERVEDという項目がありますが、これはたぶんSSL3.0の仕様策定時に誤って定義されてしまったものだと思います。実際には使いません。
struct {
ClientCertificateType certificate_types<1..2^8-1>;
DistinguishedName certificate_authorities<0..2^16-1>;
} CertificateRequest;
certificate_authoritiesの個数の下限が3から0になりました。0は無指定で、この場合はクライアント証明書は何でも構わないということです。しかしTLS1.0以前の3という数字には何の意味があったんでしょうか。ここはよく分かりません。
struct {
ClientCertificateType certificate_types<1..2^8-1>;
SignatureAndHashAlgorithm
supported_signature_algorithms<2^16-1>;
DistinguishedName certificate_authorities<0..2^16-1>;
} CertificateRequest;
supported_signature_algorithmsパラメータが追加されました。CertificateVerifyの署名方式は、この中から選択しなければなりません。
CertificateVerify
これは、クライアント証明書を使う場合に、クライアントが本当にクライアント秘密鍵を持っていることを証明するためのものです。したがって署名対象の平文には大した意味はありません。重要なのは、署名が正確かどうかです。
struct {
Signature signature;
} CertificateVerify;
CertificateVerify.signature.md5_hash
MD5(master_secret + pad_2 +
MD5(handshake_messages + master_secret + pad_1));
Certificate.signature.sha_hash
SHA(master_secret + pad_2 +
SHA(handshake_messages + master_secret + pad_1));
ここまでの間に送受信されたハンドシェークメッセージ、master_secret、パディングを組み合わせたバイト列を平文とします。署名に使うハッシュ関数は、上述のとおりMD5とSHA-1の組み合わせです。
struct {
Signature signature;
} CertificateVerify;
CertificateVerify.signature.md5_hash
MD5(handshake_messages);
Certificate.signature.sha_hash
SHA(handshake_messages);
SSL3.0と違い、master_secretやパディングを使わないhandshake_messageだけのシンプルな定義になりました。
struct {
digitally-signed struct {
opaque handshake_messages[handshake_messages_length];
}
} CertificateVerify;
ハッシュ方式がMD5とSHA-1の組み合わせではなく、digitally-signedに含まれるSignatureAndHashAlgorithmパラメータで指定する形になりました。このパラメータは、CertificateRequestのsupported_signature_algorithmsで指定された範囲内で選ばなければなりません。また、signature algorithms拡張がある場合には、その範囲にも納まっていなければなりません。
Finished
enum { client(0x434C4E54), server(0x53525652) } Sender;
struct {
opaque md5_hash[16];
opaque sha_hash[20];
} Finished;
md5_hash: MD5(master_secret + pad2 + MD5(handshake_messages + Sender
+ master_secret + pad1));
sha_hash: SHA(master_secret + pad2 + SHA(handshake_messages + Sender
+ master_secret + pad1));
SSL3.0のFinishedはMD5とSHA-1の組み合わせで36バイトもあります。
PRF(master_secret, finished_label, MD5(handshake_messages) +
SHA-1(handshake_messages)) [0..11];
finished_label引数は、クライアント送出時は"client finished"
、サーバ送出時は"server finished"
です。
SSL3.0に比べると、ハッシュ関数そのものは相変わらずMD5とSHA-1ですが、PRFを併用することで長さ12バイトの短い値になりました。
PRF(master_secret, finished_label, Hash(handshake_messages))
[0..verify_data_length-1];
verify_data_lengthは、デフォルトは12バイト、デフォルト以外はcipher suiteで指定です。
HashはPRFの定義に出てきたハッシュと同じで、デフォルトはSHA256、デフォルト以外はcipher suiteで指定です。
最後に
Q. めんどくさすぎる。これ本当に覚えないとダメか。
A. TLS1.0もTLS1.1も、厳しく言えばTLS1.2も、限界のあるプロトコルです。近年いろいろな脆弱性が発表されて運用変更やアップデートなど苦労が多いかと思いますが、この傾向は今後もまだまだ続くと考えなければなりません。
というわけで、何かあったときに問題を正確に理解し、適切に対応できるように、本稿の内容はなるべく頭のどこか片隅にでも入れておくようにして下さい。ただし、拙稿「SSL3.0, TLS1.0~1.2のハンドシェイクを復習する」は、繰り返しますが確実に徹底的に全部理解しなければなりません(断言) 。TLS1.3がもうすぐ出るとはいえ、1.2以前のTLSもまだまだ当分は現役ですからね!
参考文献
不肖私 (2016)
SSL3.0, TLS1.0~1.2のハンドシェイクを復習する
https://qiita.com/n-i-e/items/41673fd16d7bd1189a29
Moeller, B. (2004)
Security of CBC Ciphersuites in SSL/TLS: Problems and Countermeasures
http://www.openssl.org/~bodo/tls-cbc.txt (Google翻訳)
IANA
Transport Layer Security (TLS) Parameters
http://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml
The Secure Sockets Layer (SSL) Protocol Version 3.0
https://tools.ietf.org/html/rfc6101 (Google翻訳)
The TLS Protocol Version 1.0
https://tools.ietf.org/html/rfc2246 (Google翻訳)
Public-Key Cryptography Standards (PKCS) #1: RSA Cryptography Specifications Version 2.1
https://tools.ietf.org/html/rfc3447 (Google翻訳)
Transport Layer Security (TLS) Extensions
https://tools.ietf.org/html/rfc3546 (Google翻訳)
The Transport Layer Security (TLS) Protocol Version 1.1
https://tools.ietf.org/html/rfc4346 (Google翻訳)
Transport Layer Security (TLS) Extensions
https://tools.ietf.org/html/rfc4366 (Google翻訳)
The Transport Layer Security (TLS) Protocol Version 1.2
https://tools.ietf.org/html/rfc5246 (Google翻訳)