Ubuntu 12.04 LTS のOpenLDAPをSSLに対応する際の注意

# 重要な追記 (2013-05-24)

以下の説明が全体的に危ういことが判明しました。

* gnutlsとOpenSSLのアルゴリズムの違い等はslapdが起動しないこととは__関係ない__
* slapdがOpenSSLの証明書で起動しなかった最大の理由はAppArmorの設定にそぐわない秘密鍵/証明書の配置
* OpenSSLの (sha1を使った) 素の自己署名証明書/秘密鍵でも、とりあえずslapd動作はする。
* 本家の説明の通りにやる分には何ら問題ない

お、おう。

[Ubuntu 12.04 LTS のOpenLDAPをSSLに対応する際の注意 Rev.2](http://qiita.com/items/d032c087742a51fb9eb7)

# 要約

どこかの時点でUbuntuのOpenLDAPはgnutlsを用いています。このとき、デフォルトで許容する証明書のSignature Algorithm等についてOpenSSLの標準よりgnutlsが厳しい(らしい)ことにより、OpenSSLの標準の設定ファイルで発行された自己署名証明書ではOpenLDAPのSSL接続を有効にして起動できなくなるという問題が観測されました。

Canonicalの出しているUbuntu 12.04 のマニュアルに素直に従うか、よく状況を理解した上でOpenSSLの設定 (/etc/ssl/openssl.cnf) を変更することで対処出来る可能性があります (後者の動作は未確認です)。

公式の方法の方がはるかに簡単です。ただ、ここではメモ書き程度に以下では状況判明から解明までの流れを書いておきます。<del>ちなみにOpenSSLの方法はやや煩雑が過ぎるので細かい検証が適当なままです。動く方法が分かってるのに敢えて不適切な方法について全部解明するモチベーションは維持しづらい。</del>追記 2013-05-24: 一応、OpenSSLのツールセットを使って動作する例を書きました。めんどい。

動作のチェックにはUbuntu 12.04 LTS OpenLDAP 2.4.28-1.1ubuntu4 OpenSSL 1.0.1-4ubuntu3 を使っています

# 公式の方法

https://help.ubuntu.com/12.10/serverguide/openldap-server.html

概要レベルで言うとちょーかんたんです。以下の3つをOpenLDAPに伝えるだけ。

* CA証明書 (特にもし自己署名なら)
* サーバ証明書
* 鍵

単にトラブルシューティング対策ならこれでおしまいです。

この設定はOLC (cn=config) 以前と今でほぼ変化はありません。そのため、古いバージョンについての記事は (slapd.conf を使うのと同時に) openssl で自己署名の証明書を作る方法での成功事例も見受けられます。自力でビルドするケースを考えた際、gnutlsを避けることで引き続き今回の問題を回避できる可能性が高いと思われます。

# OpenSSLで自己署名の証明書を発行した場合に発生する問題

OpenSSLによる普通の自己署名の証明書では直接的には以下のエラーが出ます。

    slapd[32097]: main: TLS init def ctx failed: -1

理由が全くわかりません。

# openssl ツールで丁寧に情報をあさる

openssl x509 -in (file) -text -noout の結果を丹念に見ると、3点大きく異なる点に気が付きました。

* gnutlsを使った方法はCAに別の証明書を用いている
* CA証明書の用途がCertificate Signに限定されている (OpenSSLでは指定がない)
* Signature Algorithmがsha256WithRSAEncryptionとsha1WithRSAEncryptionで異なる

上記のエラーについては、第三者 (Signature Algorithm) がどうやら直接の原因のようでした。試しにsha256で自己署名の証明書を発行して使わせたところ、slapdが起動しない問題は回避できました。SSL接続出来るかまでは、すみません確認してません。

[6-6(0) 証明書発行の流れ](http://www.viva-musen.net/archives/cat_662133.html)

なお、OLC上で受容できるアルゴリズムは変更できるようです。man slapd.d を参照してください。それによってOpenSSL側の証明書を受け入れることも出来る可能性があります。

# 3種類の違いを検証する方法

CA.shもしくはCA.plを用いつつ /etc/ssl/openssl.cnf を編集することで挙動をgnutls的に出来ます。CA.pl等を使えばそもそも一つ目が達成されます。

    [ v3_ca ]
    ...
    keyUsage=critical, keyCertSign

これにより、二番目が達成されます。注意点として、CA証明書を作るときだけこのオプションを指定する必要があります。めんどい。

# OpenSSL のコマンドラインツールだけでなんとかする方法 (追記ed on 2013-05-24)

CA.plを使います。 /usr/lib/ssl/misc/CA.pl にあります。man ca.pl などとやってどういう動作をするかは確認しておいた方が良さそうです。主な注意点:

* 作業ディレクトリ上にファイルやディレクトリが勝手に作られます
    * CA関連のファイルとして demoCA/ が出来、操作の対象となるファイル名は固定 (newreq.pem, newcert.pem等が勝手にディレクトリ配下に出来る)
* CA.pl は/etc/ssl/openssl.cnf の内容を暗黙のうちに参照します
    * 以下ではCA.pl (やその下敷きになっている CA.sh) を改変しない前提でやっています。
* ここでは Debian wheezy のOpenSSL 1.0.1e-2 を使って検証しています。複数のOSまたがってやっててこの記事内でもバージョンに色々ズレがあったりしてます。バグ踏まなければ、動作に差はないはずですが

## 作業ディレクトリを作る

空のディレクトリを用意します。ここではca/ とします。

## /etc/ssl/openssl.cnf を変更してsha256を使うようにする

    default_md      = sha256

これをやると以降全てのopenssl コマンドでsha256を使うようになるはずです。標準の環境で使い続けたいのなら、今回の作業後に必要なら戻すことを忘れないように。あ、いや、sha256の方が強いですから、そのまんまでも、良いんですけどね

## /etc/ssl/openssl.cnf の v3_ca は以下にある keyUsage を変更

(このステップが本件に直結して本当に必要かはわかりませんが)

以下を追加

    keyUsage=critical, keyCertSign

__この設定はすぐに消します。残しておくと副作用が大変でかいので注意__

もともと、openssl.cnf に以下の注意書きがある点もあわせて参照してください

    # Key usage: this is typical for a CA certificate. However since it will
    # prevent it being used as an test self-signed certificate it is best
    # left out by default.
    # keyUsage = cRLSign, keyCertSign

critical, keyCertSignのみにしてるのは、出来るCA証明書がCanonical提供のcerttoolで生成したものと似た形式になるようにです。このあたりのフラグ、私も全部把握してませんので、好きにいじってください

この設定が正しく証明書に反映されている場合には、X509v3 extensions 下の表記が増えます。

    ca> openssl x509 -text -noout -in demoCA/cacert.pem
    Certificate:
        Data:
            Version: 3 (0x2)
            Serial Number: 12..9 (..)
        Signature Algorithm: sha256WithRSAEncryption
            Issuer: C=JP, ST=Tokyo, O=mokha Inc., CN=mokha-ca
            Validity
                Not Before: May 24 00:43:26 2013 GMT
                Not After : May 23 00:43:26 2016 GMT
            Subject: C=JP, ST=Tokyo, O=mokha Inc., CN=mokha-ca
            Subject Public Key Info:
                Public Key Algorithm: rsaEncryption
                    Public-Key: (2048 bit)
                    Modulus:
                        00:9f:b9:a2:83:02:a7:53:57:a3:8a:4f:c4:3b:47:
                        ...
                        60:fc:7c:32:eb:f4:b6:f2:a0:98:b6:c0:cb:a9:06:
                        ef:bb
                    Exponent: 65537 (0x10001)
            X509v3 extensions:
                X509v3 Subject Key Identifier: 
                    86:C6:36:B1:85:EC:CE:B5:0B:2D:F9:2C:AF:75:5F:97:02:0E:75:3A
                X509v3 Authority Key Identifier: 
                    keyid:86:C6:36:B1:85:EC:CE:B5:0B:2D:F9:2C:AF:75:5F:97:02:0E:75:3A
    
                X509v3 Basic Constraints: 
                    CA:TRUE
                X509v3 Key Usage: critical
                    Certificate Sign
        Signature Algorithm: sha256WithRSAEncryption
             04:37:1f:f3:78:6b:e1:97:94:e1:86:25:5b:3c:2d:01:a8:13:
             ..
             fe:1a:18:96

## newCA

コマンドライン上で

    ca> /usr/lib/ssl/misc/CA.pl -newca
    (CAの秘密鍵のパスワードを聞かれます)

## keyUsage を消す

コメントアウト

    # keyUsage=critical, keyCertSign

## サーバの秘密鍵とcsrを生成

ここではパスワードなしのrss秘密鍵を作っときます。ここでは秘密鍵のパスワードは外しました

    openssl genrsa -des3 2048 > test.key
    (パスワードを必要とします)
    openssl rsa -in new.key -out new.key
    (パスワードを聞かれます)
    openssl req -new -key new.key -out new.csr
    (色々聞かれますが、省略)

## 名前を newcsr.pem に変更して signCA

    > /usr/lib/ssl/misc/CA.pl -signCA
    (CAの秘密鍵のパスワードを聞かれます)

newcert.pemができます。

## 正しいディレクトリに置き、正しいownerとパーミッションを設定する

ここはgnutlsのケースと同じにしてください。例えば、秘密鍵をworld readableとかにするとミスります

## OpenLDAPの設定を変更して、リブート

公式の説明だとオンラインで (== ldapaddやldapmodify等で) 変更しましょう、とありますが、slapdが設定ミスでブートしなくなったケースではこの方法は使えません。

仮にOLC方面で設定ミスってブートしなくなったら、直接 slapd.d/ 下をいじる以外に対処方法がありません (本家非推奨の方法ですが)。/etc/ldap/slapd.d/cn\=config.ldif を手で変更します。

   olcTLSCACertificateFile: /etc/ssl/certs/test_ca.pem
   olcTLSCertificateFile: /etc/ssl/certs/test_cert.pem
   olcTLSCertificateKeyFile: /etc/ssl/private/test_key.pem

/etc/default/slapd でldaps:/// を追加するのも忘れないように。

## 動作検証

とりあえずslapdがブートしているかを確認し、ldapsをListenしていることをチェック。

    openssl s_client -connect ldap.example.com:636 -CAfile demoCA/cacert.pem

最後にLDAPの口にアクセスできるホストから openldap s_connect を使ってSSL接続で使われている証明書が上記で設定したものになっていることを確認します。

## おまけ

OpenLDAPで自己署名のサーバ証明書を使うケースでは毎度二点ハマります

* CAを証明書をアクセスする元で登録するのを忘れる
* サーバ証明書のCNをサーバのURLと一致させるのを忘れる

後者が比較的地味に効くことがありますので注意。例えばホスト名が ldap.example.com だったらサーバ証明書のCNが CN=ldap.example.com でないとldapsearchはエラーを吐きます。今回のケースではCSR作るときに正しく指定するのが一番ラクでしょう。

# cert dbを用いる方法は？

CentOS 6.3 の環境で上記の設定が全てcert dbを用いて設定されているケースを見ました。

    olcTLSCACertificatePath: /etc/openldap/certs
    olcTLSCertificateFile: "OpenLDAP Server"
    olcTLSCertificateKeyFile: /etc/openldap/certs/password

とかなってました。明らかにPEMを参照する方法ではないです。こちらの流儀について、ここでは一切の説明を省略します。

* http://www.mozilla.org/projects/security/pki/nss/tools/certutil.html
* http://www.bradchen.com/blog/2012/08/openldap-tls-issue
* http://www.cmiss.org/cmgui/wiki/CreateYourOwnCertificateAuthority

# 追記 (2013-05-24)

## なぜ gnutls を使ったし

OpenSSLにGPLと互換性ががないからだそうです。

* [WikipediaのGnuTLSの説明](http://ja.wikipedia.org/wiki/GnuTLS)
* [互換性がないことの解説](http://people.gnome.org/~markmc/openssl-and-the-gpl.html)

2つ目はわかりやすいです。ざっくり言えば「OpenSSLを使ったよって書いてね」という条項がOpenSSLのライセンスにあり、GPLはそれを許さないということですね。「GPLの配下にある全てのプロジェクトにおいてOpenSSLは使えない」というのが常識だったということです。おー

## 使い方みたいな話

* https://help.ubuntu.com/community/GnuTLS

## おまけ

Canonicalがもう少しヨサゲなやり方を出してた。

* https://help.ubuntu.com/12.04/serverguide/certificates-and-security.html