常時SSL化の対応状況を調べるスクリプトを書いてみた

# 背景

7月24日にGoogle Chrome 68がリリースされ、HTTPSでないサイトには警告が出るようになった。

自分の会社サイトでは、2016年12月から検証開始→2017年4月にHSTS Preloadまで設定完了、としていたので、
今回のChrome 68リリースでは高みの見物をしていたのだけれど、警告があまり目立たないのか意外と騒がれずちょっとがっかり。

私の会社の競合他社で今回SSLに対応したところは皆無だったのだが、
逆に「今後どのタイミングでSSL化するのだろう？」と
下世話な
興味が湧いたので、
ウェブサイトの常時SSL化対応状況を調べるスクリプトを作ってみた。

# 作ったもの

## 概要

他社サイトのHTTPS対応状況を調べてみると、
対応していないサイトではだいたい下記のパターンになることがわかった。

* HTTPS通信ができない
    * HTTPSのポート(443/tcp)が空いてない
    * 443ポートは開いてるけど、まともに応答しない
* 証明書がおかしい
    * 自己署名証明書を使っている
    * 証明書の期限が切れている
    * SAN(Subject Alternative Name)が入っていない
* 常時SSLのための対応が不足
    * HTTPページからHTTPSページへリダイレクトされていない
    * HSTSヘッダがない
    * HSTS Preloadに登録していない

なので、上記の点についてチェックする仕様とした。

トラストチェーンが正しいかの検証を行うのと、Mixed Contentsが含まれていないかの判定は
コマンドで簡単にはできそうじゃなかったので割愛。

[Qualys SSL Labs](https://www.ssllabs.com/index.html)や[testssl.sh](https://github.com/drwetter/testssl.sh)などを使ってください。
([testssl.shのウェブサイト](https://testssl.sh/)のSSL証明書の期限が切れてる!(笑))

## スクリプト

~~~bash:check_https.sh
#!/bin/bash

hstspreload=`mktemp`
curl https://cs.chromium.org/codesearch/f/chromium/src/net/http/transport_security_state_static.json -s -o $hstspreload
trap 'rm $hstspreload' EXIT

if [ -p /dev/stdin ]; then
    cat -
else
    echo $@ | sed -E 's/\s+/\n/g'
fi | while read domain; do
  echo "${domain}"
  if nmap -p 443 $domain | grep -q "443/tcp open  https"; then
    echo "	Port 443: Open"
  else
    echo "	Port 443: Closed"
    continue
  fi

  cert=`mktemp`
  openssl s_client -connect $domain:443 -servername $domain < /dev/null 2>/dev/null | openssl x509 -text >$cert 2>/dev/null
  if [ "$?" -eq "0" ]; then
    issuer=`grep "Issuer:" $cert | sed -e 's/^\s\+Issuer:\s\+//'`
    subject=`grep "Subject:" $cert | sed -e 's/^\s\+Subject:\s\+//'`
    if [ "$issuer" = "$subject" ]; then
      echo "	Issuer: Self Singed (Not OK)"
    else
      echo "	Issuer: "$issuer
    fi

    startdate=`grep "Not Before:" $cert | sed -e 's/^\s\+Not Before://' | date -f - '+%s'`
    enddate=`grep "Not After :" $cert | sed -e 's/^\s\+Not After ://' | date -f - '+%s'`
    today=`date '+%s'`

    if [ $today -gt $startdate -a $enddate -gt $today ]; then
      echo "	Date: Valid"
    else
      echo "	Date: Expired"
    fi

    sancheck=`mktemp`
    echo "	SAN: Not OK" > $sancheck
    grep -A1 "Subject Alternative Name" $cert | sed -e '1,1d' | sed -E 's/\s+DNS://g' | sed -E 's/,/\n/g' | while read i; do
      pattern='^'`echo $i | sed -e 's/\./\\\./g' | sed -e 's/\*/[^.]\\\+\\\?/g'`'$'
      if echo $domain | grep -q -e $pattern; then
        echo "	SAN: OK" > $sancheck
        break
      fi
    done
    cat $sancheck
    rm $sancheck

    rm $cert
  else
    echo "	No SSL Certificates"
    rm $cert
    continue
  fi

  if curl --head http://$domain 2>/dev/null | grep -q "Location: https://$domain"; then
    echo "	HTTPS Redirect: Enabled"
  else
    echo "	HTTPS Redirect: Disabled"
  fi

  hsts=`curl --head https://$domain 2>/dev/null | grep -i "strict-transport-security: "`
  if [ "$hsts" != "" ]; then
    echo "	HSTS: Enabled"
    if grep -q `echo $domain | sed -e 's/\www\.//'` $hstspreload; then
      echo "	HSTS Preload: Enabled"
    else
      echo "	HSTS Preload: Disabled"
    fi
  else
    echo "	HSTS: Disabled"
  fi
done
~~~

## 使い方

スクリプトの引数に調べたいドメインを並べてください。

~~~bash
./check_https.sh (調べたいドメイン1) (調べたいドメイン2)...
~~~

もしくは、別ファイルにドメインのリストを作っておき、パイプでつなげます。

~~~:domains.txt
www.cao.go.jp
www.kunaicho.go.jp
www.mof.go.jp
www.moj.go.jp
www.soumu.go.jp
cio.go.jp
~~~~

~~~bash
cat domains.txt | ./check_https.sh
~~~

このやり方は[標準入力を受け取れるシェルスクリプト、関数の作成（パイプで繋げられるようにする）](https://qiita.com/koara-local/items/7ad01e0692e36627ba2d)を参考にしました。

上記サンプルでは政府のサイトをダミーで入れてます。

HSTS Preloadに登録済みのサイトを調べるため、
毎回6MB程度のファイルをChromiumのリポジトリからダウンロードするので、
ネットワークが細いところではご注意を。
あと`www.`以外のサブドメインではHSTS Preloadに含まれているか正しく判定できません。

相手サーバに対して行儀の悪いことはしていない(443ポートが開いてるかのチェックと、証明書とHTTPヘッダのダウンロードのみ)ので、1日1回チェックする程度であれば問題ないと思うのだけれど、もし問題であれば消します。

## 出力例

出力は以下のような感じ。

* 443ポートが開いているか
* 証明書のIssuer (Self Signedか、そうでなければ認証局)
* 証明書の期限が切れていないか
* 証明書のSANに自分のサーバのFQDNが入っているか(ワイルドカードにも一応対応)
* HTTPSへのリダイレクトが行われているか
* HSTSヘッダは出力されているか
* HSTS Preloadに登録されているか

を順に調べます。

上記の`domains.txt`にある政府系のサイトを調べた結果は以下のような感じ。

~~~
www.cao.go.jp
	Port 443: Closed
www.kunaicho.go.jp
	Port 443: Open
	Issuer: C = JP, O = Japanese Government, OU = GPKI, CN = ApplicationCA2 Sub
	Date: Valid
	SAN: OK
	HTTPS Redirect: Disabled
	HSTS: Disabled
www.mof.go.jp
	Port 443: Open
	Issuer: C = US, O = DigiCert Inc, CN = DigiCert SHA2 Secure Server CA
	Date: Valid
	SAN: OK
	HTTPS Redirect: Enabled
	HSTS: Disabled
www.moj.go.jp
	Port 443: Open
	Issuer: C = JP, O = Japanese Government, OU = GPKI, CN = ApplicationCA2 Sub
	Date: Valid
	SAN: OK
	HTTPS Redirect: Disabled
	HSTS: Disabled
www.soumu.go.jp
	Port 443: Open
	Issuer: C = JP, O = Japanese Government, OU = GPKI, CN = ApplicationCA2 Sub
	Date: Valid
	SAN: OK
	HTTPS Redirect: Disabled
	HSTS: Disabled
cio.go.jp
	Port 443: Open
	Issuer: C = GB, ST = Greater Manchester, L = Salford, O = COMODO CA Limited, CN = COMODO RSA Domain Validation Secure Server CA
	Date: Valid
	SAN: OK
	HTTPS Redirect: Enabled
	HSTS: Enabled
	HSTS Preload: Enabled
~~~

# 感想

この記事を書いている2018年7月30日現在、政府系のウェブサイト(.go.jp)で、HSTSのプリロードまで対応しているのは2件だけ。
マイナポータルは知ってたけれど、政府CIOポータルというサイトは初めて知った。

HSTS Preload対応かどうかはブラウザのソースコードにハードコーディングされているのだけれど、
今後対応サイトが増加していったらどうするのだろう？