如何接受JNDI / LDAP连接的自签名证书?
我需要通过SSL连接到LDAP目录。
在非生产环境中,我们使用自签名证书,当然,这些证书无法通过以下方式validation:
javax.naming.CommunicationException: simple bind failed: ldapserver: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] at com.sun.jndi.ldap.LdapClient.authenticate(LdapClient.java:197) at com.sun.jndi.ldap.LdapCtx.connect(LdapCtx.java:2694) at com.sun.jndi.ldap.LdapCtx.(LdapCtx.java:293) at com.sun.jndi.ldap.LdapCtxFactory.getUsingURL(LdapCtxFactory.java:175) at com.sun.jndi.ldap.LdapCtxFactory.getUsingURLs(LdapCtxFactory.java:193) at com.sun.jndi.ldap.LdapCtxFactory.getLdapCtxInstance(LdapCtxFactory.java:136) at com.sun.jndi.ldap.LdapCtxFactory.getInitialContext(LdapCtxFactory.java:66) at javax.naming.spi.NamingManager.getInitialContext(NamingManager.java:667) at javax.naming.InitialContext.getDefaultInitCtx(InitialContext.java:288) at javax.naming.InitialContext.init(InitialContext.java:223) at javax.naming.ldap.InitialLdapContext.(InitialLdapContext.java:134)
我知道如何使用自定义信任管理器进行启用SSL的连接 ,但是不知道如何使用连接JNDI API的连接,而我不管理实际的连接。 也就是说,以下标准设置在哪里可以插入信任管理器?
提前致谢。
Hashtable env = new Hashtable(); env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); env.put(Context.PROVIDER_URL, "ldaps://ldapserver:636"); env.put(Context.SECURITY_PROTOCOL, "ssl"); env.put(Context.SECURITY_AUTHENTICATION, "simple"); env.put(Context.SECURITY_PRINCIPAL, "myUser"); env.put(Context.SECURITY_CREDENTIALS, "myPassword"); LdapContext ctx = new InitialLdapContext(env, null); ctx.search (...)
根据JNDI文档,似乎可以设置自定义SSLSocketFactory
http://download.oracle.com/javase/1.5.0/docs/guide/jndi/jndi-ldap-gl.html#socket
public class MySSLSocketFactory extends SocketFactory { private SSLSocketFactory sf; public MySSLSocketFactory() { KeyStore keyStore = ... /* Get a keystore containing the self-signed certificate) */ TrustManagerFactory tmf = TrustManagerFactory.getInstance(); tmf.init(keyStore); SSLContext ctx = SSLContext.getInstance("TLS"); ctx.init(null, tmf.getTrustManagers(), null); sf = ctx.getSocketFactory(); } /* delegate SSLSocketFactory public methods to sf */ ... }
配置环境以使用此套接字工厂
env.put("java.naming.ldap.factory.socket", "com.example.MySSLSocketFactory");
覆盖Trustmanager时,您可以接受任何证书:
DummyTrustmanager.java
public class DummyTrustmanager implements X509TrustManager { public void checkClientTrusted(X509Certificate[] xcs, String string) throws CertificateException { // do nothing } public void checkServerTrusted(X509Certificate[] xcs, String string) throws CertificateException { // do nothing } public X509Certificate[] getAcceptedIssuers() { return new java.security.cert.X509Certificate[0]; } }
MySSLSocketFactory.java
public class MySSLSocketFactory extends SSLSocketFactory { private SSLSocketFactory socketFactory; public MySSLSocketFactory() { try { SSLContext ctx = SSLContext.getInstance("TLS"); ctx.init(null, new TrustManager[] { new DummyTrustmanager() }, new SecureRandom()); socketFactory = ctx.getSocketFactory(); } catch (Exception ex) { ex.printStackTrace(System.err); /* handle exception */ } } public static SocketFactory getDefault() { return new MySSLSocketFactory(); } @Override public String[] getDefaultCipherSuites() { return socketFactory.getDefaultCipherSuites(); } @Override public String[] getSupportedCipherSuites() { return socketFactory.getSupportedCipherSuites(); } @Override public Socket createSocket(Socket socket, String string, int i, boolean bln) throws IOException { return socketFactory.createSocket(socket, string, i, bln); } @Override public Socket createSocket(String string, int i) throws IOException, UnknownHostException { return socketFactory.createSocket(string, i); } @Override public Socket createSocket(String string, int i, InetAddress ia, int i1) throws IOException, UnknownHostException { return socketFactory.createSocket(string, i, ia, i1); } @Override public Socket createSocket(InetAddress ia, int i) throws IOException { return socketFactory.createSocket(ia, i); } @Override public Socket createSocket(InetAddress ia, int i, InetAddress ia1, int i1) throws IOException { return socketFactory.createSocket(ia, i, ia1, i1); } }
Main.java
public class Main { public static void main(String[] args) throws NamingException { Hashtable env = new Hashtable(11); env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); env.put(Context.PROVIDER_URL, "ldaps://ldapserver:636/"); env.put(Context.SECURITY_PROTOCOL, "ssl"); env.put(Context.SECURITY_AUTHENTICATION, "simple"); env.put(Context.SECURITY_PRINCIPAL, "myUser"); env.put(Context.SECURITY_CREDENTIALS, "myPassword"); env.put("java.naming.ldap.factory.socket", "ldapsecure.MySSLSocketFactory"); LdapContext ctx = new InitialLdapContext(env, null); } }
无需代码。 只需将证书添加到测试客户端信任库即可。
不要为此编写额外的代码。 它是不安全的,并且您承担了代码泄漏到生产中的主要风险。 你真的不想那样。
@Jcs答案是正确的,但如果您不想使用自定义TrustManager,并且您愿意将该特定自签名证书作为VM中其他用途的可信CA,您可以:
- 将此证书添加到您的默认信任库(通常是JRE安全目录下的
cacerts
)或 - 在不同位置(可能基于默认
cacerts
的副本)创建一个新的信任库,该信任库将包含该特定证书,并将其用作默认信任库,但设置javax.net.ssl.trustStore*
系统属性。
自从我上次这样做了大约5年以来,所以我的答案有点模糊,我害怕; 但我认为如果你在java.security中可信任的提供者,你应该能够在/ usr / java / jre / lib / security /下找到它。 然后它会接受证书作为可信任的。
我此刻无法访问我的笔记,但我稍后会仔细阅读
完全放弃JNDI, 一些框架将引用LOCAL SYSTEM LDAP配置(这通常是/etc/ldap.conf或类似的东西)。 当我说LOCAL时,我指的是运行JNDI操作的系统。
至少在大多数支持LDAP的Linux操作系统的情况下,这样的LDAP配置文件内部(通常)是一行:
TLS_REQCERT demand
这是默认的TLS / SSL关键性设置(并且是最严格的),因为如果证书的ANYTHING WHATSOEVER错误(包括自签名),它将使连接失败。
您可以尝试使用不同的设置而不是“需求”(使用“允许”或“从不”)。 完成此操作后,再次尝试操作,看看问题是否已解决。 同样,并非所有此类内容都会读取甚至检测您的本地系统设置。 有些人有,有些则没有。 值得一看。
我希望这有帮助…
马克斯