    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="http://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</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