View Javadoc
1   /*
2    * The contents of this file are subject to the terms of the Common Development and
3    * Distribution License (the License). You may not use this file except in compliance with the
4    * License.
5    *
6    * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
7    * specific language governing permission and limitations under the License.
8    *
9    * When distributing Covered Software, include this CDDL Header Notice in each file and include
10   * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
11   * Header, with the fields enclosed by brackets [] replaced by your own identifying
12   * information: "Portions Copyright [year] [name of copyright owner]".
13   *
14   * Copyright 2015 ForgeRock AS.
15   */
16  
17  package org.forgerock.opendj.examples;
18  
19  import static org.forgerock.opendj.ldap.TrustManagers.checkHostName;
20  import static org.forgerock.util.Utils.closeSilently;
21  import static org.forgerock.opendj.ldap.LDAPConnectionFactory.SSL_USE_STARTTLS;
22  import static org.forgerock.opendj.ldap.LDAPConnectionFactory.SSL_CONTEXT;
23  
24  import org.forgerock.opendj.ldap.Connection;
25  import org.forgerock.opendj.ldap.LDAPConnectionFactory;
26  import org.forgerock.opendj.ldap.LdapException;
27  import org.forgerock.opendj.ldap.ResultCode;
28  import org.forgerock.opendj.ldap.SSLContextBuilder;
29  import org.forgerock.opendj.ldap.TrustManagers;
30  import org.forgerock.opendj.ldap.requests.Requests;
31  import org.forgerock.opendj.ldap.responses.BindResult;
32  import org.forgerock.opendj.ldap.responses.Result;
33  import org.forgerock.util.AsyncFunction;
34  import org.forgerock.util.Options;
35  import org.forgerock.util.promise.ExceptionHandler;
36  import org.forgerock.util.promise.Promise;
37  import org.forgerock.util.promise.ResultHandler;
38  
39  import javax.net.ssl.SSLContext;
40  import javax.net.ssl.TrustManager;
41  import java.io.File;
42  import java.security.GeneralSecurityException;
43  import java.util.concurrent.CountDownLatch;
44  
45  /**
46   * An example client application which performs simple authentication to a
47   * directory server using the asynchronous APIs.
48   * <br>
49   * This example takes the following command line parameters:
50   * <ul>
51   * <li>host - host name of the directory server</li>
52   * <li>port - port number of the directory server</li>
53   * <li>bind-dn - DN of the user to authenticate</li>
54   * <li>bind-password - Password of the user to authenticate</li>
55   * <li>use-starttls - (Optional) connect with StartTLS</li>
56   * <li>use-ssl - (Optional) connect over SSL</li>
57   * </ul>
58   * The host, port, bind-dn, and bind-password arguments are required.
59   * The use-starttls and use-ssl arguments are optional and mutually exclusive.
60   * <br>
61   * If the server certificate is self-signed,
62   * or otherwise not trusted out-of-the-box,
63   * then set the trust store by using the JSSE system property
64   * {@code -Djavax.net.ssl.trustStore=/path/to/opendj/config/keystore}
65   * and the trust store password if necessary by using the JSSE system property
66   * {@code -Djavax.net.ssl.trustStorePassword=`cat /path/to/opendj/config/keystore.pin`}.
67   */
68  public final class SimpleAuthAsync {
69      /** Connection to the LDAP server. */
70      private static Connection connection;
71      /** Result for the modify operation. */
72      private static int resultCode;
73      /** Count down latch to wait for modify operation to complete. */
74      private static final CountDownLatch COMPLETION_LATCH = new CountDownLatch(1);
75  
76      /**
77       * Authenticate to the directory either over LDAP, over LDAPS, or using
78       * StartTLS.
79       *
80       * @param args
81       *            The command line arguments
82       */
83      public static void main(final String[] args) {
84          parseArgs(args);
85  
86          // Connect and bind.
87          // Pass getTrustAllOptions() instead of getTrustOptions()
88          // to the connection factory constructor
89          // if you want to trust all certificates blindly.
90          new LDAPConnectionFactory(host, port, getTrustOptions(host, keystore, storepass))
91                  .getConnectionAsync()
92                  .thenAsync(new AsyncFunction<Connection, BindResult, LdapException>() {
93                      @Override
94                      public Promise<BindResult, LdapException> apply(Connection connection)
95                              throws LdapException {
96                          SimpleAuthAsync.connection = connection;
97                          return connection.bindAsync(
98                                  Requests.newSimpleBindRequest(bindDN, bindPassword.toCharArray()));
99                      }
100                 })
101                 .thenOnResult(new ResultHandler<Result>() {
102                     @Override
103                     public void handleResult(Result result) {
104                         resultCode = result.getResultCode().intValue();
105                         System.out.println("Authenticated as " + bindDN + ".");
106                         COMPLETION_LATCH.countDown();
107                     }
108                 })
109                 .thenOnException(new ExceptionHandler<LdapException>() {
110                     @Override
111                     public void handleException(LdapException e) {
112                         System.err.println(e.getMessage());
113                         resultCode = e.getResult().getResultCode().intValue();
114                         COMPLETION_LATCH.countDown();
115                     }
116                 });
117 
118         try {
119             COMPLETION_LATCH.await();
120         }  catch (InterruptedException e) {
121             System.err.println(e.getMessage());
122             System.exit(ResultCode.CLIENT_SIDE_USER_CANCELLED.intValue());
123             return;
124         }
125 
126         closeSilently(connection);
127         System.exit(resultCode);
128     }
129 
130     /**
131      * For StartTLS and SSL the connection factory needs SSL context options.
132      * In the general case, a trust manager in the SSL context serves
133      * to check server certificates, and a key manager handles client keys
134      * when the server checks certificates from our client.
135      * <br>
136      * This sample checks the server certificate,
137      * verifying that the certificate is currently valid,
138      * and that the host name of the server matches that of the certificate,
139      * based on a Java Key Store-format trust store.
140      * This sample does not present a client certificate.
141      *
142      * @param hostname      Host name expected in the server certificate
143      * @param truststore    Path to trust store file for the trust manager
144      * @param storepass     Password for the trust store
145      * @return SSL context options if SSL or StartTLS is used.
146      */
147     private static Options getTrustOptions(final String hostname,
148                                            final String truststore,
149                                            final String storepass) {
150         Options options = Options.defaultOptions();
151         if (useSSL || useStartTLS) {
152             try {
153                 TrustManager trustManager = TrustManagers.checkValidityDates(
154                         checkHostName(hostname,
155                                 TrustManagers.checkUsingTrustStore(
156                                         truststore, storepass.toCharArray(), null)));
157                 if (trustManager != null) {
158                     SSLContext sslContext = new SSLContextBuilder()
159                             .setTrustManager(trustManager).getSSLContext();
160                     options.set(SSL_CONTEXT, sslContext);
161                 }
162                 options.set(SSL_USE_STARTTLS, useStartTLS);
163             } catch (Exception e) {
164                 System.err.println(e.getMessage());
165                 System.exit(ResultCode.CLIENT_SIDE_CONNECT_ERROR.intValue());
166                 return null;
167             }
168         }
169         return options;
170     }
171 
172     /**
173      * For StartTLS and SSL the connection factory needs SSL context options. In
174      * the general case, a trust manager in the SSL context serves to check
175      * server certificates, and a key manager handles client keys when the
176      * server checks certificates from our client.
177      * <br>
178      * OpenDJ directory server lets you install by default with a self-signed
179      * certificate that is not in the system trust store. To simplify this
180      * implementation trusts all server certificates.
181      *
182      * @return SSL context options to trust all certificates without checking.
183      */
184     private static Options getTrustAllOptions() {
185         try {
186             Options options = Options.defaultOptions();
187             SSLContext sslContext =
188                     new SSLContextBuilder().setTrustManager(TrustManagers.trustAll())
189                             .getSSLContext();
190             options.set(SSL_CONTEXT, sslContext);
191             options.set(SSL_USE_STARTTLS, useStartTLS);
192             return options;
193         } catch (GeneralSecurityException e) {
194             System.err.println(e.getMessage());
195             System.exit(ResultCode.CLIENT_SIDE_CONNECT_ERROR.intValue());
196             return null;
197         }
198     }
199 
200     private static String  host;
201     private static int     port;
202     private static String  bindDN;
203     private static String  bindPassword;
204     private static boolean useStartTLS;
205     private static boolean useSSL;
206     private static String  keystore;
207     private static String  storepass;
208 
209     /**
210      * Parse command line arguments.
211      *
212      * @param args
213      *            host port bind-dn bind-password [ use-starttls | use-ssl ]
214      */
215     private static void parseArgs(String[] args) {
216         if (args.length < 4 || args.length > 5) {
217             giveUp();
218         }
219 
220         host = args[0];
221         port = Integer.parseInt(args[1]);
222         bindDN = args[2];
223         bindPassword = args[3];
224 
225         if (args.length == 5) {
226             if ("use-starttls".equals(args[4].toLowerCase())) {
227                 useStartTLS = true;
228                 useSSL = false;
229             } else if ("use-ssl".equals(args[4].toLowerCase())) {
230                 useStartTLS = false;
231                 useSSL = true;
232             } else {
233                 giveUp();
234             }
235         }
236 
237         keystore = System.getProperty("javax.net.ssl.trustStore");
238         storepass = System.getProperty("javax.net.ssl.trustStorePassword");
239         if (keystore == null) { // Try to use Java's cacerts trust store.
240             keystore = System.getProperty("java.home") + File.separator
241                     + "lib" + File.separator
242                     + "security" + File.separator
243                     + "cacerts";
244             storepass = "changeit"; // Default password
245         }
246     }
247 
248     private static void giveUp() {
249         printUsage();
250         System.exit(1);
251     }
252 
253     private static void printUsage() {
254         System.err.println("Usage: host port bind-dn bind-password [ use-starttls | use-ssl ]");
255         System.err.println("\thost, port, bind-dn, and bind-password arguments are required.");
256         System.err.println("\tuse-starttls and use-ssl are optional and mutually exclusive.");
257         System.err.println("\tOptionally set javax.net.ssl.trustStore and javax.net.ssl.trustStorePassword.");
258     }
259 
260     private SimpleAuthAsync() {
261         // Not used.
262     }
263 }