/*
 * Copyright (c) 1996, 2007, 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 net.jradius.ssl;

import java.io.*;
import java.math.BigInteger;
import java.security.*;
import java.security.interfaces.*;
import java.security.spec.*;
import java.security.cert.*;
import java.security.cert.Certificate;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

import java.lang.reflect.*;

import javax.security.auth.x500.X500Principal;

import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;

import javax.net.ssl.*;

import net.jradius.ssl.CipherSuite.*;

import sun.security.action.GetPropertyAction;

import sun.security.internal.spec.TlsPrfParameterSpec;


/**
 * Many data structures are involved in the handshake messages.  These
 * classes are used as structures, with public data members.  They are
 * not visible outside the SSL package.
 *
 * Handshake messages all have a common header format, and they are all
 * encoded in a "handshake data" SSL record substream.  The base class
 * here (HandshakeMessage) provides a common framework and records the
 * SSL record type of the particular handshake message.
 *
 * This file contains subclasses for all the basic handshake messages.
 * All handshake messages know how to encode and decode themselves on
 * SSL streams; this facilitates using the same code on SSL client and
 * server sides, although they don't send and receive the same messages.
 *
 * Messages also know how to print themselves, which is quite handy
 * for debugging.  They always identify their type, and can optionally
 * dump all of their content.
 *
 * @author David Brownell
 */
abstract class HandshakeMessage {

    HandshakeMessage() { }

    // enum HandshakeType:
    static final byte   ht_hello_request = 0;
    static final byte   ht_client_hello = 1;
    static final byte   ht_server_hello = 2;

    static final byte   ht_certificate = 11;
    static final byte   ht_server_key_exchange = 12;
    static final byte   ht_certificate_request = 13;
    static final byte   ht_server_hello_done = 14;
    static final byte   ht_certificate_verify = 15;
    static final byte   ht_client_key_exchange = 16;

    static final byte   ht_finished = 20;

    /* Class and subclass dynamic debugging support */
    static final Debug debug = Debug.getInstance("ssl");

    /**
     * Utility method to convert a BigInteger to a byte array in unsigned
     * format as needed in the handshake messages. BigInteger uses
     * 2's complement format, i.e. it prepends an extra zero if the MSB
     * is set. We remove that.
     */
    static byte[] toByteArray(BigInteger bi) {
        byte[] b = bi.toByteArray();
        if ((b.length > 1) && (b[0] == 0)) {
            int n = b.length - 1;
            byte[] newarray = new byte[n];
            System.arraycopy(b, 1, newarray, 0, n);
            b = newarray;
        }
        return b;
    }

    /*
     * SSL 3.0 MAC padding constants.
     * Also used by CertificateVerify and Finished during the handshake.
     */
    static final byte[] MD5_pad1 = genPad(0x36, 48);
    static final byte[] MD5_pad2 = genPad(0x5c, 48);

    static final byte[] SHA_pad1 = genPad(0x36, 40);
    static final byte[] SHA_pad2 = genPad(0x5c, 40);

    private static byte[] genPad(int b, int count) {
        byte[] padding = new byte[count];
        Arrays.fill(padding, (byte)b);
        return padding;
    }

    /*
     * Write a handshake message on the (handshake) output stream.
     * This is just a four byte header followed by the data.
     *
     * NOTE that huge messages -- notably, ones with huge cert
     * chains -- are handled correctly.
     */
    final void write(HandshakeOutStream s) throws IOException {
        int len = messageLength();
        if (len > (1 << 24)) {
            throw new SSLException("Handshake message too big"
                + ", type = " + messageType() + ", len = " + len);
        }
        s.write(messageType());
        s.putInt24(len);
        send(s);
    }

    /*
     * Subclasses implement these methods so those kinds of
     * messages can be emitted.  Base class delegates to subclass.
     */
    abstract int  messageType();
    abstract int  messageLength();
    abstract void send(HandshakeOutStream s) throws IOException;

    /*
     * Write a descriptive message on the output stream; for debugging.
     */
    abstract void print(PrintStream p) throws IOException;

//
// NOTE:  the rest of these classes are nested within this one, and are
// imported by other classes in this package.  There are a few other
// handshake message classes, not neatly nested here because of current
// licensing requirement for native (RSA) methods.  They belong here,
// but those native methods complicate things a lot!
//


/*
 * HelloRequest ... SERVER --> CLIENT
 *
 * Server can ask the client to initiate a new handshake, e.g. to change
 * session parameters after a connection has been (re)established.
 */
static final
class HelloRequest extends HandshakeMessage
{
    int messageType() { return ht_hello_request; }

    HelloRequest() { }

    HelloRequest(HandshakeInStream in) throws IOException
    {
        // nothing in this message
    }

    int messageLength() { return 0; }

    void send(HandshakeOutStream out) throws IOException
    {
        // nothing in this messaage
    }

    void print(PrintStream out) throws IOException
    {
        out.println("*** HelloRequest (empty)");
    }

}


/*
 * ClientHello ... CLIENT --> SERVER
 *
 * Client initiates handshake by telling server what it wants, and what it
 * can support (prioritized by what's first in the ciphe suite list).
 *
 * By RFC2246:7.4.1.2 it's explicitly anticipated that this message
 * will have more data added at the end ... e.g. what CAs the client trusts.
 * Until we know how to parse it, we will just read what we know
 * about, and let our caller handle the jumps over unknown data.
 */
static final
class ClientHello extends HandshakeMessage
{
    int messageType() { return ht_client_hello; }

    ProtocolVersion     protocolVersion;
    RandomCookie        clnt_random;
    SessionId           sessionId;
    private CipherSuiteList    cipherSuites;
    byte[]              compression_methods;

    HelloExtensions extensions = new HelloExtensions();

    private final static byte[]  NULL_COMPRESSION = new byte[] {0};

    ClientHello(SecureRandom generator, ProtocolVersion protocolVersion) {
        this.protocolVersion = protocolVersion;
        clnt_random = new RandomCookie(generator);
        compression_methods = NULL_COMPRESSION;
        // sessionId, cipher_suites TBS later
    }

    CipherSuiteList getCipherSuites() {
        return cipherSuites;
    }

    // Set the ciphersuites.
    // This method may only be called once.
    void setCipherSuites(CipherSuiteList cipherSuites) {
        this.cipherSuites = cipherSuites;
        if (cipherSuites.containsEC()) {
            extensions.add(SupportedEllipticCurvesExtension.DEFAULT);
            extensions.add(SupportedEllipticPointFormatsExtension.DEFAULT);
        }
    }

    int messageLength() {
        /*
         * Add fixed size parts of each field...
         * version + random + session + cipher + compress
         */
        return (2 + 32 + 1 + 2 + 1
            + sessionId.length()                /* ... + variable parts */
            + (cipherSuites.size() * 2)
            + compression_methods.length)
            + extensions.length();
    }

    ClientHello(HandshakeInStream s, int messageLength) throws IOException {
        protocolVersion = ProtocolVersion.valueOf(s.getInt8(), s.getInt8());
        clnt_random = new RandomCookie(s);
        sessionId = new SessionId(s.getBytes8());
        cipherSuites = new CipherSuiteList(s);
        compression_methods = s.getBytes8();
        if (messageLength() != messageLength) {
            extensions = new HelloExtensions(s);
        }
    }

    void send(HandshakeOutStream s) throws IOException {
        s.putInt8(protocolVersion.major);
        s.putInt8(protocolVersion.minor);
        clnt_random.send(s);
        s.putBytes8(sessionId.getId());
        cipherSuites.send(s);
        s.putBytes8(compression_methods);
        extensions.send(s);
    }

    void print(PrintStream s) throws IOException {
        s.println("*** ClientHello, " + protocolVersion);

        if (debug != null && Debug.isOn("verbose")) {
            s.print   ("RandomCookie:  "); clnt_random.print(s);

            s.print("Session ID:  ");
            s.println(sessionId);

            s.println("Cipher Suites: " + cipherSuites);

            Debug.println(s, "Compression Methods", compression_methods);
            extensions.print(s);
            s.println("***");
        }
    }
}

/*
 * ServerHello ... SERVER --> CLIENT
 *
 * Server chooses protocol options from among those it supports and the
 * client supports.  Then it sends the basic session descriptive parameters
 * back to the client.
 */
static final
class ServerHello extends HandshakeMessage
{
    int messageType() { return ht_server_hello; }

    ProtocolVersion     protocolVersion;
    RandomCookie        svr_random;
    SessionId           sessionId;
    CipherSuite         cipherSuite;
    byte                compression_method;
    HelloExtensions extensions = new HelloExtensions();
    int extensionLength;

    ServerHello() {
        // empty
    }

    ServerHello(HandshakeInStream input, int messageLength) throws IOException {
        protocolVersion = ProtocolVersion.valueOf(input.getInt8(),
                                                  input.getInt8());
        svr_random = new RandomCookie(input);
        sessionId = new SessionId(input.getBytes8());
        cipherSuite = CipherSuite.valueOf(input.getInt8(), input.getInt8());
        compression_method = (byte)input.getInt8();
        if (messageLength() != messageLength) {
            extensions = new HelloExtensions(input);
        }
    }

    int messageLength()
    {
        // almost fixed size, except session ID and extensions:
        //      major + minor = 2
        //      random = 32
        //      session ID len field = 1
        //      cipher suite + compression = 3
        //      extensions: if present, 2 + length of extensions
        return 38 + sessionId.length() + extensions.length();
    }

    void send(HandshakeOutStream s) throws IOException
    {
        s.putInt8(protocolVersion.major);
        s.putInt8(protocolVersion.minor);
        svr_random.send(s);
        s.putBytes8(sessionId.getId());
        s.putInt8(cipherSuite.id >> 8);
        s.putInt8(cipherSuite.id & 0xff);
        s.putInt8(compression_method);
        extensions.send(s);
    }

    void print(PrintStream s) throws IOException
    {
        s.println("*** ServerHello, " + protocolVersion);

        if (debug != null && Debug.isOn("verbose")) {
            s.print   ("RandomCookie:  "); svr_random.print(s);

            int i;

            s.print("Session ID:  ");
            s.println(sessionId);

            s.println("Cipher Suite: " + cipherSuite);
            s.println("Compression Method: " + compression_method);
            extensions.print(s);
            s.println("***");
        }
    }
}


/*
 * CertificateMsg ... send by both CLIENT and SERVER
 *
 * Each end of a connection may need to pass its certificate chain to
 * the other end.  Such chains are intended to validate an identity with
 * reference to some certifying authority.  Examples include companies
 * like Verisign, or financial institutions.  There's some control over
 * the certifying authorities which are sent.
 *
 * NOTE: that these messages might be huge, taking many handshake records.
 * Up to 2^48 bytes of certificate may be sent, in records of at most 2^14
 * bytes each ... up to 2^32 records sent on the output stream.
 */
static final
class CertificateMsg extends HandshakeMessage
{
    int messageType() { return ht_certificate; }

    private X509Certificate[] chain;

    private List<byte[]> encodedChain;

    private int messageLength;

    CertificateMsg(X509Certificate[] certs) {
        chain = certs;
    }

    CertificateMsg(HandshakeInStream input) throws IOException {
        int chainLen = input.getInt24();
        List<Certificate> v = new ArrayList<Certificate>(4);

        CertificateFactory cf = null;
        while (chainLen > 0) {
            byte[] cert = input.getBytes24();
            chainLen -= (3 + cert.length);
            try {
                if (cf == null) {
                    cf = CertificateFactory.getInstance("X.509");
                }
                v.add(cf.generateCertificate(new ByteArrayInputStream(cert)));
            } catch (CertificateException e) {
                throw (SSLProtocolException)new SSLProtocolException
                        (e.getMessage()).initCause(e);
            }
        }

        chain = v.toArray(new X509Certificate[v.size()]);
    }

    int messageLength() {
        if (encodedChain == null) {
            messageLength = 3;
            encodedChain = new ArrayList<byte[]>(chain.length);
            try {
                for (X509Certificate cert : chain) {
                    byte[] b = cert.getEncoded();
                    encodedChain.add(b);
                    messageLength += b.length + 3;
                }
            } catch (CertificateEncodingException e) {
                encodedChain = null;
                throw new RuntimeException("Could not encode certificates", e);
            }
        }
        return messageLength;
    }

    void send(HandshakeOutStream s) throws IOException {
        s.putInt24(messageLength() - 3);
        for (byte[] b : encodedChain) {
            s.putBytes24(b);
        }
    }

    void print(PrintStream s) throws IOException {
        s.println("*** Certificate chain");

        if (debug != null && Debug.isOn("verbose")) {
            for (int i = 0; i < chain.length; i++)
                s.println("chain [" + i + "] = " + chain[i]);
            s.println("***");
        }
    }

    X509Certificate[] getCertificateChain() {
        return chain;
    }
}


/*
 * ServerKeyExchange ... SERVER --> CLIENT
 *
 * The cipher suite selected, when combined with the certificate exchanged,
 * implies one of several different kinds of key exchange.  Most current
 * cipher suites require the server to send more than its certificate.
 *
 * The primary exceptions are when a server sends an encryption-capable
 * RSA public key in its cert, to be used with RSA (or RSA_export) key
 * exchange; and when a server sends its Diffie-Hellman cert.  Those kinds
 * of key exchange do not require a ServerKeyExchange message.
 *
 * Key exchange can be viewed as having three modes, which are explicit
 * for the Diffie-Hellman flavors and poorly specified for RSA ones:
 *
 *      - "Ephemeral" keys.  Here, a "temporary" key is allocated by the
 *        server, and signed.  Diffie-Hellman keys signed using RSA or
 *        DSS are ephemeral (DHE flavor).  RSA keys get used to do the same
 *        thing, to cut the key size down to 512 bits (export restrictions)
 *        or for signing-only RSA certificates.
 *
 *      - Anonymity.  Here no server certificate is sent, only the public
 *        key of the server.  This case is subject to man-in-the-middle
 *        attacks.  This can be done with Diffie-Hellman keys (DH_anon) or
 *        with RSA keys, but is only used in SSLv3 for DH_anon.
 *
 *      - "Normal" case.  Here a server certificate is sent, and the public
 *        key there is used directly in exchanging the premaster secret.
 *        For example, Diffie-Hellman "DH" flavor, and any RSA flavor with
 *        only 512 bit keys.
 *
 * If a server certificate is sent, there is no anonymity.  However,
 * when a certificate is sent, ephemeral keys may still be used to
 * exchange the premaster secret.  That's how RSA_EXPORT often works,
 * as well as how the DHE_* flavors work.
 */
static abstract class ServerKeyExchange extends HandshakeMessage
{
    int messageType() { return ht_server_key_exchange; }
}


/*
 * Using RSA for Key Exchange:  exchange a session key that's not as big
 * as the signing-only key.  Used for export applications, since exported
 * RSA encryption keys can't be bigger than 512 bytes.
 *
 * This is never used when keys are 512 bits or smaller, and isn't used
 * on "US Domestic" ciphers in any case.
 */
static final
class RSA_ServerKeyExchange extends ServerKeyExchange
{
    private byte rsa_modulus[];     // 1 to 2^16 - 1 bytes
    private byte rsa_exponent[];    // 1 to 2^16 - 1 bytes

    private Signature signature;
    private byte[] signatureBytes;

    /*
     * Hash the nonces and the ephemeral RSA public key.
     */
    private void updateSignature(byte clntNonce[], byte svrNonce[])
            throws SignatureException {
        int tmp;

        signature.update(clntNonce);
        signature.update(svrNonce);

        tmp = rsa_modulus.length;
        signature.update((byte)(tmp >> 8));
        signature.update((byte)(tmp & 0x0ff));
        signature.update(rsa_modulus);

        tmp = rsa_exponent.length;
        signature.update((byte)(tmp >> 8));
        signature.update((byte)(tmp & 0x0ff));
        signature.update(rsa_exponent);
    }


    /*
     * Construct an RSA server key exchange message, using data
     * known _only_ to the server.
     *
     * The client knows the public key corresponding to this private
     * key, from the Certificate message sent previously.  To comply
     * with US export regulations we use short RSA keys ... either
     * long term ones in the server's X509 cert, or else ephemeral
     * ones sent using this message.
     */
    RSA_ServerKeyExchange(PublicKey ephemeralKey, PrivateKey privateKey,
            RandomCookie clntNonce, RandomCookie svrNonce, SecureRandom sr)
            throws GeneralSecurityException {
        RSAPublicKeySpec rsaKey = JsseJce.getRSAPublicKeySpec(ephemeralKey);
        rsa_modulus = toByteArray(rsaKey.getModulus());
        rsa_exponent = toByteArray(rsaKey.getPublicExponent());
        signature = RSASignature.getInstance();
        signature.initSign(privateKey, sr);
        updateSignature(clntNonce.random_bytes, svrNonce.random_bytes);
        signatureBytes = signature.sign();
    }


    /*
     * Parse an RSA server key exchange message, using data known
     * to the client (and, in some situations, eavesdroppers).
     */
    RSA_ServerKeyExchange(HandshakeInStream input)
            throws IOException, NoSuchAlgorithmException {
        signature = RSASignature.getInstance();
        rsa_modulus = input.getBytes16();
        rsa_exponent = input.getBytes16();
        signatureBytes = input.getBytes16();
    }

    /*
     * Get the ephemeral RSA public key that will be used in this
     * SSL connection.
     */
    PublicKey getPublicKey() {
        try {
            KeyFactory kfac = JsseJce.getKeyFactory("RSA");
            // modulus and exponent are always positive
            RSAPublicKeySpec kspec = new RSAPublicKeySpec
                                         (new BigInteger(1, rsa_modulus),
                                          new BigInteger(1, rsa_exponent));
            return kfac.generatePublic(kspec);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /*
     * Verify the signed temporary key using the hashes computed
     * from it and the two nonces.  This is called by clients
     * with "exportable" RSA flavors.
     */
    boolean verify(PublicKey certifiedKey, RandomCookie clntNonce,
            RandomCookie svrNonce) throws GeneralSecurityException {
        signature.initVerify(certifiedKey);
        updateSignature(clntNonce.random_bytes, svrNonce.random_bytes);
        return signature.verify(signatureBytes);
    }

    int messageLength() {
        return 6 + rsa_modulus.length + rsa_exponent.length
               + signatureBytes.length;
    }

    void send(HandshakeOutStream s) throws IOException {
        s.putBytes16(rsa_modulus);
        s.putBytes16(rsa_exponent);
        s.putBytes16(signatureBytes);
    }

    void print(PrintStream s) throws IOException {
        s.println("*** RSA ServerKeyExchange");

        if (debug != null && Debug.isOn("verbose")) {
            Debug.println(s, "RSA Modulus", rsa_modulus);
            Debug.println(s, "RSA Public Exponent", rsa_exponent);
        }
    }
}


/*
 * Using Diffie-Hellman algorithm for key exchange.  All we really need to
 * do is securely get Diffie-Hellman keys (using the same P, G parameters)
 * to our peer, then we automatically have a shared secret without need
 * to exchange any more data.  (D-H only solutions, such as SKIP, could
 * eliminate key exchange negotiations and get faster connection setup.
 * But they still need a signature algorithm like DSS/DSA to support the
 * trusted distribution of keys without relying on unscalable physical
 * key distribution systems.)
 *
 * This class supports several DH-based key exchange algorithms, though
 * perhaps eventually each deserves its own class.  Notably, this has
 * basic support for DH_anon and its DHE_DSS and DHE_RSA signed variants.
 */
static final
class DH_ServerKeyExchange extends ServerKeyExchange
{
    // Fix message encoding, see 4348279
    private final static boolean dhKeyExchangeFix =
        Debug.getBooleanProperty("com.sun.net.ssl.dhKeyExchangeFix", true);

    private byte                dh_p [];        // 1 to 2^16 - 1 bytes
    private byte                dh_g [];        // 1 to 2^16 - 1 bytes
    private byte                dh_Ys [];       // 1 to 2^16 - 1 bytes

    private byte                signature [];

    /* Return the Diffie-Hellman modulus */
    BigInteger getModulus() {
        return new BigInteger(1, dh_p);
    }

    /* Return the Diffie-Hellman base/generator */
    BigInteger getBase() {
        return new BigInteger(1, dh_g);
    }

    /* Return the server's Diffie-Hellman public key */
    BigInteger getServerPublicKey() {
        return new BigInteger(1, dh_Ys);
    }

    /*
     * Update sig with nonces and Diffie-Hellman public key.
     */
    private void updateSignature(Signature sig, byte clntNonce[],
            byte svrNonce[]) throws SignatureException {
        int tmp;

        sig.update(clntNonce);
        sig.update(svrNonce);

        tmp = dh_p.length;
        sig.update((byte)(tmp >> 8));
        sig.update((byte)(tmp & 0x0ff));
        sig.update(dh_p);

        tmp = dh_g.length;
        sig.update((byte)(tmp >> 8));
        sig.update((byte)(tmp & 0x0ff));
        sig.update(dh_g);

        tmp = dh_Ys.length;
        sig.update((byte)(tmp >> 8));
        sig.update((byte)(tmp & 0x0ff));
        sig.update(dh_Ys);
    }

    /*
     * Construct from initialized DH key object, for DH_anon
     * key exchange.
     */
    DH_ServerKeyExchange(DHCrypt obj) {
        getValues(obj);
        signature = null;
    }

    /*
     * Construct from initialized DH key object and the key associated
     * with the cert chain which was sent ... for DHE_DSS and DHE_RSA
     * key exchange.  (Constructor called by server.)
     */
    DH_ServerKeyExchange(DHCrypt obj, PrivateKey key, byte clntNonce[],
            byte svrNonce[], SecureRandom sr) throws GeneralSecurityException {

        getValues(obj);

        Signature sig;
        if (key.getAlgorithm().equals("DSA")) {
            sig = JsseJce.getSignature(JsseJce.SIGNATURE_DSA);
        } else {
            sig = RSASignature.getInstance();
        }
        sig.initSign(key, sr);
        updateSignature(sig, clntNonce, svrNonce);
        signature = sig.sign();
    }

    private void getValues(DHCrypt obj) {
        dh_p = toByteArray(obj.getModulus());
        dh_g = toByteArray(obj.getBase());
        dh_Ys = toByteArray(obj.getPublicKey());
    }

    /*
     * Construct a DH_ServerKeyExchange message from an input
     * stream, as if sent from server to client for use with
     * DH_anon key exchange
     */
    DH_ServerKeyExchange(HandshakeInStream input) throws IOException {
        dh_p = input.getBytes16();
        dh_g = input.getBytes16();
        dh_Ys = input.getBytes16();
        signature = null;
    }

    /*
     * Construct a DH_ServerKeyExchange message from an input stream
     * and a certificate, as if sent from server to client for use with
     * DHE_DSS or DHE_RSA key exchange.  (Called by client.)
     */
    DH_ServerKeyExchange(HandshakeInStream input, PublicKey publicKey,
            byte clntNonce[], byte svrNonce[], int messageSize)
            throws IOException, GeneralSecurityException {

        dh_p = input.getBytes16();
        dh_g = input.getBytes16();
        dh_Ys = input.getBytes16();

        byte signature[];
        if (dhKeyExchangeFix) {
            signature = input.getBytes16();
        } else {
            messageSize -= (dh_p.length + 2);
            messageSize -= (dh_g.length + 2);
            messageSize -= (dh_Ys.length + 2);

            signature = new byte[messageSize];
            input.read(signature);
        }

        Signature sig;
        String algorithm = publicKey.getAlgorithm();
        if (algorithm.equals("DSA")) {
            sig = JsseJce.getSignature(JsseJce.SIGNATURE_DSA);
        } else if (algorithm.equals("RSA")) {
            sig = RSASignature.getInstance();
        } else {
            throw new SSLKeyException("neither an RSA or a DSA key");
        }

        sig.initVerify(publicKey);
        updateSignature(sig, clntNonce, svrNonce);

        if (sig.verify(signature) == false ) {
            throw new SSLKeyException("Server D-H key verification failed");
        }
    }

    int messageLength() {
        int temp = 6;   // overhead for p, g, y(s) values.

        temp += dh_p.length;
        temp += dh_g.length;
        temp += dh_Ys.length;
        if (signature != null) {
            temp += signature.length;
            if (dhKeyExchangeFix) {
                temp += 2;
            }
        }
        return temp;
    }

    void send(HandshakeOutStream s) throws IOException {
        s.putBytes16(dh_p);
        s.putBytes16(dh_g);
        s.putBytes16(dh_Ys);
        if (signature != null) {
            if (dhKeyExchangeFix) {
                s.putBytes16(signature);
            } else {
                s.write(signature);
            }
        }
    }

    void print(PrintStream s) throws IOException {
        s.println("*** Diffie-Hellman ServerKeyExchange");

        if (debug != null && Debug.isOn("verbose")) {
            Debug.println(s, "DH Modulus", dh_p);
            Debug.println(s, "DH Base", dh_g);
            Debug.println(s, "Server DH Public Key", dh_Ys);

            if (signature == null) {
                s.println("Anonymous");
            } else {
                s.println("Signed with a DSA or RSA public key");
            }
        }
    }
}

/*
 * ECDH server key exchange message. Sent by the server for ECDHE and ECDH_anon
 * ciphersuites to communicate its ephemeral public key (including the
 * EC domain parameters).
 *
 * We support named curves only, no explicitly encoded curves.
 */
static final
class ECDH_ServerKeyExchange extends ServerKeyExchange
{

    // constants for ECCurveType
    private final static int CURVE_EXPLICIT_PRIME = 1;
    private final static int CURVE_EXPLICIT_CHAR2 = 2;
    private final static int CURVE_NAMED_CURVE    = 3;

    // id of the curve we are using
    private int curveId;
    // encoded public point
    private byte[] pointBytes;

    // signature bytes (or null if anonymous)
    private byte[] signatureBytes;

    // public key object encapsulated in this message
    private ECPublicKey publicKey;

    ECDH_ServerKeyExchange(ECDHCrypt obj, PrivateKey privateKey,
            byte[] clntNonce, byte[] svrNonce, SecureRandom sr)
            throws GeneralSecurityException {
        publicKey = (ECPublicKey)obj.getPublicKey();
        ECParameterSpec params = publicKey.getParams();
        ECPoint point = publicKey.getW();
        pointBytes = JsseJce.encodePoint(point, params.getCurve());
        curveId = SupportedEllipticCurvesExtension.getCurveIndex(params);

        if (privateKey == null) {
            // ECDH_anon
            return;
        }

        Signature sig = getSignature(privateKey.getAlgorithm());
        sig.initSign(privateKey);

        updateSignature(sig, clntNonce, svrNonce);
        signatureBytes = sig.sign();
    }

    /*
     * Parse an ECDH server key exchange message.
     */
    ECDH_ServerKeyExchange(HandshakeInStream input, PublicKey signingKey,
            byte[] clntNonce, byte[] svrNonce)
            throws IOException, GeneralSecurityException {
        int curveType = input.getInt8();
        ECParameterSpec parameters;
        // These parsing errors should never occur as we negotiated
        // the supported curves during the exchange of the Hello messages.
        if (curveType == CURVE_NAMED_CURVE) {
            curveId = input.getInt16();
            if (SupportedEllipticCurvesExtension.isSupported(curveId) == false) {
                throw new SSLHandshakeException("Unsupported curveId: " + curveId);
            }
            String curveOid = SupportedEllipticCurvesExtension.getCurveOid(curveId);
            if (curveOid == null) {
                throw new SSLHandshakeException("Unknown named curve: " + curveId);
            }
            parameters = JsseJce.getECParameterSpec(curveOid);
            if (parameters == null) {
                throw new SSLHandshakeException("Unsupported curve: " + curveOid);
            }
        } else {
            throw new SSLHandshakeException("Unsupported ECCurveType: " + curveType);
        }
        pointBytes = input.getBytes8();

        ECPoint point = JsseJce.decodePoint(pointBytes, parameters.getCurve());
        KeyFactory factory = JsseJce.getKeyFactory("EC");
        publicKey = (ECPublicKey)factory.generatePublic(new ECPublicKeySpec(point, parameters));

        if (signingKey == null) {
            // ECDH_anon
            return;
        }

        // verify the signature
        signatureBytes = input.getBytes16();
        Signature sig = getSignature(signingKey.getAlgorithm());
        sig.initVerify(signingKey);

        updateSignature(sig, clntNonce, svrNonce);

        if (sig.verify(signatureBytes) == false ) {
            throw new SSLKeyException
                ("Invalid signature on ECDH server key exchange message");
        }
    }

    /*
     * Get the ephemeral EC public key encapsulated in this message.
     */
    ECPublicKey getPublicKey() {
        return publicKey;
    }

    private static Signature getSignature(String keyAlgorithm) throws NoSuchAlgorithmException {
        if (keyAlgorithm.equals("EC")) {
            return JsseJce.getSignature(JsseJce.SIGNATURE_ECDSA);
        } else if (keyAlgorithm.equals("RSA")) {
            return RSASignature.getInstance();
        } else {
            throw new NoSuchAlgorithmException("neither an RSA or a EC key");
        }
    }

    private void updateSignature(Signature sig, byte clntNonce[],
            byte svrNonce[]) throws SignatureException {
        sig.update(clntNonce);
        sig.update(svrNonce);

        sig.update((byte)CURVE_NAMED_CURVE);
        sig.update((byte)(curveId >> 8));
        sig.update((byte)curveId);
        sig.update((byte)pointBytes.length);
        sig.update(pointBytes);
    }

    int messageLength() {
        int sigLen = (signatureBytes == null) ? 0 : 2 + signatureBytes.length;
        return 4 + pointBytes.length + sigLen;
    }

    void send(HandshakeOutStream s) throws IOException {
        s.putInt8(CURVE_NAMED_CURVE);
        s.putInt16(curveId);
        s.putBytes8(pointBytes);
        if (signatureBytes != null) {
            s.putBytes16(signatureBytes);
        }
    }

    void print(PrintStream s) throws IOException {
        s.println("*** ECDH ServerKeyExchange");

        if (debug != null && Debug.isOn("verbose")) {
            s.println("Server key: " + publicKey);
        }
    }
}

static final class DistinguishedName {

    /*
     * DER encoded distinguished name.
     * TLS requires that its not longer than 65535 bytes.
     */
    byte name[];

    DistinguishedName(HandshakeInStream input) throws IOException {
        name = input.getBytes16();
    }

    DistinguishedName(X500Principal dn) {
        name = dn.getEncoded();
    }

    X500Principal getX500Principal() throws IOException {
        try {
            return new X500Principal(name);
        } catch (IllegalArgumentException e) {
            throw (SSLProtocolException)new SSLProtocolException
                    (e.getMessage()).initCause(e);
        }
    }

    int length() {
        return 2 + name.length;
    }

    void send(HandshakeOutStream output) throws IOException {
        output.putBytes16(name);
    }

    void print(PrintStream output) throws IOException {
        X500Principal principal = new X500Principal(name);
        output.println("<" + principal.toString() + ">");
    }
}

/*
 * CertificateRequest ... SERVER --> CLIENT
 *
 * Authenticated servers may ask clients to authenticate themselves
 * in turn, using this message.
 */
static final
class CertificateRequest extends HandshakeMessage
{
    int messageType() { return ht_certificate_request; }

    // enum ClientCertificateType
    static final int   cct_rsa_sign = 1;
    static final int   cct_dss_sign = 2;
    static final int   cct_rsa_fixed_dh = 3;
    static final int   cct_dss_fixed_dh = 4;

    // The existance of these two values is a bug in the SSL specification.
    // They are never used in the protocol.
    static final int   cct_rsa_ephemeral_dh = 5;
    static final int   cct_dss_ephemeral_dh = 6;

    // From RFC 4492 (ECC)
    static final int    cct_ecdsa_sign       = 64;
    static final int    cct_rsa_fixed_ecdh   = 65;
    static final int    cct_ecdsa_fixed_ecdh = 66;

    private final static byte[] TYPES_NO_ECC = { cct_rsa_sign, cct_dss_sign };
    private final static byte[] TYPES_ECC =
        { cct_rsa_sign, cct_dss_sign, cct_ecdsa_sign };

    byte                types [];               // 1 to 255 types
    DistinguishedName   authorities [];         // 3 to 2^16 - 1
        // ... "3" because that's the smallest DER-encoded X500 DN

    CertificateRequest(X509Certificate ca[], KeyExchange keyExchange)
            throws IOException {
        // always use X500Principal
        authorities = new DistinguishedName[ca.length];
        for (int i = 0; i < ca.length; i++) {
            X500Principal x500Principal = ca[i].getSubjectX500Principal();
            authorities[i] = new DistinguishedName(x500Principal);
        }
        // we support RSA, DSS, and ECDSA client authentication and they
        // can be used with all ciphersuites. If this changes, the code
        // needs to be adapted to take keyExchange into account.
        // We only request ECDSA client auth if we have ECC crypto available.
        this.types = JsseJce.isEcAvailable() ? TYPES_ECC : TYPES_NO_ECC;
    }

    CertificateRequest(HandshakeInStream input) throws IOException {
        types = input.getBytes8();
        int len = input.getInt16();
        ArrayList<DistinguishedName> v = new ArrayList<DistinguishedName>();
        while (len >= 3) {
            DistinguishedName dn = new DistinguishedName(input);
            v.add(dn);
            len -= dn.length();
        }

        if (len != 0) {
            throw new SSLProtocolException("Bad CertificateRequest DN length");
        }

        authorities = v.toArray(new DistinguishedName[v.size()]);
    }

    X500Principal[] getAuthorities() throws IOException {
        X500Principal[] ret = new X500Principal[authorities.length];
        for (int i = 0; i < authorities.length; i++) {
            ret[i] = authorities[i].getX500Principal();
        }
        return ret;
    }

    int messageLength()
    {
        int len;

        len = 1 + types.length + 2;
        for (int i = 0; i < authorities.length; i++)
            len += authorities[i].length();
        return len;
    }

    void send(HandshakeOutStream output) throws IOException
    {
        int     len = 0;

        for (int i = 0; i < authorities.length; i++)
            len += authorities[i].length();

        output.putBytes8(types);
        output.putInt16(len);
        for (int i = 0; i < authorities.length; i++)
            authorities[i].send(output);
    }

    void print(PrintStream s) throws IOException
    {
        s.println("*** CertificateRequest");

        if (debug != null && Debug.isOn("verbose")) {
            s.print("Cert Types: ");
            for (int i = 0; i < types.length; i++) {
                switch (types[i]) {
                  case cct_rsa_sign:
                    s.print("RSA"); break;
                  case cct_dss_sign:
                    s.print("DSS"); break;
                  case cct_rsa_fixed_dh:
                    s.print("Fixed DH (RSA sig)"); break;
                  case cct_dss_fixed_dh:
                    s.print("Fixed DH (DSS sig)"); break;
                  case cct_rsa_ephemeral_dh:
                    s.print("Ephemeral DH (RSA sig)"); break;
                  case cct_dss_ephemeral_dh:
                    s.print("Ephemeral DH (DSS sig)"); break;
                  case cct_ecdsa_sign:
                    s.print("ECDSA"); break;
                  case cct_rsa_fixed_ecdh:
                    s.print("Fixed ECDH (RSA sig)"); break;
                  case cct_ecdsa_fixed_ecdh:
                    s.print("Fixed ECDH (ECDSA sig)"); break;
                  default:
                    s.print("Type-" + (types[i] & 0xff)); break;
                }
                if (i != types.length - 1) {
                    s.print(", ");
                }
            }
            s.println();

            s.println("Cert Authorities:");
            for (int i = 0; i < authorities.length; i++)
                authorities[i].print(s);
        }
    }
}


/*
 * ServerHelloDone ... SERVER --> CLIENT
 *
 * When server's done sending its messages in response to the client's
 * "hello" (e.g. its own hello, certificate, key exchange message, perhaps
 * client certificate request) it sends this message to flag that it's
 * done that part of the handshake.
 */
static final
class ServerHelloDone extends HandshakeMessage
{
    int messageType() { return ht_server_hello_done; }

    ServerHelloDone() { }

    ServerHelloDone(HandshakeInStream input)
    {
        // nothing to do
    }

    int messageLength()
    {
        return 0;
    }

    void send(HandshakeOutStream s) throws IOException
    {
        // nothing to send
    }

    void print(PrintStream s) throws IOException
    {
        s.println("*** ServerHelloDone");
    }
}


/*
 * CertificateVerify ... CLIENT --> SERVER
 *
 * Sent after client sends signature-capable certificates (e.g. not
 * Diffie-Hellman) to verify.
 */
static final class CertificateVerify extends HandshakeMessage {

    int messageType() { return ht_certificate_verify; }

    private byte[] signature;

    /*
     * Create an RSA or DSA signed certificate verify message.
     */
    CertificateVerify(ProtocolVersion protocolVersion, HandshakeHash
            handshakeHash, PrivateKey privateKey, SecretKey masterSecret,
            SecureRandom sr) throws GeneralSecurityException {
        String algorithm = privateKey.getAlgorithm();
        Signature sig = getSignature(protocolVersion, algorithm);
        sig.initSign(privateKey, sr);
        updateSignature(sig, protocolVersion, handshakeHash, algorithm,
                        masterSecret);
        signature = sig.sign();
    }

    //
    // Unmarshal the signed data from the input stream.
    //
    CertificateVerify(HandshakeInStream input) throws IOException  {
        signature = input.getBytes16();
    }

    /*
     * Verify a certificate verify message. Return the result of verification,
     * if there is a problem throw a GeneralSecurityException.
     */
    boolean verify(ProtocolVersion protocolVersion,
            HandshakeHash handshakeHash, PublicKey publicKey,
            SecretKey masterSecret) throws GeneralSecurityException {
        String algorithm = publicKey.getAlgorithm();
        Signature sig = getSignature(protocolVersion, algorithm);
        sig.initVerify(publicKey);
        updateSignature(sig, protocolVersion, handshakeHash, algorithm,
                        masterSecret);
        return sig.verify(signature);
    }

    /*
     * Get the Signature object appropriate for verification using the
     * given signature algorithm and protocol version.
     */
    private static Signature getSignature(ProtocolVersion protocolVersion,
            String algorithm) throws GeneralSecurityException {
        if (algorithm.equals("RSA")) {
            return RSASignature.getInternalInstance();
        } else if (algorithm.equals("DSA")) {
            return JsseJce.getSignature(JsseJce.SIGNATURE_RAWDSA);
        } else if (algorithm.equals("EC")) {
            return JsseJce.getSignature(JsseJce.SIGNATURE_RAWECDSA);
        } else {
            throw new SignatureException("Unrecognized algorithm: "
                + algorithm);
        }
    }

    /*
     * Update the Signature with the data appropriate for the given
     * signature algorithm and protocol version so that the object is
     * ready for signing or verifying.
     */
    private static void updateSignature(Signature sig,
            ProtocolVersion protocolVersion,
            HandshakeHash handshakeHash, String algorithm, SecretKey masterKey)
            throws SignatureException {
        MessageDigest md5Clone = handshakeHash.getMD5Clone();
        MessageDigest shaClone = handshakeHash.getSHAClone();
        boolean tls = protocolVersion.v >= ProtocolVersion.TLS10.v;
        if (algorithm.equals("RSA")) {
            if (tls) {
                // nothing to do
            } else { // SSLv3
                updateDigest(md5Clone, MD5_pad1, MD5_pad2, masterKey);
                updateDigest(shaClone, SHA_pad1, SHA_pad2, masterKey);
            }
            // need to use these hashes directly
            RSASignature.setHashes(sig, md5Clone, shaClone);
        } else { // DSA, ECDSA
            if (tls) {
                // nothing to do
            } else { // SSLv3
                updateDigest(shaClone, SHA_pad1, SHA_pad2, masterKey);
            }
            sig.update(shaClone.digest());
        }
    }

    /*
     * Update the MessageDigest for SSLv3 certificate verify or finished
     * message calculation. The digest must already have been updated with
     * all preceding handshake messages.
     * Used by the Finished class as well.
     */
    static void updateDigest(MessageDigest md, byte[] pad1, byte[] pad2,
            SecretKey masterSecret) {
        // Digest the key bytes if available.
        // Otherwise (sensitive key), try digesting the key directly.
        // That is currently only implemented in SunPKCS11 using a private
        // reflection API, so we avoid that if possible.
        byte[] keyBytes = "RAW".equals(masterSecret.getFormat())
                        ? masterSecret.getEncoded() : null;
        if (keyBytes != null) {
            md.update(keyBytes);
        } else {
            digestKey(md, masterSecret);
        }
        md.update(pad1);
        byte[] temp = md.digest();

        if (keyBytes != null) {
            md.update(keyBytes);
        } else {
            digestKey(md, masterSecret);
        }
        md.update(pad2);
        md.update(temp);
    }

    private final static Class delegate;
    private final static Field spiField;

    static {
        try {
            delegate = Class.forName("java.security.MessageDigest$Delegate");
            spiField = delegate.getDeclaredField("digestSpi");
        } catch (Exception e) {
            throw new RuntimeException("Reflection failed", e);
        }
        makeAccessible(spiField);
    }

    private static void makeAccessible(final AccessibleObject o) {
        AccessController.doPrivileged(new PrivilegedAction<Object>() {
            public Object run() {
                o.setAccessible(true);
                return null;
            }
        });
    }

    // ConcurrentHashMap does not allow null values, use this marker object
    private final static Object NULL_OBJECT = new Object();

    // cache Method objects per Spi class
    // Note that this will prevent the Spi classes from being GC'd. We assume
    // that is not a problem.
    private final static Map<Class,Object> methodCache =
                                        new ConcurrentHashMap<Class,Object>();

    private static void digestKey(MessageDigest md, SecretKey key) {
        try {
            // Verify that md is implemented via MessageDigestSpi, not
            // via JDK 1.1 style MessageDigest subclassing.
            if (md.getClass() != delegate) {
                throw new Exception("Digest is not a MessageDigestSpi");
            }
            MessageDigestSpi spi = (MessageDigestSpi)spiField.get(md);
            Class<?> clazz = spi.getClass();
            Object r = methodCache.get(clazz);
            if (r == null) {
                try {
                    r = clazz.getDeclaredMethod("implUpdate", SecretKey.class);
                    makeAccessible((Method)r);
                } catch (NoSuchMethodException e) {
                    r = NULL_OBJECT;
                }
                methodCache.put(clazz, r);
            }
            if (r == NULL_OBJECT) {
                throw new Exception("Digest does not support implUpdate(SecretKey)");
            }
            Method update = (Method)r;
            update.invoke(spi, key);
        } catch (Exception e) {
            throw new RuntimeException
            ("Could not obtain encoded key and MessageDigest cannot digest key", e);
        }
    }

    int messageLength() {
        return 2 + signature.length;
    }

    void send(HandshakeOutStream s) throws IOException {
        s.putBytes16(signature);
    }

    void print(PrintStream s) throws IOException {
        s.println("*** CertificateVerify");
    }
}


/*
 * FINISHED ... sent by both CLIENT and SERVER
 *
 * This is the FINISHED message as defined in the SSL and TLS protocols.
 * Both protocols define this handshake message slightly differently.
 * This class supports both formats.
 *
 * When handshaking is finished, each side sends a "change_cipher_spec"
 * record, then immediately sends a "finished" handshake message prepared
 * according to the newly adopted cipher spec.
 *
 * NOTE that until this is sent, no application data may be passed, unless
 * some non-default cipher suite has already been set up on this connection
 * connection (e.g. a previous handshake arranged one).
 */
static final class Finished extends HandshakeMessage {

    int messageType() { return ht_finished; }

    // constant for a Finished message sent by the client
    final static int CLIENT = 1;

    // constant for a Finished message sent by the server
    final static int SERVER = 2;

    // enum Sender:  "CLNT" and "SRVR"
    private static final byte[] SSL_CLIENT = { 0x43, 0x4C, 0x4E, 0x54 };
    private static final byte[] SSL_SERVER = { 0x53, 0x52, 0x56, 0x52 };

    /*
     * Contents of the finished message ("checksum"). For TLS, it
     * is 12 bytes long, for SSLv3 36 bytes.
     */
    private byte[] verifyData;

    /*
     * Create a finished message to send to the remote peer.
     */
    Finished(ProtocolVersion protocolVersion, HandshakeHash handshakeHash,
            int sender, SecretKey master) {
        verifyData = getFinished(protocolVersion, handshakeHash, sender,
                                 master);
    }

    /*
     * Constructor that reads FINISHED message from stream.
     */
    Finished(ProtocolVersion protocolVersion, HandshakeInStream input)
            throws IOException {
        int msgLen = (protocolVersion.v >= ProtocolVersion.TLS10.v) ? 12 : 36;
        verifyData = new byte[msgLen];
        input.read(verifyData);
    }

    /*
     * Verify that the hashes here are what would have been produced
     * according to a given set of inputs.  This is used to ensure that
     * both client and server are fully in sync, and that the handshake
     * computations have been successful.
     */
     boolean verify(ProtocolVersion protocolVersion,
             HandshakeHash handshakeHash, int sender, SecretKey master) {
        byte[] myFinished = getFinished(protocolVersion, handshakeHash,
                                        sender, master);
        return Arrays.equals(myFinished, verifyData);
    }

    /*
     * Perform the actual finished message calculation.
     */
    private static byte[] getFinished(ProtocolVersion protocolVersion,
            HandshakeHash handshakeHash, int sender, SecretKey masterKey) {
        byte[] sslLabel;
        String tlsLabel;
        if (sender == CLIENT) {
            sslLabel = SSL_CLIENT;
            tlsLabel = "client finished";
        } else if (sender == SERVER) {
            sslLabel = SSL_SERVER;
            tlsLabel = "server finished";
        } else {
            throw new RuntimeException("Invalid sender: " + sender);
        }
        MessageDigest md5Clone = handshakeHash.getMD5Clone();
        MessageDigest shaClone = handshakeHash.getSHAClone();
        if (protocolVersion.v >= ProtocolVersion.TLS10.v) {
            // TLS
            try {
                byte[] seed = new byte[36];
                md5Clone.digest(seed, 0, 16);
                shaClone.digest(seed, 16, 20);

                TlsPrfParameterSpec spec = new TlsPrfParameterSpec
                                                (masterKey, tlsLabel, seed, 12);
                KeyGenerator prf = JsseJce.getKeyGenerator("SunTlsPrf");
                prf.init(spec);
                SecretKey prfKey = prf.generateKey();
                if ("RAW".equals(prfKey.getFormat()) == false) {
                    throw new ProviderException
                        ("Invalid PRF output, format must be RAW");
                }
                byte[] finished = prfKey.getEncoded();
                return finished;
            } catch (GeneralSecurityException e) {
                throw new RuntimeException("PRF failed", e);
            }
        } else {
            // SSLv3
            updateDigest(md5Clone, sslLabel, MD5_pad1, MD5_pad2, masterKey);
            updateDigest(shaClone, sslLabel, SHA_pad1, SHA_pad2, masterKey);
            byte[] finished = new byte[36];
            try {
                md5Clone.digest(finished, 0, 16);
                shaClone.digest(finished, 16, 20);
            } catch (DigestException e) {
                // cannot occur
                throw new RuntimeException("Digest failed", e);
            }
            return finished;
        }
    }

    /*
     * Update the MessageDigest for SSLv3 finished message calculation.
     * The digest must already have been updated with all preceding handshake
     * messages. This operation is almost identical to the certificate verify
     * hash, reuse that code.
     */
    private static void updateDigest(MessageDigest md, byte[] sender,
            byte[] pad1, byte[] pad2, SecretKey masterSecret) {
        md.update(sender);
        CertificateVerify.updateDigest(md, pad1, pad2, masterSecret);
    }

    int messageLength() {
        return verifyData.length;
    }

    void send(HandshakeOutStream out) throws IOException {
        out.write(verifyData);
    }

    void print(PrintStream s) throws IOException {
        s.println("*** Finished");
        if (debug != null && Debug.isOn("verbose")) {
            Debug.println(s, "verify_data", verifyData);
            s.println("***");
        }
    }

}

//
// END of nested classes
//

}
