/*
 * Copyright (c) 2017, Red Hat, Inc. and/or its affiliates.
 * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package sun.security.ssl;

import java.io.IOException;
import java.nio.ByteBuffer;
import javax.net.ssl.SSLProtocolException;
import static sun.security.ssl.SSLExtension.CH_EXTENDED_MASTER_SECRET;
import sun.security.ssl.SSLExtension.ExtensionConsumer;
import static sun.security.ssl.SSLExtension.SH_EXTENDED_MASTER_SECRET;
import sun.security.ssl.SSLExtension.SSLExtensionSpec;
import sun.security.ssl.SSLHandshake.HandshakeMessage;

/**
 * Pack of the "extended_master_secret" extensions [RFC 7627].
 */
final class ExtendedMasterSecretExtension {
    static final HandshakeProducer chNetworkProducer =
            new CHExtendedMasterSecretProducer();
    static final ExtensionConsumer chOnLoadConsumer =
            new CHExtendedMasterSecretConsumer();
    static final HandshakeAbsence chOnLoadAbsence =
            new CHExtendedMasterSecretAbsence();

    static final HandshakeProducer shNetworkProducer =
            new SHExtendedMasterSecretProducer();
    static final ExtensionConsumer shOnLoadConsumer =
            new SHExtendedMasterSecretConsumer();
    static final HandshakeAbsence shOnLoadAbsence =
            new SHExtendedMasterSecretAbsence();

    static final SSLStringizer emsStringizer =
            new ExtendedMasterSecretStringizer();

    /**
     * The "extended_master_secret" extension.
     */
    static final class ExtendedMasterSecretSpec implements SSLExtensionSpec {
        // A nominal object that does not holding any real renegotiation info.
        static final ExtendedMasterSecretSpec NOMINAL =
                new ExtendedMasterSecretSpec();

        private ExtendedMasterSecretSpec() {
            // blank
        }

        private ExtendedMasterSecretSpec(ByteBuffer m) throws IOException {
            // Parse the extension.
            if (m.hasRemaining()) {
                throw new SSLProtocolException(
                    "Invalid extended_master_secret extension data: " +
                    "not empty");
            }
        }

        @Override
        public String toString() {
            return "<empty>";
        }
    }

    private static final
            class ExtendedMasterSecretStringizer implements SSLStringizer {
        @Override
        public String toString(ByteBuffer buffer) {
            try {
                return (new ExtendedMasterSecretSpec(buffer)).toString();
            } catch (IOException ioe) {
                // For debug logging only, so please swallow exceptions.
                return ioe.getMessage();
            }
        }
    }

    /**
     * Network data producer of a "extended_master_secret" extension in
     * the ClientHello handshake message.
     */
    private static final
            class CHExtendedMasterSecretProducer implements HandshakeProducer {
        // Prevent instantiation of this class.
        private CHExtendedMasterSecretProducer() {
            // blank
        }

        @Override
        public byte[] produce(ConnectionContext context,
                HandshakeMessage message) throws IOException {
            // The producing happens in client side only.
            ClientHandshakeContext chc = (ClientHandshakeContext)context;

            // Is it a supported and enabled extension?
            if (!chc.sslConfig.isAvailable(CH_EXTENDED_MASTER_SECRET) ||
                    !SSLConfiguration.useExtendedMasterSecret ||
                    !chc.conContext.protocolVersion.useTLS10PlusSpec()) {
                if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
                    SSLLogger.fine(
                        "Ignore unavailable extended_master_secret extension");
                }

                return null;
            }

            if (chc.handshakeSession == null ||
                    chc.handshakeSession.useExtendedMasterSecret) {
                byte[] extData = new byte[0];
                chc.handshakeExtensions.put(CH_EXTENDED_MASTER_SECRET,
                        ExtendedMasterSecretSpec.NOMINAL);

                return extData;
            }

            return null;
        }
    }

    /**
     * Network data producer of a "extended_master_secret" extension in
     * the ServerHello handshake message.
     */
    private static final
            class CHExtendedMasterSecretConsumer implements ExtensionConsumer {
        // Prevent instantiation of this class.
        private CHExtendedMasterSecretConsumer() {
            // blank
        }

        @Override
        public void consume(ConnectionContext context,
            HandshakeMessage message, ByteBuffer buffer) throws IOException {

            // The consuming happens in server side only.
            ServerHandshakeContext shc = (ServerHandshakeContext)context;

            // Is it a supported and enabled extension?
            if (!shc.sslConfig.isAvailable(CH_EXTENDED_MASTER_SECRET) ||
                    !SSLConfiguration.useExtendedMasterSecret ||
                    !shc.negotiatedProtocol.useTLS10PlusSpec()) {
                if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
                    SSLLogger.fine("Ignore unavailable extension: " +
                            CH_EXTENDED_MASTER_SECRET.name);
                }
                return;     // ignore the extension
            }

            // Parse the extension.
            ExtendedMasterSecretSpec spec;
            try {
                spec = new ExtendedMasterSecretSpec(buffer);
            } catch (IOException ioe) {
                throw shc.conContext.fatal(Alert.UNEXPECTED_MESSAGE, ioe);
            }

            if (shc.isResumption && shc.resumingSession != null &&
                    !shc.resumingSession.useExtendedMasterSecret) {
                // For abbreviated handshake request, If the original
                // session did not use the "extended_master_secret"
                // extension but the new ClientHello contains the
                // extension, then the server MUST NOT perform the
                // abbreviated handshake.  Instead, it SHOULD continue
                // with a full handshake.
                shc.isResumption = false;
                shc.resumingSession = null;
                if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
                    SSLLogger.fine(
                        "abort session resumption which did not use " +
                        "Extended Master Secret extension");
                }
            }

            // Update the context.
            //
            shc.handshakeExtensions.put(
                CH_EXTENDED_MASTER_SECRET, ExtendedMasterSecretSpec.NOMINAL);

            // No impact on session resumption.
        }
    }

    /**
     * The absence processing if a "extended_master_secret" extension is
     * not present in the ClientHello handshake message.
     */
    private static final
            class CHExtendedMasterSecretAbsence implements HandshakeAbsence {
        @Override
        public void absent(ConnectionContext context,
                HandshakeMessage message) throws IOException {
            // The producing happens in server side only.
            ServerHandshakeContext shc = (ServerHandshakeContext)context;

            // Is it a supported and enabled extension?
            if (!shc.sslConfig.isAvailable(CH_EXTENDED_MASTER_SECRET) ||
                    !SSLConfiguration.useExtendedMasterSecret) {
                if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
                    SSLLogger.fine("Ignore unavailable extension: " +
                            CH_EXTENDED_MASTER_SECRET.name);
                }
                return;     // ignore the extension
            }

            if (shc.negotiatedProtocol.useTLS10PlusSpec() &&
                    !SSLConfiguration.allowLegacyMasterSecret) {
                // For full handshake, if the server receives a ClientHello
                // without the extension, it SHOULD abort the handshake if
                // it does not wish to interoperate with legacy clients.
                //
                // As if extended master extension is required for full
                // handshake, it MUST be used in abbreviated handshake too.
                throw shc.conContext.fatal(Alert.HANDSHAKE_FAILURE,
                    "Extended Master Secret extension is required");
            }

            if (shc.isResumption && shc.resumingSession != null) {
                if (shc.resumingSession.useExtendedMasterSecret) {
                    // For abbreviated handshake request, if the original
                    // session used the "extended_master_secret" extension
                    // but the new ClientHello does not contain it, the
                    // server MUST abort the abbreviated handshake.
                    throw shc.conContext.fatal(Alert.HANDSHAKE_FAILURE,
                            "Missing Extended Master Secret extension " +
                            "on session resumption");
                } else {
                    // For abbreviated handshake request, if neither the
                    // original session nor the new ClientHello uses the
                    // extension, the server SHOULD abort the handshake.
                    if (!SSLConfiguration.allowLegacyResumption) {
                        throw shc.conContext.fatal(Alert.HANDSHAKE_FAILURE,
                            "Missing Extended Master Secret extension " +
                            "on session resumption");
                    } else {  // Otherwise, continue with a full handshake.
                        shc.isResumption = false;
                        shc.resumingSession = null;
                        if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
                            SSLLogger.fine(
                                "abort session resumption, " +
                                "missing Extended Master Secret extension");
                        }
                    }
                }
            }
        }
    }

    /**
     * Network data producer of a "extended_master_secret" extension in
     * the ServerHello handshake message.
     */
    private static final
            class SHExtendedMasterSecretProducer implements HandshakeProducer {
        // Prevent instantiation of this class.
        private SHExtendedMasterSecretProducer() {
            // blank
        }

        @Override
        public byte[] produce(ConnectionContext context,
                HandshakeMessage message) throws IOException {
            // The producing happens in server side only.
            ServerHandshakeContext shc = (ServerHandshakeContext)context;

            if (shc.handshakeSession.useExtendedMasterSecret) {
                byte[] extData = new byte[0];
                shc.handshakeExtensions.put(SH_EXTENDED_MASTER_SECRET,
                        ExtendedMasterSecretSpec.NOMINAL);

                return extData;
            }

            return null;
        }
    }

    /**
     * Network data consumer of a "extended_master_secret" extension in
     * the ServerHello handshake message.
     */
    private static final
            class SHExtendedMasterSecretConsumer implements ExtensionConsumer {
        // Prevent instantiation of this class.
        private SHExtendedMasterSecretConsumer() {
            // blank
        }

        @Override
        public void consume(ConnectionContext context,
            HandshakeMessage message, ByteBuffer buffer) throws IOException {
            // The producing happens in client side only.
            ClientHandshakeContext chc = (ClientHandshakeContext)context;

            // In response to the client extended_master_secret extension
            // request, which is mandatory for ClientHello message.
            ExtendedMasterSecretSpec requstedSpec = (ExtendedMasterSecretSpec)
                    chc.handshakeExtensions.get(CH_EXTENDED_MASTER_SECRET);
            if (requstedSpec == null) {
                throw chc.conContext.fatal(Alert.UNSUPPORTED_EXTENSION,
                        "Server sent the extended_master_secret " +
                        "extension improperly");
            }

            // Parse the extension.
            ExtendedMasterSecretSpec spec;
            try {
                spec = new ExtendedMasterSecretSpec(buffer);
            } catch (IOException ioe) {
                throw chc.conContext.fatal(Alert.UNEXPECTED_MESSAGE, ioe);
            }

            if (chc.isResumption && chc.resumingSession != null &&
                    !chc.resumingSession.useExtendedMasterSecret) {
                throw chc.conContext.fatal(Alert.UNSUPPORTED_EXTENSION,
                        "Server sent an unexpected extended_master_secret " +
                        "extension on session resumption");
            }

            // Update the context.
            chc.handshakeExtensions.put(
                SH_EXTENDED_MASTER_SECRET, ExtendedMasterSecretSpec.NOMINAL);

            // No impact on session resumption.
        }
    }

    /**
     * The absence processing if a "extended_master_secret" extension is
     * not present in the ServerHello handshake message.
     */
    private static final
            class SHExtendedMasterSecretAbsence implements HandshakeAbsence {
        @Override
        public void absent(ConnectionContext context,
                HandshakeMessage message) throws IOException {
            // The producing happens in client side only.
            ClientHandshakeContext chc = (ClientHandshakeContext)context;

            if (SSLConfiguration.useExtendedMasterSecret &&
                    !SSLConfiguration.allowLegacyMasterSecret) {
                // For full handshake, if a client receives a ServerHello
                // without the extension, it SHOULD abort the handshake if
                // it does not wish to interoperate with legacy servers.
                throw chc.conContext.fatal(Alert.HANDSHAKE_FAILURE,
                        "Extended Master Secret extension is required");
            }

            if (chc.isResumption && chc.resumingSession != null) {
                if (chc.resumingSession.useExtendedMasterSecret) {
                    // For abbreviated handshake, if the original session used
                    // the "extended_master_secret" extension but the new
                    // ServerHello does not contain the extension, the client
                    // MUST abort the handshake.
                    throw chc.conContext.fatal(Alert.HANDSHAKE_FAILURE,
                            "Missing Extended Master Secret extension " +
                            "on session resumption");
                } else if (SSLConfiguration.useExtendedMasterSecret &&
                        !SSLConfiguration.allowLegacyResumption &&
                        chc.negotiatedProtocol.useTLS10PlusSpec()) {
                    // Unlikely, abbreviated handshake should be discarded.
                    throw chc.conContext.fatal(Alert.HANDSHAKE_FAILURE,
                        "Extended Master Secret extension is required");
                }
            }
        }
    }
}

