View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.net.imap;
19  
20  import java.io.BufferedWriter;
21  import java.io.IOException;
22  import java.io.InputStreamReader;
23  import java.io.OutputStreamWriter;
24  
25  import javax.net.ssl.HostnameVerifier;
26  import javax.net.ssl.KeyManager;
27  import javax.net.ssl.SSLContext;
28  import javax.net.ssl.SSLException;
29  import javax.net.ssl.SSLHandshakeException;
30  import javax.net.ssl.SSLSocket;
31  import javax.net.ssl.SSLSocketFactory;
32  import javax.net.ssl.TrustManager;
33  
34  import org.apache.commons.net.io.CRLFLineReader;
35  import org.apache.commons.net.util.SSLContextUtils;
36  import org.apache.commons.net.util.SSLSocketUtils;
37  
38  /**
39   * The IMAPSClient class provides SSL/TLS connection encryption to IMAPClient. Copied from
40   * <a href="http://commons.apache.org/proper/commons-net/apidocs/index.html?org/apache/commons/net/ftp/FTPSClient.html"> FTPSClient</a> and modified to suit
41   * IMAP. If implicit mode is selected (NOT the default), SSL/TLS negotiation starts right after the connection has been established. In explicit mode (the
42   * default), SSL/TLS negotiation starts when the user calls execTLS() and the server accepts the command.
43   *
44   * <pre>
45   * {@code
46   * //Implicit usage:
47   *
48   *               IMAPSClient c = new IMAPSClient(true);
49   *               c.connect("127.0.0.1", 993);
50   *
51   * //Explicit usage:
52   *
53   *               IMAPSClient c = new IMAPSClient();
54   *               c.connect("127.0.0.1", 143);
55   *               if (c.execTLS()) { /rest of the commands here/ }
56   * }
57   * </pre>
58   *
59   * <b>Warning</b>: the hostname is not verified against the certificate by default, use {@link #setHostnameVerifier(HostnameVerifier)} or
60   * {@link #setEndpointCheckingEnabled(boolean)} (on Java 1.7+) to enable verification.
61   */
62  public class IMAPSClient extends IMAPClient {
63      /** The default IMAP over SSL port. */
64      public static final int DEFAULT_IMAPS_PORT = 993;
65  
66      /** Default secure socket protocol name. */
67      public static final String DEFAULT_PROTOCOL = "TLS";
68  
69      /** The security mode. True - Implicit Mode / False - Explicit Mode. */
70      private final boolean isImplicit;
71      /** The secure socket protocol to be used, like SSL/TLS. */
72      private final String protocol;
73      /** The context object. */
74      private SSLContext context;
75      /**
76       * The cipher suites. SSLSockets have a default set of these anyway, so no initialization required.
77       */
78      private String[] suites;
79      /** The protocol versions. */
80      private String[] protocols // null;
81      ;// {"SSLv2", "SSLv3", "TLSv1", "TLSv1.1", "SSLv2Hello"};
82  
83      /** The IMAPS {@link TrustManager} implementation, default null. */
84      private TrustManager trustManager;
85  
86      /** The {@link KeyManager}, default null. */
87      private KeyManager keyManager;
88  
89      /** The {@link HostnameVerifier} to use post-TLS, default null (i.e. no verification). */
90      private HostnameVerifier hostnameVerifier;
91  
92      /** Use Java 1.7+ HTTPS Endpoint Identification Algorithm. */
93      private boolean tlsEndpointChecking;
94  
95      /**
96       * Constructor for IMAPSClient. Sets security mode to explicit (isImplicit = false).
97       */
98      public IMAPSClient() {
99          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</code>.
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</code>.
214      */
215     public String[] getEnabledProtocols() {
216         if (_socket_ instanceof SSLSocket) {
217             return ((SSLSocket) _socket_).getEnabledProtocols();
218         }
219         return null;
220     }
221 
222     /**
223      * Get 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      * Get the {@link KeyManager} instance.
234      *
235      * @return The current {@link KeyManager} instance.
236      */
237     private KeyManager getKeyManager() {
238         return keyManager;
239     }
240 
241     /**
242      * Get 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</code> to disable.
342      * @since 3.4
343      */
344     public void setHostnameVerifier(final HostnameVerifier newHostnameVerifier) {
345         hostnameVerifier = newHostnameVerifier;
346     }
347 
348     /**
349      * Set 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