2020-10-14に投稿

psql (libpq) が subjectAltName の iPAddress を見ない

Qrunch から引っ越し

ドキュメントを読むと IP アドレスでのサーバ証明書検証もやってくれそうな気配はある。

32.18.1. サーバ証明書のクライアント検証

もし接続がホスト名ではなくIPアドレスを使用するのであれば、(いかなるDNS検索もせず)IPアドレスがマッチさせられます。

しかし psql から IPアドレスで接続して証明書を検証させようとすると、以下のようなエラーになった。

$ psql "port=5432 host=192.0.2.1 sslcert=./user.crt sslkey=./user.key sslrootcert=./ca.crt sslmode=verify-full dbname=postgres user=user"
psql: server certificate for "example.com" (and 1 other name) does not match host name "192.0.2.1"

バージョン 9.6.9src/interfaces/libpq/fe-secure-openssl.c から関連部分を抜粋

verify_peer_name_matches_certificate()

    /*
     * First, get the Subject Alternative Names (SANs) from the certificate,
     * and compare them against the originally given hostname.
     */
    peer_san = (STACK_OF(GENERAL_NAME) *)
        X509_get_ext_d2i(conn->peer, NID_subject_alt_name, NULL, NULL);

    if (peer_san)
    {
        int         san_len = sk_GENERAL_NAME_num(peer_san);

        for (i = 0; i < san_len; i++)
        {
            const GENERAL_NAME *name = sk_GENERAL_NAME_value(peer_san, i);

            if (name->type == GEN_DNS)
            {
                char       *alt_name;

                names_examined++;
                rc = verify_peer_name_matches_certificate_name(conn,
                                                             name->d.dNSName,
                                                               &alt_name);
                if (rc == -1)
                    got_error = true;
                if (rc == 1)
                    found_match = true;

                if (alt_name)
                {
                    if (!first_name)
                        first_name = alt_name;
                    else
                        free(alt_name);
                }
            }
            if (found_match || got_error)
                break;
        }
        sk_GENERAL_NAME_free(peer_san);
    }

    /*
     * If there is no subjectAltName extension of type dNSName, check the
     * Common Name.
     *
     * (Per RFC 2818 and RFC 6125, if the subjectAltName extension of type
     * dNSName is present, the CN must be ignored.)
     */
    if (names_examined == 0)
    {
        X509_NAME  *subject_name;

        subject_name = X509_get_subject_name(conn->peer);
        if (subject_name != NULL)
        {
            int         cn_index;

            cn_index = X509_NAME_get_index_by_NID(subject_name,
                                                  NID_commonName, -1);
            if (cn_index >= 0)
            {
                names_examined++;
                rc = verify_peer_name_matches_certificate_name(
                                                               conn,
                                                    X509_NAME_ENTRY_get_data(
                                X509_NAME_get_entry(subject_name, cn_index)),
                                                               &first_name);

                if (rc == -1)
                    got_error = true;
                else if (rc == 1)
                    found_match = true;
            }
        }
    }

Subject Alternative Name に関しては
name->type == GEN_DNS なケースしかチェックしないので、DNS:example.com のような dNSName フィールドのみチェックし IP:192.0.2.1 のような iPAddress フィールドはスキップされるようだ。

CN に IP アドレスを入れておけば検証されると思われるが、
If there is no subjectAltName extension of type dNSName, check the Common Name. とコメントにある通り、subjectAltname に dNSName があったら CN のチェックも行われないので、名前との共存は望めないようだ。

証明書を発行する時に DNS:192.0.2.1 のように subjectAltName に dNSName として IP アドレスを入れてしまう細工しておくと検証に成功するが、素直に名前を使うのが良いだろう。

ツイッターでシェア
みんなに共有、忘れないようにメモ

albatross0

Crieitは誰でも投稿できるサービスです。 是非記事の投稿をお願いします。どんな軽い内容でも投稿できます。

また、「こんな記事が読みたいけど見つからない!」という方は是非記事投稿リクエストボードへ!

有料記事を販売できるようになりました!

こじんまりと作業ログやメモ、進捗を書き残しておきたい方はボード機能をご利用ください。
ボードとは?

コメント