| /* |
| * 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 |
| // |
| |
| } |