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