--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.base/share/classes/sun/security/ssl/HandshakeMessage.java Tue Sep 12 19:03:39 2017 +0200
@@ -0,0 +1,2426 @@
+/*
+ * Copyright (c) 1996, 2017, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package sun.security.ssl;
+
+import java.io.*;
+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.DHPublicKeySpec;
+
+import javax.net.ssl.*;
+
+import sun.security.internal.spec.TlsPrfParameterSpec;
+import sun.security.ssl.CipherSuite.*;
+import static sun.security.ssl.CipherSuite.PRF.*;
+import sun.security.util.KeyUtil;
+import sun.security.util.MessageDigestSpi2;
+import sun.security.provider.certpath.OCSPResponse;
+
+/**
+ * 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
+ */
+public abstract class HandshakeMessage {
+
+ /* Class and subclass dynamic debugging support */
+ public static final Debug debug = Debug.getInstance("ssl");
+
+ // enum HandshakeType:
+ //
+ // Please update the isUnsupported() method accordingly if the handshake
+ // types get updated in the future.
+ static final byte ht_hello_request = 0; // RFC 5246
+ static final byte ht_client_hello = 1; // RFC 5246
+ static final byte ht_server_hello = 2; // RFC 5246
+ static final byte ht_hello_verify_request = 3; // RFC 6347
+ static final byte ht_new_session_ticket = 4; // RFC 4507
+
+ static final byte ht_certificate = 11; // RFC 5246
+ static final byte ht_server_key_exchange = 12; // RFC 5246
+ static final byte ht_certificate_request = 13; // RFC 5246
+ static final byte ht_server_hello_done = 14; // RFC 5246
+ static final byte ht_certificate_verify = 15; // RFC 5246
+ static final byte ht_client_key_exchange = 16; // RFC 5246
+
+ static final byte ht_finished = 20; // RFC 5246
+ static final byte ht_certificate_url = 21; // RFC 6066
+ static final byte ht_certificate_status = 22; // RFC 6066
+ static final byte ht_supplemental_data = 23; // RFC 4680
+
+ static final byte ht_not_applicable = -1; // N/A
+
+ /*
+ * 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);
+
+ // default constructor
+ HandshakeMessage() {
+ }
+
+ /**
+ * 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;
+ }
+
+ static boolean isUnsupported(byte handshakeType) {
+ return (handshakeType != ht_hello_request) &&
+ (handshakeType != ht_client_hello) &&
+ (handshakeType != ht_server_hello) &&
+ (handshakeType != ht_hello_verify_request) &&
+ (handshakeType != ht_new_session_ticket) &&
+ (handshakeType != ht_certificate) &&
+ (handshakeType != ht_server_key_exchange) &&
+ (handshakeType != ht_certificate_request) &&
+ (handshakeType != ht_server_hello_done) &&
+ (handshakeType != ht_certificate_verify) &&
+ (handshakeType != ht_client_key_exchange) &&
+ (handshakeType != ht_finished) &&
+ (handshakeType != ht_certificate_url) &&
+ (handshakeType != ht_certificate_status) &&
+ (handshakeType != ht_supplemental_data);
+ }
+
+ 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 >= Record.OVERFLOW_OF_INT24) {
+ throw new SSLException("Handshake message too big"
+ + ", type = " + messageType() + ", len = " + len);
+ }
+ s.write(messageType());
+ s.putInt24(len);
+ send(s);
+ s.complete();
+ }
+
+ /*
+ * 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 {
+ @Override
+ int messageType() { return ht_hello_request; }
+
+ HelloRequest() { }
+
+ HelloRequest(HandshakeInStream in) throws IOException
+ {
+ // nothing in this message
+ }
+
+ @Override
+ int messageLength() { return 0; }
+
+ @Override
+ void send(HandshakeOutStream out) throws IOException
+ {
+ // nothing in this messaage
+ }
+
+ @Override
+ void print(PrintStream out) throws IOException
+ {
+ out.println("*** HelloRequest (empty)");
+ }
+
+}
+
+/*
+ * HelloVerifyRequest ... SERVER --> CLIENT [DTLS only]
+ *
+ * The definition of HelloVerifyRequest is as follows:
+ *
+ * struct {
+ * ProtocolVersion server_version;
+ * opaque cookie<0..2^8-1>;
+ * } HelloVerifyRequest;
+ *
+ * For DTLS protocols, once the client has transmitted the ClientHello message,
+ * it expects to see a HelloVerifyRequest from the server. However, if the
+ * server's message is lost, the client knows that either the ClientHello or
+ * the HelloVerifyRequest has been lost and retransmits. [RFC 6347]
+ */
+static final class HelloVerifyRequest extends HandshakeMessage {
+ ProtocolVersion protocolVersion;
+ byte[] cookie; // 1 to 2^8 - 1 bytes
+
+ HelloVerifyRequest(HelloCookieManager helloCookieManager,
+ ClientHello clientHelloMsg) {
+
+ this.protocolVersion = clientHelloMsg.protocolVersion;
+ this.cookie = helloCookieManager.getCookie(clientHelloMsg);
+ }
+
+ HelloVerifyRequest(
+ HandshakeInStream input, int messageLength) throws IOException {
+
+ this.protocolVersion =
+ ProtocolVersion.valueOf(input.getInt8(), input.getInt8());
+ this.cookie = input.getBytes8();
+
+ // Is it a valid cookie?
+ HelloCookieManager.checkCookie(protocolVersion, cookie);
+ }
+
+ @Override
+ int messageType() {
+ return ht_hello_verify_request;
+ }
+
+ @Override
+ int messageLength() {
+ return 2 + cookie.length; // 2: the length of protocolVersion
+ }
+
+ @Override
+ void send(HandshakeOutStream hos) throws IOException {
+ hos.putInt8(protocolVersion.major);
+ hos.putInt8(protocolVersion.minor);
+ hos.putBytes8(cookie);
+ }
+
+ @Override
+ void print(PrintStream out) throws IOException {
+ out.println("*** HelloVerifyRequest");
+ if (debug != null && Debug.isOn("verbose")) {
+ out.println("server_version: " + protocolVersion);
+ Debug.println(out, "cookie", cookie);
+ }
+ }
+}
+
+/*
+ * 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 {
+
+ ProtocolVersion protocolVersion;
+ RandomCookie clnt_random;
+ SessionId sessionId;
+ byte[] cookie; // DTLS only
+ private CipherSuiteList cipherSuites;
+ private final boolean isDTLS;
+ byte[] compression_methods;
+
+ HelloExtensions extensions = new HelloExtensions();
+
+ private static final byte[] NULL_COMPRESSION = new byte[] {0};
+
+ ClientHello(SecureRandom generator, ProtocolVersion protocolVersion,
+ SessionId sessionId, CipherSuiteList cipherSuites,
+ boolean isDTLS) {
+
+ this.isDTLS = isDTLS;
+ this.protocolVersion = protocolVersion;
+ this.sessionId = sessionId;
+ this.cipherSuites = cipherSuites;
+ if (isDTLS) {
+ this.cookie = new byte[0];
+ } else {
+ this.cookie = null;
+ }
+
+ clnt_random = new RandomCookie(generator);
+ compression_methods = NULL_COMPRESSION;
+ }
+
+ ClientHello(HandshakeInStream s,
+ int messageLength, boolean isDTLS) throws IOException {
+
+ this.isDTLS = isDTLS;
+
+ protocolVersion = ProtocolVersion.valueOf(s.getInt8(), s.getInt8());
+ clnt_random = new RandomCookie(s);
+ sessionId = new SessionId(s.getBytes8());
+ sessionId.checkLength(protocolVersion);
+ if (isDTLS) {
+ cookie = s.getBytes8();
+ } else {
+ cookie = null;
+ }
+
+ cipherSuites = new CipherSuiteList(s);
+ compression_methods = s.getBytes8();
+ if (messageLength() != messageLength) {
+ extensions = new HelloExtensions(s);
+ }
+ }
+
+ CipherSuiteList getCipherSuites() {
+ return cipherSuites;
+ }
+
+ // add renegotiation_info extension
+ void addRenegotiationInfoExtension(byte[] clientVerifyData) {
+ HelloExtension renegotiationInfo = new RenegotiationInfoExtension(
+ clientVerifyData, new byte[0]);
+ extensions.add(renegotiationInfo);
+ }
+
+ // add server_name extension
+ void addSNIExtension(List<SNIServerName> serverNames) {
+ try {
+ extensions.add(new ServerNameExtension(serverNames));
+ } 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);
+ }
+
+ void addMFLExtension(int maximumPacketSize) {
+ HelloExtension maxFragmentLength =
+ new MaxFragmentLengthExtension(maximumPacketSize);
+ extensions.add(maxFragmentLength);
+ }
+
+ void updateHelloCookie(MessageDigest cookieDigest) {
+ //
+ // Just use HandshakeOutStream to compute the hello verify cookie.
+ // Not actually used to output handshake message records.
+ //
+ HandshakeOutStream hos = new HandshakeOutStream(null);
+
+ try {
+ send(hos, false); // Do not count hello verify cookie.
+ } catch (IOException ioe) {
+ // unlikely to happen
+ }
+
+ cookieDigest.update(hos.toByteArray());
+ }
+
+ // Add status_request extension type
+ void addCertStatusRequestExtension() {
+ extensions.add(new CertStatusReqExtension(StatusRequestType.OCSP,
+ new OCSPStatusRequest()));
+ }
+
+ // Add status_request_v2 extension type
+ void addCertStatusReqListV2Extension() {
+ // Create a default OCSPStatusRequest that we can use for both
+ // OCSP_MULTI and OCSP request list items.
+ OCSPStatusRequest osr = new OCSPStatusRequest();
+ List<CertStatusReqItemV2> itemList = new ArrayList<>(2);
+ itemList.add(new CertStatusReqItemV2(StatusRequestType.OCSP_MULTI,
+ osr));
+ itemList.add(new CertStatusReqItemV2(StatusRequestType.OCSP, osr));
+ extensions.add(new CertStatusReqListV2Extension(itemList));
+ }
+
+ // add application_layer_protocol_negotiation extension
+ void addALPNExtension(String[] applicationProtocols) throws SSLException {
+ extensions.add(new ALPNExtension(applicationProtocols));
+ }
+
+ @Override
+ int messageType() { return ht_client_hello; }
+
+ @Override
+ int messageLength() {
+ /*
+ * Add fixed size parts of each field...
+ * version + random + session + cipher + compress
+ */
+ return (2 + 32 + 1 + 2 + 1
+ + sessionId.length() /* ... + variable parts */
+ + (isDTLS ? (1 + cookie.length) : 0)
+ + (cipherSuites.size() * 2)
+ + compression_methods.length)
+ + extensions.length();
+ }
+
+ @Override
+ void send(HandshakeOutStream s) throws IOException {
+ send(s, true); // Count hello verify cookie.
+ }
+
+ @Override
+ 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);
+
+ if (isDTLS) {
+ Debug.println(s, "cookie", cookie);
+ }
+
+ s.println("Cipher Suites: " + cipherSuites);
+
+ Debug.println(s, "Compression Methods", compression_methods);
+ extensions.print(s);
+ s.println("***");
+ }
+ }
+
+ private void send(HandshakeOutStream s,
+ boolean computeCookie) throws IOException {
+ s.putInt8(protocolVersion.major);
+ s.putInt8(protocolVersion.minor);
+ clnt_random.send(s);
+ s.putBytes8(sessionId.getId());
+ if (isDTLS && computeCookie) {
+ s.putBytes8(cookie);
+ }
+ cipherSuites.send(s);
+ s.putBytes8(compression_methods);
+ extensions.send(s);
+ }
+
+}
+
+/*
+ * 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
+{
+ @Override
+ int messageType() { return ht_server_hello; }
+
+ ProtocolVersion protocolVersion;
+ RandomCookie svr_random;
+ SessionId sessionId;
+ CipherSuite cipherSuite;
+ byte compression_method;
+ HelloExtensions extensions = new HelloExtensions();
+
+ 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());
+ sessionId.checkLength(protocolVersion);
+ cipherSuite = CipherSuite.valueOf(input.getInt8(), input.getInt8());
+ compression_method = (byte)input.getInt8();
+ if (messageLength() != messageLength) {
+ extensions = new HelloExtensions(input);
+ }
+ }
+
+ @Override
+ 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();
+ }
+
+ @Override
+ 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);
+ }
+
+ @Override
+ void print(PrintStream s) throws IOException
+ {
+ s.println("*** ServerHello, " + protocolVersion);
+
+ if (debug != null && Debug.isOn("verbose")) {
+ s.print("RandomCookie: ");
+ svr_random.print(s);
+
+ 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
+{
+ @Override
+ 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<>(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()]);
+ }
+
+ @Override
+ 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;
+ }
+
+ @Override
+ void send(HandshakeOutStream s) throws IOException {
+ s.putInt24(messageLength() - 3);
+ for (byte[] b : encodedChain) {
+ s.putBytes24(b);
+ }
+ }
+
+ @Override
+ void print(PrintStream s) throws IOException {
+ s.println("*** Certificate chain");
+
+ if (chain.length == 0) {
+ s.println("<Empty>");
+ } else 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.clone();
+ }
+}
+
+/*
+ * CertificateStatus ... SERVER --> CLIENT
+ *
+ * When a ClientHello asserting the status_request or status_request_v2
+ * extensions is accepted by the server, it will fetch and return one
+ * or more status responses in this handshake message.
+ *
+ * NOTE: Like the Certificate handshake message, this can potentially
+ * be a very large message both due to the size of multiple status
+ * responses and the certificate chains that are often attached to them.
+ * Up to 2^24 bytes of status responses may be sent, possibly fragmented
+ * over multiple TLS records.
+ */
+static final class CertificateStatus extends HandshakeMessage
+{
+ private final StatusRequestType statusType;
+ private int encodedResponsesLen;
+ private int messageLength = -1;
+ private List<byte[]> encodedResponses;
+
+ @Override
+ int messageType() { return ht_certificate_status; }
+
+ /**
+ * Create a CertificateStatus message from the certificates and their
+ * respective OCSP responses
+ *
+ * @param type an indication of the type of response (OCSP or OCSP_MULTI)
+ * @param responses a {@code List} of OCSP responses in DER-encoded form.
+ * For the OCSP type, only the first entry in the response list is
+ * used, and must correspond to the end-entity certificate sent to the
+ * peer. Zero-length or null values for the response data are not
+ * allowed for the OCSP type. For the OCSP_MULTI type, each entry in
+ * the list should match its corresponding certificate sent in the
+ * Server Certificate message. Where an OCSP response does not exist,
+ * either a zero-length array or a null value should be used.
+ *
+ * @throws SSLException if an unsupported StatusRequestType or invalid
+ * OCSP response data is provided.
+ */
+ CertificateStatus(StatusRequestType type, X509Certificate[] chain,
+ Map<X509Certificate, byte[]> responses) {
+ statusType = type;
+ encodedResponsesLen = 0;
+ encodedResponses = new ArrayList<>(chain.length);
+
+ Objects.requireNonNull(chain, "Null chain not allowed");
+ Objects.requireNonNull(responses, "Null responses not allowed");
+
+ if (statusType == StatusRequestType.OCSP) {
+ // Just get the response for the end-entity certificate
+ byte[] respDER = responses.get(chain[0]);
+ if (respDER != null && respDER.length > 0) {
+ encodedResponses.add(respDER);
+ encodedResponsesLen = 3 + respDER.length;
+ } else {
+ throw new IllegalArgumentException("Zero-length or null " +
+ "OCSP Response");
+ }
+ } else if (statusType == StatusRequestType.OCSP_MULTI) {
+ for (X509Certificate cert : chain) {
+ byte[] respDER = responses.get(cert);
+ if (respDER != null) {
+ encodedResponses.add(respDER);
+ encodedResponsesLen += (respDER.length + 3);
+ } else {
+ // If we cannot find a response for a given certificate
+ // then use a zero-length placeholder.
+ encodedResponses.add(new byte[0]);
+ encodedResponsesLen += 3;
+ }
+ }
+ } else {
+ throw new IllegalArgumentException(
+ "Unsupported StatusResponseType: " + statusType);
+ }
+ }
+
+ /**
+ * Decode the CertificateStatus handshake message coming from a
+ * {@code HandshakeInputStream}.
+ *
+ * @param input the {@code HandshakeInputStream} containing the
+ * CertificateStatus message bytes.
+ *
+ * @throws SSLHandshakeException if a zero-length response is found in the
+ * OCSP response type, or an unsupported response type is detected.
+ * @throws IOException if a decoding error occurs.
+ */
+ CertificateStatus(HandshakeInStream input) throws IOException {
+ encodedResponsesLen = 0;
+ encodedResponses = new ArrayList<>();
+
+ statusType = StatusRequestType.get(input.getInt8());
+ if (statusType == StatusRequestType.OCSP) {
+ byte[] respDER = input.getBytes24();
+ // Convert the incoming bytes to a OCSPResponse strucutre
+ if (respDER.length > 0) {
+ encodedResponses.add(respDER);
+ encodedResponsesLen = 3 + respDER.length;
+ } else {
+ throw new SSLHandshakeException("Zero-length OCSP Response");
+ }
+ } else if (statusType == StatusRequestType.OCSP_MULTI) {
+ int respListLen = input.getInt24();
+ encodedResponsesLen = respListLen;
+
+ // Add each OCSP reponse into the array list in the order
+ // we receive them off the wire. A zero-length array is
+ // allowed for ocsp_multi, and means that a response for
+ // a given certificate is not available.
+ while (respListLen > 0) {
+ byte[] respDER = input.getBytes24();
+ encodedResponses.add(respDER);
+ respListLen -= (respDER.length + 3);
+ }
+
+ if (respListLen != 0) {
+ throw new SSLHandshakeException(
+ "Bad OCSP response list length");
+ }
+ } else {
+ throw new SSLHandshakeException("Unsupported StatusResponseType: " +
+ statusType);
+ }
+ }
+
+ /**
+ * Get the length of the CertificateStatus message.
+ *
+ * @return the length of the message in bytes.
+ */
+ @Override
+ int messageLength() {
+ int len = 1; // Length + Status type
+
+ if (messageLength == -1) {
+ if (statusType == StatusRequestType.OCSP) {
+ len += encodedResponsesLen;
+ } else if (statusType == StatusRequestType.OCSP_MULTI) {
+ len += 3 + encodedResponsesLen;
+ }
+ messageLength = len;
+ }
+
+ return messageLength;
+ }
+
+ /**
+ * Encode the CertificateStatus handshake message and place it on a
+ * {@code HandshakeOutputStream}.
+ *
+ * @param s the HandshakeOutputStream that will the message bytes.
+ *
+ * @throws IOException if an encoding error occurs.
+ */
+ @Override
+ void send(HandshakeOutStream s) throws IOException {
+ s.putInt8(statusType.id);
+ if (statusType == StatusRequestType.OCSP) {
+ s.putBytes24(encodedResponses.get(0));
+ } else if (statusType == StatusRequestType.OCSP_MULTI) {
+ s.putInt24(encodedResponsesLen);
+ for (byte[] respBytes : encodedResponses) {
+ if (respBytes != null) {
+ s.putBytes24(respBytes);
+ } else {
+ s.putBytes24(null);
+ }
+ }
+ } else {
+ // It is highly unlikely that we will fall into this section of
+ // the code.
+ throw new SSLHandshakeException("Unsupported status_type: " +
+ statusType.id);
+ }
+ }
+
+ /**
+ * Display a human-readable representation of the CertificateStatus message.
+ *
+ * @param s the PrintStream used to display the message data.
+ *
+ * @throws IOException if any errors occur while parsing the OCSP response
+ * bytes into a readable form.
+ */
+ @Override
+ void print(PrintStream s) throws IOException {
+ s.println("*** CertificateStatus");
+ if (debug != null && Debug.isOn("verbose")) {
+ s.println("Type: " + statusType);
+ if (statusType == StatusRequestType.OCSP) {
+ OCSPResponse oResp = new OCSPResponse(encodedResponses.get(0));
+ s.println(oResp);
+ } else if (statusType == StatusRequestType.OCSP_MULTI) {
+ int numResponses = encodedResponses.size();
+ s.println(numResponses +
+ (numResponses == 1 ? " entry:" : " entries:"));
+ for (byte[] respDER : encodedResponses) {
+ if (respDER.length > 0) {
+ OCSPResponse oResp = new OCSPResponse(respDER);
+ s.println(oResp);
+ } else {
+ s.println("<Zero-length entry>");
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Get the type of CertificateStatus message
+ *
+ * @return the {@code StatusRequestType} for this CertificateStatus
+ * message.
+ */
+ StatusRequestType getType() {
+ return statusType;
+ }
+
+ /**
+ * Get the list of non-zero length OCSP responses.
+ * The responses returned in this list can be used to map to
+ * {@code X509Certificate} objects provided by the peer and
+ * provided to a {@code PKIXRevocationChecker}.
+ *
+ * @return an unmodifiable List of zero or more byte arrays, each one
+ * consisting of a single status response.
+ */
+ List<byte[]> getResponses() {
+ return Collections.unmodifiableList(encodedResponses);
+ }
+}
+
+/*
+ * 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.
+ */
+abstract static class ServerKeyExchange extends HandshakeMessage
+{
+ @Override
+ 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);
+ }
+
+ @Override
+ int messageLength() {
+ return 6 + rsa_modulus.length + rsa_exponent.length
+ + signatureBytes.length;
+ }
+
+ @Override
+ void send(HandshakeOutStream s) throws IOException {
+ s.putBytes16(rsa_modulus);
+ s.putBytes16(rsa_exponent);
+ s.putBytes16(signatureBytes);
+ }
+
+ @Override
+ 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 static final 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;
+
+ // protocol version being established using this ServerKeyExchange message
+ ProtocolVersion protocolVersion;
+
+ // the preferable signature algorithm used by this ServerKeyExchange message
+ private SignatureAndHashAlgorithm preferableSignatureAlgorithm;
+
+ /*
+ * Construct from initialized DH key object, for DH_anon
+ * key exchange.
+ */
+ DH_ServerKeyExchange(DHCrypt obj, ProtocolVersion protocolVersion) {
+ this.protocolVersion = protocolVersion;
+ this.preferableSignatureAlgorithm = null;
+
+ // The DH key has been validated in the constructor of DHCrypt.
+ setValues(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,
+ SignatureAndHashAlgorithm signAlgorithm,
+ ProtocolVersion protocolVersion) throws GeneralSecurityException {
+
+ this.protocolVersion = protocolVersion;
+
+ // The DH key has been validated in the constructor of DHCrypt.
+ setValues(obj);
+
+ Signature sig;
+ if (protocolVersion.useTLS12PlusSpec()) {
+ this.preferableSignatureAlgorithm = signAlgorithm;
+ sig = JsseJce.getSignature(signAlgorithm.getAlgorithmName());
+ } else {
+ 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();
+ }
+
+ /*
+ * 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,
+ ProtocolVersion protocolVersion)
+ throws IOException, GeneralSecurityException {
+
+ this.protocolVersion = protocolVersion;
+ this.preferableSignatureAlgorithm = null;
+
+ dh_p = input.getBytes16();
+ dh_g = input.getBytes16();
+ dh_Ys = input.getBytes16();
+ KeyUtil.validate(new DHPublicKeySpec(new BigInteger(1, dh_Ys),
+ new BigInteger(1, dh_p),
+ new BigInteger(1, dh_g)));
+
+ 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,
+ 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();
+ KeyUtil.validate(new DHPublicKeySpec(new BigInteger(1, dh_Ys),
+ new BigInteger(1, dh_p),
+ new BigInteger(1, dh_g)));
+
+ // read the signature and hash algorithm
+ if (protocolVersion.useTLS12PlusSpec()) {
+ 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: " +
+ preferableSignatureAlgorithm);
+ }
+ } else {
+ this.preferableSignatureAlgorithm = null;
+ }
+
+ // read the signature
+ 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 (protocolVersion.useTLS12PlusSpec()) {
+ sig = JsseJce.getSignature(
+ preferableSignatureAlgorithm.getAlgorithmName());
+ } else {
+ switch (algorithm) {
+ case "DSA":
+ sig = JsseJce.getSignature(JsseJce.SIGNATURE_DSA);
+ break;
+ case "RSA":
+ sig = RSASignature.getInstance();
+ break;
+ default:
+ throw new SSLKeyException(
+ "neither an RSA or a DSA key: " + algorithm);
+ }
+ }
+
+ sig.initVerify(publicKey);
+ updateSignature(sig, clntNonce, svrNonce);
+
+ if (sig.verify(signature) == false ) {
+ throw new SSLKeyException("Server D-H key verification failed");
+ }
+ }
+
+ /* 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);
+ }
+
+ private void setValues(DHCrypt obj) {
+ dh_p = toByteArray(obj.getModulus());
+ dh_g = toByteArray(obj.getBase());
+ dh_Ys = toByteArray(obj.getPublicKey());
+ }
+
+ @Override
+ 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) {
+ if (protocolVersion.useTLS12PlusSpec()) {
+ temp += SignatureAndHashAlgorithm.sizeInRecord();
+ }
+
+ temp += signature.length;
+ if (dhKeyExchangeFix) {
+ temp += 2;
+ }
+ }
+
+ return temp;
+ }
+
+ @Override
+ void send(HandshakeOutStream s) throws IOException {
+ s.putBytes16(dh_p);
+ s.putBytes16(dh_g);
+ s.putBytes16(dh_Ys);
+
+ if (signature != null) {
+ if (protocolVersion.useTLS12PlusSpec()) {
+ s.putInt8(preferableSignatureAlgorithm.getHashValue());
+ s.putInt8(preferableSignatureAlgorithm.getSignatureValue());
+ }
+
+ if (dhKeyExchangeFix) {
+ s.putBytes16(signature);
+ } else {
+ s.write(signature);
+ }
+ }
+ }
+
+ @Override
+ 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 {
+ if (protocolVersion.useTLS12PlusSpec()) {
+ s.println("Signature Algorithm " +
+ preferableSignatureAlgorithm.getAlgorithmName());
+ }
+
+ 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 static final int CURVE_EXPLICIT_PRIME = 1;
+ private static final int CURVE_EXPLICIT_CHAR2 = 2;
+ private static final int CURVE_NAMED_CURVE = 3;
+
+ // id of the named group we are using
+ private int groupId;
+
+ // 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;
+
+ // 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,
+ SignatureAndHashAlgorithm signAlgorithm,
+ ProtocolVersion protocolVersion)
+ throws SSLHandshakeException, GeneralSecurityException {
+
+ this.protocolVersion = protocolVersion;
+
+ publicKey = (ECPublicKey)obj.getPublicKey();
+ ECParameterSpec params = publicKey.getParams();
+ ECPoint point = publicKey.getW();
+ pointBytes = JsseJce.encodePoint(point, params.getCurve());
+
+ NamedGroup namedGroup = NamedGroup.valueOf(params);
+ if ((namedGroup == null) || (namedGroup.oid == null) ){
+ // unlikely
+ throw new SSLHandshakeException(
+ "Unnamed EC parameter spec: " + params);
+ }
+ groupId = namedGroup.id;
+
+ if (privateKey == null) {
+ // ECDH_anon
+ return;
+ }
+
+ Signature sig;
+ if (protocolVersion.useTLS12PlusSpec()) {
+ 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();
+ }
+
+ /*
+ * Parse an ECDH server key exchange message.
+ */
+ ECDH_ServerKeyExchange(HandshakeInStream input, PublicKey signingKey,
+ 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) {
+ groupId = input.getInt16();
+ NamedGroup namedGroup = NamedGroup.valueOf(groupId);
+ if (namedGroup == null) {
+ throw new SSLHandshakeException(
+ "Unknown named group ID: " + groupId);
+ }
+
+ if (!SupportedGroupsExtension.supports(namedGroup)) {
+ throw new SSLHandshakeException(
+ "Unsupported named group: " + namedGroup);
+ }
+
+ if (namedGroup.oid == null) {
+ throw new SSLHandshakeException(
+ "Unknown named EC curve: " + namedGroup);
+ }
+
+ parameters = JsseJce.getECParameterSpec(namedGroup.oid);
+ if (parameters == null) {
+ throw new SSLHandshakeException(
+ "No supported EC parameter for named group: " + namedGroup);
+ }
+ } 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;
+ }
+
+ // read the signature and hash algorithm
+ if (protocolVersion.useTLS12PlusSpec()) {
+ 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: " +
+ preferableSignatureAlgorithm);
+ }
+ }
+
+ // read the signature
+ signatureBytes = input.getBytes16();
+
+ // verify the signature
+ Signature sig;
+ if (protocolVersion.useTLS12PlusSpec()) {
+ 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");
+ }
+ }
+
+ /*
+ * Get the ephemeral EC public key encapsulated in this message.
+ */
+ ECPublicKey getPublicKey() {
+ return publicKey;
+ }
+
+ private static Signature getSignature(String keyAlgorithm)
+ throws NoSuchAlgorithmException {
+ switch (keyAlgorithm) {
+ case "EC":
+ return JsseJce.getSignature(JsseJce.SIGNATURE_ECDSA);
+ case "RSA":
+ return RSASignature.getInstance();
+ default:
+ throw new NoSuchAlgorithmException(
+ "neither an RSA or a EC key : " + keyAlgorithm);
+ }
+ }
+
+ 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)(groupId >> 8));
+ sig.update((byte)groupId);
+ sig.update((byte)pointBytes.length);
+ sig.update(pointBytes);
+ }
+
+ @Override
+ int messageLength() {
+ int sigLen = 0;
+ if (signatureBytes != null) {
+ sigLen = 2 + signatureBytes.length;
+ if (protocolVersion.useTLS12PlusSpec()) {
+ sigLen += SignatureAndHashAlgorithm.sizeInRecord();
+ }
+ }
+
+ return 4 + pointBytes.length + sigLen;
+ }
+
+ @Override
+ void send(HandshakeOutStream s) throws IOException {
+ s.putInt8(CURVE_NAMED_CURVE);
+ s.putInt16(groupId);
+ s.putBytes8(pointBytes);
+
+ if (signatureBytes != null) {
+ if (protocolVersion.useTLS12PlusSpec()) {
+ s.putInt8(preferableSignatureAlgorithm.getHashValue());
+ s.putInt8(preferableSignatureAlgorithm.getSignatureValue());
+ }
+
+ s.putBytes16(signatureBytes);
+ }
+ }
+
+ @Override
+ void print(PrintStream s) throws IOException {
+ s.println("*** ECDH ServerKeyExchange");
+
+ if (debug != null && Debug.isOn("verbose")) {
+ if (signatureBytes == null) {
+ s.println("Anonymous");
+ } else {
+ if (protocolVersion.useTLS12PlusSpec()) {
+ s.println("Signature Algorithm " +
+ preferableSignatureAlgorithm.getAlgorithmName());
+ }
+ }
+
+ 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.
+ *
+ * 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
+{
+ // 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 static final byte[] TYPES_NO_ECC = { cct_rsa_sign, cct_dss_sign };
+ private static final 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
+
+ // 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++) {
+ 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;
+
+ // Use supported_signature_algorithms for TLS 1.2 or later.
+ if (protocolVersion.useTLS12PlusSpec()) {
+ 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,
+ 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.useTLS12PlusSpec()) {
+ algorithmsLen = input.getInt16();
+ if (algorithmsLen < 2) {
+ throw new SSLProtocolException(
+ "Invalid supported_signature_algorithms field: " +
+ algorithmsLen);
+ }
+
+ 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. remains: " +
+ remains);
+ }
+ } else {
+ algorithms = new ArrayList<SignatureAndHashAlgorithm>();
+ algorithmsLen = 0;
+ }
+
+ // read the certificate_authorities
+ int len = input.getInt16();
+ ArrayList<DistinguishedName> v = new ArrayList<>();
+ while (len >= 3) {
+ DistinguishedName dn = new DistinguishedName(input);
+ v.add(dn);
+ len -= dn.length();
+ }
+
+ if (len != 0) {
+ throw new SSLProtocolException(
+ "Bad CertificateRequest DN length: " + len);
+ }
+
+ 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;
+ }
+
+ Collection<SignatureAndHashAlgorithm> getSignAlgorithms() {
+ return algorithms;
+ }
+
+ @Override
+ int messageType() {
+ return ht_certificate_request;
+ }
+
+ @Override
+ int messageLength() {
+ int len = 1 + types.length + 2;
+
+ if (protocolVersion.useTLS12PlusSpec()) {
+ len += algorithmsLen + 2;
+ }
+
+ for (int i = 0; i < authorities.length; i++) {
+ len += authorities[i].length();
+ }
+
+ return len;
+ }
+
+ @Override
+ void send(HandshakeOutStream output) throws IOException {
+ // put certificate_types
+ output.putBytes8(types);
+
+ // put supported_signature_algorithms
+ if (protocolVersion.useTLS12PlusSpec()) {
+ 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.putInt16(len);
+ for (int i = 0; i < authorities.length; i++) {
+ authorities[i].send(output);
+ }
+ }
+
+ @Override
+ 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();
+
+ if (protocolVersion.useTLS12PlusSpec()) {
+ StringBuilder sb = new StringBuilder();
+ boolean opened = false;
+ for (SignatureAndHashAlgorithm signAlg : algorithms) {
+ if (opened) {
+ sb.append(", ").append(signAlg.getAlgorithmName());
+ } else {
+ sb.append(signAlg.getAlgorithmName());
+ opened = true;
+ }
+ }
+ s.println("Supported Signature Algorithms: " + sb);
+ }
+
+ s.println("Cert Authorities:");
+ if (authorities.length == 0) {
+ s.println("<Empty>");
+ } else {
+ 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
+{
+ @Override
+ int messageType() { return ht_server_hello_done; }
+
+ ServerHelloDone() { }
+
+ ServerHelloDone(HandshakeInStream input)
+ {
+ // nothing to do
+ }
+
+ @Override
+ int messageLength()
+ {
+ return 0;
+ }
+
+ @Override
+ void send(HandshakeOutStream s) throws IOException
+ {
+ // nothing to send
+ }
+
+ @Override
+ 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 {
+
+ // the signature bytes
+ private byte[] signature;
+
+ // protocol version being established using this CertificateVerify 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,
+ SignatureAndHashAlgorithm signAlgorithm)
+ throws GeneralSecurityException {
+
+ this.protocolVersion = protocolVersion;
+
+ String algorithm = privateKey.getAlgorithm();
+ Signature sig = null;
+ if (protocolVersion.useTLS12PlusSpec()) {
+ this.preferableSignatureAlgorithm = signAlgorithm;
+ sig = JsseJce.getSignature(signAlgorithm.getAlgorithmName());
+ } else {
+ 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,
+ Collection<SignatureAndHashAlgorithm> localSupportedSignAlgs,
+ ProtocolVersion protocolVersion) throws IOException {
+
+ this.protocolVersion = protocolVersion;
+
+ // read the signature and hash algorithm
+ if (protocolVersion.useTLS12PlusSpec()) {
+ 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 " +
+ "CertificateVerify message: " + preferableSignatureAlgorithm);
+ }
+ }
+
+ // 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.
+ */
+ boolean verify(ProtocolVersion protocolVersion,
+ HandshakeHash handshakeHash, PublicKey publicKey,
+ SecretKey masterSecret) throws GeneralSecurityException {
+ String algorithm = publicKey.getAlgorithm();
+ Signature sig = null;
+ if (protocolVersion.useTLS12PlusSpec()) {
+ sig = JsseJce.getSignature(
+ preferableSignatureAlgorithm.getAlgorithmName());
+ } else {
+ 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 {
+ switch (algorithm) {
+ case "RSA":
+ return RSASignature.getInternalInstance();
+ case "DSA":
+ return JsseJce.getSignature(JsseJce.SIGNATURE_RAWDSA);
+ case "EC":
+ return JsseJce.getSignature(JsseJce.SIGNATURE_RAWECDSA);
+ default:
+ 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 {
+
+ if (algorithm.equals("RSA")) {
+ if (!protocolVersion.useTLS12PlusSpec()) { // TLS1.1-
+ MessageDigest md5Clone = handshakeHash.getMD5Clone();
+ MessageDigest shaClone = handshakeHash.getSHAClone();
+
+ if (!protocolVersion.useTLS10PlusSpec()) { // 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());
+ }
+ } else { // DSA, ECDSA
+ if (!protocolVersion.useTLS12PlusSpec()) { // TLS1.1-
+ MessageDigest shaClone = handshakeHash.getSHAClone();
+
+ if (!protocolVersion.useTLS10PlusSpec()) { // SSLv3
+ updateDigest(shaClone, SHA_pad1, SHA_pad2, masterKey);
+ }
+
+ sig.update(shaClone.digest());
+ } else { // TLS1.2+
+ sig.update(handshakeHash.getAllHandshakeMessages());
+ }
+ }
+ }
+
+ /*
+ * 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.
+ */
+ 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.
+ // 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 static void digestKey(MessageDigest md, SecretKey key) {
+ try {
+ if (md instanceof MessageDigestSpi2) {
+ ((MessageDigestSpi2)md).engineUpdate(key);
+ } else {
+ throw new Exception(
+ "Digest does not support implUpdate(SecretKey)");
+ }
+ } catch (Exception e) {
+ throw new RuntimeException(
+ "Could not obtain encoded key and "
+ + "MessageDigest cannot digest key", e);
+ }
+ }
+
+ @Override
+ int messageType() {
+ return ht_certificate_verify;
+ }
+
+ @Override
+ int messageLength() {
+ int temp = 2;
+
+ if (protocolVersion.useTLS12PlusSpec()) {
+ temp += SignatureAndHashAlgorithm.sizeInRecord();
+ }
+
+ return temp + signature.length;
+ }
+
+ @Override
+ void send(HandshakeOutStream s) throws IOException {
+ if (protocolVersion.useTLS12PlusSpec()) {
+ 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.useTLS12PlusSpec()) {
+ s.println("Signature Algorithm " +
+ preferableSignatureAlgorithm.getAlgorithmName());
+ }
+ }
+ }
+}
+
+
+/*
+ * 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 {
+
+ // constant for a Finished message sent by the client
+ static final int CLIENT = 1;
+
+ // constant for a Finished message sent by the server
+ static final 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;
+
+ /*
+ * 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, 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,
+ CipherSuite cipherSuite) throws IOException {
+ this.protocolVersion = protocolVersion;
+ this.cipherSuite = cipherSuite;
+ int msgLen = protocolVersion.useTLS10PlusSpec() ? 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(HandshakeHash handshakeHash, int sender, SecretKey master) {
+ byte[] myFinished = getFinished(handshakeHash, sender, master);
+ return MessageDigest.isEqual(myFinished, verifyData);
+ }
+
+ /*
+ * Perform the actual finished message calculation.
+ */
+ private byte[] getFinished(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);
+ }
+
+ if (protocolVersion.useTLS10PlusSpec()) {
+ // TLS 1.0+
+ try {
+ byte[] seed;
+ String prfAlg;
+ PRF prf;
+
+ // Get the KeyGenerator alg and calculate the seed.
+ if (protocolVersion.useTLS12PlusSpec()) {
+ // TLS 1.2+ or DTLS 1.2+
+ seed = handshakeHash.getFinishedHash();
+
+ prfAlg = "SunTls12Prf";
+ prf = cipherSuite.prfAlg;
+ } else {
+ // TLS 1.0/1.1, DTLS 1.0
+ MessageDigest md5Clone = handshakeHash.getMD5Clone();
+ MessageDigest shaClone = handshakeHash.getSHAClone();
+ seed = new byte[36];
+ md5Clone.digest(seed, 0, 16);
+ shaClone.digest(seed, 16, 20);
+
+ 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.
+ */
+ @SuppressWarnings("deprecation")
+ 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. " +
+ "Format received: " + prfKey.getFormat());
+ }
+ byte[] finished = prfKey.getEncoded();
+ return finished;
+ } catch (GeneralSecurityException e) {
+ throw new RuntimeException("PRF failed", e);
+ }
+ } 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];
+ 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);
+ }
+
+ // get the verify_data of the finished message
+ byte[] getVerifyData() {
+ return verifyData;
+ }
+
+ @Override
+ int messageType() { return ht_finished; }
+
+ @Override
+ int messageLength() {
+ return verifyData.length;
+ }
+
+ @Override
+ void send(HandshakeOutStream out) throws IOException {
+ out.write(verifyData);
+ }
+
+ @Override
+ 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
+//
+
+}