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