001 /* 002 * Copyright 2007-2016 UnboundID Corp. 003 * All Rights Reserved. 004 */ 005 /* 006 * Copyright (C) 2008-2016 UnboundID Corp. 007 * 008 * This program is free software; you can redistribute it and/or modify 009 * it under the terms of the GNU General Public License (GPLv2 only) 010 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only) 011 * as published by the Free Software Foundation. 012 * 013 * This program is distributed in the hope that it will be useful, 014 * but WITHOUT ANY WARRANTY; without even the implied warranty of 015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 016 * GNU General Public License for more details. 017 * 018 * You should have received a copy of the GNU General Public License 019 * along with this program; if not, see <http://www.gnu.org/licenses>. 020 */ 021 package com.unboundid.ldap.sdk.extensions; 022 023 024 025 import javax.net.ssl.SSLContext; 026 import javax.net.ssl.SSLSocketFactory; 027 028 import com.unboundid.ldap.sdk.Control; 029 import com.unboundid.ldap.sdk.ExtendedRequest; 030 import com.unboundid.ldap.sdk.ExtendedResult; 031 import com.unboundid.ldap.sdk.InternalSDKHelper; 032 import com.unboundid.ldap.sdk.LDAPConnection; 033 import com.unboundid.ldap.sdk.LDAPException; 034 import com.unboundid.ldap.sdk.LDAPExtendedOperationException; 035 import com.unboundid.ldap.sdk.ResultCode; 036 import com.unboundid.util.ssl.SSLUtil; 037 038 import static com.unboundid.ldap.sdk.extensions.ExtOpMessages.*; 039 import static com.unboundid.util.Debug.*; 040 041 042 043 /** 044 * This class provides an implementation of the LDAP StartTLS extended request 045 * as defined in <A HREF="http://www.ietf.org/rfc/rfc4511.txt">RFC 4511</A> 046 * section 4.14. It may be used to establish a secure communication channel 047 * over an otherwise unencrypted connection. 048 * <BR><BR> 049 * Note that when using the StartTLS extended operation, you should establish 050 * a connection to the server's unencrypted LDAP port rather than its secure 051 * port. Then, you can use the StartTLS extended request in order to secure 052 * that connection. 053 * <BR><BR> 054 * <H2>Example</H2> 055 * The following example attempts to use the StartTLS extended request in order 056 * to secure communication on a previously insecure connection. In this case, 057 * it will use the {@code com.unboundid.util.ssl.SSLUtil} class in conjunction 058 * with the {@code com.unboundid.util.ssl.TrustStoreTrustManager} class to 059 * ensure that only certificates from trusted authorities will be accepted. 060 * <PRE> 061 * // Create an SSLContext that will be used to perform the cryptographic 062 * // processing. 063 * SSLUtil sslUtil = new SSLUtil(new TrustStoreTrustManager(trustStorePath)); 064 * SSLContext sslContext = sslUtil.createSSLContext(); 065 * 066 * // Create and process the extended request to secure a connection. 067 * StartTLSExtendedRequest startTLSRequest = 068 * new StartTLSExtendedRequest(sslContext); 069 * ExtendedResult startTLSResult; 070 * try 071 * { 072 * startTLSResult = connection.processExtendedOperation(startTLSRequest); 073 * // This doesn't necessarily mean that the operation was successful, since 074 * // some kinds of extended operations return non-success results under 075 * // normal conditions. 076 * } 077 * catch (LDAPException le) 078 * { 079 * // For an extended operation, this generally means that a problem was 080 * // encountered while trying to send the request or read the result. 081 * startTLSResult = new ExtendedResult(le); 082 * } 083 * 084 * // Make sure that we can use the connection to interact with the server. 085 * RootDSE rootDSE = connection.getRootDSE(); 086 * </PRE> 087 */ 088 public final class StartTLSExtendedRequest 089 extends ExtendedRequest 090 { 091 /** 092 * The OID (1.3.6.1.4.1.1466.20037) for the StartTLS extended request. 093 */ 094 public static final String STARTTLS_REQUEST_OID = "1.3.6.1.4.1.1466.20037"; 095 096 097 098 /** 099 * The serial version UID for this serializable class. 100 */ 101 private static final long serialVersionUID = -3234194603452821233L; 102 103 104 105 // The SSL socket factory used to perform the negotiation. 106 private final SSLSocketFactory sslSocketFactory; 107 108 109 110 /** 111 * Creates a new StartTLS extended request using a default SSL context. 112 * 113 * @throws LDAPException If a problem occurs while trying to initialize a 114 * default SSL context. 115 */ 116 public StartTLSExtendedRequest() 117 throws LDAPException 118 { 119 this((SSLSocketFactory) null, null); 120 } 121 122 123 124 /** 125 * Creates a new StartTLS extended request using a default SSL context. 126 * 127 * @param controls The set of controls to include in the request. 128 * 129 * @throws LDAPException If a problem occurs while trying to initialize a 130 * default SSL context. 131 */ 132 public StartTLSExtendedRequest(final Control[] controls) 133 throws LDAPException 134 { 135 this((SSLSocketFactory) null, controls); 136 } 137 138 139 140 /** 141 * Creates a new StartTLS extended request using the provided SSL context. 142 * 143 * @param sslContext The SSL context to use to perform the negotiation. It 144 * may be {@code null} to indicate that a default SSL 145 * context should be used. If an SSL context is provided, 146 * then it must already be initialized. 147 * 148 * @throws LDAPException If a problem occurs while trying to initialize a 149 * default SSL context. 150 */ 151 public StartTLSExtendedRequest(final SSLContext sslContext) 152 throws LDAPException 153 { 154 this(sslContext, null); 155 } 156 157 158 159 /** 160 * Creates a new StartTLS extended request using the provided SSL socket 161 * factory. 162 * 163 * @param sslSocketFactory The SSL socket factory to use to convert an 164 * insecure connection into a secure connection. It 165 * may be {@code null} to indicate that a default 166 * SSL socket factory should be used. 167 * 168 * @throws LDAPException If a problem occurs while trying to initialize a 169 * default SSL socket factory. 170 */ 171 public StartTLSExtendedRequest(final SSLSocketFactory sslSocketFactory) 172 throws LDAPException 173 { 174 this(sslSocketFactory, null); 175 } 176 177 178 179 /** 180 * Creates a new StartTLS extended request. 181 * 182 * @param sslContext The SSL context to use to perform the negotiation. It 183 * may be {@code null} to indicate that a default SSL 184 * context should be used. If an SSL context is provided, 185 * then it must already be initialized. 186 * @param controls The set of controls to include in the request. 187 * 188 * @throws LDAPException If a problem occurs while trying to initialize a 189 * default SSL context. 190 */ 191 public StartTLSExtendedRequest(final SSLContext sslContext, 192 final Control[] controls) 193 throws LDAPException 194 { 195 super(STARTTLS_REQUEST_OID, controls); 196 197 if (sslContext == null) 198 { 199 try 200 { 201 final SSLContext ctx = 202 SSLContext.getInstance(SSLUtil.getDefaultSSLProtocol()); 203 ctx.init(null, null, null); 204 sslSocketFactory = ctx.getSocketFactory(); 205 } 206 catch (Exception e) 207 { 208 debugException(e); 209 throw new LDAPException(ResultCode.LOCAL_ERROR, 210 ERR_STARTTLS_REQUEST_CANNOT_CREATE_DEFAULT_CONTEXT.get(e), e); 211 } 212 } 213 else 214 { 215 sslSocketFactory = sslContext.getSocketFactory(); 216 } 217 } 218 219 220 221 /** 222 * Creates a new StartTLS extended request. 223 * 224 * @param sslSocketFactory The SSL socket factory to use to convert an 225 * insecure connection into a secure connection. It 226 * may be {@code null} to indicate that a default 227 * SSL socket factory should be used. 228 * @param controls The set of controls to include in the request. 229 * 230 * @throws LDAPException If a problem occurs while trying to initialize a 231 * default SSL context. 232 */ 233 public StartTLSExtendedRequest(final SSLSocketFactory sslSocketFactory, 234 final Control[] controls) 235 throws LDAPException 236 { 237 super(STARTTLS_REQUEST_OID, controls); 238 239 if (sslSocketFactory == null) 240 { 241 try 242 { 243 final SSLContext ctx = 244 SSLContext.getInstance(SSLUtil.getDefaultSSLProtocol()); 245 ctx.init(null, null, null); 246 this.sslSocketFactory = ctx.getSocketFactory(); 247 } 248 catch (Exception e) 249 { 250 debugException(e); 251 throw new LDAPException(ResultCode.LOCAL_ERROR, 252 ERR_STARTTLS_REQUEST_CANNOT_CREATE_DEFAULT_CONTEXT.get(e), e); 253 } 254 } 255 else 256 { 257 this.sslSocketFactory = sslSocketFactory; 258 } 259 } 260 261 262 263 /** 264 * Creates a new StartTLS extended request from the provided generic extended 265 * request. 266 * 267 * @param extendedRequest The generic extended request to use to create this 268 * StartTLS extended request. 269 * 270 * @throws LDAPException If a problem occurs while decoding the request. 271 */ 272 public StartTLSExtendedRequest(final ExtendedRequest extendedRequest) 273 throws LDAPException 274 { 275 this(extendedRequest.getControls()); 276 277 if (extendedRequest.hasValue()) 278 { 279 throw new LDAPException(ResultCode.DECODING_ERROR, 280 ERR_STARTTLS_REQUEST_HAS_VALUE.get()); 281 } 282 } 283 284 285 286 /** 287 * Sends this StartTLS request to the server and performs the necessary 288 * client-side security processing if the operation is processed successfully. 289 * That this method is guaranteed to throw an {@code LDAPException} if the 290 * server returns a non-success result. 291 * 292 * @param connection The connection to use to communicate with the directory 293 * server. 294 * @param depth The current referral depth for this request. It should 295 * always be zero for the initial request, and should only 296 * be incremented when following referrals. 297 * 298 * @return The extended result received from the server if StartTLS processing 299 * was completed successfully. 300 * 301 * @throws LDAPException If the server returned a non-success result, or if 302 * a problem was encountered while performing 303 * client-side security processing. 304 */ 305 @Override() 306 public ExtendedResult process(final LDAPConnection connection, 307 final int depth) 308 throws LDAPException 309 { 310 // Set an SO_TIMEOUT on the connection if it's not operating in synchronous 311 // mode to make it more responsive during the negotiation phase. 312 InternalSDKHelper.setSoTimeout(connection, 50); 313 314 final ExtendedResult result = super.process(connection, depth); 315 if (result.getResultCode() == ResultCode.SUCCESS) 316 { 317 InternalSDKHelper.convertToTLS(connection, sslSocketFactory); 318 } 319 else 320 { 321 throw new LDAPExtendedOperationException(result); 322 } 323 324 return result; 325 } 326 327 328 329 /** 330 * {@inheritDoc} 331 */ 332 @Override() 333 public StartTLSExtendedRequest duplicate() 334 { 335 return duplicate(getControls()); 336 } 337 338 339 340 /** 341 * {@inheritDoc} 342 */ 343 @Override() 344 public StartTLSExtendedRequest duplicate(final Control[] controls) 345 { 346 try 347 { 348 final StartTLSExtendedRequest r = 349 new StartTLSExtendedRequest(sslSocketFactory, controls); 350 r.setResponseTimeoutMillis(getResponseTimeoutMillis(null)); 351 return r; 352 } 353 catch (Exception e) 354 { 355 // This should never happen, since an exception should only be thrown if 356 // there is no SSL context, but this instance already has a context. 357 debugException(e); 358 return null; 359 } 360 } 361 362 363 364 /** 365 * {@inheritDoc} 366 */ 367 @Override() 368 public String getExtendedRequestName() 369 { 370 return INFO_EXTENDED_REQUEST_NAME_START_TLS.get(); 371 } 372 373 374 375 /** 376 * {@inheritDoc} 377 */ 378 @Override() 379 public void toString(final StringBuilder buffer) 380 { 381 buffer.append("StartTLSExtendedRequest("); 382 383 final Control[] controls = getControls(); 384 if (controls.length > 0) 385 { 386 buffer.append("controls={"); 387 for (int i=0; i < controls.length; i++) 388 { 389 if (i > 0) 390 { 391 buffer.append(", "); 392 } 393 394 buffer.append(controls[i]); 395 } 396 buffer.append('}'); 397 } 398 399 buffer.append(')'); 400 } 401 }