如何通过Java使用SSL和证书拨打LDAP(AD)的电话?

huangapple go评论69阅读模式
英文:

How to make a call, via Java, with ssl and certificate to an ldap (AD)?

问题

1, 我想通过 SSL 连接到一个 LDAP 数据库,通常是为了执行员工的搜索操作。

2, 必须附加一个证书到调用中,以便使调用被接受。

3, 证书可以添加到 JVM 的默认密钥存储中 - 就是在 JAVA_HOME / jre / lib / security / cacerts 中找到的位置。已经测试过这个方法,它是可行的 - 但我不想采用这个解决方案。不希望应用程序依赖于以这种方式配置其 JVM - 应用程序的操作和环境在一定程度上是我无法控制的。

4, 类似于第三点的解决方案也可以通过引用一个单独的密钥库来实现,使用 System.setProperty("javax.net.ssl.keyStore", "serverKeys")。由于与第3段相同的原因,我不想这样做,也不希望证书在环境中的每次调用中都存在。

5, 更希望所有的操作都在原生的 Java 中进行 - 不希望使用全新的 Spring 解决方案。

我更愿意能够仅仅为这次调用附加证书,只通过程序本身来完成。

以下是我当前调用的代码:

private List<Map<String, Object>> queryImp(String filter) throws NamingException {
    Hashtable env = new Hashtable();
    env.put(Context.INITIAL_CONTEXT_FACTORY, initialContextFactoryClassName);
    env.put(Context.PROVIDER_URL, url);
    env.put(Context.SECURITY_PRINCIPAL, user);
    env.put(Context.SECURITY_CREDENTIALS, password);
    DirContext context = new InitialDirContext(env);

    SearchControls sc = new SearchControls();

    sc.setReturningAttributes(attributeFilter);
    sc.setSearchScope(SearchControls.SUBTREE_SCOPE);
    NamingEnumeration results = context.search(base, filter, sc);
    List<Map<String, Object>> result = toMaps(results);
    context.close();
    return result;
}

LDAP 的地址类似于 ldaps://some-ad.foo。

在某个理想的幻想世界里,我可能会像这样做:env.put("cert-key-name", "raw-cert-base64-encoded-text") :*)

英文:

1, I want to make a call, via ssl, to an ldap database. It is a search to be performed, usually for an employee.

2, A certificate must be attached to the call in order for the call to be accepted.

3, The certificate could be added to jvm's default key storage - the one found on JAVA_HOME / jre / lib / security / cacerts. Have tested this and it works - but I do not want this solution. Do not want the application to be dependent on having its jvm configured in that way - the operation of and the environment for the application is, in part, out of my control.

4, Similar solution as in point three could also be made by referring to a separate key collection via System.setProperty ("javax.net.ssl.keyStore", "serverKeys"). Do not want to do this for the same reason as in paragraph 3 and because I dont want the cert to be in every call from the environment.

5, Would prefer to have it all in vanilla Java - not a brand new Spring solution.

I would prefer to be able to send with the certificate just for this call. To do it only through the program itself.

Below is my code for the call, as it looks now:

private List&lt;Map&lt;String, Object&gt;&gt; queryImp(String filter) throws NamingException {
    Hashtable env = new Hashtable();
    env.put(Context.INITIAL_CONTEXT_FACTORY, initialContextFactoryClassName);
    env.put(Context.PROVIDER_URL, url);
    env.put(Context.SECURITY_PRINCIPAL, user);
    env.put(Context.SECURITY_CREDENTIALS, password);
    DirContext context = new InitialDirContext(env);

    SearchControls sc = new SearchControls();

    sc.setReturningAttributes(attributeFilter);
    sc.setSearchScope(SearchControls.SUBTREE_SCOPE);
    NamingEnumeration results = context.search(base, filter, sc);
    List&lt;Map&lt;String, Object&gt;&gt; result = toMaps(results);
    context.close();
    return result;
}

The adress for the ldap is something like ldaps://some-ad.foo

In some ideal fantasy-land I would be able to do something like env.put("cert-key-name", "raw-cert-base64-encoded-text") :*)

答案1

得分: 1

以下是翻译好的部分:

有一种方法可以为单个LDAP调用设置信任存储库,而不是为所有SSL活动设置全局设置。为LDAP连接设置信任存储库涉及创建一个自定义SocketFactory。您可以使用此自定义SocketFactory从单独的信任存储库或证书文件中读取,而不依赖于javax.net.ssl.keyStore*属性引用的全局信任存储库。这个JNDI教程解释了如何实现这样的功能:<a href="https://docs.oracle.com/javase/jndi/tutorial/ldap/security/ssl.html">https://docs.oracle.com/javase/jndi/tutorial/ldap/security/ssl.html</a>

以下是您需要执行的步骤:

  1. env.put(Context.PROVIDER_URL, "ldap<b>s</b>://some-ad.foo.bar<b>:636</b>");
  2. env.put(Context.SECURITY_PROTOCOL, "SSL");
  3. env.put("java.naming.ldap.factory.socket", "<i>fully.qualified.name.of.your.custom.socket.factory.class</i>");

然后,您的自定义Socket工厂类将从您的自定义信任存储库或证书中读取。要将信任存储库的位置和密码传递给您的自定义Socket工厂类,您可以使用自定义用户定义的系统属性。

以下是使用您的LDAP查询方法和示例自定义Socket工厂的示例。我添加了额外的代码,用于完成使用自定义信任存储库:

private List&lt;Map&lt;String, Object&gt;&gt; queryImp(String filter) throws NamingException
{
    Hashtable env = new Hashtable();
    env.put(Context.INITIAL_CONTEXT_FACTORY, initialContextFactoryClassName);
    env.put(Context.PROVIDER_URL, url); //必须以“ldaps”开头并具有端口“636”
    env.put(Context.SECURITY_PRINCIPAL, user);
    env.put(Context.SECURITY_CREDENTIALS, password);

	//需要添加的新设置以进行LDAP SSL
	env.put(Context.SECURITY_PROTOCOL, &quot;SSL&quot;);
	env.put(&quot;java.naming.ldap.factory.socket&quot;, CustomLdapSslSocketFactory.class.getName()); //请参阅:https://docs.oracle.com/javase/jndi/tutorial/ldap/security/ssl.html

	/*这些是自定义用户定义的属性。可以随意命名。
    这些属性稍后将在自定义Socket工厂类中引用。*/
	System.setProperty(&quot;custom.ldap.truststore.type&quot;, &quot;pkcs12&quot;);
	System.setProperty(&quot;custom.ldap.truststore.loc&quot;, &quot;C:/certs/MyCustomLdapTruststore.p12&quot;);
	System.setProperty(&quot;custom.ldap.truststore.password&quot;, &quot;My custom ldap truststore password&quot;);
	System.setProperty(&quot;custom.ldap.ssl.protocol&quot;, &quot;TLSv1.2&quot;);

    DirContext context = new InitialDirContext(env);

    SearchControls sc = new SearchControls();

    sc.setReturningAttributes(attributeFilter);
    sc.setSearchScope(SearchControls.SUBTREE_SCOPE);
    NamingEnumeration results = context.search(base, filter, sc);
    List&lt;Map&lt;String, Object&gt;&gt; result = toMaps(results);
    context.close();
    return result;
}

以下是相应的示例自定义Socket工厂类。我使该类在每次调用public static SocketFactory getDefault()方法时返回一个单例。这就是为什么它比每次返回一个新实例所需的要复杂一些。

import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;

import javax.net.SocketFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;

public class CustomLdapSslSocketFactory extends SSLSocketFactory
{
    private SSLSocketFactory sslSocketFactory;

    private static volatile CustomLdapSslSocketFactory singletonCustLdapSslSockFact;

    private CustomLdapSslSocketFactory() throws KeyManagementException, KeyStoreException, FileNotFoundException, NoSuchAlgorithmException, CertificateException, IOException
    {
        sslSocketFactory = loadTrustStoreProgrammatically();
    }

    private static CustomLdapSslSocketFactory getSingletonInstance() throws KeyManagementException, KeyStoreException, FileNotFoundException, NoSuchAlgorithmException, CertificateException, IOException
    {
        if(CustomLdapSslSocketFactory.singletonCustLdapSslSockFact == null)
        {
            synchronized(CustomLdapSslSocketFactory.class)
            {
                if(CustomLdapSslSocketFactory.singletonCustLdapSslSockFact == null)
                {
                    CustomLdapSslSocketFactory.singletonCustLdapSslSockFact = new CustomLdapSslSocketFactory();
                }
            }
        }

        return CustomLdapSslSocketFactory.singletonCustLdapSslSockFact;
    }

    public static SocketFactory getDefault()
    {
        CustomLdapSslSocketFactory custLdapSslSockFact = null;

        try
        {
            custLdapSslSockFact = CustomLdapSslSocketFactory.getSingletonInstance();
        }
        catch(Exception e)
        {
            throw new RuntimeException("Failed create CustomSslSocketFactory. Exception: " + e.getClass().getSimpleName() + ". Reason: " + e.getMessage(), e);
        }

        return custLdapSslSockFact;
    }

    private SSLSocketFactory loadTrustStoreProgrammatically() throws KeyStoreException, FileNotFoundException, IOException, NoSuchAlgorithmException, KeyManagementException, CertificateException
    {
        String trustStoreType = System.getProperty("custom.ldap.truststore.type");
        String trustStoreLoc = System.getProperty("custom.ldap.truststore.loc");
        char[] trustStorePasswordCharArr = System.getProperty("custom.ldap.truststore.password").toCharArray();
        String sslContextProtocol = System.getProperty("custom.ldap.ssl.protocol");

        KeyStore trustStore = KeyStore.getInstance(trustStoreType);

        try(BufferedInputStream bisTrustStore = new BufferedInputStream(new FileInputStream(trustStoreLoc)))
        {
            trustStore.load(bisTrustStore, trustStorePasswordCharArr);
        }

        TrustManagerFactory trustFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        trustFactory.init(trustStore

<details>
<summary>英文:</summary>

There is a way of setting a trust-store for just an explicit LDAP call and not as a global setting for all SSL activity. Setting up a truststore for just LDAP connections involves creating a custom SocketFactory. You can use this custom SocketFactory to read from a separate truststore or certificate file, independent of the global truststore referenced by `javax.net.ssl.keyStore*` properties. This JNDI tutorial explains how to do such a thing: &lt;a href=&quot;https://docs.oracle.com/javase/jndi/tutorial/ldap/security/ssl.html&quot;&gt;https://docs.oracle.com/javase/jndi/tutorial/ldap/security/ssl.html&lt;/a&gt;

Here&#39;s what you need to do:

 1. env.put(Context.PROVIDER_URL, &quot;ldap&lt;b&gt;s&lt;/b&gt;://some-ad.foo.bar&lt;b&gt;:636&lt;/b&gt;&quot;);
 2. env.put(Context.SECURITY_PROTOCOL, &quot;SSL&quot;);
 3. env.put(&quot;java.naming.ldap.factory.socket&quot;, &quot;&lt;i&gt;fully.qualified.name.of.your.custom.socket.factory.class&lt;/i&gt;&quot;);

Your custom socket factory class will then read from your custom truststore or certificate. To pass the location and password of your truststore to your custom socket factory class, you may make use of custom user-defined system properties.

Here&#39;s an example using your ldap query method and a sample custom socket factory. I&#39;ve added in additional code that will be needed to accomplish using a custom truststore:

private List<Map<String, Object>> queryImp(String filter) throws NamingException
{
Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY, initialContextFactoryClassName);
env.put(Context.PROVIDER_URL, url); //Must start with "ldaps" and have port "636"
env.put(Context.SECURITY_PRINCIPAL, user);
env.put(Context.SECURITY_CREDENTIALS, password);

//New settings that need to be added for LDAP SSL
env.put(Context.SECURITY_PROTOCOL, &quot;SSL&quot;);
env.put(&quot;java.naming.ldap.factory.socket&quot;, CustomLdapSslSocketFactory.class.getName()); //See: https://docs.oracle.com/javase/jndi/tutorial/ldap/security/ssl.html
/*These are custom user-defined properties. Name them whatever you like.
These will be referenced later in the custom socket factory class. */
System.setProperty(&quot;custom.ldap.truststore.type&quot;, &quot;pkcs12&quot;);
System.setProperty(&quot;custom.ldap.truststore.loc&quot;, &quot;C:/certs/MyCustomLdapTruststore.p12&quot;);
System.setProperty(&quot;custom.ldap.truststore.password&quot;, &quot;My custom ldap truststore password&quot;);
System.setProperty(&quot;custom.ldap.ssl.protocol&quot;, &quot;TLSv1.2&quot;);
DirContext context = new InitialDirContext(env);
SearchControls sc = new SearchControls();
sc.setReturningAttributes(attributeFilter);
sc.setSearchScope(SearchControls.SUBTREE_SCOPE);
NamingEnumeration results = context.search(base, filter, sc);
List&lt;Map&lt;String, Object&gt;&gt; result = toMaps(results);
context.close();
return result;

}


Here&#39;s the corresponding sample custom socket factory class. I&#39;ve made the class return a singleton each time the `public static SocketFactory getDefault()` method is invoked. That&#39;s why it&#39;s a bit more complex than would be needed if a new instance were to be returned each time.

import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;

import javax.net.SocketFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;

public class CustomLdapSslSocketFactory extends SSLSocketFactory
{
private SSLSocketFactory sslSocketFactory;

private static volatile CustomLdapSslSocketFactory singletonCustLdapSslSockFact;
private CustomLdapSslSocketFactory() throws KeyManagementException, KeyStoreException, FileNotFoundException, NoSuchAlgorithmException, CertificateException, IOException
{	
sslSocketFactory = loadTrustStoreProgrammatically();
}
private static CustomLdapSslSocketFactory getSingletonInstance() throws KeyManagementException, KeyStoreException, FileNotFoundException, NoSuchAlgorithmException, CertificateException, IOException
{
if(CustomLdapSslSocketFactory.singletonCustLdapSslSockFact == null)
{
synchronized(CustomLdapSslSocketFactory.class)
{
if(CustomLdapSslSocketFactory.singletonCustLdapSslSockFact == null)
{
CustomLdapSslSocketFactory.singletonCustLdapSslSockFact = new CustomLdapSslSocketFactory();
}
}
}
return CustomLdapSslSocketFactory.singletonCustLdapSslSockFact;
}
public static SocketFactory getDefault() //this method is called by Ldap implementations to create the custom SSL socket factory. See: https://docs.oracle.com/javase/jndi/tutorial/ldap/security/ssl.html 
{
/*
There are times when you need to have more control over the SSL sockets, or sockets in general, used by the LDAP service provider.
To set the socket factory implementation used by the LDAP service provider, set the &quot;java.naming.ldap.factory.socket&quot; property to the
fully qualified class name of the socket factory.
This class must extend the javax.net.SocketFactory abstract class and provide an implementation of the getDefault() method that
returns an instance of the custom socket factory.
See:
https://docs.oracle.com/javase/jndi/tutorial/ldap/security/ssl.html
*/
CustomLdapSslSocketFactory custLdapSslSockFact = null;
try
{
//custLdapSslSockFact = new CustomLdapSslSocketFactory(); //returns a new instance each time
custLdapSslSockFact = CustomLdapSslSocketFactory.getSingletonInstance(); //returns the same instance each time (singleton pattern)
}
catch(Exception e)
{
throw new RuntimeException(&quot;Failed create CustomSslSocketFactory. Exception: &quot; + e.getClass().getSimpleName() + &quot;. Reason: &quot; + e.getMessage(), e);
}
return custLdapSslSockFact;
}
private SSLSocketFactory loadTrustStoreProgrammatically() throws KeyStoreException, FileNotFoundException, IOException, NoSuchAlgorithmException, KeyManagementException, CertificateException
{
//Now, reference the custom user-defined system properties defined in your ldap query method above.
String trustStoreType = System.getProperty(&quot;custom.ldap.truststore.type&quot;);
String trustStoreLoc = System.getProperty(&quot;custom.ldap.truststore.loc&quot;);
char[] trustStorePasswordCharArr = System.getProperty(&quot;custom.ldap.truststore.password&quot;).toCharArray();
String sslContextProtocol = System.getProperty(&quot;custom.ldap.ssl.protocol&quot;);
KeyStore trustStore = KeyStore.getInstance(trustStoreType);
try(BufferedInputStream bisTrustStore = new BufferedInputStream(new FileInputStream(trustStoreLoc)))
{
trustStore.load(bisTrustStore, trustStorePasswordCharArr); // if your does not have a password specify null
}
// initialize a trust manager factory with the trusted store
TrustManagerFactory trustFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());    
trustFactory.init(trustStore);
// get the trust managers from the factory
TrustManager[] trustManagers = trustFactory.getTrustManagers();
// initialize an ssl context to use these managers
SSLContext sslContext = SSLContext.getInstance(sslContextProtocol); //.getInstance(&quot;SSL&quot;); or TLS, etc.
sslContext.init(null, trustManagers, null);
SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
return sslSocketFactory;
}
@Override
public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException
{
return sslSocketFactory.createSocket(s, host, port, autoClose);
}
@Override
public String[] getDefaultCipherSuites()
{
return sslSocketFactory.getDefaultCipherSuites();
}
@Override
public String[] getSupportedCipherSuites()
{
return sslSocketFactory.getSupportedCipherSuites();
}
@Override
public Socket createSocket(String host, int port) throws IOException, UnknownHostException
{
return sslSocketFactory.createSocket(host, port);
}
@Override
public Socket createSocket(InetAddress host, int port) throws IOException
{
return sslSocketFactory.createSocket(host, port);
}
@Override
public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException, UnknownHostException
{
return sslSocketFactory.createSocket(localHost, port, localHost, localPort);
}
@Override
public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException
{
return sslSocketFactory.createSocket(address, port, localAddress, localPort);
}

}


See the related stackoverflow post: &lt;a href=&quot;https://stackoverflow.com/questions/9865666/how-to-send-a-params-to-the-socketfactory-in-ldap&quot;&gt;https://stackoverflow.com/questions/9865666/how-to-send-a-params-to-the-socketfactory-in-ldap&lt;/a&gt; for an explanation on why I chose to use custom user-defined system properties instead of hard-coded values in the `CustomLdapSslSocketFactory` class above.
That&#39;s it. With just these changes, you&#39;ll be able make LDAP calls over SSL.
</details>

huangapple
  • 本文由 发表于 2020年8月28日 14:01:17
  • 转载请务必保留本文链接:https://go.coder-hub.com/63628287.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定