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