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..7272d9793a557 100644
--- a/src/java.base/share/classes/javax/net/ssl/SSLParameters.java
+++ b/src/java.base/share/classes/javax/net/ssl/SSLParameters.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2005, 2026, 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
@@ -86,6 +86,7 @@ public class SSLParameters {
private String[] applicationProtocols = new String[0];
private String[] signatureSchemes = null;
private String[] namedGroups = null;
+ private boolean enableCertificateCompression = true;
/**
* Constructs SSLParameters.
@@ -960,4 +961,38 @@ public void setNamedGroups(String[] namedGroups) {
this.namedGroups = tempGroups;
}
+
+ /**
+ * Sets whether TLS certificate compression should be enabled.
+ *
+ * This method only applies to TLSv1.3.
+ *
+ * @param enableCertificateCompression
+ * {@code true} indicates that TLS certificate compression
+ * should be enabled; {@code false} indicates that TLS certificate
+ * compression should be disabled
+ *
+ * @see #getEnableCertificateCompression()
+ *
+ * @since 27
+ */
+ public void setEnableCertificateCompression(
+ boolean enableCertificateCompression) {
+ this.enableCertificateCompression = enableCertificateCompression;
+ }
+
+ /**
+ * Returns whether TLS certificate compression should be enabled
+ *
+ * This method only applies to TLSv1.3.
+ *
+ * @return true, if TLS certificate compression should be enabled
+ *
+ * @see #setEnableCertificateCompression(boolean)
+ *
+ * @since 27
+ */
+ public boolean getEnableCertificateCompression() {
+ return this.enableCertificateCompression;
+ }
}
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 d172206326f1f..52541a5f18ff2 100644
--- a/src/java.base/share/classes/sun/security/ssl/Alert.java
+++ b/src/java.base/share/classes/sun/security/ssl/Alert.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003, 2025, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2003, 2026, 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
@@ -281,6 +281,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 2a2db34cab93b..bc6423945fadc 100644
--- a/src/java.base/share/classes/sun/security/ssl/CertificateMessage.java
+++ b/src/java.base/share/classes/sun/security/ssl/CertificateMessage.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 2026, 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
@@ -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);
// Ensure that the Certificate message has not been sent w/o
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 a297d9d21b265..44ae7e117403f 100644
--- a/src/java.base/share/classes/sun/security/ssl/CertificateRequest.java
+++ b/src/java.base/share/classes/sun/security/ssl/CertificateRequest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 2026, 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
@@ -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..6f69081995f13
--- /dev/null
+++ b/src/java.base/share/classes/sun/security/ssl/CompressCertExtension.java
@@ -0,0 +1,317 @@
+/*
+ * Copyright (C) 2022 THL A29 Limited, a Tencent company. All rights reserved.
+ * Copyright (c) 2026, 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 java.util.Map;
+import java.util.function.Function;
+import javax.net.ssl.SSLProtocolException;
+import sun.security.ssl.SSLExtension.ExtensionConsumer;
+import sun.security.ssl.SSLExtension.SSLExtensionSpec;
+import sun.security.ssl.SSLHandshake.HandshakeMessage;
+
+/**
+ * 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.
+ return produceCompCertExt(context,
+ SSLExtension.CH_COMPRESS_CERTIFICATE);
+ }
+ }
+
+ /**
+ * 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.
+ consumeCompCertExt(context, buffer,
+ SSLExtension.CH_COMPRESS_CERTIFICATE);
+ }
+ }
+
+ /**
+ * 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.
+ return produceCompCertExt(context,
+ SSLExtension.CR_COMPRESS_CERTIFICATE);
+ }
+ }
+
+ /**
+ * 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.
+ consumeCompCertExt(context, buffer,
+ SSLExtension.CR_COMPRESS_CERTIFICATE);
+ }
+ }
+
+ private static byte[] produceCompCertExt(
+ ConnectionContext context, SSLExtension extension)
+ throws IOException {
+
+ HandshakeContext hc = (HandshakeContext) context;
+ // Is it a supported and enabled extension?
+ if (!hc.sslConfig.isAvailable(extension)) {
+ if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) {
+ SSLLogger.fine("Ignore unavailable " +
+ "compress_certificate extension");
+ }
+ return null;
+ }
+
+ // Produce the extension.
+ if (hc.certInflaters == null) {
+ hc.certInflaters =
+ CompressionAlgorithm.findInflaters(hc.sslConfig);
+ }
+
+ if (hc.certInflaters.isEmpty()) {
+ if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) {
+ SSLLogger.fine("Ignore unsupported " +
+ "compress_certificate extension");
+ }
+ return null;
+ }
+
+ int vectorLen = CompressionAlgorithm.sizeInRecord() *
+ hc.certInflaters.size();
+ byte[] extData = new byte[vectorLen + 1];
+ ByteBuffer m = ByteBuffer.wrap(extData);
+ Record.putInt8(m, vectorLen);
+ for (Integer algId : hc.certInflaters.keySet()) {
+ Record.putInt16(m, algId);
+ }
+
+ // Update the context.
+ hc.handshakeExtensions.put(
+ extension, new CertCompressionSpec(hc.certInflaters));
+
+ return extData;
+ }
+
+ private static void consumeCompCertExt(ConnectionContext context,
+ ByteBuffer buffer, SSLExtension extension) throws IOException {
+
+ HandshakeContext hc = (HandshakeContext) context;
+ // Is it a supported and enabled extension?
+ if (!hc.sslConfig.isAvailable(extension)) {
+ if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) {
+ SSLLogger.fine("Ignore unavailable " +
+ "compress_certificate extension");
+ }
+ return; // ignore the extension
+ }
+
+ if (hc.sslConfig.certDeflaters == null ||
+ hc.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(hc, buffer);
+
+ // Update the context.
+ hc.certDeflater = CompressionAlgorithm.selectDeflater(
+ hc.sslConfig, spec.compressionAlgorithms);
+ if (hc.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..a2e57ce5b009f
--- /dev/null
+++ b/src/java.base/share/classes/sun/security/ssl/CompressedCertificate.java
@@ -0,0 +1,228 @@
+/*
+ * Copyright (C) 2022 THL A29 Limited, a Tencent company. All rights reserved.
+ * Copyright (c) 2026, 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 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..d44a107dfd2f1
--- /dev/null
+++ b/src/java.base/share/classes/sun/security/ssl/CompressionAlgorithm.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2022 THL A29 Limited, a Tencent company. All rights reserved.
+ * Copyright (c) 2026, 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.ByteArrayOutputStream;
+import java.util.AbstractMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.zip.Deflater;
+import java.util.zip.Inflater;
+
+/**
+ * Enum for 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 compression algorithms 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")) {
+ 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")) {
+ 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;
+ }
+
+ // Default Deflaters and Inflaters.
+ // We currently support only ZLIB internally.
+
+ static Map> getDefaultDeflaters() {
+ return Map.of(ZLIB.name, (input) -> {
+ try (Deflater deflater = new Deflater();
+ ByteArrayOutputStream outputStream =
+ new ByteArrayOutputStream(input.length)) {
+
+ deflater.setInput(input);
+ deflater.finish();
+ byte[] buffer = new byte[1024];
+
+ while (!deflater.finished()) {
+ int compressedSize = deflater.deflate(buffer);
+ outputStream.write(buffer, 0, compressedSize);
+ }
+
+ return outputStream.toByteArray();
+ } catch (Exception e) {
+ if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) {
+ SSLLogger.warning(
+ "Exception during certificate compression: ", e);
+ }
+ return null;
+ }
+ });
+ }
+
+ static Map> getDefaultInflaters() {
+ return Map.of(ZLIB.name, (input) -> {
+ try (Inflater inflater = new Inflater();
+ ByteArrayOutputStream outputStream =
+ new ByteArrayOutputStream(input.length)) {
+
+ inflater.setInput(input);
+ byte[] buffer = new byte[1024];
+
+ while (!inflater.finished()) {
+ int decompressedSize = inflater.inflate(buffer);
+ outputStream.write(buffer, 0, decompressedSize);
+ }
+
+ return outputStream.toByteArray();
+ } catch (Exception e) {
+ if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) {
+ SSLLogger.warning(
+ "Exception during certificate decompression: ", e);
+ }
+ 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 a5f340d520343..0503e12c92be5 100644
--- a/src/java.base/share/classes/sun/security/ssl/HandshakeContext.java
+++ b/src/java.base/share/classes/sun/security/ssl/HandshakeContext.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2018, 2026, 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
@@ -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 ace60e41af9b0..bb62801d16860 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, 2025, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2018, 2026, 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;
@@ -64,6 +65,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;
@@ -248,6 +254,16 @@ final class SSLConfiguration implements Cloneable {
CustomizedServerSignatureSchemes.signatureSchemes != null ?
CustomizedServerSignatureSchemes.signatureSchemes :
SupportedSigSchemes.DEFAULT;
+
+ if (Utilities.getBooleanProperty(
+ "jdk.tls.enableCertificateCompression", true)) {
+ this.certDeflaters = CompressionAlgorithm.getDefaultDeflaters();
+ this.certInflaters = CompressionAlgorithm.getDefaultInflaters();
+ } else {
+ this.certDeflaters = Map.of();
+ this.certInflaters = Map.of();
+ }
+
this.namedGroups = NamedGroup.SupportedGroups.namedGroups;
this.maximumProtocolVersion = ProtocolVersion.NONE;
for (ProtocolVersion pv : enabledProtocols) {
@@ -305,6 +321,8 @@ SSLParameters getSSLParameters() {
params.setMaximumPacketSize(this.maximumPacketSize);
params.setSignatureSchemes(this.signatureSchemes);
params.setNamedGroups(this.namedGroups);
+ params.setEnableCertificateCompression(!this.certInflaters.isEmpty()
+ && !this.certDeflaters.isEmpty());
return params;
}
@@ -380,6 +398,14 @@ void setSSLParameters(SSLParameters params) {
this.namedGroups = NamedGroup.SupportedGroups.namedGroups;
}
+ if (params.getEnableCertificateCompression()) {
+ this.certDeflaters = CompressionAlgorithm.getDefaultDeflaters();
+ this.certInflaters = CompressionAlgorithm.getDefaultInflaters();
+ } else {
+ this.certDeflaters = Map.of();
+ this.certInflaters = Map.of();
+ }
+
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 47a0d0b0e4407..2c43605f75416 100644
--- a/src/java.base/share/classes/sun/security/ssl/SSLExtension.java
+++ b/src/java.base/share/classes/sun/security/ssl/SSLExtension.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2018, 2026, 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
@@ -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..2c6b58bafa51a 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, 2026, 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 76c266a628a68..903f43fa7eb98 100644
--- a/src/java.base/share/classes/sun/security/ssl/ServerHello.java
+++ b/src/java.base/share/classes/sun/security/ssl/ServerHello.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 2026, 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
@@ -1411,6 +1411,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 20b0338215c77..96894ed5ee37b 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, 2025, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 2026, 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
@@ -719,6 +719,8 @@ public static SSLParameters copySSLParameters(SSLParameters p) {
p1.setUseCipherSuitesOrder(p.getUseCipherSuitesOrder());
p1.setSignatureSchemes(p.getSignatureSchemes());
p1.setNamedGroups(p.getNamedGroups());
+ p1.setWantClientAuth(p.getWantClientAuth());
+ p1.setEnableCertificateCompression(p.getEnableCertificateCompression());
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..064bf2ebb6b42
--- /dev/null
+++ b/test/jdk/javax/net/ssl/HttpsURLConnection/HttpsCompressedCert.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2022 THL A29 Limited, a Tencent company. All rights reserved.
+ * Copyright (c) 2026, 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.
+ *
+ * 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 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 javax.net.ssl.SSLParameters;
+
+/*
+ * @test
+ * @bug 8372526
+ * @summary TLS certificate compression
+ * @library /test/lib
+ * /javax/net/ssl/templates
+ * @run main/othervm HttpsCompressedCert true
+ * @run main/othervm HttpsCompressedCert false
+ */
+
+public class HttpsCompressedCert {
+ public static void main(String[] args) throws Exception {
+ SSLParameters sslParameters = new SSLParameters();
+ sslParameters.setEnableCertificateCompression(
+ Boolean.parseBoolean(args[0]));
+ 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..3de208a3009a5
--- /dev/null
+++ b/test/jdk/javax/net/ssl/SSLParameters/CompressedCert.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2022 THL A29 Limited, a Tencent company. All rights reserved.
+ * Copyright (c) 2026, 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.
+ *
+ * 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.SSLParameters;
+import javax.net.ssl.SSLServerSocket;
+import javax.net.ssl.SSLSocket;
+
+/*
+ * @test
+ * @bug 8372526
+ * @summary TLS Certificate Compression
+ * @library /javax/net/ssl/templates
+ * @run main/othervm CompressedCert
+ */
+
+public class CompressedCert extends SSLSocketTemplate {
+
+ private final boolean enableClientCertComp;
+ private final boolean enableServerCertComp;
+ private final boolean requireClientCert;
+
+ public CompressedCert(
+ boolean enableClientCertComp,
+ boolean enableServerCertComp,
+ boolean requireClientCert) {
+ this.enableClientCertComp = enableClientCertComp;
+ this.enableServerCertComp = enableServerCertComp;
+ this.requireClientCert = requireClientCert;
+ }
+
+ @Override
+ protected void configureServerSocket(SSLServerSocket sslServerSocket) {
+ SSLParameters sslParameters = sslServerSocket.getSSLParameters();
+ sslParameters.setEnableCertificateCompression(enableClientCertComp);
+ sslParameters.setNeedClientAuth(requireClientCert);
+ sslServerSocket.setSSLParameters(sslParameters);
+ }
+
+ @Override
+ protected void configureClientSocket(SSLSocket socket) {
+ SSLParameters sslParameters = socket.getSSLParameters();
+ sslParameters.setEnableCertificateCompression(enableServerCertComp);
+ socket.setSSLParameters(sslParameters);
+ }
+
+ public static void main(String[] args) throws Exception {
+ runTest(false, false, false);
+ runTest(false, false, true);
+ runTest(false, true, false);
+ runTest(true, false, true);
+ runTest(true, true, false);
+ runTest(true, true, true);
+ }
+
+ private static void runTest(
+ boolean enableClientCertComp,
+ boolean enableServerCertComp,
+ boolean requireClientCert) throws Exception {
+ new CompressedCert(enableClientCertComp, enableServerCertComp,
+ 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;
+ }
+}
diff --git a/test/jdk/sun/security/ssl/CertificateCompression/DefaultCertCompression.java b/test/jdk/sun/security/ssl/CertificateCompression/DefaultCertCompression.java
new file mode 100644
index 0000000000000..8c9b25013fc8c
--- /dev/null
+++ b/test/jdk/sun/security/ssl/CertificateCompression/DefaultCertCompression.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (c) 2026, 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.
+ *
+ * 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 static jdk.test.lib.Asserts.assertEquals;
+import static jdk.test.lib.Asserts.assertTrue;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/*
+ * @test
+ * @bug 8372526
+ * @summary Add support for ZLIB TLS Certificate Compression.
+ * @library /javax/net/ssl/templates
+ * /test/lib
+ * @run main/othervm DefaultCertCompression
+ * @run main/othervm DefaultCertCompression -Djdk.tls.enableCertificateCompression=false
+ */
+
+public class DefaultCertCompression extends SSLEngineTemplate {
+
+ protected static final int CLI_HELLO_MSG = 1;
+ protected static final int COMP_CERT_EXT = 27;
+ // zlib(1), brotli(2), zstd(3)
+ protected static final List DEFAULT_COMP_ALGS = List.of(1);
+ private final boolean certCompEnabled;
+
+ protected DefaultCertCompression() throws Exception {
+ certCompEnabled = Boolean.parseBoolean(System.getProperty(
+ "jdk.tls.enableCertificateCompression", "true"));
+ super();
+ }
+
+ public static void main(String[] args) throws Exception {
+ new DefaultCertCompression().run();
+ }
+
+ protected void run() throws Exception {
+
+ // Produce client_hello
+ clientEngine.wrap(clientOut, cTOs);
+ cTOs.flip();
+
+ checkClientHello();
+ }
+
+ protected void checkClientHello() throws Exception {
+ if (certCompEnabled) {
+ assertTrue(DEFAULT_COMP_ALGS.equals(getCompAlgsCliHello(
+ extractHandshakeMsg(cTOs, CLI_HELLO_MSG, false))));
+ } else {
+ assertEquals(getCompAlgsCliHello(
+ extractHandshakeMsg(cTOs, CLI_HELLO_MSG, false)).size(), 0,
+ "compress_certificate extension present in ClientHello");
+ }
+ }
+
+ /**
+ * Parses the ClientHello message and extracts from it a list of
+ * compression algorithm values. It is assumed that the provided
+ * ByteBuffer has its position set at the first byte of the ClientHello
+ * message body (AFTER the handshake header) and contains the entire
+ * hello message. Upon successful completion of this method the ByteBuffer
+ * will have its position reset to the initial offset in the buffer.
+ * If an exception is thrown the position at the time of the exception
+ * will be preserved.
+ *
+ * @param data The ByteBuffer containing the ClientHello bytes.
+ * @return A List of the compression algorithm values.
+ */
+ protected List getCompAlgsCliHello(ByteBuffer data) {
+ Objects.requireNonNull(data);
+ data.mark();
+
+ // Skip over the protocol version and client random
+ data.position(data.position() + 34);
+
+ // Jump past the session ID (if there is one)
+ int sessLen = Byte.toUnsignedInt(data.get());
+ if (sessLen != 0) {
+ data.position(data.position() + sessLen);
+ }
+
+ // Jump past the cipher suites
+ int csLen = Short.toUnsignedInt(data.getShort());
+ if (csLen != 0) {
+ data.position(data.position() + csLen);
+ }
+
+ // ...and the compression
+ int compLen = Byte.toUnsignedInt(data.get());
+ if (compLen != 0) {
+ data.position(data.position() + compLen);
+ }
+
+ List extSigAlgs = getCompAlgsFromExt(data);
+
+ // We should be at the end of the ClientHello
+ data.reset();
+ return extSigAlgs;
+ }
+
+ /**
+ * Gets compression algorithms from the given TLS extension.
+ * The buffer should be positioned at the start of the extension.
+ */
+ protected List getCompAlgsFromExt(ByteBuffer data) {
+
+ List extCompAlgs = new ArrayList<>();
+ data.getShort(); // read length
+
+ while (data.hasRemaining()) {
+ int extType = Short.toUnsignedInt(data.getShort());
+ int extLen = Short.toUnsignedInt(data.getShort());
+
+ if (extType == COMP_CERT_EXT) {
+ int sigSchemeLen = data.get();
+
+ for (int ssOff = 0; ssOff < sigSchemeLen; ssOff += 2) {
+ Integer schemeName = Short.toUnsignedInt(data.getShort());
+ extCompAlgs.add(schemeName);
+ }
+ } else {
+ // Not the extension we're looking for. Skip past the
+ // extension data
+ data.position(data.position() + extLen);
+ }
+ }
+
+ return extCompAlgs;
+ }
+}