JavaからLDAPSで「unable to find valid certification path to requested target」とエラーになる現象に遭遇しました

JavaでLDAPS（LDAP over SSL/TLS）をやりたくて実験プログラムを作ったところ、以下のようなエラーに遭遇しました。

```shell-session:Javaエラーメッセージ
Lookup failed: javax.naming.CommunicationException: simple bind failed: Samba4サーバ:636 [Root exception is javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target]
```

結果からの推測ですが、デフォルトのキーストア（jre/lib/security/cacrts）であってもVM引数かJAVA_OPTSで指定する必要があるようです。

以下、参考までに実験プログラムとキーストアにサーバ証明書（公開鍵）を登録する方法を記しておきます。

##実験プログラム

```java:実験ソース
import java.util.Hashtable;

import javax.naming.AuthenticationException;
import javax.naming.Context;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;

public class App {
  public static void main(String args[]) {
    final String username = "user3";

    Hashtable<String, String> env = new Hashtable<String, String>();
    env.put(Context.INITIAL_CONTEXT_FACTORY,
        "com.sun.jndi.ldap.LdapCtxFactory");
    env.put(Context.SECURITY_AUTHENTICATION, "simple");
    env.put(Context.SECURITY_PRINCIPAL, username + "@devel.local");
    env.put(Context.SECURITY_CREDENTIALS, "User3###");
    env.put(Context.PROVIDER_URL, "ldaps://Samba4サーバ:636");
    env.put(Context.SECURITY_AUTHENTICATION, "simple");
    env.put(Context.SECURITY_PROTOCOL, "ssl");

    DirContext ctx;
    try {
      ctx = new InitialDirContext(env);
      Attributes attrs = ctx
        .getAttributes("CN=user3,CN=Users,DC=devel,DC=local");

      if (attrs != null) {
        try {
          for (NamingEnumeration ae = attrs.getAll(); ae.hasMore();) {
            Attribute attr = (Attribute) ae.next();
            System.out.println("attribute: " + attr.getID());
            for (NamingEnumeration e = attr.getAll(); e.hasMore(); 
              System.out.println("value: " + e.next()));
          }
        } catch (NamingException e) {
          e.printStackTrace();
        }
      }
      ctx.close();
    } catch (AuthenticationException e) {
      System.out.println("Invalid credentials");
    } catch (NamingException e) {
      System.out.println("Lookup failed: " + e);
    }
  }
}

```

##キーストアにサーバの自己証明書を登録する方法

###サーバの自己証明書を作成する(サーバ)

末尾に示す「Samba4のための自己証明書の作成方法」を参考にして、サーバの自己証明書を作成します。そのファイルはmyCert.pemとします。

###サーバの自己証明書をキーストアに登録する(クライアント)

サーバで作ったmyCert.pemを%JAVA_HOME%\jre\lib\securityにコピーした後、以下のコマンドでインポートします。

```dos:Windowsのコマンドプロンプトにて
cd %JAVA_HOME%\jre\lib\security
keytool -keystore cacerts -importcert -alias surnet-samba-ad -file myCert.pem
```

エリアスに指定した名前（surnet-samba-ad）は何でもいいと思います。キーストアはデフォルトのキーストアを指定しましたが、新しく作ってもいいと思います。

###実験プログラムを実行する

実験プログラムを実行する前に、JAVA_OPTSにキーストアを指定しておきます。

```shell-session:実験プログラムを実行する前に
set JAVA_OPTS="-Djavax.net.ssl.trustStore=%JAVA_HOME%\jre\lib\security\cacerts -Djavax.net.ssl.trustStorePassword=changeit"
```

##参考

- [Samba4のための自己証明書の作成方法](https://wiki.samba.org/index.php/Setup_LDAPS_on_a_DC)
- [Windows 7でキーストアにサーバの自己証明書を登録する方法](http://azure.microsoft.com/ja-jp/documentation/articles/java-add-certificate-ca-store/)