如何启用 Certificate Transparency

简单认识 Certificate Transparency

根据维基百科的解释,Certificate Transparency (CT) 目前是一项实验性的 IETF 开放标准以及开放源代码的框架,用于监控以及审计数字证书。通过证书日志、监控以及审计系统,将允许网站用户以及域名所有者来判断辨别错误或者恶意签发的证书,并且可以找出那些流氓的 CA。 翻译自维基百科:https://en.wikipedia.org/wiki/Certificate_Transparency

更多参考:What is Certificate Transparency?

Certificate Transparency 的用途

Certificate Transparency 看上去很高端是不是,我相信你也没搞清楚这个证书透明度是做什么的,通过下面这个真实的例子我来给你简单讲述一下。

在我们访问一个 https 网站的时候,这些网站都会有一个 SSL 证书,来证明这个网站就是你要访问的这个网站,并且证明网站的数据在传输过程中并没有遭到篡改。相信大家都有过被运营商进行流量挟持,被运营商在你所想要访问的页面上插广告的经历,这是因为,http 协议并不是加密的,数据在传输过程中遭到了中间设备的挟持。而 https 可以解决这个问题,因为数据的传输过程是加密的,如果运营商想要进行挟持的话,他必须有一张受浏览器信任的证书,但是这个证书他们基本上是无法获得的。

对于我们普通站长所得到的证书来说,他是由 CA (证书颁发机构) 一层一层发下来的。

imlonghao.com 的证书链,从 AddTrust 根证书经 Comodo 中间证书签发

以本站为例,本站的证书是由 COMODO ECC Certification Authority 签发给 COMODO ECC Domain Validation Secure Server CA 最后再签发给我 imlonghao.com

而 COMODO ECC Certification Authority ,则是由 AddTrust External CA Root 签发,AddTrust External CA Root 是默认安装在我们的电脑或者浏览器之中,起到根证书的作用。

由我们电脑中位于信任区的根证书签发下来的证书,包括这些证书再签发下去的证书,在没有专门被 revoke 的情况下,都是会被我们的系统所信任的。

我们来看这样一条新闻:Improved Digital Certificate Security

这条新闻就告诉了我们 Certificate Transparency 的功用

我们先来看一看谷歌的证书链

Google 的证书链,从 GeoTrust 根证书经 Google Internet Authority G2 签发

由 GeoTrust Global CA 签发给 Google Internet Authority G2 然后给签发下去到具体的域名

而上述新闻当中,Symantec’s Thawte-branded CA 就错误地为 google.com 以及 www.google.com 签发了证书,这是相当危险的,万一有人拿到了私钥,就可以到处冒充谷歌了。

根据后面 Symantec 方面所作出的解释,证书是在内部测试流程中签发的,并且证书的有效期只有一天,而且证书并没有流出到公众,也没有对用户造成影响。

那么,谷歌是怎么知道 Symantec 签发了一个他们的证书呢?难道是 Symantec 公司有内鬼呢?

根据谷歌的通告,他们是通过 Certificate Transparency 的日志发现了这个证书。

引用 Jerry Qu 对 Certificate Transparency 的解释

Certificate Transparency 的目标是提供一个开放的审计和监控系统,可以让任何域名所有者或者 CA 确定证书是否被错误签发或者被恶意使用,从而提高 HTTPS 网站的安全性。

如何启用 Certificate Transparency

  • X509v3 Certificate Extension
  • TLS Extension
  • OCSP Stapling

关于三种方式的差异,请参考文末的参考资料。

Nginx 通过 TLS 拓展启用 Certificate Transparency

通过 TLS Extension 启用 Certificate Transparency 是一种比较通用的方式,无论你用的是哪一个 CA 签发下来的证书,都可以通过这种方式来启用 Certificate Transparency

提交证书并获取 SCT 文件

正如我们前面所说的,我们需要提交自己的证书到 CT Log 服务器中。这里我们用到了一个 GO 语言写的工具:ct-submit

通过下面的命令即可安装编译

apt-get install golang git

git clone https://github.com/grahamedgecombe/ct-submit
cd ct-submit
go build

提交的命令大概如下

./ct-submit [log服务器] < [你的证书位置] > [sct文件保存位置]

需要讲一下的是,上面 [你的证书位置] 所指的是完整的证书链

例子

./ct-submit ct.googleapis.com/aviator < /var/ssl/chained.pem > /var/scts/aviator.sct

Log 服务器列表可以在这里找到:Known Logs - Certificate Transparency

部分证书不能提交到部分的 Log 服务器中,谷歌官方的几个是可以的。

编译 Nginx 的 nginx-ct 模块

为了让我们的 Nginx 支持 Certificate Transparency,我们就必须编译 nginx-ct 这个模块了

nginx-ct 对 OpenSSL 的版本有要求,要求必须是

  • OpenSSL 1.0.2 or above.
  • BoringSSL 4fac72e or above.

我们使用打过 CloudFlare 补丁的 OpenSSL 1.0.2 stable

安装一些编译所需要的库

apt-get install libpcre3 libpcre3-dev openssl libssl-dev unzip build-essential zlib1g-dev

准备好打过 CloudFlare 补丁的 OpenSSL 1.0.2 stable

git clone https://github.com/cloudflare/sslconfig
wget -O openssl.zip -c https://github.com/openssl/openssl/archive/OpenSSL_1_0_2-stable.zip
unzip openssl.zip
mv openssl-OpenSSL_1_0_2-stable/ openssl
cd openssl && patch -p1 < ../sslconfig/patches/openssl__chacha20_poly1305_cf.patch && cd ..

准备编译 Nginx 以及 nginx-ct,截至目前 Nginx 1.9.11 并不是稳定版,所以请自行选择您的 Nginx 版本

wget -c http://nginx.org/download/nginx-1.9.11.tar.gz
tar zxf nginx-1.9.11.tar.gz
wget -O nginx-ct.zip -c https://github.com/grahamedgecombe/nginx-ct/archive/v1.0.0.zip
unzip nginx-ct.zip
cd nginx-1.9.11/

Nginx 编译参数,注意我们要加上我们的 nginx-ct 模块,以及指定我们所使用的 OpenSSL,编译参数我所使用的是官方 mainline 源安装中的 nginx 通过 nginx -V 所得到的参数,并未作过多修改。

./configure --prefix=/etc/nginx --sbin-path=/usr/sbin/nginx --modules-path=%{_libdir}/nginx/modules --conf-path=/etc/nginx/nginx.conf --error-log-path=/var/log/nginx/error.log --http-log-path=/var/log/nginx/access.log --pid-path=/var/run/nginx.pid --lock-path=/var/run/nginx.lock --http-client-body-temp-path=/var/cache/nginx/client_temp --http-proxy-temp-path=/var/cache/nginx/proxy_temp --http-fastcgi-temp-path=/var/cache/nginx/fastcgi_temp --http-uwsgi-temp-path=/var/cache/nginx/uwsgi_temp --http-scgi-temp-path=/var/cache/nginx/scgi_temp --user=nginx --group=nginx --with-http_ssl_module --with-http_realip_module --with-http_addition_module --with-http_sub_module --with-http_dav_module --with-http_flv_module --with-http_mp4_module --with-http_gunzip_module --with-http_gzip_static_module --with-http_random_index_module --with-http_secure_link_module --with-http_stub_status_module --with-http_auth_request_module --with-threads --with-stream --with-stream_ssl_module --with-http_slice_module --with-mail --with-mail_ssl_module --with-file-aio --with-http_v2_module --with-cc-opt='-g -O2 -fstack-protector --param=ssp-buffer-size=4 -Wformat -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2' --with-ld-opt='-Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,--as-needed' --with-ipv6 --add-module=../nginx-ct-1.0.0 --with-openssl=../openssl

开始编译安装

make && make install

贴一份我所使用的 /etc/init.d/nginx 文件

#!/bin/sh
### BEGIN INIT INFO
# Provides:          nginx
# Required-Start:    $network $remote_fs $local_fs
# Required-Stop:     $network $remote_fs $local_fs
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: Stop/start nginx
### END INIT INFO

# Author: Sergey Budnevitch <sb@nginx.com>

PATH=/sbin:/usr/sbin:/bin:/usr/bin

if [ -L $0 ]; then
    SCRIPTNAME=`/bin/readlink -f $0`
else
    SCRIPTNAME=$0
fi

sysconfig=`/usr/bin/basename $SCRIPTNAME`

[ -r /etc/default/$sysconfig ] && . /etc/default/$sysconfig

DESC=${DESC-nginx}
NAME=${NAME-nginx}
CONFFILE=${CONFFILE-/etc/nginx/nginx.conf}
DAEMON=${DAEMON-/usr/sbin/nginx}
PIDFILE=${PIDFILE-/var/run/nginx.pid}
SLEEPSEC=1
UPGRADEWAITLOOPS=5

[ -x $DAEMON ] || exit 0

DAEMON_ARGS="-c $CONFFILE $DAEMON_ARGS"

. /lib/init/vars.sh

. /lib/lsb/init-functions

do_start()
{
    start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON -- \
        $DAEMON_ARGS
    RETVAL="$?"
    return "$RETVAL"
}

do_stop()
{
    # Return
    #   0 if daemon has been stopped
    #   1 if daemon was already stopped
    #   2 if daemon could not be stopped
    #   other if a failure occurred
    start-stop-daemon --stop --quiet --oknodo --retry=TERM/30/KILL/5 --pidfile $PIDFILE
    RETVAL="$?"
    rm -f $PIDFILE
    return "$RETVAL"
}

do_reload() {
    #
    start-stop-daemon --stop --signal HUP --quiet --pidfile $PIDFILE
    RETVAL="$?"
    return "$RETVAL"
}

do_configtest() {
    if [ "$#" -ne 0 ]; then
        case "$1" in
            -q)
                FLAG=$1
                ;;
            *)
                ;;
        esac
        shift
    fi
    $DAEMON -t $FLAG -c $CONFFILE
    RETVAL="$?"
    return $RETVAL
}

do_upgrade() {
    OLDBINPIDFILE=$PIDFILE.oldbin

    do_configtest -q || return 6
    start-stop-daemon --stop --signal USR2 --quiet --pidfile $PIDFILE
    RETVAL="$?"

    for i in `/usr/bin/seq  $UPGRADEWAITLOOPS`; do
        sleep $SLEEPSEC
        if [ -f $OLDBINPIDFILE -a -f $PIDFILE ]; then
            start-stop-daemon --stop --signal QUIT --quiet --pidfile $OLDBINPIDFILE
            RETVAL="$?"
            return
        fi
    done

    echo $"Upgrade failed!"
    RETVAL=1
    return $RETVAL
}

case "$1" in
    start)
        [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC " "$NAME"
        do_start
        case "$?" in
            0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
            2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
        esac
        ;;
    stop)
        [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME"
        do_stop
        case "$?" in
            0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
            2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
        esac
        ;;
  status)
        status_of_proc -p "$PIDFILE" "$DAEMON" "$NAME" && exit 0 || exit $?
        ;;
  configtest)
        do_configtest
        ;;
  upgrade)
        do_upgrade
        ;;
  reload|force-reload)
        log_daemon_msg "Reloading $DESC" "$NAME"
        do_reload
        log_end_msg $?
        ;;
  restart|force-reload)
        log_daemon_msg "Restarting $DESC" "$NAME"
        do_configtest -q || exit $RETVAL
        do_stop
        case "$?" in
            0|1)
                do_start
                case "$?" in
                    0) log_end_msg 0 ;;
                    1) log_end_msg 1 ;; # Old process is still running
                    *) log_end_msg 1 ;; # Failed to start
                esac
                ;;
            *)
                # Failed to stop
                log_end_msg 1
                ;;
        esac
        ;;
    *)
        echo "Usage: $SCRIPTNAME {start|stop|status|restart|reload|force-reload|upgrade|configtest}" >&2
        exit 3
        ;;
esac

exit $RETVAL

杂项(依照的是我的 nginx.conf )

chmod +x /etc/init.d/nginx
mkdir -p /var/cache/nginx/client_temp
mkdir /etc/nginx/conf.d
/usr/sbin/update-rc.d -f nginx defaults

修改 nginx 配置启用 ct

ssl_ct_static_scts 指向的是存放我们 sct 文件的文件夹,因为 sct 文件可能有很多个…

将下列两行加上到你站点的配置文件即可,具体参数具体而定。

ssl_ct on;
ssl_ct_static_scts /var/scts;

最后重启一下你的 Nginx,应该就是大功告成的了。

Chromium 的连接信息确认网站提供了有效的 Certificate Transparency 信息

参考资料

Certificate Transparency 官方网站

Certificate Transparency 那些事 | JerryQu 的小站