CLOVER🍀

That was when it all began.

OpenSSLでの暗号スイートと指定方法を確認する(+Apache、nginxでのTLSv1.3 IPAガイド設定例含む))

これは、なにをしたくて書いたもの?

Apacheやnginxの設定を見ていて、Cipher Suiteに指定している文字列の意味があんまりわかってないなと思い。

この機会に、少し見ておきたいな、と。

環境

今回の環境は、こちら。

$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 20.04.3 LTS
Release:    20.04
Codename:   focal


$ uname -srvmpio
Linux 5.4.0-81-generic #91-Ubuntu SMP Thu Jul 15 19:09:17 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux

OpenSSLのバージョンは、こちら。

$ openssl version
OpenSSL 1.1.1f  31 Mar 2020

ApacheをHTTPS化(SSL/TLS化)してみる

試しに、ApacheをインストールしてHTTPS化してみましょう。

$ sudo apt install apache2
$ sudo a2enmod ssl
$ sudo a2ensite default-ssl

デフォルトのSSLの設定はこちら。

$ grep -v '.*#' /etc/apache2/mods-enabled/ssl.conf
<IfModule mod_ssl.c>

    SSLRandomSeed startup builtin
    SSLRandomSeed startup file:/dev/urandom 512
    SSLRandomSeed connect builtin
    SSLRandomSeed connect file:/dev/urandom 512


    AddType application/x-x509-ca-cert .crt
    AddType application/x-pkcs7-crl .crl

    SSLPassPhraseDialog  exec:/usr/share/apache2/ask-for-passphrase

    SSLSessionCache     shmcb:${APACHE_RUN_DIR}/ssl_scache(512000)
    SSLSessionCacheTimeout  300



    SSLCipherSuite HIGH:!aNULL


    SSLProtocol all -SSLv3



</IfModule>

ここで、SSLCipherSuiteと書かれている部分ですね。

  SSLCipherSuite HIGH:!aNULL

Apache Module mod_ssl / SSLCipherSuite Directive

ちなみに、nginxだとデフォルト値はこちらのようです。

Default: 
ssl_ciphers HIGH:!aNULL:!MD5;

Module ngx_http_ssl_module / ssl_ciphers

今回は、このあたりの意味を知りたいな、という話です。

これらの値は、実際にどのような暗号アルゴリズムが含まれるか、opensslコマンドで確認することができます。
こんな感じですね。

$ openssl ciphers -v 'HIGH:!aNULL'
TLS_AES_256_GCM_SHA384  TLSv1.3 Kx=any      Au=any  Enc=AESGCM(256) Mac=AEAD
TLS_CHACHA20_POLY1305_SHA256 TLSv1.3 Kx=any      Au=any  Enc=CHACHA20/POLY1305(256) Mac=AEAD
TLS_AES_128_GCM_SHA256  TLSv1.3 Kx=any      Au=any  Enc=AESGCM(128) Mac=AEAD
ECDHE-ECDSA-AES256-GCM-SHA384 TLSv1.2 Kx=ECDH     Au=ECDSA Enc=AESGCM(256) Mac=AEAD
ECDHE-RSA-AES256-GCM-SHA384 TLSv1.2 Kx=ECDH     Au=RSA  Enc=AESGCM(256) Mac=AEAD

〜省略〜


$ openssl ciphers -v 'HIGH:!aNULL:!MD5'
TLS_AES_256_GCM_SHA384  TLSv1.3 Kx=any      Au=any  Enc=AESGCM(256) Mac=AEAD
TLS_CHACHA20_POLY1305_SHA256 TLSv1.3 Kx=any      Au=any  Enc=CHACHA20/POLY1305(256) Mac=AEAD
TLS_AES_128_GCM_SHA256  TLSv1.3 Kx=any      Au=any  Enc=AESGCM(128) Mac=AEAD
ECDHE-ECDSA-AES256-GCM-SHA384 TLSv1.2 Kx=ECDH     Au=ECDSA Enc=AESGCM(256) Mac=AEAD
ECDHE-RSA-AES256-GCM-SHA384 TLSv1.2 Kx=ECDH     Au=RSA  Enc=AESGCM(256) Mac=AEAD

〜省略〜

それぞれ、128個の暗号アルゴリズムが含まれています。

$ openssl ciphers -v 'HIGH:!aNULL' | wc -l
128


$ openssl ciphers -v 'HIGH:!aNULL:!MD5' | wc -l
128

つまり、OpenSSL向けの設定をそのまま書いていることになります。

この指定の意味をもうちょっと見てみよう、というのが今回のテーマです。

OpenSSLでサポートされている暗号スイートを見る

OpenSSLでサポートされている暗号スイートは、openssl ciphers -vで見ることができます。

$ openssl ciphers -v

ciphers / Options

/docs/man1.1.1/man3/SSL_CIPHER_description.html

なのですが、この後ろにキーワードを付与することができ、これでリストする暗号スイートを指定できるようです。

たとえばDEFAULT。

$ openssl ciphers -v 'DEFAULT'
TLS_AES_256_GCM_SHA384  TLSv1.3 Kx=any      Au=any  Enc=AESGCM(256) Mac=AEAD
TLS_CHACHA20_POLY1305_SHA256 TLSv1.3 Kx=any      Au=any  Enc=CHACHA20/POLY1305(256) Mac=AEAD
TLS_AES_128_GCM_SHA256  TLSv1.3 Kx=any      Au=any  Enc=AESGCM(128) Mac=AEAD
ECDHE-ECDSA-AES256-GCM-SHA384 TLSv1.2 Kx=ECDH     Au=ECDSA Enc=AESGCM(256) Mac=AEAD
ECDHE-RSA-AES256-GCM-SHA384 TLSv1.2 Kx=ECDH     Au=RSA  Enc=AESGCM(256) Mac=AEAD
DHE-RSA-AES256-GCM-SHA384 TLSv1.2 Kx=DH       Au=RSA  Enc=AESGCM(256) Mac=AEAD
ECDHE-ECDSA-CHACHA20-POLY1305 TLSv1.2 Kx=ECDH     Au=ECDSA Enc=CHACHA20/POLY1305(256) Mac=AEAD
ECDHE-RSA-CHACHA20-POLY1305 TLSv1.2 Kx=ECDH     Au=RSA  Enc=CHACHA20/POLY1305(256) Mac=AEAD
DHE-RSA-CHACHA20-POLY1305 TLSv1.2 Kx=DH       Au=RSA  Enc=CHACHA20/POLY1305(256) Mac=AEAD
ECDHE-ECDSA-AES128-GCM-SHA256 TLSv1.2 Kx=ECDH     Au=ECDSA Enc=AESGCM(128) Mac=AEA

〜省略〜

これは、文字通りデフォルトの暗号スイートで、コンパイル時に決定されるもののようです。

$ openssl ciphers -v 'DEFAULT' | wc -l
60

ALL:!COMPLEMENTOFDEFAULT:!eNULLと同じなのだとか。

The content of the default list is determined at compile time and normally corresponds to ALL:!COMPLEMENTOFDEFAULT:!eNULL.

こうやって見ていると、HIGH:!aNULL:!MD5やALL:!COMPLEMENTOFDEFAULT:!eNULLはどういう意味なのか?という
感じがしてきますね。

これは、暗号リストと呼ぶ形式で、暗号リストはひとつ以上の暗号文字列から構成されます。

The cipher list consists of one or more cipher strings separated by colons. Commas or spaces are also acceptable separators but colons are normally used.

ciphers / Cipher List Format

暗号文字列の区切り文字は通常コロン(:)ですが、カンマまたはスペースも指定可能みたいです。

暗号文字列は、いくつかの形式があり、暗号文字列の集合を指す暗号スイートを指定することもできます。
DEFAULTやALLは、暗号スイートです。

暗号スイートのリストには、修飾子が使用できます。

  • + … 暗号リストの末尾に暗号スイートを追加する。暗号リストにすでに含まれている暗号スイートがある場合は、無視される
    • デフォルトの動作であり、修飾子がなにも指定されていない場合はこの動作となる
  • - … 指定された暗号スイートを暗号リストから除去する。ただし、あとから再追加が可能
  • ! … 指定された暗号スイートを暗号リストから削除し、再追加も不可とする
  • + … 指定された暗号スイートを暗号リストの末尾に移動する

原文。

  • Lists of cipher suites can be combined in a single cipher string using the + character. This is used as a logical and operation. For example SHA1+DES represents all cipher suites containing the SHA1 and the DES algorithms.
  • If ! is used then the ciphers are permanently deleted from the list. The ciphers deleted can never reappear in the list even if they are explicitly stated.
  • If - is used then the ciphers are deleted from the list, but some or all of the ciphers can be added again by later options.
  • If + is used then the ciphers are moved to the end of the list. This option doesn't add any new ciphers it just moves matching existing ones.

@STRENGTHは、暗号強度の降順で暗号スイートを並び替えます。

暗号文字列については、こちら。この中に暗号スイートも含まれます。

ciphers / Cipher Strings

たとえば、ALLやHIGH。

$ openssl ciphers -v 'ALL' | wc -l
144


$ openssl ciphers -v 'HIGH' | wc -l
140

いつか、並べてみましょう。

  • ALL … eNULLを除く、すべての暗号スイート
  • COMPLEMENTOFDEFAULT … ALLに含まれている暗号のうち、デフォルトでは有効になっていないもの
  • COMPLEMENTOFALL … ALLで有効になっていない暗号スイート。現在はeNULLと同等
  • HIGH … 高度な暗号スイート。鍵の長さが128ビット以上のものが含まれる
  • MEDIUM … 中程度の暗号スイート。128ビットの暗号化を使うものが含まれる
  • LOW … 低級な暗号スイート。64ビットまたは56ビットの暗号化アルゴリズムを使用しているが、輸出暗号スイートを除く。安全ではない
  • TLSv1.2、TLSv1.0、SSLv3 … TLSv1.2、TLSv1.0、SSLv3.0でそれぞれサポートされている暗号スイート
  • MD5 … MD5を使用する暗号スイート。安全ではない
  • SHA1、SHA … SHA-1を使用する暗号スイート
  • SHA256、SHA3​​84 … SHA-256、SHA-384を使用する暗号スイート
  • AES128、AES256、AES … 128ビットAES、256ビットAES、128ビットまたは256ビットAESを使用する暗号スイート
  • 3DES … 3DESを使用する暗号スイート
  • RC4 … RC4を使用する暗号スイート
  • aNULL … 認証を提供しない暗号スイート。現在は、匿名DHアルゴリズムと匿名ECDHアルゴリズムを指す。安全ではない。また、DEFAULTには含まれないがALLには含まれる
  • eNULL、NULL … 暗号化を行わない暗号スイート。安全ではない。DEFAULTでもALLでも有効にされない

全部はとても載せられないので、他はドキュメント参照…。

ここまで読んでみると。

たとえば手元のOpenSSLで使えるすべての暗号スイートを表示するには、以下を指定すればよいことになります。

$ openssl ciphers -v 'ALL:COMPLEMENTOFALL'

nginxのssl_ciphersのデフォルトは、HIGHな暗号スイートからaNULL、MD5に関する暗号スイートを除いたもの、
と読むことができますね。

Default: 
ssl_ciphers HIGH:!aNULL:!MD5;

こんな感じですね。とはいえ、今のバージョンだとHIGHの中にMD5に関するものは含まれていないようですが。

$ openssl ciphers -v 'HIGH' | wc -l
140


$ openssl ciphers -v 'HIGH:!aNULL' | wc -l
128


$ openssl ciphers -v 'HIGH:!aNULL:!MD5' | wc -l
128

その他のサンプルは、こちらを参照。

ciphers / Examples

ところで、openssl ciphers -vで指定するものにTLSv1.3のものを指定するとエラーになるんですけど、なんでなんでしょうね?

こんな感じでリストして

$ openssl ciphers -v -s -tls1_3
TLS_AES_256_GCM_SHA384  TLSv1.3 Kx=any      Au=any  Enc=AESGCM(256) Mac=AEAD
TLS_CHACHA20_POLY1305_SHA256 TLSv1.3 Kx=any      Au=any  Enc=CHACHA20/POLY1305(256) Mac=AEAD
TLS_AES_128_GCM_SHA256  TLSv1.3 Kx=any      Au=any  Enc=AESGCM(128) Mac=AEAD


$ openssl ciphers -v -s -tls1_2
ECDHE-ECDSA-AES256-GCM-SHA384 TLSv1.2 Kx=ECDH     Au=ECDSA Enc=AESGCM(256) Mac=AEAD
ECDHE-RSA-AES256-GCM-SHA384 TLSv1.2 Kx=ECDH     Au=RSA  Enc=AESGCM(256) Mac=AEAD
DHE-RSA-AES256-GCM-SHA384 TLSv1.2 Kx=DH       Au=RSA  Enc=AESGCM(256) Mac=AEAD
ECDHE-ECDSA-CHACHA20-POLY1305 TLSv1.2 Kx=ECDH     Au=ECDSA Enc=CHACHA20/POLY1305(256) Mac=AEAD
ECDHE-RSA-CHACHA20-POLY1305 TLSv1.2 Kx=ECDH     Au=RSA  Enc=CHACHA20/POLY1305(256) Mac=AEAD
DHE-RSA-CHACHA20-POLY1305 TLSv1.2 Kx=DH       Au=RSA  Enc=CHACHA20/POLY1305(256) Mac=AEAD
ECDHE-ECDSA-AES128-GCM-SHA256 TLSv1.2 Kx=ECDH     Au=ECDSA Enc=AESGCM(128) Mac=AEAD
ECDHE-RSA-AES128-GCM-SHA256 TLSv1.2 Kx=ECDH     Au=RSA  Enc=AESGCM(128) Mac=AEAD
DHE-RSA-AES128-GCM-SHA256 TLSv1.2 Kx=DH       Au=RSA  Enc=AESGCM(128) Mac=AEAD
ECDHE-ECDSA-AES256-SHA384 TLSv1.2 Kx=ECDH     Au=ECDSA Enc=AES(256)  Mac=SHA384
ECDHE-RSA-AES256-SHA384 TLSv1.2 Kx=ECDH     Au=RSA  Enc=AES(256)  Mac=SHA384
DHE-RSA-AES256-SHA256   TLSv1.2 Kx=DH       Au=RSA  Enc=AES(256)  Mac=SHA256
ECDHE-ECDSA-AES128-SHA256 TLSv1.2 Kx=ECDH     Au=ECDSA Enc=AES(128)  Mac=SHA256
ECDHE-RSA-AES128-SHA256 TLSv1.2 Kx=ECDH     Au=RSA  Enc=AES(128)  Mac=SHA256
DHE-RSA-AES128-SHA256   TLSv1.2 Kx=DH       Au=RSA  Enc=AES(128)  Mac=SHA256
ECDHE-ECDSA-AES256-SHA  TLSv1 Kx=ECDH     Au=ECDSA Enc=AES(256)  Mac=SHA1
ECDHE-RSA-AES256-SHA    TLSv1 Kx=ECDH     Au=RSA  Enc=AES(256)  Mac=SHA1
DHE-RSA-AES256-SHA      SSLv3 Kx=DH       Au=RSA  Enc=AES(256)  Mac=SHA1
ECDHE-ECDSA-AES128-SHA  TLSv1 Kx=ECDH     Au=ECDSA Enc=AES(128)  Mac=SHA1
ECDHE-RSA-AES128-SHA    TLSv1 Kx=ECDH     Au=RSA  Enc=AES(128)  Mac=SHA1
DHE-RSA-AES128-SHA      SSLv3 Kx=DH       Au=RSA  Enc=AES(128)  Mac=SHA1
AES256-GCM-SHA384       TLSv1.2 Kx=RSA      Au=RSA  Enc=AESGCM(256) Mac=AEAD
AES128-GCM-SHA256       TLSv1.2 Kx=RSA      Au=RSA  Enc=AESGCM(128) Mac=AEAD
AES256-SHA256           TLSv1.2 Kx=RSA      Au=RSA  Enc=AES(256)  Mac=SHA256
AES128-SHA256           TLSv1.2 Kx=RSA      Au=RSA  Enc=AES(128)  Mac=SHA256
AES256-SHA              SSLv3 Kx=RSA      Au=RSA  Enc=AES(256)  Mac=SHA1
AES128-SHA              SSLv3 Kx=RSA      Au=RSA  Enc=AES(128)  Mac=SHA1

こうなってしまいます。

$ openssl ciphers -v 'TLS_AES_128_GCM_SHA256'
Error in cipher list
139825285784896:error:1410D0B9:SSL routines:SSL_CTX_set_cipher_list:no cipher match:../ssl/ssl_lib.c:2564:


$ openssl ciphers -v 'AES128-GCM-SHA256'
TLS_AES_256_GCM_SHA384  TLSv1.3 Kx=any      Au=any  Enc=AESGCM(256) Mac=AEAD
TLS_CHACHA20_POLY1305_SHA256 TLSv1.3 Kx=any      Au=any  Enc=CHACHA20/POLY1305(256) Mac=AEAD
TLS_AES_128_GCM_SHA256  TLSv1.3 Kx=any      Au=any  Enc=AESGCM(128) Mac=AEAD
AES128-GCM-SHA256       TLSv1.2 Kx=RSA      Au=RSA  Enc=AESGCM(128) Mac=AEAD

TLS 1.3でのIPA推奨の暗号スイートをApache、nginxに設定する

ところで、実際の指定はどうしたらいいんでしょうね。

これは、IPAのTLS暗号設定ガイドラインを見るのが良いのでしょう。

TLS暗号設定ガイドライン~安全なウェブサイトのために(暗号設定対策編)~:IPA 独立行政法人 情報処理推進機構

暗号スイートの設定という、そのものズバリなものもあります。

https://www.ipa.go.jp/security/ipg/documents/tls_cipher_suite_config_20200707.pdf

推奨セキュリティ型、セキュリティ例外型の設定例
TLS_AES_128_GCM_SHA256:TLS_AES_128_CCM_SHA256:TLS_AES_128_CCM_8_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256

設定方法まで書いてあります。

f:id:Kazuhira:20210830121919p:plain

こちらを使って、Apache、nginxそれぞれにTLSv1.3の推奨暗号スイートを設定してみましょう。

Apacheの場合、TLSv1.3向けの暗号スイートを指定する時はSSLCipherSuiteでの設定時にTLSv1.3という指定が必要です。

以下は推奨セキュリティ型、セキュリティ例外型の設定例の場合。

        SSLCipherSuite TLSv1.3 "TLS_AES_128_GCM_SHA256:TLS_AES_128_CCM_SHA256:TLS_AES_128_CCM_8_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256"

以下は高セキュリティ型の場合。

        SSLCipherSuite TLSv1.3 "TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256:TLS_AES_128_CCM_SHA256:TLS_AES_128_CCM_8_SHA256"

SSLCipherSuiteでTLSv1.3と指定しているのは、このディレクティブでのprotocolにあたる部分ですね。

Syntax: SSLCipherSuite [protocol] cipher-spec

TLSv1.3については、以下に捕捉があります。

If the SSL library supports TLSv1.3 (OpenSSL 1.1.1 and later), the protocol specifier "TLSv1.3" can be used to configure the cipher suites for that protocol. Since TLSv1.3 does not offer renegotiations, specifying ciphers for it in a directory context is not allowed.

Apache Module mod_ssl / SSLCipherSuite Directive

nginxも設定してみましょう。

インストール。

$ sudo apt install curl gnupg2 ca-certificates lsb-release ubuntu-keyring
$ curl https://nginx.org/keys/nginx_signing.key | gpg --dearmor \
    | sudo tee /usr/share/keyrings/nginx-archive-keyring.gpg >/dev/null
$ gpg --dry-run --quiet --import --import-options import-show /usr/share/keyrings/nginx-archive-keyring.gpg
$ echo "deb [signed-by=/usr/share/keyrings/nginx-archive-keyring.gpg] \
   http://nginx.org/packages/ubuntu `lsb_release -cs` nginx" \
    | sudo tee /etc/apt/sources.list.d/nginx.list
$ echo -e "Package: *\nPin: origin nginx.org\nPin: release o=nginx\nPin-Priority: 900\n" \
    | sudo tee /etc/apt/preferences.d/99nginx
$ sudo apt update
$ sudo apt install nginx

バージョン。

$ nginx -v
nginx version: nginx/1.20.1

SSL/TLS証明書の作成。

$ sudo openssl genrsa -aes-128-cbc -out /etc/nginx/server.key 2048
$ sudo openssl rsa -in /etc/nginx/server.key -out /etc/nginx/server.key
$ sudo openssl req -new -key /etc/nginx/server.key -out /etc/nginx/server.csr
$ sudo openssl x509 -req -days 365 -in /etc/nginx/server.csr -signkey /etc/nginx/server.key -out /etc/nginx/server.crt

設定。

/etc/nginx/conf.d/ssl.conf

server {
    listen       443 ssl;
    server_name  www.example.com;

    ssl_certificate               server.crt;
    ssl_certificate_key           server.key;
    ssl_protocols                 TLSv1.3;
    ssl_prefer_server_ciphers     on;
    ssl_conf_command Ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_128_CCM_SHA256:TLS_AES_128_CCM_8_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256;
    
    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
    }
}

以下は推奨セキュリティ型、セキュリティ例外型の設定例の場合。

    ssl_conf_command Ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_128_CCM_SHA256:TLS_AES_128_CCM_8_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256;

nginxでの暗号スイートの指定はssl_ciphersで行うのですが、TLSv1.3の場合はssl_ciphersで指定できずエラーになります。
これを回避するにはssl_conf_command Ciphersuitesで代替するようです。

#1529 (Could not configure TLS1.3 ciphers in OpenSSL 1.1.1 pre4) – nginx

IPAの資料でも、nginxの場合はTLSv1.2の指定方法だけが書かれていますからね。

以下は高セキュリティ型の場合。

    ssl_conf_command Ciphersuites TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256:TLS_AES_128_CCM_SHA256:TLS_AES_128_CCM_8_SHA256;

まとめ

OpenSSLでの暗号スイートの指定について、ちょっと調べてみました。

ちゃんと使える気はとてもしませんが、このあたりの情報を足がかりにして見ていけるようにしていきましょうか…。