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}