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.InputStreamReader;
22  import java.io.IOException;
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.
40   *  Copied from FTPSClient.java and modified to suit IMAP.
41   * If implicit mode is selected (NOT the default), SSL/TLS negotiation starts right
42   * after the connection has been established. In explicit mode (the default), SSL/TLS
43   * negotiation starts when the user calls execTLS() and the server accepts the command.
44   * Implicit usage:
45   *               IMAPSClient c = new IMAPSClient(true);
46   *               c.connect("127.0.0.1", 993);
47   * Explicit usage:
48   *               IMAPSClient c = new IMAPSClient();
49   *               c.connect("127.0.0.1", 143);
50   *               if (c.execTLS()) { /rest of the commands here/ }
51   *
52   * Warning: the hostname is not verified against the certificate by default, use
53   * {@link #setHostnameVerifier(HostnameVerifier)} or {@link #setEndpointCheckingEnabled(boolean)}
54   * (on Java 1.7+) to enable verification.
55   */
56  public class IMAPSClient extends IMAPClient
57  {
58      /** The default IMAP over SSL port. */
59      public static final int DEFAULT_IMAPS_PORT = 993;
60  
61      /** Default secure socket protocol name. */
62      public static final String DEFAULT_PROTOCOL = "TLS";
63  
64      /** The security mode. True - Implicit Mode / False - Explicit Mode. */
65      private final boolean isImplicit;
66      /** The secure socket protocol to be used, like SSL/TLS. */
67      private final String protocol;
68      /** The context object. */
69      private SSLContext context = null;
70      /** The cipher suites. SSLSockets have a default set of these anyway,
71          so no initialization required. */
72      private String[] suites = null;
73      /** The protocol versions. */
74      private String[] protocols = //null;
75          null;//{"SSLv2", "SSLv3", "TLSv1", "TLSv1.1", "SSLv2Hello"};
76  
77      /** The IMAPS {@link TrustManager} implementation, default null. */
78      private TrustManager trustManager = null;
79  
80      /** The {@link KeyManager}, default null. */
81      private KeyManager keyManager = null;
82  
83      /** The {@link HostnameVerifier} to use post-TLS, default null (i.e. no verification). */
84      private HostnameVerifier hostnameVerifier = null;
85  
86      /** Use Java 1.7+ HTTPS Endpoint Identification Algorithim. */
87      private boolean tlsEndpointChecking;
88  
89      /**
90       * Constructor for IMAPSClient.
91       * Sets security mode to explicit (isImplicit = false).
92       */
93      public IMAPSClient()
94      {
95          this(DEFAULT_PROTOCOL, false);
96      }
97  
98      /**
99       * Constructor for IMAPSClient.
100      * @param implicit The security mode (Implicit/Explicit).
101      */
102     public IMAPSClient(boolean implicit)
103     {
104         this(DEFAULT_PROTOCOL, implicit);
105     }
106 
107     /**
108      * Constructor for IMAPSClient.
109      * @param proto the protocol.
110      */
111     public IMAPSClient(String proto)
112     {
113         this(proto, false);
114     }
115 
116     /**
117      * Constructor for IMAPSClient.
118      * @param proto the protocol.
119      * @param implicit The security mode(Implicit/Explicit).
120      */
121     public IMAPSClient(String proto, boolean implicit)
122     {
123         this(proto, implicit, null);
124     }
125 
126     /**
127      * Constructor for IMAPSClient.
128      * @param proto the protocol.
129      * @param implicit The security mode(Implicit/Explicit).
130      * @param ctx the SSL context
131      */
132     public IMAPSClient(String proto, boolean implicit, SSLContext ctx)
133     {
134         super();
135         setDefaultPort(DEFAULT_IMAPS_PORT);
136         protocol = proto;
137         isImplicit = implicit;
138         context = ctx;
139     }
140 
141     /**
142      * Constructor for IMAPSClient.
143      * @param implicit The security mode(Implicit/Explicit).
144      * @param ctx A pre-configured SSL Context.
145      */
146     public IMAPSClient(boolean implicit, SSLContext ctx)
147     {
148         this(DEFAULT_PROTOCOL, implicit, ctx);
149     }
150 
151     /**
152      * Constructor for IMAPSClient.
153      * @param context A pre-configured SSL Context.
154      */
155     public IMAPSClient(SSLContext context)
156     {
157         this(false, context);
158     }
159 
160     /**
161      * Because there are so many connect() methods,
162      * the _connectAction_() method is provided as a means of performing
163      * some action immediately after establishing a connection,
164      * rather than reimplementing all of the connect() methods.
165      * @throws IOException If it is thrown by _connectAction_().
166      * @see org.apache.commons.net.SocketClient#_connectAction_()
167      */
168     @Override
169     protected void _connectAction_() throws IOException
170     {
171         // Implicit mode.
172         if (isImplicit) {
173             performSSLNegotiation();
174         }
175         super._connectAction_();
176         // Explicit mode - don't do anything. The user calls execTLS()
177     }
178 
179     /**
180      * Performs a lazy init of the SSL context.
181      * @throws IOException When could not initialize the SSL context.
182      */
183     private void initSSLContext() throws IOException
184     {
185         if (context == null)
186         {
187             context = SSLContextUtils.createSSLContext(protocol, getKeyManager(), getTrustManager());
188         }
189     }
190 
191     /**
192      * SSL/TLS negotiation. Acquires an SSL socket of a
193      * connection and carries out handshake processing.
194      * @throws IOException If server negotiation fails.
195      */
196     private void performSSLNegotiation() throws IOException
197     {
198         initSSLContext();
199 
200         SSLSocketFactory ssf = context.getSocketFactory();
201         String host = (_hostname_ != null) ? _hostname_ : getRemoteAddress().getHostAddress();
202         int port = getRemotePort();
203         SSLSocket socket =
204             (SSLSocket) ssf.createSocket(_socket_, host, port, true);
205         socket.setEnableSessionCreation(true);
206         socket.setUseClientMode(true);
207 
208         if (tlsEndpointChecking) {
209             SSLSocketUtils.enableEndpointNameVerification(socket);
210         }
211 
212         if (protocols != null) {
213             socket.setEnabledProtocols(protocols);
214         }
215         if (suites != null) {
216             socket.setEnabledCipherSuites(suites);
217         }
218         socket.startHandshake();
219 
220         // TODO the following setup appears to duplicate that in the super class methods
221         _socket_ = socket;
222         _input_ = socket.getInputStream();
223         _output_ = socket.getOutputStream();
224         _reader =
225           new CRLFLineReader(new InputStreamReader(_input_,
226                                                    __DEFAULT_ENCODING));
227         __writer =
228           new BufferedWriter(new OutputStreamWriter(_output_,
229                                                     __DEFAULT_ENCODING));
230 
231         if (hostnameVerifier != null && !hostnameVerifier.verify(host, socket.getSession())) {
232             throw new SSLHandshakeException("Hostname doesn't match certificate");
233         }
234     }
235 
236     /**
237      * Get the {@link KeyManager} instance.
238      * @return The current {@link KeyManager} instance.
239      */
240     private KeyManager getKeyManager()
241     {
242         return keyManager;
243     }
244 
245     /**
246      * Set a {@link KeyManager} to use.
247      * @param newKeyManager The KeyManager implementation to set.
248      * @see org.apache.commons.net.util.KeyManagerUtils
249      */
250     public void setKeyManager(KeyManager newKeyManager)
251     {
252         keyManager = newKeyManager;
253     }
254 
255     /**
256      * Controls which particular cipher suites are enabled for use on this
257      * connection. Called before server negotiation.
258      * @param cipherSuites The cipher suites.
259      */
260     public void setEnabledCipherSuites(String[] cipherSuites)
261     {
262         suites = new String[cipherSuites.length];
263         System.arraycopy(cipherSuites, 0, suites, 0, cipherSuites.length);
264     }
265 
266     /**
267      * Returns the names of the cipher suites which could be enabled
268      * for use on this connection.
269      * When the underlying {@link java.net.Socket Socket} is not an {@link SSLSocket} instance, returns null.
270      * @return An array of cipher suite names, or <code>null</code>.
271      */
272     public String[] getEnabledCipherSuites()
273     {
274         if (_socket_ instanceof SSLSocket)
275         {
276             return ((SSLSocket)_socket_).getEnabledCipherSuites();
277         }
278         return null;
279     }
280 
281     /**
282      * Controls which particular protocol versions are enabled for use on this
283      * connection. I perform setting before a server negotiation.
284      * @param protocolVersions The protocol versions.
285      */
286     public void setEnabledProtocols(String[] protocolVersions)
287     {
288         protocols = new String[protocolVersions.length];
289         System.arraycopy(protocolVersions, 0, protocols, 0, protocolVersions.length);
290     }
291 
292     /**
293      * Returns the names of the protocol versions which are currently
294      * enabled for use on this connection.
295      * When the underlying {@link java.net.Socket Socket} is not an {@link SSLSocket} instance, returns null.
296      * @return An array of protocols, or <code>null</code>.
297      */
298     public String[] getEnabledProtocols()
299     {
300         if (_socket_ instanceof SSLSocket)
301         {
302             return ((SSLSocket)_socket_).getEnabledProtocols();
303         }
304         return null;
305     }
306 
307     /**
308      * The TLS command execution.
309      * @throws SSLException If the server reply code is not positive.
310      * @throws IOException If an I/O error occurs while sending
311      * the command or performing the negotiation.
312      * @return TRUE if the command and negotiation succeeded.
313      */
314     public boolean execTLS() throws SSLException, IOException
315     {
316         if (sendCommand(IMAPCommand.getCommand(IMAPCommand.STARTTLS)) != IMAPReply.OK)
317         {
318             return false;
319             //throw new SSLException(getReplyString());
320         }
321         performSSLNegotiation();
322         return true;
323     }
324 
325     /**
326      * Get the currently configured {@link TrustManager}.
327      * @return A TrustManager instance.
328      */
329     public TrustManager getTrustManager()
330     {
331         return trustManager;
332     }
333 
334     /**
335      * Override the default {@link TrustManager} to use.
336      * @param newTrustManager The TrustManager implementation to set.
337      * @see org.apache.commons.net.util.TrustManagerUtils
338      */
339     public void setTrustManager(TrustManager newTrustManager)
340     {
341         trustManager = newTrustManager;
342     }
343 
344     /**
345      * Get the currently configured {@link HostnameVerifier}.
346      * @return A HostnameVerifier instance.
347      * @since 3.4
348      */
349     public HostnameVerifier getHostnameVerifier()
350     {
351         return hostnameVerifier;
352     }
353 
354     /**
355      * Override the default {@link HostnameVerifier} to use.
356      * @param newHostnameVerifier The HostnameVerifier implementation to set or <code>null</code> to disable.
357      * @since 3.4
358      */
359     public void setHostnameVerifier(HostnameVerifier newHostnameVerifier)
360     {
361         hostnameVerifier = newHostnameVerifier;
362     }
363 
364     /**
365      * Return whether or not endpoint identification using the HTTPS algorithm
366      * on Java 1.7+ is enabled. The default behaviour is for this to be disabled.
367      *
368      * @return True if enabled, false if not.
369      * @since 3.4
370      */
371     public boolean isEndpointCheckingEnabled()
372     {
373         return tlsEndpointChecking;
374     }
375 
376     /**
377      * Automatic endpoint identification checking using the HTTPS algorithm
378      * is supported on Java 1.7+. The default behaviour is for this to be disabled.
379      *
380      * @param enable Enable automatic endpoint identification checking using the HTTPS algorithm on Java 1.7+.
381      * @since 3.4
382      */
383     public void setEndpointCheckingEnabled(boolean enable)
384     {
385         tlsEndpointChecking = enable;
386     }
387 }
388 /* kate: indent-width 4; replace-tabs on; */