してみるテストロゴ
Apache 2.4系でHTTP/2サーバを構築してみるテスト。

Let's Encryptを使ってqmailのTLSv1.2対応

せっかく、Webサーバが、Forward Secrecy(FS)になったので、Let's Enctyptもパブリックベータが始まったし、メールサーバもFSに対応させたい!

2016年1月現在、Let's Enctyptの証明書を、メールサーバの証明書として利用する解説もあまりなさそうです。

ということで、番外編をやります。(HTTP/2というより、Let's Enctyptのページになってきた気もする…)

概要

前提は、netqmail-1.0.6と、daemontoolと、ucspi-tcp(tcpserver)がインストールされている状態です。解説しているページは結構あると思うので、適当に検索してみてください。

今回いじるのは、tcpserverだけなので、tcpserverを使っていれば、応用可能です。

SMTPのうち、サブミッションとPOP3をTLSv1.2対応にします。

動作を確認したのは、Windows10のOutlook2016と、Android4.1のK-9 Mailです。

メールサーバ間のSMTPについては、従来通り非暗号の25番ポートなので、メッセージ本体を守るための設定ではありません。メッセージの内容を保護する場合は、PGPなどをご利用ください。

ここでの作業は、POP3と、クライアントPCからのメール送信(SMTP-Submission)の暗号化になります。SMTP/POP3認証情報や、POP3プロトコル内で受信するメールの内容などを保護します。

APOPには、脆弱性が発見されており、APOPが廃止の流れですので、APOPの代わりといった位置づけの部分だけでも意味があります。

もちろん証明書は、Let's Enctyptから取得します。

そのため、このページは、Let's Encrypt(無償の証明書発行機関)から取得を理解している前提になります。

まだの方は、こちらを先にご覧ください。

前の例と同様、WebサーバのWebrootプラグインでとってくるため、メールサーバのFQDNは、mail.example.comとし、Webサーバと同じIPとします。

メールサーバにApacheなどのWebサーバソフトウエアがインストールされていないのであれば、standaloneプラグインで取得することもできます。

メールサーバとして機能するための、DNSサーバの設定は済んでいるものとします。

準備

Let'sEncrypt以前は、Webサーバと同じFQDNにして、有償の証明書を利用していた方もいると思いますが、Let's Enctyptの証明書は、いくつドメイン名があっても、無償。なので、あえて別ドメイン名で行きます。

まず、mail.example.comのサーバ証明書をとってきます。

前の例のように、バーチャルホストを作ってもいいのですが、今回は、IP直打ちなどで使われるデフォルトサーバのhtdocsを使います。

ここでは、/var/http/default/htdocs/だとします。つまり、

/usr/local/apache2/conf/httpd.conf
…(省略)…
DocumentRoot "/var/http/default/htdocs"
…(省略)…

といった設定を想定します。この場合、以下のコマンドで、証明書が取れます。

# cd /usr/local/letsencrypt
# ./letsencrypt-auto certonly -t -d mail.examile.com  -a webroot --webroot-path=/var/http/default/htdocs/ --rsa-key-size 2048 --server https://acme-v01.api.letsencrypt.org/directory 

一方、メールサーバにApacheなどのWebサーバソフトウエアがインストールされていないのであれば、standaloneプラグインで取得することもできます。

この場合、-a webroot --webroot-path=…を-a standaloneに置き換えます。

ファイアウォールなどを通している場合、80番ポートをあけておいてください。

# cd /usr/local/letsencrypt
# ./letsencrypt-auto certonly -t -d mail.examile.com  -a standalone --rsa-key-size 2048 --server https://acme-v01.api.letsencrypt.org/directory 

webrootプラグインか、standaloneプラグインの、いずれかの方法で、うまく証明書が取れたら、これと秘密鍵を結合するスクリプトを作ります。

# vi make_qmail_pem.sh
/etc/letsencrypt/make_qmail_pem.sh
#! /bin/sh
/bin/cat /etc/letsencrypt/live/mail.examile.com/privkey.pem > /etc/letsencrypt/live/mail.examile.com/qmail.pem
/bin/cat /etc/letsencrypt/live/mail.examile.com/fullchain.pem >> /etc/letsencrypt/live/mail.examile.com/qmail.pem

何でこんなことをするかというと、ucspi-tcpのtcpserver用のSSLパッチが、秘密鍵と証明書関連を1ファイルにまとめる仕様になっているからです。

実行できるようにパーミッションを設定します。

# chmod 700 make_qmail_pem.sh
# ./make_qmail_pem.sh

/etc/letsencrypt/live/mail.examile.com/qmail.pemが生成されているか確認してください。

# more /etc/letsencrypt/live/mail.examile.com/qmail.pem
-----BEGIN PRIVATE KEY-----
(秘密鍵)
-----END PRIVATE KEY-----
-----BEGIN CERTIFICATE-----
(証明書)
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
Let's Encryptの中間証明書
-----END CERTIFICATE-----

続いて、DHE用のパラメータを生成しておきます。これは結構時間がかかります。ビビらないでください。

# openssl dhparam -out /var/qmail/control/dh_param.pem 2048
(…省略…)
# chmod 600 /var/qmail/control/dh_param.pem

いよいよ、ucspi-tcpのtcpserverをビルドします。

tcpserverのコンパイル

# cd ~
# wget http://cr.yp.to/ucspi-tcp/ucspi-tcp-0.88.tar.gz
# wget http://www.nrg4u.com/qmail/ucspi-tcp-ssl-20050405.patch.gz
# tar xzf ucspi-tcp-0.88.tar.gz
# gzip -d ucspi-tcp-ssl-20050405.patch.gz
# cd ucspi-tcp-0.88
# patch < ../ucspi-tcp-ssl-20050405.patch

このままでは、鍵交換にDHEおよび、ECDHEが使えないので、以下のコードを修正

即興で作ったものなので、各自自己責任でお使いください。

# vi tcpserver.c
~/ucspi-tcp-0.88/tcpserver.c
//2行目あたりに以下を追加(ECDHE用)
#include <openssl/ec.h>
#include <openssl/bn.h>
…(省略)…
//530行目あたり
/*
#ifdef WITH_SSL
  if (flagssl == 1) {
    /* setup SSL context (load key and cert into ctx) */
    SSL_library_init();
    ctx=SSL_CTX_new(SSLv23_server_method());
    if (!ctx) strerr_die2x(111,FATAL,"unable to create SSL context");

    /* set prefered ciphers */
    if (env_get("SSL_CIPHER"))
      if (SSL_CTX_set_cipher_list(ctx, env_get("SSL_CIPHER")) == 0)
       strerr_die2x(111,FATAL,"unable to set cipher list");

    if(SSL_CTX_use_RSAPrivateKey_file(ctx, certfile.s, SSL_FILETYPE_PEM) != 1)
      strerr_die2x(111,FATAL,"unable to load RSA private key");
    if(SSL_CTX_use_certificate_chain_file(ctx, certfile.s) != 1)
      strerr_die2x(111,FATAL,"unable to load certificate");
  }
#endif
*/
//これを下記のものに置き換え。
#ifdef WITH_SSL
  if (flagssl == 1) {
    /* setup SSL context (load key and cert into ctx) */
    SSL_library_init();
    ctx=SSL_CTX_new(SSLv23_server_method());
    if (!ctx) strerr_die2x(111,FATAL,"unable to create SSL context");

    /* set prefered ciphers */
    if (env_get("SSL_CIPHER"))
      if (SSL_CTX_set_cipher_list(ctx, env_get("SSL_CIPHER")) == 0)
        strerr_die2x(111,FATAL,"unable to set cipher list");


    long nOptions= SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3;
    nOptions|=SSL_OP_NO_COMPRESSION;
    nOptions|= SSL_OP_CIPHER_SERVER_PREFERENCE;

    FILE *paramfile = fopen("/var/qmail/control/dh_param.pem", "r");
    if (paramfile) {
        DH* dh = PEM_read_DHparams(paramfile, NULL, NULL, NULL);
        fclose(paramfile);
        if (dh != NULL) {
            if (SSL_CTX_set_tmp_dh(ctx, dh) != 1) {
                 strerr_die2x(111,FATAL,"unable to set DHparams");
            }else{
                nOptions|=SSL_OP_SINGLE_DH_USE;
            }
            DH_free(dh);
        }else{
            strerr_die2x(111,FATAL,"unable to read DHparams");
        }
    }

    EC_KEY *ecdh = EC_KEY_new_by_curve_name (NID_X9_62_prime256v1);
    if (ecdh){
        if (1 != SSL_CTX_set_tmp_ecdh (ctx, ecdh)){
            strerr_die2x(111,FATAL,"unable to set EC_KEY");
        }else{
            nOptions|= SSL_OP_SAFARI_ECDHE_ECDSA_BUG;
            nOptions|=SSL_OP_SINGLE_ECDH_USE;
        }
        EC_KEY_free (ecdh);
    }else{
        strerr_die2x(111,FATAL,"unable to new EC_KEY");
    }

    SSL_CTX_set_options(ctx,nOptions); 

    if(SSL_CTX_use_RSAPrivateKey_file(ctx, certfile.s, SSL_FILETYPE_PEM) != 1)
      strerr_die2x(111,FATAL,"unable to load RSA private key");
    if(SSL_CTX_use_certificate_chain_file(ctx, certfile.s) != 1)
      strerr_die2x(111,FATAL,"unable to load certificate");
  }
#endif
…(省略)…

つまるところ、DHCとECDHEの初期化を追加します。ついでに,SSLv2とv3を切ります。

仕上げに、makeとインストールします。

# make
# make setup

これで、SSL対応の/usr/local/bin/tcpserverが、インストールされたと思います。

起動スクリプト

最後に、daemontoolの起動スクリプトを書き換えます。

先ほどビルドしたtcpserverにはオプションが増えています。

-sオプションで、SSLを有効にします。さらに-nオプションで、先ほど結合した、証明書ファイルを指定します。

暗号スイートは、環境変数で設定します。SSL_CIPHER="TLSv1.2"といった感じです。

たとえば決め打ちでFSな暗号スイートを指定するため、SSL_CIPHER="DHE-RSA-AES256-GCM-SHA384"とすることもできます。

(これは、Outlook2016も対応している暗号アルゴリズムです。)

早速、daemontoolの起動スクリプトを書き換えます。

/service/qmail-pop3に平文のPOP3の設定があるとします。

# vi /service/qmail-pop3/run
/service/qmail-pop3/run
#!/bin/sh
exec env - PATH="/var/qmail/bin:$PATH" SSL_CIPHER="TLSv1.2" \
/usr/local/bin/tcpserver -v -s -n /etc/letsencrypt/live/mail.example.com/qmail.pem -R -H 0 995 \
/var/qmail/bin/qmail-popup mail.example.com /usr/local/bin/checkpassword \
/var/qmail/bin/qmail-pop3d Maildir 2>&1

以下のコマンドで、tcpserverを再起動します。

# /usr/local/bin/svc -d /service/qmail-pop3
# /usr/local/bin/svc -u /service/qmail-pop3

SMTP-Submission側も同様です。

# vi /service/qmail-submission/run
/service/qmail-submission/run
#!/bin/sh
exec env - PATH="/var/qmail/bin:$PATH" SSL_CIPHER="TLSv1.2" \
/usr/local/bin/tcpserver -v -s -n /etc/letsencrypt/live/mail.example.com/qmail.pem -H -R 0 465 \
/var/qmail/bin/qmail-smtpd /usr/local/bin/checkpassword 2>&1

以下のコマンドで、tcpserverを再起動します。

# /usr/local/bin/svc -d /service/qmail-submission
# /usr/local/bin/svc -u /service/qmail-submission

Outlook2016の設定

次に、OutlookのSSL設定を行います。

電子メールアカウント→電子メールアカウントの変更→詳細設定に進み、以下の通り、設定します。

outlook2016のスクリーンショット

POP3のポートを995に、「このサーバは暗号化された接続」にチェックを入れます。

SMTP側も同様、465ポートを選びます。

注意点としては、「使用する暗号化接続の種類」で「SSL」を選ぶことです。「TLS」だと、tcpserver(が使っているOpenSSL)がAcceptする段階で止まってしまいます。ここが「SSL」でも、DHE-RSA-AES256-GCM-SHA384といった、FS対応の暗号スイート(アルゴリズム)が利用できます。ネゴシエーション上、tcpserverからは、TLSv1.2に見えているので、問題ないでしょう。

AndroidのK-9 Mailの設定

アカウント設定から、以下の手順で、「メール受信」、「メール送信」の設定を変更します。

k-9 Mailのスクリーンショット

POP3のポートを995に、「SSL/TLSを使用する」を選択します。

SMTPのポートは465に、「SSL/TLSを使用する」を選択します。

cronの設定

Webサーバの時と同様、コマンド+オプションをシェルスクリプトに記載し、cronで、月に1回実行しましょう。qmailのpop3とsubmissionのリスタートも行います。

# vi /var/qmail//bin/cert_refresh.sh
/var/qmail//bin/cert_refresh.sh (webrootプラグインを使う場合)
#!/bin/sh
/usr/local/letsencrypt/letsencrypt-auto certonly -t -d mail.example.com -a webroot --webroot-path=/var/http/default/htdocs/ --renew-by-default --server https://acme-v01.api.letsencrypt.org/directory
/etc/letsencrypt/make_qmail_pem.sh
/usr/local/bin/svc -d /service/qmail-pop3
/usr/local/bin/svc -u /service/qmail-pop3
/usr/local/bin/svc -d /service/qmail-submission
/usr/local/bin/svc -u /service/qmail-submission
/var/qmail//bin/cert_refresh.sh (standaloneプラグインを使う場合)
#!/bin/sh
/usr/local/letsencrypt/letsencrypt-auto certonly -t -d mail.example.com -a standalone --renew-by-default --server https://acme-v01.api.letsencrypt.org/directory
/etc/letsencrypt/make_qmail_pem.sh
/usr/local/bin/svc -d /service/qmail-pop3
/usr/local/bin/svc -u /service/qmail-pop3
/usr/local/bin/svc -d /service/qmail-submission
/usr/local/bin/svc -u /service/qmail-submission

仕上げに、実行できるようにパーミッションを変えます。

# chmod 700 /var/qmail//bin/cert_refresh.sh

なお、cronの設定は、短期間に連続した発行の制限がありますので、よく考えてタイミングを決めてください。

cronの起動日時は、Webサーバの証明書再取得のスクリプトとは、ずらしておいた方がいいでしょう。

たとえば、毎月10日の午前3時30分に証明書の再取得と、tcpserverのリスタートをするとすれば、

# crontab -e
/var/spool/cron/crontabs/root/
30 3 10 * * * /var/qmail//bin/cert_refresh.sh

となります。これで、Let's Encryptのサービスが終わったり、障害が起きない限り、有効な証明書が、永久に維持され続けます。

NEXT >> トップページに戻る

©Copyrights 2015-2023, non-standard programmer

このサイトは、あくまでも私の個人的体験を、綴ったものです。 軽く参考程度にご利用ください。