Thursday, January 9, 2014

UnboundID SDK で LDAP over SSL/TLS を

手元の環境の LDAP サーバー (OpenLDAP 2.4.38) をビルドしなおし, SSL/TLS 対応させてみました。 今回は, UnboundID LDAP SDK for Java を使って, LDAP over SSL/TLS (いわゆる LDAPS) 経由で LDAP サーバーに接続する方法をいくつかためしてみましたので, それらについて書き残しておきたいと思います。

SSL/TLS 経由で接続する場合, 何らかの形で SSL ソケットを生成するソケットファクトリを用意し, LDAPConnection のコンストラクターに引数として渡してあげることが必要です。 今回は, 次のようなメソッドを, あらかじめ用意しておきます。

    private void testConnection(SocketFactory socketFactory) {
        LDAPConnection conn = null;
        try {
            conn = new LDAPConnection(socketFactory, "192.168.1.1", 636);
            logger.info("Connection successful.");
        } catch (LDAPException e) {
            logger.error("Connection failed.", e);
        }
        if (conn != null)
            conn.close();
    }

1. デフォルトのソケットファクトリを利用する方法

ここでデフォルトのソケットファクトリというのは, javax.net.ssl.SSLSocketFactorygetDefault という static メソッドを利用して取得することのできるソケットファクトリのことを言っています。

    private void useDefaultSocketFactory() {
        SocketFactory socketFactory = SSLSocketFactory.getDefault();
        testConnection(socketFactory);
    }

残念ながら, わたしの手元の環境で用意した証明書はプライベートなものであり, Java 実行環境のデフォルトの証明書ストアには存在しませんので, このままでは失敗してしまいます。 そこで, LDAP サーバーの証明書をインポートしたキーストアファイルを用意し, それを利用することを考えてみます。 以下では, truststore という名前のキーストアファイルのパスなどをシステムプロパティに渡してあげてから, ソケットファクトリのインスタンスを作成するようにしています。

    private void useDefaultSocketFactory() {
        System.setProperty("javax.net.ssl.trustStore", "truststore");
        System.setProperty("javax.net.ssl.trustStoreType", "jks");
        SocketFactory socketFactory = SSLSocketFactory.getDefault();
        testConnection(socketFactory);
    }

ただ, 一度でも, 同一 JVM 上で動作する他のアプリケーションのどこかで, 先にソケットファクトリのインスタンスを作成するようなことがおこなわれてしまいますと, あとから上記のようにシステムプロパティを変更しても, もはやそれは効果を持たないようであります。 かと言って, JVM の起動オプションでそれらを指定するのは, 同一 JVM 上で動作する他のアプリケーションに迷惑をかける可能性もありますし, あまりスマートではないような気がします。 (はっきりとした根拠を調べたわけではないのですが, Creating Https Connection Without javax.net.ssl.trustStore Property (May 8, 2013) の前半のほうに, それっぽいことが書かれていました。)

ならば, デフォルトのものとは違う自前のソケットファクトリを利用することを考えてみたらどうか, ということになります。 UnboundID LDAP SDK for Java には, com.unboundid.util.ssl.SSLUtil というユーティリティクラスがあります。 javax.net.ssl.TrustManager インスタンスから成る可変長配列を SSOUtil のコンストラクターに渡してあげてから createSSLSocketFactory というメソッドを呼びさえすれば, それ相応のソケットファクトリを返してくれます。 これは便利そうですので, これ以降はこれを使うことを前提とし, そのうえで, TrustManager インスタンスをどのように用意するかという点について, 見ていきたいと思います。

2. TrustStoreTrustManager を利用する方法

UnboundID LDAP SDK for Java には, TrustManager インターフェースを実装したクラスがいくつかあります。 それらのうち, ここでは com.unboundid.util.ssl.TrustStoreTrustManager を使ってみます。

    private void useTrustStoreTrustManager() {
        TrustManager trustManager
                = new TrustStoreTrustManager("truststore");
        SSLUtil sslUtil = new SSLUtil(trustManager);
        SocketFactory socketFactory = null;
        try {
            socketFactory = sslUtil.createSSLSocketFactory();
        } catch (GeneralSecurityException e) {
            logger.error("Socket factory not ready.", e);
        }
        if (socketFactory != null)
            testConnection(socketFactory);
    }

TrustStoreTrustManager のコンストラクターには, 信頼済み証明書を保管したキーストアファイルのパスを, 引数として渡してあげます。

これは, なかなか手軽でよいのですが, ファイルの絶対パスもしくは相対パスでしか指定することができず, たとえばインプットストリーム経由でキーストアを取り込むといったことがしづらいのが, やや不便かもしれません。

3. TrustManager を自前で用意する方法

キーストアをインプットストリーム経由で取り込むことができるようにしたいと考えると, 自前で TrustManager インスタンスを用意することになるのだろうと思います。

    private void useCustomTrustManager() {
        SocketFactory socketFactory = null;
        try(InputStream in = new FileInputStream("truststore")) {
            KeyStore trustStore
                    = KeyStore.getInstance(KeyStore.getDefaultType());
            trustStore.load(in, "changeit".toCharArray());
            TrustManagerFactory trustManagerFactory
                    = TrustManagerFactory.getInstance("PKIX");
            trustManagerFactory.init(trustStore);
            TrustManager[] trustManagers
                    = trustManagerFactory.getTrustManagers();
            SSLUtil sslUtil = new SSLUtil(trustManagers);
            socketFactory = sslUtil.createSSLSocketFactory();
        } catch (GeneralSecurityException | IOException e) {
            logger.error("Socket factory not ready.", e);
        }
        if (socketFactory != null)
            testConnection(socketFactory);
    }

上記では java.security.KeyStore のインスタンスを作成し, インプットストリーム経由でキーストアを取り込み, それを基に TrustManager インスタンスを作成しています。 この方法であれば, 信頼済み証明書を保管したキーストアをどこから入手するかという点に関して, 柔軟性が上がるんじゃないかと思います。

4. キーストアをインメモリで用意する方法

さらに進んで, こんな方法も考えることができそうです。 たとえば, 信頼したい証明書はあるんだけれど, わざわざそれを保管したキーストアをあらかじめどこかに用意しておくのは面倒くさい, といった場合に, キーストア自体を作成するという方法です。

    private void useInMemoryTrustStore() {
        SocketFactory socketFactory = null;
        try(InputStream in = new FileInputStream("openldap.crt")) {
            CertificateFactory certificateFactory
                    = CertificateFactory.getInstance("X.509");
            Certificate certificate
                    = certificateFactory.generateCertificate(in);
            KeyStore trustStore
                    = KeyStore.getInstance(KeyStore.getDefaultType());
            trustStore.load(null);
            trustStore.setCertificateEntry("openldap", certificate);
            TrustManagerFactory trustManagerFactory
                    = TrustManagerFactory.getInstance("PKIX");
            trustManagerFactory.init(trustStore);
            TrustManager[] trustManagers
                    = trustManagerFactory.getTrustManagers();
            SSLUtil sslUtil = new SSLUtil(trustManagers);
            socketFactory = sslUtil.createSSLSocketFactory();
        } catch (GeneralSecurityException | IOException e) {
            logger.error("Socket factory not ready.", e);
        }
        if (socketFactory != null)
            testConnection(socketFactory);
    }

上記コードで, "openldap.crt" というのは, わたしの手元の環境で OpenSSL を使って作成した LDAP サーバーの証明書ファイルです。

この方法は, How to use UnboundID SDK to connect to an LDAP server with the SSL server certificate? - Stack Overflow (Jul 16, 2013) に記載されていたものを参考にしました。

まとめ

以上です。

No comments:

Post a Comment