001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 018package org.apache.commons.net.imap; 019 020import java.io.BufferedWriter; 021import java.io.InputStreamReader; 022import java.io.IOException; 023import java.io.OutputStreamWriter; 024 025import javax.net.ssl.HostnameVerifier; 026import javax.net.ssl.KeyManager; 027import javax.net.ssl.SSLContext; 028import javax.net.ssl.SSLException; 029import javax.net.ssl.SSLHandshakeException; 030import javax.net.ssl.SSLSocket; 031import javax.net.ssl.SSLSocketFactory; 032import javax.net.ssl.TrustManager; 033 034import org.apache.commons.net.io.CRLFLineReader; 035import org.apache.commons.net.util.SSLContextUtils; 036import org.apache.commons.net.util.SSLSocketUtils; 037 038/** 039 * The IMAPSClient class provides SSL/TLS connection encryption to IMAPClient. 040 * Copied from 041 * <a href="http://commons.apache.org/proper/commons-net/apidocs/index.html?org/apache/commons/net/ftp/FTPSClient.html"> 042 * FTPSClient</a> and modified to suit IMAP. 043 * If implicit mode is selected (NOT the default), SSL/TLS negotiation starts right 044 * after the connection has been established. In explicit mode (the default), SSL/TLS 045 * negotiation starts when the user calls execTLS() and the server accepts the command. 046 * 047 * <pre> 048 * {@code 049 * //Implicit usage: 050 * 051 * IMAPSClient c = new IMAPSClient(true); 052 * c.connect("127.0.0.1", 993); 053 * 054 * //Explicit usage: 055 * 056 * IMAPSClient c = new IMAPSClient(); 057 * c.connect("127.0.0.1", 143); 058 * if (c.execTLS()) { /rest of the commands here/ } 059 * } 060 * </pre> 061 * <b>Warning</b>: the hostname is not verified against the certificate by default, use 062 * {@link #setHostnameVerifier(HostnameVerifier)} or {@link #setEndpointCheckingEnabled(boolean)} 063 * (on Java 1.7+) to enable verification. 064 */ 065public class IMAPSClient extends IMAPClient 066{ 067 /** The default IMAP over SSL port. */ 068 public static final int DEFAULT_IMAPS_PORT = 993; 069 070 /** Default secure socket protocol name. */ 071 public static final String DEFAULT_PROTOCOL = "TLS"; 072 073 /** The security mode. True - Implicit Mode / False - Explicit Mode. */ 074 private final boolean isImplicit; 075 /** The secure socket protocol to be used, like SSL/TLS. */ 076 private final String protocol; 077 /** The context object. */ 078 private SSLContext context = null; 079 /** The cipher suites. SSLSockets have a default set of these anyway, 080 so no initialization required. */ 081 private String[] suites = null; 082 /** The protocol versions. */ 083 private String[] protocols = //null; 084 null;//{"SSLv2", "SSLv3", "TLSv1", "TLSv1.1", "SSLv2Hello"}; 085 086 /** The IMAPS {@link TrustManager} implementation, default null. */ 087 private TrustManager trustManager = null; 088 089 /** The {@link KeyManager}, default null. */ 090 private KeyManager keyManager = null; 091 092 /** The {@link HostnameVerifier} to use post-TLS, default null (i.e. no verification). */ 093 private HostnameVerifier hostnameVerifier = null; 094 095 /** Use Java 1.7+ HTTPS Endpoint Identification Algorithim. */ 096 private boolean tlsEndpointChecking; 097 098 /** 099 * Constructor for IMAPSClient. 100 * Sets security mode to explicit (isImplicit = false). 101 */ 102 public IMAPSClient() 103 { 104 this(DEFAULT_PROTOCOL, false); 105 } 106 107 /** 108 * Constructor for IMAPSClient. 109 * @param implicit The security mode (Implicit/Explicit). 110 */ 111 public IMAPSClient(final boolean implicit) 112 { 113 this(DEFAULT_PROTOCOL, implicit); 114 } 115 116 /** 117 * Constructor for IMAPSClient. 118 * @param proto the protocol. 119 */ 120 public IMAPSClient(final String proto) 121 { 122 this(proto, false); 123 } 124 125 /** 126 * Constructor for IMAPSClient. 127 * @param proto the protocol. 128 * @param implicit The security mode(Implicit/Explicit). 129 */ 130 public IMAPSClient(final String proto, final boolean implicit) 131 { 132 this(proto, implicit, null); 133 } 134 135 /** 136 * Constructor for IMAPSClient. 137 * @param proto the protocol. 138 * @param implicit The security mode(Implicit/Explicit). 139 * @param ctx the SSL context 140 */ 141 public IMAPSClient(final String proto, final boolean implicit, final SSLContext ctx) 142 { 143 super(); 144 setDefaultPort(DEFAULT_IMAPS_PORT); 145 protocol = proto; 146 isImplicit = implicit; 147 context = ctx; 148 } 149 150 /** 151 * Constructor for IMAPSClient. 152 * @param implicit The security mode(Implicit/Explicit). 153 * @param ctx A pre-configured SSL Context. 154 */ 155 public IMAPSClient(final boolean implicit, final SSLContext ctx) 156 { 157 this(DEFAULT_PROTOCOL, implicit, ctx); 158 } 159 160 /** 161 * Constructor for IMAPSClient. 162 * @param context A pre-configured SSL Context. 163 */ 164 public IMAPSClient(final SSLContext context) 165 { 166 this(false, context); 167 } 168 169 /** 170 * Because there are so many connect() methods, 171 * the _connectAction_() method is provided as a means of performing 172 * some action immediately after establishing a connection, 173 * rather than reimplementing all of the connect() methods. 174 * @throws IOException If it is thrown by _connectAction_(). 175 * @see org.apache.commons.net.SocketClient#_connectAction_() 176 */ 177 @Override 178 protected void _connectAction_() throws IOException 179 { 180 // Implicit mode. 181 if (isImplicit) { 182 performSSLNegotiation(); 183 } 184 super._connectAction_(); 185 // Explicit mode - don't do anything. The user calls execTLS() 186 } 187 188 /** 189 * Performs a lazy init of the SSL context. 190 * @throws IOException When could not initialize the SSL context. 191 */ 192 private void initSSLContext() throws IOException 193 { 194 if (context == null) 195 { 196 context = SSLContextUtils.createSSLContext(protocol, getKeyManager(), getTrustManager()); 197 } 198 } 199 200 /** 201 * SSL/TLS negotiation. Acquires an SSL socket of a 202 * connection and carries out handshake processing. 203 * @throws IOException If server negotiation fails. 204 */ 205 private void performSSLNegotiation() throws IOException 206 { 207 initSSLContext(); 208 209 final SSLSocketFactory ssf = context.getSocketFactory(); 210 final String host = _hostname_ != null ? _hostname_ : getRemoteAddress().getHostAddress(); 211 final int port = getRemotePort(); 212 final SSLSocket socket = 213 (SSLSocket) ssf.createSocket(_socket_, host, port, true); 214 socket.setEnableSessionCreation(true); 215 socket.setUseClientMode(true); 216 217 if (tlsEndpointChecking) { 218 SSLSocketUtils.enableEndpointNameVerification(socket); 219 } 220 221 if (protocols != null) { 222 socket.setEnabledProtocols(protocols); 223 } 224 if (suites != null) { 225 socket.setEnabledCipherSuites(suites); 226 } 227 socket.startHandshake(); 228 229 // TODO the following setup appears to duplicate that in the super class methods 230 _socket_ = socket; 231 _input_ = socket.getInputStream(); 232 _output_ = socket.getOutputStream(); 233 _reader = 234 new CRLFLineReader(new InputStreamReader(_input_, 235 __DEFAULT_ENCODING)); 236 __writer = 237 new BufferedWriter(new OutputStreamWriter(_output_, 238 __DEFAULT_ENCODING)); 239 240 if (hostnameVerifier != null && !hostnameVerifier.verify(host, socket.getSession())) { 241 throw new SSLHandshakeException("Hostname doesn't match certificate"); 242 } 243 } 244 245 /** 246 * Get the {@link KeyManager} instance. 247 * @return The current {@link KeyManager} instance. 248 */ 249 private KeyManager getKeyManager() 250 { 251 return keyManager; 252 } 253 254 /** 255 * Set a {@link KeyManager} to use. 256 * @param newKeyManager The KeyManager implementation to set. 257 * @see org.apache.commons.net.util.KeyManagerUtils 258 */ 259 public void setKeyManager(final KeyManager newKeyManager) 260 { 261 keyManager = newKeyManager; 262 } 263 264 /** 265 * Controls which particular cipher suites are enabled for use on this 266 * connection. Called before server negotiation. 267 * @param cipherSuites The cipher suites. 268 */ 269 public void setEnabledCipherSuites(final String[] cipherSuites) 270 { 271 suites = new String[cipherSuites.length]; 272 System.arraycopy(cipherSuites, 0, suites, 0, cipherSuites.length); 273 } 274 275 /** 276 * Returns the names of the cipher suites which could be enabled 277 * for use on this connection. 278 * When the underlying {@link java.net.Socket Socket} is not an {@link SSLSocket} instance, returns null. 279 * @return An array of cipher suite names, or <code>null</code>. 280 */ 281 public String[] getEnabledCipherSuites() 282 { 283 if (_socket_ instanceof SSLSocket) 284 { 285 return ((SSLSocket)_socket_).getEnabledCipherSuites(); 286 } 287 return null; 288 } 289 290 /** 291 * Controls which particular protocol versions are enabled for use on this 292 * connection. I perform setting before a server negotiation. 293 * @param protocolVersions The protocol versions. 294 */ 295 public void setEnabledProtocols(final String[] protocolVersions) 296 { 297 protocols = new String[protocolVersions.length]; 298 System.arraycopy(protocolVersions, 0, protocols, 0, protocolVersions.length); 299 } 300 301 /** 302 * Returns the names of the protocol versions which are currently 303 * enabled for use on this connection. 304 * When the underlying {@link java.net.Socket Socket} is not an {@link SSLSocket} instance, returns null. 305 * @return An array of protocols, or <code>null</code>. 306 */ 307 public String[] getEnabledProtocols() 308 { 309 if (_socket_ instanceof SSLSocket) 310 { 311 return ((SSLSocket)_socket_).getEnabledProtocols(); 312 } 313 return null; 314 } 315 316 /** 317 * The TLS command execution. 318 * @throws SSLException If the server reply code is not positive. 319 * @throws IOException If an I/O error occurs while sending 320 * the command or performing the negotiation. 321 * @return TRUE if the command and negotiation succeeded. 322 */ 323 public boolean execTLS() throws SSLException, IOException 324 { 325 if (sendCommand(IMAPCommand.getCommand(IMAPCommand.STARTTLS)) != IMAPReply.OK) 326 { 327 return false; 328 //throw new SSLException(getReplyString()); 329 } 330 performSSLNegotiation(); 331 return true; 332 } 333 334 /** 335 * Get the currently configured {@link TrustManager}. 336 * @return A TrustManager instance. 337 */ 338 public TrustManager getTrustManager() 339 { 340 return trustManager; 341 } 342 343 /** 344 * Override the default {@link TrustManager} to use. 345 * @param newTrustManager The TrustManager implementation to set. 346 * @see org.apache.commons.net.util.TrustManagerUtils 347 */ 348 public void setTrustManager(final TrustManager newTrustManager) 349 { 350 trustManager = newTrustManager; 351 } 352 353 /** 354 * Get the currently configured {@link HostnameVerifier}. 355 * @return A HostnameVerifier instance. 356 * @since 3.4 357 */ 358 public HostnameVerifier getHostnameVerifier() 359 { 360 return hostnameVerifier; 361 } 362 363 /** 364 * Override the default {@link HostnameVerifier} to use. 365 * @param newHostnameVerifier The HostnameVerifier implementation to set or <code>null</code> to disable. 366 * @since 3.4 367 */ 368 public void setHostnameVerifier(final HostnameVerifier newHostnameVerifier) 369 { 370 hostnameVerifier = newHostnameVerifier; 371 } 372 373 /** 374 * Return whether or not endpoint identification using the HTTPS algorithm 375 * on Java 1.7+ is enabled. The default behavior is for this to be disabled. 376 * 377 * @return True if enabled, false if not. 378 * @since 3.4 379 */ 380 public boolean isEndpointCheckingEnabled() 381 { 382 return tlsEndpointChecking; 383 } 384 385 /** 386 * Automatic endpoint identification checking using the HTTPS algorithm 387 * is supported on Java 1.7+. The default behavior is for this to be disabled. 388 * 389 * @param enable Enable automatic endpoint identification checking using the HTTPS algorithm on Java 1.7+. 390 * @since 3.4 391 */ 392 public void setEndpointCheckingEnabled(final boolean enable) 393 { 394 tlsEndpointChecking = enable; 395 } 396} 397/* kate: indent-width 4; replace-tabs on; */