OpenJDK / jdk / jdk
changeset 50768:68fa3d4026ea
8196584: TLS 1.3 Implementation
Reviewed-by: ascarpino, coffeys, dfuchs, jjiang, jnimeh, mullan, rhalade, ssahoo, valeriep, weijun, wetmore, xuelei
Contributed-by: Adam Petcher <adam.petcher@oracle.com>, Amanda Jiang <amanda.jiang@oracle.com>, Anthony Scarpino <anthony.scarpino@oracle.com>, Bradford Wetmore <bradford.wetmore@oracle.com>, Jamil Nimeh <jamil.j.nimeh@oracle.com>, John Jiang <sha.jiang@oracle.com>, Rajan Halade <rajan.halade@oracle.com>, Sibabrata Sahoo <sibabrata.sahoo@oracle.com>, Valerie Peng <valerie.peng@oracle.com>, Weijun Wang <weijun.wang@oracle.com>, Xuelei Fan <xuelei.fan@oracle.com>
line wrap: on
line diff
--- a/src/java.base/share/classes/com/sun/crypto/provider/TlsMasterSecretGenerator.java Mon Jun 25 21:22:16 2018 +0300 +++ b/src/java.base/share/classes/com/sun/crypto/provider/TlsMasterSecretGenerator.java Mon Jun 25 13:41:39 2018 -0700 @@ -95,7 +95,7 @@ premasterMajor = premaster[0] & 0xff; premasterMinor = premaster[1] & 0xff; } else { - // DH, KRB5, others + // DH, others premasterMajor = -1; premasterMinor = -1; }
--- a/src/java.base/share/classes/com/sun/net/ssl/internal/www/protocol/https/DelegateHttpsURLConnection.java Mon Jun 25 21:22:16 2018 +0300 +++ b/src/java.base/share/classes/com/sun/net/ssl/internal/www/protocol/https/DelegateHttpsURLConnection.java Mon Jun 25 13:41:39 2018 -0700 @@ -113,27 +113,19 @@ * In com.sun.net.ssl.HostnameVerifier the method is defined * as verify(String urlHostname, String certHostname). * This means we need to extract the hostname from the X.509 certificate - * or from the Kerberos principal name, in this wrapper. + * in this wrapper. */ public boolean verify(String hostname, javax.net.ssl.SSLSession session) { try { - String serverName; - // Use ciphersuite to determine whether Kerberos is active. - if (session.getCipherSuite().startsWith("TLS_KRB5")) { - serverName = - HostnameChecker.getServerName(getPeerPrincipal(session)); - - } else { // X.509 - Certificate[] serverChain = session.getPeerCertificates(); - if ((serverChain == null) || (serverChain.length == 0)) { - return false; - } - if (serverChain[0] instanceof X509Certificate == false) { - return false; - } - X509Certificate serverCert = (X509Certificate)serverChain[0]; - serverName = getServername(serverCert); + Certificate[] serverChain = session.getPeerCertificates(); + if ((serverChain == null) || (serverChain.length == 0)) { + return false; } + if (serverChain[0] instanceof X509Certificate == false) { + return false; + } + X509Certificate serverCert = (X509Certificate)serverChain[0]; + String serverName = getServername(serverCert); if (serverName == null) { return false; } @@ -144,23 +136,6 @@ } /* - * Get the peer principal from the session - */ - private Principal getPeerPrincipal(javax.net.ssl.SSLSession session) - throws javax.net.ssl.SSLPeerUnverifiedException - { - Principal principal; - try { - principal = session.getPeerPrincipal(); - } catch (AbstractMethodError e) { - // if the provider does not support it, return null, since - // we need it only for Kerberos. - principal = null; - } - return principal; - } - - /* * Extract the name of the SSL server from the certificate. * * Note this code is essentially a subset of the hostname extraction
--- a/src/java.base/share/classes/module-info.java Mon Jun 25 21:22:16 2018 +0300 +++ b/src/java.base/share/classes/module-info.java Mon Jun 25 13:41:39 2018 -0700 @@ -360,7 +360,6 @@ // JDK-internal service types uses jdk.internal.logger.DefaultLoggerFinder; - uses sun.security.ssl.ClientKeyExchangeService; uses sun.text.spi.JavaTimeDateTimePatternProvider; uses sun.util.spi.CalendarProvider; uses sun.util.locale.provider.LocaleDataMetaInfo;
--- a/src/java.base/share/classes/sun/net/www/protocol/https/HttpsClient.java Mon Jun 25 21:22:16 2018 +0300 +++ b/src/java.base/share/classes/sun/net/www/protocol/https/HttpsClient.java Mon Jun 25 13:41:39 2018 -0700 @@ -608,26 +608,17 @@ HostnameChecker checker = HostnameChecker.getInstance( HostnameChecker.TYPE_TLS); - // Use ciphersuite to determine whether Kerberos is present. - if (cipher.startsWith("TLS_KRB5")) { - if (!HostnameChecker.match(host, getPeerPrincipal())) { - throw new SSLPeerUnverifiedException("Hostname checker" + - " failed for Kerberos"); - } - } else { // X.509 + // get the subject's certificate + peerCerts = session.getPeerCertificates(); - // get the subject's certificate - peerCerts = session.getPeerCertificates(); - - X509Certificate peerCert; - if (peerCerts[0] instanceof - java.security.cert.X509Certificate) { - peerCert = (java.security.cert.X509Certificate)peerCerts[0]; - } else { - throw new SSLPeerUnverifiedException(""); - } - checker.match(host, peerCert); + X509Certificate peerCert; + if (peerCerts[0] instanceof + java.security.cert.X509Certificate) { + peerCert = (java.security.cert.X509Certificate)peerCerts[0]; + } else { + throw new SSLPeerUnverifiedException(""); } + checker.match(host, peerCert); // if it doesn't throw an exception, we passed. Return. return;
--- a/src/java.base/share/classes/sun/security/ssl/ALPNExtension.java Mon Jun 25 21:22:16 2018 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,168 +0,0 @@ -/* - * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package sun.security.ssl; - -import java.io.IOException; -import java.nio.charset.*; -import java.util.*; - -import javax.net.ssl.*; - -/* - * [RFC 7301] - * This TLS extension facilitates the negotiation of application-layer protocols - * within the TLS handshake. Clients MAY include an extension of type - * "application_layer_protocol_negotiation" in the (extended) ClientHello - * message. The "extension_data" field of this extension SHALL contain a - * "ProtocolNameList" value: - * - * enum { - * application_layer_protocol_negotiation(16), (65535) - * } ExtensionType; - * - * opaque ProtocolName<1..2^8-1>; - * - * struct { - * ProtocolName protocol_name_list<2..2^16-1> - * } ProtocolNameList; - */ -final class ALPNExtension extends HelloExtension { - - final static int ALPN_HEADER_LENGTH = 1; - final static int MAX_APPLICATION_PROTOCOL_LENGTH = 255; - final static int MAX_APPLICATION_PROTOCOL_LIST_LENGTH = 65535; - private int listLength = 0; // ProtocolNameList length - private List<String> protocolNames = null; - - // constructor for ServerHello - ALPNExtension(String protocolName) throws SSLException { - this(new String[]{ protocolName }); - } - - // constructor for ClientHello - ALPNExtension(String[] protocolNames) throws SSLException { - super(ExtensionType.EXT_ALPN); - if (protocolNames.length == 0) { // never null, never empty - throw new IllegalArgumentException( - "The list of application protocols cannot be empty"); - } - this.protocolNames = Arrays.asList(protocolNames); - for (String p : protocolNames) { - int length = p.getBytes(StandardCharsets.UTF_8).length; - if (length == 0) { - throw new SSLProtocolException( - "Application protocol name is empty"); - } - if (length <= MAX_APPLICATION_PROTOCOL_LENGTH) { - listLength += length + ALPN_HEADER_LENGTH; - } else { - throw new SSLProtocolException( - "Application protocol name is too long: " + p); - } - if (listLength > MAX_APPLICATION_PROTOCOL_LIST_LENGTH) { - throw new SSLProtocolException( - "Application protocol name list is too long"); - } - } - } - - // constructor for ServerHello for parsing ALPN extension - ALPNExtension(HandshakeInStream s, int len) throws IOException { - super(ExtensionType.EXT_ALPN); - - if (len >= 2) { - listLength = s.getInt16(); // list length - if (listLength < 2 || listLength + 2 != len) { - throw new SSLProtocolException( - "Invalid " + type + " extension: incorrect list length " + - "(length=" + listLength + ")"); - } - } else { - throw new SSLProtocolException( - "Invalid " + type + " extension: insufficient data " + - "(length=" + len + ")"); - } - - int remaining = listLength; - this.protocolNames = new ArrayList<>(); - while (remaining > 0) { - // opaque ProtocolName<1..2^8-1>; // RFC 7301 - byte[] bytes = s.getBytes8(); - if (bytes.length == 0) { - throw new SSLProtocolException("Invalid " + type + - " extension: empty application protocol name"); - } - String p = - new String(bytes, StandardCharsets.UTF_8); // app protocol - protocolNames.add(p); - remaining -= bytes.length + ALPN_HEADER_LENGTH; - } - - if (remaining != 0) { - throw new SSLProtocolException( - "Invalid " + type + " extension: extra data " + - "(length=" + remaining + ")"); - } - } - - List<String> getPeerAPs() { - return protocolNames; - } - - /* - * Return the length in bytes, including extension type and length fields. - */ - @Override - int length() { - return 6 + listLength; - } - - @Override - void send(HandshakeOutStream s) throws IOException { - s.putInt16(type.id); - s.putInt16(listLength + 2); // length of extension_data - s.putInt16(listLength); // length of ProtocolNameList - - for (String p : protocolNames) { - s.putBytes8(p.getBytes(StandardCharsets.UTF_8)); - } - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - if (protocolNames == null || protocolNames.isEmpty()) { - sb.append("<empty>"); - } else { - for (String protocolName : protocolNames) { - sb.append("[" + protocolName + "]"); - } - } - - return "Extension " + type + - ", protocol names: " + sb; - } -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/java.base/share/classes/sun/security/ssl/Alert.java Mon Jun 25 13:41:39 2018 -0700 @@ -0,0 +1,270 @@ +/* + * Copyright (c) 2003, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package sun.security.ssl; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.text.MessageFormat; +import java.util.Locale; +import javax.net.ssl.SSLException; +import javax.net.ssl.SSLHandshakeException; + +/** + * SSL/(D)TLS Alter description + */ +enum Alert { + // Please refer to TLS Alert Registry for the latest (D)TLS Alert values: + // https://www.iana.org/assignments/tls-parameters/ + CLOSE_NOTIFY ((byte)0, "close_notify", false), + UNEXPECTED_MESSAGE ((byte)10, "unexpected_message", false), + BAD_RECORD_MAC ((byte)20, "bad_record_mac", false), + DECRYPTION_FAILED ((byte)21, "decryption_failed", false), + RECORD_OVERFLOW ((byte)22, "record_overflow", false), + DECOMPRESSION_FAILURE ((byte)30, "decompression_failure", false), + HANDSHAKE_FAILURE ((byte)40, "handshake_failure", true), + NO_CERTIFICATE ((byte)41, "no_certificate", true), + BAD_CERTIFICATE ((byte)42, "bad_certificate", true), + UNSUPPORTED_CERTIFCATE ((byte)43, "unsupported_certificate", true), + CERTIFICATE_REVOKED ((byte)44, "certificate_revoked", true), + CERTIFICATE_EXPIRED ((byte)45, "certificate_expired", true), + CERTIFICATE_UNKNOWN ((byte)46, "certificate_unknown", true), + ILLEGAL_PARAMETER ((byte)47, "illegal_parameter", true), + UNKNOWN_CA ((byte)48, "unknown_ca", true), + ACCESS_DENIED ((byte)49, "access_denied", true), + DECODE_ERROR ((byte)50, "decode_error", true), + DECRYPT_ERROR ((byte)51, "decrypt_error", true), + EXPORT_RESTRICTION ((byte)60, "export_restriction", true), + PROTOCOL_VERSION ((byte)70, "protocol_version", true), + INSUFFICIENT_SECURITY ((byte)71, "insufficient_security", true), + INTERNAL_ERROR ((byte)80, "internal_error", false), + INAPPROPRIATE_FALLBACK ((byte)86, "inappropriate_fallback", false), + USER_CANCELED ((byte)90, "user_canceled", false), + NO_RENEGOTIATION ((byte)100, "no_renegotiation", true), + MISSING_EXTENSION ((byte)109, "missing_extension", true), + UNSUPPORTED_EXTENSION ((byte)110, "unsupported_extension", true), + CERT_UNOBTAINABLE ((byte)111, "certificate_unobtainable", true), + UNRECOGNIZED_NAME ((byte)112, "unrecognized_name", true), + BAD_CERT_STATUS_RESPONSE((byte)113, + "bad_certificate_status_response", true), + BAD_CERT_HASH_VALUE ((byte)114, "bad_certificate_hash_value", true), + UNKNOWN_PSK_IDENTITY ((byte)115, "unknown_psk_identity", true), + CERTIFICATE_REQUIRED ((byte)116, "certificate_required", true), + NO_APPLICATION_PROTOCOL ((byte)120, "no_application_protocol", true); + + // ordinal value of the Alert + final byte id; + + // description of the Alert + final String description; + + // Does tha alert happen during handshake only? + final boolean handshakeOnly; + + // Alert message consumer + static final SSLConsumer alertConsumer = new AlertConsumer(); + + private Alert(byte id, String description, boolean handshakeOnly) { + this.id = id; + this.description = description; + this.handshakeOnly = handshakeOnly; + } + + static Alert valueOf(byte id) { + for (Alert al : Alert.values()) { + if (al.id == id) { + return al; + } + } + + return null; + } + + static String nameOf(byte id) { + for (Alert al : Alert.values()) { + if (al.id == id) { + return al.description; + } + } + + return "UNKNOWN ALERT (" + (id & 0x0FF) + ")"; + } + + SSLException createSSLException(String reason) { + return createSSLException(reason, null); + } + + SSLException createSSLException(String reason, Throwable cause) { + if (reason == null) { + reason = (cause != null) ? cause.getMessage() : ""; + } + + SSLException ssle = handshakeOnly ? + new SSLHandshakeException(reason) : new SSLException(reason); + if (cause != null) { + ssle.initCause(cause); + } + + return ssle; + } + + /** + * SSL/(D)TLS Alert level. + */ + enum Level { + WARNING ((byte)1, "warning"), + FATAL ((byte)2, "fatal"); + + // ordinal value of the Alert level + final byte level; + + // description of the Alert level + final String description; + + private Level(byte level, String description) { + this.level = level; + this.description = description; + } + + static Level valueOf(byte level) { + for (Level lv : Level.values()) { + if (lv.level == level) { + return lv; + } + } + + return null; + } + + static String nameOf(byte level) { + for (Level lv : Level.values()) { + if (lv.level == level) { + return lv.description; + } + } + + return "UNKNOWN ALERT LEVEL (" + (level & 0x0FF) + ")"; + } + } + + /** + * The Alert message. + */ + private static final class AlertMessage { + private final byte level; // level + private final byte id; // description + + AlertMessage(TransportContext context, + ByteBuffer m) throws IOException { + // struct { + // AlertLevel level; + // AlertDescription description; + // } Alert; + if (m.remaining() != 2) { + context.fatal(Alert.ILLEGAL_PARAMETER, + "Invalid Alert message: no sufficient data"); + } + + this.level = m.get(); // level + this.id = m.get(); // description + } + + @Override + public String toString() { + MessageFormat messageFormat = new MessageFormat( + "\"Alert\": '{'\n" + + " \"level\" : \"{0}\",\n" + + " \"description\": \"{1}\"\n" + + "'}'", + Locale.ENGLISH); + + Object[] messageFields = { + Level.nameOf(level), + Alert.nameOf(id) + }; + + return messageFormat.format(messageFields); + } + } + + /** + * Consumer of alert messages + */ + private static final class AlertConsumer implements SSLConsumer { + // Prevent instantiation of this class. + private AlertConsumer() { + // blank + } + + @Override + public void consume(ConnectionContext context, + ByteBuffer m) throws IOException { + TransportContext tc = (TransportContext)context; + + AlertMessage am = new AlertMessage(tc, m); + if (SSLLogger.isOn && SSLLogger.isOn("ssl")) { + SSLLogger.fine("Received alert message", am); + } + + Level level = Level.valueOf(am.level); + Alert alert = Alert.valueOf(am.id); + if (alert == Alert.CLOSE_NOTIFY) { + if (tc.handshakeContext != null) { + tc.fatal(Alert.UNEXPECTED_MESSAGE, + "Received close_notify during handshake"); + } + + tc.isInputCloseNotified = true; + tc.closeInbound(); + } else if ((level == Level.WARNING) && (alert != null)) { + // Terminate the connection if an alert with a level of warning + // is received during handshaking, except the no_certificate + // warning. + if (alert.handshakeOnly && (tc.handshakeContext != null)) { + // It's OK to get a no_certificate alert from a client of + // which we requested client authentication. However, + // if we required it, then this is not acceptable. + if (tc.sslConfig.isClientMode || + alert != Alert.NO_CERTIFICATE || + (tc.sslConfig.clientAuthType != + ClientAuthType.CLIENT_AUTH_REQUESTED)) { + tc.fatal(Alert.HANDSHAKE_FAILURE, + "received handshake warning: " + alert.description); + } // Otherwise, ignore the warning + } // Otherwise, ignore the warning. + } else { // fatal or unknown + String diagnostic; + if (alert == null) { + alert = Alert.UNEXPECTED_MESSAGE; + diagnostic = "Unknown alert description (" + am.id + ")"; + } else { + diagnostic = "Received fatal alert: " + alert.description; + } + + tc.fatal(alert, diagnostic, true, null); + } + } + } +}
--- a/src/java.base/share/classes/sun/security/ssl/Alerts.java Mon Jun 25 21:22:16 2018 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,223 +0,0 @@ -/* - * Copyright (c) 2003, 2015, 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 javax.net.ssl.*; - -/* - * A simple class to congregate alerts, their definitions, and common - * support methods. - */ - -final class Alerts { - - /* - * Alerts are always a fixed two byte format (level/description). - */ - - // warnings and fatal errors are package private facilities/constants - - // Alert levels (enum AlertLevel) - static final byte alert_warning = 1; - static final byte alert_fatal = 2; - - /* - * Alert descriptions (enum AlertDescription) - * - * We may not use them all in our processing, but if someone - * sends us one, we can at least convert it to a string for the - * user. - */ - static final byte alert_close_notify = 0; - static final byte alert_unexpected_message = 10; - static final byte alert_bad_record_mac = 20; - static final byte alert_decryption_failed = 21; - static final byte alert_record_overflow = 22; - static final byte alert_decompression_failure = 30; - static final byte alert_handshake_failure = 40; - static final byte alert_no_certificate = 41; - static final byte alert_bad_certificate = 42; - static final byte alert_unsupported_certificate = 43; - static final byte alert_certificate_revoked = 44; - static final byte alert_certificate_expired = 45; - static final byte alert_certificate_unknown = 46; - static final byte alert_illegal_parameter = 47; - static final byte alert_unknown_ca = 48; - static final byte alert_access_denied = 49; - static final byte alert_decode_error = 50; - static final byte alert_decrypt_error = 51; - static final byte alert_export_restriction = 60; - static final byte alert_protocol_version = 70; - static final byte alert_insufficient_security = 71; - static final byte alert_internal_error = 80; - static final byte alert_user_canceled = 90; - static final byte alert_no_renegotiation = 100; - - // from RFC 3546 (TLS Extensions) - static final byte alert_unsupported_extension = 110; - static final byte alert_certificate_unobtainable = 111; - static final byte alert_unrecognized_name = 112; - static final byte alert_bad_certificate_status_response = 113; - static final byte alert_bad_certificate_hash_value = 114; - - // from RFC 7301 (TLS ALPN Extension) - static final byte alert_no_application_protocol = 120; - - static String alertDescription(byte code) { - switch (code) { - - case alert_close_notify: - return "close_notify"; - case alert_unexpected_message: - return "unexpected_message"; - case alert_bad_record_mac: - return "bad_record_mac"; - case alert_decryption_failed: - return "decryption_failed"; - case alert_record_overflow: - return "record_overflow"; - case alert_decompression_failure: - return "decompression_failure"; - case alert_handshake_failure: - return "handshake_failure"; - case alert_no_certificate: - return "no_certificate"; - case alert_bad_certificate: - return "bad_certificate"; - case alert_unsupported_certificate: - return "unsupported_certificate"; - case alert_certificate_revoked: - return "certificate_revoked"; - case alert_certificate_expired: - return "certificate_expired"; - case alert_certificate_unknown: - return "certificate_unknown"; - case alert_illegal_parameter: - return "illegal_parameter"; - case alert_unknown_ca: - return "unknown_ca"; - case alert_access_denied: - return "access_denied"; - case alert_decode_error: - return "decode_error"; - case alert_decrypt_error: - return "decrypt_error"; - case alert_export_restriction: - return "export_restriction"; - case alert_protocol_version: - return "protocol_version"; - case alert_insufficient_security: - return "insufficient_security"; - case alert_internal_error: - return "internal_error"; - case alert_user_canceled: - return "user_canceled"; - case alert_no_renegotiation: - return "no_renegotiation"; - case alert_unsupported_extension: - return "unsupported_extension"; - case alert_certificate_unobtainable: - return "certificate_unobtainable"; - case alert_unrecognized_name: - return "unrecognized_name"; - case alert_bad_certificate_status_response: - return "bad_certificate_status_response"; - case alert_bad_certificate_hash_value: - return "bad_certificate_hash_value"; - case alert_no_application_protocol: - return "no_application_protocol"; - - default: - return "<UNKNOWN ALERT: " + (code & 0x0ff) + ">"; - } - } - - static SSLException getSSLException(byte description, String reason) { - return getSSLException(description, null, reason); - } - - /* - * Try to be a little more specific in our choice of - * exceptions to throw. - */ - static SSLException getSSLException(byte description, Throwable cause, - String reason) { - - SSLException e; - // the SSLException classes do not have a no-args constructor - // make up a message if there is none - if (reason == null) { - if (cause != null) { - reason = cause.toString(); - } else { - reason = ""; - } - } - switch (description) { - case alert_handshake_failure: - case alert_no_certificate: - case alert_bad_certificate: - case alert_unsupported_certificate: - case alert_certificate_revoked: - case alert_certificate_expired: - case alert_certificate_unknown: - case alert_unknown_ca: - case alert_access_denied: - case alert_decrypt_error: - case alert_export_restriction: - case alert_insufficient_security: - case alert_unsupported_extension: - case alert_certificate_unobtainable: - case alert_unrecognized_name: - case alert_bad_certificate_status_response: - case alert_bad_certificate_hash_value: - case alert_no_application_protocol: - e = new SSLHandshakeException(reason); - break; - - case alert_close_notify: - case alert_unexpected_message: - case alert_bad_record_mac: - case alert_decryption_failed: - case alert_record_overflow: - case alert_decompression_failure: - case alert_illegal_parameter: - case alert_decode_error: - case alert_protocol_version: - case alert_internal_error: - case alert_user_canceled: - case alert_no_renegotiation: - default: - e = new SSLException(reason); - break; - } - - if (cause != null) { - e.initCause(cause); - } - return e; - } -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/java.base/share/classes/sun/security/ssl/AlpnExtension.java Mon Jun 25 13:41:39 2018 -0700 @@ -0,0 +1,514 @@ +/* + * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package sun.security.ssl; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLProtocolException; +import javax.net.ssl.SSLSocket; +import sun.security.ssl.SSLExtension.ExtensionConsumer; +import sun.security.ssl.SSLExtension.SSLExtensionSpec; +import sun.security.ssl.SSLHandshake.HandshakeMessage; + +/** + * Pack of the "application_layer_protocol_negotiation" extensions [RFC 7301]. + */ +final class AlpnExtension { + static final HandshakeProducer chNetworkProducer = new CHAlpnProducer(); + static final ExtensionConsumer chOnLoadConsumer = new CHAlpnConsumer(); + static final HandshakeAbsence chOnLoadAbsence = new CHAlpnAbsence(); + + static final HandshakeProducer shNetworkProducer = new SHAlpnProducer(); + static final ExtensionConsumer shOnLoadConsumer = new SHAlpnConsumer(); + static final HandshakeAbsence shOnLoadAbsence = new SHAlpnAbsence(); + + // Note: we reuse ServerHello operations for EncryptedExtensions for now. + // Please be careful about any code or specification changes in the future. + static final HandshakeProducer eeNetworkProducer = new SHAlpnProducer(); + static final ExtensionConsumer eeOnLoadConsumer = new SHAlpnConsumer(); + static final HandshakeAbsence eeOnLoadAbsence = new SHAlpnAbsence(); + + static final SSLStringizer alpnStringizer = new AlpnStringizer(); + + /** + * The "application_layer_protocol_negotiation" extension. + * + * See RFC 7301 for the specification of this extension. + */ + static final class AlpnSpec implements SSLExtensionSpec { + final List<String> applicationProtocols; + + private AlpnSpec(String[] applicationProtocols) { + this.applicationProtocols = Collections.unmodifiableList( + Arrays.asList(applicationProtocols)); + } + + private AlpnSpec(ByteBuffer buffer) throws IOException { + // ProtocolName protocol_name_list<2..2^16-1>, RFC 7301. + if (buffer.remaining() < 2) { + throw new SSLProtocolException( + "Invalid application_layer_protocol_negotiation: " + + "insufficient data (length=" + buffer.remaining() + ")"); + } + + int listLen = Record.getInt16(buffer); + if (listLen < 2 || listLen != buffer.remaining()) { + throw new SSLProtocolException( + "Invalid application_layer_protocol_negotiation: " + + "incorrect list length (length=" + listLen + ")"); + } + + List<String> protocolNames = new LinkedList<>(); + while (buffer.hasRemaining()) { + // opaque ProtocolName<1..2^8-1>, RFC 7301. + byte[] bytes = Record.getBytes8(buffer); + if (bytes.length == 0) { + throw new SSLProtocolException( + "Invalid application_layer_protocol_negotiation " + + "extension: empty application protocol name"); + } + + String appProtocol = new String(bytes, StandardCharsets.UTF_8); + protocolNames.add(appProtocol); + } + + this.applicationProtocols = + Collections.unmodifiableList(protocolNames); + } + + @Override + public String toString() { + return applicationProtocols.toString(); + } + } + + private static final class AlpnStringizer implements SSLStringizer { + @Override + public String toString(ByteBuffer buffer) { + try { + return (new AlpnSpec(buffer)).toString(); + } catch (IOException ioe) { + // For debug logging only, so please swallow exceptions. + return ioe.getMessage(); + } + } + } + + /** + * Network data producer of the extension in a ClientHello + * handshake message. + */ + private static final class CHAlpnProducer implements HandshakeProducer { + static final int MAX_AP_LENGTH = 255; + static final int MAX_AP_LIST_LENGTH = 65535; + + // Prevent instantiation of this class. + private CHAlpnProducer() { + // blank + } + + @Override + public byte[] produce(ConnectionContext context, + HandshakeMessage message) throws IOException { + // The producing happens in client side only. + ClientHandshakeContext chc = (ClientHandshakeContext)context; + + // Is it a supported and enabled extension? + if (!chc.sslConfig.isAvailable(SSLExtension.CH_ALPN)) { + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.info( + "Ignore client unavailable extension: " + + SSLExtension.CH_ALPN.name); + } + + chc.applicationProtocol = ""; + chc.conContext.applicationProtocol = ""; + return null; + } + + String[] laps = chc.sslConfig.applicationProtocols; + if ((laps == null) || (laps.length == 0)) { + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.info( + "No available application protocols"); + } + return null; + } + + // Produce the extension. + int listLength = 0; // ProtocolNameList length + for (String ap : laps) { + int length = ap.getBytes(StandardCharsets.UTF_8).length; + if (length == 0) { + // log the configuration problem + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.severe( + "Application protocol name cannot be empty"); + } + chc.conContext.fatal(Alert.ILLEGAL_PARAMETER, + "Application protocol name cannot be empty"); + } + + if (length <= MAX_AP_LENGTH) { + // opaque ProtocolName<1..2^8-1>, RFC 7301. + listLength += (length + 1); + } else { + // log the configuration problem + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.severe( + "Application protocol name (" + ap + + ") exceeds the size limit (" + + MAX_AP_LENGTH + " bytes)"); + } + chc.conContext.fatal(Alert.ILLEGAL_PARAMETER, + "Application protocol name (" + ap + + ") exceeds the size limit (" + + MAX_AP_LENGTH + " bytes)"); + } + + if (listLength > MAX_AP_LIST_LENGTH) { + // log the configuration problem + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.severe( + "The configured application protocols (" + + Arrays.toString(laps) + + ") exceed the size limit (" + + MAX_AP_LIST_LENGTH + " bytes)"); + } + chc.conContext.fatal(Alert.ILLEGAL_PARAMETER, + "The configured application protocols (" + + Arrays.toString(laps) + + ") exceed the size limit (" + + MAX_AP_LIST_LENGTH + " bytes)"); + } + } + + // ProtocolName protocol_name_list<2..2^16-1>, RFC 7301. + byte[] extData = new byte[listLength + 2]; + ByteBuffer m = ByteBuffer.wrap(extData); + Record.putInt16(m, listLength); + for (String ap : laps) { + Record.putBytes8(m, ap.getBytes(StandardCharsets.UTF_8)); + } + + // Update the context. + chc.handshakeExtensions.put(SSLExtension.CH_ALPN, + new AlpnSpec(chc.sslConfig.applicationProtocols)); + + return extData; + } + } + + /** + * Network data consumer of the extension in a ClientHello + * handshake message. + */ + private static final class CHAlpnConsumer implements ExtensionConsumer { + // Prevent instantiation of this class. + private CHAlpnConsumer() { + // blank + } + + @Override + public void consume(ConnectionContext context, + HandshakeMessage message, ByteBuffer buffer) throws IOException { + // The consuming happens in server side only. + ServerHandshakeContext shc = (ServerHandshakeContext)context; + + // Is it a supported and enabled extension? + if (!shc.sslConfig.isAvailable(SSLExtension.CH_ALPN)) { + shc.applicationProtocol = ""; + shc.conContext.applicationProtocol = ""; + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.info( + "Ignore server unavailable extension: " + + SSLExtension.CH_ALPN.name); + } + return; // ignore the extension + } + + // Is the extension enabled? + boolean noAPSelector; + if (shc.conContext.transport instanceof SSLEngine) { + noAPSelector = (shc.sslConfig.engineAPSelector == null); + } else { + noAPSelector = (shc.sslConfig.socketAPSelector == null); + } + + boolean noAlpnProtocols = + shc.sslConfig.applicationProtocols == null || + shc.sslConfig.applicationProtocols.length == 0; + if (noAPSelector && noAlpnProtocols) { + shc.applicationProtocol = ""; + shc.conContext.applicationProtocol = ""; + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.fine( + "Ignore server unenabled extension: " + + SSLExtension.CH_ALPN.name); + } + return; // ignore the extension + } + + // Parse the extension. + AlpnSpec spec; + try { + spec = new AlpnSpec(buffer); + } catch (IOException ioe) { + shc.conContext.fatal(Alert.UNEXPECTED_MESSAGE, ioe); + return; // fatal() always throws, make the compiler happy. + } + + // Update the context. + if (noAPSelector) { // noAlpnProtocols is false + List<String> protocolNames = spec.applicationProtocols; + boolean matched = false; + // Use server application protocol preference order. + for (String ap : shc.sslConfig.applicationProtocols) { + if (protocolNames.contains(ap)) { + shc.applicationProtocol = ap; + shc.conContext.applicationProtocol = ap; + matched = true; + break; + } + } + + if (!matched) { + shc.conContext.fatal(Alert.NO_APPLICATION_PROTOCOL, + "No matching application layer protocol values"); + } + } // Otherwise, applicationProtocol will be set by the + // application selector callback later. + + shc.handshakeExtensions.put(SSLExtension.CH_ALPN, spec); + + // No impact on session resumption. + // + // [RFC 7301] Unlike many other TLS extensions, this extension + // does not establish properties of the session, only of the + // connection. When session resumption or session tickets are + // used, the previous contents of this extension are irrelevant, + // and only the values in the new handshake messages are + // considered. + } + } + + /** + * The absence processing if the extension is not present in + * a ClientHello handshake message. + */ + private static final class CHAlpnAbsence implements HandshakeAbsence { + @Override + public void absent(ConnectionContext context, + HandshakeMessage message) throws IOException { + // The producing happens in server side only. + ServerHandshakeContext shc = (ServerHandshakeContext)context; + + // Please don't use the previous negotiated application protocol. + shc.applicationProtocol = ""; + shc.conContext.applicationProtocol = ""; + } + } + + /** + * Network data producer of the extension in the ServerHello + * handshake message. + */ + private static final class SHAlpnProducer implements HandshakeProducer { + // Prevent instantiation of this class. + private SHAlpnProducer() { + // blank + } + + @Override + public byte[] produce(ConnectionContext context, + HandshakeMessage message) throws IOException { + // The producing happens in client side only. + ServerHandshakeContext shc = (ServerHandshakeContext)context; + + // In response to ALPN request only + AlpnSpec requestedAlps = + (AlpnSpec)shc.handshakeExtensions.get(SSLExtension.CH_ALPN); + if (requestedAlps == null) { + // Ignore, this extension was not requested and accepted. + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.fine( + "Ignore unavailable extension: " + + SSLExtension.SH_ALPN.name); + } + + shc.applicationProtocol = ""; + shc.conContext.applicationProtocol = ""; + return null; + } + + List<String> alps = requestedAlps.applicationProtocols; + if (shc.conContext.transport instanceof SSLEngine) { + if (shc.sslConfig.engineAPSelector != null) { + SSLEngine engine = (SSLEngine)shc.conContext.transport; + shc.applicationProtocol = + shc.sslConfig.engineAPSelector.apply(engine, alps); + if ((shc.applicationProtocol == null) || + (!shc.applicationProtocol.isEmpty() && + !alps.contains(shc.applicationProtocol))) { + shc.conContext.fatal(Alert.NO_APPLICATION_PROTOCOL, + "No matching application layer protocol values"); + } + } + } else { + if (shc.sslConfig.socketAPSelector != null) { + SSLSocket socket = (SSLSocket)shc.conContext.transport; + shc.applicationProtocol = + shc.sslConfig.socketAPSelector.apply(socket, alps); + if ((shc.applicationProtocol == null) || + (!shc.applicationProtocol.isEmpty() && + !alps.contains(shc.applicationProtocol))) { + shc.conContext.fatal(Alert.NO_APPLICATION_PROTOCOL, + "No matching application layer protocol values"); + } + } + } + + if ((shc.applicationProtocol == null) || + (shc.applicationProtocol.isEmpty())) { + // Ignore, no negotiated application layer protocol. + shc.applicationProtocol = ""; + shc.conContext.applicationProtocol = ""; + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.warning( + "Ignore, no negotiated application layer protocol"); + } + + return null; + } + + // opaque ProtocolName<1..2^8-1>, RFC 7301. + int listLen = shc.applicationProtocol.length() + 1; + // 1: length byte + // ProtocolName protocol_name_list<2..2^16-1>, RFC 7301. + byte[] extData = new byte[listLen + 2]; // 2: list length + ByteBuffer m = ByteBuffer.wrap(extData); + Record.putInt16(m, listLen); + Record.putBytes8(m, + shc.applicationProtocol.getBytes(StandardCharsets.UTF_8)); + + // Update the context. + shc.conContext.applicationProtocol = shc.applicationProtocol; + + // Clean or register the extension + // + // No further use of the request and respond extension any more. + shc.handshakeExtensions.remove(SSLExtension.CH_ALPN); + + return extData; + } + } + + /** + * Network data consumer of the extension in the ServerHello + * handshake message. + */ + private static final class SHAlpnConsumer implements ExtensionConsumer { + // Prevent instantiation of this class. + private SHAlpnConsumer() { + // blank + } + + @Override + public void consume(ConnectionContext context, + HandshakeMessage message, ByteBuffer buffer) throws IOException { + // The producing happens in client side only. + ClientHandshakeContext chc = (ClientHandshakeContext)context; + + // In response to ALPN request only + AlpnSpec requestedAlps = + (AlpnSpec)chc.handshakeExtensions.get(SSLExtension.CH_ALPN); + if (requestedAlps == null || + requestedAlps.applicationProtocols == null || + requestedAlps.applicationProtocols.isEmpty()) { + chc.conContext.fatal(Alert.UNEXPECTED_MESSAGE, + "Unexpected " + SSLExtension.CH_ALPN.name + " extension"); + } + + // Parse the extension. + AlpnSpec spec; + try { + spec = new AlpnSpec(buffer); + } catch (IOException ioe) { + chc.conContext.fatal(Alert.UNEXPECTED_MESSAGE, ioe); + return; // fatal() always throws, make the compiler happy. + } + + // Only one application protocol is allowed. + if (spec.applicationProtocols.size() != 1) { + chc.conContext.fatal(Alert.UNEXPECTED_MESSAGE, + "Invalid " + SSLExtension.CH_ALPN.name + " extension: " + + "Only one application protocol name " + + "is allowed in ServerHello message"); + } + + // The respond application protocol must be one of the requested. + if (!requestedAlps.applicationProtocols.containsAll( + spec.applicationProtocols)) { + chc.conContext.fatal(Alert.UNEXPECTED_MESSAGE, + "Invalid " + SSLExtension.CH_ALPN.name + " extension: " + + "Only client specified application protocol " + + "is allowed in ServerHello message"); + } + + // Update the context. + chc.applicationProtocol = spec.applicationProtocols.get(0); + chc.conContext.applicationProtocol = chc.applicationProtocol; + + // Clean or register the extension + // + // No further use of the request and respond extension any more. + chc.handshakeExtensions.remove(SSLExtension.CH_ALPN); + } + } + + /** + * The absence processing if the extension is not present in + * the ServerHello handshake message. + */ + private static final class SHAlpnAbsence implements HandshakeAbsence { + @Override + public void absent(ConnectionContext context, + HandshakeMessage message) throws IOException { + // The producing happens in client side only. + ClientHandshakeContext chc = (ClientHandshakeContext)context; + + // Please don't use the previous negotiated application protocol. + chc.applicationProtocol = ""; + chc.conContext.applicationProtocol = ""; + } + } +}
--- a/src/java.base/share/classes/sun/security/ssl/AppInputStream.java Mon Jun 25 21:22:16 2018 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,212 +0,0 @@ -/* - * Copyright (c) 1996, 2015, 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.InputStream; -import java.io.IOException; -import java.nio.ByteBuffer; - -import javax.net.ssl.SSLProtocolException; - -/** - * InputStream for application data as returned by SSLSocket.getInputStream(). - * - * @author David Brownell - */ -final class AppInputStream extends InputStream { - // the buffer size for each read of network data - private static final int READ_BUFFER_SIZE = 4096; - - // static dummy array we use to implement skip() - private static final byte[] SKIP_ARRAY = new byte[256]; - - // the related socket of the input stream - private final SSLSocketImpl socket; - - // the temporary buffer used to read network - private ByteBuffer buffer; - - // Is application data available in the stream? - private boolean appDataIsAvailable; - - // One element array used to implement the single byte read() method - private final byte[] oneByte = new byte[1]; - - AppInputStream(SSLSocketImpl conn) { - this.buffer = ByteBuffer.allocate(READ_BUFFER_SIZE); - this.socket = conn; - this.appDataIsAvailable = false; - } - - /** - * Return the minimum number of bytes that can be read without blocking. - * - * Currently not synchronized. - */ - @Override - public int available() throws IOException { - if ((!appDataIsAvailable) || socket.checkEOF()) { - return 0; - } - - return buffer.remaining(); - } - - /** - * Read a single byte, returning -1 on non-fault EOF status. - */ - @Override - public synchronized int read() throws IOException { - int n = read(oneByte, 0, 1); - if (n <= 0) { // EOF - return -1; - } - return oneByte[0] & 0xFF; - } - - /** - * Reads up to {@code len} bytes of data from the input stream into an - * array of bytes. An attempt is made to read as many as {@code len} bytes, - * but a smaller number may be read. The number of bytes actually read - * is returned as an integer. - * - * If the layer above needs more data, it asks for more, so we - * are responsible only for blocking to fill at most one buffer, - * and returning "-1" on non-fault EOF status. - */ - @Override - public synchronized int read(byte[] b, int off, int len) - throws IOException { - if (b == null) { - throw new NullPointerException(); - } else if (off < 0 || len < 0 || len > b.length - off) { - throw new IndexOutOfBoundsException(); - } else if (len == 0) { - return 0; - } - - if (socket.checkEOF()) { - return -1; - } - - // Read the available bytes at first. - int remains = available(); - if (remains > 0) { - int howmany = Math.min(remains, len); - buffer.get(b, off, howmany); - - return howmany; - } - - appDataIsAvailable = false; - int volume = 0; - - try { - /* - * Read data if needed ... notice that the connection guarantees - * that handshake, alert, and change cipher spec data streams are - * handled as they arrive, so we never see them here. - */ - while(volume == 0) { - // Clear the buffer for a new record reading. - buffer.clear(); - - // - // grow the buffer if needed - // - - // Read the header of a record into the buffer, and return - // the packet size. - int packetLen = socket.bytesInCompletePacket(); - if (packetLen < 0) { // EOF - return -1; - } - - // Is this packet bigger than SSL/TLS normally allows? - if (packetLen > SSLRecord.maxLargeRecordSize) { - throw new SSLProtocolException( - "Illegal packet size: " + packetLen); - } - - if (packetLen > buffer.remaining()) { - buffer = ByteBuffer.allocate(packetLen); - } - - volume = socket.readRecord(buffer); - if (volume < 0) { // EOF - return -1; - } else if (volume > 0) { - appDataIsAvailable = true; - break; - } - } - - int howmany = Math.min(len, volume); - buffer.get(b, off, howmany); - return howmany; - } catch (Exception e) { - // shutdown and rethrow (wrapped) exception as appropriate - socket.handleException(e); - - // dummy for compiler - return -1; - } - } - - - /** - * Skip n bytes. This implementation is somewhat less efficient - * than possible, but not badly so (redundant copy). We reuse - * the read() code to keep things simpler. Note that SKIP_ARRAY - * is static and may garbled by concurrent use, but we are not interested - * in the data anyway. - */ - @Override - public synchronized long skip(long n) throws IOException { - long skipped = 0; - while (n > 0) { - int len = (int)Math.min(n, SKIP_ARRAY.length); - int r = read(SKIP_ARRAY, 0, len); - if (r <= 0) { - break; - } - n -= r; - skipped += r; - } - return skipped; - } - - /* - * Socket close is already synchronized, no need to block here. - */ - @Override - public void close() throws IOException { - socket.close(); - } - - // inherit default mark/reset behavior (throw Exceptions) from InputStream -}
--- a/src/java.base/share/classes/sun/security/ssl/AppOutputStream.java Mon Jun 25 21:22:16 2018 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,93 +0,0 @@ -/* - * Copyright (c) 1996, 2012, 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.OutputStream; -import java.io.IOException; -import java.nio.ByteBuffer; - -/* - * OutputStream for application data as returned by SSLSocket.getOutputStream(). - * - * @author David Brownell - */ -class AppOutputStream extends OutputStream { - - private SSLSocketImpl socket; - - // One element array used to implement the write(byte) method - private final byte[] oneByte = new byte[1]; - - AppOutputStream(SSLSocketImpl conn) { - this.socket = conn; - } - - /** - * Write the data out, NOW. - */ - @Override - public synchronized void write(byte[] b, int off, int len) - throws IOException { - if (b == null) { - throw new NullPointerException(); - } else if (off < 0 || len < 0 || len > b.length - off) { - throw new IndexOutOfBoundsException(); - } else if (len == 0) { - return; - } - - // check if the Socket is invalid (error or closed) - socket.checkWrite(); - - // Delegate the writing to the underlying socket. - try { - socket.writeRecord(b, off, len); - socket.checkWrite(); - } catch (Exception e) { - // shutdown and rethrow (wrapped) exception as appropriate - socket.handleException(e); - } - } - - /** - * Write one byte now. - */ - @Override - public synchronized void write(int i) throws IOException { - oneByte[0] = (byte)i; - write(oneByte, 0, 1); - } - - /* - * Socket close is already synchronized, no need to block here. - */ - @Override - public void close() throws IOException { - socket.close(); - } - - // inherit no-op flush() -}
--- a/src/java.base/share/classes/sun/security/ssl/Authenticator.java Mon Jun 25 21:22:16 2018 +0300 +++ b/src/java.base/share/classes/sun/security/ssl/Authenticator.java Mon Jun 25 13:41:39 2018 -0700 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2015, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2018, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,92 +25,78 @@ package sun.security.ssl; +import java.nio.ByteBuffer; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; import java.util.Arrays; +import javax.crypto.Mac; +import javax.crypto.SecretKey; +import sun.security.ssl.CipherSuite.MacAlg; /** * This class represents an SSL/TLS/DTLS message authentication token, * which encapsulates a sequence number and ensures that attempts to * delete or reorder messages can be detected. - * - * Each connection state contains a sequence number, which is maintained - * separately for read and write states. - * - * For SSL/TLS protocols, the sequence number MUST be set to zero - * whenever a connection state is made the active state. - * - * DTLS uses an explicit sequence number, rather than an implicit one. - * Sequence numbers are maintained separately for each epoch, with - * each sequence number initially being 0 for each epoch. The sequence - * number used to compute the DTLS MAC is the 64-bit value formed by - * concatenating the epoch and the sequence number. - * - * Sequence numbers do not wrap. If an implementation would need to wrap - * a sequence number, it must renegotiate instead. A sequence number is - * incremented after each record: specifically, the first record transmitted - * under a particular connection state MUST use sequence number 0. */ -class Authenticator { - +abstract class Authenticator { // byte array containing the additional authentication information for // each record - private final byte[] block; - - // the block size of SSL v3.0: - // sequence number + record type + + record length - private static final int BLOCK_SIZE_SSL = 8 + 1 + 2; - - // the block size of TLS v1.0 and later: - // sequence number + record type + protocol version + record length - private static final int BLOCK_SIZE_TLS = 8 + 1 + 2 + 2; - - // the block size of DTLS v1.0 and later: - // epoch + sequence number + record type + protocol version + record length - private static final int BLOCK_SIZE_DTLS = 2 + 6 + 1 + 2 + 2; - - private final boolean isDTLS; + protected final byte[] block; // at least 8 bytes for sequence number - /** - * Default construct, no message authentication token is initialized. - * - * Note that this construct can only be called for null MAC - */ - protected Authenticator(boolean isDTLS) { - if (isDTLS) { - // For DTLS protocols, plaintexts use explicit epoch and - // sequence number in each record. The first 8 byte of - // the block is initialized for null MAC so that the - // epoch and sequence number can be acquired to generate - // plaintext records. - block = new byte[8]; - } else { - block = new byte[0]; - } - - this.isDTLS = isDTLS; + private Authenticator(byte[] block) { + this.block = block; } /** * Constructs the message authentication token for the specified * SSL/TLS protocol. */ - Authenticator(ProtocolVersion protocolVersion) { - if (protocolVersion.isDTLSProtocol()) { - block = new byte[BLOCK_SIZE_DTLS]; - block[9] = protocolVersion.major; - block[10] = protocolVersion.minor; + static Authenticator valueOf(ProtocolVersion protocolVersion) { + if (protocolVersion.isDTLS) { + if (protocolVersion.useTLS13PlusSpec()) { + return new DTLS13Authenticator(protocolVersion); + } else { + return new DTLS10Authenticator(protocolVersion); + } + } else { + if (protocolVersion.useTLS13PlusSpec()) { + return new TLS13Authenticator(protocolVersion); + } else if (protocolVersion.useTLS10PlusSpec()) { + return new TLS10Authenticator(protocolVersion); + } else { + return new SSL30Authenticator(); + } + } + } - this.isDTLS = true; - } else if (protocolVersion.v >= ProtocolVersion.TLS10.v) { - block = new byte[BLOCK_SIZE_TLS]; - block[9] = protocolVersion.major; - block[10] = protocolVersion.minor; + @SuppressWarnings({"unchecked"}) + static <T extends Authenticator & MAC> T + valueOf(ProtocolVersion protocolVersion, MacAlg macAlg, + SecretKey key) throws NoSuchAlgorithmException, + InvalidKeyException { + if (protocolVersion.isDTLS) { + if (protocolVersion.useTLS13PlusSpec()) { + throw new RuntimeException("No MacAlg used in DTLS 1.3"); + } else { + return (T)(new DTLS10Mac(protocolVersion, macAlg, key)); + } + } else { + if (protocolVersion.useTLS13PlusSpec()) { + throw new RuntimeException("No MacAlg used in TLS 1.3"); + } else if (protocolVersion.useTLS10PlusSpec()) { + return (T)(new TLS10Mac(protocolVersion, macAlg, key)); + } else { + return (T)(new SSL30Mac(protocolVersion, macAlg, key)); + } + } + } - this.isDTLS = false; - } else { - block = new byte[BLOCK_SIZE_SSL]; + static Authenticator nullTlsMac() { + return new SSLNullMac(); + } - this.isDTLS = false; - } + static Authenticator nullDtlsMac() { + return new DTLSNullMac(); } /** @@ -122,25 +108,7 @@ * * @return true if the sequence number is close to wrap */ - final boolean seqNumOverflow() { - /* - * Conservatively, we don't allow more records to be generated - * when there are only 2^8 sequence numbers left. - */ - if (isDTLS) { - return (block.length != 0 && - // no epoch bytes, block[0] and block[1] - block[2] == (byte)0xFF && block[3] == (byte)0xFF && - block[4] == (byte)0xFF && block[5] == (byte)0xFF && - block[6] == (byte)0xFF); - } else { - return (block.length != 0 && - block[0] == (byte)0xFF && block[1] == (byte)0xFF && - block[2] == (byte)0xFF && block[3] == (byte)0xFF && - block[4] == (byte)0xFF && block[5] == (byte)0xFF && - block[6] == (byte)0xFF); - } - } + abstract boolean seqNumOverflow(); /** * Checks whether the sequence number close to renew. @@ -152,21 +120,7 @@ * * @return true if the sequence number is huge enough to renew */ - final boolean seqNumIsHuge() { - /* - * Conservatively, we should ask for renegotiation when there are - * only 2^32 sequence numbers left. - */ - if (isDTLS) { - return (block.length != 0 && - // no epoch bytes, block[0] and block[1] - block[2] == (byte)0xFF && block[3] == (byte)0xFF); - } else { - return (block.length != 0 && - block[0] == (byte)0xFF && block[1] == (byte)0xFF && - block[2] == (byte)0xFF && block[3] == (byte)0xFF); - } - } + abstract boolean seqNumIsHuge(); /** * Gets the current sequence number, including the epoch number for @@ -181,14 +135,9 @@ /** * Sets the epoch number (only apply to DTLS protocols). */ - final void setEpochNumber(int epoch) { - if (!isDTLS) { - throw new RuntimeException( + void setEpochNumber(int epoch) { + throw new UnsupportedOperationException( "Epoch numbers apply to DTLS protocols only"); - } - - block[0] = (byte)((epoch >> 8) & 0xFF); - block[1] = (byte)(epoch & 0xFF); } /** @@ -208,7 +157,7 @@ /** * Acquires the current message authentication information with the * specified record type and fragment length, and then increases the - * sequence number. + * sequence number if using implicit sequence number. * * @param type the record type * @param length the fragment of the record @@ -216,32 +165,465 @@ * * @return the byte array of the current message authentication information */ - final byte[] acquireAuthenticationBytes( + byte[] acquireAuthenticationBytes( byte type, int length, byte[] sequence) { + throw new UnsupportedOperationException("Used by AEAD algorithms only"); + } + + private static class SSLAuthenticator extends Authenticator { + private SSLAuthenticator(byte[] block) { + super(block); + } + + @Override + boolean seqNumOverflow() { + /* + * Conservatively, we don't allow more records to be generated + * when there are only 2^8 sequence numbers left. + */ + return (block.length != 0 && + block[0] == (byte)0xFF && block[1] == (byte)0xFF && + block[2] == (byte)0xFF && block[3] == (byte)0xFF && + block[4] == (byte)0xFF && block[5] == (byte)0xFF && + block[6] == (byte)0xFF); + } + + @Override + boolean seqNumIsHuge() { + return (block.length != 0 && + block[0] == (byte)0xFF && block[1] == (byte)0xFF && + block[2] == (byte)0xFF && block[3] == (byte)0xFF); + } + } + + // For null MAC only. + private static class SSLNullAuthenticator extends SSLAuthenticator { + private SSLNullAuthenticator() { + super(new byte[8]); + } + } + + // For SSL 3.0 + private static class SSL30Authenticator extends SSLAuthenticator { + // Block size of SSL v3.0: + // sequence number + record type + + record length + private static final int BLOCK_SIZE = 11; // 8 + 1 + 2 - byte[] copy = block.clone(); - if (sequence != null) { - if (sequence.length != 8) { - throw new RuntimeException( - "Insufficient explicit sequence number bytes"); + private SSL30Authenticator() { + super(new byte[BLOCK_SIZE]); + } + + @Override + byte[] acquireAuthenticationBytes( + byte type, int length, byte[] sequence) { + byte[] ad = block.clone(); + + // Increase the implicit sequence number in the block array. + increaseSequenceNumber(); + + ad[8] = type; + ad[9] = (byte)(length >> 8); + ad[10] = (byte)(length); + + return ad; + } + } + + // For TLS 1.0 - 1.2 + private static class TLS10Authenticator extends SSLAuthenticator { + // Block size of TLS v1.0/1.1/1.2. + // sequence number + record type + protocol version + record length + private static final int BLOCK_SIZE = 13; // 8 + 1 + 2 + 2 + + private TLS10Authenticator(ProtocolVersion protocolVersion) { + super(new byte[BLOCK_SIZE]); + block[9] = protocolVersion.major; + block[10] = protocolVersion.minor; + } + + @Override + byte[] acquireAuthenticationBytes( + byte type, int length, byte[] sequence) { + byte[] ad = block.clone(); + if (sequence != null) { + if (sequence.length != 8) { + throw new RuntimeException( + "Insufficient explicit sequence number bytes"); + } + + System.arraycopy(sequence, 0, ad, 0, sequence.length); + } else { // Otherwise, use the implicit sequence number. + // Increase the implicit sequence number in the block array. + increaseSequenceNumber(); } - System.arraycopy(sequence, 0, copy, 0, sequence.length); - } // Otherwise, use the implicit sequence number. + ad[8] = type; + ad[11] = (byte)(length >> 8); + ad[12] = (byte)(length); + + return ad; + } + } + + // For TLS 1.3 + private static final class TLS13Authenticator extends SSLAuthenticator { + // Block size of TLS v1.3: + // record type + protocol version + record length + sequence number + private static final int BLOCK_SIZE = 13; // 1 + 2 + 2 + 8 + + private TLS13Authenticator(ProtocolVersion protocolVersion) { + super(new byte[BLOCK_SIZE]); + block[9] = ProtocolVersion.TLS12.major; + block[10] = ProtocolVersion.TLS12.minor; + } + + @Override + byte[] acquireAuthenticationBytes( + byte type, int length, byte[] sequence) { + byte[] ad = Arrays.copyOfRange(block, 8, 13); + + // Increase the implicit sequence number in the block array. + increaseSequenceNumber(); + + ad[0] = type; + ad[3] = (byte)(length >> 8); + ad[4] = (byte)(length & 0xFF); + + return ad; + } + } + + private static class DTLSAuthenticator extends Authenticator { + private DTLSAuthenticator(byte[] block) { + super(block); + } - if (block.length != 0) { - copy[8] = type; + @Override + boolean seqNumOverflow() { + /* + * Conservatively, we don't allow more records to be generated + * when there are only 2^8 sequence numbers left. + */ + return (block.length != 0 && + // no epoch bytes, block[0] and block[1] + block[2] == (byte)0xFF && block[3] == (byte)0xFF && + block[4] == (byte)0xFF && block[5] == (byte)0xFF && + block[6] == (byte)0xFF); + } + + @Override + boolean seqNumIsHuge() { + return (block.length != 0 && + // no epoch bytes, block[0] and block[1] + block[2] == (byte)0xFF && block[3] == (byte)0xFF); + } + + @Override + void setEpochNumber(int epoch) { + block[0] = (byte)((epoch >> 8) & 0xFF); + block[1] = (byte)(epoch & 0xFF); + } + } - copy[copy.length - 2] = (byte)(length >> 8); - copy[copy.length - 1] = (byte)(length); + // For null MAC only. + private static class DTLSNullAuthenticator extends DTLSAuthenticator { + private DTLSNullAuthenticator() { + // For DTLS protocols, plaintexts use explicit epoch and + // sequence number in each record. The first 8 byte of + // the block is initialized for null MAC so that the + // epoch and sequence number can be acquired to generate + // plaintext records. + super(new byte[8]); + } + } + + // DTLS 1.0/1.2 + private static class DTLS10Authenticator extends DTLSAuthenticator { + // Block size of DTLS v1.0 and later: + // epoch + sequence number + + // record type + protocol version + record length + private static final int BLOCK_SIZE = 13; // 2 + 6 + 1 + 2 + 2; - if (sequence == null || sequence.length != 0) { + private DTLS10Authenticator(ProtocolVersion protocolVersion) { + super(new byte[BLOCK_SIZE]); + block[9] = protocolVersion.major; + block[10] = protocolVersion.minor; + } + + @Override + byte[] acquireAuthenticationBytes( + byte type, int length, byte[] sequence) { + byte[] ad = block.clone(); + if (sequence != null) { + if (sequence.length != 8) { + throw new RuntimeException( + "Insufficient explicit sequence number bytes"); + } + + System.arraycopy(sequence, 0, ad, 0, sequence.length); + } else { // Otherwise, use the implicit sequence number. // Increase the implicit sequence number in the block array. increaseSequenceNumber(); } + + ad[8] = type; + ad[11] = (byte)(length >> 8); + ad[12] = (byte)(length); + + return ad; + } + } + + // DTLS 1.3 + private static final class DTLS13Authenticator extends DTLSAuthenticator { + // Block size of DTLS v1.0 and later: + // epoch + sequence number + + // record type + protocol version + record length + private static final int BLOCK_SIZE = 13; // 2 + 6 + 1 + 2 + 2; + + private DTLS13Authenticator(ProtocolVersion protocolVersion) { + super(new byte[BLOCK_SIZE]); + block[9] = ProtocolVersion.TLS12.major; + block[10] = ProtocolVersion.TLS12.minor; + } + + @Override + byte[] acquireAuthenticationBytes( + byte type, int length, byte[] sequence) { + byte[] ad = Arrays.copyOfRange(block, 8, 13); + + // Increase the implicit sequence number in the block array. + increaseSequenceNumber(); + + ad[0] = type; + ad[3] = (byte)(length >> 8); + ad[4] = (byte)(length & 0xFF); + + return ad; + } + } + + interface MAC { + MacAlg macAlg(); + + /** + * Compute and returns the MAC for the remaining data + * in this ByteBuffer. + * + * On return, the bb position == limit, and limit will + * have not changed. + * + * @param type record type + * @param bb a ByteBuffer in which the position and limit + * demarcate the data to be MAC'd. + * @param isSimulated if true, simulate the MAC computation + * @param sequence the explicit sequence number, or null if using + * the implicit sequence number for the computation + * + * @return the MAC result + */ + byte[] compute(byte type, ByteBuffer bb, + byte[] sequence, boolean isSimulated); + + + /** + * Compute and returns the MAC for the remaining data + * in this ByteBuffer. + * + * On return, the bb position == limit, and limit will + * have not changed. + * + * @param type record type + * @param bb a ByteBuffer in which the position and limit + * demarcate the data to be MAC'd. + * @param isSimulated if true, simulate the MAC computation + * + * @return the MAC result + */ + default byte[] compute(byte type, ByteBuffer bb, boolean isSimulated) { + return compute(type, bb, null, isSimulated); + } + } + + private class MacImpl implements MAC { + // internal identifier for the MAC algorithm + private final MacAlg macAlg; + + // JCE Mac object + private final Mac mac; + + private MacImpl() { + macAlg = MacAlg.M_NULL; + mac = null; + } + + private MacImpl(ProtocolVersion protocolVersion, MacAlg macAlg, + SecretKey key) throws NoSuchAlgorithmException, + InvalidKeyException { + if (macAlg == null) { + throw new RuntimeException("Null MacAlg"); + } + + // using SSL MAC computation? + boolean useSSLMac = (protocolVersion.id < ProtocolVersion.TLS10.id); + String algorithm; + switch (macAlg) { + case M_MD5: + algorithm = useSSLMac ? "SslMacMD5" : "HmacMD5"; + break; + case M_SHA: + algorithm = useSSLMac ? "SslMacSHA1" : "HmacSHA1"; + break; + case M_SHA256: + algorithm = "HmacSHA256"; // TLS 1.2+ + break; + case M_SHA384: + algorithm = "HmacSHA384"; // TLS 1.2+ + break; + default: + throw new RuntimeException("Unknown MacAlg " + macAlg); + } + + Mac m = JsseJce.getMac(algorithm); + m.init(key); + this.macAlg = macAlg; + this.mac = m; + } + + @Override + public MacAlg macAlg() { + return macAlg; } - return copy; + @Override + public byte[] compute(byte type, ByteBuffer bb, + byte[] sequence, boolean isSimulated) { + + if (macAlg.size == 0) { + return new byte[0]; + } + + if (!isSimulated) { + // Uses the explicit sequence number for the computation. + byte[] additional = + acquireAuthenticationBytes(type, bb.remaining(), sequence); + mac.update(additional); + } + mac.update(bb); + + return mac.doFinal(); + } + } + + // NULL SSL MAC + private static final + class SSLNullMac extends SSLNullAuthenticator implements MAC { + private final MacImpl macImpl; + public SSLNullMac() { + super(); + this.macImpl = new MacImpl(); + } + + @Override + public MacAlg macAlg() { + return macImpl.macAlg; + } + + @Override + public byte[] compute(byte type, ByteBuffer bb, + byte[] sequence, boolean isSimulated) { + return macImpl.compute(type, bb, sequence, isSimulated); + } + } + + // For SSL 3.0 + private static final + class SSL30Mac extends SSL30Authenticator implements MAC { + private final MacImpl macImpl; + public SSL30Mac(ProtocolVersion protocolVersion, + MacAlg macAlg, SecretKey key) throws NoSuchAlgorithmException, + InvalidKeyException { + super(); + this.macImpl = new MacImpl(protocolVersion, macAlg, key); + } + + @Override + public MacAlg macAlg() { + return macImpl.macAlg; + } + + @Override + public byte[] compute(byte type, ByteBuffer bb, + byte[] sequence, boolean isSimulated) { + return macImpl.compute(type, bb, sequence, isSimulated); + } + } + + // For TLS 1.0 - 1.2 + private static final + class TLS10Mac extends TLS10Authenticator implements MAC { + private final MacImpl macImpl; + public TLS10Mac(ProtocolVersion protocolVersion, + MacAlg macAlg, SecretKey key) throws NoSuchAlgorithmException, + InvalidKeyException { + super(protocolVersion); + this.macImpl = new MacImpl(protocolVersion, macAlg, key); + } + + @Override + public MacAlg macAlg() { + return macImpl.macAlg; + } + + @Override + public byte[] compute(byte type, ByteBuffer bb, + byte[] sequence, boolean isSimulated) { + return macImpl.compute(type, bb, sequence, isSimulated); + } + } + + // NULL DTLS MAC + private static final + class DTLSNullMac extends DTLSNullAuthenticator implements MAC { + private final MacImpl macImpl; + public DTLSNullMac() { + super(); + this.macImpl = new MacImpl(); + } + + @Override + public MacAlg macAlg() { + return macImpl.macAlg; + } + + @Override + public byte[] compute(byte type, ByteBuffer bb, + byte[] sequence, boolean isSimulated) { + return macImpl.compute(type, bb, sequence, isSimulated); + } + } + + // DTLS 1.0/1.2 + private static final class DTLS10Mac + extends DTLS10Authenticator implements MAC { + private final MacImpl macImpl; + public DTLS10Mac(ProtocolVersion protocolVersion, + MacAlg macAlg, SecretKey key) throws NoSuchAlgorithmException, + InvalidKeyException { + super(protocolVersion); + this.macImpl = new MacImpl(protocolVersion, macAlg, key); + } + + @Override + public MacAlg macAlg() { + return macImpl.macAlg; + } + + @Override + public byte[] compute(byte type, ByteBuffer bb, + byte[] sequence, boolean isSimulated) { + return macImpl.compute(type, bb, sequence, isSimulated); + } } static final long toLong(byte[] recordEnS) {
--- a/src/java.base/share/classes/sun/security/ssl/BaseSSLSocketImpl.java Mon Jun 25 21:22:16 2018 +0300 +++ b/src/java.base/share/classes/sun/security/ssl/BaseSSLSocketImpl.java Mon Jun 25 13:41:39 2018 -0700 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2002, 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2002, 2018, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -26,24 +26,23 @@ package sun.security.ssl; import java.io.*; +import java.net.*; import java.nio.channels.SocketChannel; -import java.net.*; import java.util.Set; - import javax.net.ssl.*; /** - * Abstract base class for SSLSocketImpl. Its purpose is to house code with - * no SSL related logic (or no logic at all). This makes SSLSocketImpl shorter - * and easier to read. It contains a few constants and static methods plus - * overridden java.net.Socket methods. + * Abstract base class for SSLSocketImpl. + * + * Its purpose is to house code with no SSL related logic (or no logic at all). + * This makes SSLSocketImpl shorter and easier to read. It contains a few + * constants and static methods plus overridden java.net.Socket methods. * * Methods are defined final to ensure that they are not accidentally * overridden in SSLSocketImpl. * * @see javax.net.ssl.SSLSocket * @see SSLSocketImpl - * */ abstract class BaseSSLSocketImpl extends SSLSocket { @@ -92,7 +91,7 @@ "com.sun.net.ssl.requireCloseNotify"; static final boolean requireCloseNotify = - Debug.getBooleanProperty(PROP_NAME, false); + Utilities.getBooleanProperty(PROP_NAME, false); // // MISC SOCKET METHODS
--- a/src/java.base/share/classes/sun/security/ssl/ByteBufferInputStream.java Mon Jun 25 21:22:16 2018 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,187 +0,0 @@ -/* - * Copyright (c) 2003, 2014, 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.nio.*; - -/** - * A simple InputStream which uses ByteBuffers as it's backing store. - * <P> - * The only IOException should come if the InputStream has been closed. - * All other IOException should not occur because all the data is local. - * Data reads on an exhausted ByteBuffer returns a -1. - * - * @author Brad Wetmore - */ -class ByteBufferInputStream extends InputStream { - - ByteBuffer bb; - - ByteBufferInputStream(ByteBuffer bb) { - this.bb = bb; - } - - /** - * Returns a byte from the ByteBuffer. - * - * Increments position(). - */ - @Override - public int read() throws IOException { - - if (bb == null) { - throw new IOException("read on a closed InputStream"); - } - - if (bb.remaining() == 0) { - return -1; - } - - return (bb.get() & 0xFF); // need to be in the range 0 to 255 - } - - /** - * Returns a byte array from the ByteBuffer. - * - * Increments position(). - */ - @Override - public int read(byte[] b) throws IOException { - - if (bb == null) { - throw new IOException("read on a closed InputStream"); - } - - return read(b, 0, b.length); - } - - /** - * Returns a byte array from the ByteBuffer. - * - * Increments position(). - */ - @Override - public int read(byte[] b, int off, int len) throws IOException { - - if (bb == null) { - throw new IOException("read on a closed InputStream"); - } - - if (b == null) { - throw new NullPointerException(); - } else if (off < 0 || len < 0 || len > b.length - off) { - throw new IndexOutOfBoundsException(); - } else if (len == 0) { - return 0; - } - - int length = Math.min(bb.remaining(), len); - if (length == 0) { - return -1; - } - - bb.get(b, off, length); - return length; - } - - /** - * Skips over and discards <code>n</code> bytes of data from this input - * stream. - */ - @Override - public long skip(long n) throws IOException { - - if (bb == null) { - throw new IOException("skip on a closed InputStream"); - } - - if (n <= 0) { - return 0; - } - - /* - * ByteBuffers have at most an int, so lose the upper bits. - * The contract allows this. - */ - int nInt = (int) n; - int skip = Math.min(bb.remaining(), nInt); - - bb.position(bb.position() + skip); - - return nInt; - } - - /** - * Returns the number of bytes that can be read (or skipped over) - * from this input stream without blocking by the next caller of a - * method for this input stream. - */ - @Override - public int available() throws IOException { - - if (bb == null) { - throw new IOException("available on a closed InputStream"); - } - - return bb.remaining(); - } - - /** - * Closes this input stream and releases any system resources associated - * with the stream. - * - * @exception IOException if an I/O error occurs. - */ - @Override - public void close() throws IOException { - bb = null; - } - - /** - * Marks the current position in this input stream. - */ - @Override - public synchronized void mark(int readlimit) {} - - /** - * Repositions this stream to the position at the time the - * <code>mark</code> method was last called on this input stream. - */ - @Override - public synchronized void reset() throws IOException { - throw new IOException("mark/reset not supported"); - } - - /** - * Tests if this input stream supports the <code>mark</code> and - * <code>reset</code> methods. - */ - @Override - public boolean markSupported() { - return false; - } -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/java.base/share/classes/sun/security/ssl/CertSignAlgsExtension.java Mon Jun 25 13:41:39 2018 -0700 @@ -0,0 +1,348 @@ +/* + * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package sun.security.ssl; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.List; +import sun.security.ssl.SSLExtension.ExtensionConsumer; +import sun.security.ssl.SSLHandshake.HandshakeMessage; +import sun.security.ssl.SignatureAlgorithmsExtension.SignatureSchemesSpec; + +/** + * Pack of the "signature_algorithms_cert" extensions. + */ +final class CertSignAlgsExtension { + static final HandshakeProducer chNetworkProducer = + new CHCertSignatureSchemesProducer(); + static final ExtensionConsumer chOnLoadConsumer = + new CHCertSignatureSchemesConsumer(); + static final HandshakeConsumer chOnTradeConsumer = + new CHCertSignatureSchemesUpdate(); + + static final HandshakeProducer crNetworkProducer = + new CRCertSignatureSchemesProducer(); + static final ExtensionConsumer crOnLoadConsumer = + new CRCertSignatureSchemesConsumer(); + static final HandshakeConsumer crOnTradeConsumer = + new CRCertSignatureSchemesUpdate(); + + static final SSLStringizer ssStringizer = + new CertSignatureSchemesStringizer(); + + private static final + class CertSignatureSchemesStringizer implements SSLStringizer { + @Override + public String toString(ByteBuffer buffer) { + try { + return (new SignatureSchemesSpec(buffer)).toString(); + } catch (IOException ioe) { + // For debug logging only, so please swallow exceptions. + return ioe.getMessage(); + } + } + } + + /** + * Network data producer of a "signature_algorithms_cert" extension in + * the ClientHello handshake message. + */ + private static final + class CHCertSignatureSchemesProducer implements HandshakeProducer { + // Prevent instantiation of this class. + private CHCertSignatureSchemesProducer() { + // blank + } + + @Override + public byte[] produce(ConnectionContext context, + HandshakeMessage message) throws IOException { + // The producing happens in client side only. + ClientHandshakeContext chc = (ClientHandshakeContext)context; + + // Is it a supported and enabled extension? + if (!chc.sslConfig.isAvailable( + SSLExtension.CH_SIGNATURE_ALGORITHMS_CERT)) { + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.fine( + "Ignore unavailable " + + "signature_algorithms_cert extension"); + } + + return null; // ignore the extension + } + + // Produce the extension. + if (chc.localSupportedSignAlgs == null) { + chc.localSupportedSignAlgs = + SignatureScheme.getSupportedAlgorithms( + chc.algorithmConstraints, chc.activeProtocols); + } + + int vectorLen = SignatureScheme.sizeInRecord() * + chc.localSupportedSignAlgs.size(); + byte[] extData = new byte[vectorLen + 2]; + ByteBuffer m = ByteBuffer.wrap(extData); + Record.putInt16(m, vectorLen); + for (SignatureScheme ss : chc.localSupportedSignAlgs) { + Record.putInt16(m, ss.id); + } + + // Update the context. + chc.handshakeExtensions.put( + SSLExtension.CH_SIGNATURE_ALGORITHMS_CERT, + new SignatureSchemesSpec(chc.localSupportedSignAlgs)); + + return extData; + } + } + + /** + * Network data consumer of a "signature_algorithms_cert" extension in + * the ClientHello handshake message. + */ + private static final + class CHCertSignatureSchemesConsumer implements ExtensionConsumer { + // Prevent instantiation of this class. + private CHCertSignatureSchemesConsumer() { + // blank + } + + @Override + public void consume(ConnectionContext context, + HandshakeMessage message, ByteBuffer buffer) throws IOException { + // The consuming happens in server side only. + ServerHandshakeContext shc = (ServerHandshakeContext)context; + + // Is it a supported and enabled extension? + if (!shc.sslConfig.isAvailable( + SSLExtension.CH_SIGNATURE_ALGORITHMS_CERT)) { + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.fine( + "Ignore unavailable " + + "signature_algorithms_cert extension"); + } + return; // ignore the extension + } + + // Parse the extension. + SignatureSchemesSpec spec; + try { + spec = new SignatureSchemesSpec(buffer); + } catch (IOException ioe) { + shc.conContext.fatal(Alert.UNEXPECTED_MESSAGE, ioe); + return; // fatal() always throws, make the compiler happy. + } + + // Update the context. + shc.handshakeExtensions.put( + SSLExtension.CH_SIGNATURE_ALGORITHMS_CERT, spec); + + // No impact on session resumption. + } + } + + /** + * After session creation consuming of a "signature_algorithms_cert" + * extension in the ClientHello handshake message. + */ + private static final class CHCertSignatureSchemesUpdate + implements HandshakeConsumer { + // Prevent instantiation of this class. + private CHCertSignatureSchemesUpdate() { + // blank + } + + @Override + public void consume(ConnectionContext context, + HandshakeMessage message) throws IOException { + // The consuming happens in server side only. + ServerHandshakeContext shc = (ServerHandshakeContext)context; + + SignatureSchemesSpec spec = (SignatureSchemesSpec) + shc.handshakeExtensions.get( + SSLExtension.CH_SIGNATURE_ALGORITHMS_CERT); + if (spec == null) { + // Ignore, no signature_algorithms_cert extension requested. + return; + } + + // update the context + List<SignatureScheme> shemes = + SignatureScheme.getSupportedAlgorithms( + shc.algorithmConstraints, shc.negotiatedProtocol, + spec.signatureSchemes); + shc.peerRequestedCertSignSchemes = shemes; + shc.handshakeSession.setPeerSupportedSignatureAlgorithms(shemes); + + if (!shc.isResumption && shc.negotiatedProtocol.useTLS13PlusSpec()) { + if (shc.sslConfig.clientAuthType != + ClientAuthType.CLIENT_AUTH_NONE) { + shc.handshakeProducers.putIfAbsent( + SSLHandshake.CERTIFICATE_REQUEST.id, + SSLHandshake.CERTIFICATE_REQUEST); + } + shc.handshakeProducers.put(SSLHandshake.CERTIFICATE.id, + SSLHandshake.CERTIFICATE); + shc.handshakeProducers.putIfAbsent( + SSLHandshake.CERTIFICATE_VERIFY.id, + SSLHandshake.CERTIFICATE_VERIFY); + } + } + } + + /** + * Network data producer of a "signature_algorithms_cert" extension in + * the CertificateRequest handshake message. + */ + private static final + class CRCertSignatureSchemesProducer implements HandshakeProducer { + // Prevent instantiation of this class. + private CRCertSignatureSchemesProducer() { + // blank + } + + @Override + public byte[] produce(ConnectionContext context, + HandshakeMessage message) throws IOException { + // The producing happens in server side only. + ServerHandshakeContext shc = (ServerHandshakeContext)context; + + // Is it a supported and enabled extension? + if (!shc.sslConfig.isAvailable( + SSLExtension.CH_SIGNATURE_ALGORITHMS_CERT)) { + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.fine( + "Ignore unavailable " + + "signature_algorithms_cert extension"); + } + return null; // ignore the extension + } + + // Produce the extension. + if (shc.localSupportedSignAlgs == null) { + shc.localSupportedSignAlgs = + SignatureScheme.getSupportedAlgorithms( + shc.algorithmConstraints, shc.activeProtocols); + } + + int vectorLen = SignatureScheme.sizeInRecord() * + shc.localSupportedSignAlgs.size(); + byte[] extData = new byte[vectorLen + 2]; + ByteBuffer m = ByteBuffer.wrap(extData); + Record.putInt16(m, vectorLen); + for (SignatureScheme ss : shc.localSupportedSignAlgs) { + Record.putInt16(m, ss.id); + } + + // Update the context. + shc.handshakeExtensions.put( + SSLExtension.CR_SIGNATURE_ALGORITHMS_CERT, + new SignatureSchemesSpec(shc.localSupportedSignAlgs)); + + return extData; + } + } + + /** + * Network data consumer of a "signature_algorithms_cert" extension in + * the CertificateRequest handshake message. + */ + private static final + class CRCertSignatureSchemesConsumer implements ExtensionConsumer { + // Prevent instantiation of this class. + private CRCertSignatureSchemesConsumer() { + // blank + } + @Override + public void consume(ConnectionContext context, + HandshakeMessage message, ByteBuffer buffer) throws IOException { + // The consuming happens in client side only. + ClientHandshakeContext chc = (ClientHandshakeContext)context; + + // Is it a supported and enabled extension? + if (!chc.sslConfig.isAvailable( + SSLExtension.CH_SIGNATURE_ALGORITHMS_CERT)) { + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.fine( + "Ignore unavailable " + + "signature_algorithms_cert extension"); + } + return; // ignore the extension + } + + // Parse the extension. + SignatureSchemesSpec spec; + try { + spec = new SignatureSchemesSpec(buffer); + } catch (IOException ioe) { + chc.conContext.fatal(Alert.UNEXPECTED_MESSAGE, ioe); + return; // fatal() always throws, make the compiler happy. + } + + // Update the context. + chc.handshakeExtensions.put( + SSLExtension.CR_SIGNATURE_ALGORITHMS_CERT, spec); + + // No impact on session resumption. + } + } + + /** + * After session creation consuming of a "signature_algorithms_cert" + * extension in the CertificateRequest handshake message. + */ + private static final class CRCertSignatureSchemesUpdate + implements HandshakeConsumer { + // Prevent instantiation of this class. + private CRCertSignatureSchemesUpdate() { + // blank + } + + @Override + public void consume(ConnectionContext context, + HandshakeMessage message) throws IOException { + // The consuming happens in client side only. + ClientHandshakeContext chc = (ClientHandshakeContext)context; + + SignatureSchemesSpec spec = (SignatureSchemesSpec) + chc.handshakeExtensions.get( + SSLExtension.CR_SIGNATURE_ALGORITHMS_CERT); + if (spec == null) { + // Ignore, no "signature_algorithms_cert" extension requested. + return; + } + + // update the context + List<SignatureScheme> shemes = + SignatureScheme.getSupportedAlgorithms( + chc.algorithmConstraints, chc.negotiatedProtocol, + spec.signatureSchemes); + chc.peerRequestedCertSignSchemes = shemes; + chc.handshakeSession.setPeerSupportedSignatureAlgorithms(shemes); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/java.base/share/classes/sun/security/ssl/CertStatusExtension.java Mon Jun 25 13:41:39 2018 -0700 @@ -0,0 +1,1219 @@ +/* + * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package sun.security.ssl; + +import java.io.IOException; +import java.io.ByteArrayInputStream; +import java.nio.ByteBuffer; +import java.security.cert.Extension; +import java.security.cert.CertificateFactory; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import javax.net.ssl.SSLProtocolException; +import sun.security.provider.certpath.OCSPResponse; +import sun.security.provider.certpath.ResponderId; +import static sun.security.ssl.SSLExtension.CH_STATUS_REQUEST; +import static sun.security.ssl.SSLExtension.CH_STATUS_REQUEST_V2; +import sun.security.ssl.SSLExtension.ExtensionConsumer; +import static sun.security.ssl.SSLExtension.SH_STATUS_REQUEST; +import static sun.security.ssl.SSLExtension.SH_STATUS_REQUEST_V2; +import sun.security.ssl.SSLExtension.SSLExtensionSpec; +import sun.security.ssl.SSLHandshake.HandshakeMessage; +import sun.security.util.DerInputStream; +import sun.security.util.DerValue; +import sun.security.util.HexDumpEncoder; + +/** + * Pack of "status_request" and "status_request_v2" extensions. + */ +final class CertStatusExtension { + static final HandshakeProducer chNetworkProducer = + new CHCertStatusReqProducer(); + static final ExtensionConsumer chOnLoadConsumer = + new CHCertStatusReqConsumer(); + + static final HandshakeProducer shNetworkProducer = + new SHCertStatusReqProducer(); + static final ExtensionConsumer shOnLoadConsumer = + new SHCertStatusReqConsumer(); + + static final HandshakeProducer ctNetworkProducer = + new CTCertStatusResponseProducer(); + static final ExtensionConsumer ctOnLoadConsumer = + new CTCertStatusResponseConsumer(); + + static final SSLStringizer certStatusReqStringizer = + new CertStatusRequestStringizer(); + + static final HandshakeProducer chV2NetworkProducer = + new CHCertStatusReqV2Producer(); + static final ExtensionConsumer chV2OnLoadConsumer = + new CHCertStatusReqV2Consumer(); + + static final HandshakeProducer shV2NetworkProducer = + new SHCertStatusReqV2Producer(); + static final ExtensionConsumer shV2OnLoadConsumer = + new SHCertStatusReqV2Consumer(); + + static final SSLStringizer certStatusReqV2Stringizer = + new CertStatusRequestsStringizer(); + + static final SSLStringizer certStatusRespStringizer = + new CertStatusRespStringizer(); + + /** + * The "status_request" extension. + * + * RFC6066 defines the TLS extension,"status_request" (type 0x5), + * which allows the client to request that the server perform OCSP + * on the client's behalf. + * + * The "extension data" field of this extension contains a + * "CertificateStatusRequest" structure: + * + * struct { + * CertificateStatusType status_type; + * select (status_type) { + * case ocsp: OCSPStatusRequest; + * } request; + * } CertificateStatusRequest; + * + * enum { ocsp(1), (255) } CertificateStatusType; + * + * struct { + * ResponderID responder_id_list<0..2^16-1>; + * Extensions request_extensions; + * } OCSPStatusRequest; + * + * opaque ResponderID<1..2^16-1>; + * opaque Extensions<0..2^16-1>; + */ + static final class CertStatusRequestSpec implements SSLExtensionSpec { + static final CertStatusRequestSpec DEFAULT = + new CertStatusRequestSpec(OCSPStatusRequest.EMPTY_OCSP); + + final CertStatusRequest statusRequest; + + private CertStatusRequestSpec(CertStatusRequest statusRequest) { + this.statusRequest = statusRequest; + } + + private CertStatusRequestSpec(ByteBuffer buffer) throws IOException { + // Is it a empty extension_data? + if (buffer.remaining() == 0) { + // server response + this.statusRequest = null; + return; + } + + if (buffer.remaining() < 1) { + throw new SSLProtocolException( + "Invalid status_request extension: insufficient data"); + } + + byte statusType = (byte)Record.getInt8(buffer); + byte[] encoded = new byte[buffer.remaining()]; + if (encoded.length != 0) { + buffer.get(encoded); + } + if (statusType == CertStatusRequestType.OCSP.id) { + this.statusRequest = new OCSPStatusRequest(statusType, encoded); + } else { + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.info( + "Unknown certificate status request " + + "(status type: " + statusType + ")"); + } + + this.statusRequest = new CertStatusRequest(statusType, encoded); + } + } + + @Override + public String toString() { + return statusRequest == null ? + "<empty>" : statusRequest.toString(); + } + } + + /** + * Defines the CertificateStatus response structure as outlined in + * RFC 6066. This will contain a status response type, plus a single, + * non-empty OCSP response in DER-encoded form. + * + * struct { + * CertificateStatusType status_type; + * select (status_type) { + * case ocsp: OCSPResponse; + * } response; + * } CertificateStatus; + */ + static final class CertStatusResponseSpec implements SSLExtensionSpec { + final CertStatusResponse statusResponse; + + private CertStatusResponseSpec(CertStatusResponse resp) { + this.statusResponse = resp; + } + + private CertStatusResponseSpec(ByteBuffer buffer) throws IOException { + if (buffer.remaining() < 2) { + throw new SSLProtocolException( + "Invalid status_request extension: insufficient data"); + } + + // Get the status type (1 byte) and response data (vector) + byte type = (byte)Record.getInt8(buffer); + byte[] respData = Record.getBytes24(buffer); + + // Create the CertStatusResponse based on the type + if (type == CertStatusRequestType.OCSP.id) { + this.statusResponse = new OCSPStatusResponse(type, respData); + } else { + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.info( + "Unknown certificate status response " + + "(status type: " + type + ")"); + } + + this.statusResponse = new CertStatusResponse(type, respData); + } + } + + @Override + public String toString() { + return statusResponse == null ? + "<empty>" : statusResponse.toString(); + } + } + + private static final + class CertStatusRequestStringizer implements SSLStringizer { + @Override + public String toString(ByteBuffer buffer) { + try { + return (new CertStatusRequestSpec(buffer)).toString(); + } catch (IOException ioe) { + // For debug logging only, so please swallow exceptions. + return ioe.getMessage(); + } + } + } + + private static final + class CertStatusRespStringizer implements SSLStringizer { + @Override + public String toString(ByteBuffer buffer) { + try { + return (new CertStatusResponseSpec(buffer)).toString(); + } catch (IOException ioe) { + // For debug logging only, so please swallow exceptions. + return ioe.getMessage(); + } + } + } + + static enum CertStatusRequestType { + OCSP ((byte)0x01, "ocsp"), // RFC 6066/6961 + OCSP_MULTI ((byte)0x02, "ocsp_multi"); // RFC 6961 + + final byte id; + final String name; + + private CertStatusRequestType(byte id, String name) { + this.id = id; + this.name = name; + } + + /** + * Returns the enum constant of the specified id (see RFC 6066). + */ + static CertStatusRequestType valueOf(byte id) { + for (CertStatusRequestType srt : CertStatusRequestType.values()) { + if (srt.id == id) { + return srt; + } + } + + return null; + } + + static String nameOf(byte id) { + for (CertStatusRequestType srt : CertStatusRequestType.values()) { + if (srt.id == id) { + return srt.name; + } + } + + return "UNDEFINED-CERT-STATUS-TYPE(" + id + ")"; + } + } + + static class CertStatusRequest { + final byte statusType; + final byte[] encodedRequest; + + protected CertStatusRequest(byte statusType, byte[] encodedRequest) { + this.statusType = statusType; + this.encodedRequest = encodedRequest; + } + + @Override + public String toString() { + MessageFormat messageFormat = new MessageFormat( + "\"certificate status type\": {0}\n" + + "\"encoded certificate status\": '{'\n" + + "{1}\n" + + "'}'", + Locale.ENGLISH); + + HexDumpEncoder hexEncoder = new HexDumpEncoder(); + String encoded = hexEncoder.encodeBuffer(encodedRequest); + + Object[] messageFields = { + CertStatusRequestType.nameOf(statusType), + Utilities.indent(encoded) + }; + + return messageFormat.format(messageFields); + } + } + + /* + * RFC6066 defines the TLS extension,"status_request" (type 0x5), + * which allows the client to request that the server perform OCSP + * on the client's behalf. + * + * The RFC defines an OCSPStatusRequest structure: + * + * struct { + * ResponderID responder_id_list<0..2^16-1>; + * Extensions request_extensions; + * } OCSPStatusRequest; + */ + static final class OCSPStatusRequest extends CertStatusRequest { + static final OCSPStatusRequest EMPTY_OCSP; + static final OCSPStatusRequest EMPTY_OCSP_MULTI; + + final List<ResponderId> responderIds; + final List<Extension> extensions; + private final int ridListLen; + private final int extListLen; + + static { + OCSPStatusRequest ocspReq = null; + OCSPStatusRequest multiReq = null; + + try { + ocspReq = new OCSPStatusRequest( + CertStatusRequestType.OCSP.id, + new byte[] {0x00, 0x00, 0x00, 0x00}); + multiReq = new OCSPStatusRequest( + CertStatusRequestType.OCSP_MULTI.id, + new byte[] {0x00, 0x00, 0x00, 0x00}); + } catch (IOException ioe) { + // unlikely + } + + EMPTY_OCSP = ocspReq; + EMPTY_OCSP_MULTI = multiReq; + } + + private OCSPStatusRequest(byte statusType, + byte[] encoded) throws IOException { + super(statusType, encoded); + + if (encoded == null || encoded.length < 4) { + // 2: length of responder_id_list + // +2: length of request_extensions + throw new SSLProtocolException( + "Invalid OCSP status request: insufficient data"); + } + + List<ResponderId> rids = new ArrayList<>(); + List<Extension> exts = new ArrayList<>(); + ByteBuffer m = ByteBuffer.wrap(encoded); + + this.ridListLen = Record.getInt16(m); + if (m.remaining() < (ridListLen + 2)) { + throw new SSLProtocolException( + "Invalid OCSP status request: insufficient data"); + } + + int ridListBytesRemaining = ridListLen; + while (ridListBytesRemaining >= 2) { // 2: length of responder_id + byte[] ridBytes = Record.getBytes16(m); + try { + rids.add(new ResponderId(ridBytes)); + } catch (IOException ioe) { + throw new SSLProtocolException( + "Invalid OCSP status request: invalid responder ID"); + } + ridListBytesRemaining -= ridBytes.length + 2; + } + + if (ridListBytesRemaining != 0) { + throw new SSLProtocolException( + "Invalid OCSP status request: incomplete data"); + } + + byte[] extListBytes = Record.getBytes16(m); + this.extListLen = extListBytes.length; + if (extListLen > 0) { + try { + DerInputStream dis = new DerInputStream(extListBytes); + DerValue[] extSeqContents = + dis.getSequence(extListBytes.length); + for (DerValue extDerVal : extSeqContents) { + exts.add(new sun.security.x509.Extension(extDerVal)); + } + } catch (IOException ioe) { + throw new SSLProtocolException( + "Invalid OCSP status request: invalid extension"); + } + } + + this.responderIds = rids; + this.extensions = exts; + } + + @Override + public String toString() { + MessageFormat messageFormat = new MessageFormat( + "\"certificate status type\": {0}\n" + + "\"OCSP status request\": '{'\n" + + "{1}\n" + + "'}'", + Locale.ENGLISH); + + MessageFormat requestFormat = new MessageFormat( + "\"responder_id\": {0}\n" + + "\"request extensions\": '{'\n" + + "{1}\n" + + "'}'", + Locale.ENGLISH); + + String ridStr = "<empty>"; + if (!responderIds.isEmpty()) { + ridStr = responderIds.toString(); + } + + String extsStr = "<empty>"; + if (!extensions.isEmpty()) { + StringBuilder extBuilder = new StringBuilder(512); + boolean isFirst = true; + for (Extension ext : this.extensions) { + if (isFirst) { + isFirst = false; + } else { + extBuilder.append(",\n"); + } + extBuilder.append( + "{\n" + Utilities.indent(ext.toString()) + "}"); + } + + extsStr = extBuilder.toString(); + } + + Object[] requestFields = { + ridStr, + Utilities.indent(extsStr) + }; + String ocspStatusRequest = requestFormat.format(requestFields); + + Object[] messageFields = { + CertStatusRequestType.nameOf(statusType), + Utilities.indent(ocspStatusRequest) + }; + + return messageFormat.format(messageFields); + } + } + + static class CertStatusResponse { + final byte statusType; + final byte[] encodedResponse; + + protected CertStatusResponse(byte statusType, byte[] respDer) { + this.statusType = statusType; + this.encodedResponse = respDer; + } + + byte[] toByteArray() throws IOException { + // Create a byte array large enough to handle the status_type + // field (1) + OCSP length (3) + OCSP data (variable) + byte[] outData = new byte[encodedResponse.length + 4]; + ByteBuffer buf = ByteBuffer.wrap(outData); + Record.putInt8(buf, statusType); + Record.putBytes24(buf, encodedResponse); + return buf.array(); + } + + @Override + public String toString() { + MessageFormat messageFormat = new MessageFormat( + "\"certificate status response type\": {0}\n" + + "\"encoded certificate status\": '{'\n" + + "{1}\n" + + "'}'", + Locale.ENGLISH); + + HexDumpEncoder hexEncoder = new HexDumpEncoder(); + String encoded = hexEncoder.encodeBuffer(encodedResponse); + + Object[] messageFields = { + CertStatusRequestType.nameOf(statusType), + Utilities.indent(encoded) + }; + + return messageFormat.format(messageFields); + } + } + + static final class OCSPStatusResponse extends CertStatusResponse { + final OCSPResponse ocspResponse; + + private OCSPStatusResponse(byte statusType, + byte[] encoded) throws IOException { + super(statusType, encoded); + + // The DER-encoded OCSP response must not be zero length + if (encoded == null || encoded.length < 1) { + throw new SSLProtocolException( + "Invalid OCSP status response: insufficient data"); + } + + // Otherwise, make an OCSPResponse object from the data + ocspResponse = new OCSPResponse(encoded); + } + + @Override + public String toString() { + MessageFormat messageFormat = new MessageFormat( + "\"certificate status response type\": {0}\n" + + "\"OCSP status response\": '{'\n" + + "{1}\n" + + "'}'", + Locale.ENGLISH); + + Object[] messageFields = { + CertStatusRequestType.nameOf(statusType), + Utilities.indent(ocspResponse.toString()) + }; + + return messageFormat.format(messageFields); + } + } + + /** + * Network data producer of a "status_request" extension in the + * ClientHello handshake message. + */ + private static final + class CHCertStatusReqProducer implements HandshakeProducer { + // Prevent instantiation of this class. + private CHCertStatusReqProducer() { + // blank + } + + @Override + public byte[] produce(ConnectionContext context, + HandshakeMessage message) throws IOException { + // The producing happens in client side only. + ClientHandshakeContext chc = (ClientHandshakeContext)context; + + if (!chc.sslContext.isStaplingEnabled(true)) { + return null; + } + + if (!chc.sslConfig.isAvailable(CH_STATUS_REQUEST)) { + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.fine( + "Ignore unavailable extension: " + + CH_STATUS_REQUEST.name); + } + return null; + } + + // Produce the extension. + // + // We are using empty OCSPStatusRequest at present. May extend to + // support specific responder or extensions later. + byte[] extData = new byte[] {0x01, 0x00, 0x00, 0x00, 0x00}; + + // Update the context. + chc.handshakeExtensions.put( + CH_STATUS_REQUEST, CertStatusRequestSpec.DEFAULT); + + return extData; + } + } + + /** + * Network data consumer of a "status_request" extension in the + * ClientHello handshake message. + */ + private static final + class CHCertStatusReqConsumer implements ExtensionConsumer { + // Prevent instantiation of this class. + private CHCertStatusReqConsumer() { + // blank + } + + @Override + public void consume(ConnectionContext context, + HandshakeMessage message, ByteBuffer buffer) throws IOException { + + // The consuming happens in server side only. + ServerHandshakeContext shc = (ServerHandshakeContext)context; + + if (!shc.sslConfig.isAvailable(CH_STATUS_REQUEST)) { + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.fine("Ignore unavailable extension: " + + CH_STATUS_REQUEST.name); + } + return; // ignore the extension + } + + // Parse the extension. + CertStatusRequestSpec spec; + try { + spec = new CertStatusRequestSpec(buffer); + } catch (IOException ioe) { + shc.conContext.fatal(Alert.UNEXPECTED_MESSAGE, ioe); + return; // fatal() always throws, make the compiler happy. + } + + // Update the context. + shc.handshakeExtensions.put(CH_STATUS_REQUEST, spec); + if (!shc.isResumption && + !shc.negotiatedProtocol.useTLS13PlusSpec()) { + shc.handshakeProducers.put(SSLHandshake.CERTIFICATE_STATUS.id, + SSLHandshake.CERTIFICATE_STATUS); + } // Otherwise, the certificate status presents in server cert. + + // No impact on session resumption. + } + } + + /** + * Network data producer of a "status_request" extension in the + * ServerHello handshake message. + */ + private static final + class SHCertStatusReqProducer implements HandshakeProducer { + // Prevent instantiation of this class. + private SHCertStatusReqProducer() { + // blank + } + + @Override + public byte[] produce(ConnectionContext context, + HandshakeMessage message) throws IOException { + // The producing happens in client side only. + ServerHandshakeContext shc = (ServerHandshakeContext)context; + + // The StaplingParameters in the ServerHandshakeContext will + // contain the info about what kind of stapling (if any) to + // perform and whether this status_request extension should be + // produced or the status_request_v2 (found in a different producer) + // No explicit check is required for isStaplingEnabled here. If + // it is false then stapleParams will be null. If it is true + // then stapleParams may or may not be false and the check below + // is sufficient. + if ((shc.stapleParams == null) || + (shc.stapleParams.statusRespExt != + SSLExtension.CH_STATUS_REQUEST)) { + return null; // Do not produce status_request in ServerHello + } + + // In response to "status_request" extension request only. + CertStatusRequestSpec spec = (CertStatusRequestSpec) + shc.handshakeExtensions.get(CH_STATUS_REQUEST); + if (spec == null) { + // Ignore, no status_request extension requested. + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.finest( + "Ignore unavailable extension: " + + CH_STATUS_REQUEST.name); + } + + return null; // ignore the extension + } + + // Is it a session resuming? + if (shc.isResumption) { + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.finest( + "No status_request response for session resuming"); + } + + return null; // ignore the extension + } + + // The "extension_data" in the extended ServerHello handshake + // message MUST be empty. + byte[] extData = new byte[0]; + + // Update the context. + shc.handshakeExtensions.put( + SH_STATUS_REQUEST, CertStatusRequestSpec.DEFAULT); + + return extData; + } + } + + /** + * Network data consumer of a "status_request" extension in the + * ServerHello handshake message. + */ + private static final + class SHCertStatusReqConsumer implements ExtensionConsumer { + // Prevent instantiation of this class. + private SHCertStatusReqConsumer() { + // blank + } + + @Override + public void consume(ConnectionContext context, + HandshakeMessage message, ByteBuffer buffer) throws IOException { + + // The producing happens in client side only. + ClientHandshakeContext chc = (ClientHandshakeContext)context; + + // In response to "status_request" extension request only. + CertStatusRequestSpec requestedCsr = (CertStatusRequestSpec) + chc.handshakeExtensions.get(CH_STATUS_REQUEST); + if (requestedCsr == null) { + chc.conContext.fatal(Alert.UNEXPECTED_MESSAGE, + "Unexpected status_request extension in ServerHello"); + } + + // Parse the extension. + if (buffer.hasRemaining()) { + chc.conContext.fatal(Alert.UNEXPECTED_MESSAGE, + "Invalid status_request extension in ServerHello message: " + + "the extension data must be empty"); + } + + // Update the context. + chc.handshakeExtensions.put( + SH_STATUS_REQUEST, CertStatusRequestSpec.DEFAULT); + chc.handshakeConsumers.put(SSLHandshake.CERTIFICATE_STATUS.id, + SSLHandshake.CERTIFICATE_STATUS); + + // Since we've received a legitimate status_request in the + // ServerHello, stapling is active if it's been enabled. + chc.staplingActive = chc.sslContext.isStaplingEnabled(true); + + // No impact on session resumption. + } + } + + /** + * The "status_request_v2" extension. + * + * RFC6961 defines the TLS extension,"status_request_v2" (type 0x5), + * which allows the client to request that the server perform OCSP + * on the client's behalf. + * + * The RFC defines an CertStatusReqItemV2 structure: + * + * struct { + * CertificateStatusType status_type; + * uint16 request_length; + * select (status_type) { + * case ocsp: OCSPStatusRequest; + * case ocsp_multi: OCSPStatusRequest; + * } request; + * } CertificateStatusRequestItemV2; + * + * enum { ocsp(1), ocsp_multi(2), (255) } CertificateStatusType; + * struct { + * ResponderID responder_id_list<0..2^16-1>; + * Extensions request_extensions; + * } OCSPStatusRequest; + * + * opaque ResponderID<1..2^16-1>; + * opaque Extensions<0..2^16-1>; + * + * struct { + * CertificateStatusRequestItemV2 + * certificate_status_req_list<1..2^16-1>; + * } CertificateStatusRequestListV2; + */ + static final class CertStatusRequestV2Spec implements SSLExtensionSpec { + static final CertStatusRequestV2Spec DEFAULT = + new CertStatusRequestV2Spec(new CertStatusRequest[] { + OCSPStatusRequest.EMPTY_OCSP_MULTI}); + + final CertStatusRequest[] certStatusRequests; + + private CertStatusRequestV2Spec(CertStatusRequest[] certStatusRequests) { + this.certStatusRequests = certStatusRequests; + } + + private CertStatusRequestV2Spec(ByteBuffer message) throws IOException { + // Is it a empty extension_data? + if (message.remaining() == 0) { + // server response + this.certStatusRequests = new CertStatusRequest[0]; + return; + } + + if (message.remaining() < 5) { // 2: certificate_status_req_list + // +1: status_type + // +2: request_length + throw new SSLProtocolException( + "Invalid status_request_v2 extension: insufficient data"); + } + + int listLen = Record.getInt16(message); + if (listLen <= 0) { + throw new SSLProtocolException( + "certificate_status_req_list length must be positive " + + "(received length: " + listLen + ")"); + } + + int remaining = listLen; + List<CertStatusRequest> statusRequests = new ArrayList<>(); + while (remaining > 0) { + byte statusType = (byte)Record.getInt8(message); + int requestLen = Record.getInt16(message); + + if (message.remaining() < requestLen) { + throw new SSLProtocolException( + "Invalid status_request_v2 extension: " + + "insufficient data (request_length=" + requestLen + + ", remining=" + message.remaining() + ")"); + } + + byte[] encoded = new byte[requestLen]; + if (encoded.length != 0) { + message.get(encoded); + } + remaining -= 3; // 1(status type) + 2(request_length) bytes + remaining -= requestLen; + + if (statusType == CertStatusRequestType.OCSP.id || + statusType == CertStatusRequestType.OCSP_MULTI.id) { + if (encoded.length < 4) { + // 2: length of responder_id_list + // +2: length of request_extensions + throw new SSLProtocolException( + "Invalid status_request_v2 extension: " + + "insufficient data"); + } + statusRequests.add( + new OCSPStatusRequest(statusType, encoded)); + } else { + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.info( + "Unknown certificate status request " + + "(status type: " + statusType + ")"); + } + statusRequests.add( + new CertStatusRequest(statusType, encoded)); + } + } + + certStatusRequests = + statusRequests.toArray(new CertStatusRequest[0]); + } + + @Override + public String toString() { + if (certStatusRequests == null || certStatusRequests.length == 0) { + return "<empty>"; + } else { + MessageFormat messageFormat = new MessageFormat( + "\"cert status request\": '{'\n{0}\n'}'", Locale.ENGLISH); + + StringBuilder builder = new StringBuilder(512); + boolean isFirst = true; + for (CertStatusRequest csr : certStatusRequests) { + if (isFirst) { + isFirst = false; + } else { + builder.append(", "); + } + Object[] messageFields = { + Utilities.indent(csr.toString()) + }; + builder.append(messageFormat.format(messageFields)); + } + + return builder.toString(); + } + } + } + + private static final + class CertStatusRequestsStringizer implements SSLStringizer { + @Override + public String toString(ByteBuffer buffer) { + try { + return (new CertStatusRequestV2Spec(buffer)).toString(); + } catch (IOException ioe) { + // For debug logging only, so please swallow exceptions. + return ioe.getMessage(); + } + } + } + + /** + * Network data producer of a "status_request_v2" extension in the + * ClientHello handshake message. + */ + private static final + class CHCertStatusReqV2Producer implements HandshakeProducer { + // Prevent instantiation of this class. + private CHCertStatusReqV2Producer() { + // blank + } + + @Override + public byte[] produce(ConnectionContext context, + HandshakeMessage message) throws IOException { + // The producing happens in client side only. + ClientHandshakeContext chc = (ClientHandshakeContext)context; + + if (!chc.sslContext.isStaplingEnabled(true)) { + return null; + } + + if (!chc.sslConfig.isAvailable(CH_STATUS_REQUEST_V2)) { + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.finest( + "Ignore unavailable status_request_v2 extension"); + } + + return null; + } + + // Produce the extension. + // + // We are using empty OCSPStatusRequest at present. May extend to + // support specific responder or extensions later. + byte[] extData = new byte[] { + 0x00, 0x07, 0x02, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00}; + + // Update the context. + chc.handshakeExtensions.put( + CH_STATUS_REQUEST_V2, CertStatusRequestV2Spec.DEFAULT); + + return extData; + } + } + + /** + * Network data consumer of a "status_request_v2" extension in the + * ClientHello handshake message. + */ + private static final + class CHCertStatusReqV2Consumer implements ExtensionConsumer { + // Prevent instantiation of this class. + private CHCertStatusReqV2Consumer() { + // blank + } + + @Override + public void consume(ConnectionContext context, + HandshakeMessage message, ByteBuffer buffer) throws IOException { + + // The consuming happens in server side only. + ServerHandshakeContext shc = (ServerHandshakeContext)context; + + if (!shc.sslConfig.isAvailable(CH_STATUS_REQUEST_V2)) { + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.finest( + "Ignore unavailable status_request_v2 extension"); + } + + return; // ignore the extension + } + + // Parse the extension. + CertStatusRequestV2Spec spec; + try { + spec = new CertStatusRequestV2Spec(buffer); + } catch (IOException ioe) { + shc.conContext.fatal(Alert.UNEXPECTED_MESSAGE, ioe); + return; // fatal() always throws, make the compiler happy. + } + + // Update the context. + shc.handshakeExtensions.put(CH_STATUS_REQUEST_V2, spec); + if (!shc.isResumption) { + shc.handshakeProducers.putIfAbsent( + SSLHandshake.CERTIFICATE_STATUS.id, + SSLHandshake.CERTIFICATE_STATUS); + } + + // No impact on session resumption. + } + } + + /** + * Network data producer of a "status_request_v2" extension in the + * ServerHello handshake message. + */ + private static final + class SHCertStatusReqV2Producer implements HandshakeProducer { + // Prevent instantiation of this class. + private SHCertStatusReqV2Producer() { + // blank + } + + @Override + public byte[] produce(ConnectionContext context, + HandshakeMessage message) throws IOException { + // The producing happens in client side only. + + ServerHandshakeContext shc = (ServerHandshakeContext)context; + // The StaplingParameters in the ServerHandshakeContext will + // contain the info about what kind of stapling (if any) to + // perform and whether this status_request extension should be + // produced or the status_request_v2 (found in a different producer) + // No explicit check is required for isStaplingEnabled here. If + // it is false then stapleParams will be null. If it is true + // then stapleParams may or may not be false and the check below + // is sufficient. + if ((shc.stapleParams == null) || + (shc.stapleParams.statusRespExt != + SSLExtension.CH_STATUS_REQUEST_V2)) { + return null; // Do not produce status_request_v2 in SH + } + + // In response to "status_request_v2" extension request only + CertStatusRequestV2Spec spec = (CertStatusRequestV2Spec) + shc.handshakeExtensions.get(CH_STATUS_REQUEST_V2); + if (spec == null) { + // Ignore, no status_request_v2 extension requested. + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.finest( + "Ignore unavailable status_request_v2 extension"); + } + + return null; // ignore the extension + } + + // Is it a session resuming? + if (shc.isResumption) { + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.finest( + "No status_request_v2 response for session resumption"); + } + return null; // ignore the extension + } + + // The "extension_data" in the extended ServerHello handshake + // message MUST be empty. + byte[] extData = new byte[0]; + + // Update the context. + shc.handshakeExtensions.put( + SH_STATUS_REQUEST_V2, CertStatusRequestV2Spec.DEFAULT); + + return extData; + } + } + + /** + * Network data consumer of a "status_request_v2" extension in the + * ServerHello handshake message. + */ + private static final + class SHCertStatusReqV2Consumer implements ExtensionConsumer { + // Prevent instantiation of this class. + private SHCertStatusReqV2Consumer() { + // blank + } + + @Override + public void consume(ConnectionContext context, + HandshakeMessage message, ByteBuffer buffer) throws IOException { + + // The consumption happens in client side only. + ClientHandshakeContext chc = (ClientHandshakeContext)context; + + // In response to "status_request" extension request only + CertStatusRequestV2Spec requestedCsr = (CertStatusRequestV2Spec) + chc.handshakeExtensions.get(CH_STATUS_REQUEST_V2); + if (requestedCsr == null) { + chc.conContext.fatal(Alert.UNEXPECTED_MESSAGE, + "Unexpected status_request_v2 extension in ServerHello"); + } + + // Parse the extension. + if (buffer.hasRemaining()) { + chc.conContext.fatal(Alert.UNEXPECTED_MESSAGE, + "Invalid status_request_v2 extension in ServerHello: " + + "the extension data must be empty"); + } + + // Update the context. + chc.handshakeExtensions.put( + SH_STATUS_REQUEST_V2, CertStatusRequestV2Spec.DEFAULT); + chc.handshakeConsumers.put(SSLHandshake.CERTIFICATE_STATUS.id, + SSLHandshake.CERTIFICATE_STATUS); + + // Since we've received a legitimate status_request in the + // ServerHello, stapling is active if it's been enabled. + chc.staplingActive = chc.sslContext.isStaplingEnabled(true); + + // No impact on session resumption. + } + } + + private static final + class CTCertStatusResponseProducer implements HandshakeProducer { + // Prevent instantiation of this class. + private CTCertStatusResponseProducer() { + // blank + } + + @Override + public byte[] produce(ConnectionContext context, + HandshakeMessage message) throws IOException { + ServerHandshakeContext shc = (ServerHandshakeContext)context; + byte[] producedData = null; + + // Stapling needs to be active and have valid data to proceed + if (shc.stapleParams == null) { + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.finest( + "Stapling is disabled for this connection"); + } + return null; + } + + // There needs to be a non-null CertificateEntry to proceed + if (shc.currentCertEntry == null) { + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.finest("Found null CertificateEntry in context"); + } + return null; + } + + // Pull the certificate from the CertificateEntry and find + // a response from the response map. If one exists we will + // staple it. + try { + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + X509Certificate x509Cert = + (X509Certificate)cf.generateCertificate( + new ByteArrayInputStream( + shc.currentCertEntry.encoded)); + byte[] respBytes = shc.stapleParams.responseMap.get(x509Cert); + if (respBytes == null) { + // We're done with this entry. Clear it from the context + if (SSLLogger.isOn && + SSLLogger.isOn("ssl,handshake,verbose")) { + SSLLogger.finest("No status response found for " + + x509Cert.getSubjectX500Principal()); + } + shc.currentCertEntry = null; + return null; + } + + // Build a proper response buffer from the stapling information + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake,verbose")) { + SSLLogger.finest("Found status response for " + + x509Cert.getSubjectX500Principal() + + ", response length: " + respBytes.length); + } + CertStatusResponse certResp = (shc.stapleParams.statReqType == + CertStatusRequestType.OCSP) ? + new OCSPStatusResponse(shc.stapleParams.statReqType.id, + respBytes) : + new CertStatusResponse(shc.stapleParams.statReqType.id, + respBytes); + producedData = certResp.toByteArray(); + } catch (CertificateException ce) { + shc.conContext.fatal(Alert.BAD_CERTIFICATE, + "Failed to parse server certificates", ce); + } catch (IOException ioe) { + shc.conContext.fatal(Alert.BAD_CERT_STATUS_RESPONSE, + "Failed to parse certificate status response", ioe); + } + + // Clear the pinned CertificateEntry from the context + shc.currentCertEntry = null; + return producedData; + } + } + + private static final + class CTCertStatusResponseConsumer implements ExtensionConsumer { + // Prevent instantiation of this class. + private CTCertStatusResponseConsumer() { + // blank + } + + @Override + public void consume(ConnectionContext context, + HandshakeMessage message, ByteBuffer buffer) throws IOException { + // The consumption happens in client side only. + ClientHandshakeContext chc = (ClientHandshakeContext)context; + + // Parse the extension. + CertStatusResponseSpec spec; + try { + spec = new CertStatusResponseSpec(buffer); + } catch (IOException ioe) { + chc.conContext.fatal(Alert.DECODE_ERROR, ioe); + return; // fatal() always throws, make the compiler happy. + } + + if (chc.sslContext.isStaplingEnabled(true)) { + // Activate stapling + chc.staplingActive = true; + } else { + // Do no further processing of stapled responses + return; + } + + // Get response list from the session. This is unmodifiable + // so we need to create a new list. Then add this new response + // to the end and submit it back to the session object. + if ((chc.handshakeSession != null) && (!chc.isResumption)) { + List<byte[]> respList = new ArrayList<>( + chc.handshakeSession.getStatusResponses()); + respList.add(spec.statusResponse.encodedResponse); + chc.handshakeSession.setStatusResponses(respList); + } else { + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake,verbose")) { + SSLLogger.finest( + "Ignoring stapled data on resumed session"); + } + } + } + } +}
--- a/src/java.base/share/classes/sun/security/ssl/CertStatusReqExtension.java Mon Jun 25 21:22:16 2018 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,205 +0,0 @@ -/* - * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package sun.security.ssl; - -import java.io.IOException; -import java.util.Objects; - -/* - * RFC6066 defines the TLS extension,"status_request" (type 0x5), - * which allows the client to request that the server perform OCSP - * on the client's behalf. - * The "extension data" field of this extension contains a - * "CertificateStatusRequest" structure: - * - * struct { - * CertificateStatusType status_type; - * select (status_type) { - * case ocsp: OCSPStatusRequest; - * } request; - * } CertificateStatusRequest; - * - * enum { ocsp(1), (255) } CertificateStatusType; - * - * struct { - * ResponderID responder_id_list<0..2^16-1>; - * Extensions request_extensions; - * } OCSPStatusRequest; - * - * opaque ResponderID<1..2^16-1>; - * opaque Extensions<0..2^16-1>; - */ - -final class CertStatusReqExtension extends HelloExtension { - - private final StatusRequestType statReqType; - private final StatusRequest request; - - - /** - * Construct the default status request extension object. The default - * object results in a status_request extension where the extension - * data segment is zero-length. This is used primarily in ServerHello - * messages where the server asserts it can do RFC 6066 status stapling. - */ - CertStatusReqExtension() { - super(ExtensionType.EXT_STATUS_REQUEST); - statReqType = null; - request = null; - } - - /** - * Construct the status request extension object given a request type - * and {@code StatusRequest} object. - * - * @param reqType a {@code StatusRequestExtType object correspoding - * to the underlying {@code StatusRequest} object. A value of - * {@code null} is not allowed. - * @param statReq the {@code StatusRequest} object used to provide the - * encoding for the TLS extension. A value of {@code null} is not - * allowed. - * - * @throws IllegalArgumentException if the provided {@code StatusRequest} - * does not match the type. - * @throws NullPointerException if either the {@code reqType} or - * {@code statReq} arguments are {@code null}. - */ - CertStatusReqExtension(StatusRequestType reqType, StatusRequest statReq) { - super(ExtensionType.EXT_STATUS_REQUEST); - - statReqType = Objects.requireNonNull(reqType, - "Unallowed null value for status_type"); - request = Objects.requireNonNull(statReq, - "Unallowed null value for request"); - - // There is currently only one known status type (OCSP) - // We can add more clauses to cover other types in the future - if (statReqType == StatusRequestType.OCSP) { - if (!(statReq instanceof OCSPStatusRequest)) { - throw new IllegalArgumentException("StatusRequest not " + - "of type OCSPStatusRequest"); - } - } - } - - /** - * Construct the {@code CertStatusReqExtension} object from data read from - * a {@code HandshakeInputStream} - * - * @param s the {@code HandshakeInputStream} providing the encoded data - * @param len the length of the extension data - * - * @throws IOException if any decoding errors happen during object - * construction. - */ - CertStatusReqExtension(HandshakeInStream s, int len) throws IOException { - super(ExtensionType.EXT_STATUS_REQUEST); - - if (len > 0) { - // Obtain the status type (first byte) - statReqType = StatusRequestType.get(s.getInt8()); - if (statReqType == StatusRequestType.OCSP) { - request = new OCSPStatusRequest(s); - } else { - // This is a status_type we don't understand. Create - // an UnknownStatusRequest in order to preserve the data - request = new UnknownStatusRequest(s, len - 1); - } - } else { - // Treat this as a zero-length extension (i.e. from a ServerHello - statReqType = null; - request = null; - } - } - - /** - * Return the length of the encoded extension, including extension type, - * extension length and status_type fields. - * - * @return the length in bytes, including the extension type and - * length fields. - */ - @Override - int length() { - return (statReqType != null ? 5 + request.length() : 4); - } - - /** - * Send the encoded TLS extension through a {@code HandshakeOutputStream} - * - * @param s the {@code HandshakeOutputStream} used to send the encoded data - * - * @throws IOException tf any errors occur during the encoding process - */ - @Override - void send(HandshakeOutStream s) throws IOException { - s.putInt16(type.id); - s.putInt16(this.length() - 4); - - if (statReqType != null) { - s.putInt8(statReqType.id); - request.send(s); - } - } - - /** - * Create a string representation of this {@code CertStatusReqExtension} - * - * @return the string representation of this {@code CertStatusReqExtension} - */ - @Override - public String toString() { - StringBuilder sb = new StringBuilder("Extension ").append(type); - if (statReqType != null) { - sb.append(": ").append(statReqType).append(", ").append(request); - } - - return sb.toString(); - } - - /** - * Return the type field for this {@code CertStatusReqExtension} - * - * @return the {@code StatusRequestType} for this extension. {@code null} - * will be returned if the default constructor is used to create - * a zero length status_request extension (found in ServerHello - * messages) - */ - StatusRequestType getType() { - return statReqType; - } - - /** - * Get the underlying {@code StatusRequest} for this - * {@code CertStatusReqExtension} - * - * @return the {@code StatusRequest} or {@code null} if the default - * constructor was used to create this extension. - */ - StatusRequest getRequest() { - return request; - } -}
--- a/src/java.base/share/classes/sun/security/ssl/CertStatusReqItemV2.java Mon Jun 25 21:22:16 2018 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,199 +0,0 @@ -/* - * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package sun.security.ssl; - -import java.io.IOException; -import java.nio.ByteBuffer; -import java.util.Objects; -import javax.net.ssl.SSLException; - -/* - * RFC6961 defines the TLS extension,"status_request_v2" (type 0x5), - * which allows the client to request that the server perform OCSP - * on the client's behalf. - * - * The RFC defines an CertStatusReqItemV2 structure: - * - * struct { - * CertificateStatusType status_type; - * uint16 request_length; - * select (status_type) { - * case ocsp: OCSPStatusRequest; - * case ocsp_multi: OCSPStatusRequest; - * } request; - * } CertificateStatusRequestItemV2; - * - * enum { ocsp(1), ocsp_multi(2), (255) } CertificateStatusType; - */ - -final class CertStatusReqItemV2 { - - private final StatusRequestType statReqType; - private final StatusRequest request; - - /** - * Construct a {@code CertStatusReqItemV2} object using a type value - * and empty ResponderId and Extension lists. - * - * @param reqType the type of request (e.g. ocsp). A {@code null} value - * is not allowed. - * @param statReq the {@code StatusRequest} object used to provide the - * encoding for this {@code CertStatusReqItemV2}. A {@code null} - * value is not allowed. - * - * @throws IllegalArgumentException if the provided {@code StatusRequest} - * does not match the type. - * @throws NullPointerException if either the reqType or statReq arguments - * are {@code null}. - */ - CertStatusReqItemV2(StatusRequestType reqType, StatusRequest statReq) { - statReqType = Objects.requireNonNull(reqType, - "Unallowed null value for status_type"); - request = Objects.requireNonNull(statReq, - "Unallowed null value for request"); - - // There is currently only one known status type (OCSP) - // We can add more clauses to cover other types in the future - if (statReqType.equals(StatusRequestType.OCSP) || - statReqType.equals(StatusRequestType.OCSP_MULTI)) { - if (!(statReq instanceof OCSPStatusRequest)) { - throw new IllegalArgumentException("StatusRequest not " + - "of type OCSPStatusRequest"); - } - } - } - - /** - * Construct a {@code CertStatusReqItemV2} object from encoded bytes - * - * @param requestBytes the encoded bytes for the {@code CertStatusReqItemV2} - * - * @throws IOException if any decoding errors take place - * @throws IllegalArgumentException if the parsed reqType value is not a - * supported status request type. - */ - CertStatusReqItemV2(byte[] reqItemBytes) throws IOException { - ByteBuffer reqBuf = ByteBuffer.wrap(reqItemBytes); - statReqType = StatusRequestType.get(reqBuf.get()); - int requestLength = Short.toUnsignedInt(reqBuf.getShort()); - - if (requestLength == reqBuf.remaining()) { - byte[] statReqBytes = new byte[requestLength]; - reqBuf.get(statReqBytes); - if (statReqType == StatusRequestType.OCSP || - statReqType == StatusRequestType.OCSP_MULTI) { - request = new OCSPStatusRequest(statReqBytes); - } else { - request = new UnknownStatusRequest(statReqBytes); - } - } else { - throw new SSLException("Incorrect request_length: " + - "Expected " + reqBuf.remaining() + ", got " + - requestLength); - } - } - - /** - * Construct an {@code CertStatusReqItemV2} object from data read from - * a {@code HandshakeInputStream} - * - * @param s the {@code HandshakeInputStream} providing the encoded data - * - * @throws IOException if any decoding errors happen during object - * construction. - * @throws IllegalArgumentException if the parsed reqType value is not a - * supported status request type. - */ - CertStatusReqItemV2(HandshakeInStream in) throws IOException { - statReqType = StatusRequestType.get(in.getInt8()); - int requestLength = in.getInt16(); - - if (statReqType == StatusRequestType.OCSP || - statReqType == StatusRequestType.OCSP_MULTI) { - request = new OCSPStatusRequest(in); - } else { - request = new UnknownStatusRequest(in, requestLength); - } - } - - /** - * Return the length of this {@code CertStatusReqItemV2} in its encoded form - * - * @return the encoded length of this {@code CertStatusReqItemV2} - */ - int length() { - // The length is the status type (1 byte) + the request length - // field (2 bytes) + the StatusRequest data length. - return request.length() + 3; - } - - /** - * Send the encoded {@code CertStatusReqItemV2} through a - * {@code HandshakeOutputStream} - * - * @param s the {@code HandshakeOutputStream} used to send the encoded data - * - * @throws IOException if any errors occur during the encoding process - */ - void send(HandshakeOutStream s) throws IOException { - s.putInt8(statReqType.id); - s.putInt16(request.length()); - request.send(s); - } - - /** - * Create a string representation of this {@code CertStatusReqItemV2} - * - * @return the string representation of this {@code CertStatusReqItemV2} - */ - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append("CertStatusReqItemV2: ").append(statReqType).append(", "); - sb.append(request.toString()); - - return sb.toString(); - } - - /** - * Return the type field for this {@code CertStatusReqItemV2} - * - * @return the {@code StatusRequestType} for this extension. - */ - StatusRequestType getType() { - return statReqType; - } - - /** - * Get the underlying {@code StatusRequest} for this - * {@code CertStatusReqItemV2} - * - * @return the {@code StatusRequest} - */ - StatusRequest getRequest() { - return request; - } -}
--- a/src/java.base/share/classes/sun/security/ssl/CertStatusReqListV2Extension.java Mon Jun 25 21:22:16 2018 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,220 +0,0 @@ -/* - * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package sun.security.ssl; - -import java.io.IOException; -import java.util.List; -import java.util.Collections; -import java.util.ArrayList; -import java.util.Objects; -import javax.net.ssl.SSLException; - -/* - * RFC6066 defines the TLS extension,"status_request" (type 0x5), - * which allows the client to request that the server perform OCSP - * on the client's behalf. - * The "extension data" field of this extension contains a - * "CertificateStatusRequest" structure: - * - * struct { - * CertificateStatusType status_type; - * select (status_type) { - * case ocsp: OCSPStatusRequest; - * } request; - * } CertificateStatusRequest; - * - * enum { ocsp(1), (255) } CertificateStatusType; - * - * struct { - * ResponderID responder_id_list<0..2^16-1>; - * Extensions request_extensions; - * } OCSPStatusRequest; - * - * opaque ResponderID<1..2^16-1>; - * opaque Extensions<0..2^16-1>; - */ - -final class CertStatusReqListV2Extension extends HelloExtension { - - private final List<CertStatusReqItemV2> itemList; - private final int itemListLength; - - /** - * Construct a default {@code CertStatusReqListV2Extension}. The default - * object results in a status_request_v2 extension where the extension - * data segment is zero-length. This is used primarily in ServerHello - * messages where the server asserts it can do RFC 6961 status stapling. - */ - CertStatusReqListV2Extension() { - super(ExtensionType.EXT_STATUS_REQUEST_V2); - itemList = Collections.emptyList(); - itemListLength = 0; - } - - /** - * Construct a {@code CertStatusReqListV2Extension} from a provided list - * of {@code CertStatusReqItemV2} objects. - * - * @param reqList a {@code List} containing one or more - * {@code CertStatusReqItemV2} objects to be included in this TLS - * Hello extension. Passing an empty list will result in the encoded - * extension having a zero-length extension_data segment, and is - * the same as using the default constructor. - * - * @throws NullPointerException if reqList is {@code null} - */ - CertStatusReqListV2Extension(List<CertStatusReqItemV2> reqList) { - super(ExtensionType.EXT_STATUS_REQUEST_V2); - Objects.requireNonNull(reqList, - "Unallowed null value for certificate_status_req_list"); - itemList = Collections.unmodifiableList(new ArrayList<>(reqList)); - itemListLength = calculateListLength(); - } - - /** - * Construct the {@code CertStatusReqListV2Extension} object from data - * read from a {@code HandshakeInputStream} - * - * @param s the {@code HandshakeInputStream} providing the encoded data - * @param len the length of the extension data - * - * @throws IOException if any decoding errors happen during object - * construction. - */ - CertStatusReqListV2Extension(HandshakeInStream s, int len) - throws IOException { - super(ExtensionType.EXT_STATUS_REQUEST_V2); - - if (len <= 0) { - // Handle the empty extension data case (from a ServerHello) - itemList = Collections.emptyList(); - itemListLength = 0; - } else { - List<CertStatusReqItemV2> workingList = new ArrayList<>(); - - itemListLength = s.getInt16(); - if (itemListLength <= 0) { - throw new SSLException("certificate_status_req_list length " + - "must be greater than zero (received length: " + - itemListLength + ")"); - } - - int totalRead = 0; - CertStatusReqItemV2 reqItem; - do { - reqItem = new CertStatusReqItemV2(s); - totalRead += reqItem.length(); - } while (workingList.add(reqItem) && totalRead < itemListLength); - - // If for some reason the add returns false, we may not have read - // all the necessary bytes from the stream. Check this and throw - // an exception if we terminated the loop early. - if (totalRead != itemListLength) { - throw new SSLException("Not all certificate_status_req_list " + - "bytes were read: expected " + itemListLength + - ", read " + totalRead); - } - - itemList = Collections.unmodifiableList(workingList); - } - } - - /** - * Get the list of {@code CertStatusReqItemV2} objects for this extension - * - * @return an unmodifiable list of {@code CertStatusReqItemV2} objects - */ - List<CertStatusReqItemV2> getRequestItems() { - return itemList; - } - - /** - * Return the length of the encoded extension, including extension type - * and extension length fields. - * - * @return the length in bytes, including the extension type and - * extension_data length. - */ - @Override - int length() { - return (itemList.isEmpty() ? 4 : itemListLength + 6); - } - - /** - * Send the encoded {@code CertStatusReqListV2Extension} through a - * {@code HandshakeOutputStream} - * - * @param s the {@code HandshakeOutputStream} used to send the encoded data - * - * @throws IOException if any errors occur during the encoding process - */ - @Override - void send(HandshakeOutStream s) throws IOException { - s.putInt16(type.id); - s.putInt16(this.length() - 4); - if (itemListLength > 0) { - s.putInt16(itemListLength); - for (CertStatusReqItemV2 item : itemList) { - item.send(s); - } - } - } - - /** - * Create a string representation of this - * {@code CertStatusReqListV2Extension} - * - * @return the string representation of this - * {@code CertStatusReqListV2Extension} - */ - @Override - public String toString() { - StringBuilder sb = new StringBuilder("Extension ").append(type); - for (CertStatusReqItemV2 item : itemList) { - sb.append("\n").append(item); - } - - return sb.toString(); - } - - /** - * Determine the length of the certificate_status_req_list field in - * the status_request_v2 extension. - * - * @return the total encoded length of all items in the list, or 0 if the - * encapsulating extension_data is zero-length (from a ServerHello) - */ - private int calculateListLength() { - int listLen = 0; - - for (CertStatusReqItemV2 item : itemList) { - listLen += item.length(); - } - - return listLen; - } - -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/java.base/share/classes/sun/security/ssl/CertificateMessage.java Mon Jun 25 13:41:39 2018 -0700 @@ -0,0 +1,1370 @@ +/* + * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package sun.security.ssl; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.security.PublicKey; +import java.security.cert.CertPathValidatorException; +import java.security.cert.CertPathValidatorException.BasicReason; +import java.security.cert.CertPathValidatorException.Reason; +import java.security.cert.CertificateEncodingException; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.CertificateParsingException; +import java.security.cert.X509Certificate; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Locale; +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLException; +import javax.net.ssl.SSLProtocolException; +import javax.net.ssl.SSLSocket; +import javax.net.ssl.X509ExtendedTrustManager; +import javax.net.ssl.X509TrustManager; +import javax.security.auth.x500.X500Principal; +import static sun.security.ssl.ClientAuthType.CLIENT_AUTH_REQUIRED; +import sun.security.ssl.ClientHello.ClientHelloMessage; +import sun.security.ssl.SSLHandshake.HandshakeMessage; +import sun.security.ssl.X509Authentication.X509Credentials; +import sun.security.ssl.X509Authentication.X509Possession; + +/** + * Pack of the CertificateMessage handshake message. + */ +final class CertificateMessage { + static final SSLConsumer t12HandshakeConsumer = + new T12CertificateConsumer(); + static final HandshakeProducer t12HandshakeProducer = + new T12CertificateProducer(); + + static final SSLConsumer t13HandshakeConsumer = + new T13CertificateConsumer(); + static final HandshakeProducer t13HandshakeProducer = + new T13CertificateProducer(); + + /** + * The Certificate handshake message for TLS 1.2 and previous + * SSL/TLS protocol versions. + * + * In server mode, the certificate handshake message is sent whenever the + * agreed-upon key exchange method uses certificates for authentication. + * In client mode, this message is only sent if the server requests a + * certificate for client authentication. + * + * opaque ASN.1Cert<1..2^24-1>; + * + * SSL 3.0: + * struct { + * ASN.1Cert certificate_list<1..2^24-1>; + * } Certificate; + * Note: For SSL 3.0 client authentication, if no suitable certificate + * is available, the client should send a no_certificate alert instead. + * This alert is only a warning; however, the server may respond with + * a fatal handshake failure alert if client authentication is required. + * + * TLS 1.0/1.1/1.2: + * struct { + * ASN.1Cert certificate_list<0..2^24-1>; + * } Certificate; + */ + static final class T12CertificateMessage extends HandshakeMessage { + final List<byte[]> encodedCertChain; + + T12CertificateMessage(HandshakeContext handshakeContext, + X509Certificate[] certChain) throws SSLException { + super(handshakeContext); + + List<byte[]> encodedCerts = new ArrayList<>(certChain.length); + for (X509Certificate cert : certChain) { + try { + encodedCerts.add(cert.getEncoded()); + } catch (CertificateEncodingException cee) { + // unlikely + handshakeContext.conContext.fatal(Alert.INTERNAL_ERROR, + "Could not encode certificate (" + + cert.getSubjectX500Principal() + ")", cee); + break; + } + } + + this.encodedCertChain = encodedCerts; + } + + T12CertificateMessage(HandshakeContext handshakeContext, + ByteBuffer m) throws IOException { + super(handshakeContext); + + int listLen = Record.getInt24(m); + if (listLen > m.remaining()) { + handshakeContext.conContext.fatal(Alert.ILLEGAL_PARAMETER, + "Error parsing certificate message:no sufficient data"); + } + if (listLen > 0) { + List<byte[]> encodedCerts = new LinkedList<>(); + while (listLen > 0) { + byte[] encodedCert = Record.getBytes24(m); + listLen -= (3 + encodedCert.length); + encodedCerts.add(encodedCert); + } + this.encodedCertChain = encodedCerts; + } else { + this.encodedCertChain = Collections.emptyList(); + } + } + + @Override + public SSLHandshake handshakeType() { + return SSLHandshake.CERTIFICATE; + } + + @Override + public int messageLength() { + int msgLen = 3; + for (byte[] encodedCert : encodedCertChain) { + msgLen += (encodedCert.length + 3); + } + + return msgLen; + } + + @Override + public void send(HandshakeOutStream hos) throws IOException { + int listLen = 0; + for (byte[] encodedCert : encodedCertChain) { + listLen += (encodedCert.length + 3); + } + + hos.putInt24(listLen); + for (byte[] encodedCert : encodedCertChain) { + hos.putBytes24(encodedCert); + } + } + + @Override + public String toString() { + if (encodedCertChain.isEmpty()) { + return "\"Certificates\": <empty list>"; + } + + Object[] x509Certs = new Object[encodedCertChain.size()]; + try { + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + int i = 0; + for (byte[] encodedCert : encodedCertChain) { + Object obj; + try { + obj = (X509Certificate)cf.generateCertificate( + new ByteArrayInputStream(encodedCert)); + } catch (CertificateException ce) { + obj = encodedCert; + } + x509Certs[i++] = obj; + } + } catch (CertificateException ce) { + // no X.509 certificate factory service + int i = 0; + for (byte[] encodedCert : encodedCertChain) { + x509Certs[i++] = encodedCert; + } + } + + MessageFormat messageFormat = new MessageFormat( + "\"Certificates\": [\n" + + "{0}\n" + + "]", + Locale.ENGLISH); + Object[] messageFields = { + SSLLogger.toString(x509Certs) + }; + + return messageFormat.format(messageFields); + } + } + + /** + * The "Certificate" handshake message producer for TLS 1.2 and + * previous SSL/TLS protocol versions. + */ + private static final + class T12CertificateProducer implements HandshakeProducer { + // Prevent instantiation of this class. + private T12CertificateProducer() { + // blank + } + + @Override + public byte[] produce(ConnectionContext context, + HandshakeMessage message) throws IOException { + // The producing happens in handshake context only. + HandshakeContext hc = (HandshakeContext)context; + if (hc.sslConfig.isClientMode) { + return onProduceCertificate( + (ClientHandshakeContext)context, message); + } else { + return onProduceCertificate( + (ServerHandshakeContext)context, message); + } + } + + private byte[] onProduceCertificate(ServerHandshakeContext shc, + SSLHandshake.HandshakeMessage message) throws IOException { + X509Possession x509Possession = null; + for (SSLPossession possession : shc.handshakePossessions) { + if (possession instanceof X509Possession) { + x509Possession = (X509Possession)possession; + break; + } + } + + if (x509Possession == null) { // unlikely + shc.conContext.fatal(Alert.INTERNAL_ERROR, + "No expected X.509 certificate for server authentication"); + + return null; // make the compiler happy + } + + shc.handshakeSession.setLocalPrivateKey( + x509Possession.popPrivateKey); + shc.handshakeSession.setLocalCertificates(x509Possession.popCerts); + T12CertificateMessage cm = + new T12CertificateMessage(shc, x509Possession.popCerts); + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.fine( + "Produced server Certificate handshake message", cm); + } + + // Output the handshake message. + cm.write(shc.handshakeOutput); + shc.handshakeOutput.flush(); + + // The handshake message has been delivered. + return null; + } + + private byte[] onProduceCertificate(ClientHandshakeContext chc, + SSLHandshake.HandshakeMessage message) throws IOException { + X509Possession x509Possession = null; + for (SSLPossession possession : chc.handshakePossessions) { + if (possession instanceof X509Possession) { + x509Possession = (X509Possession)possession; + break; + } + } + + // Report to the server if no appropriate cert was found. For + // SSL 3.0, send a no_certificate alert; TLS 1.0/1.1/1.2 uses + // an empty cert chain instead. + if (x509Possession == null) { + if (chc.negotiatedProtocol.useTLS10PlusSpec()) { + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.fine( + "No X.509 certificate for client authentication, " + + "use empty Certificate message instead"); + } + + x509Possession = + new X509Possession(null, new X509Certificate[0]); + } else { + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.fine( + "No X.509 certificate for client authentication, " + + "send a no_certificate alert"); + } + + chc.conContext.warning(Alert.NO_CERTIFICATE); + return null; + } + } + + chc.handshakeSession.setLocalPrivateKey( + x509Possession.popPrivateKey); + if (x509Possession.popCerts != null && + x509Possession.popCerts.length != 0) { + chc.handshakeSession.setLocalCertificates( + x509Possession.popCerts); + } else { + chc.handshakeSession.setLocalCertificates(null); + } + T12CertificateMessage cm = + new T12CertificateMessage(chc, x509Possession.popCerts); + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.fine( + "Produced client Certificate handshake message", cm); + } + + // Output the handshake message. + cm.write(chc.handshakeOutput); + chc.handshakeOutput.flush(); + + // The handshake message has been delivered. + return null; + } + } + + /** + * The "Certificate" handshake message consumer for TLS 1.2 and + * previous SSL/TLS protocol versions. + */ + static final + class T12CertificateConsumer implements SSLConsumer { + // Prevent instantiation of this class. + private T12CertificateConsumer() { + // blank + } + + @Override + public void consume(ConnectionContext context, + ByteBuffer message) throws IOException { + // The consuming happens in handshake context only. + HandshakeContext hc = (HandshakeContext)context; + + // clean up this consumer + hc.handshakeConsumers.remove(SSLHandshake.CERTIFICATE.id); + + T12CertificateMessage cm = new T12CertificateMessage(hc, message); + if (hc.sslConfig.isClientMode) { + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.fine( + "Consuming server Certificate handshake message", cm); + } + onCertificate((ClientHandshakeContext)context, cm); + } else { + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.fine( + "Consuming client Certificate handshake message", cm); + } + onCertificate((ServerHandshakeContext)context, cm); + } + } + + private void onCertificate(ServerHandshakeContext shc, + T12CertificateMessage certificateMessage )throws IOException { + List<byte[]> encodedCerts = certificateMessage.encodedCertChain; + if (encodedCerts == null || encodedCerts.isEmpty()) { + if (shc.sslConfig.clientAuthType != + ClientAuthType.CLIENT_AUTH_REQUESTED) { + // unexpected or require client authentication + shc.conContext.fatal(Alert.BAD_CERTIFICATE, + "Empty server certificate chain"); + } else { + return; + } + } + + X509Certificate[] x509Certs = + new X509Certificate[encodedCerts.size()]; + try { + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + int i = 0; + for (byte[] encodedCert : encodedCerts) { + x509Certs[i++] = (X509Certificate)cf.generateCertificate( + new ByteArrayInputStream(encodedCert)); + } + } catch (CertificateException ce) { + shc.conContext.fatal(Alert.BAD_CERTIFICATE, + "Failed to parse server certificates", ce); + } + + checkClientCerts(shc, x509Certs); + + // + // update + // + shc.handshakeCredentials.add( + new X509Credentials(x509Certs[0].getPublicKey(), x509Certs)); + shc.handshakeSession.setPeerCertificates(x509Certs); + } + + private void onCertificate(ClientHandshakeContext chc, + T12CertificateMessage certificateMessage) throws IOException { + List<byte[]> encodedCerts = certificateMessage.encodedCertChain; + if (encodedCerts == null || encodedCerts.isEmpty()) { + chc.conContext.fatal(Alert.BAD_CERTIFICATE, + "Empty server certificate chain"); + } + + X509Certificate[] x509Certs = + new X509Certificate[encodedCerts.size()]; + try { + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + int i = 0; + for (byte[] encodedCert : encodedCerts) { + x509Certs[i++] = (X509Certificate)cf.generateCertificate( + new ByteArrayInputStream(encodedCert)); + } + } catch (CertificateException ce) { + chc.conContext.fatal(Alert.BAD_CERTIFICATE, + "Failed to parse server certificates", ce); + } + + // Allow server certificate change in client side during + // renegotiation after a session-resumption abbreviated + // initial handshake? + // + // DO NOT need to check allowUnsafeServerCertChange here. We only + // reserve server certificates when allowUnsafeServerCertChange is + // false. + if (chc.reservedServerCerts != null && + !chc.handshakeSession.useExtendedMasterSecret) { + // It is not necessary to check the certificate update if + // endpoint identification is enabled. + String identityAlg = chc.sslConfig.identificationProtocol; + if ((identityAlg == null || identityAlg.length() == 0) && + !isIdentityEquivalent(x509Certs[0], + chc.reservedServerCerts[0])) { + chc.conContext.fatal(Alert.BAD_CERTIFICATE, + "server certificate change is restricted " + + "during renegotiation"); + } + } + + // ask the trust manager to verify the chain + if (chc.staplingActive) { + // Defer the certificate check until after we've received the + // CertificateStatus message. If that message doesn't come in + // immediately following this message we will execute the + // check from CertificateStatus' absent handler. + chc.deferredCerts = x509Certs; + } else { + // We're not doing stapling, so perform the check right now + checkServerCerts(chc, x509Certs); + } + + // + // update + // + chc.handshakeCredentials.add( + new X509Credentials(x509Certs[0].getPublicKey(), x509Certs)); + chc.handshakeSession.setPeerCertificates(x509Certs); + } + + /* + * Whether the certificates can represent the same identity? + * + * The certificates can be used to represent the same identity: + * 1. If the subject alternative names of IP address are present + * in both certificates, they should be identical; otherwise, + * 2. if the subject alternative names of DNS name are present in + * both certificates, they should be identical; otherwise, + * 3. if the subject fields are present in both certificates, the + * certificate subjects and issuers should be identical. + */ + private static boolean isIdentityEquivalent(X509Certificate thisCert, + X509Certificate prevCert) { + if (thisCert.equals(prevCert)) { + return true; + } + + // check subject alternative names + Collection<List<?>> thisSubjectAltNames = null; + try { + thisSubjectAltNames = thisCert.getSubjectAlternativeNames(); + } catch (CertificateParsingException cpe) { + if (SSLLogger.isOn && SSLLogger.isOn("handshake")) { + SSLLogger.fine( + "Attempt to obtain subjectAltNames extension failed!"); + } + } + + Collection<List<?>> prevSubjectAltNames = null; + try { + prevSubjectAltNames = prevCert.getSubjectAlternativeNames(); + } catch (CertificateParsingException cpe) { + if (SSLLogger.isOn && SSLLogger.isOn("handshake")) { + SSLLogger.fine( + "Attempt to obtain subjectAltNames extension failed!"); + } + } + + if (thisSubjectAltNames != null && prevSubjectAltNames != null) { + // check the iPAddress field in subjectAltName extension + // + // 7: subject alternative name of type IP. + Collection<String> thisSubAltIPAddrs = + getSubjectAltNames(thisSubjectAltNames, 7); + Collection<String> prevSubAltIPAddrs = + getSubjectAltNames(prevSubjectAltNames, 7); + if (thisSubAltIPAddrs != null && prevSubAltIPAddrs != null && + isEquivalent(thisSubAltIPAddrs, prevSubAltIPAddrs)) { + return true; + } + + // check the dNSName field in subjectAltName extension + // 2: subject alternative name of type IP. + Collection<String> thisSubAltDnsNames = + getSubjectAltNames(thisSubjectAltNames, 2); + Collection<String> prevSubAltDnsNames = + getSubjectAltNames(prevSubjectAltNames, 2); + if (thisSubAltDnsNames != null && prevSubAltDnsNames != null && + isEquivalent(thisSubAltDnsNames, prevSubAltDnsNames)) { + return true; + } + } + + // check the certificate subject and issuer + X500Principal thisSubject = thisCert.getSubjectX500Principal(); + X500Principal prevSubject = prevCert.getSubjectX500Principal(); + X500Principal thisIssuer = thisCert.getIssuerX500Principal(); + X500Principal prevIssuer = prevCert.getIssuerX500Principal(); + + return (!thisSubject.getName().isEmpty() && + !prevSubject.getName().isEmpty() && + thisSubject.equals(prevSubject) && + thisIssuer.equals(prevIssuer)); + } + + /* + * Returns the subject alternative name of the specified type in the + * subjectAltNames extension of a certificate. + * + * Note that only those subjectAltName types that use String data + * should be passed into this function. + */ + private static Collection<String> getSubjectAltNames( + Collection<List<?>> subjectAltNames, int type) { + HashSet<String> subAltDnsNames = null; + for (List<?> subjectAltName : subjectAltNames) { + int subjectAltNameType = (Integer)subjectAltName.get(0); + if (subjectAltNameType == type) { + String subAltDnsName = (String)subjectAltName.get(1); + if ((subAltDnsName != null) && !subAltDnsName.isEmpty()) { + if (subAltDnsNames == null) { + subAltDnsNames = + new HashSet<>(subjectAltNames.size()); + } + subAltDnsNames.add(subAltDnsName); + } + } + } + + return subAltDnsNames; + } + + private static boolean isEquivalent(Collection<String> thisSubAltNames, + Collection<String> prevSubAltNames) { + for (String thisSubAltName : thisSubAltNames) { + for (String prevSubAltName : prevSubAltNames) { + // Only allow the exactly match. No wildcard character + // checking. + if (thisSubAltName.equalsIgnoreCase(prevSubAltName)) { + return true; + } + } + } + + return false; + } + + /** + * Perform client-side checking of server certificates. + * + * @param certs an array of {@code X509Certificate} objects presented + * by the server in the ServerCertificate message. + * + * @throws IOException if a failure occurs during validation or + * the trust manager associated with the {@code SSLContext} is not + * an {@code X509ExtendedTrustManager}. + */ + static void checkServerCerts(ClientHandshakeContext chc, + X509Certificate[] certs) throws IOException { + + X509TrustManager tm = chc.sslContext.getX509TrustManager(); + + // find out the key exchange algorithm used + // use "RSA" for non-ephemeral "RSA_EXPORT" + String keyExchangeString; + if (chc.negotiatedCipherSuite.keyExchange == + CipherSuite.KeyExchange.K_RSA_EXPORT || + chc.negotiatedCipherSuite.keyExchange == + CipherSuite.KeyExchange.K_DHE_RSA_EXPORT) { + keyExchangeString = CipherSuite.KeyExchange.K_RSA.name; + } else { + keyExchangeString = chc.negotiatedCipherSuite.keyExchange.name; + } + + try { + if (tm instanceof X509ExtendedTrustManager) { + if (chc.conContext.transport instanceof SSLEngine) { + SSLEngine engine = (SSLEngine)chc.conContext.transport; + ((X509ExtendedTrustManager)tm).checkServerTrusted( + certs.clone(), + keyExchangeString, + engine); + } else { + SSLSocket socket = (SSLSocket)chc.conContext.transport; + ((X509ExtendedTrustManager)tm).checkServerTrusted( + certs.clone(), + keyExchangeString, + socket); + } + } else { + // Unlikely to happen, because we have wrapped the old + // X509TrustManager with the new X509ExtendedTrustManager. + throw new CertificateException( + "Improper X509TrustManager implementation"); + } + + // Once the server certificate chain has been validated, set + // the certificate chain in the TLS session. + chc.handshakeSession.setPeerCertificates(certs); + } catch (CertificateException ce) { + chc.conContext.fatal(getCertificateAlert(chc, ce), ce); + } + } + + private static void checkClientCerts(ServerHandshakeContext shc, + X509Certificate[] certs) throws IOException { + X509TrustManager tm = shc.sslContext.getX509TrustManager(); + + // find out the types of client authentication used + PublicKey key = certs[0].getPublicKey(); + String keyAlgorithm = key.getAlgorithm(); + String authType; + switch (keyAlgorithm) { + case "RSA": + case "DSA": + case "EC": + case "RSASSA-PSS": + authType = keyAlgorithm; + break; + default: + // unknown public key type + authType = "UNKNOWN"; + } + + try { + if (tm instanceof X509ExtendedTrustManager) { + if (shc.conContext.transport instanceof SSLEngine) { + SSLEngine engine = (SSLEngine)shc.conContext.transport; + ((X509ExtendedTrustManager)tm).checkClientTrusted( + certs.clone(), + authType, + engine); + } else { + SSLSocket socket = (SSLSocket)shc.conContext.transport; + ((X509ExtendedTrustManager)tm).checkClientTrusted( + certs.clone(), + authType, + socket); + } + } else { + // Unlikely to happen, because we have wrapped the old + // X509TrustManager with the new X509ExtendedTrustManager. + throw new CertificateException( + "Improper X509TrustManager implementation"); + } + } catch (CertificateException ce) { + shc.conContext.fatal(Alert.CERTIFICATE_UNKNOWN, ce); + } + } + + /** + * When a failure happens during certificate checking from an + * {@link X509TrustManager}, determine what TLS alert description + * to use. + * + * @param cexc The exception thrown by the {@link X509TrustManager} + * + * @return A byte value corresponding to a TLS alert description number. + */ + private static Alert getCertificateAlert( + ClientHandshakeContext chc, CertificateException cexc) { + // The specific reason for the failure will determine how to + // set the alert description value + Alert alert = Alert.CERTIFICATE_UNKNOWN; + + Throwable baseCause = cexc.getCause(); + if (baseCause instanceof CertPathValidatorException) { + CertPathValidatorException cpve = + (CertPathValidatorException)baseCause; + Reason reason = cpve.getReason(); + if (reason == BasicReason.REVOKED) { + alert = chc.staplingActive ? + Alert.BAD_CERT_STATUS_RESPONSE : + Alert.CERTIFICATE_REVOKED; + } else if ( + reason == BasicReason.UNDETERMINED_REVOCATION_STATUS) { + alert = chc.staplingActive ? + Alert.BAD_CERT_STATUS_RESPONSE : + Alert.CERTIFICATE_UNKNOWN; + } + } + + return alert; + } + + } + + /** + * The certificate entry used in Certificate handshake message for TLS 1.3. + */ + static final class CertificateEntry { + final byte[] encoded; // encoded cert or public key + private final SSLExtensions extensions; + + CertificateEntry(byte[] encoded, SSLExtensions extensions) { + this.encoded = encoded; + this.extensions = extensions; + } + + private int getEncodedSize() { + int extLen = extensions.length(); + if (extLen == 0) { + extLen = 2; // empty extensions + } + return 3 + encoded.length + extLen; + } + + @Override + public String toString() { + MessageFormat messageFormat = new MessageFormat( + "\n'{'\n" + + "{0}\n" + // X.509 certificate + " \"extensions\": '{'\n" + + "{1}\n" + + " '}'\n" + + "'}',", Locale.ENGLISH); + + Object x509Certs; + try { + // Don't support certificate type extension (RawPublicKey) yet. + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + x509Certs = + cf.generateCertificate(new ByteArrayInputStream(encoded)); + } catch (CertificateException ce) { + // no X.509 certificate factory service + x509Certs = encoded; + } + + Object[] messageFields = { + SSLLogger.toString(x509Certs), + Utilities.indent(extensions.toString(), " ") + }; + + return messageFormat.format(messageFields); + } + } + + /** + * The Certificate handshake message for TLS 1.3. + */ + static final class T13CertificateMessage extends HandshakeMessage { + private final byte[] requestContext; + private final List<CertificateEntry> certEntries; + + T13CertificateMessage(HandshakeContext context, + byte[] requestContext, X509Certificate[] certificates) + throws SSLException, CertificateException { + super(context); + + this.requestContext = requestContext.clone(); + this.certEntries = new LinkedList<>(); + for (X509Certificate cert : certificates) { + byte[] encoded = cert.getEncoded(); + SSLExtensions extensions = new SSLExtensions(this); + certEntries.add(new CertificateEntry(encoded, extensions)); + } + } + + T13CertificateMessage(HandshakeContext handshakeContext, + byte[] requestContext, List<CertificateEntry> certificates) { + super(handshakeContext); + + this.requestContext = requestContext.clone(); + this.certEntries = certificates; + } + + T13CertificateMessage(HandshakeContext handshakeContext, + ByteBuffer m) throws IOException { + super(handshakeContext); + + // struct { + // opaque certificate_request_context<0..2^8-1>; + // CertificateEntry certificate_list<0..2^24-1>; + // } Certificate; + if (m.remaining() < 4) { + throw new SSLProtocolException( + "Invalid Certificate message: " + + "insufficient data (length=" + m.remaining() + ")"); + } + this.requestContext = Record.getBytes8(m); + + if (m.remaining() < 3) { + throw new SSLProtocolException( + "Invalid Certificate message: " + + "insufficient certificate entries data (length=" + + m.remaining() + ")"); + } + + int listLen = Record.getInt24(m); + if (listLen != m.remaining()) { + throw new SSLProtocolException( + "Invalid Certificate message: " + + "incorrect list length (length=" + listLen + ")"); + } + + SSLExtension[] enabledExtensions = + handshakeContext.sslConfig.getEnabledExtensions( + SSLHandshake.CERTIFICATE); + List<CertificateEntry> certList = new LinkedList<>(); + while (m.hasRemaining()) { + // Note: support only X509 CertificateType right now. + byte[] encodedCert = Record.getBytes24(m); + if (encodedCert.length == 0) { + throw new SSLProtocolException( + "Invalid Certificate message: empty cert_data"); + } + + SSLExtensions extensions = + new SSLExtensions(this, m, enabledExtensions); + certList.add(new CertificateEntry(encodedCert, extensions)); + } + + this.certEntries = Collections.unmodifiableList(certList); + } + + @Override + public SSLHandshake handshakeType() { + return SSLHandshake.CERTIFICATE; + } + + @Override + public int messageLength() { + int msgLen = 4 + requestContext.length; + for (CertificateEntry entry : certEntries) { + msgLen += entry.getEncodedSize(); + } + + return msgLen; + } + + @Override + public void send(HandshakeOutStream hos) throws IOException { + int entryListLen = 0; + for (CertificateEntry entry : certEntries) { + entryListLen += entry.getEncodedSize(); + } + + hos.putBytes8(requestContext); + hos.putInt24(entryListLen); + for (CertificateEntry entry : certEntries) { + hos.putBytes24(entry.encoded); + // Is it an empty extensions? + if (entry.extensions.length() == 0) { + hos.putInt16(0); + } else { + entry.extensions.send(hos); + } + } + } + + @Override + public String toString() { + MessageFormat messageFormat = new MessageFormat( + "\"Certificate\": '{'\n" + + " \"certificate_request_context\": \"{0}\",\n" + + " \"certificate_list\": [{1}\n]\n" + + "'}'", + Locale.ENGLISH); + + StringBuilder builder = new StringBuilder(512); + for (CertificateEntry entry : certEntries) { + builder.append(entry.toString()); + } + + Object[] messageFields = { + Utilities.toHexString(requestContext), + Utilities.indent(builder.toString()) + }; + + return messageFormat.format(messageFields); + } + } + + /** + * The "Certificate" handshake message producer for TLS 1.3. + */ + private static final + class T13CertificateProducer implements HandshakeProducer { + // Prevent instantiation of this class. + private T13CertificateProducer() { + // blank + } + + @Override + public byte[] produce(ConnectionContext context, + HandshakeMessage message) throws IOException { + // The producing happens in handshake context only. + HandshakeContext hc = (HandshakeContext)context; + if (hc.sslConfig.isClientMode) { + return onProduceCertificate( + (ClientHandshakeContext)context, message); + } else { + return onProduceCertificate( + (ServerHandshakeContext)context, message); + } + } + + private byte[] onProduceCertificate(ServerHandshakeContext shc, + HandshakeMessage message) throws IOException { + ClientHelloMessage clientHello = (ClientHelloMessage)message; + + SSLPossession pos = choosePossession(shc, clientHello); + if (pos == null) { + shc.conContext.fatal(Alert.HANDSHAKE_FAILURE, + "No available authentication scheme"); + return null; // make the complier happy + } + + if (!(pos instanceof X509Possession)) { + shc.conContext.fatal(Alert.HANDSHAKE_FAILURE, + "No X.509 certificate for server authentication"); + } + + X509Possession x509Possession = (X509Possession)pos; + X509Certificate[] localCerts = x509Possession.popCerts; + if (localCerts == null || localCerts.length == 0) { + shc.conContext.fatal(Alert.HANDSHAKE_FAILURE, + "No X.509 certificate for server authentication"); + return null; // make the complier happy + } + + // update the context + shc.handshakePossessions.add(x509Possession); + shc.handshakeSession.setLocalPrivateKey( + x509Possession.popPrivateKey); + shc.handshakeSession.setLocalCertificates(localCerts); + T13CertificateMessage cm; + try { + cm = new T13CertificateMessage(shc, (new byte[0]), localCerts); + } catch (SSLException | CertificateException ce) { + shc.conContext.fatal(Alert.HANDSHAKE_FAILURE, + "Failed to produce server Certificate message", ce); + return null; // make the complier happy + } + + // Check the OCSP stapling extensions and attempt + // to get responses. If the resulting stapleParams is non + // null, it implies that stapling is enabled on the server side. + shc.stapleParams = StatusResponseManager.processStapling(shc); + shc.staplingActive = (shc.stapleParams != null); + + // Process extensions for each CertificateEntry. + // Since there can be multiple CertificateEntries within a + // single CT message, we will pin a specific CertificateEntry + // into the ServerHandshakeContext so individual extension + // producers know which X509Certificate it is processing in + // each call. + SSLExtension[] enabledCTExts = shc.sslConfig.getEnabledExtensions( + SSLHandshake.CERTIFICATE, + Arrays.asList(ProtocolVersion.PROTOCOLS_OF_13)); + for (CertificateEntry certEnt : cm.certEntries) { + shc.currentCertEntry = certEnt; + certEnt.extensions.produce(shc, enabledCTExts); + } + + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.fine("Produced server Certificate message", cm); + } + + // Output the handshake message. + cm.write(shc.handshakeOutput); + shc.handshakeOutput.flush(); + + // The handshake message has been delivered. + return null; + } + + private static SSLPossession choosePossession( + HandshakeContext hc, + ClientHelloMessage clientHello) throws IOException { + if (hc.peerRequestedCertSignSchemes == null || + hc.peerRequestedCertSignSchemes.isEmpty()) { + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.warning( + "No signature_algorithms(_cert) in ClientHello"); + } + return null; + } + + Collection<String> checkedKeyTypes = new HashSet<>(); + for (SignatureScheme ss : hc.peerRequestedCertSignSchemes) { + if (checkedKeyTypes.contains(ss.keyAlgorithm)) { + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.warning( + "Unsupported authentication scheme: " + ss.name); + } + continue; + } + + // Don't select a signature scheme unless we will be able to + // produce a CertificateVerify message later + if (SignatureScheme.getPreferableAlgorithm( + hc.peerRequestedSignatureSchemes, + ss, hc.negotiatedProtocol) == null) { + + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.warning( + "Unable to produce CertificateVerify for " + + "signature scheme: " + ss.name); + } + checkedKeyTypes.add(ss.keyAlgorithm); + continue; + } + + SSLAuthentication ka = X509Authentication.valueOf(ss); + if (ka == null) { + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.warning( + "Unsupported authentication scheme: " + ss.name); + } + checkedKeyTypes.add(ss.keyAlgorithm); + continue; + } + + SSLPossession pos = ka.createPossession(hc); + if (pos == null) { + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.warning( + "Unavailable authentication scheme: " + ss.name); + } + continue; + } + + return pos; + } + + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.warning("No available authentication scheme"); + } + return null; + } + + private byte[] onProduceCertificate(ClientHandshakeContext chc, + HandshakeMessage message) throws IOException { + ClientHelloMessage clientHello = (ClientHelloMessage)message; + SSLPossession pos = choosePossession(chc, clientHello); + X509Certificate[] localCerts; + if (pos == null) { + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.fine("No available client authentication scheme"); + } + localCerts = new X509Certificate[0]; + } else { + chc.handshakePossessions.add(pos); + if (!(pos instanceof X509Possession)) { + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.fine( + "No X.509 certificate for client authentication"); + } + localCerts = new X509Certificate[0]; + } else { + X509Possession x509Possession = (X509Possession)pos; + localCerts = x509Possession.popCerts; + chc.handshakeSession.setLocalPrivateKey( + x509Possession.popPrivateKey); + } + } + + if (localCerts != null && localCerts.length != 0) { + chc.handshakeSession.setLocalCertificates(localCerts); + } else { + chc.handshakeSession.setLocalCertificates(null); + } + + T13CertificateMessage cm; + try { + cm = new T13CertificateMessage( + chc, chc.certRequestContext, localCerts); + } catch (SSLException | CertificateException ce) { + chc.conContext.fatal(Alert.HANDSHAKE_FAILURE, + "Failed to produce client Certificate message", ce); + return null; + } + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.fine("Produced client Certificate message", cm); + } + + // Output the handshake message. + cm.write(chc.handshakeOutput); + chc.handshakeOutput.flush(); + + // The handshake message has been delivered. + return null; + } + } + + /** + * The "Certificate" handshake message consumer for TLS 1.3. + */ + private static final class T13CertificateConsumer implements SSLConsumer { + // Prevent instantiation of this class. + private T13CertificateConsumer() { + // blank + } + + @Override + public void consume(ConnectionContext context, + ByteBuffer message) throws IOException { + // The consuming happens in handshake context only. + HandshakeContext hc = (HandshakeContext)context; + + // clean up this consumer + hc.handshakeConsumers.remove(SSLHandshake.CERTIFICATE.id); + T13CertificateMessage cm = new T13CertificateMessage(hc, message); + if (hc.sslConfig.isClientMode) { + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.fine( + "Consuming server Certificate handshake message", cm); + } + onConsumeCertificate((ClientHandshakeContext)context, cm); + } else { + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.fine( + "Consuming client Certificate handshake message", cm); + } + onConsumeCertificate((ServerHandshakeContext)context, cm); + } + } + + private void onConsumeCertificate(ServerHandshakeContext shc, + T13CertificateMessage certificateMessage )throws IOException { + if (certificateMessage.certEntries == null || + certificateMessage.certEntries.isEmpty()) { + if (shc.sslConfig.clientAuthType == CLIENT_AUTH_REQUIRED) { + shc.conContext.fatal(Alert.BAD_CERTIFICATE, + "Empty client certificate chain"); + } else { + // optional client authentication + return; + } + } + + // check client certificate entries + X509Certificate[] cliCerts = + checkClientCerts(shc, certificateMessage.certEntries); + + // + // update + // + shc.handshakeCredentials.add( + new X509Credentials(cliCerts[0].getPublicKey(), cliCerts)); + shc.handshakeSession.setPeerCertificates(cliCerts); + } + + private void onConsumeCertificate(ClientHandshakeContext chc, + T13CertificateMessage certificateMessage )throws IOException { + if (certificateMessage.certEntries == null || + certificateMessage.certEntries.isEmpty()) { + chc.conContext.fatal(Alert.BAD_CERTIFICATE, + "Empty server certificate chain"); + } + + // Each CertificateEntry will have its own set of extensions + // which must be consumed. + SSLExtension[] enabledExtensions = + chc.sslConfig.getEnabledExtensions(SSLHandshake.CERTIFICATE); + for (CertificateEntry certEnt : certificateMessage.certEntries) { + certEnt.extensions.consumeOnLoad(chc, enabledExtensions); + } + + // check server certificate entries + X509Certificate[] srvCerts = + checkServerCerts(chc, certificateMessage.certEntries); + + // + // update + // + chc.handshakeCredentials.add( + new X509Credentials(srvCerts[0].getPublicKey(), srvCerts)); + chc.handshakeSession.setPeerCertificates(srvCerts); + } + + private static X509Certificate[] checkClientCerts( + ServerHandshakeContext shc, + List<CertificateEntry> certEntries) throws IOException { + X509Certificate[] certs = + new X509Certificate[certEntries.size()]; + try { + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + int i = 0; + for (CertificateEntry entry : certEntries) { + certs[i++] = (X509Certificate)cf.generateCertificate( + new ByteArrayInputStream(entry.encoded)); + } + } catch (CertificateException ce) { + shc.conContext.fatal(Alert.BAD_CERTIFICATE, + "Failed to parse server certificates", ce); + } + + // find out the types of client authentication used + String keyAlgorithm = certs[0].getPublicKey().getAlgorithm(); + String authType; + switch (keyAlgorithm) { + case "RSA": + case "DSA": + case "EC": + case "RSASSA-PSS": + authType = keyAlgorithm; + break; + default: + // unknown public key type + authType = "UNKNOWN"; + } + + try { + X509TrustManager tm = shc.sslContext.getX509TrustManager(); + if (tm instanceof X509ExtendedTrustManager) { + if (shc.conContext.transport instanceof SSLEngine) { + SSLEngine engine = (SSLEngine)shc.conContext.transport; + ((X509ExtendedTrustManager)tm).checkClientTrusted( + certs.clone(), + authType, + engine); + } else { + SSLSocket socket = (SSLSocket)shc.conContext.transport; + ((X509ExtendedTrustManager)tm).checkClientTrusted( + certs.clone(), + authType, + socket); + } + } else { + // Unlikely to happen, because we have wrapped the old + // X509TrustManager with the new X509ExtendedTrustManager. + throw new CertificateException( + "Improper X509TrustManager implementation"); + } + + // Once the client certificate chain has been validated, set + // the certificate chain in the TLS session. + shc.handshakeSession.setPeerCertificates(certs); + } catch (CertificateException ce) { + shc.conContext.fatal(Alert.CERTIFICATE_UNKNOWN, ce); + } + + return certs; + } + + private static X509Certificate[] checkServerCerts( + ClientHandshakeContext chc, + List<CertificateEntry> certEntries) throws IOException { + X509Certificate[] certs = + new X509Certificate[certEntries.size()]; + try { + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + int i = 0; + for (CertificateEntry entry : certEntries) { + certs[i++] = (X509Certificate)cf.generateCertificate( + new ByteArrayInputStream(entry.encoded)); + } + } catch (CertificateException ce) { + chc.conContext.fatal(Alert.BAD_CERTIFICATE, + "Failed to parse server certificates", ce); + } + + // find out the types of server authentication used + // + // Note that the "UNKNOWN" authentication type is sufficient to + // check the required digitalSignature KeyUsage for TLS 1.3. + String authType = "UNKNOWN"; + + try { + X509TrustManager tm = chc.sslContext.getX509TrustManager(); + if (tm instanceof X509ExtendedTrustManager) { + if (chc.conContext.transport instanceof SSLEngine) { + SSLEngine engine = (SSLEngine)chc.conContext.transport; + ((X509ExtendedTrustManager)tm).checkServerTrusted( + certs.clone(), + authType, + engine); + } else { + SSLSocket socket = (SSLSocket)chc.conContext.transport; + ((X509ExtendedTrustManager)tm).checkServerTrusted( + certs.clone(), + authType, + socket); + } + } else { + // Unlikely to happen, because we have wrapped the old + // X509TrustManager with the new X509ExtendedTrustManager. + throw new CertificateException( + "Improper X509TrustManager implementation"); + } + + // Once the server certificate chain has been validated, set + // the certificate chain in the TLS session. + chc.handshakeSession.setPeerCertificates(certs); + } catch (CertificateException ce) { + chc.conContext.fatal(getCertificateAlert(chc, ce), ce); + } + + return certs; + } + + /** + * When a failure happens during certificate checking from an + * {@link X509TrustManager}, determine what TLS alert description + * to use. + * + * @param cexc The exception thrown by the {@link X509TrustManager} + * + * @return A byte value corresponding to a TLS alert description number. + */ + private static Alert getCertificateAlert( + ClientHandshakeContext chc, CertificateException cexc) { + // The specific reason for the failure will determine how to + // set the alert description value + Alert alert = Alert.CERTIFICATE_UNKNOWN; + + Throwable baseCause = cexc.getCause(); + if (baseCause instanceof CertPathValidatorException) { + CertPathValidatorException cpve = + (CertPathValidatorException)baseCause; + Reason reason = cpve.getReason(); + if (reason == BasicReason.REVOKED) { + alert = chc.staplingActive ? + Alert.BAD_CERT_STATUS_RESPONSE : + Alert.CERTIFICATE_REVOKED; + } else if ( + reason == BasicReason.UNDETERMINED_REVOCATION_STATUS) { + alert = chc.staplingActive ? + Alert.BAD_CERT_STATUS_RESPONSE : + Alert.CERTIFICATE_UNKNOWN; + } + } + + return alert; + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/java.base/share/classes/sun/security/ssl/CertificateRequest.java Mon Jun 25 13:41:39 2018 -0700 @@ -0,0 +1,890 @@ +/* + * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package sun.security.ssl; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.security.PrivateKey; +import java.security.cert.X509Certificate; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.Locale; +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLSocket; +import javax.net.ssl.X509ExtendedKeyManager; +import javax.security.auth.x500.X500Principal; +import sun.security.ssl.CipherSuite.KeyExchange; +import sun.security.ssl.SSLHandshake.HandshakeMessage; +import sun.security.ssl.X509Authentication.X509Possession; + +/** + * Pack of the CertificateRequest handshake message. + */ +final class CertificateRequest { + static final SSLConsumer t10HandshakeConsumer = + new T10CertificateRequestConsumer(); + static final HandshakeProducer t10HandshakeProducer = + new T10CertificateRequestProducer(); + + static final SSLConsumer t12HandshakeConsumer = + new T12CertificateRequestConsumer(); + static final HandshakeProducer t12HandshakeProducer = + new T12CertificateRequestProducer(); + + static final SSLConsumer t13HandshakeConsumer = + new T13CertificateRequestConsumer(); + static final HandshakeProducer t13HandshakeProducer = + new T13CertificateRequestProducer(); + + // TLS 1.2 and prior versions + private static enum ClientCertificateType { + // RFC 2246 + RSA_SIGN ((byte)0x01, "rsa_sign", "RSA", true), + DSS_SIGN ((byte)0x02, "dss_sign", "DSA", true), + RSA_FIXED_DH ((byte)0x03, "rsa_fixed_dh"), + DSS_FIXED_DH ((byte)0x04, "dss_fixed_dh"), + + // RFC 4346 + RSA_EPHEMERAL_DH ((byte)0x05, "rsa_ephemeral_dh"), + DSS_EPHEMERAL_DH ((byte)0x06, "dss_ephemeral_dh"), + FORTEZZA_DMS ((byte)0x14, "fortezza_dms"), + + // RFC 4492 + ECDSA_SIGN ((byte)0x40, "ecdsa_sign", + "EC", JsseJce.isEcAvailable()), + RSA_FIXED_ECDH ((byte)0x41, "rsa_fixed_ecdh"), + ECDSA_FIXED_ECDH ((byte)0x42, "ecdsa_fixed_ecdh"); + + private static final byte[] CERT_TYPES = + JsseJce.isEcAvailable() ? new byte[] { + ECDSA_SIGN.id, + RSA_SIGN.id, + DSS_SIGN.id + } : new byte[] { + RSA_SIGN.id, + DSS_SIGN.id + }; + + final byte id; + final String name; + final String keyAlgorithm; + final boolean isAvailable; + + private ClientCertificateType(byte id, String name) { + this(id, name, null, false); + } + + private ClientCertificateType(byte id, String name, + String keyAlgorithm, boolean isAvailable) { + this.id = id; + this.name = name; + this.keyAlgorithm = keyAlgorithm; + this.isAvailable = isAvailable; + } + + private static String nameOf(byte id) { + for (ClientCertificateType cct : ClientCertificateType.values()) { + if (cct.id == id) { + return cct.name; + } + } + return "UNDEFINED-CLIENT-CERTIFICATE-TYPE(" + (int)id + ")"; + } + + private static ClientCertificateType valueOf(byte id) { + for (ClientCertificateType cct : ClientCertificateType.values()) { + if (cct.id == id) { + return cct; + } + } + + return null; + } + + private static String[] getKeyTypes(byte[] ids) { + ArrayList<String> keyTypes = new ArrayList<>(3); + for (byte id : ids) { + ClientCertificateType cct = ClientCertificateType.valueOf(id); + if (cct.isAvailable) { + keyTypes.add(cct.keyAlgorithm); + } + } + + return keyTypes.toArray(new String[0]); + } + } + + /** + * The "CertificateRequest" handshake message for SSL 3.0 and TLS 1.0/1.1. + */ + static final class T10CertificateRequestMessage extends HandshakeMessage { + final byte[] types; // certificate types + final List<byte[]> authorities; // certificate authorities + + T10CertificateRequestMessage(HandshakeContext handshakeContext, + X509Certificate[] trustedCerts, KeyExchange keyExchange) { + super(handshakeContext); + + this.authorities = new ArrayList<>(trustedCerts.length); + for (X509Certificate cert : trustedCerts) { + X500Principal x500Principal = cert.getSubjectX500Principal(); + authorities.add(x500Principal.getEncoded()); + } + + this.types = ClientCertificateType.CERT_TYPES; + } + + T10CertificateRequestMessage(HandshakeContext handshakeContext, + ByteBuffer m) throws IOException { + super(handshakeContext); + + // struct { + // ClientCertificateType certificate_types<1..2^8-1>; + // DistinguishedName certificate_authorities<0..2^16-1>; + // } CertificateRequest; + if (m.remaining() < 4) { + handshakeContext.conContext.fatal(Alert.ILLEGAL_PARAMETER, + "Incorrect CertificateRequest message: no sufficient data"); + } + this.types = Record.getBytes8(m); + + int listLen = Record.getInt16(m); + if (listLen > m.remaining()) { + handshakeContext.conContext.fatal(Alert.ILLEGAL_PARAMETER, + "Incorrect CertificateRequest message:no sufficient data"); + } + + if (listLen > 0) { + this.authorities = new LinkedList<>(); + while (listLen > 0) { + // opaque DistinguishedName<1..2^16-1>; + byte[] encoded = Record.getBytes16(m); + listLen -= (2 + encoded.length); + authorities.add(encoded); + } + } else { + this.authorities = Collections.emptyList(); + } + } + + String[] getKeyTypes() { + return ClientCertificateType.getKeyTypes(types); + } + + X500Principal[] getAuthorities() { + List<X500Principal> principals = + new ArrayList<>(authorities.size()); + for (byte[] encoded : authorities) { + X500Principal principal = new X500Principal(encoded); + principals.add(principal); + } + + return principals.toArray(new X500Principal[0]); + } + + @Override + public SSLHandshake handshakeType() { + return SSLHandshake.CERTIFICATE_REQUEST; + } + + @Override + public int messageLength() { + int len = 1 + types.length + 2; + for (byte[] encoded : authorities) { + len += encoded.length + 2; + } + return len; + } + + @Override + public void send(HandshakeOutStream hos) throws IOException { + hos.putBytes8(types); + + int listLen = 0; + for (byte[] encoded : authorities) { + listLen += encoded.length + 2; + } + + hos.putInt16(listLen); + for (byte[] encoded : authorities) { + hos.putBytes16(encoded); + } + } + + @Override + public String toString() { + MessageFormat messageFormat = new MessageFormat( + "\"CertificateRequest\": '{'\n" + + " \"certificate types\": {0}\n" + + " \"certificate authorities\": {1}\n" + + "'}'", + Locale.ENGLISH); + + List<String> typeNames = new ArrayList<>(types.length); + for (byte type : types) { + typeNames.add(ClientCertificateType.nameOf(type)); + } + + List<String> authorityNames = new ArrayList<>(authorities.size()); + for (byte[] encoded : authorities) { + X500Principal principal = new X500Principal(encoded); + authorityNames.add(principal.toString()); + } + Object[] messageFields = { + typeNames, + authorityNames + }; + + return messageFormat.format(messageFields); + } + } + + /** + * The "CertificateRequest" handshake message producer for SSL 3.0 and + * TLS 1.0/1.1. + */ + private static final + class T10CertificateRequestProducer implements HandshakeProducer { + // Prevent instantiation of this class. + private T10CertificateRequestProducer() { + // blank + } + + @Override + public byte[] produce(ConnectionContext context, + HandshakeMessage message) throws IOException { + // The producing happens in server side only. + ServerHandshakeContext shc = (ServerHandshakeContext)context; + + X509Certificate[] caCerts = + shc.sslContext.getX509TrustManager().getAcceptedIssuers(); + T10CertificateRequestMessage crm = new T10CertificateRequestMessage( + shc, caCerts, shc.negotiatedCipherSuite.keyExchange); + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.fine( + "Produced CertificateRequest handshake message", crm); + } + + // Output the handshake message. + crm.write(shc.handshakeOutput); + shc.handshakeOutput.flush(); + + // + // update + // + shc.handshakeConsumers.put(SSLHandshake.CERTIFICATE.id, + SSLHandshake.CERTIFICATE); + shc.handshakeConsumers.put(SSLHandshake.CERTIFICATE_VERIFY.id, + SSLHandshake.CERTIFICATE_VERIFY); + + // The handshake message has been delivered. + return null; + } + } + + /** + * The "CertificateRequest" handshake message consumer for SSL 3.0 and + * TLS 1.0/1.1. + */ + private static final + class T10CertificateRequestConsumer implements SSLConsumer { + // Prevent instantiation of this class. + private T10CertificateRequestConsumer() { + // blank + } + + @Override + public void consume(ConnectionContext context, + ByteBuffer message) throws IOException { + // The consuming happens in client side only. + ClientHandshakeContext chc = (ClientHandshakeContext)context; + + // clean up this consumer + chc.handshakeConsumers.remove(SSLHandshake.CERTIFICATE_REQUEST.id); + + T10CertificateRequestMessage crm = + new T10CertificateRequestMessage(chc, message); + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.fine( + "Consuming CertificateRequest handshake message", crm); + } + + // + // validate + // + // blank + + // + // update + // + + // An empty client Certificate handshake message may be allow. + chc.handshakeProducers.put(SSLHandshake.CERTIFICATE.id, + SSLHandshake.CERTIFICATE); + + X509ExtendedKeyManager km = chc.sslContext.getX509KeyManager(); + String clientAlias = null; + if (chc.conContext.transport instanceof SSLSocketImpl) { + clientAlias = km.chooseClientAlias(crm.getKeyTypes(), + crm.getAuthorities(), (SSLSocket)chc.conContext.transport); + } else if (chc.conContext.transport instanceof SSLEngineImpl) { + clientAlias = km.chooseEngineClientAlias(crm.getKeyTypes(), + crm.getAuthorities(), (SSLEngine)chc.conContext.transport); + } + + + if (clientAlias == null) { + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.warning("No available client authentication"); + } + return; + } + + PrivateKey clientPrivateKey = km.getPrivateKey(clientAlias); + if (clientPrivateKey == null) { + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.warning("No available client private key"); + } + return; + } + + X509Certificate[] clientCerts = km.getCertificateChain(clientAlias); + if ((clientCerts == null) || (clientCerts.length == 0)) { + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.warning("No available client certificate"); + } + return; + } + + chc.handshakePossessions.add( + new X509Possession(clientPrivateKey, clientCerts)); + chc.handshakeProducers.put(SSLHandshake.CERTIFICATE_VERIFY.id, + SSLHandshake.CERTIFICATE_VERIFY); + } + } + + /** + * The CertificateRequest handshake message for TLS 1.2. + */ + static final class T12CertificateRequestMessage extends HandshakeMessage { + final byte[] types; // certificate types + final int[] algorithmIds; // supported signature algorithms + final List<byte[]> authorities; // certificate authorities + + T12CertificateRequestMessage(HandshakeContext handshakeContext, + X509Certificate[] trustedCerts, KeyExchange keyExchange, + List<SignatureScheme> signatureSchemes) throws IOException { + super(handshakeContext); + + this.types = ClientCertificateType.CERT_TYPES; + + if (signatureSchemes == null || signatureSchemes.isEmpty()) { + handshakeContext.conContext.fatal(Alert.ILLEGAL_PARAMETER, + "No signature algorithms specified for " + + "CertificateRequest hanshake message"); + } + this.algorithmIds = new int[signatureSchemes.size()]; + int i = 0; + for (SignatureScheme scheme : signatureSchemes) { + algorithmIds[i++] = scheme.id; + } + + this.authorities = new ArrayList<>(trustedCerts.length); + for (X509Certificate cert : trustedCerts) { + X500Principal x500Principal = cert.getSubjectX500Principal(); + authorities.add(x500Principal.getEncoded()); + } + } + + T12CertificateRequestMessage(HandshakeContext handshakeContext, + ByteBuffer m) throws IOException { + super(handshakeContext); + + // struct { + // ClientCertificateType certificate_types<1..2^8-1>; + // SignatureAndHashAlgorithm + // supported_signature_algorithms<2..2^16-2>; + // DistinguishedName certificate_authorities<0..2^16-1>; + // } CertificateRequest; + + // certificate_authorities + if (m.remaining() < 8) { + handshakeContext.conContext.fatal(Alert.ILLEGAL_PARAMETER, + "Invalid CertificateRequest handshake message: " + + "no sufficient data"); + } + this.types = Record.getBytes8(m); + + // supported_signature_algorithms + if (m.remaining() < 6) { + handshakeContext.conContext.fatal(Alert.ILLEGAL_PARAMETER, + "Invalid CertificateRequest handshake message: " + + "no sufficient data"); + } + + byte[] algs = Record.getBytes16(m); + if (algs == null || algs.length == 0 || (algs.length & 0x01) != 0) { + handshakeContext.conContext.fatal(Alert.ILLEGAL_PARAMETER, + "Invalid CertificateRequest handshake message: " + + "incomplete signature algorithms"); + } + + this.algorithmIds = new int[(algs.length >> 1)]; + for (int i = 0, j = 0; i < algs.length;) { + byte hash = algs[i++]; + byte sign = algs[i++]; + algorithmIds[j++] = ((hash & 0xFF) << 8) | (sign & 0xFF); + } + + // certificate_authorities + if (m.remaining() < 2) { + handshakeContext.conContext.fatal(Alert.ILLEGAL_PARAMETER, + "Invalid CertificateRequest handshake message: " + + "no sufficient data"); + } + + int listLen = Record.getInt16(m); + if (listLen > m.remaining()) { + handshakeContext.conContext.fatal(Alert.ILLEGAL_PARAMETER, + "Invalid CertificateRequest message: no sufficient data"); + } + + if (listLen > 0) { + this.authorities = new LinkedList<>(); + while (listLen > 0) { + // opaque DistinguishedName<1..2^16-1>; + byte[] encoded = Record.getBytes16(m); + listLen -= (2 + encoded.length); + authorities.add(encoded); + } + } else { + this.authorities = Collections.emptyList(); + } + } + + String[] getKeyTypes() { + return ClientCertificateType.getKeyTypes(types); + } + + X500Principal[] getAuthorities() { + List<X500Principal> principals = + new ArrayList<>(authorities.size()); + for (byte[] encoded : authorities) { + X500Principal principal = new X500Principal(encoded); + principals.add(principal); + } + + return principals.toArray(new X500Principal[0]); + } + + @Override + public SSLHandshake handshakeType() { + return SSLHandshake.CERTIFICATE_REQUEST; + } + + @Override + public int messageLength() { + int len = 1 + types.length + 2 + (algorithmIds.length << 1) + 2; + for (byte[] encoded : authorities) { + len += encoded.length + 2; + } + return len; + } + + @Override + public void send(HandshakeOutStream hos) throws IOException { + hos.putBytes8(types); + + int listLen = 0; + for (byte[] encoded : authorities) { + listLen += encoded.length + 2; + } + + hos.putInt16(algorithmIds.length << 1); + for (int algorithmId : algorithmIds) { + hos.putInt16(algorithmId); + } + + hos.putInt16(listLen); + for (byte[] encoded : authorities) { + hos.putBytes16(encoded); + } + } + + @Override + public String toString() { + MessageFormat messageFormat = new MessageFormat( + "\"CertificateRequest\": '{'\n" + + " \"certificate types\": {0}\n" + + " \"supported signature algorithms\": {1}\n" + + " \"certificate authorities\": {2}\n" + + "'}'", + Locale.ENGLISH); + + List<String> typeNames = new ArrayList<>(types.length); + for (byte type : types) { + typeNames.add(ClientCertificateType.nameOf(type)); + } + + List<String> algorithmNames = new ArrayList<>(algorithmIds.length); + for (int algorithmId : algorithmIds) { + algorithmNames.add(SignatureScheme.nameOf(algorithmId)); + } + + List<String> authorityNames = new ArrayList<>(authorities.size()); + for (byte[] encoded : authorities) { + X500Principal principal = new X500Principal(encoded); + authorityNames.add(principal.toString()); + } + Object[] messageFields = { + typeNames, + algorithmNames, + authorityNames + }; + + return messageFormat.format(messageFields); + } + } + + /** + * The "CertificateRequest" handshake message producer for TLS 1.2. + */ + private static final + class T12CertificateRequestProducer implements HandshakeProducer { + // Prevent instantiation of this class. + private T12CertificateRequestProducer() { + // blank + } + + @Override + public byte[] produce(ConnectionContext context, + HandshakeMessage message) throws IOException { + // The producing happens in server side only. + ServerHandshakeContext shc = (ServerHandshakeContext)context; + if (shc.localSupportedSignAlgs == null) { + shc.localSupportedSignAlgs = + SignatureScheme.getSupportedAlgorithms( + shc.algorithmConstraints, shc.activeProtocols); + } + + if (shc.localSupportedSignAlgs == null || + shc.localSupportedSignAlgs.isEmpty()) { + shc.conContext.fatal(Alert.HANDSHAKE_FAILURE, + "No supported signature algorithm"); + } + + X509Certificate[] caCerts = + shc.sslContext.getX509TrustManager().getAcceptedIssuers(); + T12CertificateRequestMessage crm = new T12CertificateRequestMessage( + shc, caCerts, shc.negotiatedCipherSuite.keyExchange, + shc.localSupportedSignAlgs); + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.fine( + "Produced CertificateRequest handshake message", crm); + } + + // Output the handshake message. + crm.write(shc.handshakeOutput); + shc.handshakeOutput.flush(); + + // + // update + // + shc.handshakeConsumers.put(SSLHandshake.CERTIFICATE.id, + SSLHandshake.CERTIFICATE); + shc.handshakeConsumers.put(SSLHandshake.CERTIFICATE_VERIFY.id, + SSLHandshake.CERTIFICATE_VERIFY); + + // The handshake message has been delivered. + return null; + } + } + + /** + * The "CertificateRequest" handshake message consumer for TLS 1.2. + */ + private static final + class T12CertificateRequestConsumer implements SSLConsumer { + // Prevent instantiation of this class. + private T12CertificateRequestConsumer() { + // blank + } + + @Override + public void consume(ConnectionContext context, + ByteBuffer message) throws IOException { + // The consuming happens in client side only. + ClientHandshakeContext chc = (ClientHandshakeContext)context; + + // clean up this consumer + chc.handshakeConsumers.remove(SSLHandshake.CERTIFICATE_REQUEST.id); + + T12CertificateRequestMessage crm = + new T12CertificateRequestMessage(chc, message); + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.fine( + "Consuming CertificateRequest handshake message", crm); + } + + // + // validate + // + // blank + + // + // update + // + + // An empty client Certificate handshake message may be allow. + chc.handshakeProducers.put(SSLHandshake.CERTIFICATE.id, + SSLHandshake.CERTIFICATE); + + List<SignatureScheme> sss = new LinkedList<>(); + for (int id : crm.algorithmIds) { + SignatureScheme ss = SignatureScheme.valueOf(id); + if (ss != null) { + sss.add(ss); + } + } + chc.peerRequestedSignatureSchemes = sss; + chc.peerRequestedCertSignSchemes = sss; // use the same schemes + chc.handshakeSession.setPeerSupportedSignatureAlgorithms(sss); + + X509ExtendedKeyManager km = chc.sslContext.getX509KeyManager(); + String clientAlias = null; + if (chc.conContext.transport instanceof SSLSocketImpl) { + clientAlias = km.chooseClientAlias(crm.getKeyTypes(), + crm.getAuthorities(), (SSLSocket)chc.conContext.transport); + } else if (chc.conContext.transport instanceof SSLEngineImpl) { + clientAlias = km.chooseEngineClientAlias(crm.getKeyTypes(), + crm.getAuthorities(), (SSLEngine)chc.conContext.transport); + } + + if (clientAlias == null) { + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.warning("No available client authentication"); + } + return; + } + + PrivateKey clientPrivateKey = km.getPrivateKey(clientAlias); + if (clientPrivateKey == null) { + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.warning("No available client private key"); + } + return; + } + + X509Certificate[] clientCerts = km.getCertificateChain(clientAlias); + if ((clientCerts == null) || (clientCerts.length == 0)) { + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.warning("No available client certificate"); + } + return; + } + + chc.handshakePossessions.add( + new X509Possession(clientPrivateKey, clientCerts)); + chc.handshakeProducers.put(SSLHandshake.CERTIFICATE_VERIFY.id, + SSLHandshake.CERTIFICATE_VERIFY); + } + } + + /** + * The CertificateRequest handshake message for TLS 1.3. + */ + static final class T13CertificateRequestMessage extends HandshakeMessage { + private final byte[] requestContext; + private final SSLExtensions extensions; + + T13CertificateRequestMessage( + HandshakeContext handshakeContext) throws IOException { + super(handshakeContext); + + this.requestContext = new byte[0]; + this.extensions = new SSLExtensions(this); + } + + T13CertificateRequestMessage(HandshakeContext handshakeContext, + ByteBuffer m) throws IOException { + super(handshakeContext); + + // struct { + // opaque certificate_request_context<0..2^8-1>; + // Extension extensions<2..2^16-1>; + // } CertificateRequest; + if (m.remaining() < 5) { + handshakeContext.conContext.fatal(Alert.ILLEGAL_PARAMETER, + "Invalid CertificateRequest handshake message: " + + "no sufficient data"); + } + this.requestContext = Record.getBytes8(m); + + if (m.remaining() < 4) { + handshakeContext.conContext.fatal(Alert.ILLEGAL_PARAMETER, + "Invalid CertificateRequest handshake message: " + + "no sufficient extensions data"); + } + SSLExtension[] enabledExtensions = + handshakeContext.sslConfig.getEnabledExtensions( + SSLHandshake.CERTIFICATE_REQUEST); + this.extensions = new SSLExtensions(this, m, enabledExtensions); + } + + @Override + SSLHandshake handshakeType() { + return SSLHandshake.CERTIFICATE_REQUEST; + } + + @Override + int messageLength() { + // In TLS 1.3, use of certain extensions is mandatory. + return 1 + requestContext.length + extensions.length(); + } + + @Override + void send(HandshakeOutStream hos) throws IOException { + hos.putBytes8(requestContext); + + // In TLS 1.3, use of certain extensions is mandatory. + extensions.send(hos); + } + + @Override + public String toString() { + MessageFormat messageFormat = new MessageFormat( + "\"CertificateRequest\": '{'\n" + + " \"certificate_request_context\": \"{0}\",\n" + + " \"extensions\": [\n" + + "{1}\n" + + " ]\n" + + "'}'", + Locale.ENGLISH); + Object[] messageFields = { + Utilities.toHexString(requestContext), + Utilities.indent(Utilities.indent(extensions.toString())) + }; + + return messageFormat.format(messageFields); + } + } + + /** + * The "CertificateRequest" handshake message producer for TLS 1.3. + */ + private static final + class T13CertificateRequestProducer implements HandshakeProducer { + // Prevent instantiation of this class. + private T13CertificateRequestProducer() { + // blank + } + + @Override + public byte[] produce(ConnectionContext context, + HandshakeMessage message) throws IOException { + // The producing happens in server side only. + ServerHandshakeContext shc = (ServerHandshakeContext)context; + + T13CertificateRequestMessage crm = + new T13CertificateRequestMessage(shc); + // Produce extensions for CertificateRequest handshake message. + SSLExtension[] extTypes = shc.sslConfig.getEnabledExtensions( + SSLHandshake.CERTIFICATE_REQUEST, shc.negotiatedProtocol); + crm.extensions.produce(shc, extTypes); + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.fine("Produced CertificateRequest message", crm); + } + + // Output the handshake message. + crm.write(shc.handshakeOutput); + shc.handshakeOutput.flush(); + + // + // update + // + shc.certRequestContext = crm.requestContext.clone(); + shc.handshakeConsumers.put(SSLHandshake.CERTIFICATE.id, + SSLHandshake.CERTIFICATE); + shc.handshakeConsumers.put(SSLHandshake.CERTIFICATE_VERIFY.id, + SSLHandshake.CERTIFICATE_VERIFY); + + // The handshake message has been delivered. + return null; + } + } + + /** + * The "CertificateRequest" handshake message consumer for TLS 1.3. + */ + private static final + class T13CertificateRequestConsumer implements SSLConsumer { + // Prevent instantiation of this class. + private T13CertificateRequestConsumer() { + // blank + } + + @Override + public void consume(ConnectionContext context, + ByteBuffer message) throws IOException { + // The consuming happens in client side only. + ClientHandshakeContext chc = (ClientHandshakeContext)context; + + // clean up this consumer + chc.handshakeConsumers.remove(SSLHandshake.CERTIFICATE_REQUEST.id); + + T13CertificateRequestMessage crm = + new T13CertificateRequestMessage(chc, message); + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.fine( + "Consuming CertificateRequest handshake message", crm); + } + + // + // validate + // + SSLExtension[] extTypes = chc.sslConfig.getEnabledExtensions( + SSLHandshake.CERTIFICATE_REQUEST); + crm.extensions.consumeOnLoad(chc, extTypes); + + // + // update + // + crm.extensions.consumeOnTrade(chc, extTypes); + + // + // produce + // + chc.certRequestContext = crm.requestContext.clone(); + chc.handshakeProducers.put(SSLHandshake.CERTIFICATE.id, + SSLHandshake.CERTIFICATE); + chc.handshakeProducers.put(SSLHandshake.CERTIFICATE_VERIFY.id, + SSLHandshake.CERTIFICATE_VERIFY); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/java.base/share/classes/sun/security/ssl/CertificateStatus.java Mon Jun 25 13:41:39 2018 -0700 @@ -0,0 +1,362 @@ +/* + * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package sun.security.ssl; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.text.MessageFormat; +import java.util.List; +import java.util.ArrayList; +import java.util.Locale; +import javax.net.ssl.SSLHandshakeException; +import java.security.cert.X509Certificate; +import sun.security.provider.certpath.OCSPResponse; +import sun.security.ssl.SSLHandshake.HandshakeMessage; +import static sun.security.ssl.CertStatusExtension.*; +import static sun.security.ssl.CertificateMessage.*; + +/** + * Consumers and producers for the CertificateStatus handshake message. + * This message takes one of two related but slightly different forms, + * depending on the type of stapling selected by the server. The message + * data will be of the form(s): + * + * [status_request, RFC 6066] + * + * struct { + * CertificateStatusType status_type; + * select (status_type) { + * case ocsp: OCSPResponse; + * } response; + * } CertificateStatus; + * + * opaque OCSPResponse<1..2^24-1>; + * + * [status_request_v2, RFC 6961] + * + * struct { + * CertificateStatusType status_type; + * select (status_type) { + * case ocsp: OCSPResponse; + * case ocsp_multi: OCSPResponseList; + * } response; + * } CertificateStatus; + * + * opaque OCSPResponse<0..2^24-1>; + * + * struct { + * OCSPResponse ocsp_response_list<1..2^24-1>; + * } OCSPResponseList; + */ +final class CertificateStatus { + static final SSLConsumer handshakeConsumer = + new CertificateStatusConsumer(); + static final HandshakeProducer handshakeProducer = + new CertificateStatusProducer(); + static final HandshakeAbsence handshakeAbsence = + new CertificateStatusAbsence(); + + /** + * The CertificateStatus handshake message. + */ + static final class CertificateStatusMessage extends HandshakeMessage { + + final CertStatusRequestType statusType; + int encodedResponsesLen = 0; + int messageLength = -1; + final List<byte[]> encodedResponses = new ArrayList<>(); + + CertificateStatusMessage(HandshakeContext handshakeContext) { + super(handshakeContext); + + ServerHandshakeContext shc = + (ServerHandshakeContext)handshakeContext; + + // Get the Certificates from the SSLContextImpl amd the Stapling + // parameters + StatusResponseManager.StaplingParameters stapleParams = + shc.stapleParams; + if (stapleParams == null) { + throw new IllegalArgumentException( + "Unexpected null stapling parameters"); + } + + X509Certificate[] certChain = + (X509Certificate[])shc.handshakeSession.getLocalCertificates(); + if (certChain == null) { + throw new IllegalArgumentException( + "Unexpected null certificate chain"); + } + + // Walk the certificate list and add the correct encoded responses + // to the encoded responses list + statusType = stapleParams.statReqType; + if (statusType == CertStatusRequestType.OCSP) { + // Just worry about the first cert in the chain + byte[] resp = stapleParams.responseMap.get(certChain[0]); + if (resp == null) { + // A not-found return status means we should include + // a zero-length response in CertificateStatus. + // This is highly unlikely to happen in practice. + resp = new byte[0]; + } + encodedResponses.add(resp); + encodedResponsesLen += resp.length + 3; + } else if (statusType == CertStatusRequestType.OCSP_MULTI) { + for (X509Certificate cert : certChain) { + byte[] resp = stapleParams.responseMap.get(cert); + if (resp == null) { + resp = new byte[0]; + } + encodedResponses.add(resp); + encodedResponsesLen += resp.length + 3; + } + } else { + throw new IllegalArgumentException( + "Unsupported StatusResponseType: " + statusType); + } + + messageLength = messageLength(); + } + + CertificateStatusMessage(HandshakeContext handshakeContext, + ByteBuffer m) throws IOException { + super(handshakeContext); + + statusType = CertStatusRequestType.valueOf((byte)Record.getInt8(m)); + if (statusType == CertStatusRequestType.OCSP) { + byte[] respDER = Record.getBytes24(m); + // Convert the incoming bytes to a OCSPResponse strucutre + if (respDER.length > 0) { + encodedResponses.add(respDER); + encodedResponsesLen = 3 + respDER.length; + } else { + handshakeContext.conContext.fatal(Alert.HANDSHAKE_FAILURE, + "Zero-length OCSP Response"); + } + } else if (statusType == CertStatusRequestType.OCSP_MULTI) { + int respListLen = Record.getInt24(m); + 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 = Record.getBytes24(m); + encodedResponses.add(respDER); + respListLen -= (respDER.length + 3); + } + + if (respListLen != 0) { + handshakeContext.conContext.fatal(Alert.INTERNAL_ERROR, + "Bad OCSP response list length"); + } + } else { + handshakeContext.conContext.fatal(Alert.HANDSHAKE_FAILURE, + "Unsupported StatusResponseType: " + statusType); + } + messageLength = messageLength(); + } + + @Override + public SSLHandshake handshakeType() { + return SSLHandshake.CERTIFICATE_STATUS; + } + + @Override + public int messageLength() { + int len = 1; + + if (messageLength == -1) { + if (statusType == CertStatusRequestType.OCSP) { + len += encodedResponsesLen; + } else if (statusType == CertStatusRequestType.OCSP_MULTI) { + len += 3 + encodedResponsesLen; + } + messageLength = len; + } + + return messageLength; + } + + @Override + public void send(HandshakeOutStream s) throws IOException { + s.putInt8(statusType.id); + if (statusType == CertStatusRequestType.OCSP) { + s.putBytes24(encodedResponses.get(0)); + } else if (statusType == CertStatusRequestType.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); + } + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + + // Stringify the encoded OCSP response list + for (byte[] respDER : encodedResponses) { + if (respDER.length > 0) { + try { + OCSPResponse oResp = new OCSPResponse(respDER); + sb.append(oResp.toString()).append("\n"); + } catch (IOException ioe) { + sb.append("OCSP Response Exception: ").append(ioe) + .append("\n"); + } + } else { + sb.append("<Zero-length entry>\n"); + } + } + + MessageFormat messageFormat = new MessageFormat( + "\"CertificateStatus\": '{'\n" + + " \"type\" : \"{0}\",\n" + + " \"responses \" : [\n" + "{1}\n" + " ]\n" + + "'}'", + Locale.ENGLISH); + Object[] messageFields = { + statusType.name, + Utilities.indent(Utilities.indent(sb.toString())) + }; + + return messageFormat.format(messageFields); + } + } + + /** + * The CertificateStatus handshake message consumer. + */ + private static final class CertificateStatusConsumer + implements SSLConsumer { + // Prevent instantiation of this class. + private CertificateStatusConsumer() { + // blank + } + + @Override + public void consume(ConnectionContext context, + ByteBuffer message) throws IOException { + ClientHandshakeContext chc = (ClientHandshakeContext)context; + CertificateStatusMessage cst = + new CertificateStatusMessage(chc, message); + + // Log the message + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.fine( + "Consuming server CertificateStatus handshake message", + cst); + } + + // Pin the received responses to the SSLSessionImpl. It will + // be retrieved by the X509TrustManagerImpl during the certficicate + // checking phase. + chc.handshakeSession.setStatusResponses(cst.encodedResponses); + + // Now perform the check + T12CertificateConsumer.checkServerCerts(chc, chc.deferredCerts); + } + } + + /** + * The CertificateStatus handshake message consumer. + */ + private static final class CertificateStatusProducer + implements HandshakeProducer { + // Prevent instantiation of this class. + private CertificateStatusProducer() { + // blank + } + + @Override + public byte[] produce(ConnectionContext context, + HandshakeMessage message) throws IOException { + // Only the server-side should be a producer of this message + ServerHandshakeContext shc = (ServerHandshakeContext)context; + + // If stapling is not active, immediately return without producing + // a message or any further processing. + if (!shc.staplingActive) { + return null; + } + + // Create the CertificateStatus message from info in the + CertificateStatusMessage csm = new CertificateStatusMessage(shc); + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.fine( + "Produced server CertificateStatus handshake message", csm); + } + + // Output the handshake message. + csm.write(shc.handshakeOutput); + shc.handshakeOutput.flush(); + + // The handshake message has been delivered. + return null; + } + } + + private static final class CertificateStatusAbsence + implements HandshakeAbsence { + // Prevent instantiation of this class + private CertificateStatusAbsence() { + // blank + } + + @Override + public void absent(ConnectionContext context, + HandshakeMessage message) throws IOException { + ClientHandshakeContext chc = (ClientHandshakeContext)context; + + // Processing should only continue if stapling is active + if (chc.staplingActive) { + // Because OCSP stapling is active, it means two things + // if we're here: 1) The server hello asserted the + // status_request[_v2] extension. 2) The CertificateStatus + // message was not sent. This means that cert path checking + // was deferred, but must happen immediately. + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.fine("Server did not send CertificateStatus, " + + "checking cert chain without status info."); + } + T12CertificateConsumer.checkServerCerts(chc, chc.deferredCerts); + } + } + } +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/java.base/share/classes/sun/security/ssl/CertificateVerify.java Mon Jun 25 13:41:39 2018 -0700 @@ -0,0 +1,1143 @@ + /* + * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package sun.security.ssl; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.security.*; +import java.text.MessageFormat; +import java.util.Arrays; +import java.util.Locale; +import sun.security.ssl.SSLHandshake.HandshakeMessage; +import sun.security.ssl.X509Authentication.X509Credentials; +import sun.security.ssl.X509Authentication.X509Possession; +import sun.security.util.HexDumpEncoder; + +/** + * Pack of the CertificateVerify handshake message. + */ +final class CertificateVerify { + static final SSLConsumer s30HandshakeConsumer = + new S30CertificateVerifyConsumer(); + static final HandshakeProducer s30HandshakeProducer = + new S30CertificateVerifyProducer(); + + static final SSLConsumer t10HandshakeConsumer = + new T10CertificateVerifyConsumer(); + static final HandshakeProducer t10HandshakeProducer = + new T10CertificateVerifyProducer(); + + static final SSLConsumer t12HandshakeConsumer = + new T12CertificateVerifyConsumer(); + static final HandshakeProducer t12HandshakeProducer = + new T12CertificateVerifyProducer(); + + static final SSLConsumer t13HandshakeConsumer = + new T13CertificateVerifyConsumer(); + static final HandshakeProducer t13HandshakeProducer = + new T13CertificateVerifyProducer(); + + /** + * The CertificateVerify handshake message (SSL 3.0). + */ + static final class S30CertificateVerifyMessage extends HandshakeMessage { + // signature bytes + private final byte[] signature; + + S30CertificateVerifyMessage(HandshakeContext context, + X509Possession x509Possession) throws IOException { + super(context); + + // This happens in client side only. + ClientHandshakeContext chc = (ClientHandshakeContext)context; + byte[] temproary = null; + String algorithm = x509Possession.popPrivateKey.getAlgorithm(); + try { + Signature signer = + getSignature(algorithm, x509Possession.popPrivateKey); + byte[] hashes = chc.handshakeHash.digest(algorithm, + chc.handshakeSession.getMasterSecret()); + signer.update(hashes); + temproary = signer.sign(); + } catch (NoSuchAlgorithmException nsae) { + chc.conContext.fatal(Alert.INTERNAL_ERROR, + "Unsupported signature algorithm (" + algorithm + + ") used in CertificateVerify handshake message", nsae); + } catch (GeneralSecurityException gse) { + chc.conContext.fatal(Alert.HANDSHAKE_FAILURE, + "Cannot produce CertificateVerify signature", gse); + } + + this.signature = temproary; + } + + S30CertificateVerifyMessage(HandshakeContext context, + ByteBuffer m) throws IOException { + super(context); + + // This happens in server side only. + ServerHandshakeContext shc = (ServerHandshakeContext)context; + + // digitally-signed struct { + // select(SignatureAlgorithm) { + // case anonymous: struct { }; + // case rsa: + // opaque md5_hash[16]; + // opaque sha_hash[20]; + // case dsa: + // opaque sha_hash[20]; + // }; + // } Signature; + if (m.remaining() < 2) { + shc.conContext.fatal(Alert.ILLEGAL_PARAMETER, + "Invalid CertificateVerify message: no sufficient data"); + } + + // read and verify the signature + this.signature = Record.getBytes16(m); + X509Credentials x509Credentials = null; + for (SSLCredentials cd : shc.handshakeCredentials) { + if (cd instanceof X509Credentials) { + x509Credentials = (X509Credentials)cd; + break; + } + } + + if (x509Credentials == null || + x509Credentials.popPublicKey == null) { + shc.conContext.fatal(Alert.HANDSHAKE_FAILURE, + "No X509 credentials negotiated for CertificateVerify"); + } + + String algorithm = x509Credentials.popPublicKey.getAlgorithm(); + try { + Signature signer = + getSignature(algorithm, x509Credentials.popPublicKey); + byte[] hashes = shc.handshakeHash.digest(algorithm, + shc.handshakeSession.getMasterSecret()); + signer.update(hashes); + if (!signer.verify(signature)) { + shc.conContext.fatal(Alert.HANDSHAKE_FAILURE, + "Invalid CertificateVerify message: invalid signature"); + } + } catch (NoSuchAlgorithmException nsae) { + shc.conContext.fatal(Alert.INTERNAL_ERROR, + "Unsupported signature algorithm (" + algorithm + + ") used in CertificateVerify handshake message", nsae); + } catch (GeneralSecurityException gse) { + shc.conContext.fatal(Alert.HANDSHAKE_FAILURE, + "Cannot verify CertificateVerify signature", gse); + } + } + + @Override + public SSLHandshake handshakeType() { + return SSLHandshake.CERTIFICATE_VERIFY; + } + + @Override + public int messageLength() { + return 2 + signature.length; // 2: length of signature + } + + @Override + public void send(HandshakeOutStream hos) throws IOException { + hos.putBytes16(signature); + } + + @Override + public String toString() { + MessageFormat messageFormat = new MessageFormat( + "\"CertificateVerify\": '{'\n" + + " \"signature\": '{'\n" + + "{0}\n" + + " '}'\n" + + "'}'", + Locale.ENGLISH); + + HexDumpEncoder hexEncoder = new HexDumpEncoder(); + Object[] messageFields = { + Utilities.indent( + hexEncoder.encodeBuffer(signature), " ") + }; + + return messageFormat.format(messageFields); + } + + /* + * Get the Signature object appropriate for verification using the + * given signature algorithm. + */ + private static Signature getSignature(String algorithm, + Key key) throws GeneralSecurityException { + Signature signer = null; + switch (algorithm) { + case "RSA": + signer = JsseJce.getSignature(JsseJce.SIGNATURE_RAWRSA); + break; + case "DSA": + signer = JsseJce.getSignature(JsseJce.SIGNATURE_RAWDSA); + break; + case "EC": + signer = JsseJce.getSignature(JsseJce.SIGNATURE_RAWECDSA); + break; + default: + throw new SignatureException("Unrecognized algorithm: " + + algorithm); + } + + if (signer != null) { + if (key instanceof PublicKey) { + signer.initVerify((PublicKey)(key)); + } else { + signer.initSign((PrivateKey)key); + } + } + + return signer; + } + } + + /** + * The "CertificateVerify" handshake message producer. + */ + private static final + class S30CertificateVerifyProducer implements HandshakeProducer { + // Prevent instantiation of this class. + private S30CertificateVerifyProducer() { + // blank + } + + @Override + public byte[] produce(ConnectionContext context, + HandshakeMessage message) throws IOException { + // The producing happens in client side only. + ClientHandshakeContext chc = (ClientHandshakeContext)context; + + X509Possession x509Possession = null; + for (SSLPossession possession : chc.handshakePossessions) { + if (possession instanceof X509Possession) { + x509Possession = (X509Possession)possession; + break; + } + } + + if (x509Possession == null || + x509Possession.popPrivateKey == null) { + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.fine( + "No X.509 credentials negotiated for CertificateVerify"); + } + + return null; + } + + S30CertificateVerifyMessage cvm = + new S30CertificateVerifyMessage(chc, x509Possession); + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.fine( + "Produced CertificateVerify handshake message", cvm); + } + + // Output the handshake message. + cvm.write(chc.handshakeOutput); + chc.handshakeOutput.flush(); + + // The handshake message has been delivered. + return null; + } + } + + /** + * The "CertificateVerify" handshake message consumer. + */ + private static final + class S30CertificateVerifyConsumer implements SSLConsumer { + // Prevent instantiation of this class. + private S30CertificateVerifyConsumer() { + // blank + } + + @Override + public void consume(ConnectionContext context, + ByteBuffer message) throws IOException { + // The consuming happens in server side only. + ServerHandshakeContext shc = (ServerHandshakeContext)context; + S30CertificateVerifyMessage cvm = + new S30CertificateVerifyMessage(shc, message); + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.fine( + "Consuming CertificateVerify handshake message", cvm); + } + + // + // update + // + // Need no additional validation. + + // + // produce + // + // Need no new handshake message producers here. + } + } + + /** + * The CertificateVerify handshake message (TLS 1.0/1.1). + */ + static final class T10CertificateVerifyMessage extends HandshakeMessage { + // signature bytes + private final byte[] signature; + + T10CertificateVerifyMessage(HandshakeContext context, + X509Possession x509Possession) throws IOException { + super(context); + + // This happens in client side only. + ClientHandshakeContext chc = (ClientHandshakeContext)context; + byte[] temproary = null; + String algorithm = x509Possession.popPrivateKey.getAlgorithm(); + try { + Signature signer = + getSignature(algorithm, x509Possession.popPrivateKey); + byte[] hashes = chc.handshakeHash.digest(algorithm); + signer.update(hashes); + temproary = signer.sign(); + } catch (NoSuchAlgorithmException nsae) { + chc.conContext.fatal(Alert.INTERNAL_ERROR, + "Unsupported signature algorithm (" + algorithm + + ") used in CertificateVerify handshake message", nsae); + } catch (GeneralSecurityException gse) { + chc.conContext.fatal(Alert.HANDSHAKE_FAILURE, + "Cannot produce CertificateVerify signature", gse); + } + + this.signature = temproary; + } + + T10CertificateVerifyMessage(HandshakeContext context, + ByteBuffer m) throws IOException { + super(context); + + // This happens in server side only. + ServerHandshakeContext shc = (ServerHandshakeContext)context; + + // digitally-signed struct { + // select(SignatureAlgorithm) { + // case anonymous: struct { }; + // case rsa: + // opaque md5_hash[16]; + // opaque sha_hash[20]; + // case dsa: + // opaque sha_hash[20]; + // }; + // } Signature; + if (m.remaining() < 2) { + shc.conContext.fatal(Alert.ILLEGAL_PARAMETER, + "Invalid CertificateVerify message: no sufficient data"); + } + + // read and verify the signature + this.signature = Record.getBytes16(m); + X509Credentials x509Credentials = null; + for (SSLCredentials cd : shc.handshakeCredentials) { + if (cd instanceof X509Credentials) { + x509Credentials = (X509Credentials)cd; + break; + } + } + + if (x509Credentials == null || + x509Credentials.popPublicKey == null) { + shc.conContext.fatal(Alert.HANDSHAKE_FAILURE, + "No X509 credentials negotiated for CertificateVerify"); + } + + String algorithm = x509Credentials.popPublicKey.getAlgorithm(); + try { + Signature signer = + getSignature(algorithm, x509Credentials.popPublicKey); + byte[] hashes = shc.handshakeHash.digest(algorithm); + signer.update(hashes); + if (!signer.verify(signature)) { + shc.conContext.fatal(Alert.HANDSHAKE_FAILURE, + "Invalid CertificateVerify message: invalid signature"); + } + } catch (NoSuchAlgorithmException nsae) { + shc.conContext.fatal(Alert.INTERNAL_ERROR, + "Unsupported signature algorithm (" + algorithm + + ") used in CertificateVerify handshake message", nsae); + } catch (GeneralSecurityException gse) { + shc.conContext.fatal(Alert.HANDSHAKE_FAILURE, + "Cannot verify CertificateVerify signature", gse); + } + } + + @Override + public SSLHandshake handshakeType() { + return SSLHandshake.CERTIFICATE_VERIFY; + } + + @Override + public int messageLength() { + return 2 + signature.length; // 2: length of signature + } + + @Override + public void send(HandshakeOutStream hos) throws IOException { + hos.putBytes16(signature); + } + + @Override + public String toString() { + MessageFormat messageFormat = new MessageFormat( + "\"CertificateVerify\": '{'\n" + + " \"signature\": '{'\n" + + "{0}\n" + + " '}'\n" + + "'}'", + Locale.ENGLISH); + + HexDumpEncoder hexEncoder = new HexDumpEncoder(); + Object[] messageFields = { + Utilities.indent( + hexEncoder.encodeBuffer(signature), " ") + }; + + return messageFormat.format(messageFields); + } + + /* + * Get the Signature object appropriate for verification using the + * given signature algorithm. + */ + private static Signature getSignature(String algorithm, + Key key) throws GeneralSecurityException { + Signature signer = null; + switch (algorithm) { + case "RSA": + signer = JsseJce.getSignature(JsseJce.SIGNATURE_RAWRSA); + break; + case "DSA": + signer = JsseJce.getSignature(JsseJce.SIGNATURE_RAWDSA); + break; + case "EC": + signer = JsseJce.getSignature(JsseJce.SIGNATURE_RAWECDSA); + break; + default: + throw new SignatureException("Unrecognized algorithm: " + + algorithm); + } + + if (signer != null) { + if (key instanceof PublicKey) { + signer.initVerify((PublicKey)(key)); + } else { + signer.initSign((PrivateKey)key); + } + } + + return signer; + } + } + + /** + * The "CertificateVerify" handshake message producer. + */ + private static final + class T10CertificateVerifyProducer implements HandshakeProducer { + // Prevent instantiation of this class. + private T10CertificateVerifyProducer() { + // blank + } + + @Override + public byte[] produce(ConnectionContext context, + HandshakeMessage message) throws IOException { + // The producing happens in client side only. + ClientHandshakeContext chc = (ClientHandshakeContext)context; + X509Possession x509Possession = null; + for (SSLPossession possession : chc.handshakePossessions) { + if (possession instanceof X509Possession) { + x509Possession = (X509Possession)possession; + break; + } + } + + if (x509Possession == null || + x509Possession.popPrivateKey == null) { + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.fine( + "No X.509 credentials negotiated for CertificateVerify"); + } + + return null; + } + + T10CertificateVerifyMessage cvm = + new T10CertificateVerifyMessage(chc, x509Possession); + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.fine( + "Produced CertificateVerify handshake message", cvm); + } + + // Output the handshake message. + cvm.write(chc.handshakeOutput); + chc.handshakeOutput.flush(); + + // The handshake message has been delivered. + return null; + } + } + + /** + * The "CertificateVerify" handshake message consumer. + */ + private static final + class T10CertificateVerifyConsumer implements SSLConsumer { + // Prevent instantiation of this class. + private T10CertificateVerifyConsumer() { + // blank + } + + @Override + public void consume(ConnectionContext context, + ByteBuffer message) throws IOException { + // The consuming happens in server side only. + ServerHandshakeContext shc = (ServerHandshakeContext)context; + T10CertificateVerifyMessage cvm = + new T10CertificateVerifyMessage(shc, message); + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.fine( + "Consuming CertificateVerify handshake message", cvm); + } + + // + // update + // + // Need no additional validation. + + // + // produce + // + // Need no new handshake message producers here. } + } + } + + /** + * The CertificateVerify handshake message (TLS 1.2). + */ + static final class T12CertificateVerifyMessage extends HandshakeMessage { + // the signature algorithm + private final SignatureScheme signatureScheme; + + // signature bytes + private final byte[] signature; + + T12CertificateVerifyMessage(HandshakeContext context, + X509Possession x509Possession) throws IOException { + super(context); + + // This happens in client side only. + ClientHandshakeContext chc = (ClientHandshakeContext)context; + this.signatureScheme = SignatureScheme.getPreferableAlgorithm( + chc.peerRequestedSignatureSchemes, + x509Possession.popPrivateKey, + chc.negotiatedProtocol); + if (signatureScheme == null) { + // Unlikely, the credentials generator should have + // selected the preferable signature algorithm properly. + chc.conContext.fatal(Alert.INTERNAL_ERROR, + "No preferred signature algorithm for CertificateVerify"); + } + + byte[] temproary = null; + try { + Signature signer = + signatureScheme.getSignature(x509Possession.popPrivateKey); + signer.update(chc.handshakeHash.archived()); + temproary = signer.sign(); + } catch (NoSuchAlgorithmException | + InvalidAlgorithmParameterException nsae) { + chc.conContext.fatal(Alert.INTERNAL_ERROR, + "Unsupported signature algorithm (" + + signatureScheme.name + + ") used in CertificateVerify handshake message", nsae); + } catch (InvalidKeyException | SignatureException ikse) { + chc.conContext.fatal(Alert.HANDSHAKE_FAILURE, + "Cannot produce CertificateVerify signature", ikse); + } + + this.signature = temproary; + } + + T12CertificateVerifyMessage(HandshakeContext handshakeContext, + ByteBuffer m) throws IOException { + super(handshakeContext); + + // This happens in server side only. + ServerHandshakeContext shc = + (ServerHandshakeContext)handshakeContext; + + // struct { + // SignatureAndHashAlgorithm algorithm; + // opaque signature<0..2^16-1>; + // } DigitallySigned; + if (m.remaining() < 4) { + shc.conContext.fatal(Alert.ILLEGAL_PARAMETER, + "Invalid CertificateVerify message: no sufficient data"); + } + + // SignatureAndHashAlgorithm algorithm + int ssid = Record.getInt16(m); + this.signatureScheme = SignatureScheme.valueOf(ssid); + if (signatureScheme == null) { + shc.conContext.fatal(Alert.HANDSHAKE_FAILURE, + "Invalid signature algorithm (" + ssid + + ") used in CertificateVerify handshake message"); + } + + if (!shc.localSupportedSignAlgs.contains(signatureScheme)) { + shc.conContext.fatal(Alert.HANDSHAKE_FAILURE, + "Unsupported signature algorithm (" + + signatureScheme.name + + ") used in CertificateVerify handshake message"); + } + + // read and verify the signature + X509Credentials x509Credentials = null; + for (SSLCredentials cd : shc.handshakeCredentials) { + if (cd instanceof X509Credentials) { + x509Credentials = (X509Credentials)cd; + break; + } + } + + if (x509Credentials == null || + x509Credentials.popPublicKey == null) { + shc.conContext.fatal(Alert.HANDSHAKE_FAILURE, + "No X509 credentials negotiated for CertificateVerify"); + } + + // opaque signature<0..2^16-1>; + this.signature = Record.getBytes16(m); + try { + Signature signer = + signatureScheme.getSignature(x509Credentials.popPublicKey); + signer.update(shc.handshakeHash.archived()); + if (!signer.verify(signature)) { + shc.conContext.fatal(Alert.HANDSHAKE_FAILURE, + "Invalid CertificateVerify signature"); + } + } catch (NoSuchAlgorithmException | + InvalidAlgorithmParameterException nsae) { + shc.conContext.fatal(Alert.INTERNAL_ERROR, + "Unsupported signature algorithm (" + + signatureScheme.name + + ") used in CertificateVerify handshake message", nsae); + } catch (InvalidKeyException | SignatureException ikse) { + shc.conContext.fatal(Alert.HANDSHAKE_FAILURE, + "Cannot verify CertificateVerify signature", ikse); + } + } + + @Override + public SSLHandshake handshakeType() { + return SSLHandshake.CERTIFICATE_VERIFY; + } + + @Override + public int messageLength() { + return 4 + signature.length; // 2: signature algorithm + // +2: length of signature + } + + @Override + public void send(HandshakeOutStream hos) throws IOException { + hos.putInt16(signatureScheme.id); + hos.putBytes16(signature); + } + + @Override + public String toString() { + MessageFormat messageFormat = new MessageFormat( + "\"CertificateVerify\": '{'\n" + + " \"signature algorithm\": {0}\n" + + " \"signature\": '{'\n" + + "{1}\n" + + " '}'\n" + + "'}'", + Locale.ENGLISH); + + HexDumpEncoder hexEncoder = new HexDumpEncoder(); + Object[] messageFields = { + signatureScheme.name, + Utilities.indent( + hexEncoder.encodeBuffer(signature), " ") + }; + + return messageFormat.format(messageFields); + } + } + + /** + * The "CertificateVerify" handshake message producer. + */ + private static final + class T12CertificateVerifyProducer implements HandshakeProducer { + // Prevent instantiation of this class. + private T12CertificateVerifyProducer() { + // blank + } + + @Override + public byte[] produce(ConnectionContext context, + HandshakeMessage message) throws IOException { + // The producing happens in client side only. + ClientHandshakeContext chc = (ClientHandshakeContext)context; + + X509Possession x509Possession = null; + for (SSLPossession possession : chc.handshakePossessions) { + if (possession instanceof X509Possession) { + x509Possession = (X509Possession)possession; + break; + } + } + + if (x509Possession == null || + x509Possession.popPrivateKey == null) { + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.fine( + "No X.509 credentials negotiated for CertificateVerify"); + } + + return null; + } + + T12CertificateVerifyMessage cvm = + new T12CertificateVerifyMessage(chc, x509Possession); + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.fine( + "Produced CertificateVerify handshake message", cvm); + } + + // Output the handshake message. + cvm.write(chc.handshakeOutput); + chc.handshakeOutput.flush(); + + // The handshake message has been delivered. + return null; + } + } + + /** + * The "CertificateVerify" handshake message consumer. + */ + private static final + class T12CertificateVerifyConsumer implements SSLConsumer { + // Prevent instantiation of this class. + private T12CertificateVerifyConsumer() { + // blank + } + + @Override + public void consume(ConnectionContext context, + ByteBuffer message) throws IOException { + // The consuming happens in server side only. + ServerHandshakeContext shc = (ServerHandshakeContext)context; + T12CertificateVerifyMessage cvm = + new T12CertificateVerifyMessage(shc, message); + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.fine( + "Consuming CertificateVerify handshake message", cvm); + } + + // + // update + // + // Need no additional validation. + + // + // produce + // + // Need no new handshake message producers here. + } + } + + /** + * The CertificateVerify handshake message (TLS 1.3). + */ + static final class T13CertificateVerifyMessage extends HandshakeMessage { + private static final byte[] serverSignHead = new byte[] { + // repeated 0x20 for 64 times + (byte)0x20, (byte)0x20, (byte)0x20, (byte)0x20, + (byte)0x20, (byte)0x20, (byte)0x20, (byte)0x20, + (byte)0x20, (byte)0x20, (byte)0x20, (byte)0x20, + (byte)0x20, (byte)0x20, (byte)0x20, (byte)0x20, + (byte)0x20, (byte)0x20, (byte)0x20, (byte)0x20, + (byte)0x20, (byte)0x20, (byte)0x20, (byte)0x20, + (byte)0x20, (byte)0x20, (byte)0x20, (byte)0x20, + (byte)0x20, (byte)0x20, (byte)0x20, (byte)0x20, + (byte)0x20, (byte)0x20, (byte)0x20, (byte)0x20, + (byte)0x20, (byte)0x20, (byte)0x20, (byte)0x20, + (byte)0x20, (byte)0x20, (byte)0x20, (byte)0x20, + (byte)0x20, (byte)0x20, (byte)0x20, (byte)0x20, + (byte)0x20, (byte)0x20, (byte)0x20, (byte)0x20, + (byte)0x20, (byte)0x20, (byte)0x20, (byte)0x20, + (byte)0x20, (byte)0x20, (byte)0x20, (byte)0x20, + (byte)0x20, (byte)0x20, (byte)0x20, (byte)0x20, + + // "TLS 1.3, server CertificateVerify" + 0x00 + (byte)0x54, (byte)0x4c, (byte)0x53, (byte)0x20, + (byte)0x31, (byte)0x2e, (byte)0x33, (byte)0x2c, + (byte)0x20, (byte)0x73, (byte)0x65, (byte)0x72, + (byte)0x76, (byte)0x65, (byte)0x72, (byte)0x20, + (byte)0x43, (byte)0x65, (byte)0x72, (byte)0x74, + (byte)0x69, (byte)0x66, (byte)0x69, (byte)0x63, + (byte)0x61, (byte)0x74, (byte)0x65, (byte)0x56, + (byte)0x65, (byte)0x72, (byte)0x69, (byte)0x66, + (byte)0x79, (byte)0x00 + }; + + private static final byte[] clientSignHead = new byte[] { + // repeated 0x20 for 64 times + (byte)0x20, (byte)0x20, (byte)0x20, (byte)0x20, + (byte)0x20, (byte)0x20, (byte)0x20, (byte)0x20, + (byte)0x20, (byte)0x20, (byte)0x20, (byte)0x20, + (byte)0x20, (byte)0x20, (byte)0x20, (byte)0x20, + (byte)0x20, (byte)0x20, (byte)0x20, (byte)0x20, + (byte)0x20, (byte)0x20, (byte)0x20, (byte)0x20, + (byte)0x20, (byte)0x20, (byte)0x20, (byte)0x20, + (byte)0x20, (byte)0x20, (byte)0x20, (byte)0x20, + (byte)0x20, (byte)0x20, (byte)0x20, (byte)0x20, + (byte)0x20, (byte)0x20, (byte)0x20, (byte)0x20, + (byte)0x20, (byte)0x20, (byte)0x20, (byte)0x20, + (byte)0x20, (byte)0x20, (byte)0x20, (byte)0x20, + (byte)0x20, (byte)0x20, (byte)0x20, (byte)0x20, + (byte)0x20, (byte)0x20, (byte)0x20, (byte)0x20, + (byte)0x20, (byte)0x20, (byte)0x20, (byte)0x20, + (byte)0x20, (byte)0x20, (byte)0x20, (byte)0x20, + + // "TLS 1.3, client CertificateVerify" + 0x00 + (byte)0x54, (byte)0x4c, (byte)0x53, (byte)0x20, + (byte)0x31, (byte)0x2e, (byte)0x33, (byte)0x2c, + (byte)0x20, (byte)0x63, (byte)0x6c, (byte)0x69, + (byte)0x65, (byte)0x6e, (byte)0x74, (byte)0x20, + (byte)0x43, (byte)0x65, (byte)0x72, (byte)0x74, + (byte)0x69, (byte)0x66, (byte)0x69, (byte)0x63, + (byte)0x61, (byte)0x74, (byte)0x65, (byte)0x56, + (byte)0x65, (byte)0x72, (byte)0x69, (byte)0x66, + (byte)0x79, (byte)0x00 + }; + + + // the signature algorithm + private final SignatureScheme signatureScheme; + + // signature bytes + private final byte[] signature; + + T13CertificateVerifyMessage(HandshakeContext context, + X509Possession x509Possession) throws IOException { + super(context); + + this.signatureScheme = SignatureScheme.getPreferableAlgorithm( + context.peerRequestedSignatureSchemes, + x509Possession.popPrivateKey, + context.negotiatedProtocol); + if (signatureScheme == null) { + // Unlikely, the credentials generator should have + // selected the preferable signature algorithm properly. + context.conContext.fatal(Alert.INTERNAL_ERROR, + "No preferred signature algorithm for CertificateVerify"); + } + + byte[] hashValue = context.handshakeHash.digest(); + byte[] contentCovered; + if (context.sslConfig.isClientMode) { + contentCovered = Arrays.copyOf(clientSignHead, + clientSignHead.length + hashValue.length); + System.arraycopy(hashValue, 0, contentCovered, + clientSignHead.length, hashValue.length); + } else { + contentCovered = Arrays.copyOf(serverSignHead, + serverSignHead.length + hashValue.length); + System.arraycopy(hashValue, 0, contentCovered, + serverSignHead.length, hashValue.length); + } + + byte[] temproary = null; + try { + Signature signer = + signatureScheme.getSignature(x509Possession.popPrivateKey); + signer.update(contentCovered); + temproary = signer.sign(); + } catch (NoSuchAlgorithmException | + InvalidAlgorithmParameterException nsae) { + context.conContext.fatal(Alert.INTERNAL_ERROR, + "Unsupported signature algorithm (" + + signatureScheme.name + + ") used in CertificateVerify handshake message", nsae); + } catch (InvalidKeyException | SignatureException ikse) { + context.conContext.fatal(Alert.HANDSHAKE_FAILURE, + "Cannot produce CertificateVerify signature", ikse); + } + + this.signature = temproary; + } + + T13CertificateVerifyMessage(HandshakeContext context, + ByteBuffer m) throws IOException { + super(context); + + // struct { + // SignatureAndHashAlgorithm algorithm; + // opaque signature<0..2^16-1>; + // } DigitallySigned; + if (m.remaining() < 4) { + context.conContext.fatal(Alert.ILLEGAL_PARAMETER, + "Invalid CertificateVerify message: no sufficient data"); + } + + // SignatureAndHashAlgorithm algorithm + int ssid = Record.getInt16(m); + this.signatureScheme = SignatureScheme.valueOf(ssid); + if (signatureScheme == null) { + context.conContext.fatal(Alert.HANDSHAKE_FAILURE, + "Invalid signature algorithm (" + ssid + + ") used in CertificateVerify handshake message"); + } + + if (!context.localSupportedSignAlgs.contains(signatureScheme)) { + context.conContext.fatal(Alert.HANDSHAKE_FAILURE, + "Unsupported signature algorithm (" + + signatureScheme.name + + ") used in CertificateVerify handshake message"); + } + + // read and verify the signature + X509Credentials x509Credentials = null; + for (SSLCredentials cd : context.handshakeCredentials) { + if (cd instanceof X509Credentials) { + x509Credentials = (X509Credentials)cd; + break; + } + } + + if (x509Credentials == null || + x509Credentials.popPublicKey == null) { + context.conContext.fatal(Alert.HANDSHAKE_FAILURE, + "No X509 credentials negotiated for CertificateVerify"); + } + + // opaque signature<0..2^16-1>; + this.signature = Record.getBytes16(m); + + byte[] hashValue = context.handshakeHash.digest(); + byte[] contentCovered; + if (context.sslConfig.isClientMode) { + contentCovered = Arrays.copyOf(serverSignHead, + serverSignHead.length + hashValue.length); + System.arraycopy(hashValue, 0, contentCovered, + serverSignHead.length, hashValue.length); + } else { + contentCovered = Arrays.copyOf(clientSignHead, + clientSignHead.length + hashValue.length); + System.arraycopy(hashValue, 0, contentCovered, + clientSignHead.length, hashValue.length); + } + + try { + Signature signer = + signatureScheme.getSignature(x509Credentials.popPublicKey); + signer.update(contentCovered); + if (!signer.verify(signature)) { + context.conContext.fatal(Alert.HANDSHAKE_FAILURE, + "Invalid CertificateVerify signature"); + } + } catch (NoSuchAlgorithmException | + InvalidAlgorithmParameterException nsae) { + context.conContext.fatal(Alert.INTERNAL_ERROR, + "Unsupported signature algorithm (" + + signatureScheme.name + + ") used in CertificateVerify handshake message", nsae); + } catch (InvalidKeyException | SignatureException ikse) { + context.conContext.fatal(Alert.HANDSHAKE_FAILURE, + "Cannot verify CertificateVerify signature", ikse); + } + } + + @Override + public SSLHandshake handshakeType() { + return SSLHandshake.CERTIFICATE_VERIFY; + } + + @Override + public int messageLength() { + return 4 + signature.length; // 2: signature algorithm + // +2: length of signature + } + + @Override + public void send(HandshakeOutStream hos) throws IOException { + hos.putInt16(signatureScheme.id); + hos.putBytes16(signature); + } + + @Override + public String toString() { + MessageFormat messageFormat = new MessageFormat( + "\"CertificateVerify\": '{'\n" + + " \"signature algorithm\": {0}\n" + + " \"signature\": '{'\n" + + "{1}\n" + + " '}'\n" + + "'}'", + Locale.ENGLISH); + + HexDumpEncoder hexEncoder = new HexDumpEncoder(); + Object[] messageFields = { + signatureScheme.name, + Utilities.indent( + hexEncoder.encodeBuffer(signature), " ") + }; + + return messageFormat.format(messageFields); + } + } + + /** + * The "CertificateVerify" handshake message producer. + */ + private static final + class T13CertificateVerifyProducer implements HandshakeProducer { + // Prevent instantiation of this class. + private T13CertificateVerifyProducer() { + // blank + } + + @Override + public byte[] produce(ConnectionContext context, + HandshakeMessage message) throws IOException { + // The producing happens in handshake context only. + HandshakeContext hc = (HandshakeContext)context; + + X509Possession x509Possession = null; + for (SSLPossession possession : hc.handshakePossessions) { + if (possession instanceof X509Possession) { + x509Possession = (X509Possession)possession; + break; + } + } + + if (x509Possession == null || + x509Possession.popPrivateKey == null) { + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.fine( + "No X.509 credentials negotiated for CertificateVerify"); + } + + return null; + } + + if (hc.sslConfig.isClientMode) { + return onProduceCertificateVerify( + (ClientHandshakeContext)context, x509Possession); + } else { + return onProduceCertificateVerify( + (ServerHandshakeContext)context, x509Possession); + } + } +