jdk/src/share/classes/sun/security/ssl/HandshakeMessage.java
changeset 7043 5e2d1edeb2c7
parent 7039 6464c8e62a18
child 7990 57019dc81b66
--- a/jdk/src/share/classes/sun/security/ssl/HandshakeMessage.java	Mon Nov 01 10:59:03 2010 -0700
+++ b/jdk/src/share/classes/sun/security/ssl/HandshakeMessage.java	Mon Nov 01 22:02:35 2010 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1996, 2010, Oracle and/or its affiliates. All rights reserved.
+ * copyright (c) 1996, 2010, 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
@@ -41,15 +41,12 @@
 
 import javax.crypto.KeyGenerator;
 import javax.crypto.SecretKey;
-import javax.crypto.spec.SecretKeySpec;
 
 import javax.net.ssl.*;
 
-import sun.security.action.GetPropertyAction;
-
 import sun.security.internal.spec.TlsPrfParameterSpec;
-
 import sun.security.ssl.CipherSuite.*;
+import static sun.security.ssl.CipherSuite.PRF.*;
 
 /**
  * Many data structures are involved in the handshake messages.  These
@@ -258,6 +255,27 @@
         extensions.add(renegotiationInfo);
     }
 
+    // add server_name extension
+    void addServerNameIndicationExtension(String hostname) {
+        // We would have checked that the hostname ia a FQDN.
+        ArrayList<String> hostnames = new ArrayList<String>(1);
+        hostnames.add(hostname);
+
+        try {
+            extensions.add(new ServerNameExtension(hostnames));
+        } catch (IOException ioe) {
+            // ignore the exception and return
+        }
+    }
+
+    // add signature_algorithm extension
+    void addSignatureAlgorithmsExtension(
+            Collection<SignatureAndHashAlgorithm> algorithms) {
+        HelloExtension signatureAlgorithm =
+                new SignatureAlgorithmsExtension(algorithms);
+        extensions.add(signatureAlgorithm);
+    }
+
     @Override
     int messageType() { return ht_client_hello; }
 
@@ -290,7 +308,8 @@
         s.println("*** ClientHello, " + protocolVersion);
 
         if (debug != null && Debug.isOn("verbose")) {
-            s.print   ("RandomCookie:  "); clnt_random.print(s);
+            s.print("RandomCookie:  ");
+            clnt_random.print(s);
 
             s.print("Session ID:  ");
             s.println(sessionId);
@@ -327,7 +346,8 @@
         // empty
     }
 
-    ServerHello(HandshakeInStream input, int messageLength) throws IOException {
+    ServerHello(HandshakeInStream input, int messageLength)
+            throws IOException {
         protocolVersion = ProtocolVersion.valueOf(input.getInt8(),
                                                   input.getInt8());
         svr_random = new RandomCookie(input);
@@ -367,7 +387,8 @@
         s.println("*** ServerHello, " + protocolVersion);
 
         if (debug != null && Debug.isOn("verbose")) {
-            s.print   ("RandomCookie:  "); svr_random.print(s);
+            s.print("RandomCookie:  ");
+            svr_random.print(s);
 
             int i;
 
@@ -425,8 +446,8 @@
                 }
                 v.add(cf.generateCertificate(new ByteArrayInputStream(cert)));
             } catch (CertificateException e) {
-                throw (SSLProtocolException)new SSLProtocolException
-                        (e.getMessage()).initCause(e);
+                throw (SSLProtocolException)new SSLProtocolException(
+                    e.getMessage()).initCause(e);
             }
         }
 
@@ -469,7 +490,7 @@
     }
 
     X509Certificate[] getCertificateChain() {
-        return chain;
+        return chain.clone();
     }
 }
 
@@ -597,9 +618,9 @@
         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));
+            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);
@@ -667,6 +688,12 @@
 
     private byte                signature [];
 
+    // protocol version being established using this ServerKeyExchange message
+    ProtocolVersion protocolVersion;
+
+    // the preferable signature algorithm used by this ServerKeyExchange message
+    private SignatureAndHashAlgorithm preferableSignatureAlgorithm;
+
     /* Return the Diffie-Hellman modulus */
     BigInteger getModulus() {
         return new BigInteger(1, dh_p);
@@ -712,8 +739,11 @@
      * Construct from initialized DH key object, for DH_anon
      * key exchange.
      */
-    DH_ServerKeyExchange(DHCrypt obj) {
-        getValues(obj);
+    DH_ServerKeyExchange(DHCrypt obj, ProtocolVersion protocolVersion) {
+        this.protocolVersion = protocolVersion;
+        this.preferableSignatureAlgorithm = null;
+
+        setValues(obj);
         signature = null;
     }
 
@@ -723,22 +753,33 @@
      * key exchange.  (Constructor called by server.)
      */
     DH_ServerKeyExchange(DHCrypt obj, PrivateKey key, byte clntNonce[],
-            byte svrNonce[], SecureRandom sr) throws GeneralSecurityException {
+            byte svrNonce[], SecureRandom sr,
+            SignatureAndHashAlgorithm signAlgorithm,
+            ProtocolVersion protocolVersion) throws GeneralSecurityException {
 
-        getValues(obj);
+        this.protocolVersion = protocolVersion;
+
+        setValues(obj);
 
         Signature sig;
-        if (key.getAlgorithm().equals("DSA")) {
-            sig = JsseJce.getSignature(JsseJce.SIGNATURE_DSA);
+        if (protocolVersion.v >= ProtocolVersion.TLS12.v) {
+            this.preferableSignatureAlgorithm = signAlgorithm;
+            sig = JsseJce.getSignature(signAlgorithm.getAlgorithmName());
         } else {
-            sig = RSASignature.getInstance();
+            this.preferableSignatureAlgorithm = null;
+            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) {
+    private void setValues(DHCrypt obj) {
         dh_p = toByteArray(obj.getModulus());
         dh_g = toByteArray(obj.getBase());
         dh_Ys = toByteArray(obj.getPublicKey());
@@ -749,7 +790,12 @@
      * stream, as if sent from server to client for use with
      * DH_anon key exchange
      */
-    DH_ServerKeyExchange(HandshakeInStream input) throws IOException {
+    DH_ServerKeyExchange(HandshakeInStream input,
+            ProtocolVersion protocolVersion) throws IOException {
+
+        this.protocolVersion = protocolVersion;
+        this.preferableSignatureAlgorithm = null;
+
         dh_p = input.getBytes16();
         dh_g = input.getBytes16();
         dh_Ys = input.getBytes16();
@@ -762,13 +808,38 @@
      * DHE_DSS or DHE_RSA key exchange.  (Called by client.)
      */
     DH_ServerKeyExchange(HandshakeInStream input, PublicKey publicKey,
-            byte clntNonce[], byte svrNonce[], int messageSize)
+            byte clntNonce[], byte svrNonce[], int messageSize,
+            Collection<SignatureAndHashAlgorithm> localSupportedSignAlgs,
+            ProtocolVersion protocolVersion)
             throws IOException, GeneralSecurityException {
 
+        this.protocolVersion = protocolVersion;
+
+        // read params: ServerDHParams
         dh_p = input.getBytes16();
         dh_g = input.getBytes16();
         dh_Ys = input.getBytes16();
 
+        // read the signature and hash algorithm
+        if (protocolVersion.v >= ProtocolVersion.TLS12.v) {
+            int hash = input.getInt8();         // hash algorithm
+            int signature = input.getInt8();    // signature algorithm
+
+            preferableSignatureAlgorithm =
+                SignatureAndHashAlgorithm.valueOf(hash, signature, 0);
+
+            // Is it a local supported signature algorithm?
+            if (!localSupportedSignAlgs.contains(
+                    preferableSignatureAlgorithm)) {
+                throw new SSLHandshakeException(
+                        "Unsupported SignatureAndHashAlgorithm in " +
+                        "ServerKeyExchange message");
+            }
+        } else {
+            this.preferableSignatureAlgorithm = null;
+        }
+
+        // read the signature
         byte signature[];
         if (dhKeyExchangeFix) {
             signature = input.getBytes16();
@@ -783,12 +854,17 @@
 
         Signature sig;
         String algorithm = publicKey.getAlgorithm();
-        if (algorithm.equals("DSA")) {
-            sig = JsseJce.getSignature(JsseJce.SIGNATURE_DSA);
-        } else if (algorithm.equals("RSA")) {
-            sig = RSASignature.getInstance();
+        if (protocolVersion.v >= ProtocolVersion.TLS12.v) {
+            sig = JsseJce.getSignature(
+                        preferableSignatureAlgorithm.getAlgorithmName());
         } else {
-            throw new SSLKeyException("neither an RSA or a DSA key");
+            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);
@@ -805,12 +881,18 @@
         temp += dh_p.length;
         temp += dh_g.length;
         temp += dh_Ys.length;
+
         if (signature != null) {
+            if (protocolVersion.v >= ProtocolVersion.TLS12.v) {
+                temp += SignatureAndHashAlgorithm.sizeInRecord();
+            }
+
             temp += signature.length;
             if (dhKeyExchangeFix) {
                 temp += 2;
             }
         }
+
         return temp;
     }
 
@@ -818,7 +900,13 @@
         s.putBytes16(dh_p);
         s.putBytes16(dh_g);
         s.putBytes16(dh_Ys);
+
         if (signature != null) {
+            if (protocolVersion.v >= ProtocolVersion.TLS12.v) {
+                s.putInt8(preferableSignatureAlgorithm.getHashValue());
+                s.putInt8(preferableSignatureAlgorithm.getSignatureValue());
+            }
+
             if (dhKeyExchangeFix) {
                 s.putBytes16(signature);
             } else {
@@ -838,6 +926,11 @@
             if (signature == null) {
                 s.println("Anonymous");
             } else {
+                if (protocolVersion.v >= ProtocolVersion.TLS12.v) {
+                    s.println("Signature Algorithm " +
+                        preferableSignatureAlgorithm.getAlgorithmName());
+                }
+
                 s.println("Signed with a DSA or RSA public key");
             }
         }
@@ -871,9 +964,19 @@
     // public key object encapsulated in this message
     private ECPublicKey publicKey;
 
+    // protocol version being established using this ServerKeyExchange message
+    ProtocolVersion protocolVersion;
+
+    // the preferable signature algorithm used by this ServerKeyExchange message
+    private SignatureAndHashAlgorithm preferableSignatureAlgorithm;
+
     ECDH_ServerKeyExchange(ECDHCrypt obj, PrivateKey privateKey,
-            byte[] clntNonce, byte[] svrNonce, SecureRandom sr)
-            throws GeneralSecurityException {
+            byte[] clntNonce, byte[] svrNonce, SecureRandom sr,
+            SignatureAndHashAlgorithm signAlgorithm,
+            ProtocolVersion protocolVersion) throws GeneralSecurityException {
+
+        this.protocolVersion = protocolVersion;
+
         publicKey = (ECPublicKey)obj.getPublicKey();
         ECParameterSpec params = publicKey.getParams();
         ECPoint point = publicKey.getW();
@@ -885,8 +988,14 @@
             return;
         }
 
-        Signature sig = getSignature(privateKey.getAlgorithm());
-        sig.initSign(privateKey);
+        Signature sig;
+        if (protocolVersion.v >= ProtocolVersion.TLS12.v) {
+            this.preferableSignatureAlgorithm = signAlgorithm;
+            sig = JsseJce.getSignature(signAlgorithm.getAlgorithmName());
+        } else {
+            sig = getSignature(privateKey.getAlgorithm());
+        }
+        sig.initSign(privateKey);  // where is the SecureRandom?
 
         updateSignature(sig, clntNonce, svrNonce);
         signatureBytes = sig.sign();
@@ -896,49 +1005,87 @@
      * Parse an ECDH server key exchange message.
      */
     ECDH_ServerKeyExchange(HandshakeInStream input, PublicKey signingKey,
-            byte[] clntNonce, byte[] svrNonce)
+            byte[] clntNonce, byte[] svrNonce,
+            Collection<SignatureAndHashAlgorithm> localSupportedSignAlgs,
+            ProtocolVersion protocolVersion)
             throws IOException, GeneralSecurityException {
+
+        this.protocolVersion = protocolVersion;
+
+        // read params: ServerECDHParams
         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);
+            if (SupportedEllipticCurvesExtension.isSupported(curveId)
+                    == false) {
+                throw new SSLHandshakeException(
+                    "Unsupported curveId: " + curveId);
             }
-            String curveOid = SupportedEllipticCurvesExtension.getCurveOid(curveId);
+            String curveOid =
+                SupportedEllipticCurvesExtension.getCurveOid(curveId);
             if (curveOid == null) {
-                throw new SSLHandshakeException("Unknown named curve: " + curveId);
+                throw new SSLHandshakeException(
+                    "Unknown named curve: " + curveId);
             }
             parameters = JsseJce.getECParameterSpec(curveOid);
             if (parameters == null) {
-                throw new SSLHandshakeException("Unsupported curve: " + curveOid);
+                throw new SSLHandshakeException(
+                    "Unsupported curve: " + curveOid);
             }
         } else {
-            throw new SSLHandshakeException("Unsupported ECCurveType: " + curveType);
+            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));
+        publicKey = (ECPublicKey)factory.generatePublic(
+            new ECPublicKeySpec(point, parameters));
 
         if (signingKey == null) {
             // ECDH_anon
             return;
         }
 
-        // verify the signature
+        // read the signature and hash algorithm
+        if (protocolVersion.v >= ProtocolVersion.TLS12.v) {
+            int hash = input.getInt8();         // hash algorithm
+            int signature = input.getInt8();    // signature algorithm
+
+            preferableSignatureAlgorithm =
+                SignatureAndHashAlgorithm.valueOf(hash, signature, 0);
+
+            // Is it a local supported signature algorithm?
+            if (!localSupportedSignAlgs.contains(
+                    preferableSignatureAlgorithm)) {
+                throw new SSLHandshakeException(
+                        "Unsupported SignatureAndHashAlgorithm in " +
+                        "ServerKeyExchange message");
+            }
+        }
+
+        // read the signature
         signatureBytes = input.getBytes16();
-        Signature sig = getSignature(signingKey.getAlgorithm());
+
+        // verify the signature
+        Signature sig;
+        if (protocolVersion.v >= ProtocolVersion.TLS12.v) {
+            sig = JsseJce.getSignature(
+                        preferableSignatureAlgorithm.getAlgorithmName());
+        } else {
+            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");
+            throw new SSLKeyException(
+                "Invalid signature on ECDH server key exchange message");
         }
     }
 
@@ -949,7 +1096,8 @@
         return publicKey;
     }
 
-    private static Signature getSignature(String keyAlgorithm) throws NoSuchAlgorithmException {
+    private static Signature getSignature(String keyAlgorithm)
+            throws NoSuchAlgorithmException {
         if (keyAlgorithm.equals("EC")) {
             return JsseJce.getSignature(JsseJce.SIGNATURE_ECDSA);
         } else if (keyAlgorithm.equals("RSA")) {
@@ -973,6 +1121,11 @@
 
     int messageLength() {
         int sigLen = (signatureBytes == null) ? 0 : 2 + signatureBytes.length;
+
+        if (protocolVersion.v >= ProtocolVersion.TLS12.v) {
+            sigLen += SignatureAndHashAlgorithm.sizeInRecord();
+        }
+
         return 4 + pointBytes.length + sigLen;
     }
 
@@ -980,6 +1133,11 @@
         s.putInt8(CURVE_NAMED_CURVE);
         s.putInt16(curveId);
         s.putBytes8(pointBytes);
+        if (protocolVersion.v >= ProtocolVersion.TLS12.v) {
+            s.putInt8(preferableSignatureAlgorithm.getHashValue());
+            s.putInt8(preferableSignatureAlgorithm.getSignatureValue());
+        }
+
         if (signatureBytes != null) {
             s.putBytes16(signatureBytes);
         }
@@ -989,6 +1147,11 @@
         s.println("*** ECDH ServerKeyExchange");
 
         if (debug != null && Debug.isOn("verbose")) {
+            if (protocolVersion.v >= ProtocolVersion.TLS12.v) {
+                s.println("Signature Algorithm " +
+                        preferableSignatureAlgorithm.getAlgorithmName());
+            }
+
             s.println("Server key: " + publicKey);
         }
     }
@@ -1014,8 +1177,8 @@
         try {
             return new X500Principal(name);
         } catch (IllegalArgumentException e) {
-            throw (SSLProtocolException)new SSLProtocolException
-                    (e.getMessage()).initCause(e);
+            throw (SSLProtocolException)new SSLProtocolException(
+                e.getMessage()).initCause(e);
         }
     }
 
@@ -1038,12 +1201,25 @@
  *
  * Authenticated servers may ask clients to authenticate themselves
  * in turn, using this message.
+ *
+ * Prior to TLS 1.2, the structure of the message is defined as:
+ *     struct {
+ *         ClientCertificateType certificate_types<1..2^8-1>;
+ *         DistinguishedName certificate_authorities<0..2^16-1>;
+ *     } CertificateRequest;
+ *
+ * In TLS 1.2, the structure is changed to:
+ *     struct {
+ *         ClientCertificateType certificate_types<1..2^8-1>;
+ *         SignatureAndHashAlgorithm
+ *           supported_signature_algorithms<2^16-1>;
+ *         DistinguishedName certificate_authorities<0..2^16-1>;
+ *     } CertificateRequest;
+ *
  */
 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;
@@ -1068,8 +1244,21 @@
     DistinguishedName   authorities [];         // 3 to 2^16 - 1
         // ... "3" because that's the smallest DER-encoded X500 DN
 
-    CertificateRequest(X509Certificate ca[], KeyExchange keyExchange)
-            throws IOException {
+    // protocol version being established using this CertificateRequest message
+    ProtocolVersion protocolVersion;
+
+    // supported_signature_algorithms for TLS 1.2 or later
+    private Collection<SignatureAndHashAlgorithm> algorithms;
+
+    // length of supported_signature_algorithms
+    private int algorithmsLen;
+
+    CertificateRequest(X509Certificate ca[], KeyExchange keyExchange,
+            Collection<SignatureAndHashAlgorithm> signAlgs,
+            ProtocolVersion protocolVersion) throws IOException {
+
+        this.protocolVersion = protocolVersion;
+
         // always use X500Principal
         authorities = new DistinguishedName[ca.length];
         for (int i = 0; i < ca.length; i++) {
@@ -1081,10 +1270,63 @@
         // 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;
+
+        // Use supported_signature_algorithms for TLS 1.2 or later.
+        if (protocolVersion.v >= ProtocolVersion.TLS12.v) {
+            if (signAlgs == null || signAlgs.isEmpty()) {
+                throw new SSLProtocolException(
+                        "No supported signature algorithms");
+            }
+
+            algorithms = new ArrayList<SignatureAndHashAlgorithm>(signAlgs);
+            algorithmsLen =
+                SignatureAndHashAlgorithm.sizeInRecord() * algorithms.size();
+        } else {
+            algorithms = new ArrayList<SignatureAndHashAlgorithm>();
+            algorithmsLen = 0;
+        }
     }
 
-    CertificateRequest(HandshakeInStream input) throws IOException {
+    CertificateRequest(HandshakeInStream input,
+            ProtocolVersion protocolVersion) throws IOException {
+
+        this.protocolVersion = protocolVersion;
+
+        // Read the certificate_types.
         types = input.getBytes8();
+
+        // Read the supported_signature_algorithms for TLS 1.2 or later.
+        if (protocolVersion.v >= ProtocolVersion.TLS12.v) {
+            algorithmsLen = input.getInt16();
+            if (algorithmsLen < 2) {
+                throw new SSLProtocolException(
+                        "Invalid supported_signature_algorithms field");
+            }
+
+            algorithms = new ArrayList<SignatureAndHashAlgorithm>();
+            int remains = algorithmsLen;
+            int sequence = 0;
+            while (remains > 1) {    // needs at least two bytes
+                int hash = input.getInt8();         // hash algorithm
+                int signature = input.getInt8();    // signature algorithm
+
+                SignatureAndHashAlgorithm algorithm =
+                    SignatureAndHashAlgorithm.valueOf(hash, signature,
+                                                                ++sequence);
+                algorithms.add(algorithm);
+                remains -= 2;  // one byte for hash, one byte for signature
+            }
+
+            if (remains != 0) {
+                throw new SSLProtocolException(
+                        "Invalid supported_signature_algorithms field");
+            }
+        } else {
+            algorithms = new ArrayList<SignatureAndHashAlgorithm>();
+            algorithmsLen = 0;
+        }
+
+        // read the certificate_authorities
         int len = input.getInt16();
         ArrayList<DistinguishedName> v = new ArrayList<DistinguishedName>();
         while (len >= 3) {
@@ -1108,31 +1350,58 @@
         return ret;
     }
 
-    int messageLength()
-    {
-        int len;
+    Collection<SignatureAndHashAlgorithm> getSignAlgorithms() {
+        return algorithms;
+    }
+
+    @Override
+    int messageType() {
+        return ht_certificate_request;
+    }
 
-        len = 1 + types.length + 2;
-        for (int i = 0; i < authorities.length; i++)
+    @Override
+    int messageLength() {
+        int len = 1 + types.length + 2;
+
+        if (protocolVersion.v >= ProtocolVersion.TLS12.v) {
+            len += algorithmsLen + 2;
+        }
+
+        for (int i = 0; i < authorities.length; i++) {
             len += authorities[i].length();
+        }
+
         return len;
     }
 
-    void send(HandshakeOutStream output) throws IOException
-    {
-        int     len = 0;
+    @Override
+    void send(HandshakeOutStream output) throws IOException {
+        // put certificate_types
+        output.putBytes8(types);
 
-        for (int i = 0; i < authorities.length; i++)
+        // put supported_signature_algorithms
+        if (protocolVersion.v >= ProtocolVersion.TLS12.v) {
+            output.putInt16(algorithmsLen);
+            for (SignatureAndHashAlgorithm algorithm : algorithms) {
+                output.putInt8(algorithm.getHashValue());      // hash
+                output.putInt8(algorithm.getSignatureValue()); // signature
+            }
+        }
+
+        // put certificate_authorities
+        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++)
+        for (int i = 0; i < authorities.length; i++) {
             authorities[i].send(output);
+        }
     }
 
-    void print(PrintStream s) throws IOException
-    {
+    @Override
+    void print(PrintStream s) throws IOException {
         s.println("*** CertificateRequest");
 
         if (debug != null && Debug.isOn("verbose")) {
@@ -1166,6 +1435,20 @@
             }
             s.println();
 
+            if (protocolVersion.v >= ProtocolVersion.TLS12.v) {
+                StringBuffer buffer = new StringBuffer();
+                boolean opened = false;
+                for (SignatureAndHashAlgorithm signAlg : algorithms) {
+                    if (opened) {
+                        buffer.append(", " + signAlg.getAlgorithmName());
+                    } else {
+                        buffer.append(signAlg.getAlgorithmName());
+                        opened = true;
+                    }
+                }
+                s.println("Supported Signature Algorithms: " + buffer);
+            }
+
             s.println("Cert Authorities:");
             if (authorities.length == 0) {
                 s.println("<Empty>");
@@ -1224,18 +1507,34 @@
  */
 static final class CertificateVerify extends HandshakeMessage {
 
-    int messageType() { return ht_certificate_verify; }
+    // the signature bytes
+    private byte[] signature;
 
-    private byte[] signature;
+    // protocol version being established using this ServerKeyExchange message
+    ProtocolVersion protocolVersion;
+
+    // the preferable signature algorithm used by this CertificateVerify message
+    private SignatureAndHashAlgorithm preferableSignatureAlgorithm = null;
 
     /*
      * Create an RSA or DSA signed certificate verify message.
      */
-    CertificateVerify(ProtocolVersion protocolVersion, HandshakeHash
-            handshakeHash, PrivateKey privateKey, SecretKey masterSecret,
-            SecureRandom sr) throws GeneralSecurityException {
+    CertificateVerify(ProtocolVersion protocolVersion,
+            HandshakeHash handshakeHash, PrivateKey privateKey,
+            SecretKey masterSecret, SecureRandom sr,
+            SignatureAndHashAlgorithm signAlgorithm)
+            throws GeneralSecurityException {
+
+        this.protocolVersion = protocolVersion;
+
         String algorithm = privateKey.getAlgorithm();
-        Signature sig = getSignature(protocolVersion, algorithm);
+        Signature sig = null;
+        if (protocolVersion.v >= ProtocolVersion.TLS12.v) {
+            this.preferableSignatureAlgorithm = signAlgorithm;
+            sig = JsseJce.getSignature(signAlgorithm.getAlgorithmName());
+        } else {
+            sig = getSignature(protocolVersion, algorithm);
+        }
         sig.initSign(privateKey, sr);
         updateSignature(sig, protocolVersion, handshakeHash, algorithm,
                         masterSecret);
@@ -1245,11 +1544,41 @@
     //
     // Unmarshal the signed data from the input stream.
     //
-    CertificateVerify(HandshakeInStream input) throws IOException  {
+    CertificateVerify(HandshakeInStream input,
+            Collection<SignatureAndHashAlgorithm> localSupportedSignAlgs,
+            ProtocolVersion protocolVersion) throws IOException  {
+
+        this.protocolVersion = protocolVersion;
+
+        // read the signature and hash algorithm
+        if (protocolVersion.v >= ProtocolVersion.TLS12.v) {
+            int hashAlg = input.getInt8();         // hash algorithm
+            int signAlg = input.getInt8();         // signature algorithm
+
+            preferableSignatureAlgorithm =
+                SignatureAndHashAlgorithm.valueOf(hashAlg, signAlg, 0);
+
+            // Is it a local supported signature algorithm?
+            if (!localSupportedSignAlgs.contains(
+                    preferableSignatureAlgorithm)) {
+                throw new SSLHandshakeException(
+                        "Unsupported SignatureAndHashAlgorithm in " +
+                        "ServerKeyExchange message");
+            }
+        }
+
+        // read the signature
         signature = input.getBytes16();
     }
 
     /*
+     * Get the preferable signature algorithm used by this message
+     */
+    SignatureAndHashAlgorithm getPreferableSignatureAlgorithm() {
+        return preferableSignatureAlgorithm;
+    }
+
+    /*
      * Verify a certificate verify message. Return the result of verification,
      * if there is a problem throw a GeneralSecurityException.
      */
@@ -1257,7 +1586,13 @@
             HandshakeHash handshakeHash, PublicKey publicKey,
             SecretKey masterSecret) throws GeneralSecurityException {
         String algorithm = publicKey.getAlgorithm();
-        Signature sig = getSignature(protocolVersion, algorithm);
+        Signature sig = null;
+        if (protocolVersion.v >= ProtocolVersion.TLS12.v) {
+            sig = JsseJce.getSignature(
+                        preferableSignatureAlgorithm.getAlgorithmName());
+        } else {
+            sig = getSignature(protocolVersion, algorithm);
+        }
         sig.initVerify(publicKey);
         updateSignature(sig, protocolVersion, handshakeHash, algorithm,
                         masterSecret);
@@ -1291,25 +1626,35 @@
             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);
+            if (protocolVersion.v < ProtocolVersion.TLS12.v) { // TLS1.1-
+                MessageDigest md5Clone = handshakeHash.getMD5Clone();
+                MessageDigest shaClone = handshakeHash.getSHAClone();
+
+                if (protocolVersion.v < ProtocolVersion.TLS10.v) { // SSLv3
+                    updateDigest(md5Clone, MD5_pad1, MD5_pad2, masterKey);
+                    updateDigest(shaClone, SHA_pad1, SHA_pad2, masterKey);
+                }
+
+                // The signature must be an instance of RSASignature, need
+                // to use these hashes directly.
+                RSASignature.setHashes(sig, md5Clone, shaClone);
+            } else {  // TLS1.2+
+                sig.update(handshakeHash.getAllHandshakeMessages());
             }
-            // 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);
+            if (protocolVersion.v < ProtocolVersion.TLS12.v) { // TLS1.1-
+                MessageDigest shaClone = handshakeHash.getSHAClone();
+
+                if (protocolVersion.v < ProtocolVersion.TLS10.v) { // SSLv3
+                    updateDigest(shaClone, SHA_pad1, SHA_pad2, masterKey);
+                }
+
+                sig.update(shaClone.digest());
+            } else {  // TLS1.2+
+                sig.update(handshakeHash.getAllHandshakeMessages());
             }
-            sig.update(shaClone.digest());
         }
     }
 
@@ -1319,7 +1664,8 @@
      * all preceding handshake messages.
      * Used by the Finished class as well.
      */
-    static void updateDigest(MessageDigest md, byte[] pad1, byte[] pad2,
+    private 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.
@@ -1395,26 +1741,54 @@
                 methodCache.put(clazz, r);
             }
             if (r == NULL_OBJECT) {
-                throw new Exception("Digest does not support implUpdate(SecretKey)");
+                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);
+            throw new RuntimeException(
+                "Could not obtain encoded key and "
+                + "MessageDigest cannot digest key", e);
         }
     }
 
-    int messageLength() {
-        return 2 + signature.length;
+    @Override
+    int messageType() {
+        return ht_certificate_verify;
     }
 
+    @Override
+    int messageLength() {
+        int temp = 2;
+
+        if (protocolVersion.v >= ProtocolVersion.TLS12.v) {
+            temp += SignatureAndHashAlgorithm.sizeInRecord();
+        }
+
+        return temp + signature.length;
+    }
+
+    @Override
     void send(HandshakeOutStream s) throws IOException {
+        if (protocolVersion.v >= ProtocolVersion.TLS12.v) {
+            s.putInt8(preferableSignatureAlgorithm.getHashValue());
+            s.putInt8(preferableSignatureAlgorithm.getSignatureValue());
+        }
+
         s.putBytes16(signature);
     }
 
+    @Override
     void print(PrintStream s) throws IOException {
         s.println("*** CertificateVerify");
+
+        if (debug != null && Debug.isOn("verbose")) {
+            if (protocolVersion.v >= ProtocolVersion.TLS12.v) {
+                s.println("Signature Algorithm " +
+                        preferableSignatureAlgorithm.getAlgorithmName());
+            }
+        }
     }
 }
 
@@ -1453,19 +1827,29 @@
     private byte[] verifyData;
 
     /*
+     * Current cipher suite we are negotiating.  TLS 1.2 has
+     * ciphersuite-defined PRF algorithms.
+     */
+    private ProtocolVersion protocolVersion;
+    private CipherSuite cipherSuite;
+
+    /*
      * 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);
+            int sender, SecretKey master, CipherSuite cipherSuite) {
+        this.protocolVersion = protocolVersion;
+        this.cipherSuite = cipherSuite;
+        verifyData = getFinished(handshakeHash, sender, master);
     }
 
     /*
      * Constructor that reads FINISHED message from stream.
      */
-    Finished(ProtocolVersion protocolVersion, HandshakeInStream input)
-            throws IOException {
+    Finished(ProtocolVersion protocolVersion, HandshakeInStream input,
+            CipherSuite cipherSuite) throws IOException {
+        this.protocolVersion = protocolVersion;
+        this.cipherSuite = cipherSuite;
         int msgLen = (protocolVersion.v >= ProtocolVersion.TLS10.v) ? 12 : 36;
         verifyData = new byte[msgLen];
         input.read(verifyData);
@@ -1477,18 +1861,16 @@
      * 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);
+    boolean verify(HandshakeHash handshakeHash, int sender, SecretKey master) {
+        byte[] myFinished = getFinished(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) {
+    private byte[] getFinished(HandshakeHash handshakeHash,
+            int sender, SecretKey masterKey) {
         byte[] sslLabel;
         String tlsLabel;
         if (sender == CLIENT) {
@@ -1500,23 +1882,53 @@
         } else {
             throw new RuntimeException("Invalid sender: " + sender);
         }
-        MessageDigest md5Clone = handshakeHash.getMD5Clone();
-        MessageDigest shaClone = handshakeHash.getSHAClone();
+
         if (protocolVersion.v >= ProtocolVersion.TLS10.v) {
-            // TLS
+            // TLS 1.0+
             try {
-                byte[] seed = new byte[36];
-                md5Clone.digest(seed, 0, 16);
-                shaClone.digest(seed, 16, 20);
+                byte [] seed;
+                String prfAlg;
+                PRF prf;
+
+                // Get the KeyGenerator alg and calculate the seed.
+                if (protocolVersion.v >= ProtocolVersion.TLS12.v) {
+                    // TLS 1.2
+                    seed = handshakeHash.getFinishedHash();
+
+                    prfAlg = "SunTls12Prf";
+                    prf = cipherSuite.prfAlg;
+                } else {
+                    // TLS 1.0/1.1
+                    MessageDigest md5Clone = handshakeHash.getMD5Clone();
+                    MessageDigest shaClone = handshakeHash.getSHAClone();
+                    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();
+                    prfAlg = "SunTlsPrf";
+                    prf = P_NONE;
+                }
+
+                String prfHashAlg = prf.getPRFHashAlg();
+                int prfHashLength = prf.getPRFHashLength();
+                int prfBlockSize = prf.getPRFBlockSize();
+
+                /*
+                 * RFC 5246/7.4.9 says that finished messages can
+                 * be ciphersuite-specific in both length/PRF hash
+                 * algorithm.  If we ever run across a different
+                 * length, this call will need to be updated.
+                 */
+                TlsPrfParameterSpec spec = new TlsPrfParameterSpec(
+                    masterKey, tlsLabel, seed, 12,
+                    prfHashAlg, prfHashLength, prfBlockSize);
+
+                KeyGenerator kg = JsseJce.getKeyGenerator(prfAlg);
+                kg.init(spec);
+                SecretKey prfKey = kg.generateKey();
                 if ("RAW".equals(prfKey.getFormat()) == false) {
-                    throw new ProviderException
-                        ("Invalid PRF output, format must be RAW");
+                    throw new ProviderException(
+                        "Invalid PRF output, format must be RAW");
                 }
                 byte[] finished = prfKey.getEncoded();
                 return finished;
@@ -1525,6 +1937,8 @@
             }
         } else {
             // SSLv3
+            MessageDigest md5Clone = handshakeHash.getMD5Clone();
+            MessageDigest shaClone = handshakeHash.getSHAClone();
             updateDigest(md5Clone, sslLabel, MD5_pad1, MD5_pad2, masterKey);
             updateDigest(shaClone, sslLabel, SHA_pad1, SHA_pad2, masterKey);
             byte[] finished = new byte[36];