diff --git a/src/java.base/share/classes/javax/net/ssl/SSLParameters.java b/src/java.base/share/classes/javax/net/ssl/SSLParameters.java index 3ac38bd91dba9..cfcf01e7004a2 100644 --- a/src/java.base/share/classes/javax/net/ssl/SSLParameters.java +++ b/src/java.base/share/classes/javax/net/ssl/SSLParameters.java @@ -27,6 +27,7 @@ import java.security.AlgorithmConstraints; import java.util.*; +import java.util.function.Function; /** * Encapsulates parameters for an SSL/TLS/DTLS connection. The parameters @@ -86,6 +87,8 @@ public class SSLParameters { private String[] applicationProtocols = new String[0]; private String[] signatureSchemes = null; private String[] namedGroups = null; + private Map> certDeflaters = null; + private Map> certInflaters = null; /** * Constructs SSLParameters. @@ -960,4 +963,316 @@ public void setNamedGroups(String[] namedGroups) { this.namedGroups = tempGroups; } + + /** + * Returns a prioritized map of certificate compression algorithm names + * and functions that can be used over the SSL/TLS/DTLS protocols. + *

+ * Note that the standard list of certificate compression algorithm + * names are defined in the + * Certificate Compression section of the Java Security Standard + * Algorithm Names Specification. Providers may support certificate + * compression algorithms not defined in this list or may not use the + * recommended name for a certain certificate compression algorithm. + *

+ * The set of certificate compression algorithm names and functions + * that will be used over the SSL/TLS/DTLS connections is determined by + * the returned map of this method and the underlying provider-specific + * default certificate compression algorithm names and functions. + *

+ * If the returned map is {@code null}, then the underlying + * provider-specific default certificate compression algorithm names + * and functions will be used over the SSL/TLS/DTLS connections. + *

+ * If the returned map is empty (zero-length), then the certificate + * compression mechanism is turned off for SSL/TLS/DTLS protocols. + *

+ * If the returned map is not {@code null} or empty (zero-length), then + * the certificate compression mechanism is turned on for SSL/TLS/DTLS + * protocols. The certificate compression algorithm names and + * functions in the returned map will be used over the connections. + *

+ * If the {@link #setCertificateDeflaters} method has not been called, + * this method should return the default certificate compression + * algorithm names and functions for connection populated objects, or + * {@code null} for pre-populated objects. + * + * @apiNote + * Note that a provider may not have been updated to support this method + * and in that case may return {@code null} instead of the default + * certificate compression algorithm names and functions for connection + * populated objects. + * + * @implNote + * The SunJSSE provider supports this method, but does not define the + * default certificate compression algorithm names and functions. The + * certificate compression mechanism is turned off by default for the + * SunJSSE provider. Applications could enable the mechanism in the + * SunJSSE provider by calling {@link #setCertificateDeflaters} in + * the compression side and {@link #setCertificateInflaters} in the + * decompression side. + * + * @return an immutable map of certificate compression algorithm names + * {@code String}s and {@link Function}s, or {@code null} if + * none have been set. Each map entry in the map is non-null, and + * represents the certificate compression algorithm. The key of + * the map entry is a non-null {@code String} object which + * represents the certificate compression algorithm name. The + * value of the map entry is a non-null {@link Function} object + * which will be applied to the Certificate message byte array, + * and produce Compressed Certificate message byte array. The + * map entries are ordered based on certificate compression + * algorithm preference, with the first entry being the most + * preferred. Providers should ignore unknown certificate + * compression algorithm names while establishing the + * SSL/TLS/DTLS connections. + * + * @see #setCertificateDeflaters + * + * @since 19 + */ + public Map> getCertificateDeflaters() { + return this.certDeflaters; + } + + /** + * Sets a prioritized map of certificate compression algorithm names and + * functions that can be used over the SSL/TLS/DTLS protocols. + *

+ * Note that the standard list of certificate compression algorithm + * names are defined in the + * Certificate Compression section of the Java Security Standard + * Algorithm Names Specification. Providers may support certificate + * compression algorithms not defined in this list or may not use the + * recommended name for a certain certificate compression algorithm. + *

+ * The set of certificate compression algorithm names and functions + * that will be used over the SSL/TLS/DTLS connections is determined by + * the returned map of this method and the underlying provider-specific + * default certificate compression algorithm names and functions. See + * {@link #getCertificateDeflaters} for specific details on how the + * parameters are used in SSL/TLS/DTLS connections. + * + * @apiNote + * Note that a provider may not have been updated to support this method + * and in that case may ignore the certificate compression algorithm + * names and functions that are set. + * + * @implNote + * The SunJSSE provider supports this method. + * + * @param certDeflaters an ordered map of certificate compression + * algorithm names {@code String}s and {@link Function}s with the + * first entry being the most preferred, or {@code null}. Each + * entry in the map is non-null, and represents the certificate + * compression algorithm. The key of the map entry is a non-null + * {@code String} object which represents the certificate + * compression algorithm name. The value of the map entry is a + * non-null {@link Function} object which will be applied to the + * Certificate message byte array, and produce Compressed + * Certificate message byte array. This method will make a copy of + * this map if necessary to protect against subsequent modification. + * Providers should ignore unknown certificate compression + * algorithm names and the relevant functions while establishing + * the SSL/TLS/DTLS connections. + * @throws IllegalArgumentException if any entry, key, or value in the + * {@code certDeflaters} map is {@code null}, or any key is + * {@linkplain String#isBlank() blank}. + * + * @see #getCertificateDeflaters + * + * @since 19 + */ + public void setCertificateDeflaters( + Map> certDeflaters) { + if (this.certDeflaters == certDeflaters) { + return; + } + + if (certDeflaters == null) { + this.certDeflaters = null; + } else if (certDeflaters.isEmpty()) { + this.certDeflaters = Map.of(); + } else { + @SuppressWarnings({"unchecked", "rawtypes"}) + Map.Entry>[] entries = + new Map.Entry[certDeflaters.size()]; + int i = 0; + for (Map.Entry> entry : + certDeflaters.entrySet()) { + if (entry == null) { + throw new IllegalArgumentException( + "Null entry is not allowed"); + } else if (entry.getKey() == null || entry.getKey().isBlank()) { + throw new IllegalArgumentException( + "Null or blank compression algorithm " + + "name is not allowed"); + } else if (entry.getValue() == null) { + throw new IllegalArgumentException( + "Null compression function is not allowed"); + } + entries[i++] = entry; + } + + this.certDeflaters = Map.ofEntries(entries); + } + } + + /** + * Returns a prioritized map of certificate decompression algorithm + * names and functions that can be used over the SSL/TLS/DTLS protocols. + *

+ * Note that the standard list of certificate decompression algorithm + * names are defined in the + * Certificate Compression section of the Java Security Standard + * Algorithm Names Specification. Providers may support certificate + * decompression algorithms not defined in this list or may not use the + * recommended name for a certain certificate decompression algorithm. + *

+ * The set of certificate decompression algorithm names and functions + * that will be used over the SSL/TLS/DTLS connections is determined by + * the returned map of this method and the underlying provider-specific + * default certificate decompression algorithm names and functions. + *

+ * If the returned map is {@code null}, then the underlying + * provider-specific default certificate decompression algorithm names + * and functions will be used over the connections. + *

+ * If the returned map is empty (zero-length), then the certificate + * compression mechanism is turned off for SSL/TLS/DTLS protocols. + *

+ * If the returned map is not {@code null} or empty (zero-length), then + * the certificate compression mechanism is turned on for SSL/TLS/DTLS + * protocols. The certificate decompression algorithm names and + * functions in the returned map will be used over the connections. + *

+ * If the {@link #setCertificateInflaters} method has not been called, + * this method should return the default certificate decompression + * algorithm names and functions for connection populated objects, or + * {@code null} for pre-populated objects. + * + * @apiNote + * Note that a provider may not have been updated to support this method + * and in that case may return {@code null} instead of the default + * certificate decompression algorithm names and functions for connection + * populated objects. + * + * @implNote + * The SunJSSE provider supports this method, but does not define the + * default certificate decompression algorithm names and functions. The + * certificate compression mechanism is turned off by default for the + * SunJSSE provider. Applications could enable the mechanism in the + * SunJSSE provider by calling {@link #setCertificateDeflaters} in + * the compression side and {@link #setCertificateInflaters} in the + * decompression side. + * + * @return an immutable map of certificate decompression algorithm names + * {@code String}s and {@link Function}s, or {@code null} if + * none have been set. Each map entry in the map is non-null, and + * represents the certificate decompression algorithm. The key of + * the map entry is a non-null {@code String} object which + * represents the certificate decompression algorithm name. The + * value of the map entry is a non-null {@link Function} object + * which will be applied to the Compressed Certificate message + * byte array, and produce Certificate message byte array. The + * map entries are ordered based on certificate decompression + * algorithm preference, with the first entry being the most + * preferred. Providers should ignore unknown certificate + * decompression algorithm names while establishing the + * SSL/TLS/DTLS connections. + * + * @see #setCertificateDeflaters + * + * @since 19 + */ + public Map> getCertificateInflaters() { + return this.certInflaters; + } + + /** + * Sets a prioritized map of certificate decompression algorithm names + * and functions that can be used over the SSL/TLS/DTLS protocols. + *

+ * Note that the standard list of certificate decompression algorithm + * names are defined in the + * Certificate Compression section of the Java Security Standard + * Algorithm Names Specification. Providers may support certificate + * decompression algorithms not defined in this list or may not use the + * recommended name for a certain certificate decompression algorithm. + *

+ * The set of certificate decompression algorithm names and functions + * that will be used over the SSL/TLS/DTLS connections is determined by + * the returned map of this method and the underlying provider-specific + * default certificate decompression algorithm names and functions. See + * {@link #getCertificateInflaters} for specific details on how the + * parameters are used in SSL/TLS/DTLS connections. + * + * @apiNote + * Note that a provider may not have been updated to support this method + * and in that case may ignore the certificate decompression algorithm + * names and functions that are set. + * + * @implNote + * The SunJSSE provider supports this method. + * + * @param certInflaters an ordered map of certificate decompression + * algorithm names {@code String}s and {@link Function}s with the + * first entry being the most preferred, or {@code null}. Each + * entry in the map is non-null, and represents the certificate + * decompression algorithm. The key of the map entry is a non-null + * {@code String} object which represents the certificate + * decompression algorithm name. The value of the map entry is a + * non-null {@link Function} object which will be applied to the + * Compressed Certificate message byte array, and produce + * Certificate message byte array. This method will make a copy of + * this map if necessary to protect against subsequent modification. + * Providers should ignore unknown certificate decompression + * algorithm names and the relevant functions while establishing + * the SSL/TLS/DTLS connections. + * @throws IllegalArgumentException if any entry, key, or value in the + * {@code certInflaters} map is {@code null}, or any key is + * {@linkplain String#isBlank() blank}. + * + * @see #getCertificateInflaters + * + * @since 19 + */ + public void setCertificateInflaters( + Map> certInflaters) { + if (this.certInflaters == certInflaters) { + return; + } + + if (certInflaters == null) { + this.certInflaters = null; + } else if (certInflaters.isEmpty()) { + this.certInflaters = Map.of(); + } else { + @SuppressWarnings({"unchecked", "rawtypes"}) + Map.Entry>[] entries = + new Map.Entry[certInflaters.size()]; + int i = 0; + for (Map.Entry> entry : + certInflaters.entrySet()) { + if (entry == null) { + throw new IllegalArgumentException( + "Null entry is not allowed"); + } else if (entry.getKey() == null || entry.getKey().isBlank()) { + throw new IllegalArgumentException( + "Null or blank decompression algorithm " + + "name is not allowed"); + } else if (entry.getValue() == null) { + throw new IllegalArgumentException( + "Null decompression function is not allowed"); + } + entries[i++] = entry; + } + + this.certInflaters = Map.ofEntries(entries); + } + } } diff --git a/src/java.base/share/classes/sun/security/ssl/Alert.java b/src/java.base/share/classes/sun/security/ssl/Alert.java index 4e1ccf385c728..761699c7aa6d5 100644 --- a/src/java.base/share/classes/sun/security/ssl/Alert.java +++ b/src/java.base/share/classes/sun/security/ssl/Alert.java @@ -271,6 +271,8 @@ public void consume(ConnectionContext context, // consumer so the state machine doesn't expect it. tc.handshakeContext.handshakeConsumers.remove( SSLHandshake.CERTIFICATE.id); + tc.handshakeContext.handshakeConsumers.remove( + SSLHandshake.COMPRESSED_CERTIFICATE.id); tc.handshakeContext.handshakeConsumers.remove( SSLHandshake.CERTIFICATE_VERIFY.id); } diff --git a/src/java.base/share/classes/sun/security/ssl/CertificateMessage.java b/src/java.base/share/classes/sun/security/ssl/CertificateMessage.java index c055673b45ba2..0eaf807a364db 100644 --- a/src/java.base/share/classes/sun/security/ssl/CertificateMessage.java +++ b/src/java.base/share/classes/sun/security/ssl/CertificateMessage.java @@ -917,16 +917,26 @@ 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( + T13CertificateMessage cm = hc.sslConfig.isClientMode ? + onProduceCertificate( + (ClientHandshakeContext)context, message) : + onProduceCertificate( (ServerHandshakeContext)context, message); + + // Output the handshake message. + if (hc.certDeflater == null) { + cm.write(hc.handshakeOutput); + hc.handshakeOutput.flush(); + } else { + // Replace with CompressedCertificate message + CompressedCertificate.handshakeProducer.produce(hc, cm); } + + // The handshake message has been delivered. + return null; } - private byte[] onProduceCertificate(ServerHandshakeContext shc, + private T13CertificateMessage onProduceCertificate(ServerHandshakeContext shc, HandshakeMessage message) throws IOException { ClientHelloMessage clientHello = (ClientHelloMessage)message; @@ -984,12 +994,7 @@ private byte[] onProduceCertificate(ServerHandshakeContext shc, 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; + return cm; } private static SSLPossession choosePossession( @@ -1028,7 +1033,7 @@ private static SSLPossession choosePossession( return pos; } - private byte[] onProduceCertificate(ClientHandshakeContext chc, + private T13CertificateMessage onProduceCertificate(ClientHandshakeContext chc, HandshakeMessage message) throws IOException { ClientHelloMessage clientHello = (ClientHelloMessage)message; SSLPossession pos = choosePossession(chc, clientHello); @@ -1071,12 +1076,7 @@ private byte[] onProduceCertificate(ClientHandshakeContext chc, 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; + return cm; } } @@ -1096,6 +1096,7 @@ public void consume(ConnectionContext context, HandshakeContext hc = (HandshakeContext)context; // clean up this consumer + hc.handshakeConsumers.remove(SSLHandshake.COMPRESSED_CERTIFICATE.id); hc.handshakeConsumers.remove(SSLHandshake.CERTIFICATE.id); T13CertificateMessage cm = new T13CertificateMessage(hc, message); if (hc.sslConfig.isClientMode) { diff --git a/src/java.base/share/classes/sun/security/ssl/CertificateRequest.java b/src/java.base/share/classes/sun/security/ssl/CertificateRequest.java index 66b8c0487032f..2666a2c3437b1 100644 --- a/src/java.base/share/classes/sun/security/ssl/CertificateRequest.java +++ b/src/java.base/share/classes/sun/security/ssl/CertificateRequest.java @@ -945,6 +945,11 @@ public byte[] produce(ConnectionContext context, // update // shc.certRequestContext = crm.requestContext.clone(); + if (shc.certInflaters != null && !shc.certInflaters.isEmpty()) { + shc.handshakeConsumers.put( + SSLHandshake.COMPRESSED_CERTIFICATE.id, + SSLHandshake.COMPRESSED_CERTIFICATE); + } shc.handshakeConsumers.put(SSLHandshake.CERTIFICATE.id, SSLHandshake.CERTIFICATE); shc.handshakeConsumers.put(SSLHandshake.CERTIFICATE_VERIFY.id, diff --git a/src/java.base/share/classes/sun/security/ssl/CompressCertExtension.java b/src/java.base/share/classes/sun/security/ssl/CompressCertExtension.java new file mode 100644 index 0000000000000..1a23107264bc4 --- /dev/null +++ b/src/java.base/share/classes/sun/security/ssl/CompressCertExtension.java @@ -0,0 +1,370 @@ +/* + * Copyright (C) 2022 THL A29 Limited, a Tencent company. 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 sun.security.ssl.SSLExtension.ExtensionConsumer; +import sun.security.ssl.SSLExtension.SSLExtensionSpec; +import sun.security.ssl.SSLHandshake.HandshakeMessage; + +import javax.net.ssl.SSLProtocolException; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.text.MessageFormat; +import java.util.Locale; +import java.util.Map; +import java.util.function.Function; + +/** + * Pack of the "compress_certificate" extensions [RFC 5246]. + */ +final class CompressCertExtension { + static final HandshakeProducer chNetworkProducer = + new CHCompressCertificateProducer(); + static final ExtensionConsumer chOnLoadConsumer = + new CHCompressCertificateConsumer(); + + static final HandshakeProducer crNetworkProducer = + new CRCompressCertificateProducer(); + static final ExtensionConsumer crOnLoadConsumer = + new CRCompressCertificateConsumer(); + + static final SSLStringizer ccStringizer = + new CompressCertificateStringizer(); + + /** + * The "signature_algorithms" extension. + */ + static final class CertCompressionSpec implements SSLExtensionSpec { + private final int[] compressionAlgorithms; // non-null + + CertCompressionSpec( + Map> certInflaters) { + compressionAlgorithms = new int[certInflaters.size()]; + int i = 0; + for (Integer id : certInflaters.keySet()) { + compressionAlgorithms[i++] = id; + } + } + + CertCompressionSpec(HandshakeContext hc, + ByteBuffer buffer) throws IOException { + if (buffer.remaining() < 2) { // 2: the length of the list + throw hc.conContext.fatal(Alert.DECODE_ERROR, + new SSLProtocolException( + "Invalid compress_certificate: insufficient data")); + } + + byte[] algs = Record.getBytes8(buffer); + if (buffer.hasRemaining()) { + throw hc.conContext.fatal(Alert.DECODE_ERROR, + new SSLProtocolException( + "Invalid compress_certificate: unknown extra data")); + } + + if (algs.length == 0 || (algs.length & 0x01) != 0) { + throw hc.conContext.fatal(Alert.DECODE_ERROR, + new SSLProtocolException( + "Invalid compress_certificate: incomplete data")); + } + + int[] compressionAlgs = new int[algs.length / 2]; + for (int i = 0, j = 0; i < algs.length;) { + byte hash = algs[i++]; + byte sign = algs[i++]; + compressionAlgs[j++] = ((hash & 0xFF) << 8) | (sign & 0xFF); + } + + this.compressionAlgorithms = compressionAlgs; + } + + @Override + public String toString() { + MessageFormat messageFormat = new MessageFormat( + "\"compression algorithms\": '['{0}']'", Locale.ENGLISH); + + if (compressionAlgorithms.length == 0) { + Object[] messageFields = { + "" + }; + return messageFormat.format(messageFields); + } else { + StringBuilder builder = new StringBuilder(512); + boolean isFirst = true; + for (int ca : compressionAlgorithms) { + if (isFirst) { + isFirst = false; + } else { + builder.append(", "); + } + + builder.append(CompressionAlgorithm.nameOf(ca)); + } + + Object[] messageFields = { + builder.toString() + }; + + return messageFormat.format(messageFields); + } + } + } + + private static final + class CompressCertificateStringizer implements SSLStringizer { + @Override + public String toString(HandshakeContext hc, ByteBuffer buffer) { + try { + return (new CertCompressionSpec(hc, buffer)).toString(); + } catch (IOException ioe) { + // For debug logging only, so please swallow exceptions. + return ioe.getMessage(); + } + } + } + + /** + * Network data producer of a "compress_certificate" extension in + * the ClientHello handshake message. + */ + private static final + class CHCompressCertificateProducer implements HandshakeProducer { + // Prevent instantiation of this class. + private CHCompressCertificateProducer() { + // 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_COMPRESS_CERTIFICATE)) { + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.fine("Ignore unavailable " + + "compress_certificate extension"); + } + return null; + } + + // Produce the extension. + if (chc.certInflaters == null) { + chc.certInflaters = + CompressionAlgorithm.findInflaters(chc.sslConfig); + } + + if (chc.certInflaters.isEmpty()) { + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.fine("Ignore unsupported " + + "compress_certificate extension"); + } + return null; + } + + int vectorLen = CompressionAlgorithm.sizeInRecord() * + chc.certInflaters.size(); + byte[] extData = new byte[vectorLen + 1]; + ByteBuffer m = ByteBuffer.wrap(extData); + Record.putInt8(m, vectorLen); + for (Integer algId : chc.certInflaters.keySet()) { + Record.putInt16(m, algId); + } + + // Update the context. + chc.handshakeExtensions.put( + SSLExtension.CH_COMPRESS_CERTIFICATE, + new CertCompressionSpec(chc.certInflaters)); + + return extData; + } + } + + /** + * Network data consumer of a "compress_certificate" extension in + * the ClientHello handshake message. + */ + private static final + class CHCompressCertificateConsumer implements ExtensionConsumer { + // Prevent instantiation of this class. + private CHCompressCertificateConsumer() { + // 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_COMPRESS_CERTIFICATE)) { + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.fine("Ignore unavailable " + + "compress_certificate extension"); + } + return; // ignore the extension + } + + if (shc.sslConfig.certDeflaters == null || + shc.sslConfig.certDeflaters.isEmpty()) { + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.fine("Ignore unsupported " + + "compress_certificate extension"); + } + return; // ignore the extension + } + + // Parse the extension. + CertCompressionSpec spec = new CertCompressionSpec(shc, buffer); + + // Update the context. + shc.certDeflater = CompressionAlgorithm.selectDeflater( + shc.sslConfig, spec.compressionAlgorithms); + if (shc.certDeflater == null) { + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.fine("Ignore, no supported " + + "certificate compression algorithms"); + } + } + + // No impact on session resumption. + } + } + + /** + * Network data producer of a "compress_certificate" extension in + * the CertificateRequest handshake message. + */ + private static final + class CRCompressCertificateProducer implements HandshakeProducer { + // Prevent instantiation of this class. + private CRCompressCertificateProducer() { + // 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.CR_COMPRESS_CERTIFICATE)) { + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.fine("Ignore unavailable " + + "compress_certificate extension"); + } + return null; + } + + // Produce the extension. + if (shc.certInflaters == null) { + shc.certInflaters = + CompressionAlgorithm.findInflaters(shc.sslConfig); + } + + if (shc.certInflaters.isEmpty()) { + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.fine("Ignore unsupported " + + "compress_certificate extension"); + } + return null; + } + + int vectorLen = CompressionAlgorithm.sizeInRecord() * + shc.certInflaters.size(); + byte[] extData = new byte[vectorLen + 1]; + ByteBuffer m = ByteBuffer.wrap(extData); + Record.putInt8(m, vectorLen); + for (Integer algId : shc.certInflaters.keySet()) { + Record.putInt16(m, algId); + } + + // Update the context. + shc.handshakeExtensions.put( + SSLExtension.CR_COMPRESS_CERTIFICATE, + new CertCompressionSpec(shc.certInflaters)); + + return extData; + } + } + + /** + * Network data consumer of a "compress_certificate" extension in + * the CertificateRequest handshake message. + */ + private static final + class CRCompressCertificateConsumer implements ExtensionConsumer { + // Prevent instantiation of this class. + private CRCompressCertificateConsumer() { + // 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.CR_COMPRESS_CERTIFICATE)) { + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.fine("Ignore unavailable " + + "compress_certificate extension"); + } + return; // ignore the extension + } + + if (chc.sslConfig.certDeflaters == null || + chc.sslConfig.certDeflaters.isEmpty()) { + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.fine("Ignore unsupported " + + "compress_certificate extension"); + } + return; // ignore the extension + } + + // Parse the extension. + CertCompressionSpec spec = new CertCompressionSpec(chc, buffer); + + // Update the context. + chc.certDeflater = CompressionAlgorithm.selectDeflater( + chc.sslConfig, spec.compressionAlgorithms); + if (chc.certDeflater == null) { + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.fine("Ignore, no supported " + + "certificate compression algorithms"); + } + } + + // No impact on session resumption. + } + } +} diff --git a/src/java.base/share/classes/sun/security/ssl/CompressedCertificate.java b/src/java.base/share/classes/sun/security/ssl/CompressedCertificate.java new file mode 100644 index 0000000000000..e391c493345dc --- /dev/null +++ b/src/java.base/share/classes/sun/security/ssl/CompressedCertificate.java @@ -0,0 +1,227 @@ +/* + * Copyright (C) 2022 THL A29 Limited, a Tencent company. 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 sun.security.ssl.SSLHandshake.HandshakeMessage; +import sun.security.util.HexDumpEncoder; + +import javax.net.ssl.*; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.text.MessageFormat; +import java.util.*; +import java.util.function.Function; + +/** + * Pack of the CompressedCertificate handshake message. + */ +final class CompressedCertificate { + static final SSLConsumer handshakeConsumer = + new CompressedCertConsumer(); + static final HandshakeProducer handshakeProducer = + new CompressedCertProducer(); + + /** + * The CompressedCertificate handshake message for TLS 1.3. + */ + static final class CompressedCertMessage extends HandshakeMessage { + private final int algorithmId; + private final int uncompressedLength; + private final byte[] compressedCert; + + CompressedCertMessage(HandshakeContext context, + int algorithmId, int uncompressedLength, + byte[] compressedCert) { + super(context); + + this.algorithmId = algorithmId; + this.uncompressedLength = uncompressedLength; + this.compressedCert = compressedCert; + } + + CompressedCertMessage(HandshakeContext handshakeContext, + ByteBuffer m) throws IOException { + super(handshakeContext); + + // struct { + // CertificateCompressionAlgorithm algorithm; + // uint24 uncompressed_length; + // opaque compressed_certificate_message<1..2^24-1>; + // } CompressedCertificate; + if (m.remaining() < 9) { + throw new SSLProtocolException( + "Invalid CompressedCertificate message: " + + "insufficient data (length=" + m.remaining() + ")"); + } + this.algorithmId = Record.getInt16(m); + this.uncompressedLength = Record.getInt24(m); + this.compressedCert = Record.getBytes24(m); + + if (m.hasRemaining()) { + throw handshakeContext.conContext.fatal( + Alert.HANDSHAKE_FAILURE, + "Invalid CompressedCertificate message: " + + "unknown extra data"); + } + } + + @Override + public SSLHandshake handshakeType() { + return SSLHandshake.COMPRESSED_CERTIFICATE; + } + + @Override + public int messageLength() { + return 8 + compressedCert.length; + } + + @Override + public void send(HandshakeOutStream hos) throws IOException { + hos.putInt16(algorithmId); + hos.putInt24(uncompressedLength); + hos.putBytes24(compressedCert); + } + + @Override + public String toString() { + MessageFormat messageFormat = new MessageFormat( + """ + "CompressedCertificate": '{' + "algorithm": "{0}", + "uncompressed_length": {1} + "compressed_certificate_message": [ + {2} + ] + '}'""", + Locale.ENGLISH); + + HexDumpEncoder hexEncoder = new HexDumpEncoder(); + Object[] messageFields = { + CompressionAlgorithm.nameOf(algorithmId), + uncompressedLength, + Utilities.indent(hexEncoder.encode(compressedCert), " ") + }; + + return messageFormat.format(messageFields); + } + } + + /** + * The "Certificate" handshake message producer for TLS 1.3. + */ + private static final + class CompressedCertProducer implements HandshakeProducer { + // Prevent instantiation of this class. + private CompressedCertProducer() { + // blank + } + + // Note this is a special producer, which can only be called from + // the CertificateMessage producer. The input 'message' parameter + // represents the Certificate handshake message. + @Override + public byte[] produce(ConnectionContext context, + HandshakeMessage message) throws IOException { + // The producing happens in handshake context only. + HandshakeContext hc = (HandshakeContext)context; + + // Compress the Certificate message. + HandshakeOutStream hos = new HandshakeOutStream(null); + message.send(hos); + byte[] certMsg = hos.toByteArray(); + byte[] compressedCertMsg = + hc.certDeflater.getValue().apply(certMsg); + if (compressedCertMsg == null || compressedCertMsg.length == 0) { + throw hc.conContext.fatal(Alert.HANDSHAKE_FAILURE, + "No compressed Certificate data"); + } + + CompressedCertMessage ccm = new CompressedCertMessage(hc, + hc.certDeflater.getKey(), certMsg.length, + compressedCertMsg); + + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.fine("Produced Compressed Certificate message", ccm); + } + + ccm.write(hc.handshakeOutput); + hc.handshakeOutput.flush(); + + // The handshake message has been delivered. + return null; + } + } + + /** + * The "Certificate" handshake message consumer for TLS 1.3. + */ + private static final class CompressedCertConsumer implements SSLConsumer { + // Prevent instantiation of this class. + private CompressedCertConsumer() { + // 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.COMPRESSED_CERTIFICATE.id); + hc.handshakeConsumers.remove(SSLHandshake.CERTIFICATE.id); + + // Parse the handshake message + CompressedCertMessage ccm = new CompressedCertMessage(hc, message); + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.fine( + "Consuming CompressedCertificate handshake message", ccm); + } + + // check the compression algorithm + Function inflater = + hc.certInflaters.get(ccm.algorithmId); + if (inflater == null) { + throw hc.conContext.fatal(Alert.BAD_CERTIFICATE, + "Unsupported certificate compression algorithm"); + } + + // decompress + byte[] certificateMessage = inflater.apply(ccm.compressedCert); + + // check the uncompressed length + if (certificateMessage == null || + certificateMessage.length != ccm.uncompressedLength) { + throw hc.conContext.fatal(Alert.BAD_CERTIFICATE, + "Improper certificate compression"); + } + + // Call the Certificate handshake message consumer. + CertificateMessage.t13HandshakeConsumer.consume(hc, + ByteBuffer.wrap(certificateMessage)); + } + } +} diff --git a/src/java.base/share/classes/sun/security/ssl/CompressionAlgorithm.java b/src/java.base/share/classes/sun/security/ssl/CompressionAlgorithm.java new file mode 100644 index 0000000000000..3b561ebf2d22d --- /dev/null +++ b/src/java.base/share/classes/sun/security/ssl/CompressionAlgorithm.java @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2022 THL A29 Limited, a Tencent company. 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.util.AbstractMap; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.function.Function; + +/** + * Enum for (D)TLS certificate compression algorithms. + */ +enum CompressionAlgorithm { + ZLIB (1, "zlib"), + BROTLI (2, "brotli"), + ZSTD (3, "zstd"); + + final int id; + final String name; + + CompressionAlgorithm(int id, String name) { + this.id = id; + this.name = name; + } + + static CompressionAlgorithm nameOf(String name) { + for (CompressionAlgorithm cca : + CompressionAlgorithm.values()) { + if (cca.name.equals(name)) { + return cca; + } + } + + return null; + } + + static String nameOf(int id) { + for (CompressionAlgorithm cca : + CompressionAlgorithm.values()) { + if (cca.id == id) { + return cca.name; + } + } + + return ""; + } + + // Return the size of a SignatureScheme structure in TLS record + static int sizeInRecord() { + return 2; + } + + // Get local supported algorithm collection. + static Map> findInflaters( + SSLConfiguration config) { + if (config.certInflaters == null || config.certInflaters.isEmpty()) { + if (SSLLogger.isOn && + SSLLogger.isOn("ssl,handshake,verbose")) { + SSLLogger.finest( + "No supported certificate compression algorithms"); + } + return Map.of(); + } + + Map> inflaters = + new LinkedHashMap<>(config.certInflaters.size()); + for (Map.Entry> entry : + config.certInflaters.entrySet()) { + CompressionAlgorithm ca = + CompressionAlgorithm.nameOf(entry.getKey()); + if (ca == null) { + if (SSLLogger.isOn && + SSLLogger.isOn("ssl,handshake,verbose")) { + SSLLogger.finest( + "Ignore unsupported certificate " + + "compression algorithm: " + entry.getKey()); + } + continue; + } + + inflaters.putIfAbsent(ca.id, entry.getValue()); + } + + return inflaters; + } + + static Map.Entry> selectDeflater( + SSLConfiguration config, + int[] compressionAlgorithmIds) { + if (config.certDeflaters == null) { + return null; + } + + for (Map.Entry> entry : + config.certDeflaters.entrySet()) { + CompressionAlgorithm ca = + CompressionAlgorithm.nameOf(entry.getKey()); + if (ca != null) { + for (int id : compressionAlgorithmIds) { + if (ca.id == id) { + return new AbstractMap.SimpleImmutableEntry<>( + id, entry.getValue()); + } + } + } + } + + return null; + } +} diff --git a/src/java.base/share/classes/sun/security/ssl/HandshakeContext.java b/src/java.base/share/classes/sun/security/ssl/HandshakeContext.java index 8455ddfc65d3f..721c105f27840 100644 --- a/src/java.base/share/classes/sun/security/ssl/HandshakeContext.java +++ b/src/java.base/share/classes/sun/security/ssl/HandshakeContext.java @@ -33,6 +33,7 @@ import java.security.CryptoPrimitive; import java.util.*; import java.util.AbstractMap.SimpleImmutableEntry; +import java.util.function.Function; import javax.crypto.SecretKey; import javax.net.ssl.SNIServerName; import javax.net.ssl.SSLHandshakeException; @@ -131,6 +132,10 @@ abstract class HandshakeContext implements ConnectionContext { List peerRequestedSignatureSchemes; List peerRequestedCertSignSchemes; + // CertificateCompressionAlgorithm + Map> certInflaters; + Map.Entry> certDeflater; + // Known authorities X500Principal[] peerSupportedAuthorities = null; diff --git a/src/java.base/share/classes/sun/security/ssl/SSLConfiguration.java b/src/java.base/share/classes/sun/security/ssl/SSLConfiguration.java index bb032e019d31c..168937f246fe7 100644 --- a/src/java.base/share/classes/sun/security/ssl/SSLConfiguration.java +++ b/src/java.base/share/classes/sun/security/ssl/SSLConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2025, 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 @@ -29,6 +29,7 @@ import java.security.NoSuchAlgorithmException; import java.util.*; import java.util.function.BiFunction; +import java.util.function.Function; import javax.crypto.KeyGenerator; import javax.net.ssl.HandshakeCompletedListener; import javax.net.ssl.SNIMatcher; @@ -63,6 +64,11 @@ final class SSLConfiguration implements Cloneable { // the configured named groups for the "supported_groups" extensions String[] namedGroups; + // The configured certificate compression algorithms for + // "compress_certificate" extensions + Map> certDeflaters; + Map> certInflaters; + // the maximum protocol version of enabled protocols ProtocolVersion maximumProtocolVersion; @@ -242,6 +248,10 @@ final class SSLConfiguration implements Cloneable { this.signatureSchemes = isClientMode ? CustomizedClientSignatureSchemes.signatureSchemes : CustomizedServerSignatureSchemes.signatureSchemes; + + this.certDeflaters = Map.of(); + this.certInflaters = Map.of(); + this.namedGroups = NamedGroup.SupportedGroups.namedGroups; this.maximumProtocolVersion = ProtocolVersion.NONE; for (ProtocolVersion pv : enabledProtocols) { @@ -293,6 +303,8 @@ SSLParameters getSSLParameters() { params.setSNIMatchers(this.sniMatchers); } + params.setCertificateDeflaters(this.certDeflaters); + params.setCertificateInflaters(this.certInflaters); params.setApplicationProtocols(this.applicationProtocols); params.setUseCipherSuitesOrder(this.preferLocalCipherSuites); params.setEnableRetransmissions(this.enableRetransmissions); @@ -372,6 +384,8 @@ void setSSLParameters(SSLParameters params) { this.namedGroups = NamedGroup.SupportedGroups.namedGroups; } + this.certDeflaters = params.getCertificateDeflaters(); + this.certInflaters = params.getCertificateInflaters(); this.preferLocalCipherSuites = params.getUseCipherSuitesOrder(); this.enableRetransmissions = params.getEnableRetransmissions(); this.maximumPacketSize = params.getMaximumPacketSize(); diff --git a/src/java.base/share/classes/sun/security/ssl/SSLExtension.java b/src/java.base/share/classes/sun/security/ssl/SSLExtension.java index b28ef763796f3..794503e054f4c 100644 --- a/src/java.base/share/classes/sun/security/ssl/SSLExtension.java +++ b/src/java.base/share/classes/sun/security/ssl/SSLExtension.java @@ -270,6 +270,27 @@ enum SSLExtension implements SSLStringizer { // Extensions defined in RFC 7924 (TLS Cached Information Extension) CACHED_INFO (0x0019, "cached_info"), + // Extensions defined in RFC 8879 (TLS Certificate Compression) + CH_COMPRESS_CERTIFICATE (0x001B, "compress_certificate", + SSLHandshake.CLIENT_HELLO, + ProtocolVersion.PROTOCOLS_OF_13, + CompressCertExtension.chNetworkProducer, + CompressCertExtension.chOnLoadConsumer, + null, + null, + null, + CompressCertExtension.ccStringizer), + + CR_COMPRESS_CERTIFICATE (0x001B, "compress_certificate", + SSLHandshake.CERTIFICATE_REQUEST, + ProtocolVersion.PROTOCOLS_OF_13, + CompressCertExtension.crNetworkProducer, + CompressCertExtension.crOnLoadConsumer, + null, + null, + null, + CompressCertExtension.ccStringizer), + // Extensions defined in RFC 5077 (TLS Session Resumption without Server-Side State) CH_SESSION_TICKET (0x0023, "session_ticket", SSLHandshake.CLIENT_HELLO, diff --git a/src/java.base/share/classes/sun/security/ssl/SSLHandshake.java b/src/java.base/share/classes/sun/security/ssl/SSLHandshake.java index 7c78f6c300526..7fe31e5c3c46a 100644 --- a/src/java.base/share/classes/sun/security/ssl/SSLHandshake.java +++ b/src/java.base/share/classes/sun/security/ssl/SSLHandshake.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2006, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2006, 2025, 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 @@ -370,7 +370,22 @@ enum SSLHandshake implements SSLConsumer, HandshakeProducer { }), // RFC 8879 - TLS Certificate Compression - COMPRESSED_CERTIFICATE ((byte)0x19, "compressed_certificate"), + @SuppressWarnings({"unchecked", "rawtypes"}) + COMPRESSED_CERTIFICATE ((byte)0x19, "compressed_certificate", + (new Map.Entry[] { + new SimpleImmutableEntry<>( + CompressedCertificate.handshakeConsumer, + ProtocolVersion.PROTOCOLS_OF_13 + ) + }), + (new Map.Entry[] { + // Note that the producing of this message is delegated to + // CertificateMessage producer. + new SimpleImmutableEntry<>( + CertificateMessage.t13HandshakeProducer, + ProtocolVersion.PROTOCOLS_OF_13 + ) + })), // RFC 8870 - Encrypted Key Transport for DTLS/Secure RTP EKT_KEY ((byte)0x1A, "ekt_key"), diff --git a/src/java.base/share/classes/sun/security/ssl/ServerHello.java b/src/java.base/share/classes/sun/security/ssl/ServerHello.java index d092d6c07ded2..c9bf57249a314 100644 --- a/src/java.base/share/classes/sun/security/ssl/ServerHello.java +++ b/src/java.base/share/classes/sun/security/ssl/ServerHello.java @@ -1387,6 +1387,11 @@ public void consume(ConnectionContext context, chc.handshakeConsumers.put( SSLHandshake.CERTIFICATE_REQUEST.id, SSLHandshake.CERTIFICATE_REQUEST); + if (chc.certInflaters != null && !chc.certInflaters.isEmpty()) { + chc.handshakeConsumers.put( + SSLHandshake.COMPRESSED_CERTIFICATE.id, + SSLHandshake.COMPRESSED_CERTIFICATE); + } chc.handshakeConsumers.put( SSLHandshake.CERTIFICATE.id, SSLHandshake.CERTIFICATE); diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/common/Utils.java b/src/java.net.http/share/classes/jdk/internal/net/http/common/Utils.java index d035a8c8da1cf..56d309ecfc293 100644 --- a/src/java.net.http/share/classes/jdk/internal/net/http/common/Utils.java +++ b/src/java.net.http/share/classes/jdk/internal/net/http/common/Utils.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2025, 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 @@ -599,6 +599,9 @@ public static SSLParameters copySSLParameters(SSLParameters p) { p1.setSNIMatchers(p.getSNIMatchers()); p1.setServerNames(p.getServerNames()); p1.setUseCipherSuitesOrder(p.getUseCipherSuitesOrder()); + p1.setWantClientAuth(p.getWantClientAuth()); + p1.setCertificateDeflaters(p.getCertificateDeflaters()); + p1.setCertificateInflaters(p.getCertificateInflaters()); return p1; } diff --git a/test/jdk/javax/net/ssl/HttpsURLConnection/HttpsCompressedCert.java b/test/jdk/javax/net/ssl/HttpsURLConnection/HttpsCompressedCert.java new file mode 100644 index 0000000000000..a132b26dc6188 --- /dev/null +++ b/test/jdk/javax/net/ssl/HttpsURLConnection/HttpsCompressedCert.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2022 THL A29 Limited, a Tencent company. 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. + * + * 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. + */ + +/** + * @test + * @bug 8273042 + * @summary TLS certificate compression + * @library /test/lib + * @run main/othervm HttpsCompressedCert + */ + +import static java.net.http.HttpResponse.BodyHandlers.ofString; +import static jdk.test.lib.Asserts.assertEquals; + +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.util.Map; +import java.util.function.Function; +import java.util.zip.Inflater; +import javax.net.ssl.SSLParameters; + +public class HttpsCompressedCert { + private static final Function certInflater = (input) -> { + try { + Inflater inflater = new Inflater(); + inflater.setInput(input); + byte[] output = new byte[1024 * 8]; + int l = inflater.inflate(output); + inflater.end(); + + byte[] data = new byte[l]; + System.arraycopy(output, 0, data, 0, l); + + return data; + } catch (Exception ex) { + // just ignore + return null; + } + }; + + public static void main(String[] args) throws Exception { + SSLParameters sslParameters = new SSLParameters(); + sslParameters.setCertificateInflaters(Map.of("zlib", certInflater)); + HttpClient httpClient = HttpClient.newBuilder() + .sslContext(SSLClientContext.createClientSSLContext()) + .version(HttpClient.Version.HTTP_2) + .sslParameters(sslParameters) + .build(); + + HttpRequest httpRequest = HttpRequest.newBuilder( + new URI("https://www.google.com/")) + .GET() + .build(); + HttpResponse response = httpClient.send(httpRequest, ofString()); + assertEquals(response.statusCode(), 200); + } +} + diff --git a/test/jdk/javax/net/ssl/SSLParameters/CompressedCert.java b/test/jdk/javax/net/ssl/SSLParameters/CompressedCert.java new file mode 100644 index 0000000000000..fee0abbc6a1b6 --- /dev/null +++ b/test/jdk/javax/net/ssl/SSLParameters/CompressedCert.java @@ -0,0 +1,270 @@ +/* + * Copyright (C) 2022 THL A29 Limited, a Tencent company. 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. + * + * 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. + */ + +// SunJSSE does not support dynamic system properties, no way to re-use +// system properties in samevm/agentvm mode. + +/* + * @test + * @bug 8273042 + * @summary TLS Certificate Compression + * @library /javax/net/ssl/templates + * @run main/othervm CompressedCert + */ + +import javax.net.ssl.SSLParameters; +import javax.net.ssl.SSLServerSocket; +import javax.net.ssl.SSLSocket; +import java.security.Security; +import java.util.Map; +import java.util.function.Function; +import java.util.zip.Deflater; +import java.util.zip.Inflater; + +public class CompressedCert extends SSLSocketTemplate { + private final Map> certDeflaters; + private final Map> certInflaters; + private final boolean requireClientCert; + private final boolean exceptionExpected; + + private static final Function certDeflater = (input) -> { + Deflater deflater = new Deflater(); + deflater.setInput(input); + deflater.finish(); + byte[] output = new byte[input.length]; + int l = deflater.deflate(output); + deflater.end(); + + byte[] data = new byte[l]; + System.arraycopy(output, 0, data, 0, l); + + return data; + }; + + private static final Function certInflater = (input) -> { + try { + Inflater inflater = new Inflater(); + inflater.setInput(input); + byte[] output = new byte[1024 * 8]; + int l = inflater.inflate(output); + inflater.end(); + + byte[] data = new byte[l]; + System.arraycopy(output, 0, data, 0, l); + + return data; + } catch (Exception ex) { + // just ignore + return null; + } + }; + + private static final Function invalidDeflater = (input) -> { + return input; + }; + + private static final Function invalidInflater = (input) -> { + return input; + }; + + public CompressedCert( + Map> certDeflaters, + Map> certInflaters, + boolean requireClientCert, + boolean exceptionExpected) { + this.certDeflaters = certDeflaters; + this.certInflaters = certInflaters; + this.requireClientCert = requireClientCert; + this.exceptionExpected = exceptionExpected; + } + + @Override + protected void configureServerSocket(SSLServerSocket sslServerSocket) { + SSLParameters sslParameters = sslServerSocket.getSSLParameters(); + sslParameters.setCertificateDeflaters(certDeflaters); + sslParameters.setCertificateInflaters(certInflaters); + sslParameters.setNeedClientAuth(requireClientCert); + sslServerSocket.setSSLParameters(sslParameters); + } + + @Override + protected void configureClientSocket(SSLSocket socket) { + SSLParameters sslParameters = socket.getSSLParameters(); + sslParameters.setCertificateDeflaters(certDeflaters); + sslParameters.setCertificateInflaters(certInflaters); + socket.setSSLParameters(sslParameters); + } + + @Override + protected void runServerApplication(SSLSocket socket) { + try { + super.runServerApplication(socket); + } catch (Exception ex) { + // Just ignore, let the client handle the failure information. + } + } + + @Override + protected void runClientApplication(SSLSocket sslSocket) throws Exception { + try { + super.runClientApplication(sslSocket); + if (exceptionExpected) { + throw new RuntimeException("Unexpected success!"); + } + } catch (Exception ex) { + if (!exceptionExpected) { + throw ex; + } + } + } + + public static void main(String[] args) throws Exception { + Security.setProperty("jdk.tls.disabledAlgorithms", ""); + + runTest(Map.of("zlib", certDeflater), + Map.of("zlib", certInflater), + false, + false); + runTest(Map.of("zlib", certDeflater), + Map.of("zlib", certInflater), + true, + false); + + runTest(Map.of("zlib", certDeflater), + Map.of(), + false, + false); + runTest(Map.of("zlib", certDeflater), + Map.of(), + true, + false); + + runTest(Map.of("zlib", certDeflater), + null, + false, + false); + runTest(Map.of("zlib", certDeflater), + null, + true, + false); + + runTest(Map.of(), + Map.of("zlib", certInflater), + false, + false); + runTest(Map.of(), + Map.of("zlib", certInflater), + true, + false); + + runTest(Map.of(), + Map.of(), + false, + false); + runTest(Map.of(), + Map.of(), + true, + false); + + runTest(Map.of(), + null, + false, + false); + runTest(Map.of(), + null, + true, + false); + + runTest(null, + Map.of("zlib", certInflater), + false, + false); + runTest(null, + Map.of("zlib", certInflater), + true, + false); + + runTest(null, + Map.of(), + false, + false); + runTest(null, + Map.of(), + true, + false); + + runTest(null, + null, + false, + false); + runTest(null, + null, + true, + false); + + runTest(Map.of("zlib", certDeflater), + Map.of("brotli", certInflater), + false, + false); + runTest(Map.of("zlib", certDeflater), + Map.of("brotli", certInflater), + true, + false); + + runTest(Map.of("brotli", certDeflater), + Map.of("zlib", certInflater), + false, + false); + runTest(Map.of("brotli", certDeflater), + Map.of("zlib", certInflater), + true, + false); + + runTest(Map.of("zlib", certDeflater), + Map.of("zlib", invalidInflater), + false, + true); + runTest(Map.of("zlib", certDeflater), + Map.of("zlib", invalidInflater), + true, + true); + + runTest(Map.of("zlib", invalidDeflater), + Map.of("zlib", certInflater), + false, + true); + runTest(Map.of("zlib", invalidDeflater), + Map.of("zlib", certInflater), + true, + true); + } + + private static void runTest( + Map> certDeflaters, + Map> certInflaters, + boolean requireClientCert, + boolean exceptionExpected) throws Exception { + new CompressedCert(certDeflaters, certInflaters, + requireClientCert, exceptionExpected).run(); + } +} diff --git a/test/jdk/javax/net/ssl/SSLParameters/UnsetCompressedCert.java b/test/jdk/javax/net/ssl/SSLParameters/UnsetCompressedCert.java new file mode 100644 index 0000000000000..c6c67d04abb8b --- /dev/null +++ b/test/jdk/javax/net/ssl/SSLParameters/UnsetCompressedCert.java @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2022 THL A29 Limited, a Tencent company. 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. + * + * 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. + */ + +// SunJSSE does not support dynamic system properties, no way to re-use +// system properties in samevm/agentvm mode. + +/* + * @test + * @bug 8273042 + * @summary TLS Certificate Compression + * @library /javax/net/ssl/templates + * @run main/othervm UnsetCompressedCert + */ + +import javax.net.ssl.SSLParameters; +import javax.net.ssl.SSLServerSocket; +import javax.net.ssl.SSLSocket; +import java.security.Security; +import java.util.Map; +import java.util.function.Function; +import java.util.zip.Deflater; +import java.util.zip.Inflater; + +public class UnsetCompressedCert extends SSLSocketTemplate { + private final Map> certDeflaters; + private final Map> certInflaters; + private final boolean requireClientCert; + + private static final Function certDeflater = (input) -> { + Deflater deflater = new Deflater(); + deflater.setInput(input); + deflater.finish(); + byte[] output = new byte[input.length]; + int l = deflater.deflate(output); + deflater.end(); + + byte[] data = new byte[l]; + System.arraycopy(output, 0, data, 0, l); + + return data; + }; + + private static final Function certInflater = (input) -> { + try { + Inflater inflater = new Inflater(); + inflater.setInput(input); + byte[] output = new byte[1024 * 8]; + int l = inflater.inflate(output); + inflater.end(); + + byte[] data = new byte[l]; + System.arraycopy(output, 0, data, 0, l); + + return data; + } catch (Exception ex) { + // just ignore + return null; + } + }; + + public UnsetCompressedCert( + Map> certDeflaters, + Map> certInflaters, + boolean requireClientCert) { + this.certDeflaters = certDeflaters; + this.certInflaters = certInflaters; + this.requireClientCert = requireClientCert; + } + + @Override + protected void configureServerSocket(SSLServerSocket sslServerSocket) { + SSLParameters sslParameters = sslServerSocket.getSSLParameters(); + if (certDeflaters != null) { + sslParameters.setCertificateDeflaters(certDeflaters); + } + if (certInflaters != null) { + sslParameters.setCertificateInflaters(certInflaters); + } + sslParameters.setNeedClientAuth(requireClientCert); + sslServerSocket.setSSLParameters(sslParameters); + } + + @Override + protected void configureClientSocket(SSLSocket socket) { + SSLParameters sslParameters = socket.getSSLParameters(); + if (certDeflaters != null) { + sslParameters.setCertificateDeflaters(certDeflaters); + } + + if (certInflaters != null) { + sslParameters.setCertificateInflaters(certInflaters); + } + socket.setSSLParameters(sslParameters); + } + + public static void main(String[] args) throws Exception { + Security.setProperty("jdk.tls.disabledAlgorithms", ""); + + runTest(Map.of("zlib", certDeflater), + null, + false); + runTest(Map.of("zlib", certDeflater), + null, + true); + + runTest(Map.of(), + null, + false); + runTest(Map.of(), + null, + true); + + runTest(null, + Map.of("zlib", certInflater), + false); + runTest(null, + Map.of("zlib", certInflater), + true); + + runTest(null, + Map.of(), + false); + runTest(null, + Map.of(), + true); + + runTest(null, + null, + false); + runTest(null, + null, + true); + } + + private static void runTest( + Map> certDeflaters, + Map> certInflaters, + boolean requireClientCert) throws Exception { + new UnsetCompressedCert( + certDeflaters, certInflaters, requireClientCert).run(); + } +} diff --git a/test/jdk/javax/net/ssl/templates/SSLClientContext.java b/test/jdk/javax/net/ssl/templates/SSLClientContext.java new file mode 100644 index 0000000000000..46fcbaedcfd17 --- /dev/null +++ b/test/jdk/javax/net/ssl/templates/SSLClientContext.java @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2022 THL A29 Limited, a Tencent company. 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. + * + * 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. + */ + +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManagerFactory; +import java.io.ByteArrayInputStream; +import java.security.KeyStore; +import java.security.cert.CertificateFactory; + +public enum SSLClientContext { + GTS_ROOT_R1(""" + -----BEGIN CERTIFICATE----- + MIIFWjCCA0KgAwIBAgIQbkepxUtHDA3sM9CJuRz04TANBgkqhkiG9w0BAQwFADBH + MQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExM + QzEUMBIGA1UEAxMLR1RTIFJvb3QgUjEwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIy + MDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNl + cnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjEwggIiMA0GCSqGSIb3DQEB + AQUAA4ICDwAwggIKAoICAQC2EQKLHuOhd5s73L+UPreVp0A8of2C+X0yBoJx9vaM + f/vo27xqLpeXo4xL+Sv2sfnOhB2x+cWX3u+58qPpvBKJXqeqUqv4IyfLpLGcY9vX + mX7wCl7raKb0xlpHDU0QM+NOsROjyBhsS+z8CZDfnWQpJSMHobTSPS5g4M/SCYe7 + zUjwTcLCeoiKu7rPWRnWr4+wB7CeMfGCwcDfLqZtbBkOtdh+JhpFAz2weaSUKK0P + fyblqAj+lug8aJRT7oM6iCsVlgmy4HqMLnXWnOunVmSPlk9orj2XwoSPwLxAwAtc + vfaHszVsrBhQf4TgTM2S0yDpM7xSma8ytSmzJSq0SPly4cpk9+aCEI3oncKKiPo4 + Zor8Y/kB+Xj9e1x3+naH+uzfsQ55lVe0vSbv1gHR6xYKu44LtcXFilWr06zqkUsp + zBmkMiVOKvFlRNACzqrOSbTqn3yDsEB750Orp2yjj32JgfpMpf/VjsPOS+C12LOO + Rc92wO1AK/1TD7Cn1TsNsYqiA94xrcx36m97PtbfkSIS5r762DL8EGMUUXLeXdYW + k70paDPvOmbsB4om3xPXV2V4J95eSRQAogB/mqghtqmxlbCluQ0WEdrHbEg8QOB+ + DVrNVjzRlwW5y0vtOUucxD/SVRNuJLDWcfr0wbrM7Rv1/oFB2ACYPTrIrnqYNxgF + lQIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV + HQ4EFgQU5K8rJnEaK0gnhS9SZizv8IkTcT4wDQYJKoZIhvcNAQEMBQADggIBADiW + Cu49tJYeX++dnAsznyvgyv3SjgofQXSlfKqE1OXyHuY3UjKcC9FhHb8owbZEKTV1 + d5iyfNm9dKyKaOOpMQkpAWBz40d8U6iQSifvS9efk+eCNs6aaAyC58/UEBZvXw6Z + XPYfcX3v73svfuo21pdwCxXu11xWajOl40k4DLh9+42FpLFZXvRq4d2h9mREruZR + gyFmxhE+885H7pwoHyXa/6xmld01D1zvICxi/ZG6qcz8WpyTgYMpl0p8WnK0OdC3 + d8t5/Wk6kjftbjhlRn7pYL15iJdfOBL07q9bgsiG1eGZbYwE8na6SfZu6W0eX6Dv + J4J2QPim01hcDyxC2kLGe4g0x8HYRZvBPsVhHdljUEn2NIVq4BjFbkerQUIpm/Zg + DdIx02OYI5NaAIFItO/Nis3Jz5nu2Z6qNuFoS3FJFDYoOj0dzpqPJeaAcWErtXvM + +SUWgeExX6GjfhaknBZqlxi9dnKlC54dNuYvoS++cJEPqOba+MSSQGwlfnuzCdyy + F62ARPBopY+Udf90WuioAnwMCeKpSwughQtiue+hMZL77/ZRBIls6Kl0obsXs7X9 + SQ98POyDGCBDTtWTurQ0sR8WNh8M5mQ5Fkzc4P4dyKliPUDqysU0ArSuiYgzNdws + E3PYJ/HQcu51OyLemGhmW/HGY0dVHLqlCFF1pkgl + -----END CERTIFICATE-----"""), + + GTS_ROOT_R2(""" + -----BEGIN CERTIFICATE----- + MIIFWjCCA0KgAwIBAgIQbkepxlqz5yDFMJo/aFLybzANBgkqhkiG9w0BAQwFADBH + MQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExM + QzEUMBIGA1UEAxMLR1RTIFJvb3QgUjIwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIy + MDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNl + cnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjIwggIiMA0GCSqGSIb3DQEB + AQUAA4ICDwAwggIKAoICAQDO3v2m++zsFDQ8BwZabFn3GTXd98GdVarTzTukk3Lv + CvptnfbwhYBboUhSnznFt+4orO/LdmgUud+tAWyZH8QiHZ/+cnfgLFuv5AS/T3Kg + GjSY6Dlo7JUle3ah5mm5hRm9iYz+re026nO8/4Piy33B0s5Ks40FnotJk9/BW9Bu + XvAuMC6C/Pq8tBcKSOWIm8Wba96wyrQD8Nr0kLhlZPdcTK3ofmZemde4wj7I0BOd + re7kRXuJVfeKH2JShBKzwkCX44ofR5GmdFrS+LFjKBC4swm4VndAoiaYecb+3yXu + PuWgf9RhD1FLPD+M2uFwdNjCaKH5wQzpoeJ/u1U8dgbuak7MkogwTZq9TwtImoS1 + mKPV+3PBV2HdKFZ1E66HjucMUQkQdYhMvI35ezzUIkgfKtzra7tEscszcTJGr61K + 8YzodDqs5xoic4DSMPclQsciOzsSrZYuxsN2B6ogtzVJV+mSSeh2FnIxZyuWfoqj + x5RWIr9qS34BIbIjMt/kmkRtWVtd9QCgHJvGeJeNkP+byKq0rxFROV7Z+2et1VsR + nTKaG73VululycslaVNVJ1zgyjbLiGH7HrfQy+4W+9OmTN6SpdTi3/UGVN4unUu0 + kzCqgc7dGtxRcw1PcOnlthYhGXmy5okLdWTK1au8CcEYof/UVKGFPP0UJAOyh9Ok + twIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV + HQ4EFgQUu//KjiOfT5nK2+JopqUVJxce2Q4wDQYJKoZIhvcNAQEMBQADggIBALZp + 8KZ3/p7uC4Gt4cCpx/k1HUCCq+YEtN/L9x0Pg/B+E02NjO7jMyLDOfxA325BS0JT + vhaI8dI4XsRomRyYUpOM52jtG2pzegVATX9lO9ZY8c6DR2Dj/5epnGB3GFW1fgiT + z9D2PGcDFWEJ+YF59exTpJ/JjwGLc8R3dtyDovUMSRqodt6Sm2T4syzFJ9MHwAiA + pJiS4wGWAqoC7o87xdFtCjMwc3i5T1QWvwsHoaRc5svJXISPD+AVdyx+Jn7axEvb + pxZ3B7DNdehyQtaVhJ2Gg/LkkM0JR9SLA3DaWsYDQvTtN6LwG1BUSw7YhN4ZKJmB + R64JGz9I0cNv4rBgF/XuIwKl2gBbbZCr7qLpGzvpx0QnRY5rn/WkhLx3+WuXrD5R + RaIRpsyF7gpo8j5QOHokYh4XIDdtak23CZvJ/KRY9bb7nE4Yu5UC56GtmwfuNmsk + 0jmGwZODUNKBRqhfYlcsu2xkiAhu7xNUX90txGdj08+JN7+dIPT7eoOboB6BAFDC + 5AwiWVIQ7UNWhwD4FFKnHYuTjKJNRn8nxnGbJN7k2oaLDX5rIMHAnuFl2GqjpuiF + izoHCBy69Y9Vmhh1fuXsgWbRIXOhNUQLgD1bnF5vKheW0YMjiGZt5obicDIvUiLn + yOd/xCxgXS/Dr55FBcOEArf9LAhST4Ldo/DUhgkC + -----END CERTIFICATE-----"""), + + GTS_ROOT_R3(""" + -----BEGIN CERTIFICATE----- + MIICDDCCAZGgAwIBAgIQbkepx2ypcyRAiQ8DVd2NHTAKBggqhkjOPQQDAzBHMQsw + CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU + MBIGA1UEAxMLR1RTIFJvb3QgUjMwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAw + MDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZp + Y2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjMwdjAQBgcqhkjOPQIBBgUrgQQA + IgNiAAQfTzOHMymKoYTey8chWEGJ6ladK0uFxh1MJ7x/JlFyb+Kf1qPKzEUURout + 736GjOyxfi//qXGdGIRFBEFVbivqJn+7kAHjSxm65FSWRQmx1WyRRK2EE46ajA2A + DDL24CejQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud + DgQWBBTB8Sa6oC2uhYHP0/EqEr24Cmf9vDAKBggqhkjOPQQDAwNpADBmAjEAgFuk + fCPAlaUs3L6JbyO5o91lAFJekazInXJ0glMLfalAvWhgxeG4VDvBNhcl2MG9AjEA + njWSdIUlUfUk7GRSJFClH9voy8l27OyCbvWFGFPouOOaKaqW04MjyaR7YbPMAuhd + -----END CERTIFICATE-----"""), + + GTS_ROOT_R4(""" + -----BEGIN CERTIFICATE----- + MIICCjCCAZGgAwIBAgIQbkepyIuUtui7OyrYorLBmTAKBggqhkjOPQQDAzBHMQsw + CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU + MBIGA1UEAxMLR1RTIFJvb3QgUjQwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAw + MDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZp + Y2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjQwdjAQBgcqhkjOPQIBBgUrgQQA + IgNiAATzdHOnaItgrkO4NcWBMHtLSZ37wWHO5t5GvWvVYRg1rkDdc/eJkTBa6zzu + hXyiQHY7qca4R9gq55KRanPpsXI5nymfopjTX15YhmUPoYRlBtHci8nHc8iMai/l + xKvRHYqjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud + DgQWBBSATNbrdP9JNqPV2Py1PsVq8JQdjDAKBggqhkjOPQQDAwNnADBkAjBqUFJ0 + CMRw3J5QdCHojXohw0+WbhXRIjVhLfoIN+4Zba3bssx9BzT1YBkstTTZbyACMANx + sbqjYAuG7ZoIapVon+Kz4ZNkfF6Tpt95LY2F45TPI11xzPKwTdb+mciUqXWi4w== + -----END CERTIFICATE-----"""); + + final String certStr; + + SSLClientContext(String certStr) { + this.certStr = certStr; + } + + public static SSLContext createClientSSLContext() throws Exception { + // Generate certificate from cert string. + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + + // Import the trusted certs. + KeyStore ts = KeyStore.getInstance("PKCS12"); + ts.load(null, null); + for (SSLClientContext clientContext : SSLClientContext.values()) { + try (ByteArrayInputStream is = new ByteArrayInputStream( + clientContext.certStr.getBytes())) { + ts.setCertificateEntry("trusted-cert-" + + clientContext.name(), cf.generateCertificate(is)); + } + } + + // Create an SSLContext object. + TrustManagerFactory tmf = TrustManagerFactory.getInstance("PKIX"); + tmf.init(ts); + + SSLContext context = SSLContext.getInstance("TLS"); + context.init(null, tmf.getTrustManagers(), null); + + return context; + } +}