001/*
002 * The contents of this file are subject to the terms of the Common Development and
003 * Distribution License (the License). You may not use this file except in compliance with the
004 * License.
005 *
006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
007 * specific language governing permission and limitations under the License.
008 *
009 * When distributing Covered Software, include this CDDL Header Notice in each file and include
010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
011 * Header, with the fields enclosed by brackets [] replaced by your own identifying
012 * information: "Portions Copyright [year] [name of copyright owner]".
013 *
014 * Copyright 2015 ForgeRock AS.
015 */
016
017package org.forgerock.opendj.examples;
018
019import static org.forgerock.opendj.ldap.TrustManagers.checkHostName;
020import static org.forgerock.util.Utils.closeSilently;
021import static org.forgerock.opendj.ldap.LDAPConnectionFactory.SSL_USE_STARTTLS;
022import static org.forgerock.opendj.ldap.LDAPConnectionFactory.SSL_CONTEXT;
023
024import org.forgerock.opendj.ldap.Connection;
025import org.forgerock.opendj.ldap.LDAPConnectionFactory;
026import org.forgerock.opendj.ldap.LdapException;
027import org.forgerock.opendj.ldap.ResultCode;
028import org.forgerock.opendj.ldap.SSLContextBuilder;
029import org.forgerock.opendj.ldap.TrustManagers;
030import org.forgerock.opendj.ldap.requests.Requests;
031import org.forgerock.opendj.ldap.responses.BindResult;
032import org.forgerock.opendj.ldap.responses.Result;
033import org.forgerock.util.AsyncFunction;
034import org.forgerock.util.Options;
035import org.forgerock.util.promise.ExceptionHandler;
036import org.forgerock.util.promise.Promise;
037import org.forgerock.util.promise.ResultHandler;
038
039import javax.net.ssl.SSLContext;
040import javax.net.ssl.TrustManager;
041import java.io.File;
042import java.security.GeneralSecurityException;
043import java.util.concurrent.CountDownLatch;
044
045/**
046 * An example client application which performs simple authentication to a
047 * directory server using the asynchronous APIs.
048 * <br>
049 * This example takes the following command line parameters:
050 * <ul>
051 * <li>host - host name of the directory server</li>
052 * <li>port - port number of the directory server</li>
053 * <li>bind-dn - DN of the user to authenticate</li>
054 * <li>bind-password - Password of the user to authenticate</li>
055 * <li>use-starttls - (Optional) connect with StartTLS</li>
056 * <li>use-ssl - (Optional) connect over SSL</li>
057 * </ul>
058 * The host, port, bind-dn, and bind-password arguments are required.
059 * The use-starttls and use-ssl arguments are optional and mutually exclusive.
060 * <br>
061 * If the server certificate is self-signed,
062 * or otherwise not trusted out-of-the-box,
063 * then set the trust store by using the JSSE system property
064 * {@code -Djavax.net.ssl.trustStore=/path/to/opendj/config/keystore}
065 * and the trust store password if necessary by using the JSSE system property
066 * {@code -Djavax.net.ssl.trustStorePassword=`cat /path/to/opendj/config/keystore.pin`}.
067 */
068public final class SimpleAuthAsync {
069    /** Connection to the LDAP server. */
070    private static Connection connection;
071    /** Result for the modify operation. */
072    private static int resultCode;
073    /** Count down latch to wait for modify operation to complete. */
074    private static final CountDownLatch COMPLETION_LATCH = new CountDownLatch(1);
075
076    /**
077     * Authenticate to the directory either over LDAP, over LDAPS, or using
078     * StartTLS.
079     *
080     * @param args
081     *            The command line arguments
082     */
083    public static void main(final String[] args) {
084        parseArgs(args);
085
086        // Connect and bind.
087        // Pass getTrustAllOptions() instead of getTrustOptions()
088        // to the connection factory constructor
089        // if you want to trust all certificates blindly.
090        new LDAPConnectionFactory(host, port, getTrustOptions(host, keystore, storepass))
091                .getConnectionAsync()
092                .thenAsync(new AsyncFunction<Connection, BindResult, LdapException>() {
093                    @Override
094                    public Promise<BindResult, LdapException> apply(Connection connection)
095                            throws LdapException {
096                        SimpleAuthAsync.connection = connection;
097                        return connection.bindAsync(
098                                Requests.newSimpleBindRequest(bindDN, bindPassword.toCharArray()));
099                    }
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}