001/*
002 * CDDL HEADER START
003 *
004 * The contents of this file are subject to the terms of the
005 * Common Development and Distribution License, Version 1.0 only
006 * (the "License").  You may not use this file except in compliance
007 * with the License.
008 *
009 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
010 * or http://forgerock.org/license/CDDLv1.0.html.
011 * See the License for the specific language governing permissions
012 * and limitations under the License.
013 *
014 * When distributing Covered Code, include this CDDL HEADER in each
015 * file and include the License file at legal-notices/CDDLv1_0.txt.
016 * If applicable, add the following below this CDDL HEADER, with the
017 * fields enclosed by brackets "[]" replaced with your own identifying
018 * information:
019 *      Portions Copyright [yyyy] [name of copyright owner]
020 *
021 * CDDL HEADER END
022 *
023 *
024 *      Copyright 2015 ForgeRock AS.
025 */
026
027package org.forgerock.opendj.examples;
028
029import static org.forgerock.opendj.ldap.TrustManagers.checkHostName;
030import static org.forgerock.util.Utils.closeSilently;
031import static org.forgerock.opendj.ldap.LDAPConnectionFactory.SSL_USE_STARTTLS;
032import static org.forgerock.opendj.ldap.LDAPConnectionFactory.SSL_CONTEXT;
033
034import org.forgerock.opendj.ldap.Connection;
035import org.forgerock.opendj.ldap.LDAPConnectionFactory;
036import org.forgerock.opendj.ldap.LdapException;
037import org.forgerock.opendj.ldap.ResultCode;
038import org.forgerock.opendj.ldap.SSLContextBuilder;
039import org.forgerock.opendj.ldap.TrustManagers;
040import org.forgerock.opendj.ldap.requests.Requests;
041import org.forgerock.opendj.ldap.responses.BindResult;
042import org.forgerock.opendj.ldap.responses.Result;
043import org.forgerock.util.AsyncFunction;
044import org.forgerock.util.Options;
045import org.forgerock.util.promise.ExceptionHandler;
046import org.forgerock.util.promise.Promise;
047import org.forgerock.util.promise.ResultHandler;
048
049import javax.net.ssl.SSLContext;
050import javax.net.ssl.TrustManager;
051import java.io.File;
052import java.security.GeneralSecurityException;
053import java.util.concurrent.CountDownLatch;
054
055/**
056 * An example client application which performs simple authentication to a
057 * directory server using the asynchronous APIs.
058 * <br>
059 * This example takes the following command line parameters:
060 * <ul>
061 * <li>host - host name of the directory server</li>
062 * <li>port - port number of the directory server</li>
063 * <li>bind-dn - DN of the user to authenticate</li>
064 * <li>bind-password - Password of the user to authenticate</li>
065 * <li>use-starttls - (Optional) connect with StartTLS</li>
066 * <li>use-ssl - (Optional) connect over SSL</li>
067 * </ul>
068 * The host, port, bind-dn, and bind-password arguments are required.
069 * The use-starttls and use-ssl arguments are optional and mutually exclusive.
070 * <br>
071 * If the server certificate is self-signed,
072 * or otherwise not trusted out-of-the-box,
073 * then set the trust store by using the JSSE system property
074 * {@code -Djavax.net.ssl.trustStore=/path/to/opendj/config/keystore}
075 * and the trust store password if necessary by using the JSSE system property
076 * {@code -Djavax.net.ssl.trustStorePassword=`cat /path/to/opendj/config/keystore.pin`}.
077 */
078public final class SimpleAuthAsync {
079    /** Connection to the LDAP server. */
080    private static Connection connection;
081    /** Result for the modify operation. */
082    private static int resultCode;
083    /** Count down latch to wait for modify operation to complete. */
084    private static final CountDownLatch COMPLETION_LATCH = new CountDownLatch(1);
085
086    /**
087     * Authenticate to the directory either over LDAP, over LDAPS, or using
088     * StartTLS.
089     *
090     * @param args
091     *            The command line arguments
092     */
093    public static void main(final String[] args) {
094        parseArgs(args);
095
096        // Connect and bind.
097        // Pass getTrustAllOptions() instead of getTrustOptions()
098        // to the connection factory constructor
099        // if you want to trust all certificates blindly.
100        new LDAPConnectionFactory(host, port, getTrustOptions(host, keystore, storepass))
101                .getConnectionAsync()
102                .thenAsync(new AsyncFunction<Connection, BindResult, LdapException>() {
103                    @Override
104                    public Promise<BindResult, LdapException> apply(Connection connection)
105                            throws LdapException {
106                        SimpleAuthAsync.connection = connection;
107                        return connection.bindAsync(
108                                Requests.newSimpleBindRequest(bindDN, bindPassword.toCharArray()));
109                    }
110                })
111                .thenOnResult(new ResultHandler<Result>() {
112                    @Override
113                    public void handleResult(Result result) {
114                        resultCode = result.getResultCode().intValue();
115                        System.out.println("Authenticated as " + bindDN + ".");
116                        COMPLETION_LATCH.countDown();
117                    }
118                })
119                .thenOnException(new ExceptionHandler<LdapException>() {
120                    @Override
121                    public void handleException(LdapException e) {
122                        System.err.println(e.getMessage());
123                        resultCode = e.getResult().getResultCode().intValue();
124                        COMPLETION_LATCH.countDown();
125                    }
126                });
127
128        try {
129            COMPLETION_LATCH.await();
130        }  catch (InterruptedException e) {
131            System.err.println(e.getMessage());
132            System.exit(ResultCode.CLIENT_SIDE_USER_CANCELLED.intValue());
133            return;
134        }
135
136        closeSilently(connection);
137        System.exit(resultCode);
138    }
139
140    /**
141     * For StartTLS and SSL the connection factory needs SSL context options.
142     * In the general case, a trust manager in the SSL context serves
143     * to check server certificates, and a key manager handles client keys
144     * when the server checks certificates from our client.
145     * <br>
146     * This sample checks the server certificate,
147     * verifying that the certificate is currently valid,
148     * and that the host name of the server matches that of the certificate,
149     * based on a Java Key Store-format trust store.
150     * This sample does not present a client certificate.
151     *
152     * @param hostname      Host name expected in the server certificate
153     * @param truststore    Path to trust store file for the trust manager
154     * @param storepass     Password for the trust store
155     * @return SSL context options if SSL or StartTLS is used.
156     */
157    private static Options getTrustOptions(final String hostname,
158                                           final String truststore,
159                                           final String storepass) {
160        Options options = Options.defaultOptions();
161        if (useSSL || useStartTLS) {
162            try {
163                TrustManager trustManager = TrustManagers.checkValidityDates(
164                        checkHostName(hostname,
165                                TrustManagers.checkUsingTrustStore(
166                                        truststore, storepass.toCharArray(), null)));
167                if (trustManager != null) {
168                    SSLContext sslContext = new SSLContextBuilder()
169                            .setTrustManager(trustManager).getSSLContext();
170                    options.set(SSL_CONTEXT, sslContext);
171                }
172                options.set(SSL_USE_STARTTLS, useStartTLS);
173            } catch (Exception e) {
174                System.err.println(e.getMessage());
175                System.exit(ResultCode.CLIENT_SIDE_CONNECT_ERROR.intValue());
176                return null;
177            }
178        }
179        return options;
180    }
181
182    /**
183     * For StartTLS and SSL the connection factory needs SSL context options. In
184     * the general case, a trust manager in the SSL context serves to check
185     * server certificates, and a key manager handles client keys when the
186     * server checks certificates from our client.
187     * <br>
188     * OpenDJ directory server lets you install by default with a self-signed
189     * certificate that is not in the system trust store. To simplify this
190     * implementation trusts all server certificates.
191     *
192     * @return SSL context options to trust all certificates without checking.
193     */
194    private static Options getTrustAllOptions() {
195        try {
196            Options options = Options.defaultOptions();
197            SSLContext sslContext =
198                    new SSLContextBuilder().setTrustManager(TrustManagers.trustAll())
199                            .getSSLContext();
200            options.set(SSL_CONTEXT, sslContext);
201            options.set(SSL_USE_STARTTLS, useStartTLS);
202            return options;
203        } catch (GeneralSecurityException e) {
204            System.err.println(e.getMessage());
205            System.exit(ResultCode.CLIENT_SIDE_CONNECT_ERROR.intValue());
206            return null;
207        }
208    }
209
210    private static String  host;
211    private static int     port;
212    private static String  bindDN;
213    private static String  bindPassword;
214    private static boolean useStartTLS;
215    private static boolean useSSL;
216    private static String  keystore;
217    private static String  storepass;
218
219    /**
220     * Parse command line arguments.
221     *
222     * @param args
223     *            host port bind-dn bind-password [ use-starttls | use-ssl ]
224     */
225    private static void parseArgs(String[] args) {
226        if (args.length < 4 || args.length > 5) {
227            giveUp();
228        }
229
230        host = args[0];
231        port = Integer.parseInt(args[1]);
232        bindDN = args[2];
233        bindPassword = args[3];
234
235        if (args.length == 5) {
236            if ("use-starttls".equals(args[4].toLowerCase())) {
237                useStartTLS = true;
238                useSSL = false;
239            } else if ("use-ssl".equals(args[4].toLowerCase())) {
240                useStartTLS = false;
241                useSSL = true;
242            } else {
243                giveUp();
244            }
245        }
246
247        keystore = System.getProperty("javax.net.ssl.trustStore");
248        storepass = System.getProperty("javax.net.ssl.trustStorePassword");
249        if (keystore == null) { // Try to use Java's cacerts trust store.
250            keystore = System.getProperty("java.home") + File.separator
251                    + "lib" + File.separator
252                    + "security" + File.separator
253                    + "cacerts";
254            storepass = "changeit"; // Default password
255        }
256    }
257
258    private static void giveUp() {
259        printUsage();
260        System.exit(1);
261    }
262
263    private static void printUsage() {
264        System.err.println("Usage: host port bind-dn bind-password [ use-starttls | use-ssl ]");
265        System.err.println("\thost, port, bind-dn, and bind-password arguments are required.");
266        System.err.println("\tuse-starttls and use-ssl are optional and mutually exclusive.");
267        System.err.println("\tOptionally set javax.net.ssl.trustStore and javax.net.ssl.trustStorePassword.");
268    }
269
270    private SimpleAuthAsync() {
271        // Not used.
272    }
273}