LINE株式会社は、2023年10月1日にLINEヤフー株式会社になりました。LINEヤフー株式会社の新しいブログはこちらです。 LINEヤフー Tech Blog

Blog


Best practices to secure your SSL/TLS Implementation

この記事は、 LINE Engineering Blog 「夏休みの自由研究 -Summer Homework-」 の 11日目の記事です。

こんにちは、LINE Infra Protection チームのSecurity Engineeringを担当しているLee Jihoon(李志勲)です。

Infra Protectionチームは、業務環境とサービス環境で使用されるインフラを、より安全に作る業務を担当しています。そのような活動の一つとして、SSL/TLS証明書に対する管理や関連ガイドを提供する活動も行っています。大規模な環境ではセキュリティはもちろん、クライアントに対する互換性とサービス環境についても考慮が必要ですが、それらに関連する経験の一部を共有します。

はじめに

まず、TLS(Transport Layer Security、以前のSSL。一般的な表記に従い以下 SSL/TLS と表記します)とは何か、簡単に解説することからスタートします。

ブラウザのようなクライアントとウェブサーバが、公開されたインターネット網を利用してコミュニケーションする際に、望む相手と安全につながるために、必要なセキュリティメカニズムを提供するインターネットプロトコルがTLSです。

技術的な要件についてはRFC 2246(TLS 1.0)、RFC 4346(TLS 1.1)、RFC 5246(TLS 1.2)まで、現在広く利用されているTLSプロトコルについて確認することができますが、持続的にセキュリティ水準が高まっています。 最近(2018/3/21)、Proposed Standardで定められたTLS 1.3には、既知のセキュリティ問題を追加で考慮した設計が含まれています。

代表的にHTTPがSSL/TLSを利用してHTTPS(HTTP over TLS)を提供しており、その他様々なインターネットプロトコルがSSL/TLSを利用し、安全なコネクションを提供しています。

SNMP、FTPなど、様々なインターネットプロトコルでSSL/TLSが利用されていますが、本記事ではnginxのようなウェブサーバを利用してウェブサービスを提供する場合を想定し、これに対する安全な設定との考慮事項に関する話をしようと思います。

また、本記事を読む方が、下記のような目的に活用できるように執筆しました。

  • 技術的に理解することができなくても、その通りに進めるだけで、安全な設定をしたい場合
    • 前半のStep by Stepの項目を確認すれば良いです
  • もっと内容を理解したい場合
    • まず記事の内容を読んでから、記事が引用している外部リンクや用語を確認してみることをお勧めします

Step by Step

便宜上 Linuxで nginxをウェブサーバで利用する場合を例に挙げて説明をします。 他のウェブサーバの場合、各設定ファイルのDirectiveについての文書を参照してください。

Step #1. Obtain a Certificate

まず、Comodo、DigiCert、GlobalSignなど、証明書発行​​サービスを提供する CAを通じて発行​​を受けたり、Let's Encryptのような無料で証明書を取得して準備をしておきます。

下は、infraprotection.line.me サービスで使用する証明書をLet's Encryptを通じて、個別の証明書を発行​​してもらう場合、下記の方法で発行​​が可能です。

Certbot Let's Encryptクライアントの設置

Certbotパッケージを設置します。

$ sudo yum install certbot

証明書の発行

  • 以下の前提条件を満足する環境かまず確認が必要です。
    • 前提条件
      DNSのAまたは CNAMEレコードで設定が可能
      ポート80番が外部からアクセス可能な状態
      本例題ではポート80番のwebrootディレクトリを /var/ssltest/に設定
      ドメインは infraprotection.line.meと仮定
      
    • 下は certbotクライアントのcertonlyコマンドオプションを利用して証明書を発行​​を受ける過程です。
      $ sudo certbot certonly --webroot -w /var/ssltest/ -d infraprotection.line.me
      Saving debug log to /var/log/letsencrypt/letsencrypt.log
      Enter email address (used for urgent renewal and security notices) (Enter 'c' to
      cancel): youremail (ex. lee.jihoon@linecorp.com)
      Starting new HTTPS connection (1): acme-v01.api.letsencrypt.org
      -------------------------------------------------------------------------------
      Please read the Terms of Service at
      https://letsencrypt.org/documents/LE-SA-v1.1.1-August-1-2016.pdf. You must agree
      in order to register with the ACME server at
      https://acme-v01.api.letsencrypt.org/directory
      -------------------------------------------------------------------------------
      (A)gree/(C)ancel:
      -------------------------------------------------------------------------------
      Would you be willing to share your email address with the Electronic Frontier
      Foundation, a founding partner of the Let's Encrypt project and the non-profit
      organization that develops Certbot? We'd like to send you email about EFF and
      our work to encrypt the web, protect its users and defend digital rights.
      -------------------------------------------------------------------------------
      (Y)es/(N)o:
      Obtaining a new certificate
      Performing the following challenges:
      http-01 challenge for infraprotection.line.me
      Using the webroot path /var/ssltest for all unmatched domains.
      Waiting for verification...
      Cleaning up challenges
      Generating key (2048 bits): /etc/letsencrypt/keys/0000_key-certbot.pem
      Creating CSR: /etc/letsencrypt/csr/0000_csr-certbot.pem
      IMPORTANT NOTES:
      - Congratulations! Your certificate and chain have been saved at
      /etc/letsencrypt/live/infraprotection.line.me/fullchain.pem. Your
      cert will expire on 2017-07-10. To obtain a new or tweaked version
      of this certificate in the future, simply run certbot again. To
      non-interactively renew *all* of your certificates, run "certbot
      renew"
      - If you like Certbot, please consider supporting our work by:
      Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate
      Donating to EFF: https://eff.org/donate-le
      
  • 生成された証明書ファイル情報
    • 生成されたファイルは以下の経路で確認できます。
      $ sudo ls -l /etc/letsencrypt/live/infraprotection.line.me/
      
  • 生成されたファイルはそれぞれ下記の内容です。
    • cert.pem:署名された証明書
    • chain.pem:Let's Encryptの中間証明書(intermediate certificate)
    • fullchain.pem:cert.pemファイルとchain.pemファイルを合わせたファイル
    • privkey.pem:証明書のprivate key
  • nginxのようなウェブサーバでは下記のように設定をすることになります。
    • nginxの基本的な設定の場合
      $ sudo vim /etc/nginx/conf.d/ssl.conf
      
      server{
        .....
        ssl_certificate/etc/letsencrypt/live/infraprotection.line.me/fullchain.pem;
        ssl_certificate_key/etc/letsencrypt/live/infraprotection.line.me/privkey.pem;
        .....
      }
      
    • nginx>=1.3.7で OCSP stapling機能を使用している場合
      $ sudo vim /etc/nginx/conf.d/ssl.conf
      
      server{
        .....
        ssl_stapling on;
        .....
        ssl_certificate/etc/letsencrypt/live/infraprotection.line.me/fullchain.pem;
        ssl_certificate_key/etc/letsencrypt/live/infraprotection.line.me/privkey.pem;
      
        ssl_trusted_certificate/etc/letsencrypt/live/infraprotection.line.me/chain.pem;
        .....
      }
      

自動更新設定

  • '--dry-run'パラメータを使用して、自動更新設定のコマンドに対してテストをしてみます。
    $ sudo certbot renew --dry-run
    Saving debug log to/var/log/letsencrypt/letsencrypt.log
    
    -------------------------------------------------------------------------------
    Processing/etc/letsencrypt/renewal/infraprotection.line.me.conf
    -------------------------------------------------------------------------------
    Cert not due for renewal、but simulating renewal for dry run
    Starting new HTTPS connection(1):acme-staging.api.letsencrypt.org
    Renewing an existing certificate
    Performing the following challenges:
    http-01 challenge for infraprotection.line.me
    Waiting for verification...
    Cleaning up challenges
    Generating key(2048 bits):/etc/letsencrypt/keys/0001_key-certbot.pem
    Creating CSR:/etc/letsencrypt/csr/0001_csr-certbot.pem
    
    -------------------------------------------------------------------------------
    new certificate deployed without reload、fullchain is
    /etc/letsencrypt/live/infraprotection.line.me/fullchain.pem
    -------------------------------------------------------------------------------
    **DRY RUN:simulating'certbot renew'close to cert expiry
    **(The test certificates below have not been saved.)
    
    Congratulations、all renewals succeeded.The following certs have been renewed:
    /etc/letsencrypt/live/infraprotection.line.me/fullchain.pem (success)
    **DRY RUN:simulating'certbot renew'close to cert expiry
    **(The test certificates above have not been saved.)
    
    IMPORTANT NOTES:
    -Your account credentials have been saved in your Certbot
    configuration directory at/etc/letsencrypt.You should make a
    secure backup of this folder now.This configuration directory will
    also contain certificates and private keys obtained by Certbot so
    making regular backups of this folder is ideal.
    
  • 動作に問題がない場合crontabに登録します。
    $ sudo crontab -e
    05 8、20 * * * /usr/bin/certbot renew --renew-hook "systemctl reload nginx"
    
    $ sudo crontab -l
    05 8、20 * * * /usr/bin/certbot renew --renew-hook "systemctl reload nginx"
    

付録)証明書管理

  • cerbotクライアントを通じて管理している証明書のリストを見るためには、、、
    $ sudo certbot certificates
    
  • 証明書の失効後、再発行・更新するためには、、、
    $ sudo certbot revoke --cert-path /etc/letsencrypt/live/infraprotection.line.me/cert.pem
    Saving debug log to/var/log/letsencrypt/letsencrypt.log
    Starting new HTTPS connection(1):acme-v01.api.letsencrypt.org
    
    -------------------------------------------------------------------------------
    Congratulations!You have successfully revoked the certificate that was located
    at/etc/letsencrypt/live/infraprotection.line.me/cert.pem
    -------------------------------------------------------------------------------
    
    $ sudo certbot certonly --webroot -w /var/ssltest/ -d infraprotection.line.me
    Saving debug log to/var/log/letsencrypt/letsencrypt.log
    Starting new HTTPS connection(1):acme-v01.api.letsencrypt.org
    Cert not yet due for renewal
    
    You have an existing certificate that has exactly the same domains or certificate name you requested and isn't close to expiry.
    (ref:/etc/letsencrypt/renewal/infraprotection.line.me.conf)
    
    What would you like to do?
    -------------------------------------------------------------------------------
    1:Keep the existing certificate for now
    2:Renew&replace the cert(limit~5 per 7 days)
    -------------------------------------------------------------------------------
    Select the appropriate number[1-2]then[enter](press'c'to cancel):2
    Renewing an existing certificate
    Performing the following challenges:
    http-01 challenge for infraprotection.line.me
    Using the webroot path/var/ssltest for all unmatched domains.
    Waiting for verification...
    Cleaning up challenges
    Generating key(2048 bits):/etc/letsencrypt/keys/0002_key-certbot.pem
    Creating CSR:/etc/letsencrypt/csr/0002_csr-certbot.pem
    
    IMPORTANT NOTES:
    -Congratulations!Your certificate and chain have been saved at
    /etc/letsencrypt/live/infraprotection.line.me/fullchain.pem. Your
    cert will expire on 2018-10-05.To obtain a new or tweaked version
    of this certificate in the future、simply run certbot again.To
    non-interactively renew*all*of your certificates、run"certbot
    renew"
    -If you like Certbot、please consider supporting our work by:
    
    Donating to ISRG/Let's Encrypt:https://letsencrypt.org/donate
    Donating to EFF:https://eff.org/donate-le
    
    $ sudo systemctl reload nginx
    

Step #2. Configure TLS/SSL on Web Server

Step #2-1. OpenSSLモジュールのアップデート

まず、現在のシステムで使用しているOpenSSLライブラリをアップデートします。

現時点(2018年8月)OpenSSLで配布されているバージョンの基準では、各シリーズ以下のバージョン、または運営体制が配布するパッケージの最新の安定バージョンにアップデートをしなければなりません。

Step #2-2. 設定の修正

プロトコルおよびcipher suiteの設定

nginxの設定を例に挙げて説明をします。

Category Directive
基本設定ファイル nginx.conf
SSLプロトコル ssl_protocols TLSv1.1 TLSv1.2;
SSL CipherSuite ssl_ciphers "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA";
その他の設定 ssl_prefer_server_ciphers on; ssl_session_cache shared:SSL:10m;

強力な鍵の長さを持つDHEアルゴリズム鍵の設定

「/path/to/openssl」: 通常「openssl」だけを入力すれば済みます。ただ、同じサーバに複数バージョンのOpenSSLをインストールしている場合は、最近アップデートしたOpenSSLのパスを使用します。例は以下のとおりです。

$ sudo /usr/local/openssl/bin/openssl dhparam 2048

1. DHパラメータファイルを生成
$ sudo /path/to/openssl dhparam -out /etc/pki/tls/dhparams.pem 2048
$ sudo chmod 600 /etc/pki/tls/dhparams.pem

2. Configファイルに生成されたDHパラメータを設定
$ sudo vim $SSL_CONFIGFILE (ex. /etc/nginx/nginx.conf)
ssl_dhparam /etc/pki/tls/dhparams.pem;

Step #2-3. Webサーバの再起動

Webサーバを再起動する前に、修正された設定ファイルのバリデーションチェックを行ってください。
Gracefulオプションで再起動する場合、新しくインストールされたOpenSSLライブラリが反映されません。そのため、「stop / restart」でマスタープロセスを再起動する必要があります。

Step #3. 確認

正しく設定されているかテストしてみましょう。
パブリックサーバの場合はQualys SSL Labs Serviceをご利用ください。Qualys SSL Labs Serviceの利用

https://www.ssllabs.com/ssltest/analyze.html?d=[Test Domain]&hideResults=on
  +「hideResults=on」で設定し、テストサイトのダッシュボードにテスト結果が表示されないように注意してください。

もっと理解したい方のため

Step by Stepで設定を問題なくできたら、上のStep#3で'A'レベルのテストの結果を見ることができます。しかし、もっと内容について理解して、より安全な設定をする場合には、次の内容を追加で確認してください。

セキュアな状態とは?

セキュリティメカニズム(Security Mechanisms)は基準を満たさない場合、攻撃者が悪用できる弱い状態になってしまうことがあります。このような基準を満たした状態が、安全な状態だと言えます。つまり、設定に神経を払わなければ、安全でない状態となることがありえるということです。

SSL/TLSの実現における'安全な(Secure)'状態は以下の基準を満たす状態だと言えます。

  • Criteria#1.証明書をクライアントが信頼できる状態でなければなりません。
  • Criteria#2.安全な設定になっていなければなりません。
    • Criteria#2-1.安全なプロトコルを提供(Protocol support)していなければならず、
    • Criteria#2-2.安全な暗号(Securecipher suites)に対する設定をしていなければなりません。

上のStep by Stepの項目でも、上の内容で設定項目を提示しましたが、 以下でもこの2部分に各項目について詳細に説明します。

Criteria #1. クライアントが信頼できる証明書

一般的には、以下の方法でクライアントが信頼できる証明書を取得して設定できるようになります。

  1. サービスのドメインを向けた適切な証明書のタイプを確認したあと、
  2. GlobalSign、DigiCertなどの信頼できるRoot CAから証明書を購入し、満了しないように管理します。 または、Let's Encrypt、AWS Certificate Managerのような無料で利用できるCAを通じて自動更新するように証明書の発行​​を受けます。
  3. 発行​​された証明書のprivate keyを安全に保存します。
  4. ウェブサーバで適切な中間CA証明書を設定してクライアントでChain of Trustを確認できるようにします。

適切な証明書のタイプとは?

一般的に使用するサーバ証明書は、様々な種類があります。
FQDNに対する信頼を提供するための個別証明書を発行された場合のほかにも、
様々なFQDNに対する信頼を提供するためのSAN(Subject Alternative Name)証明書、
ワイルドカード文字"*"を接頭辞として使用し、サブドメイン全体に対する信頼を提供するWildcard証明書など類型があります。

まず、内容についてもっと説明する前に、ドメイン名(Domain name)について理解する必要があります。
FQDN(fully qualified domain name、正規化されたドメイン名)というSubdomainからTLD(.meと同じTop-Level Domain)までを含む完全なドメイン名を表しており、下記のような形式を持っています。
[subdomain levelN].[subdomain level2]...[subdomain level1][apex domain].[tld(top–level domain)]ドメイン名についてもっと知りたい場合、RFC 1035の内容を読んで見ると良いでしょう。

説明したタイプのうちWildcard証明書を利用する場合に、ドメインカバレッジについて誤った認識を持つことが多いです。
Wildcardの証明書がすべての場合に使用可能であると間違えて、設定する可能性があります。 Wildcard証明書は'最初のレベルのSubdomainに対してのみ有効'あるドメインに対するカバレッジが存在します。
例えば、*.line.me Wildcard証明書はinfraprotection.line.meのような最初のレベルのサブドメインを持ったドメインについて、利用可能です。
tls.infraprotection.line.meと同じ2番目のレベルのサブドメインを持ったサービスに設定をしたら、クライアントでは、信頼しないようになります。
したがって、このような場合に同様の証明書を利用したい場合は、tls-infraprotection.line.meといった形でサービスのドメインを変更しなければなりません。

信頼できるCAから証明書を取得

ブラウザの設定画面で基本的に信頼している証明書の情報を確認することがありますが、 下記のChromeブラウザのTrusted Root CAのリストのように多様にあります。

どのCAを選択しなければならないかはCA/Browser Forumではこう書いていますが、もう少し考慮すべき部分を書いてみます。

How to select your CA?
It is crucial to choose the most suitable Certification Authority for the sake of your certificate. You should take care of the reputation of the CA together with its convenience and certificate prices while choosing the best one for you.

追加に考慮すべき部分は下記の通りです。CAの評判

まず、CA別にシェアについては<a href="https://w3techs.com/technologies/overview/ssl_certificate/all" target="_blank">W3TechsのSurvey</a>で確認できます。

記事を作成している2018年現在、Let's EncryptのCross Root(ここについては以下のTrust Path項目を参考にしてください)のIdenTrustが最も高いシェアを記録しています。

Certificate Transparencyに参加するかどうか

SSL/TLS証明書の使用において、最も重要な仮定条件はCAを信頼することができるということです。 もしCAが誤って証明書を発行したり、CAが毀損(Compromise)された状況で間違った証明書を発行​​する可能性があるため、このためにChromeのようなブラウザでは発行​​記録を透明性のログに記録をする場合に信頼するようにしています。 それで、<a href="https://www.certificate-transparency.org/what-is-ct/" target="_blank">GoogleのCertificate Transparency</a>に参加をしているのか、それで今後SCT(<a href="https://www.certificate-transparency.org/faq/" target="_blank">signed certificate timestamp</a>
)について対応計画があるか確認が必要です。

参加しているCAで証明書発行時、ログを記録して特定のドメインに関する証明書発行の現状について、証明書の<a href="https://crt.sh/" target="_blank">検索サービス</a>を通じて誰でも照会できるようにしています。

例えば、Let's Encryptの場合、CTに参加しているために発行された証明書については、以下にて確認することができます。<a href="https://crt.sh?Identity=%25&iCAID=16418" target="_blank">https://crt.sh?Identity=%25&iCAID=16418</a>

CA/Browser ForumのBase Requirementによる運営するかどうか

関連ポリシーを定めている<a href="https://cabforum.org/about-the-baseline-requirements/" target="_blank">CA/Browser ForumのBase Requirement</a>のポリシーに沿った実装計画があるか確認することが必要です。

もしここで定められている要求事項を、定められた期限内に実装していない場合、正常に発行された証明書でもウェブブラウザで信頼できない状態になる恐れがあるためです。

そして、無料で証明書発行​​を支援しているLet's Encryptは信頼できるCAであるため、こうしたサービスを利用して証明書を発行することができます。

また、証明書には有効期間があるために有効期間の終了日になる前に再発行をして交代する必要があります。

private keyを安全に管理

例を挙げて説明したLet's Encryptを通じて証明書が正常に発行されたなら、当該ディレクトリに様々なファイルが生成されますが、各ファイルの目的は下記の通りです。 もちろん、他のCAを通じて発行される場合でも、同じ目的の証明書内容を得ることができます。

また、リストから外されているが、証明書を発行する時CAに署名を要請するためのCSR(Certificate Signing Request)ファイルを生成することになります。 発行​​プロセス以降で使用されないため、リストに追加していません。

Index Purpose Filename
1 CAから署名を受けた証明書(X.509フォーマット) cert.pem
2 証明書のprivate key privkey.pem
3 中間CAの証明書 chain.pem
4 Fullchain(1+3 combined) fullchain.pem

上の証明書ファイル中、まるでパスワードのように安全に管理が必要なのは上記の2番'証明書のprivate key(Certificate's private key)'です。その他のファイルの内容は、クライアントが接続する際に伝達される内容であるので公開されても大丈夫です。

したがって、'証明書のprivate key(Certificate's private key)'は 実際の使用が必要なサーバなど安全な場所で最初に生成された後に、他の所に公開しないように注意しなければなりません。サーバ内でもrootなど管理者を所有者として、他のユーザーはアクセスできないように設定することが必要です。

証明書パス & 中間CAの証明書

上の証明書ファイルのうち3番と4番(3回を含めているので)に該当する話です。

証明書を取得して、安全にシステムに保管をしておけば、今回は当該証明書をクライアントが信頼できるように十分な情報を伝えるようにする番です。信頼できる経路(chain of trust)を指定してくれるということです。 一般的な場合には中間CA情報を追加で設定していますが、特殊な場合(Feature Phoneのような古い端末をサポートしなければならない場合)のために一部CAで提供するCross Signed Rootを設定することが必要かもしれません。

Chain of Trust&中間CA

クライアントは接続するサーバから送られてきた証明書の内容を確認して、信頼されているCAから署名されたのか確認することになります。

以下は、Digicertのデモサイトに設定されている証明書の発行経路を表示しています。ちなみに、opensslを利用して確認をしたい場合には以下のようなコマンドにすることができます。

$ openssl s_client -connect global-root-ca.chain-demos.digicert.com:443 -showcerts -servername global-root-ca.chain-demos.digicert.com
CONNECTED(00000003)
depth=2 C = US, O = DigiCert Inc, OU = www.digicert.com, CN = DigiCert Global Root CA
verify return:1
depth=1 C = US, O = DigiCert Inc, CN = DigiCert SHA2 Secure Server CA
verify return:1
depth=0 businessCategory = Private Organization, jurisdictionC = US, jurisdictionST = Utah, serialNumber = 5299537-0142, C = US, ST = Utah, L = Lehi, O = "DigiCert, Inc.", CN = global-root-ca.chain-demos.digicert.com
verify return:1
---
Certificate chain
 0 s:/businessCategory=Private Organization/jurisdictionC=US/jurisdictionST=Utah/serialNumber=5299537-0142/C=US/ST=Utah/L=Lehi/O=DigiCert, Inc./CN=global-root-ca.chain-demos.digicert.com
   i:/C=US/O=DigiCert Inc/CN=DigiCert SHA2 Secure Server CA
-----BEGIN CERTIFICATE-----
MIIFnzCCBIegAwIBAgIQDZzoezayHYgZcb6176w9szANBgkqhkiG9w0BAQsFADBN

「以下略」

経路を見ると、サーバに設定されているLeaf証明書であるglobal-root-ca.chain-demos.digicert.comを中間CAのDigiCert SHA2 Secure Server CAが署名をしており、当該中間CAはDigiCert Root CAが署名をしました。

クライアントは、上記のDigiCertのように、すでに信頼するRoot CAに対する情報を'信頼しているRoot CAのリスト'に設定しており、受け取ったLeaf証明書を署名した中間CA情報および中間CAを署名したRoot CAに対する情報を、順に確認(Chain of Trust)して検証することになります。
Leaf Certificate--Verify-->Intermediate CA(中間CA)--Verify-->Root CA
Wikipediaでは、画像を通じて説明をしています。それで、もしクライアントで中間CAに対する情報を伝達しない場合、Root CAに対する信頼関係を検証できない場合が発生することになります。
実際にはほとんどのクライアントが'信頼しているRoot CAのリスト'だけでなく、'信頼している中間CA'に対する情報も設定しています。 それで、中間CA設定をしなくても問題がない場合が多いですが、一部のクライアントでは情報が無い場合がありますので、設定した方が安全です。

でも、読んでいるうちに、このような疑問を持つかもしれません。 Root CAが直接Leaf証明書を署名するようにすれば簡単だと思いますが、どうして中間CAを置いているのでしょうか?

Root CAは名前の通り、証明書の信頼関係の原点(Trust anchor)であるため、棄損されている場合には全体の信頼の構造が壊れるようになります。 したがって、ネットワークから分離された(Air GappedまたはOffiline)環境で安全に管理されなければなりません。
このような部分がよく管理されているか検証を受けるCAになることが必要です。
それで、Root CAは中間CAを発行(署名)する場合にのみ限定的に使われており、中間CAを署名(発行)、システム上にオンラインで置いてLeaf証明書を発行(署名)していくのに使用する形で運営されているためにこれと同じ構造を持っています。

Cross Root/Cross Signed Root

Cross Signed Rootと呼ばれる証明書を設定しなければならない場合もあります。 どういうことでしょうか?

まずクライアント側から考えてみましょう。
上記でクライアントが'信頼しているRoot CAのリスト'を持っていると言いましたが、 該当リストを過去に作成を行ってから、その後アップデートをしていない場合を考えてみることができます。
古い携帯電話とか、非常に古いOS(例えばCentOS4、あるいは初期CentOS5くらい)の場合同じ非常に長くなったクライアント環境等をサポートしなければならない場合、クライアントが当該Root CAをTrusted Root CAに含んでいない状況があります。

または、Root CAが作成されたばかりの場合も考えてみることができます。

そこで、当該Root CAをウェブブラウザのようなクライアントで基本的に含んでいない状況を考えてみることができます。このような場合のために、互換性が高い、つまり、過去に発行されて、ほとんどのクライアントが '信頼しているRoot CAのリスト'に含まれている、Root CAを通じて署名を受けておく場合があります。

実際の事例を見ると、もっと理解をすることができるはずです。まず、Baltimore CyberTrust Rootを通じて署名したDigiCert証明書の例です。

他の例ではLet's Encryptが自分たちのRoot CAのISRG Root X1のほかにもIdenTrustのDST Root CA X3を通じてCross Signingしたことなどを挙げることができます

SNI(Server Name Indication)

または証明書は正常ですが、実際のウェブブラウザのようなクライアントとして他の証明書が配信された事例についても知るのが良いでしょう。一つのウェブサーバでIP、Portが同一の場合にも、サーバブロックを複数設定して、多くのドメインに対してサービスすることができます。 この場合、SNIがサポートされているクライアントなら、接続しようとするホスト名を要請して適切な証明書を受けることがありますが、SNIをサポートしないクライアントの場合、ウェブサーバに設定された最初のサーバブロックの証明書を受けないと信頼できないと判断してしまうことがあります。

もし、サーバブロック2個だけサービスして、2個のうちの一つのサービスのみSNIをサポートしないクライアントを考慮しなければならないのなら、そのサービスをサーバブロックの前方に移動させることで解決することができます。しかし、この場合、一般的な解決方法は、サービスするすべてのドメインを含むSAN証明書を利用することです。 そうすれば、SNIをサポートしないクライアントの場合にも要請したホスト名に該当する証明書を受けるようになります。

Criteria #2. Secure Configuration

下記のような流れで安全な設定になったかを確認することができます。

  1. 使用するOpenSSLライブラリを最新バージョンにアップデートをして、
  2. 安全ではないプロトコルをサービスしないように設定をします。
  3. また、安全な(十分なセキュリティ強度を持つような)cipher suiteが設定できるだけします。
  4. 上の基本的な設定よりも安全なコネクションを提供しようとする場合、追加のセキュリティ設定をします。

Reference Configuration

下記の設定はnginxでのnginx.confファイルです。

本記事内で説明しない部分はnginx公式ドキュメントのngx_http_ssl_module関連Directiveの方を読んで見ていただければ良いと思います。

http {
    server {
        listen       80;
        server_name  infraprotection.line.me;

        # redirect http > https
        return 301 https://$host$request_uri;

        location / {
            root   /var/infraprotection/;
            index  index.html index.htm;
        }

        # For DV(Domain Validation) when using Let's Encrypt
        location ^~ /.well-known/acme-challenge/ {
                default_type "text/plain";
                root   /var/infraprotection/;
        }


        error_page 400 401 403 404 500 501 502 503 505 https://infraprotection.line.me/;
    }

    server {       
        listen       443 ssl http2;
        server_name  infraprotection.line.me;

        keepalive_requests 10;
        keepalive_timeout 20;

        # Set Headers such as HSTS
        add_header Strict-Transport-Security "max-age=31536000; includeSubDomains";
        add_header X-Frame-Options SAMEORIGIN;
        add_header X-Content-Type-Options nosniff;

        # if the service use Normal Intermediate CA(中間CA)
        ssl_certificate      /etc/pki/tls/certs/infraprotection.line.me_fullchain.crt;
        # else if the service use Cross Root CA
        # ssl_certificate    /etc/pki/tls/certs/infraprotection.line.me_fullchain_cross.crt;

        # Certificate private key Path
        ssl_certificate_key  /etc/pki/tls/private/infraprotection.line.me.key;

        ssl_session_cache    shared:SSL:10m;
        ssl_session_timeout  5m;

        server_tokens off;

        # Set cipher suite
        ssl_ciphers "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA";
        ssl_prefer_server_ciphers  on;
        # Only Allow TLSv1.2
        ssl_protocols TLSv1.2;


        # Set strong parameter for DH key exchange
        ssl_dhparam /etc/pki/tls/dhparams4096.pem;


        #
        ssl_stapling on;
        ssl_stapling_verify on;
        ssl_trusted_certificate /etc/pki/tls/certs/infraprotection.line.me_chain.crt;

        error_page 400 401 403 404 500 501 502 503 505 https://infraprotection.line.me/;

        location / {
            root   /var/infraprotection.line.me/;
            index  index.html index.htm;
        }
    }
}

Updated OpenSSL Library

ロードバランサなどを利用してTLS Terminationをする場合がなければ、一般的にシステムにインストールされたOpenSSLライブラリを使用してSSL/TLSを提供することになり、これと関連して考慮する事項について書いてみます。

ライブラリのカップリング(Coupling)

nginxなどのウェブサーバを設置する際にOpenSSLライブラリを共にビルド(Static Build)するようにガイドする場合をたくさん見ました。 しかし、こうした場合不要なカップリング(Coupling)が高くなって、OpenSSLのホールへの対応などアップデートが必要な時にウェブサーバを新たにビルドして配布しなければならない問題が生じます。 したがって、その後OpenSSLライブラリのアップデートなど管理を考慮して、システムライブラリの使用を奨励し、(例:$sudo yum update openssl)、静的ビルド(static build)バージョンを使用しないことが良いです。

静的にリンクされたOpenSSLのインスタンスを使用するように設定していないか、設置時に設定したパラメータを下記のように確認できます。

$ nginx -V(例:/usr/sbin/nginx -V)
または、ビルドのバイナリを直接確認して確認することもできます。
$ strings -a /usr/local/nginx-installed-path/sbin/nginx | grep '^OpenSSL[0-9]' | awk '{print$2}' | head -n 1

静的リンクしたライブラリの代わりにシステムにインストールされたOpenSSLパッケージを使用するようにするには、nginxウェブサーバを使用する場合を例に挙げれば、下記のようにすればいいです。

$ sudo yum install openssl-devel
$ sudo yum update openssl
nginxビルド時に"--with-openssl parameter"を削除します。

OpenSSLのバージョン

下記のようなコマンドを通じてインストールされたライブボリのバージョン情報を確認することができます。

$ /path/to/openssl version -a
$ yum list installed openssl

RHELなどで配布するOpensslライブラリのバージョンを確認してみると、少しバージョンがおかしいことに気づくでしょう。RHEL 6で配布をするOpenSSL x86_64アーキテクチャの場合、パッケージ名を見ると、'openssl-1.0.1e-57.el6.x86_64'形です。
理由はUpstreamになるOpenSSLのバージョンとはバージョン命名規則が違ってです。 各運営体制のベンダーから提供するリポジトリで配布するライブラリの最新バージョンにアップデート($ sudo yum update openssl)すればいいです。

その他の参考事項

古いOpenSSL 0.9.8及び1.0.0バージョンはTLSv1.1以上のプロトコルをサポートしません。 したがって、OpenSSLを1.0.2/1.1.0シリーズのバージョンにアップデートしなければなりません。

  • https://www.openssl.org/news/cl101.txt - Initial TLSv1.1 support
  • https://www.openssl.org/source/ - The 0.9.8 and 1.0.0 versions are now out of support and should not be used.

OSがOpenSSL 1.0.2以上のバージョンを公式支援しない場合(例えば、RHEL 5)、OSを上位バージョンにアップグレードする前までは静的ビルドバージョンを代わりに使用する必要があります。
OpenSSL 1.0.1シリーズの場合、2016年12月31日、公式支援が終了されてセキュリティアップデートが提供されません。 1.0.2など上位バージョンを使用しなければなりません。
OpenSSL 1.1.1シリーズの場合、安定化前まではテスト目的だけに使用するのがいいです。

Protocol

記事の前でも言及したようにRFC 2246(TLS 1.0)、RFC 4346(TLS 1.1)、RFC 5246(TLS 1.2)まで、現在広く利用されるTLSプトトコルについて確認しますが、持続的にセキュリティ水準を高めています。 最近、(2018/3/21)Proposed Standardに定められたTLS 1.3では既知のセキュリティ問題を追加で考慮した設計が含まれています。

PCI-DSS(PCI Security Standards Council)では低いバージョンのプロトコルを上位バージョンのプロトコルとマイグレーションするよう、規制をしていることも(Migrating from SSL and Early TLS v1.1)します。

セキィリティのためには、上位バージョンのプロトコルだけを使うように設定をすることが必要です。 ただし、上位プロトコルをサポートしないクライアントがあるため、互換性に対する確認をしながら、マイグレーションしていくことが必要です。

例えば、下位バージョンのプロトコルに対するサポートを制限する場合には、一般的な使用者環境の他にも、連携している他社のシステムなどのクライアントが連結に失敗する可能性もあるため、連結をテストして見られる環境(Handshake Simulation)を準備してクライアントの方にマイグレーションの事実及び日程について教えた後、マイグレーションする形でのアクセスが必要です。

Android 2.3.7, 4.0.4, 4.1.1, 4.2.2, 4.3
IE 10 / Win Phone 8.0
Baidu Jan 2015
IE 7/Vista, 8-10/Win 7
Java 7u25
OpenSSL 0.9.8y
Safari 5.1.9 / OS X 10.6.8, 6.0.4 / OS X 10.8.4

Secure cipher suite

cipher suiteはその言葉通り、Suite(組、揃(そろ)い)なので、安全な接続を生成するために必要な以下の4つの暗号アルゴリズムを一つで表現しています。

'鍵交換アルゴリズム(Key exchange/agreement)'、'アクセスする際相手を確認するための認証アルゴリズム(Authentication)'、'機密性のためのブロック暗号化アルゴリズム(Block/stream cipher)'、'メッセージ完全性のための暗号アルゴリズム(Message authentication)'であり、上記の例で定義したcipher suiteに対する使用、暗号アルゴリズムは以下のようなopensslコマンドで確認できます。

$ openssl ciphers -v 'ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA' | column -t
ECDHE-RSA-AES256-GCM-SHA384  TLSv1.2  Kx=ECDH  Au=RSA  Enc=AESGCM(256)  Mac=AEAD
ECDHE-RSA-AES128-GCM-SHA256  TLSv1.2  Kx=ECDH  Au=RSA  Enc=AESGCM(128)  Mac=AEAD
DHE-RSA-AES256-GCM-SHA384    TLSv1.2  Kx=DH    Au=RSA  Enc=AESGCM(256)  Mac=AEAD
DHE-RSA-AES128-GCM-SHA256    TLSv1.2  Kx=DH    Au=RSA  Enc=AESGCM(128)  Mac=AEAD
ECDHE-RSA-AES256-SHA384      TLSv1.2  Kx=ECDH  Au=RSA  Enc=AES(256)     Mac=SHA384
ECDHE-RSA-AES128-SHA256      TLSv1.2  Kx=ECDH  Au=RSA  Enc=AES(128)     Mac=SHA256
ECDHE-RSA-AES256-SHA         SSLv3    Kx=ECDH  Au=RSA  Enc=AES(256)     Mac=SHA1
ECDHE-RSA-AES128-SHA         SSLv3    Kx=ECDH  Au=RSA  Enc=AES(128)     Mac=SHA1
DHE-RSA-AES256-SHA256        TLSv1.2  Kx=DH    Au=RSA  Enc=AES(256)     Mac=SHA256
DHE-RSA-AES128-SHA256        TLSv1.2  Kx=DH    Au=RSA  Enc=AES(128)     Mac=SHA256
DHE-RSA-AES256-SHA           SSLv3    Kx=DH    Au=RSA  Enc=AES(256)     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

このcipher suiteの場合、RFCで定義している名前を使用する場合(Naming scheme)もあり、OpenSSLで定義している名前を使用する場合もあり、見分けがつかないことがあります。 これを理解するためには、マッピング表を提供しているところがありますので参考にしてください。

各アルゴリズムについては、この記事で説明はしませんが、実際、当該アルゴリズムが使用されるのがどこなのかを考えることは、もう少し深く理解する上で役立ちます。
例えば、鍵交換にDHを使用する場合には、DHアルゴリズム上で鍵をそれぞれ導き出すために共有されるパラメータに署名をする方式で認証のためのRSAが使用されるように実装された部分について考えてみることです。

また、大規模のトラフィックを処理しなければならない機器でcipher suiteごとの性能について悩む場合もあります。
一見考えてみると、ECDHEを使用し、鍵交換をする場合が最も処理が速くなりそうですが、、実際には鍵交換のほかにも認証のためにRSAを追加で使用しなければならず、RSAを鍵交換と認証の両方に使用する場合に比べて処理性能が落ちるのを見ることもあります。
あるいは、ハードウェア的な制約によってECDHEを使用する場合がDHEを使用する場合より性能が落ちることもあり、処理性能が重要な場合、実際にテストをしてみることにアプローチするのが良いでしょう。

OpenSSLライブラリで!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4のように特別な表現が使われる場合がありますが、 頻繁に使用されている内容は下記の通りです。

!:除外する
aNULL:認証機能がないcipher suite.したがって、Man-In-The-Middle(MITM)攻撃に脆弱さ
eNULL(NULL):暗号化しないcipher suite(null-encryption ciphers)。
EXPORT:米国の関連法によって輸出が可能な40、56 Bitsの弱いcipher suite.
DES:DESを使用するcipher suite.
MD5:MD5を使用するcipher suite.
PSK:PSK(pre-shared keys)を使用するcipher suite.
RC4:RC4を使用するcipher suite.
HIGH:"High"水準のcipher suitesのリストです。 OpenSSLバージョンごとに含まれるcipher suiteが違うので、現在は128 bitより大きな鍵長を持つCipherを使用するcipher suiteです。
MEDIUM:"Medium"水準のcipher suitesのリストです。 OpenSSLバージョンごとに含まれるcipher suiteが違うので、現在は128 bit暗号を使用するcipher suiteです。
LOW:"Low"水準のcipher suitesのリストです。 OpenSSLバージョンごとに含まれるcipher suiteが違うので、現在は64/56 bit暗号を使用するcipher suiteです。 しかし、Exportレベルのcipher suiteらは除外します。
@STRENGTH:暗号化アルゴリズム(Encryption Algorithm)鍵長さの順で現在Cipherリストを整列するために使用することができます。

より詳細な内容を参考にしてください。

Criteria #3. Rating Guide

Qualysで提供するサービスで確認する方法を上で説明しましたが、 もっと理解したい方に向けて説明を追加します。

下記のようなスコアリング方法(Scoring Scheme)を使って、加重値などについて定めると知られています。 しかし、項目については、変更があったりしてTなどのGradeを付与する場合など全て含んでいないため、より具体的な内容は関連ページを参考にしてください。

Category Scoring Weight Score Criteria Rating Guide
a certificate to verify that it is valid and trusted. N/A CertificateInspection= {0, 1} Set Zero Score, if
  • Domain name mismatch
  • Certificate not yet valid
  • Certificate expired
  • Use of a self-signed certificate
  • Use of a certificate that is not trusted (unknown CA or some other validation error)
  • Use of a revoked certificate
  • Insecure certificate signature (MD2 or MD5)
  • Insecure key
server configuration / Protocol support ProtocolSupportWeight = 30% ProtocolSupport= (Score of the best protocol + Score of the worst protocol) / 2 Protocol support rating guide
  • SSL 2.0 = 0%
  • SSL 3.0 = 80%
  • TLS 1.0 = 90%
  • TLS 1.1 = 95%
  • TLS 1.2 = 100%
server configuration / cipher suites KeyExchangeWeight = 30% KeyExchange= Key exchange rating Key exchange rating guide
  • Weak key (Debian OpenSSL flaw) = 0%
  • Anonymous key exchange (no authentication) = 0%
  • Key or DH parameter strength < 512 bits = 20%
  • Exportable key exchange (limited to 512 bits) = 40%
  • Key or DH parameter strength < 1024 bits (e.g., 512) = 40%
  • Key or DH parameter strength < 2048 bits (e.g., 1024) = 80%
  • Key or DH parameter strength < 4096 bits (e.g., 2048) = 90%
  • Key or DH parameter strength >= 4096 bits (e.g., 4096) = 100%
CipherStrengthWeight = 40% CipherStrength= (Score of the strongest cipher + Score of the weakest cipher) / 2 Cipher strength rating guide
  • 0 bits (no encryption) = 0%
  • < 128 bits (e.g., 40, 56) = 20%
  • < 256 bits (e.g., 128, 168) = 80%
  • >= 256 bits (e.g., 256) = 100%

上で算出された点数を下記のような形でGradeに変換をして示しています。

Numerical Score Grade
score >= 80 A
score >= 65 B
score >= 50 C
score >= 35 D
score >= 20 E
score < 20 F

おわりに

本記事では、セキュアなSSL/TLSの実現について紹介しました。この内容をサーバに直接適用してくれた多くの仲間のサポートを受けて執筆しました。作成に役立つ分析情報と専門知識を提供してくれた仲間のみなさんに感謝します。

明日はKunihiko Satoさんによる「任意のSignal-to-Noise比の音声波形をPythonで作ろう!」です。お楽しみに!