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