IMAPSClient.java

  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. package org.apache.commons.net.imap;

  18. import java.io.BufferedWriter;
  19. import java.io.IOException;
  20. import java.io.InputStreamReader;
  21. import java.io.OutputStreamWriter;

  22. import javax.net.ssl.HostnameVerifier;
  23. import javax.net.ssl.KeyManager;
  24. import javax.net.ssl.SSLContext;
  25. import javax.net.ssl.SSLException;
  26. import javax.net.ssl.SSLHandshakeException;
  27. import javax.net.ssl.SSLSocket;
  28. import javax.net.ssl.SSLSocketFactory;
  29. import javax.net.ssl.TrustManager;

  30. import org.apache.commons.net.io.CRLFLineReader;
  31. import org.apache.commons.net.util.SSLContextUtils;
  32. import org.apache.commons.net.util.SSLSocketUtils;

  33. /**
  34.  * The IMAPSClient class provides SSL/TLS connection encryption to IMAPClient. Copied from
  35.  * <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
  36.  * IMAP. If implicit mode is selected (NOT the default), SSL/TLS negotiation starts right after the connection has been established. In explicit mode (the
  37.  * default), SSL/TLS negotiation starts when the user calls execTLS() and the server accepts the command.
  38.  *
  39.  * <pre>
  40.  * {@code
  41.  * //Implicit usage:
  42.  *
  43.  *               IMAPSClient c = new IMAPSClient(true);
  44.  *               c.connect("127.0.0.1", 993);
  45.  *
  46.  * //Explicit usage:
  47.  *
  48.  *               IMAPSClient c = new IMAPSClient();
  49.  *               c.connect("127.0.0.1", 143);
  50.  *               if (c.execTLS()) { /rest of the commands here/ }
  51.  * }
  52.  * </pre>
  53.  *
  54.  * <b>Warning</b>: the hostname is not verified against the certificate by default, use {@link #setHostnameVerifier(HostnameVerifier)} or
  55.  * {@link #setEndpointCheckingEnabled(boolean)} (on Java 1.7+) to enable verification.
  56.  */
  57. public class IMAPSClient extends IMAPClient {
  58.     /** The default IMAP over SSL port. */
  59.     public static final int DEFAULT_IMAPS_PORT = 993;

  60.     /** Default secure socket protocol name. */
  61.     public static final String DEFAULT_PROTOCOL = "TLS";

  62.     /** The security mode. True - Implicit Mode / False - Explicit Mode. */
  63.     private final boolean isImplicit;
  64.     /** The secure socket protocol to be used, like SSL/TLS. */
  65.     private final String protocol;
  66.     /** The context object. */
  67.     private SSLContext context;
  68.     /**
  69.      * The cipher suites. SSLSockets have a default set of these anyway, so no initialization required.
  70.      */
  71.     private String[] suites;
  72.     /** The protocol versions. */
  73.     private String[] protocols // null;
  74.     ; // {"SSLv2", "SSLv3", "TLSv1", "TLSv1.1", "SSLv2Hello"};

  75.     /** The IMAPS {@link TrustManager} implementation, default null. */
  76.     private TrustManager trustManager;

  77.     /** The {@link KeyManager}, default null. */
  78.     private KeyManager keyManager;

  79.     /** The {@link HostnameVerifier} to use post-TLS, default null (i.e. no verification). */
  80.     private HostnameVerifier hostnameVerifier;

  81.     /** Use Java 1.7+ HTTPS Endpoint Identification Algorithm. */
  82.     private boolean tlsEndpointChecking;

  83.     /**
  84.      * Constructor for IMAPSClient. Sets security mode to explicit (isImplicit = false).
  85.      */
  86.     public IMAPSClient() {
  87.         this(DEFAULT_PROTOCOL, false);
  88.     }

  89.     /**
  90.      * Constructor for IMAPSClient.
  91.      *
  92.      * @param implicit The security mode (Implicit/Explicit).
  93.      */
  94.     public IMAPSClient(final boolean implicit) {
  95.         this(DEFAULT_PROTOCOL, implicit);
  96.     }

  97.     /**
  98.      * Constructor for IMAPSClient.
  99.      *
  100.      * @param implicit The security mode(Implicit/Explicit).
  101.      * @param ctx      A pre-configured SSL Context.
  102.      */
  103.     public IMAPSClient(final boolean implicit, final SSLContext ctx) {
  104.         this(DEFAULT_PROTOCOL, implicit, ctx);
  105.     }

  106.     /**
  107.      * Constructor for IMAPSClient.
  108.      *
  109.      * @param context A pre-configured SSL Context.
  110.      */
  111.     public IMAPSClient(final SSLContext context) {
  112.         this(false, context);
  113.     }

  114.     /**
  115.      * Constructor for IMAPSClient.
  116.      *
  117.      * @param proto the protocol.
  118.      */
  119.     public IMAPSClient(final String proto) {
  120.         this(proto, false);
  121.     }

  122.     /**
  123.      * Constructor for IMAPSClient.
  124.      *
  125.      * @param proto    the protocol.
  126.      * @param implicit The security mode(Implicit/Explicit).
  127.      */
  128.     public IMAPSClient(final String proto, final boolean implicit) {
  129.         this(proto, implicit, null);
  130.     }

  131.     /**
  132.      * Constructor for IMAPSClient.
  133.      *
  134.      * @param proto    the protocol.
  135.      * @param implicit The security mode(Implicit/Explicit).
  136.      * @param ctx      the SSL context
  137.      */
  138.     public IMAPSClient(final String proto, final boolean implicit, final SSLContext ctx) {
  139.         setDefaultPort(DEFAULT_IMAPS_PORT);
  140.         protocol = proto;
  141.         isImplicit = implicit;
  142.         context = ctx;
  143.     }

  144.     /**
  145.      * Because there are so many connect() methods, the _connectAction_() method is provided as a means of performing some action immediately after establishing
  146.      * a connection, rather than reimplementing all the connect() methods.
  147.      *
  148.      * @throws IOException If it is thrown by _connectAction_().
  149.      * @see org.apache.commons.net.SocketClient#_connectAction_()
  150.      */
  151.     @Override
  152.     protected void _connectAction_() throws IOException {
  153.         // Implicit mode.
  154.         if (isImplicit) {
  155.             performSSLNegotiation();
  156.         }
  157.         super._connectAction_();
  158.         // Explicit mode - don't do anything. The user calls execTLS()
  159.     }

  160.     /**
  161.      * The TLS command execution.
  162.      *
  163.      * @throws SSLException If the server reply code is not positive.
  164.      * @throws IOException  If an I/O error occurs while sending the command or performing the negotiation.
  165.      * @return TRUE if the command and negotiation succeeded.
  166.      */
  167.     public boolean execTLS() throws SSLException, IOException {
  168.         if (sendCommand(IMAPCommand.getCommand(IMAPCommand.STARTTLS)) != IMAPReply.OK) {
  169.             return false;
  170.             // throw new SSLException(getReplyString());
  171.         }
  172.         performSSLNegotiation();
  173.         return true;
  174.     }

  175.     /**
  176.      * 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
  177.      * {@link SSLSocket} instance, returns null.
  178.      *
  179.      * @return An array of cipher suite names, or {@code null}.
  180.      */
  181.     public String[] getEnabledCipherSuites() {
  182.         if (_socket_ instanceof SSLSocket) {
  183.             return ((SSLSocket) _socket_).getEnabledCipherSuites();
  184.         }
  185.         return null;
  186.     }

  187.     /**
  188.      * 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
  189.      * not an {@link SSLSocket} instance, returns null.
  190.      *
  191.      * @return An array of protocols, or {@code null}.
  192.      */
  193.     public String[] getEnabledProtocols() {
  194.         if (_socket_ instanceof SSLSocket) {
  195.             return ((SSLSocket) _socket_).getEnabledProtocols();
  196.         }
  197.         return null;
  198.     }

  199.     /**
  200.      * Gets the currently configured {@link HostnameVerifier}.
  201.      *
  202.      * @return A HostnameVerifier instance.
  203.      * @since 3.4
  204.      */
  205.     public HostnameVerifier getHostnameVerifier() {
  206.         return hostnameVerifier;
  207.     }

  208.     /**
  209.      * Gets the {@link KeyManager} instance.
  210.      *
  211.      * @return The current {@link KeyManager} instance.
  212.      */
  213.     private KeyManager getKeyManager() {
  214.         return keyManager;
  215.     }

  216.     /**
  217.      * Gets the currently configured {@link TrustManager}.
  218.      *
  219.      * @return A TrustManager instance.
  220.      */
  221.     public TrustManager getTrustManager() {
  222.         return trustManager;
  223.     }

  224.     /**
  225.      * Performs a lazy init of the SSL context.
  226.      *
  227.      * @throws IOException When could not initialize the SSL context.
  228.      */
  229.     private void initSSLContext() throws IOException {
  230.         if (context == null) {
  231.             context = SSLContextUtils.createSSLContext(protocol, getKeyManager(), getTrustManager());
  232.         }
  233.     }

  234.     /**
  235.      * 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.
  236.      *
  237.      * @return True if enabled, false if not.
  238.      * @since 3.4
  239.      */
  240.     public boolean isEndpointCheckingEnabled() {
  241.         return tlsEndpointChecking;
  242.     }

  243.     /**
  244.      * SSL/TLS negotiation. Acquires an SSL socket of a connection and carries out handshake processing.
  245.      *
  246.      * @throws IOException If server negotiation fails.
  247.      */
  248.     private void performSSLNegotiation() throws IOException {
  249.         initSSLContext();

  250.         final SSLSocketFactory ssf = context.getSocketFactory();
  251.         final String host = _hostname_ != null ? _hostname_ : getRemoteAddress().getHostAddress();
  252.         final int port = getRemotePort();
  253.         final SSLSocket socket = (SSLSocket) ssf.createSocket(_socket_, host, port, true);
  254.         socket.setEnableSessionCreation(true);
  255.         socket.setUseClientMode(true);

  256.         if (tlsEndpointChecking) {
  257.             SSLSocketUtils.enableEndpointNameVerification(socket);
  258.         }

  259.         if (protocols != null) {
  260.             socket.setEnabledProtocols(protocols);
  261.         }
  262.         if (suites != null) {
  263.             socket.setEnabledCipherSuites(suites);
  264.         }
  265.         socket.startHandshake();

  266.         // TODO the following setup appears to duplicate that in the super class methods
  267.         _socket_ = socket;
  268.         _input_ = socket.getInputStream();
  269.         _output_ = socket.getOutputStream();
  270.         _reader = new CRLFLineReader(new InputStreamReader(_input_, __DEFAULT_ENCODING));
  271.         __writer = new BufferedWriter(new OutputStreamWriter(_output_, __DEFAULT_ENCODING));

  272.         if (hostnameVerifier != null && !hostnameVerifier.verify(host, socket.getSession())) {
  273.             throw new SSLHandshakeException("Hostname doesn't match certificate");
  274.         }
  275.     }

  276.     /**
  277.      * Controls which particular cipher suites are enabled for use on this connection. Called before server negotiation.
  278.      *
  279.      * @param cipherSuites The cipher suites.
  280.      */
  281.     public void setEnabledCipherSuites(final String[] cipherSuites) {
  282.         suites = cipherSuites.clone();
  283.     }

  284.     /**
  285.      * Controls which particular protocol versions are enabled for use on this connection. I perform setting before a server negotiation.
  286.      *
  287.      * @param protocolVersions The protocol versions.
  288.      */
  289.     public void setEnabledProtocols(final String[] protocolVersions) {
  290.         protocols = protocolVersions.clone();
  291.     }

  292.     /**
  293.      * Automatic endpoint identification checking using the HTTPS algorithm is supported on Java 1.7+. The default behavior is for this to be disabled.
  294.      *
  295.      * @param enable Enable automatic endpoint identification checking using the HTTPS algorithm on Java 1.7+.
  296.      * @since 3.4
  297.      */
  298.     public void setEndpointCheckingEnabled(final boolean enable) {
  299.         tlsEndpointChecking = enable;
  300.     }

  301.     /**
  302.      * Override the default {@link HostnameVerifier} to use.
  303.      *
  304.      * @param newHostnameVerifier The HostnameVerifier implementation to set or {@code null} to disable.
  305.      * @since 3.4
  306.      */
  307.     public void setHostnameVerifier(final HostnameVerifier newHostnameVerifier) {
  308.         hostnameVerifier = newHostnameVerifier;
  309.     }

  310.     /**
  311.      * Sets a {@link KeyManager} to use.
  312.      *
  313.      * @param newKeyManager The KeyManager implementation to set.
  314.      * @see org.apache.commons.net.util.KeyManagerUtils
  315.      */
  316.     public void setKeyManager(final KeyManager newKeyManager) {
  317.         keyManager = newKeyManager;
  318.     }

  319.     /**
  320.      * Override the default {@link TrustManager} to use.
  321.      *
  322.      * @param newTrustManager The TrustManager implementation to set.
  323.      * @see org.apache.commons.net.util.TrustManagerUtils
  324.      */
  325.     public void setTrustManager(final TrustManager newTrustManager) {
  326.         trustManager = newTrustManager;
  327.     }
  328. }