From f5a403c4db0a4f7d622bc211b5ded3b9e8c76c09 Mon Sep 17 00:00:00 2001 From: Bradford Wetmore Date: Wed, 30 Apr 2025 15:30:40 -0700 Subject: [PATCH 01/27] 8341346: Add support for exporting TLS Keying Material --- .../javax/net/ssl/ExtendedSSLSession.java | 93 +++- .../sun/security/ssl/DHClientKeyExchange.java | 6 +- .../security/ssl/ECDHClientKeyExchange.java | 10 +- .../classes/sun/security/ssl/Finished.java | 16 +- .../security/ssl/RSAClientKeyExchange.java | 8 +- .../sun/security/ssl/SSLSessionImpl.java | 335 ++++++++++++- .../ExtendedSSLSession/TLSKeyExporters.java | 466 ++++++++++++++++++ 7 files changed, 915 insertions(+), 19 deletions(-) create mode 100644 test/jdk/javax/net/ssl/ExtendedSSLSession/TLSKeyExporters.java diff --git a/src/java.base/share/classes/javax/net/ssl/ExtendedSSLSession.java b/src/java.base/share/classes/javax/net/ssl/ExtendedSSLSession.java index 2b98f4845cf66..ec9acad547ae6 100644 --- a/src/java.base/share/classes/javax/net/ssl/ExtendedSSLSession.java +++ b/src/java.base/share/classes/javax/net/ssl/ExtendedSSLSession.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 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 @@ -26,6 +26,7 @@ package javax.net.ssl; import java.util.List; +import javax.crypto.SecretKey; /** * Extends the {@code SSLSession} interface to support additional @@ -163,4 +164,94 @@ public List getRequestedServerNames() { public List getStatusResponses() { throw new UnsupportedOperationException(); } + + /** + * Generate Exported Key Material (EKM) calculated according to the + * algorithms defined in RFCs 5705/8446. + *

+ * Note RFC 5705 calculates different EKM values if {@code context} + * is null vs. non-null/empty. RFC 8446 does not make such a + * distinction. + *

+ * The {@code label} {@code String} will be converted to bytes using + * the {@link java.nio.charset.StandardCharsets#UTF_8} + * character encoding. + * + * @spec https://www.rfc-editor.org/info/rfc5705 + * RFC 5705: Keying Material Exporters for Transport Layer + * Security (TLS) + * @spec https://www.rfc-editor.org/info/rfc8446 + * RFC 8446: The Transport Layer Security (TLS) Protocol Version 1.3 + * + * @param label the label bytes used in the EKM calculation. + * {@code label} will be converted to a {@code byte[]} + * before the operation begins. + * @param context the context bytes used in the EKM calculation + * @param length the number of bytes of EKM material needed + * + * @throws SSLKeyException if the key could not be generated + * @throws IllegalArgumentException if {@code length} is negative + * or the {@code context} length is larger than can be + * accommodated + * @throws NullPointerException if {@code label} is null + * @throws UnsupportedOperationException if the underlying provider + * does not implement the operation + * + * @return a {@code SecretKey} that contains {@code length} bytes of the + * EKM material. + * + * @since 25 + */ + public SecretKey exportKeyMaterialKey( + String label, byte[] context, int length) throws SSLKeyException { + throw new UnsupportedOperationException(); + } + + /** + * Generate Exported Key Material (EKM) calculated according to the + * algorithms defined in RFCs 5705/8446. + *

+ * Note RFC 5705 calculates different EKM values if {@code context} + * is null vs. non-null/empty. RFC 8446 does not make such a + * distinction. + *

+ * The {@code label} {@code String} will be converted to bytes using + * the {@link java.nio.charset.StandardCharsets#UTF_8} + * character encoding. + *

+ * Depending on the chosen underlying key derivation mechanism, the + * raw bytes might not be extractable/exportable. In such cases, the + * {@link #exportKeyMaterialKey(String, byte[], int)} method should be + * used instead to access the generated key material. + * + * @spec https://www.rfc-editor.org/info/rfc5705 + * RFC 5705: Keying Material Exporters for Transport Layer + * Security (TLS) + * @spec https://www.rfc-editor.org/info/rfc8446 + * RFC 8446: The Transport Layer Security (TLS) Protocol Version 1.3 + * + * @param label the label bytes used in the EKM calculation + * {@code label} will be converted to a {@code byte[]} + * before the operation begins. + * @param context the context bytes used in the EKM calculation + * @param length the number of bytes of EKM material needed + * + * @throws SSLKeyException if the key could not be generated + * @throws IllegalArgumentException if {@code length} is negative + * or the {@code context} length is larger than can be + * accommodated + * @throws NullPointerException if {@code label} is null + * @throws UnsupportedOperationException if the underlying provider + * does not implement the operation + * + * @return a byte buffer of size {@code length} that contains the EKM + * material or {@code null} if the key material could not be + * extracted + * + * @since 25 + */ + public byte[] exportKeyMaterialData( + String label, byte[] context, int length) throws SSLKeyException { + throw new UnsupportedOperationException(); + } } diff --git a/src/java.base/share/classes/sun/security/ssl/DHClientKeyExchange.java b/src/java.base/share/classes/sun/security/ssl/DHClientKeyExchange.java index b8f8310284088..1076d6ed6bf1f 100644 --- a/src/java.base/share/classes/sun/security/ssl/DHClientKeyExchange.java +++ b/src/java.base/share/classes/sun/security/ssl/DHClientKeyExchange.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 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 @@ -209,6 +209,8 @@ public byte[] produce(ConnectionContext context, SecretKey masterSecret = masterKD.deriveKey("MasterSecret", null); chc.handshakeSession.setMasterSecret(masterSecret); + chc.handshakeSession.setRandoms( + chc.clientHelloRandom, chc.serverHelloRandom); SSLTrafficKeyDerivation kd = SSLTrafficKeyDerivation.valueOf(chc.negotiatedProtocol); @@ -305,6 +307,8 @@ public void consume(ConnectionContext context, SecretKey masterSecret = masterKD.deriveKey("MasterSecret", null); shc.handshakeSession.setMasterSecret(masterSecret); + shc.handshakeSession.setRandoms( + shc.clientHelloRandom, shc.serverHelloRandom); SSLTrafficKeyDerivation kd = SSLTrafficKeyDerivation.valueOf(shc.negotiatedProtocol); diff --git a/src/java.base/share/classes/sun/security/ssl/ECDHClientKeyExchange.java b/src/java.base/share/classes/sun/security/ssl/ECDHClientKeyExchange.java index 0dcf5ec27b789..a074d79312244 100644 --- a/src/java.base/share/classes/sun/security/ssl/ECDHClientKeyExchange.java +++ b/src/java.base/share/classes/sun/security/ssl/ECDHClientKeyExchange.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 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 @@ -221,6 +221,8 @@ public byte[] produce(ConnectionContext context, SecretKey masterSecret = masterKD.deriveKey("MasterSecret", null); chc.handshakeSession.setMasterSecret(masterSecret); + chc.handshakeSession.setRandoms( + chc.clientHelloRandom, chc.serverHelloRandom); SSLTrafficKeyDerivation kd = SSLTrafficKeyDerivation.valueOf(chc.negotiatedProtocol); @@ -341,6 +343,8 @@ public void consume(ConnectionContext context, SecretKey masterSecret = masterKD.deriveKey("MasterSecret", null); shc.handshakeSession.setMasterSecret(masterSecret); + shc.handshakeSession.setRandoms( + shc.clientHelloRandom, shc.serverHelloRandom); SSLTrafficKeyDerivation kd = SSLTrafficKeyDerivation.valueOf(shc.negotiatedProtocol); @@ -421,6 +425,8 @@ public byte[] produce(ConnectionContext context, SecretKey masterSecret = masterKD.deriveKey("MasterSecret", null); chc.handshakeSession.setMasterSecret(masterSecret); + chc.handshakeSession.setRandoms( + chc.clientHelloRandom, chc.serverHelloRandom); SSLTrafficKeyDerivation kd = SSLTrafficKeyDerivation.valueOf(chc.negotiatedProtocol); @@ -525,6 +531,8 @@ public void consume(ConnectionContext context, SecretKey masterSecret = masterKD.deriveKey("MasterSecret", null); shc.handshakeSession.setMasterSecret(masterSecret); + shc.handshakeSession.setRandoms( + shc.clientHelloRandom, shc.serverHelloRandom); SSLTrafficKeyDerivation kd = SSLTrafficKeyDerivation.valueOf(shc.negotiatedProtocol); diff --git a/src/java.base/share/classes/sun/security/ssl/Finished.java b/src/java.base/share/classes/sun/security/ssl/Finished.java index 9afa83a0afcc0..1496ea4d17b6e 100644 --- a/src/java.base/share/classes/sun/security/ssl/Finished.java +++ b/src/java.base/share/classes/sun/security/ssl/Finished.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 @@ -750,6 +750,12 @@ private byte[] onProduceFinished(ClientHandshakeContext chc, "Failure to derive application secrets", gse); } + // Calculate/save the exporter_master_secret. It uses + // the same handshakeHash as the client/server app traffic. + SecretKey exporterSecret = kd.deriveKey( + "TlsExporterMasterSecret", null); + chc.handshakeSession.setExporterMasterSecret(exporterSecret); + // The resumption master secret is stored in the session so // it can be used after the handshake is completed. SSLSecretDerivation sd = ((SSLSecretDerivation) kd).forContext(chc); @@ -1110,13 +1116,19 @@ private void onConsumeFinished(ServerHandshakeContext shc, shc.baseReadSecret = readSecret; shc.conContext.inputRecord.changeReadCiphers(readCipher); + // Calculate/save the exporter_master_secret. It uses + // the same handshakeHash as the client/server app traffic. + SecretKey exporterSecret = kd.deriveKey( + "TlsExporterMasterSecret", null); + shc.handshakeSession.setExporterMasterSecret(exporterSecret); + // The resumption master secret is stored in the session so // it can be used after the handshake is completed. shc.handshakeHash.update(); SSLSecretDerivation sd = ((SSLSecretDerivation)kd).forContext(shc); SecretKey resumptionMasterSecret = sd.deriveKey( - "TlsResumptionMasterSecret", null); + "TlsResumptionMasterSecret", null); shc.handshakeSession.setResumptionMasterSecret( resumptionMasterSecret); } catch (GeneralSecurityException gse) { diff --git a/src/java.base/share/classes/sun/security/ssl/RSAClientKeyExchange.java b/src/java.base/share/classes/sun/security/ssl/RSAClientKeyExchange.java index 5189822da3087..1e8179900b363 100644 --- a/src/java.base/share/classes/sun/security/ssl/RSAClientKeyExchange.java +++ b/src/java.base/share/classes/sun/security/ssl/RSAClientKeyExchange.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 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 @@ -213,6 +213,9 @@ public byte[] produce(ConnectionContext context, // update the states chc.handshakeSession.setMasterSecret(masterSecret); + chc.handshakeSession.setRandoms( + chc.clientHelloRandom, chc.serverHelloRandom); + SSLTrafficKeyDerivation kd = SSLTrafficKeyDerivation.valueOf(chc.negotiatedProtocol); if (kd == null) { // unlikely @@ -301,6 +304,9 @@ public void consume(ConnectionContext context, // update the states shc.handshakeSession.setMasterSecret(masterSecret); + shc.handshakeSession.setRandoms( + shc.clientHelloRandom, shc.serverHelloRandom); + SSLTrafficKeyDerivation kd = SSLTrafficKeyDerivation.valueOf(shc.negotiatedProtocol); if (kd == null) { // unlikely diff --git a/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java b/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java index 566415e23c7e4..9c969927cca1b 100644 --- a/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java +++ b/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java @@ -29,8 +29,8 @@ import java.io.IOException; import java.net.InetAddress; import java.nio.ByteBuffer; -import java.security.Principal; -import java.security.PrivateKey; +import java.nio.charset.StandardCharsets; +import java.security.*; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Arrays; @@ -38,19 +38,21 @@ import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.locks.ReentrantLock; +import javax.crypto.KeyGenerator; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; -import javax.net.ssl.ExtendedSSLSession; -import javax.net.ssl.SNIHostName; -import javax.net.ssl.SNIServerName; -import javax.net.ssl.SSLException; -import javax.net.ssl.SSLPeerUnverifiedException; -import javax.net.ssl.SSLSessionBindingEvent; -import javax.net.ssl.SSLSessionBindingListener; -import javax.net.ssl.SSLSessionContext; +import javax.net.ssl.*; +import javax.security.auth.DestroyFailedException; + +import sun.security.ssl.CipherSuite.HashAlg; +import sun.security.internal.spec.TlsPrfParameterSpec; +import static sun.security.ssl.CipherSuite.HashAlg.H_NONE; +import static sun.security.ssl.ProtocolVersion.*; + /** * Implements the SSL session interface, and exposes the session context @@ -99,6 +101,9 @@ final class SSLSessionImpl extends ExtendedSSLSession { private Collection peerSupportedSignAlgs; //for certificate private boolean useDefaultPeerSignAlgs = false; private List statusResponses; + private SecretKey exporterMasterSecret; // TLSv1.3+ exporter info + private RandomCookie clientRandom, // TLSv1.2- exporter info + serverRandom; private SecretKey resumptionMasterSecret; private SecretKey preSharedKey; private byte[] pskIdentity; @@ -237,13 +242,16 @@ final class SSLSessionImpl extends ExtendedSSLSession { this.requestedServerNames = baseSession.getRequestedServerNames(); this.masterSecret = baseSession.getMasterSecret(); this.useExtendedMasterSecret = baseSession.useExtendedMasterSecret; + this.exporterMasterSecret = baseSession.exporterMasterSecret; + this.resumptionMasterSecret = baseSession.resumptionMasterSecret; + this.clientRandom = baseSession.clientRandom; + this.serverRandom = baseSession.serverRandom; this.creationTime = baseSession.getCreationTime(); this.lastUsedTime = System.currentTimeMillis(); this.identificationProtocol = baseSession.getIdentificationProtocol(); this.localCerts = baseSession.localCerts; this.peerCerts = baseSession.peerCerts; this.statusResponses = baseSession.statusResponses; - this.resumptionMasterSecret = baseSession.resumptionMasterSecret; this.context = baseSession.context; this.negotiatedMaxFragLen = baseSession.negotiatedMaxFragLen; this.maximumPacketSize = baseSession.maximumPacketSize; @@ -265,8 +273,7 @@ final class SSLSessionImpl extends ExtendedSSLSession { * < length in bytes > preSharedKey * < 1 byte > pskIdentity length * < length in bytes > pskIdentity - * < 1 byte > masterSecret length - * < 1 byte > masterSecret algorithm length + * < 1 byte > masterSecret algorithm length (if == 0, no Key) * < length in bytes > masterSecret algorithm * < 2 bytes > masterSecretKey length * < length in bytes> masterSecretKey @@ -305,6 +312,14 @@ final class SSLSessionImpl extends ExtendedSSLSession { * < length in bytes> PSK identity * Anonymous * < 1 byte > + * < 1 byte > exporterMasterSecret algorithm length (if == 0, no Key) + * < length in bytes > exporterMasterSecret algorithm + * < 2 bytes > exporterMasterSecretKey length + * < length in bytes> exporterMasterSecretKey + * < 1 byte > Length of clientRandom + * < length in bytes > clientRandom + * < 1 byte > Length of serverRandom + * < length in bytes > serverRandom */ SSLSessionImpl(HandshakeContext hc, ByteBuffer buf) throws IOException { @@ -379,6 +394,7 @@ final class SSLSessionImpl extends ExtendedSSLSession { } else { this.masterSecret = null; } + // Use extended master secret this.useExtendedMasterSecret = (buf.get() != 0); @@ -514,6 +530,46 @@ final class SSLSessionImpl extends ExtendedSSLSession { throw new SSLException("Failed local certs of session."); } + // Exporter master secret length of secret key algorithm (one byte) + i = buf.get(); + if (i > 0) { + b = new byte[i]; + // Get algorithm string + buf.get(b, 0, i); + String algName = new String(b); + // Encoded length + i = Short.toUnsignedInt(buf.getShort()); + // Encoded SecretKey + b = new byte[i]; + buf.get(b); + this.exporterMasterSecret = new SecretKeySpec(b, algName); + } else { + // TLSv1.2- + this.exporterMasterSecret = null; + } + + // Get clientRandom + i = Byte.toUnsignedInt(buf.get()); + if (i > 0) { + b = new byte[i]; + buf.get(b, 0, i); + this.clientRandom = new RandomCookie(ByteBuffer.wrap(b)); + } else { + // TLSv1.3+ + this.clientRandom = null; + } + + // Get serverRandom + i = Byte.toUnsignedInt(buf.get()); + if (i > 0) { + b = new byte[i]; + buf.get(b, 0, i); + this.serverRandom = new RandomCookie(ByteBuffer.wrap(b)); + } else { + // TLSv1.3+ + this.serverRandom = null; + } + context = (SSLSessionContextImpl) hc.sslContext.engineGetServerSessionContext(); this.lastUsedTime = System.currentTimeMillis(); @@ -694,6 +750,36 @@ byte[] write() throws Exception { hos.putInt8(0); } + // Exporter Master Secret from TLSv1.3+ + if (getExporterMasterSecret() == null || + getExporterMasterSecret().getAlgorithm() == null) { + hos.putInt8(0); + } else { + String alg = getExporterMasterSecret().getAlgorithm(); + hos.putInt8(alg.length()); + if (alg.length() != 0) { + hos.write(alg.getBytes()); + } + b = getExporterMasterSecret().getEncoded(); + hos.putInt16(b.length); + hos.write(b, 0, b.length); + } + + // Randoms from TLSv1.2- + if ( clientRandom == null || clientRandom.randomBytes.length == 0) { + hos.putInt8(0); + } else { + hos.putInt8(clientRandom.randomBytes.length); + hos.writeBytes(clientRandom.randomBytes); + } + + if ( serverRandom == null || serverRandom.randomBytes.length == 0) { + hos.putInt8(0); + } else { + hos.putInt8(serverRandom.randomBytes.length); + hos.writeBytes(serverRandom.randomBytes); + } + return hos.toByteArray(); } @@ -701,6 +787,15 @@ void setMasterSecret(SecretKey secret) { masterSecret = secret; } + void setExporterMasterSecret(SecretKey secret) { + exporterMasterSecret = secret; + } + + void setRandoms(RandomCookie client, RandomCookie server) { + clientRandom = client; + serverRandom = server; + } + void setResumptionMasterSecret(SecretKey secret) { resumptionMasterSecret = secret; } @@ -736,6 +831,27 @@ SecretKey getMasterSecret() { return masterSecret; } + /** + * Returns the exporter master secret + */ + SecretKey getExporterMasterSecret() { + return exporterMasterSecret; + } + + /** + * Returns the client's RandomCookie + */ + RandomCookie getClientRandom() { + return clientRandom; + } + + /** + * Returns the server's RandomCookie + */ + RandomCookie getServerRandom() { + return serverRandom; + } + SecretKey getResumptionMasterSecret() { return resumptionMasterSecret; } @@ -1486,6 +1602,199 @@ public List getRequestedServerNames() { return requestedServerNames; } + /** + * Generate Exported Key Material (EKM) calculated according to the + * algorithms defined in RFCs 5705/8446. + */ + @Override + public SecretKey exportKeyMaterialKey( + String label, byte[] context, int length) throws SSLKeyException { + + // Global preconditions + Objects.requireNonNull(label, "label can not be null"); + if (length < 0) { + throw new IllegalArgumentException( + "Output length can not be negative"); + } + + // Calculations are primarily based on protocol version. + switch (protocolVersion) { + case TLS13: // HKDF-based + // Unlikely, but check anyway. + if (exporterMasterSecret == null) { + throw new RuntimeException( + "Exporter master secret not captured"); + } + + // Do RFC 8446:7.1-7.5 calculations + + /* + * TLS-Exporter(label, context_value, key_length) = + * HKDF-Expand-Label(Derive-Secret(Secret, label, ""), + * "exporter", Hash(context_value), key_length) + * + * Derive-Secret(Secret, Label, Messages) = + * HKDF-Expand-Label(Secret, Label, + * Transcript-Hash(Messages), Hash.length) + */ + + // If no context (null) is provided, RFC 8446 requires an empty + // context be used, unlike RFC 5705. + context = (context != null ? context : new byte[0]); + + try { + // Use the ciphersuite's hashAlg for these calcs. + HashAlg hashAlg = cipherSuite.hashAlg; + HKDF hkdf = new HKDF(hashAlg.name); + + // First calculate the inner Derive-Secret(Secret, label, "") + MessageDigest md; + byte[] emptyHash; + + // Create the "" digest... + try { + md = MessageDigest.getInstance(hashAlg.toString()); + emptyHash = md.digest(); + } catch (NoSuchAlgorithmException nsae) { + throw new RuntimeException( + "Hash algorithm " + cipherSuite.hashAlg.name + + " is not available", nsae); + } + + // ...then the hkdfInfo... + byte[] hkdfInfo = SSLSecretDerivation.createHkdfInfo( + ("tls13 " + label).getBytes(StandardCharsets.UTF_8), + emptyHash, hashAlg.hashLength); + + // ...then the "inner" HKDF-Expand-Label() to get the + // derivedSecret that is used as the Secret in the "outer" + // HKDF-Expand-Label(). + SecretKey derivedSecret = hkdf.expand(exporterMasterSecret, + hkdfInfo, hashAlg.hashLength, "DerivedSecret"); + + // Now do the "outer" HKDF-Expand-Label. + // HKDF-Expand-Label(derivedSecret, "exporter", + // Hash(context_value), key_length) + + // If a context was supplied, use it, otherwise, use the + // previous hashed value of ""... + byte[] hash = ((context.length > 0) ? + md.digest(context): emptyHash); + + // ...now the hkdfInfo... + hkdfInfo = SSLSecretDerivation.createHkdfInfo(("tls13 " + + "exporter").getBytes(StandardCharsets.UTF_8), hash, + length); + + // ...now the final expand. + SecretKey key = hkdf.expand(derivedSecret, hkdfInfo, length, + "label"); + try { + // Best effort + derivedSecret.destroy(); + } catch (DestroyFailedException e) { + // swallow + } + return key; + } catch (Exception e) { + // For whatever reason, we couldn't generate. Wrap and return. + throw new SSLKeyException("Couldn't generate Exporter/HKDF", e); + } + + case TLS12: // RFC 7505 using PRF-based (RFC 2246/4346/5246) calcs. + case TLS11: + case TLS10: + + // RFC 7627: + // + // If a client or server chooses to continue with a full handshake + // without the extended master secret extension ... they MUST NOT + // export any key material based on the new master secret for any + // subsequent application-level authentication ... it MUST + // disable [RFC5705] ... + + if (!useExtendedMasterSecret) { + throw new SSLKeyException( + "Exporters require extended master secrets"); + } + + // Unlikely, but check if randoms were not captured. + if (clientRandom == null || serverRandom == null) { + throw new RuntimeException("Random nonces not captured"); + } + + // context length must fit in 2 unsigned bytes. + if ((context != null) && context.length >= (1 << 16)) { + throw new IllegalArgumentException( + "Only 16-bit context lengths supported"); + } + + // Perform RFC 5705 calculations using the internal SunJCE PRF. + String prfAlg; + HashAlg hashAlg; + if (protocolVersion == TLS12) { + prfAlg = "SunTls12Prf"; + hashAlg = cipherSuite.hashAlg; + } else { // all other cases + prfAlg = "SunTlsPrf"; + hashAlg = H_NONE; + } + + // Make a seed with randoms and optional context + // Note that if context is null, it is omitted from the calc + byte[] clientRandomBytes = clientRandom.randomBytes; + byte[] serverRandomBytes = serverRandom.randomBytes; + byte[] seed = new byte[ + clientRandomBytes.length + serverRandomBytes.length + + ((context != null) ? (2 + context.length) : 0)]; + + int pos = 0; + System.arraycopy( + clientRandomBytes, 0, seed, pos, clientRandomBytes.length); + pos += clientRandomBytes.length; + System.arraycopy( + serverRandomBytes, 0, seed, pos, serverRandomBytes.length); + pos += serverRandomBytes.length; + if (context != null) { + // RFC 5705, "If no context is provided, ..." + seed[pos++] = (byte) ((context.length >> 8) & 0xFF); + seed[pos++] = (byte) ((context.length) & 0xFF); + System.arraycopy( + context, 0, seed, pos, context.length); + } + + // Call the PRF function. + try { + @SuppressWarnings("deprecation") + TlsPrfParameterSpec spec = new TlsPrfParameterSpec( + masterSecret, label, seed, length, + hashAlg.name, hashAlg.hashLength, hashAlg.blockSize); + KeyGenerator kg = KeyGenerator.getInstance(prfAlg); + kg.init(spec); + return kg.generateKey(); + } catch (NoSuchAlgorithmException | + InvalidAlgorithmParameterException e) { + throw new SSLKeyException("Could not generate Exporter/PRF", e); + } + default: + // SSLv3 is vulnerable to a triple handshake attack and can't be + // mitigated by RFC 7627. Don't support this or any other + // unknown protocol. + throw new SSLKeyException( + "Exporters not supported in " + protocolVersion); + } + } + + /** + * Generate Exported Key Material (EKM) calculated according to the + * algorithms defined in RFCs 5705/8446. + */ + @Override + public byte[] exportKeyMaterialData( + String label, byte[] context, int length) throws SSLKeyException { + return exportKeyMaterialKey(label, context, length).getEncoded(); + } + /** Returns a string representation of this SSL session */ @Override public String toString() { diff --git a/test/jdk/javax/net/ssl/ExtendedSSLSession/TLSKeyExporters.java b/test/jdk/javax/net/ssl/ExtendedSSLSession/TLSKeyExporters.java new file mode 100644 index 0000000000000..3d8d500bb0e47 --- /dev/null +++ b/test/jdk/javax/net/ssl/ExtendedSSLSession/TLSKeyExporters.java @@ -0,0 +1,466 @@ +/* + * Copyright (c) 2003, 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 + * 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 8341346 + * @summary Add support for exporting TLS Keying Material + * @library /javax/net/ssl/templates + * @build SSLContextTemplate + * @run main/othervm TLSKeyExporters + */ + +import java.security.Security; +import java.util.Arrays; +import javax.net.ssl.*; +import javax.net.ssl.SSLEngineResult.HandshakeStatus; +import java.nio.ByteBuffer; +import java.util.Random; + +/** + * A SSLEngine usage example which simplifies the presentation + * by removing the I/O and multi-threading concerns. + *

+ * The test creates two SSLEngines, simulating a client and server. + * The "transport" layer consists two byte buffers: think of them + * as directly connected pipes. + *

+ * Note, this is a *very* simple example: real code will be much more + * involved. For example, different threading and I/O models could be + * used, transport mechanisms could close unexpectedly, and so on. + *

+ * When this application runs, notice that several messages + * (wrap/unwrap) pass before any application data is consumed or + * produced. + */ +public class TLSKeyExporters extends SSLContextTemplate { + protected final SSLEngine clientEngine; // client Engine + protected final ByteBuffer clientOut; // write side of clientEngine + protected final ByteBuffer clientIn; // read side of clientEngine + + protected final SSLEngine serverEngine; // server Engine + protected final ByteBuffer serverOut; // write side of serverEngine + protected final ByteBuffer serverIn; // read side of serverEngine + + // For data transport, this example uses local ByteBuffers. This + // isn't really useful, but the purpose of this example is to show + // SSLEngine concepts, not how to do network transport. + protected final ByteBuffer cTOs; // "reliable" transport client->server + protected final ByteBuffer sTOc; // "reliable" transport server->client + + protected TLSKeyExporters(String protocol, String ciphersuite) + throws Exception { + serverEngine = configureServerEngine( + createServerSSLContext().createSSLEngine()); + + clientEngine = configureClientEngine( + createClientSSLContext().createSSLEngine(), + protocol, ciphersuite); + + // We'll assume the buffer sizes are the same + // between client and server. + SSLSession session = clientEngine.getSession(); + int appBufferMax = session.getApplicationBufferSize(); + int netBufferMax = session.getPacketBufferSize(); + + // We'll make the input buffers a bit bigger than the max needed + // size, so that unwrap()s following a successful data transfer + // won't generate BUFFER_OVERFLOWS. + // + // We'll use a mix of direct and indirect ByteBuffers for + // tutorial purposes only. In reality, only use direct + // ByteBuffers when they give a clear performance enhancement. + clientIn = ByteBuffer.allocate(appBufferMax + 50); + serverIn = ByteBuffer.allocate(appBufferMax + 50); + + cTOs = ByteBuffer.allocateDirect(netBufferMax); + sTOc = ByteBuffer.allocateDirect(netBufferMax); + + clientOut = createClientOutputBuffer(); + serverOut = createServerOutputBuffer(); + } + + protected ByteBuffer createServerOutputBuffer() { + return ByteBuffer.wrap("Hello Client, I'm Server".getBytes()); + } + + // + // Protected methods could be used to customize the test case. + // + + protected ByteBuffer createClientOutputBuffer() { + return ByteBuffer.wrap("Hi Server, I'm Client".getBytes()); + } + + /* + * Configure the client side engine. + */ + protected SSLEngine configureClientEngine(SSLEngine clientEngine, + String protocol, String ciphersuite) { + clientEngine.setUseClientMode(true); + + // Get/set parameters if needed + SSLParameters paramsClient = clientEngine.getSSLParameters(); + paramsClient.setProtocols(new String[] { protocol }); + paramsClient.setCipherSuites(new String[] { ciphersuite }); + clientEngine.setSSLParameters(paramsClient); + + return clientEngine; + } + + /* + * Configure the server side engine. + */ + protected SSLEngine configureServerEngine(SSLEngine serverEngine) { + serverEngine.setUseClientMode(false); + serverEngine.setNeedClientAuth(true); + + // Get/set parameters if needed + // + SSLParameters paramsServer = serverEngine.getSSLParameters(); + paramsServer.setProtocols(new String[] { + "TLSv1.3", "TLSv1.2", "TLSv1.1", "TLSv1", "SSLv3" + }); + serverEngine.setSSLParameters(paramsServer); + + return serverEngine; + } + + public static void main(String[] args) throws Exception { + // Turn off the disabled Algorithms so we can also test SSLv3/TLSv1/etc. + Security.setProperty("jdk.tls.disabledAlgorithms", ""); + + // Exercise all of the triggers which capture data + // in the various key exchange algorithms. + + // Use appropriate protocol/ciphersuite combos for TLSv1.3 + new TLSKeyExporters( + "TLSv1.3", "TLS_AES_128_GCM_SHA256").runTest(); + new TLSKeyExporters( + "TLSv1.3", "TLS_AES_256_GCM_SHA384").runTest(); + new TLSKeyExporters( + "TLSv1.3", "TLS_CHACHA20_POLY1305_SHA256").runTest(); + + // Try the various GCM suites for TLSv1.2 + new TLSKeyExporters( + "TLSv1.2", "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384").runTest(); + new TLSKeyExporters( + "TLSv1.2", "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256").runTest(); + new TLSKeyExporters( + "TLSv1.2", "TLS_RSA_WITH_AES_256_GCM_SHA384").runTest(); + + // Try one TLSv1.2/CBC suite just for grins, the triggers are the same. + new TLSKeyExporters( + "TLSv1.2", "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256").runTest(); + + // Use appropriate protocol/ciphersuite combos. Some of the 1.2 + // suites (e.g. GCM) can't be used in earlier TLS versions. + new TLSKeyExporters( + "TLSv1.1", "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA").runTest(); + new TLSKeyExporters( + "TLSv1.1", "TLS_DHE_RSA_WITH_AES_256_CBC_SHA").runTest(); + new TLSKeyExporters( + "TLSv1.1", "TLS_RSA_WITH_AES_256_CBC_SHA").runTest(); + + new TLSKeyExporters( + "TLSv1", "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA").runTest(); + new TLSKeyExporters( + "TLSv1", "TLS_DHE_RSA_WITH_AES_256_CBC_SHA").runTest(); + new TLSKeyExporters( + "TLSv1", "TLS_RSA_WITH_AES_256_CBC_SHA").runTest(); + + try { + new TLSKeyExporters( + "SSLv3", "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA").runTest(); + throw new Exception("SSLv3 export test should not have passed"); + } catch (SSLException e) { + System.out.println("SSLv3 test failed as expected"); + } + + System.out.println("All tests PASSED"); + } + + // + // Private methods that used to build the common part of the test. + // + + private void runTest() throws Exception { + SSLEngineResult clientResult; + SSLEngineResult serverResult; + + boolean dataDone = false; + while (isOpen(clientEngine) || isOpen(serverEngine)) { + log("================="); + + // client wrap + log("---Client Wrap---"); + clientResult = clientEngine.wrap(clientOut, cTOs); + logEngineStatus(clientEngine, clientResult); + runDelegatedTasks(clientEngine); + + // server wrap + log("---Server Wrap---"); + serverResult = serverEngine.wrap(serverOut, sTOc); + logEngineStatus(serverEngine, serverResult); + runDelegatedTasks(serverEngine); + + cTOs.flip(); + sTOc.flip(); + + // client unwrap + log("---Client Unwrap---"); + clientResult = clientEngine.unwrap(sTOc, clientIn); + logEngineStatus(clientEngine, clientResult); + runDelegatedTasks(clientEngine); + + // server unwrap + log("---Server Unwrap---"); + serverResult = serverEngine.unwrap(cTOs, serverIn); + logEngineStatus(serverEngine, serverResult); + runDelegatedTasks(serverEngine); + + cTOs.compact(); + sTOc.compact(); + + // After we've transferred all application data between the client + // and server, we close the clientEngine's outbound stream. + // This generates a close_notify handshake message, which the + // server engine receives and responds by closing itself. + if (!dataDone && (clientOut.limit() == serverIn.position()) && + (serverOut.limit() == clientIn.position())) { + + runExporterTests( + (ExtendedSSLSession) clientEngine.getSession(), + (ExtendedSSLSession) serverEngine.getSession()); + + // A sanity check to ensure we got what was sent. + checkTransfer(serverOut, clientIn); + checkTransfer(clientOut, serverIn); + + log("\tClosing clientEngine's *OUTBOUND*..."); + clientEngine.closeOutbound(); + logEngineStatus(clientEngine); + + dataDone = true; + log("\tClosing serverEngine's *OUTBOUND*..."); + serverEngine.closeOutbound(); + logEngineStatus(serverEngine); + } + } + } + + private static void runExporterTests( + ExtendedSSLSession cessls, + ExtendedSSLSession sessls) throws Exception { + + // Create output arrays + byte[] clientBytes, serverBytes; + + // Create various input arrays and fill with junk. + Random random = new Random(); + byte[] bytes = new byte[20]; + random.nextBytes(bytes); + + // Slightly change 1 byte in the middle + byte[] bytesDiff = Arrays.copyOf(bytes, bytes.length); + bytesDiff[bytes.length/2]++; + + byte[] bytesDiffSize = new byte[21]; + random.nextBytes(bytesDiffSize); + + // Inputs exactly equal. Use exportKeyMaterialKey() + clientBytes = cessls.exportKeyMaterialKey("hello", + bytes, 128).getEncoded(); + serverBytes = sessls.exportKeyMaterialKey("hello", + bytes, 128).getEncoded(); + if (!Arrays.equals(clientBytes, serverBytes)) { + throw new Exception("Equal inputs but exporters are not"); + } else { + log("Equal inputs test passed"); + } + + // Empty label. I don't see anything that says this is + // forbidden. There is some verbiage about: labels being registered + // with IANA, must not collide with existing PRF labels, SHOULD use + // "EXPORTER"/"EXPERIMENTAL" prefixes, etc. + clientBytes = cessls.exportKeyMaterialKey("", + bytes, 128).getEncoded(); + serverBytes = sessls.exportKeyMaterialKey("", + bytes, 128).getEncoded(); + if (!Arrays.equals(clientBytes, serverBytes)) { + throw new Exception("Empty label and exporters are equal"); + } else { + log("Empty label test passed"); + } + + // Different labels, now use exportKeyMaterialData() for coverage + clientBytes = cessls.exportKeyMaterialData("hello", + bytes, 128); + serverBytes = sessls.exportKeyMaterialData("goodbye", + bytes, 128); + if (Arrays.equals(clientBytes, serverBytes)) { + throw new Exception("Different labels but exporters same"); + } else { + log("Different labels test passed"); + } + + // Different output sizes + clientBytes = cessls.exportKeyMaterialData("hello", + bytes, 128); + serverBytes = sessls.exportKeyMaterialData("hello", + bytes, 127); + if ((clientBytes.length != 128) || (serverBytes.length != 127)) { + throw new Exception("Output sizes incorrect: " + + clientBytes.length + "/" + serverBytes.length); + } + if (Arrays.equals(clientBytes, serverBytes)) { + throw new Exception("Different output sizes but exporters same"); + } else { + log("Different output size test passed"); + } + + // Different context values + clientBytes = cessls.exportKeyMaterialData("hello", + bytes, 128); + serverBytes = sessls.exportKeyMaterialData("hello", + bytesDiff, 128); + if (Arrays.equals(clientBytes, serverBytes)) { + throw new Exception("Different context but exporters same"); + } else { + log("Different context test passed"); + } + + // Different context sizes + clientBytes = cessls.exportKeyMaterialData("hello", + bytes, 128); + serverBytes = sessls.exportKeyMaterialData("hello", + bytesDiffSize, 128); + if (Arrays.equals(clientBytes, serverBytes)) { + throw new Exception("Different context sizes but exporters same."); + } else { + log("Different context sizes test passed"); + } + + // No context, but otherwise the same + clientBytes = cessls.exportKeyMaterialData("hello", + null, 128); + serverBytes = sessls.exportKeyMaterialData("hello", + null, 128); + if (!Arrays.equals(clientBytes, serverBytes)) { + throw new Exception("No context and exporters are not the same."); + } else { + log("No context test passed"); + } + + // Check error conditions + try { + cessls.exportKeyMaterialData(null, bytes, 128); + throw new Exception("null label accepted"); + } catch (NullPointerException e) { + log("null label test passed"); + } + + try { + cessls.exportKeyMaterialData("hello", new byte[1<<16], 128); + if (!cessls.getProtocol().equals("TLSv1.3")) { + throw new Exception("large context accepted in " + + "SSLv3/TLSv1/TLSv1.1/TLSv1.2"); + } else { + log("large context test passed in TLSv1.3"); + } + } catch (IllegalArgumentException e) { + log("large context test passed in " + + "SSLv3/TLSv1/TLSv1.1/TLSv1.2"); + } + + try { + cessls.exportKeyMaterialData("hello", bytes, -20); + throw new Exception("negative length accepted"); + } catch (IllegalArgumentException e) { + log("negative length test passed"); + } + } + + static boolean isOpen(SSLEngine engine) { + return (!engine.isOutboundDone() || !engine.isInboundDone()); + } + + private static void logEngineStatus(SSLEngine engine) { + log("\tCurrent HS State: " + engine.getHandshakeStatus()); + log("\tisInboundDone() : " + engine.isInboundDone()); + log("\tisOutboundDone(): " + engine.isOutboundDone()); + } + + private static void logEngineStatus( + SSLEngine engine, SSLEngineResult result) { + log("\tResult Status : " + result.getStatus()); + log("\tResult HS Status : " + result.getHandshakeStatus()); + log("\tEngine HS Status : " + engine.getHandshakeStatus()); + log("\tisInboundDone() : " + engine.isInboundDone()); + log("\tisOutboundDone() : " + engine.isOutboundDone()); + log("\tMore Result : " + result); + } + + private static void log(String message) { + System.err.println(message); + } + + // If the result indicates that we have outstanding tasks to do, + // go ahead and run them in this thread. + protected static void runDelegatedTasks(SSLEngine engine) throws Exception { + if (engine.getHandshakeStatus() == HandshakeStatus.NEED_TASK) { + Runnable runnable; + while ((runnable = engine.getDelegatedTask()) != null) { + log(" running delegated task..."); + runnable.run(); + } + HandshakeStatus hsStatus = engine.getHandshakeStatus(); + if (hsStatus == HandshakeStatus.NEED_TASK) { + throw new Exception( + "handshake shouldn't need additional tasks"); + } + logEngineStatus(engine); + } + } + + // Simple check to make sure everything came across as expected. + static void checkTransfer(ByteBuffer a, ByteBuffer b) + throws Exception { + a.flip(); + b.flip(); + + if (!a.equals(b)) { + throw new Exception("Data didn't transfer cleanly"); + } else { + log("\tData transferred cleanly"); + } + + a.position(a.limit()); + b.position(b.limit()); + a.limit(a.capacity()); + b.limit(b.capacity()); + } +} From e9745966db59231b2a0c8d90af9bdd43773e8b15 Mon Sep 17 00:00:00 2001 From: Bradford Wetmore Date: Wed, 30 Apr 2025 16:28:52 -0700 Subject: [PATCH 02/27] Tweak API to be more KDF like in unextractable case. --- .../share/classes/javax/net/ssl/ExtendedSSLSession.java | 7 +++---- .../share/classes/sun/security/ssl/SSLSessionImpl.java | 5 ++++- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/java.base/share/classes/javax/net/ssl/ExtendedSSLSession.java b/src/java.base/share/classes/javax/net/ssl/ExtendedSSLSession.java index ec9acad547ae6..ef431753c9b9b 100644 --- a/src/java.base/share/classes/javax/net/ssl/ExtendedSSLSession.java +++ b/src/java.base/share/classes/javax/net/ssl/ExtendedSSLSession.java @@ -242,12 +242,11 @@ public SecretKey exportKeyMaterialKey( * accommodated * @throws NullPointerException if {@code label} is null * @throws UnsupportedOperationException if the underlying provider - * does not implement the operation + * does not implement the operation, or if the derived key + * material is not extractable. * * @return a byte buffer of size {@code length} that contains the EKM - * material or {@code null} if the key material could not be - * extracted - * + * material * @since 25 */ public byte[] exportKeyMaterialData( diff --git a/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java b/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java index 9c969927cca1b..056e6a12a0c74 100644 --- a/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java +++ b/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java @@ -1792,7 +1792,10 @@ public SecretKey exportKeyMaterialKey( @Override public byte[] exportKeyMaterialData( String label, byte[] context, int length) throws SSLKeyException { - return exportKeyMaterialKey(label, context, length).getEncoded(); + if (exportKeyMaterialKey(label, context, length).getEncoded() == null) { + throw new UnsupportedOperationException( + "Exported key material is not extractable"); + }; } /** Returns a string representation of this SSL session */ From a0332f8e03ab9109e702fff165e2ae0a1ce449e1 Mon Sep 17 00:00:00 2001 From: Bradford Wetmore Date: Wed, 30 Apr 2025 16:35:00 -0700 Subject: [PATCH 03/27] Moved too fast --- .../share/classes/sun/security/ssl/SSLSessionImpl.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java b/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java index 056e6a12a0c74..aaac264690941 100644 --- a/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java +++ b/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java @@ -1792,10 +1792,13 @@ public SecretKey exportKeyMaterialKey( @Override public byte[] exportKeyMaterialData( String label, byte[] context, int length) throws SSLKeyException { - if (exportKeyMaterialKey(label, context, length).getEncoded() == null) { + byte[] bytes = + exportKeyMaterialKey(label, context, length).getEncoded(); + if (bytes == null) { throw new UnsupportedOperationException( "Exported key material is not extractable"); - }; + } + return bytes; } /** Returns a string representation of this SSL session */ From bdd16052a97b73349396bb7b221031a2294562e4 Mon Sep 17 00:00:00 2001 From: Bradford Wetmore Date: Thu, 1 May 2025 13:14:33 -0700 Subject: [PATCH 04/27] Codereview comments. --- .../sun/security/ssl/SSLSessionImpl.java | 2 +- .../ExtendedSSLSession/TLSKeyExporters.java | 115 +++++++++--------- 2 files changed, 56 insertions(+), 61 deletions(-) diff --git a/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java b/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java index aaac264690941..d8e8e96bc9003 100644 --- a/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java +++ b/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java @@ -1688,7 +1688,7 @@ public SecretKey exportKeyMaterialKey( // ...now the final expand. SecretKey key = hkdf.expand(derivedSecret, hkdfInfo, length, - "label"); + label); try { // Best effort derivedSecret.destroy(); diff --git a/test/jdk/javax/net/ssl/ExtendedSSLSession/TLSKeyExporters.java b/test/jdk/javax/net/ssl/ExtendedSSLSession/TLSKeyExporters.java index 3d8d500bb0e47..d35f9f5cd5d4c 100644 --- a/test/jdk/javax/net/ssl/ExtendedSSLSession/TLSKeyExporters.java +++ b/test/jdk/javax/net/ssl/ExtendedSSLSession/TLSKeyExporters.java @@ -28,7 +28,7 @@ * @test * @bug 8341346 * @summary Add support for exporting TLS Keying Material - * @library /javax/net/ssl/templates + * @library /javax/net/ssl/templates /test/lib * @build SSLContextTemplate * @run main/othervm TLSKeyExporters */ @@ -40,6 +40,8 @@ import java.nio.ByteBuffer; import java.util.Random; +import static jdk.test.lib.Asserts.*; + /** * A SSLEngine usage example which simplifies the presentation * by removing the I/O and multi-threading concerns. @@ -273,8 +275,8 @@ private void runTest() throws Exception { } private static void runExporterTests( - ExtendedSSLSession cessls, - ExtendedSSLSession sessls) throws Exception { + ExtendedSSLSession clientSession, + ExtendedSSLSession serverSession) throws Exception { // Create output arrays byte[] clientBytes, serverBytes; @@ -292,100 +294,93 @@ private static void runExporterTests( random.nextBytes(bytesDiffSize); // Inputs exactly equal. Use exportKeyMaterialKey() - clientBytes = cessls.exportKeyMaterialKey("hello", + clientBytes = clientSession.exportKeyMaterialKey("hello", bytes, 128).getEncoded(); - serverBytes = sessls.exportKeyMaterialKey("hello", + serverBytes = serverSession.exportKeyMaterialKey("hello", bytes, 128).getEncoded(); - if (!Arrays.equals(clientBytes, serverBytes)) { - throw new Exception("Equal inputs but exporters are not"); - } else { - log("Equal inputs test passed"); - } + assertEqualsByteArray(clientBytes, serverBytes, + "Equal inputs but exporters are not equal"); + log("Equal inputs test passed"); // Empty label. I don't see anything that says this is // forbidden. There is some verbiage about: labels being registered // with IANA, must not collide with existing PRF labels, SHOULD use // "EXPORTER"/"EXPERIMENTAL" prefixes, etc. - clientBytes = cessls.exportKeyMaterialKey("", + clientBytes = clientSession.exportKeyMaterialKey("", bytes, 128).getEncoded(); - serverBytes = sessls.exportKeyMaterialKey("", + serverBytes = serverSession.exportKeyMaterialKey("", bytes, 128).getEncoded(); - if (!Arrays.equals(clientBytes, serverBytes)) { - throw new Exception("Empty label and exporters are equal"); - } else { - log("Empty label test passed"); - } + assertEqualsByteArray(clientBytes, serverBytes, + "Empty label and exporters are equal"); + log("Empty label test passed"); // Different labels, now use exportKeyMaterialData() for coverage - clientBytes = cessls.exportKeyMaterialData("hello", + clientBytes = clientSession.exportKeyMaterialData("hello", bytes, 128); - serverBytes = sessls.exportKeyMaterialData("goodbye", + serverBytes = serverSession.exportKeyMaterialData("goodbye", bytes, 128); - if (Arrays.equals(clientBytes, serverBytes)) { - throw new Exception("Different labels but exporters same"); - } else { - log("Different labels test passed"); - } + assertNotEqualsByteArray(clientBytes, serverBytes, + "Different labels but exporters same"); + log("Different labels test passed"); // Different output sizes - clientBytes = cessls.exportKeyMaterialData("hello", + clientBytes = clientSession.exportKeyMaterialData("hello", bytes, 128); - serverBytes = sessls.exportKeyMaterialData("hello", + serverBytes = serverSession.exportKeyMaterialData("hello", bytes, 127); - if ((clientBytes.length != 128) || (serverBytes.length != 127)) { - throw new Exception("Output sizes incorrect: " + - clientBytes.length + "/" + serverBytes.length); - } - if (Arrays.equals(clientBytes, serverBytes)) { - throw new Exception("Different output sizes but exporters same"); - } else { - log("Different output size test passed"); - } + assertEquals(clientBytes.length, 128, "client length != 128"); + assertEquals(serverBytes.length, 127, "server length != 127"); + assertNotEqualsByteArray(clientBytes, serverBytes, + "Different output sizes but exporters same"); + log("Different output size test passed"); // Different context values - clientBytes = cessls.exportKeyMaterialData("hello", + clientBytes = clientSession.exportKeyMaterialData("hello", bytes, 128); - serverBytes = sessls.exportKeyMaterialData("hello", + serverBytes = serverSession.exportKeyMaterialData("hello", bytesDiff, 128); - if (Arrays.equals(clientBytes, serverBytes)) { - throw new Exception("Different context but exporters same"); - } else { - log("Different context test passed"); - } + assertNotEqualsByteArray(clientBytes, serverBytes, + "Different context but exporters same"); + log("Different context test passed"); // Different context sizes - clientBytes = cessls.exportKeyMaterialData("hello", + clientBytes = clientSession.exportKeyMaterialData("hello", bytes, 128); - serverBytes = sessls.exportKeyMaterialData("hello", + serverBytes = serverSession.exportKeyMaterialData("hello", bytesDiffSize, 128); - if (Arrays.equals(clientBytes, serverBytes)) { - throw new Exception("Different context sizes but exporters same."); - } else { - log("Different context sizes test passed"); - } + assertNotEqualsByteArray(clientBytes, serverBytes, + "Different context sizes but exporters same"); + log("Different context sizes test passed"); // No context, but otherwise the same - clientBytes = cessls.exportKeyMaterialData("hello", + clientBytes = clientSession.exportKeyMaterialData("hello", null, 128); - serverBytes = sessls.exportKeyMaterialData("hello", + serverBytes = serverSession.exportKeyMaterialData("hello", null, 128); - if (!Arrays.equals(clientBytes, serverBytes)) { - throw new Exception("No context and exporters are not the same."); - } else { - log("No context test passed"); - } + assertEqualsByteArray(clientBytes, serverBytes, + "No context and exporters are not the same"); + log("No context test passed"); + + // Smaller key size + clientBytes = clientSession.exportKeyMaterialData("hello", + bytes, 40); + serverBytes = serverSession.exportKeyMaterialData("hello", + bytes, 40); + assertEqualsByteArray(clientBytes, serverBytes, + "Smaller key size should be the same"); + log("Smaller key size test passed"); // Check error conditions try { - cessls.exportKeyMaterialData(null, bytes, 128); + clientSession.exportKeyMaterialData(null, bytes, 128); throw new Exception("null label accepted"); } catch (NullPointerException e) { log("null label test passed"); } try { - cessls.exportKeyMaterialData("hello", new byte[1<<16], 128); - if (!cessls.getProtocol().equals("TLSv1.3")) { + clientSession.exportKeyMaterialData("hello", new byte[1<<16], 128); + if (!clientSession.getProtocol().equals("TLSv1.3")) { throw new Exception("large context accepted in " + "SSLv3/TLSv1/TLSv1.1/TLSv1.2"); } else { @@ -397,7 +392,7 @@ private static void runExporterTests( } try { - cessls.exportKeyMaterialData("hello", bytes, -20); + clientSession.exportKeyMaterialData("hello", bytes, -20); throw new Exception("negative length accepted"); } catch (IllegalArgumentException e) { log("negative length test passed"); From 64d46d9aa372b6017c6cc501ee3afc019cdeae47 Mon Sep 17 00:00:00 2001 From: Bradford Wetmore Date: Tue, 6 May 2025 21:20:09 -0700 Subject: [PATCH 05/27] More codereview/CSR comments --- .../javax/net/ssl/ExtendedSSLSession.java | 28 ++++--- .../sun/security/ssl/SSLSessionImpl.java | 6 +- ...rs.java => ExportKeyingMaterialTests.java} | 73 ++++++++++--------- 3 files changed, 57 insertions(+), 50 deletions(-) rename test/jdk/javax/net/ssl/ExtendedSSLSession/{TLSKeyExporters.java => ExportKeyingMaterialTests.java} (88%) diff --git a/src/java.base/share/classes/javax/net/ssl/ExtendedSSLSession.java b/src/java.base/share/classes/javax/net/ssl/ExtendedSSLSession.java index ef431753c9b9b..7d024d248ca10 100644 --- a/src/java.base/share/classes/javax/net/ssl/ExtendedSSLSession.java +++ b/src/java.base/share/classes/javax/net/ssl/ExtendedSSLSession.java @@ -169,9 +169,9 @@ public List getStatusResponses() { * Generate Exported Key Material (EKM) calculated according to the * algorithms defined in RFCs 5705/8446. *

- * Note RFC 5705 calculates different EKM values if {@code context} - * is null vs. non-null/empty. RFC 8446 does not make such a - * distinction. + * Note RFC 5705 (for (D)TLSv1.2 and earlier) calculates different EKM + * values depending on whether {@code context} is null or non-null/empty. + * RFC 8446 (TLSv1.3) treats a null context as non-null/empty. *

* The {@code label} {@code String} will be converted to bytes using * the {@link java.nio.charset.StandardCharsets#UTF_8} @@ -183,9 +183,12 @@ public List getStatusResponses() { * @spec https://www.rfc-editor.org/info/rfc8446 * RFC 8446: The Transport Layer Security (TLS) Protocol Version 1.3 * + * @implSpec The default implementation throws + * {@code UnsupportedOperationException} + * * @param label the label bytes used in the EKM calculation. * {@code label} will be converted to a {@code byte[]} - * before the operation begins. + * before the operation begins * @param context the context bytes used in the EKM calculation * @param length the number of bytes of EKM material needed * @@ -202,7 +205,7 @@ public List getStatusResponses() { * * @since 25 */ - public SecretKey exportKeyMaterialKey( + public SecretKey exportKeyingMaterialKey( String label, byte[] context, int length) throws SSLKeyException { throw new UnsupportedOperationException(); } @@ -211,9 +214,9 @@ public SecretKey exportKeyMaterialKey( * Generate Exported Key Material (EKM) calculated according to the * algorithms defined in RFCs 5705/8446. *

- * Note RFC 5705 calculates different EKM values if {@code context} - * is null vs. non-null/empty. RFC 8446 does not make such a - * distinction. + * Note RFC 5705 (for (D)TLSv1.2 and earlier) calculates different EKM + * values depending on whether {@code context} is null or non-null/empty. + * RFC 8446 (TLSv1.3) treats a null context as non-null/empty. *

* The {@code label} {@code String} will be converted to bytes using * the {@link java.nio.charset.StandardCharsets#UTF_8} @@ -221,7 +224,7 @@ public SecretKey exportKeyMaterialKey( *

* Depending on the chosen underlying key derivation mechanism, the * raw bytes might not be extractable/exportable. In such cases, the - * {@link #exportKeyMaterialKey(String, byte[], int)} method should be + * {@link #exportKeyingMaterialKey(String, byte[], int)} method should be * used instead to access the generated key material. * * @spec https://www.rfc-editor.org/info/rfc5705 @@ -230,9 +233,12 @@ public SecretKey exportKeyMaterialKey( * @spec https://www.rfc-editor.org/info/rfc8446 * RFC 8446: The Transport Layer Security (TLS) Protocol Version 1.3 * + * @implSpec The default implementation throws + * {@code UnsupportedOperationException} + * * @param label the label bytes used in the EKM calculation * {@code label} will be converted to a {@code byte[]} - * before the operation begins. + * before the operation begins * @param context the context bytes used in the EKM calculation * @param length the number of bytes of EKM material needed * @@ -249,7 +255,7 @@ public SecretKey exportKeyMaterialKey( * material * @since 25 */ - public byte[] exportKeyMaterialData( + public byte[] exportKeyingMaterialData( String label, byte[] context, int length) throws SSLKeyException { throw new UnsupportedOperationException(); } diff --git a/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java b/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java index d8e8e96bc9003..4fa7ba5eadb0f 100644 --- a/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java +++ b/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java @@ -1607,7 +1607,7 @@ public List getRequestedServerNames() { * algorithms defined in RFCs 5705/8446. */ @Override - public SecretKey exportKeyMaterialKey( + public SecretKey exportKeyingMaterialKey( String label, byte[] context, int length) throws SSLKeyException { // Global preconditions @@ -1790,10 +1790,10 @@ public SecretKey exportKeyMaterialKey( * algorithms defined in RFCs 5705/8446. */ @Override - public byte[] exportKeyMaterialData( + public byte[] exportKeyingMaterialData( String label, byte[] context, int length) throws SSLKeyException { byte[] bytes = - exportKeyMaterialKey(label, context, length).getEncoded(); + exportKeyingMaterialKey(label, context, length).getEncoded(); if (bytes == null) { throw new UnsupportedOperationException( "Exported key material is not extractable"); diff --git a/test/jdk/javax/net/ssl/ExtendedSSLSession/TLSKeyExporters.java b/test/jdk/javax/net/ssl/ExtendedSSLSession/ExportKeyingMaterialTests.java similarity index 88% rename from test/jdk/javax/net/ssl/ExtendedSSLSession/TLSKeyExporters.java rename to test/jdk/javax/net/ssl/ExtendedSSLSession/ExportKeyingMaterialTests.java index d35f9f5cd5d4c..cfbf86f049c47 100644 --- a/test/jdk/javax/net/ssl/ExtendedSSLSession/TLSKeyExporters.java +++ b/test/jdk/javax/net/ssl/ExtendedSSLSession/ExportKeyingMaterialTests.java @@ -30,7 +30,7 @@ * @summary Add support for exporting TLS Keying Material * @library /javax/net/ssl/templates /test/lib * @build SSLContextTemplate - * @run main/othervm TLSKeyExporters + * @run main/othervm ExportKeyingMaterialTests */ import java.security.Security; @@ -58,7 +58,7 @@ * (wrap/unwrap) pass before any application data is consumed or * produced. */ -public class TLSKeyExporters extends SSLContextTemplate { +public class ExportKeyingMaterialTests extends SSLContextTemplate { protected final SSLEngine clientEngine; // client Engine protected final ByteBuffer clientOut; // write side of clientEngine protected final ByteBuffer clientIn; // read side of clientEngine @@ -73,7 +73,7 @@ public class TLSKeyExporters extends SSLContextTemplate { protected final ByteBuffer cTOs; // "reliable" transport client->server protected final ByteBuffer sTOc; // "reliable" transport server->client - protected TLSKeyExporters(String protocol, String ciphersuite) + protected ExportKeyingMaterialTests(String protocol, String ciphersuite) throws Exception { serverEngine = configureServerEngine( createServerSSLContext().createSSLEngine()); @@ -159,43 +159,43 @@ public static void main(String[] args) throws Exception { // in the various key exchange algorithms. // Use appropriate protocol/ciphersuite combos for TLSv1.3 - new TLSKeyExporters( + new ExportKeyingMaterialTests( "TLSv1.3", "TLS_AES_128_GCM_SHA256").runTest(); - new TLSKeyExporters( + new ExportKeyingMaterialTests( "TLSv1.3", "TLS_AES_256_GCM_SHA384").runTest(); - new TLSKeyExporters( + new ExportKeyingMaterialTests( "TLSv1.3", "TLS_CHACHA20_POLY1305_SHA256").runTest(); // Try the various GCM suites for TLSv1.2 - new TLSKeyExporters( + new ExportKeyingMaterialTests( "TLSv1.2", "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384").runTest(); - new TLSKeyExporters( + new ExportKeyingMaterialTests( "TLSv1.2", "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256").runTest(); - new TLSKeyExporters( + new ExportKeyingMaterialTests( "TLSv1.2", "TLS_RSA_WITH_AES_256_GCM_SHA384").runTest(); // Try one TLSv1.2/CBC suite just for grins, the triggers are the same. - new TLSKeyExporters( + new ExportKeyingMaterialTests( "TLSv1.2", "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256").runTest(); // Use appropriate protocol/ciphersuite combos. Some of the 1.2 // suites (e.g. GCM) can't be used in earlier TLS versions. - new TLSKeyExporters( + new ExportKeyingMaterialTests( "TLSv1.1", "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA").runTest(); - new TLSKeyExporters( + new ExportKeyingMaterialTests( "TLSv1.1", "TLS_DHE_RSA_WITH_AES_256_CBC_SHA").runTest(); - new TLSKeyExporters( + new ExportKeyingMaterialTests( "TLSv1.1", "TLS_RSA_WITH_AES_256_CBC_SHA").runTest(); - new TLSKeyExporters( + new ExportKeyingMaterialTests( "TLSv1", "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA").runTest(); - new TLSKeyExporters( + new ExportKeyingMaterialTests( "TLSv1", "TLS_DHE_RSA_WITH_AES_256_CBC_SHA").runTest(); - new TLSKeyExporters( + new ExportKeyingMaterialTests( "TLSv1", "TLS_RSA_WITH_AES_256_CBC_SHA").runTest(); try { - new TLSKeyExporters( + new ExportKeyingMaterialTests( "SSLv3", "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA").runTest(); throw new Exception("SSLv3 export test should not have passed"); } catch (SSLException e) { @@ -294,9 +294,9 @@ private static void runExporterTests( random.nextBytes(bytesDiffSize); // Inputs exactly equal. Use exportKeyMaterialKey() - clientBytes = clientSession.exportKeyMaterialKey("hello", + clientBytes = clientSession.exportKeyingMaterialKey("hello", bytes, 128).getEncoded(); - serverBytes = serverSession.exportKeyMaterialKey("hello", + serverBytes = serverSession.exportKeyingMaterialKey("hello", bytes, 128).getEncoded(); assertEqualsByteArray(clientBytes, serverBytes, "Equal inputs but exporters are not equal"); @@ -306,27 +306,27 @@ private static void runExporterTests( // forbidden. There is some verbiage about: labels being registered // with IANA, must not collide with existing PRF labels, SHOULD use // "EXPORTER"/"EXPERIMENTAL" prefixes, etc. - clientBytes = clientSession.exportKeyMaterialKey("", + clientBytes = clientSession.exportKeyingMaterialKey("", bytes, 128).getEncoded(); - serverBytes = serverSession.exportKeyMaterialKey("", + serverBytes = serverSession.exportKeyingMaterialKey("", bytes, 128).getEncoded(); assertEqualsByteArray(clientBytes, serverBytes, "Empty label and exporters are equal"); log("Empty label test passed"); // Different labels, now use exportKeyMaterialData() for coverage - clientBytes = clientSession.exportKeyMaterialData("hello", + clientBytes = clientSession.exportKeyingMaterialData("hello", bytes, 128); - serverBytes = serverSession.exportKeyMaterialData("goodbye", + serverBytes = serverSession.exportKeyingMaterialData("goodbye", bytes, 128); assertNotEqualsByteArray(clientBytes, serverBytes, "Different labels but exporters same"); log("Different labels test passed"); // Different output sizes - clientBytes = clientSession.exportKeyMaterialData("hello", + clientBytes = clientSession.exportKeyingMaterialData("hello", bytes, 128); - serverBytes = serverSession.exportKeyMaterialData("hello", + serverBytes = serverSession.exportKeyingMaterialData("hello", bytes, 127); assertEquals(clientBytes.length, 128, "client length != 128"); assertEquals(serverBytes.length, 127, "server length != 127"); @@ -335,36 +335,36 @@ private static void runExporterTests( log("Different output size test passed"); // Different context values - clientBytes = clientSession.exportKeyMaterialData("hello", + clientBytes = clientSession.exportKeyingMaterialData("hello", bytes, 128); - serverBytes = serverSession.exportKeyMaterialData("hello", + serverBytes = serverSession.exportKeyingMaterialData("hello", bytesDiff, 128); assertNotEqualsByteArray(clientBytes, serverBytes, "Different context but exporters same"); log("Different context test passed"); // Different context sizes - clientBytes = clientSession.exportKeyMaterialData("hello", + clientBytes = clientSession.exportKeyingMaterialData("hello", bytes, 128); - serverBytes = serverSession.exportKeyMaterialData("hello", + serverBytes = serverSession.exportKeyingMaterialData("hello", bytesDiffSize, 128); assertNotEqualsByteArray(clientBytes, serverBytes, "Different context sizes but exporters same"); log("Different context sizes test passed"); // No context, but otherwise the same - clientBytes = clientSession.exportKeyMaterialData("hello", + clientBytes = clientSession.exportKeyingMaterialData("hello", null, 128); - serverBytes = serverSession.exportKeyMaterialData("hello", + serverBytes = serverSession.exportKeyingMaterialData("hello", null, 128); assertEqualsByteArray(clientBytes, serverBytes, "No context and exporters are not the same"); log("No context test passed"); // Smaller key size - clientBytes = clientSession.exportKeyMaterialData("hello", + clientBytes = clientSession.exportKeyingMaterialData("hello", bytes, 40); - serverBytes = serverSession.exportKeyMaterialData("hello", + serverBytes = serverSession.exportKeyingMaterialData("hello", bytes, 40); assertEqualsByteArray(clientBytes, serverBytes, "Smaller key size should be the same"); @@ -372,14 +372,15 @@ private static void runExporterTests( // Check error conditions try { - clientSession.exportKeyMaterialData(null, bytes, 128); + clientSession.exportKeyingMaterialData(null, bytes, 128); throw new Exception("null label accepted"); } catch (NullPointerException e) { log("null label test passed"); } try { - clientSession.exportKeyMaterialData("hello", new byte[1<<16], 128); + clientSession.exportKeyingMaterialData("hello", + new byte[1<<16], 128); if (!clientSession.getProtocol().equals("TLSv1.3")) { throw new Exception("large context accepted in " + "SSLv3/TLSv1/TLSv1.1/TLSv1.2"); @@ -392,7 +393,7 @@ private static void runExporterTests( } try { - clientSession.exportKeyMaterialData("hello", bytes, -20); + clientSession.exportKeyingMaterialData("hello", bytes, -20); throw new Exception("negative length accepted"); } catch (IllegalArgumentException e) { log("negative length test passed"); From 92f45f7fd168a593e25d819eef710ca223c58cc1 Mon Sep 17 00:00:00 2001 From: Bradford Wetmore Date: Tue, 6 May 2025 22:04:10 -0700 Subject: [PATCH 06/27] Add in the SharedSecrets SecretKeySpec clearing mechanism --- .../sun/security/ssl/SSLSessionImpl.java | 54 +++++++++++-------- 1 file changed, 31 insertions(+), 23 deletions(-) diff --git a/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java b/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java index 4fa7ba5eadb0f..4836a98ba1e51 100644 --- a/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java +++ b/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java @@ -24,6 +24,7 @@ */ package sun.security.ssl; +import jdk.internal.access.SharedSecrets; import sun.security.provider.X509Factory; import java.io.IOException; @@ -1670,32 +1671,39 @@ public SecretKey exportKeyingMaterialKey( // derivedSecret that is used as the Secret in the "outer" // HKDF-Expand-Label(). SecretKey derivedSecret = hkdf.expand(exporterMasterSecret, - hkdfInfo, hashAlg.hashLength, "DerivedSecret"); - - // Now do the "outer" HKDF-Expand-Label. - // HKDF-Expand-Label(derivedSecret, "exporter", - // Hash(context_value), key_length) - - // If a context was supplied, use it, otherwise, use the - // previous hashed value of ""... - byte[] hash = ((context.length > 0) ? - md.digest(context): emptyHash); - - // ...now the hkdfInfo... - hkdfInfo = SSLSecretDerivation.createHkdfInfo(("tls13 " + - "exporter").getBytes(StandardCharsets.UTF_8), hash, - length); - - // ...now the final expand. - SecretKey key = hkdf.expand(derivedSecret, hkdfInfo, length, - label); + hkdfInfo, hashAlg.hashLength, "DerivedSecret");; try { + // Now do the "outer" HKDF-Expand-Label. + // HKDF-Expand-Label(derivedSecret, "exporter", + // Hash(context_value), key_length) + + // If a context was supplied, use it, otherwise, use the + // previous hashed value of ""... + byte[] hash = ((context.length > 0) ? + md.digest(context) : emptyHash); + + // ...now the hkdfInfo... + hkdfInfo = SSLSecretDerivation.createHkdfInfo( + ("tls13 exporter").getBytes(StandardCharsets.UTF_8), + hash, length); + + // ...now the final expand. + SecretKey key = hkdf.expand(derivedSecret, hkdfInfo, length, + label); + return key; + } finally { // Best effort - derivedSecret.destroy(); - } catch (DestroyFailedException e) { - // swallow + if (derivedSecret instanceof SecretKeySpec s) { + SharedSecrets.getJavaxCryptoSpecAccess() + .clearSecretKeySpec(s); + } else { + try { + derivedSecret.destroy(); + } catch (DestroyFailedException e) { + // swallow + } + } } - return key; } catch (Exception e) { // For whatever reason, we couldn't generate. Wrap and return. throw new SSLKeyException("Couldn't generate Exporter/HKDF", e); From c6baa83b0b27a041dcd0518cd0bd6e2f8e056454 Mon Sep 17 00:00:00 2001 From: Bradford Wetmore Date: Tue, 6 May 2025 22:43:50 -0700 Subject: [PATCH 07/27] Updated to use the upcoming KDF (still in preview) + bits of JDK-8353578 for compilation) --- .../classes/sun/security/ssl/CipherSuite.java | 4 +++- .../classes/sun/security/ssl/SSLSessionImpl.java | 14 +++++++++----- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/java.base/share/classes/sun/security/ssl/CipherSuite.java b/src/java.base/share/classes/sun/security/ssl/CipherSuite.java index 019f1ad34d925..4a2063ded4238 100644 --- a/src/java.base/share/classes/sun/security/ssl/CipherSuite.java +++ b/src/java.base/share/classes/sun/security/ssl/CipherSuite.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2002, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2002, 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 @@ -1199,11 +1199,13 @@ enum HashAlg { final String name; final int hashLength; final int blockSize; + final String hkdfAlgorithm; HashAlg(String hashAlg, int hashLength, int blockSize) { this.name = hashAlg; this.hashLength = hashLength; this.blockSize = blockSize; + this.hkdfAlgorithm = "HKDF-" + hashAlg.replace("-", ""); } @Override diff --git a/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java b/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java index 4836a98ba1e51..1ce6eb49bfbcf 100644 --- a/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java +++ b/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java @@ -43,8 +43,10 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.locks.ReentrantLock; +import javax.crypto.KDF; import javax.crypto.KeyGenerator; import javax.crypto.SecretKey; +import javax.crypto.spec.HKDFParameterSpec; import javax.crypto.spec.SecretKeySpec; import javax.net.ssl.*; import javax.security.auth.DestroyFailedException; @@ -1646,7 +1648,7 @@ public SecretKey exportKeyingMaterialKey( try { // Use the ciphersuite's hashAlg for these calcs. HashAlg hashAlg = cipherSuite.hashAlg; - HKDF hkdf = new HKDF(hashAlg.name); + KDF hkdf = KDF.getInstance(hashAlg.hkdfAlgorithm); // First calculate the inner Derive-Secret(Secret, label, "") MessageDigest md; @@ -1670,8 +1672,9 @@ public SecretKey exportKeyingMaterialKey( // ...then the "inner" HKDF-Expand-Label() to get the // derivedSecret that is used as the Secret in the "outer" // HKDF-Expand-Label(). - SecretKey derivedSecret = hkdf.expand(exporterMasterSecret, - hkdfInfo, hashAlg.hashLength, "DerivedSecret");; + SecretKey derivedSecret = hkdf.deriveKey("DerivedSecret", + HKDFParameterSpec.expandOnly(exporterMasterSecret, + hkdfInfo, hashAlg.hashLength)); try { // Now do the "outer" HKDF-Expand-Label. // HKDF-Expand-Label(derivedSecret, "exporter", @@ -1688,8 +1691,9 @@ public SecretKey exportKeyingMaterialKey( hash, length); // ...now the final expand. - SecretKey key = hkdf.expand(derivedSecret, hkdfInfo, length, - label); + SecretKey key = hkdf.deriveKey(label, + HKDFParameterSpec.expandOnly(derivedSecret, + hkdfInfo, length)); return key; } finally { // Best effort From 2e5f5342c3d4abcda48db20dd712d78b31ea9e30 Mon Sep 17 00:00:00 2001 From: Bradford Wetmore Date: Wed, 7 May 2025 14:20:57 -0700 Subject: [PATCH 08/27] More Codereview comments --- .../share/classes/javax/net/ssl/ExtendedSSLSession.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/java.base/share/classes/javax/net/ssl/ExtendedSSLSession.java b/src/java.base/share/classes/javax/net/ssl/ExtendedSSLSession.java index 7d024d248ca10..c5edf8f621383 100644 --- a/src/java.base/share/classes/javax/net/ssl/ExtendedSSLSession.java +++ b/src/java.base/share/classes/javax/net/ssl/ExtendedSSLSession.java @@ -166,7 +166,7 @@ public List getStatusResponses() { } /** - * Generate Exported Key Material (EKM) calculated according to the + * Generate Exported Keying Material (EKM) calculated according to the * algorithms defined in RFCs 5705/8446. *

* Note RFC 5705 (for (D)TLSv1.2 and earlier) calculates different EKM @@ -211,7 +211,7 @@ public SecretKey exportKeyingMaterialKey( } /** - * Generate Exported Key Material (EKM) calculated according to the + * Generate Exported Keying Material (EKM) calculated according to the * algorithms defined in RFCs 5705/8446. *

* Note RFC 5705 (for (D)TLSv1.2 and earlier) calculates different EKM From 598fc5792f03304edd20d20b77726218c3863090 Mon Sep 17 00:00:00 2001 From: Bradford Wetmore Date: Thu, 8 May 2025 22:04:26 -0700 Subject: [PATCH 09/27] Rework to avoid PKCS11 data extraction problems, and enhanced input verification and unit testing --- .../javax/net/ssl/ExtendedSSLSession.java | 22 +- .../sun/security/ssl/SSLSessionImpl.java | 101 +++++--- .../ExportKeyingMaterialTests.java | 220 ++++++++++++++---- 3 files changed, 256 insertions(+), 87 deletions(-) diff --git a/src/java.base/share/classes/javax/net/ssl/ExtendedSSLSession.java b/src/java.base/share/classes/javax/net/ssl/ExtendedSSLSession.java index c5edf8f621383..d2f1f13ba6772 100644 --- a/src/java.base/share/classes/javax/net/ssl/ExtendedSSLSession.java +++ b/src/java.base/share/classes/javax/net/ssl/ExtendedSSLSession.java @@ -193,9 +193,9 @@ public List getStatusResponses() { * @param length the number of bytes of EKM material needed * * @throws SSLKeyException if the key could not be generated - * @throws IllegalArgumentException if {@code length} is negative - * or the {@code context} length is larger than can be - * accommodated + * @throws IllegalArgumentException if {@code length} is non-positive, + * or if the {@code label} or {@code context} length can + * not be accommodated * @throws NullPointerException if {@code label} is null * @throws UnsupportedOperationException if the underlying provider * does not implement the operation @@ -236,23 +236,23 @@ public SecretKey exportKeyingMaterialKey( * @implSpec The default implementation throws * {@code UnsupportedOperationException} * - * @param label the label bytes used in the EKM calculation + * @param label the label bytes used in the EKM calculation. * {@code label} will be converted to a {@code byte[]} * before the operation begins * @param context the context bytes used in the EKM calculation * @param length the number of bytes of EKM material needed * * @throws SSLKeyException if the key could not be generated - * @throws IllegalArgumentException if {@code length} is negative - * or the {@code context} length is larger than can be - * accommodated + * @throws IllegalArgumentException if {@code length} is non-positive, + * or if the {@code label} or {@code context} length can + * not be accommodated * @throws NullPointerException if {@code label} is null * @throws UnsupportedOperationException if the underlying provider - * does not implement the operation, or if the derived key - * material is not extractable. + * does not implement the operation * - * @return a byte buffer of size {@code length} that contains the EKM - * material + * @return a byte array of size {@code length} that contains the EKM + * material, or null if the derived key material does not support + * encoding * @since 25 */ public byte[] exportKeyingMaterialData( diff --git a/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java b/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java index 1ce6eb49bfbcf..4fa2cebeb9563 100644 --- a/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java +++ b/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java @@ -1605,24 +1605,59 @@ public List getRequestedServerNames() { return requestedServerNames; } - /** - * Generate Exported Key Material (EKM) calculated according to the - * algorithms defined in RFCs 5705/8446. + /* + * deriveKey is used for switching between Keys/Data. Will redo + * if we ever introduce additional types. */ - @Override - public SecretKey exportKeyingMaterialKey( - String label, byte[] context, int length) throws SSLKeyException { + public Object exportKeyingMaterial( + boolean deriveKey, String label, byte[] context, int length) + throws SSLKeyException { // Global preconditions + Objects.requireNonNull(label, "label can not be null"); - if (length < 0) { + if (length < 1) { throw new IllegalArgumentException( - "Output length can not be negative"); + "length must be positive"); } // Calculations are primarily based on protocol version. switch (protocolVersion) { case TLS13: // HKDF-based + + // Check the label/context lengths: + // struct { + // uint16 length = Length; + // opaque label<7..255> = "tls13 " + Label; + // opaque context<0..255> = Context; + // } HkdfLabel; + // label can have 249 bytes (+6 for "tls13 "), and context 255 + + // RFC 8446 allows for length of 2^16-1 (65536), but RFC 5869 + // states: + // + // L length of output keying material in octets + // (<= 255*HashLen) + if (length >= (255 * cipherSuite.hashAlg.hashLength )) { + throw new IllegalArgumentException( + "length is too large"); + } + + byte[] hkdfInfoLabel = + ("tls13 " + label).getBytes(StandardCharsets.UTF_8); + if ((hkdfInfoLabel.length < 7) || hkdfInfoLabel.length > 255) { + throw new IllegalArgumentException( + "label length outside range"); + } + + // If no context (null) is provided, RFC 8446 requires an empty + // context be used, unlike RFC 5705. + context = (context != null ? context : new byte[0]); + if (context.length > 255) { + throw new IllegalArgumentException( + "context length outside range"); + } + // Unlikely, but check anyway. if (exporterMasterSecret == null) { throw new RuntimeException( @@ -1641,10 +1676,6 @@ public SecretKey exportKeyingMaterialKey( * Transcript-Hash(Messages), Hash.length) */ - // If no context (null) is provided, RFC 8446 requires an empty - // context be used, unlike RFC 5705. - context = (context != null ? context : new byte[0]); - try { // Use the ciphersuite's hashAlg for these calcs. HashAlg hashAlg = cipherSuite.hashAlg; @@ -1666,8 +1697,7 @@ public SecretKey exportKeyingMaterialKey( // ...then the hkdfInfo... byte[] hkdfInfo = SSLSecretDerivation.createHkdfInfo( - ("tls13 " + label).getBytes(StandardCharsets.UTF_8), - emptyHash, hashAlg.hashLength); + hkdfInfoLabel, emptyHash, hashAlg.hashLength); // ...then the "inner" HKDF-Expand-Label() to get the // derivedSecret that is used as the Secret in the "outer" @@ -1691,12 +1721,15 @@ public SecretKey exportKeyingMaterialKey( hash, length); // ...now the final expand. - SecretKey key = hkdf.deriveKey(label, - HKDFParameterSpec.expandOnly(derivedSecret, - hkdfInfo, length)); - return key; + return (deriveKey ? + hkdf.deriveKey("ExportKeyingMaterial", + HKDFParameterSpec.expandOnly(derivedSecret, + hkdfInfo, length)) : + hkdf.deriveData( + HKDFParameterSpec.expandOnly(derivedSecret, + hkdfInfo, length))); } finally { - // Best effort + // Best effort to clear the intermediate SecretKey. if (derivedSecret instanceof SecretKeySpec s) { SharedSecrets.getJavaxCryptoSpecAccess() .clearSecretKeySpec(s); @@ -1730,13 +1763,20 @@ public SecretKey exportKeyingMaterialKey( "Exporters require extended master secrets"); } + // Check for a "disambiguating label string" (i.e. non-empty). + // Don't see a max length restriction. + if (label.isEmpty()) { + throw new IllegalArgumentException( + "label length outside range"); + } + // Unlikely, but check if randoms were not captured. if (clientRandom == null || serverRandom == null) { throw new RuntimeException("Random nonces not captured"); } // context length must fit in 2 unsigned bytes. - if ((context != null) && context.length >= (1 << 16)) { + if ((context != null) && context.length >= 65536) { throw new IllegalArgumentException( "Only 16-bit context lengths supported"); } @@ -1783,7 +1823,8 @@ public SecretKey exportKeyingMaterialKey( hashAlg.name, hashAlg.hashLength, hashAlg.blockSize); KeyGenerator kg = KeyGenerator.getInstance(prfAlg); kg.init(spec); - return kg.generateKey(); + SecretKey key = kg.generateKey(); + return (deriveKey ? key : key.getEncoded()); } catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException e) { throw new SSLKeyException("Could not generate Exporter/PRF", e); @@ -1797,6 +1838,16 @@ public SecretKey exportKeyingMaterialKey( } } + /** + * Generate Exported Key Material (EKM) calculated according to the + * algorithms defined in RFCs 5705/8446. + */ + @Override + public SecretKey exportKeyingMaterialKey( + String label, byte[] context, int length) throws SSLKeyException { + return (SecretKey)exportKeyingMaterial(true, label, context, length); + } + /** * Generate Exported Key Material (EKM) calculated according to the * algorithms defined in RFCs 5705/8446. @@ -1804,13 +1855,7 @@ public SecretKey exportKeyingMaterialKey( @Override public byte[] exportKeyingMaterialData( String label, byte[] context, int length) throws SSLKeyException { - byte[] bytes = - exportKeyingMaterialKey(label, context, length).getEncoded(); - if (bytes == null) { - throw new UnsupportedOperationException( - "Exported key material is not extractable"); - } - return bytes; + return (byte[])exportKeyingMaterial(false, label, context, length); } /** Returns a string representation of this SSL session */ diff --git a/test/jdk/javax/net/ssl/ExtendedSSLSession/ExportKeyingMaterialTests.java b/test/jdk/javax/net/ssl/ExtendedSSLSession/ExportKeyingMaterialTests.java index cfbf86f049c47..2f7ee9270dec1 100644 --- a/test/jdk/javax/net/ssl/ExtendedSSLSession/ExportKeyingMaterialTests.java +++ b/test/jdk/javax/net/ssl/ExtendedSSLSession/ExportKeyingMaterialTests.java @@ -35,6 +35,7 @@ import java.security.Security; import java.util.Arrays; +import javax.crypto.SecretKey; import javax.net.ssl.*; import javax.net.ssl.SSLEngineResult.HandshakeStatus; import java.nio.ByteBuffer; @@ -278,6 +279,8 @@ private static void runExporterTests( ExtendedSSLSession clientSession, ExtendedSSLSession serverSession) throws Exception { + SecretKey clientKey, serverKey; + // Create output arrays byte[] clientBytes, serverBytes; @@ -293,37 +296,53 @@ private static void runExporterTests( byte[] bytesDiffSize = new byte[21]; random.nextBytes(bytesDiffSize); - // Inputs exactly equal. Use exportKeyMaterialKey() - clientBytes = clientSession.exportKeyingMaterialKey("hello", - bytes, 128).getEncoded(); - serverBytes = serverSession.exportKeyingMaterialKey("hello", - bytes, 128).getEncoded(); - assertEqualsByteArray(clientBytes, serverBytes, - "Equal inputs but exporters are not equal"); - log("Equal inputs test passed"); - - // Empty label. I don't see anything that says this is - // forbidden. There is some verbiage about: labels being registered - // with IANA, must not collide with existing PRF labels, SHOULD use - // "EXPORTER"/"EXPERIMENTAL" prefixes, etc. - clientBytes = clientSession.exportKeyingMaterialKey("", - bytes, 128).getEncoded(); - serverBytes = serverSession.exportKeyingMaterialKey("", - bytes, 128).getEncoded(); + // Run a bunch of similar derivations using both the Key/Data methods, + // exercising the various valid/invalid combinations. + + // We may need to adjust if it turns out that this is run with + // non-extractable keys if .equals() doesn't work. + + // Inputs exactly equal. + clientKey = clientSession.exportKeyingMaterialKey("hello", bytes, 128); + serverKey = serverSession.exportKeyingMaterialKey("hello", bytes, 128); + assertEquals(clientKey, serverKey, + "Key: Equal inputs but exporters are not equal"); + log("Key: Equal inputs test passed"); + + clientBytes = clientSession.exportKeyingMaterialData("hello", + bytes, 128); + serverBytes = serverSession.exportKeyingMaterialData("hello", + bytes, 128); assertEqualsByteArray(clientBytes, serverBytes, - "Empty label and exporters are equal"); - log("Empty label test passed"); + "Data: Equal inputs but exporters are not equal"); + log("Data: Equal inputs test passed"); // Different labels, now use exportKeyMaterialData() for coverage + clientKey = clientSession.exportKeyingMaterialKey("hello", + bytes, 128); + serverKey = serverSession.exportKeyingMaterialKey("goodbye", + bytes, 128); + assertNotEquals(clientKey, serverKey, + "Key: Different labels but exporters same"); + log("Key: Different labels test passed"); + clientBytes = clientSession.exportKeyingMaterialData("hello", bytes, 128); serverBytes = serverSession.exportKeyingMaterialData("goodbye", bytes, 128); assertNotEqualsByteArray(clientBytes, serverBytes, - "Different labels but exporters same"); - log("Different labels test passed"); + "Data: Different labels but exporters same"); + log("Data: Different labels test passed"); // Different output sizes + clientKey = clientSession.exportKeyingMaterialKey("hello", + bytes, 128); + serverKey = serverSession.exportKeyingMaterialKey("hello", + bytes, 127); + assertNotEquals(clientKey, serverKey, + "Key: Different output sizes but exporters same"); + log("Key: Different output size test passed"); + clientBytes = clientSession.exportKeyingMaterialData("hello", bytes, 128); serverBytes = serverSession.exportKeyingMaterialData("hello", @@ -331,46 +350,93 @@ private static void runExporterTests( assertEquals(clientBytes.length, 128, "client length != 128"); assertEquals(serverBytes.length, 127, "server length != 127"); assertNotEqualsByteArray(clientBytes, serverBytes, - "Different output sizes but exporters same"); - log("Different output size test passed"); + "Data: Different output sizes but exporters same"); + log("Data: Different output size test passed"); // Different context values + clientKey = clientSession.exportKeyingMaterialKey("hello", + bytes, 128); + serverKey = serverSession.exportKeyingMaterialKey("hello", + bytesDiff, 128); + assertNotEquals(clientKey, serverKey, + "Key: Different context but exporters same"); + log("Key: Different context test passed"); + clientBytes = clientSession.exportKeyingMaterialData("hello", bytes, 128); serverBytes = serverSession.exportKeyingMaterialData("hello", bytesDiff, 128); assertNotEqualsByteArray(clientBytes, serverBytes, - "Different context but exporters same"); - log("Different context test passed"); + "Data: Different context but exporters same"); + log("Data: Different context test passed"); // Different context sizes + clientKey = clientSession.exportKeyingMaterialKey("hello", + bytes, 128); + serverKey = serverSession.exportKeyingMaterialKey("hello", + bytesDiffSize, 128); + assertNotEquals(clientKey, serverKey, + "Key: Different context sizes but exporters same"); + log("Key: Different context sizes test passed"); + clientBytes = clientSession.exportKeyingMaterialData("hello", bytes, 128); serverBytes = serverSession.exportKeyingMaterialData("hello", bytesDiffSize, 128); assertNotEqualsByteArray(clientBytes, serverBytes, - "Different context sizes but exporters same"); - log("Different context sizes test passed"); + "Data: Different context sizes but exporters same"); + log("Data: Different context sizes test passed"); // No context, but otherwise the same + clientKey = clientSession.exportKeyingMaterialKey("hello", + null, 128); + serverKey = serverSession.exportKeyingMaterialKey("hello", + null, 128); + assertEquals(clientKey, serverKey, + "Key: No context and exporters are not the same"); + log("Key: No context test passed"); + clientBytes = clientSession.exportKeyingMaterialData("hello", null, 128); serverBytes = serverSession.exportKeyingMaterialData("hello", null, 128); assertEqualsByteArray(clientBytes, serverBytes, - "No context and exporters are not the same"); - log("No context test passed"); + "Data: No context and exporters are not the same"); + log("Data: No context test passed"); // Smaller key size + clientKey = clientSession.exportKeyingMaterialKey("hello", + bytes, 1); + serverKey = serverSession.exportKeyingMaterialKey("hello", + bytes, 1); + assertEquals(clientKey, serverKey, + "Key: Smaller key size should be the same"); + log("Key: Smaller key size test passed"); + clientBytes = clientSession.exportKeyingMaterialData("hello", - bytes, 40); + bytes, 1); serverBytes = serverSession.exportKeyingMaterialData("hello", - bytes, 40); + bytes, 1); assertEqualsByteArray(clientBytes, serverBytes, - "Smaller key size should be the same"); - log("Smaller key size test passed"); + "Data: Smaller key size should be the same"); + log("Data: Smaller key size test passed"); // Check error conditions + + try { + clientSession.exportKeyingMaterialData("hello", bytes, -1); + throw new Exception("negative length accepted"); + } catch (IllegalArgumentException e) { + log("negative length test passed"); + } + + try { + clientSession.exportKeyingMaterialData("hello", bytes, 0); + throw new Exception("zero length accepted"); + } catch (IllegalArgumentException e) { + log("zero length test passed"); + } + try { clientSession.exportKeyingMaterialData(null, bytes, 128); throw new Exception("null label accepted"); @@ -379,25 +445,83 @@ private static void runExporterTests( } try { - clientSession.exportKeyingMaterialData("hello", - new byte[1<<16], 128); - if (!clientSession.getProtocol().equals("TLSv1.3")) { - throw new Exception("large context accepted in " + - "SSLv3/TLSv1/TLSv1.1/TLSv1.2"); - } else { - log("large context test passed in TLSv1.3"); - } + clientSession.exportKeyingMaterialData("", bytes, 128); + throw new Exception("empty label accepted"); } catch (IllegalArgumentException e) { - log("large context test passed in " + - "SSLv3/TLSv1/TLSv1.1/TLSv1.2"); + log("empty label test passed"); } - try { - clientSession.exportKeyingMaterialData("hello", bytes, -20); - throw new Exception("negative length accepted"); - } catch (IllegalArgumentException e) { - log("negative length test passed"); + switch (clientSession.getProtocol()) { + + case "TLSv1.3": + // 249 bytes is the max label we can accept (<7..255>, since + // "tls13 " is added in HkdfLabel) + String longString = + "12345678901234567890123456789012345678901234567890" + + "12345678901234567890123456789012345678901234567890" + + "12345678901234567890123456789012345678901234567890" + + "12345678901234567890123456789012345678901234567890" + + "1234567890123456789012345678901234567890123456789"; + + clientSession.exportKeyingMaterialData(longString, bytes, 128); + log("large label test passed in TLSv1.3"); + + try { + clientSession.exportKeyingMaterialData( + longString + "0", bytes, 128); + throw new Exception("too large label accepted in TLSv1.3"); + } catch (IllegalArgumentException e) { + log("too large label test passed in TLSv1.3"); + } + + // 255 bytes is the max context we can accept (<0..255>) + clientSession.exportKeyingMaterialData( + longString, new byte[255], 128); + log("large context test passed in TLSv1.3"); + + try { + clientSession.exportKeyingMaterialData( + longString, new byte[256], 128); + throw new Exception("too large context accepted in TLSv1.3"); + } catch (IllegalArgumentException e) { + log("too large context test passed in TLSv1.3"); + } + + // RFC 5869 says 255*HashLen bytes is the max length we can accept. + // So we'll choose something a bit bigger than the largest + // hashLen/ciphertext which is 384 (48 bytes) so this will always + // fail. + try { + clientSession.exportKeyingMaterialData( + longString, new byte[256], 12240); + throw new Exception("too large length accepted in TLSv1.3"); + } catch (IllegalArgumentException e) { + log("too large length test passed in TLSv1.3"); + } + + break; + + case "TLSv1": + case "TLSv1.1": + case "TLSv1.2": + // Don't see a limit of the label.length or output length. + + // Check for large context.length + try { + clientSession.exportKeyingMaterialData("hello", + new byte[1 << 16], 128); + throw new Exception("large context accepted in " + + "TLSv1/TLSv1.1/TLSv1.2"); + } catch (IllegalArgumentException e) { + log("large context passed in TLSv1/TLSv1.1/TLSv1.2"); + } + + break; + + default: + throw new RuntimeException("Unknown protocol: " + clientSession.getProtocol()); } + } static boolean isOpen(SSLEngine engine) { From 4d9a3a04d6a003d0be026d6b9f6e2a13ce45be1b Mon Sep 17 00:00:00 2001 From: Bradford Wetmore Date: Fri, 9 May 2025 14:32:35 -0700 Subject: [PATCH 10/27] Adjustments made for JDK-8350830 --- .../sun/security/ssl/SSLSessionImpl.java | 25 ++++++------------- 1 file changed, 7 insertions(+), 18 deletions(-) diff --git a/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java b/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java index 6c5eae53dcbc7..ad8067ebbfb88 100644 --- a/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java +++ b/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java @@ -501,17 +501,10 @@ final class SSLSessionImpl extends ExtendedSSLSession { } // Exporter master secret length of secret key algorithm (one byte) - i = buf.get(); - if (i > 0) { - b = new byte[i]; - // Get algorithm string - buf.get(b, 0, i); + b = Record.getBytes8(buf); + if (b.length > 0) { String algName = new String(b); - // Encoded length - i = Short.toUnsignedInt(buf.getShort()); - // Encoded SecretKey - b = new byte[i]; - buf.get(b); + b = Record.getBytes16(buf); this.exporterMasterSecret = new SecretKeySpec(b, algName); } else { // TLSv1.2- @@ -519,10 +512,8 @@ final class SSLSessionImpl extends ExtendedSSLSession { } // Get clientRandom - i = Byte.toUnsignedInt(buf.get()); - if (i > 0) { - b = new byte[i]; - buf.get(b, 0, i); + b = Record.getBytes8(buf); + if (b.length > 0) { this.clientRandom = new RandomCookie(ByteBuffer.wrap(b)); } else { // TLSv1.3+ @@ -530,10 +521,8 @@ final class SSLSessionImpl extends ExtendedSSLSession { } // Get serverRandom - i = Byte.toUnsignedInt(buf.get()); - if (i > 0) { - b = new byte[i]; - buf.get(b, 0, i); + b = Record.getBytes8(buf); + if (b.length > 0) { this.serverRandom = new RandomCookie(ByteBuffer.wrap(b)); } else { // TLSv1.3+ From b21a42d05c9fcdebaf6e0ffe6c380d9080c34240 Mon Sep 17 00:00:00 2001 From: Bradford Wetmore Date: Mon, 12 May 2025 22:14:50 -0700 Subject: [PATCH 11/27] More codereview comments --- .../javax/net/ssl/ExtendedSSLSession.java | 18 +++++++++++------- .../sun/security/ssl/SSLSessionImpl.java | 4 ++-- .../security/pkcs11/P11SecretKeyFactory.java | 2 ++ 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/java.base/share/classes/javax/net/ssl/ExtendedSSLSession.java b/src/java.base/share/classes/javax/net/ssl/ExtendedSSLSession.java index d2f1f13ba6772..d21fda297ec0a 100644 --- a/src/java.base/share/classes/javax/net/ssl/ExtendedSSLSession.java +++ b/src/java.base/share/classes/javax/net/ssl/ExtendedSSLSession.java @@ -169,7 +169,7 @@ public List getStatusResponses() { * Generate Exported Keying Material (EKM) calculated according to the * algorithms defined in RFCs 5705/8446. *

- * Note RFC 5705 (for (D)TLSv1.2 and earlier) calculates different EKM + * RFC 5705 (for (D)TLSv1.2 and earlier) calculates different EKM * values depending on whether {@code context} is null or non-null/empty. * RFC 8446 (TLSv1.3) treats a null context as non-null/empty. *

@@ -184,7 +184,9 @@ public List getStatusResponses() { * RFC 8446: The Transport Layer Security (TLS) Protocol Version 1.3 * * @implSpec The default implementation throws - * {@code UnsupportedOperationException} + * {@code UnsupportedOperationException}. + * Classes derived from ExtendedSSLSession must implement + * this method. * * @param label the label bytes used in the EKM calculation. * {@code label} will be converted to a {@code byte[]} @@ -192,7 +194,7 @@ public List getStatusResponses() { * @param context the context bytes used in the EKM calculation * @param length the number of bytes of EKM material needed * - * @throws SSLKeyException if the key could not be generated + * @throws SSLKeyException if the key cannot be generated * @throws IllegalArgumentException if {@code length} is non-positive, * or if the {@code label} or {@code context} length can * not be accommodated @@ -201,7 +203,7 @@ public List getStatusResponses() { * does not implement the operation * * @return a {@code SecretKey} that contains {@code length} bytes of the - * EKM material. + * EKM material * * @since 25 */ @@ -214,7 +216,7 @@ public SecretKey exportKeyingMaterialKey( * Generate Exported Keying Material (EKM) calculated according to the * algorithms defined in RFCs 5705/8446. *

- * Note RFC 5705 (for (D)TLSv1.2 and earlier) calculates different EKM + * RFC 5705 (for (D)TLSv1.2 and earlier) calculates different EKM * values depending on whether {@code context} is null or non-null/empty. * RFC 8446 (TLSv1.3) treats a null context as non-null/empty. *

@@ -234,7 +236,9 @@ public SecretKey exportKeyingMaterialKey( * RFC 8446: The Transport Layer Security (TLS) Protocol Version 1.3 * * @implSpec The default implementation throws - * {@code UnsupportedOperationException} + * {@code UnsupportedOperationException}. + * Classes derived from ExtendedSSLSession must implement + * this method. * * @param label the label bytes used in the EKM calculation. * {@code label} will be converted to a {@code byte[]} @@ -242,7 +246,7 @@ public SecretKey exportKeyingMaterialKey( * @param context the context bytes used in the EKM calculation * @param length the number of bytes of EKM material needed * - * @throws SSLKeyException if the key could not be generated + * @throws SSLKeyException if the key cannot be generated * @throws IllegalArgumentException if {@code length} is non-positive, * or if the {@code label} or {@code context} length can * not be accommodated diff --git a/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java b/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java index ad8067ebbfb88..0a98387b205bc 100644 --- a/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java +++ b/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java @@ -1659,7 +1659,7 @@ public Object exportKeyingMaterial( // ...then the "inner" HKDF-Expand-Label() to get the // derivedSecret that is used as the Secret in the "outer" // HKDF-Expand-Label(). - SecretKey derivedSecret = hkdf.deriveKey("DerivedSecret", + SecretKey derivedSecret = hkdf.deriveKey("TlsKey", HKDFParameterSpec.expandOnly(exporterMasterSecret, hkdfInfo, hashAlg.hashLength)); try { @@ -1679,7 +1679,7 @@ public Object exportKeyingMaterial( // ...now the final expand. return (deriveKey ? - hkdf.deriveKey("ExportKeyingMaterial", + hkdf.deriveKey("TlsExporterKeyingMaterial", HKDFParameterSpec.expandOnly(derivedSecret, hkdfInfo, length)) : hkdf.deriveData( diff --git a/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11SecretKeyFactory.java b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11SecretKeyFactory.java index 3e32b0c7c2daf..11d3ae7081953 100644 --- a/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11SecretKeyFactory.java +++ b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11SecretKeyFactory.java @@ -276,6 +276,8 @@ static final class P12MacPBEKeyInfo extends PBEKeyInfo { putKeyInfo(new TLSKeyInfo("TlsClientAppTrafficSecret")); putKeyInfo(new TLSKeyInfo("TlsClientHandshakeTrafficSecret")); putKeyInfo(new TLSKeyInfo("TlsEarlySecret")); + putKeyInfo(new TLSKeyInfo("TlsExporterMasterSecret")); + putKeyInfo(new TLSKeyInfo("TlsExporterKeyingMaterial")); putKeyInfo(new TLSKeyInfo("TlsFinishedSecret")); putKeyInfo(new TLSKeyInfo("TlsHandshakeSecret")); putKeyInfo(new TLSKeyInfo("TlsKey")); From 0da8f6f8e8bcd66926ea56158d5147c5cdce5c8a Mon Sep 17 00:00:00 2001 From: Bradford Wetmore Date: Mon, 12 May 2025 22:29:02 -0700 Subject: [PATCH 12/27] Missed one review comment --- .../share/classes/javax/net/ssl/ExtendedSSLSession.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/java.base/share/classes/javax/net/ssl/ExtendedSSLSession.java b/src/java.base/share/classes/javax/net/ssl/ExtendedSSLSession.java index d21fda297ec0a..a6c94df8007d5 100644 --- a/src/java.base/share/classes/javax/net/ssl/ExtendedSSLSession.java +++ b/src/java.base/share/classes/javax/net/ssl/ExtendedSSLSession.java @@ -191,7 +191,7 @@ public List getStatusResponses() { * @param label the label bytes used in the EKM calculation. * {@code label} will be converted to a {@code byte[]} * before the operation begins - * @param context the context bytes used in the EKM calculation + * @param context the context bytes used in the EKM calculation, or null * @param length the number of bytes of EKM material needed * * @throws SSLKeyException if the key cannot be generated @@ -243,7 +243,7 @@ public SecretKey exportKeyingMaterialKey( * @param label the label bytes used in the EKM calculation. * {@code label} will be converted to a {@code byte[]} * before the operation begins - * @param context the context bytes used in the EKM calculation + * @param context the context bytes used in the EKM calculation, or null * @param length the number of bytes of EKM material needed * * @throws SSLKeyException if the key cannot be generated From 513c80326dca9914f6327615129e8bcf45c077aa Mon Sep 17 00:00:00 2001 From: Bradford Wetmore Date: Tue, 13 May 2025 20:59:31 -0700 Subject: [PATCH 13/27] Merged with changes for JDK-8353578 --- .../share/classes/sun/security/ssl/Finished.java | 4 ++-- .../classes/sun/security/ssl/SSLSessionImpl.java | 13 ++----------- 2 files changed, 4 insertions(+), 13 deletions(-) diff --git a/src/java.base/share/classes/sun/security/ssl/Finished.java b/src/java.base/share/classes/sun/security/ssl/Finished.java index 158a689211c3d..e388672cb7ff5 100644 --- a/src/java.base/share/classes/sun/security/ssl/Finished.java +++ b/src/java.base/share/classes/sun/security/ssl/Finished.java @@ -748,7 +748,7 @@ private byte[] onProduceFinished(ClientHandshakeContext chc, // Calculate/save the exporter_master_secret. It uses // the same handshakeHash as the client/server app traffic. SecretKey exporterSecret = kd.deriveKey( - "TlsExporterMasterSecret", null); + "TlsExporterMasterSecret"); chc.handshakeSession.setExporterMasterSecret(exporterSecret); // The resumption master secret is stored in the session so @@ -1108,7 +1108,7 @@ private void onConsumeFinished(ServerHandshakeContext shc, // Calculate/save the exporter_master_secret. It uses // the same handshakeHash as the client/server app traffic. SecretKey exporterSecret = kd.deriveKey( - "TlsExporterMasterSecret", null); + "TlsExporterMasterSecret"); shc.handshakeSession.setExporterMasterSecret(exporterSecret); // The resumption master secret is stored in the session so diff --git a/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java b/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java index 0a98387b205bc..de133a8298bbd 100644 --- a/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java +++ b/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java @@ -55,6 +55,7 @@ import sun.security.internal.spec.TlsPrfParameterSpec; import static sun.security.ssl.CipherSuite.HashAlg.H_NONE; import static sun.security.ssl.ProtocolVersion.*; +import sun.security.util.KeyUtil; /** @@ -1686,17 +1687,7 @@ public Object exportKeyingMaterial( HKDFParameterSpec.expandOnly(derivedSecret, hkdfInfo, length))); } finally { - // Best effort to clear the intermediate SecretKey. - if (derivedSecret instanceof SecretKeySpec s) { - SharedSecrets.getJavaxCryptoSpecAccess() - .clearSecretKeySpec(s); - } else { - try { - derivedSecret.destroy(); - } catch (DestroyFailedException e) { - // swallow - } - } + KeyUtil.destroySecretKeys(derivedSecret); } } catch (Exception e) { // For whatever reason, we couldn't generate. Wrap and return. From e912fb66c8cb6effc4959b92a4fa7d98eeff0931 Mon Sep 17 00:00:00 2001 From: Bradford Wetmore Date: Wed, 14 May 2025 16:51:52 -0700 Subject: [PATCH 14/27] Codereview: removed serialization of of exporter Secret/randoms, and adjusted TLSv1-1.2 randoms capture locations since mastersecret could be reused --- .../sun/security/ssl/DHClientKeyExchange.java | 4 - .../security/ssl/ECDHClientKeyExchange.java | 8 -- .../security/ssl/RSAClientKeyExchange.java | 6 -- .../sun/security/ssl/SSLSessionImpl.java | 77 +------------------ .../classes/sun/security/ssl/ServerHello.java | 6 ++ 5 files changed, 10 insertions(+), 91 deletions(-) diff --git a/src/java.base/share/classes/sun/security/ssl/DHClientKeyExchange.java b/src/java.base/share/classes/sun/security/ssl/DHClientKeyExchange.java index cc2e2a9e56915..fb5d6feef556b 100644 --- a/src/java.base/share/classes/sun/security/ssl/DHClientKeyExchange.java +++ b/src/java.base/share/classes/sun/security/ssl/DHClientKeyExchange.java @@ -208,8 +208,6 @@ public byte[] produce(ConnectionContext context, SSLKeyDerivation masterKD = ke.createKeyDerivation(chc); SecretKey masterSecret = masterKD.deriveKey("MasterSecret"); chc.handshakeSession.setMasterSecret(masterSecret); - chc.handshakeSession.setRandoms( - chc.clientHelloRandom, chc.serverHelloRandom); SSLTrafficKeyDerivation kd = SSLTrafficKeyDerivation.valueOf(chc.negotiatedProtocol); @@ -305,8 +303,6 @@ public void consume(ConnectionContext context, SSLKeyDerivation masterKD = ke.createKeyDerivation(shc); SecretKey masterSecret = masterKD.deriveKey("MasterSecret"); shc.handshakeSession.setMasterSecret(masterSecret); - shc.handshakeSession.setRandoms( - shc.clientHelloRandom, shc.serverHelloRandom); SSLTrafficKeyDerivation kd = SSLTrafficKeyDerivation.valueOf(shc.negotiatedProtocol); diff --git a/src/java.base/share/classes/sun/security/ssl/ECDHClientKeyExchange.java b/src/java.base/share/classes/sun/security/ssl/ECDHClientKeyExchange.java index 8f89adf78fb3a..e1c1b1377ad25 100644 --- a/src/java.base/share/classes/sun/security/ssl/ECDHClientKeyExchange.java +++ b/src/java.base/share/classes/sun/security/ssl/ECDHClientKeyExchange.java @@ -220,8 +220,6 @@ public byte[] produce(ConnectionContext context, SSLKeyDerivation masterKD = ke.createKeyDerivation(chc); SecretKey masterSecret = masterKD.deriveKey("MasterSecret"); chc.handshakeSession.setMasterSecret(masterSecret); - chc.handshakeSession.setRandoms( - chc.clientHelloRandom, chc.serverHelloRandom); SSLTrafficKeyDerivation kd = SSLTrafficKeyDerivation.valueOf(chc.negotiatedProtocol); @@ -341,8 +339,6 @@ public void consume(ConnectionContext context, SSLKeyDerivation masterKD = ke.createKeyDerivation(shc); SecretKey masterSecret = masterKD.deriveKey("MasterSecret"); shc.handshakeSession.setMasterSecret(masterSecret); - shc.handshakeSession.setRandoms( - shc.clientHelloRandom, shc.serverHelloRandom); SSLTrafficKeyDerivation kd = SSLTrafficKeyDerivation.valueOf(shc.negotiatedProtocol); @@ -422,8 +418,6 @@ public byte[] produce(ConnectionContext context, SSLKeyDerivation masterKD = ke.createKeyDerivation(chc); SecretKey masterSecret = masterKD.deriveKey("MasterSecret"); chc.handshakeSession.setMasterSecret(masterSecret); - chc.handshakeSession.setRandoms( - chc.clientHelloRandom, chc.serverHelloRandom); SSLTrafficKeyDerivation kd = SSLTrafficKeyDerivation.valueOf(chc.negotiatedProtocol); @@ -527,8 +521,6 @@ public void consume(ConnectionContext context, SSLKeyDerivation masterKD = ke.createKeyDerivation(shc); SecretKey masterSecret = masterKD.deriveKey("MasterSecret"); shc.handshakeSession.setMasterSecret(masterSecret); - shc.handshakeSession.setRandoms( - shc.clientHelloRandom, shc.serverHelloRandom); SSLTrafficKeyDerivation kd = SSLTrafficKeyDerivation.valueOf(shc.negotiatedProtocol); diff --git a/src/java.base/share/classes/sun/security/ssl/RSAClientKeyExchange.java b/src/java.base/share/classes/sun/security/ssl/RSAClientKeyExchange.java index bd0c43aea27a7..701ba35174e8f 100644 --- a/src/java.base/share/classes/sun/security/ssl/RSAClientKeyExchange.java +++ b/src/java.base/share/classes/sun/security/ssl/RSAClientKeyExchange.java @@ -212,9 +212,6 @@ public byte[] produce(ConnectionContext context, // update the states chc.handshakeSession.setMasterSecret(masterSecret); - chc.handshakeSession.setRandoms( - chc.clientHelloRandom, chc.serverHelloRandom); - SSLTrafficKeyDerivation kd = SSLTrafficKeyDerivation.valueOf(chc.negotiatedProtocol); if (kd == null) { // unlikely @@ -302,9 +299,6 @@ public void consume(ConnectionContext context, // update the states shc.handshakeSession.setMasterSecret(masterSecret); - shc.handshakeSession.setRandoms( - shc.clientHelloRandom, shc.serverHelloRandom); - SSLTrafficKeyDerivation kd = SSLTrafficKeyDerivation.valueOf(shc.negotiatedProtocol); if (kd == null) { // unlikely diff --git a/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java b/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java index de133a8298bbd..8b4abc691e06a 100644 --- a/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java +++ b/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java @@ -106,8 +106,8 @@ final class SSLSessionImpl extends ExtendedSSLSession { private boolean useDefaultPeerSignAlgs = false; private List statusResponses; private SecretKey exporterMasterSecret; // TLSv1.3+ exporter info - private RandomCookie clientRandom, // TLSv1.2- exporter info - serverRandom; + private RandomCookie clientRandom; // TLSv1.2- exporter info + private RandomCookie serverRandom; private SecretKey resumptionMasterSecret; private SecretKey preSharedKey; private byte[] pskIdentity; @@ -246,16 +246,13 @@ final class SSLSessionImpl extends ExtendedSSLSession { this.requestedServerNames = baseSession.getRequestedServerNames(); this.masterSecret = baseSession.getMasterSecret(); this.useExtendedMasterSecret = baseSession.useExtendedMasterSecret; - this.exporterMasterSecret = baseSession.exporterMasterSecret; - this.resumptionMasterSecret = baseSession.resumptionMasterSecret; - this.clientRandom = baseSession.clientRandom; - this.serverRandom = baseSession.serverRandom; this.creationTime = baseSession.getCreationTime(); this.lastUsedTime = System.currentTimeMillis(); this.identificationProtocol = baseSession.getIdentificationProtocol(); this.localCerts = baseSession.localCerts; this.peerCerts = baseSession.peerCerts; this.statusResponses = baseSession.statusResponses; + this.exporterMasterSecret = baseSession.exporterMasterSecret; this.context = baseSession.context; this.negotiatedMaxFragLen = baseSession.negotiatedMaxFragLen; this.maximumPacketSize = baseSession.maximumPacketSize; @@ -316,14 +313,7 @@ final class SSLSessionImpl extends ExtendedSSLSession { * < length in bytes> PSK identity * Anonymous * < 1 byte > - * < 1 byte > exporterMasterSecret algorithm length (if == 0, no Key) - * < length in bytes > exporterMasterSecret algorithm - * < 2 bytes > exporterMasterSecretKey length - * < length in bytes> exporterMasterSecretKey - * < 1 byte > Length of clientRandom - * < length in bytes > clientRandom - * < 1 byte > Length of serverRandom - * < length in bytes > serverRandom + */ SSLSessionImpl(HandshakeContext hc, ByteBuffer buf) throws IOException { @@ -501,35 +491,6 @@ final class SSLSessionImpl extends ExtendedSSLSession { throw new SSLException("Failed local certs of session."); } - // Exporter master secret length of secret key algorithm (one byte) - b = Record.getBytes8(buf); - if (b.length > 0) { - String algName = new String(b); - b = Record.getBytes16(buf); - this.exporterMasterSecret = new SecretKeySpec(b, algName); - } else { - // TLSv1.2- - this.exporterMasterSecret = null; - } - - // Get clientRandom - b = Record.getBytes8(buf); - if (b.length > 0) { - this.clientRandom = new RandomCookie(ByteBuffer.wrap(b)); - } else { - // TLSv1.3+ - this.clientRandom = null; - } - - // Get serverRandom - b = Record.getBytes8(buf); - if (b.length > 0) { - this.serverRandom = new RandomCookie(ByteBuffer.wrap(b)); - } else { - // TLSv1.3+ - this.serverRandom = null; - } - context = (SSLSessionContextImpl) hc.sslContext.engineGetServerSessionContext(); this.lastUsedTime = System.currentTimeMillis(); @@ -711,36 +672,6 @@ byte[] write() throws Exception { hos.putInt8(0); } - // Exporter Master Secret from TLSv1.3+ - if (getExporterMasterSecret() == null || - getExporterMasterSecret().getAlgorithm() == null) { - hos.putInt8(0); - } else { - String alg = getExporterMasterSecret().getAlgorithm(); - hos.putInt8(alg.length()); - if (alg.length() != 0) { - hos.write(alg.getBytes()); - } - b = getExporterMasterSecret().getEncoded(); - hos.putInt16(b.length); - hos.write(b, 0, b.length); - } - - // Randoms from TLSv1.2- - if ( clientRandom == null || clientRandom.randomBytes.length == 0) { - hos.putInt8(0); - } else { - hos.putInt8(clientRandom.randomBytes.length); - hos.writeBytes(clientRandom.randomBytes); - } - - if ( serverRandom == null || serverRandom.randomBytes.length == 0) { - hos.putInt8(0); - } else { - hos.putInt8(serverRandom.randomBytes.length); - hos.writeBytes(serverRandom.randomBytes); - } - return hos.toByteArray(); } 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 e38022f1c3e6c..d092d6c07ded2 100644 --- a/src/java.base/share/classes/sun/security/ssl/ServerHello.java +++ b/src/java.base/share/classes/sun/security/ssl/ServerHello.java @@ -356,6 +356,9 @@ public byte[] produce(ConnectionContext context, clientHello); shc.serverHelloRandom = shm.serverRandom; + shc.handshakeSession.setRandoms(shc.clientHelloRandom, + shc.serverHelloRandom); + // Produce extensions for ServerHello handshake message. SSLExtension[] serverHelloExtensions = shc.sslConfig.getEnabledExtensions( @@ -1129,6 +1132,9 @@ public void consume(ConnectionContext context, chc.sslConfig.maximumPacketSize); } + chc.handshakeSession.setRandoms(chc.clientHelloRandom, + chc.serverHelloRandom); + // // update // From cbcac3169bb80c1375381f08869ea594f831c040 Mon Sep 17 00:00:00 2001 From: Bradford Wetmore Date: Wed, 14 May 2025 19:07:44 -0700 Subject: [PATCH 15/27] Codereview comments: updated test to extend from SSLEngineTemplate, API tweaks, couple small bugs --- .../javax/net/ssl/ExtendedSSLSession.java | 4 - .../ExportKeyingMaterialTests.java | 81 ++++--------------- 2 files changed, 14 insertions(+), 71 deletions(-) diff --git a/src/java.base/share/classes/javax/net/ssl/ExtendedSSLSession.java b/src/java.base/share/classes/javax/net/ssl/ExtendedSSLSession.java index a6c94df8007d5..6ba816337863d 100644 --- a/src/java.base/share/classes/javax/net/ssl/ExtendedSSLSession.java +++ b/src/java.base/share/classes/javax/net/ssl/ExtendedSSLSession.java @@ -185,8 +185,6 @@ public List getStatusResponses() { * * @implSpec The default implementation throws * {@code UnsupportedOperationException}. - * Classes derived from ExtendedSSLSession must implement - * this method. * * @param label the label bytes used in the EKM calculation. * {@code label} will be converted to a {@code byte[]} @@ -237,8 +235,6 @@ public SecretKey exportKeyingMaterialKey( * * @implSpec The default implementation throws * {@code UnsupportedOperationException}. - * Classes derived from ExtendedSSLSession must implement - * this method. * * @param label the label bytes used in the EKM calculation. * {@code label} will be converted to a {@code byte[]} diff --git a/test/jdk/javax/net/ssl/ExtendedSSLSession/ExportKeyingMaterialTests.java b/test/jdk/javax/net/ssl/ExtendedSSLSession/ExportKeyingMaterialTests.java index 2f7ee9270dec1..619c7f8a06585 100644 --- a/test/jdk/javax/net/ssl/ExtendedSSLSession/ExportKeyingMaterialTests.java +++ b/test/jdk/javax/net/ssl/ExtendedSSLSession/ExportKeyingMaterialTests.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 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,7 +29,7 @@ * @bug 8341346 * @summary Add support for exporting TLS Keying Material * @library /javax/net/ssl/templates /test/lib - * @build SSLContextTemplate + * @build SSLEngineTemplate * @run main/othervm ExportKeyingMaterialTests */ @@ -59,70 +59,24 @@ * (wrap/unwrap) pass before any application data is consumed or * produced. */ -public class ExportKeyingMaterialTests extends SSLContextTemplate { - protected final SSLEngine clientEngine; // client Engine - protected final ByteBuffer clientOut; // write side of clientEngine - protected final ByteBuffer clientIn; // read side of clientEngine +public class ExportKeyingMaterialTests extends SSLEngineTemplate { - protected final SSLEngine serverEngine; // server Engine - protected final ByteBuffer serverOut; // write side of serverEngine - protected final ByteBuffer serverIn; // read side of serverEngine - - // For data transport, this example uses local ByteBuffers. This - // isn't really useful, but the purpose of this example is to show - // SSLEngine concepts, not how to do network transport. - protected final ByteBuffer cTOs; // "reliable" transport client->server - protected final ByteBuffer sTOc; // "reliable" transport server->client + private String protocol; + private String ciphersuite; protected ExportKeyingMaterialTests(String protocol, String ciphersuite) throws Exception { - serverEngine = configureServerEngine( - createServerSSLContext().createSSLEngine()); - - clientEngine = configureClientEngine( - createClientSSLContext().createSSLEngine(), - protocol, ciphersuite); - - // We'll assume the buffer sizes are the same - // between client and server. - SSLSession session = clientEngine.getSession(); - int appBufferMax = session.getApplicationBufferSize(); - int netBufferMax = session.getPacketBufferSize(); - - // We'll make the input buffers a bit bigger than the max needed - // size, so that unwrap()s following a successful data transfer - // won't generate BUFFER_OVERFLOWS. - // - // We'll use a mix of direct and indirect ByteBuffers for - // tutorial purposes only. In reality, only use direct - // ByteBuffers when they give a clear performance enhancement. - clientIn = ByteBuffer.allocate(appBufferMax + 50); - serverIn = ByteBuffer.allocate(appBufferMax + 50); - - cTOs = ByteBuffer.allocateDirect(netBufferMax); - sTOc = ByteBuffer.allocateDirect(netBufferMax); - - clientOut = createClientOutputBuffer(); - serverOut = createServerOutputBuffer(); - } - - protected ByteBuffer createServerOutputBuffer() { - return ByteBuffer.wrap("Hello Client, I'm Server".getBytes()); - } - - // - // Protected methods could be used to customize the test case. - // - - protected ByteBuffer createClientOutputBuffer() { - return ByteBuffer.wrap("Hi Server, I'm Client".getBytes()); + super(); + this.protocol = protocol; + this.ciphersuite = ciphersuite; } /* - * Configure the client side engine. + * Configure the engines. */ - protected SSLEngine configureClientEngine(SSLEngine clientEngine, - String protocol, String ciphersuite) { + private void configureEngines(SSLEngine clientEngine, + SSLEngine serverEngine) { + clientEngine.setUseClientMode(true); // Get/set parameters if needed @@ -131,13 +85,6 @@ protected SSLEngine configureClientEngine(SSLEngine clientEngine, paramsClient.setCipherSuites(new String[] { ciphersuite }); clientEngine.setSSLParameters(paramsClient); - return clientEngine; - } - - /* - * Configure the server side engine. - */ - protected SSLEngine configureServerEngine(SSLEngine serverEngine) { serverEngine.setUseClientMode(false); serverEngine.setNeedClientAuth(true); @@ -148,8 +95,6 @@ protected SSLEngine configureServerEngine(SSLEngine serverEngine) { "TLSv1.3", "TLSv1.2", "TLSv1.1", "TLSv1", "SSLv3" }); serverEngine.setSSLParameters(paramsServer); - - return serverEngine; } public static void main(String[] args) throws Exception { @@ -214,6 +159,8 @@ private void runTest() throws Exception { SSLEngineResult clientResult; SSLEngineResult serverResult; + configureEngines(clientEngine, serverEngine); + boolean dataDone = false; while (isOpen(clientEngine) || isOpen(serverEngine)) { log("================="); From 7c6839434d53f2eeb2cf86218b0b3499105508f6 Mon Sep 17 00:00:00 2001 From: Bradford Wetmore Date: Wed, 14 May 2025 19:08:32 -0700 Subject: [PATCH 16/27] Codereview comments: updated test to extend from SSLEngineTemplate, API tweaks, couple small bugs --- .../sun/security/ssl/SSLSessionImpl.java | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java b/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java index 8b4abc691e06a..c5d18fac1843b 100644 --- a/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java +++ b/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java @@ -1511,8 +1511,9 @@ public Object exportKeyingMaterial( } // Calculations are primarily based on protocol version. - switch (protocolVersion) { - case TLS13: // HKDF-based + if (protocolVersion.useTLS13PlusSpec()) { + // TLS 1.3+ using HKDF-based calcs. + // TLS 1.3 (RFC 8446) // Check the label/context lengths: // struct { @@ -1527,7 +1528,7 @@ public Object exportKeyingMaterial( // // L length of output keying material in octets // (<= 255*HashLen) - if (length >= (255 * cipherSuite.hashAlg.hashLength )) { + if (length > (255 * cipherSuite.hashAlg.hashLength )) { throw new IllegalArgumentException( "length is too large"); } @@ -1624,12 +1625,12 @@ public Object exportKeyingMaterial( // For whatever reason, we couldn't generate. Wrap and return. throw new SSLKeyException("Couldn't generate Exporter/HKDF", e); } + } else if (protocolVersion.useTLS10PlusSpec()) { + // RFC 7505 using PRF-based calcs. + // TLS 1/1.1/1.2 (RFCs 2246/4346/5246) or + // DTLS 1.0/1.2 (RFCs 4347/6347) - case TLS12: // RFC 7505 using PRF-based (RFC 2246/4346/5246) calcs. - case TLS11: - case TLS10: - - // RFC 7627: + // Note: In RFC 7627: // // If a client or server chooses to continue with a full handshake // without the extended master secret extension ... they MUST NOT @@ -1655,7 +1656,7 @@ public Object exportKeyingMaterial( } // context length must fit in 2 unsigned bytes. - if ((context != null) && context.length >= 65536) { + if ((context != null) && context.length > 0xFFFF) { throw new IllegalArgumentException( "Only 16-bit context lengths supported"); } @@ -1708,7 +1709,7 @@ public Object exportKeyingMaterial( InvalidAlgorithmParameterException e) { throw new SSLKeyException("Could not generate Exporter/PRF", e); } - default: + } else { // SSLv3 is vulnerable to a triple handshake attack and can't be // mitigated by RFC 7627. Don't support this or any other // unknown protocol. From bf05ddc5fa99844526cc1cfa058ef6015ae599b7 Mon Sep 17 00:00:00 2001 From: Bradford Wetmore Date: Thu, 15 May 2025 18:21:57 -0700 Subject: [PATCH 17/27] Minor Codereview comments. --- .../share/classes/javax/net/ssl/ExtendedSSLSession.java | 4 ++-- .../share/classes/sun/security/ssl/SSLSessionImpl.java | 2 +- .../net/ssl/ExtendedSSLSession/ExportKeyingMaterialTests.java | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/java.base/share/classes/javax/net/ssl/ExtendedSSLSession.java b/src/java.base/share/classes/javax/net/ssl/ExtendedSSLSession.java index 6ba816337863d..d96441ed880e3 100644 --- a/src/java.base/share/classes/javax/net/ssl/ExtendedSSLSession.java +++ b/src/java.base/share/classes/javax/net/ssl/ExtendedSSLSession.java @@ -166,7 +166,7 @@ public List getStatusResponses() { } /** - * Generate Exported Keying Material (EKM) calculated according to the + * Generates Exported Keying Material (EKM) calculated according to the * algorithms defined in RFCs 5705/8446. *

* RFC 5705 (for (D)TLSv1.2 and earlier) calculates different EKM @@ -211,7 +211,7 @@ public SecretKey exportKeyingMaterialKey( } /** - * Generate Exported Keying Material (EKM) calculated according to the + * Generates Exported Keying Material (EKM) calculated according to the * algorithms defined in RFCs 5705/8446. *

* RFC 5705 (for (D)TLSv1.2 and earlier) calculates different EKM diff --git a/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java b/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java index c5d18fac1843b..29bbf099b7241 100644 --- a/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java +++ b/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java @@ -1656,7 +1656,7 @@ public Object exportKeyingMaterial( } // context length must fit in 2 unsigned bytes. - if ((context != null) && context.length > 0xFFFF) { + if ((context != null) && (context.length > 0xFFFF)) { throw new IllegalArgumentException( "Only 16-bit context lengths supported"); } diff --git a/test/jdk/javax/net/ssl/ExtendedSSLSession/ExportKeyingMaterialTests.java b/test/jdk/javax/net/ssl/ExtendedSSLSession/ExportKeyingMaterialTests.java index 619c7f8a06585..89ddf237a8424 100644 --- a/test/jdk/javax/net/ssl/ExtendedSSLSession/ExportKeyingMaterialTests.java +++ b/test/jdk/javax/net/ssl/ExtendedSSLSession/ExportKeyingMaterialTests.java @@ -264,7 +264,7 @@ private static void runExporterTests( "Data: Equal inputs but exporters are not equal"); log("Data: Equal inputs test passed"); - // Different labels, now use exportKeyMaterialData() for coverage + // Different labels, now use exportKeyingMaterialData() for coverage clientKey = clientSession.exportKeyingMaterialKey("hello", bytes, 128); serverKey = serverSession.exportKeyingMaterialKey("goodbye", From 1355e3fbbbe9be9f021cb7f56a771f5aac742a7c Mon Sep 17 00:00:00 2001 From: Bradford Wetmore Date: Fri, 16 May 2025 14:06:51 -0700 Subject: [PATCH 18/27] Updated API to include SecretKey type, and a couple minor codereview comments --- .../sun/crypto/provider/TlsPrfGenerator.java | 4 +- .../javax/net/ssl/ExtendedSSLSession.java | 26 ++++--- .../internal/spec/TlsPrfParameterSpec.java | 40 +++++++++++ .../sun/security/ssl/SSLSessionImpl.java | 39 ++++++++--- .../security/pkcs11/P11TlsPrfGenerator.java | 12 +++- .../ExportKeyingMaterialTests.java | 68 ++++++++++++------- 6 files changed, 138 insertions(+), 51 deletions(-) diff --git a/src/java.base/share/classes/com/sun/crypto/provider/TlsPrfGenerator.java b/src/java.base/share/classes/com/sun/crypto/provider/TlsPrfGenerator.java index f8f733b6d2f74..16c53fc58c6e8 100644 --- a/src/java.base/share/classes/com/sun/crypto/provider/TlsPrfGenerator.java +++ b/src/java.base/share/classes/com/sun/crypto/provider/TlsPrfGenerator.java @@ -163,7 +163,9 @@ SecretKey engineGenerateKey0(boolean tls12) { spec.getPRFBlockSize()) : doTLS10PRF(secret, labelBytes, spec.getSeed(), n)); try { - return new SecretKeySpec(prfBytes, "TlsPrf"); + String keyAlg = spec.getKeyAlg(); + return new SecretKeySpec(prfBytes, + keyAlg == null ? "TlsPrf" : keyAlg); } finally { Arrays.fill(prfBytes, (byte)0); } diff --git a/src/java.base/share/classes/javax/net/ssl/ExtendedSSLSession.java b/src/java.base/share/classes/javax/net/ssl/ExtendedSSLSession.java index d96441ed880e3..6c1c2bef8098c 100644 --- a/src/java.base/share/classes/javax/net/ssl/ExtendedSSLSession.java +++ b/src/java.base/share/classes/javax/net/ssl/ExtendedSSLSession.java @@ -186,6 +186,12 @@ public List getStatusResponses() { * @implSpec The default implementation throws * {@code UnsupportedOperationException}. * + * @param keyAlg the algorithm of the resultant {@code SecretKey} object. + * See the SecretKey Algorithms section in the + * + * Java Security Standard Algorithm Names Specification + * for information about standard secret key algorithm + * names. * @param label the label bytes used in the EKM calculation. * {@code label} will be converted to a {@code byte[]} * before the operation begins @@ -193,10 +199,10 @@ public List getStatusResponses() { * @param length the number of bytes of EKM material needed * * @throws SSLKeyException if the key cannot be generated - * @throws IllegalArgumentException if {@code length} is non-positive, - * or if the {@code label} or {@code context} length can - * not be accommodated - * @throws NullPointerException if {@code label} is null + * @throws IllegalArgumentException if {@code keyAlg} is empty, + * {@code length} is non-positive, or if the {@code label} or + * {@code context} length can not be accommodated + * @throws NullPointerException if {@code keyAlg} or {@code label} is null * @throws UnsupportedOperationException if the underlying provider * does not implement the operation * @@ -205,7 +211,7 @@ public List getStatusResponses() { * * @since 25 */ - public SecretKey exportKeyingMaterialKey( + public SecretKey exportKeyingMaterialKey(String keyAlg, String label, byte[] context, int length) throws SSLKeyException { throw new UnsupportedOperationException(); } @@ -224,8 +230,8 @@ public SecretKey exportKeyingMaterialKey( *

* Depending on the chosen underlying key derivation mechanism, the * raw bytes might not be extractable/exportable. In such cases, the - * {@link #exportKeyingMaterialKey(String, byte[], int)} method should be - * used instead to access the generated key material. + * {@link #exportKeyingMaterialKey(String, String, byte[], int)} method + * should be used instead to access the generated key material. * * @spec https://www.rfc-editor.org/info/rfc5705 * RFC 5705: Keying Material Exporters for Transport Layer @@ -248,11 +254,11 @@ public SecretKey exportKeyingMaterialKey( * not be accommodated * @throws NullPointerException if {@code label} is null * @throws UnsupportedOperationException if the underlying provider - * does not implement the operation + * does not implement the operation, or if the derived + * keying material is not extractable * * @return a byte array of size {@code length} that contains the EKM - * material, or null if the derived key material does not support - * encoding + * material * @since 25 */ public byte[] exportKeyingMaterialData( diff --git a/src/java.base/share/classes/sun/security/internal/spec/TlsPrfParameterSpec.java b/src/java.base/share/classes/sun/security/internal/spec/TlsPrfParameterSpec.java index f87579ebb7095..eab5c089c0f1c 100644 --- a/src/java.base/share/classes/sun/security/internal/spec/TlsPrfParameterSpec.java +++ b/src/java.base/share/classes/sun/security/internal/spec/TlsPrfParameterSpec.java @@ -45,6 +45,7 @@ public class TlsPrfParameterSpec implements AlgorithmParameterSpec { private final SecretKey secret; + private final String keyAlg; private final String label; private final byte[] seed; private final int outputLength; @@ -72,6 +73,33 @@ public class TlsPrfParameterSpec implements AlgorithmParameterSpec { public TlsPrfParameterSpec(SecretKey secret, String label, byte[] seed, int outputLength, String prfHashAlg, int prfHashLength, int prfBlockSize) { + this(secret, null, label, seed, outputLength, prfHashAlg, + prfHashLength, prfBlockSize); + } + + /** + * Constructs a new TlsPrfParameterSpec. + * + * @param secret the secret to use in the calculation (or null) + * @param keyAlg the algorithm name that the generated SecretKey should + * have, or null if the default should be used + * @param label the label to use in the calculation + * @param seed the random seed to use in the calculation + * @param outputLength the length in bytes of the output key to be produced + * @param prfHashAlg the name of the TLS PRF hash algorithm to use. + * Used only for TLS 1.2+. TLS1.1 and earlier use a fixed PRF. + * @param prfHashLength the output length of the TLS PRF hash algorithm. + * Used only for TLS 1.2+. + * @param prfBlockSize the input block size of the TLS PRF hash algorithm. + * Used only for TLS 1.2+. + * + * @throws NullPointerException if label or seed is null + * @throws IllegalArgumentException if outputLength is negative + */ + public TlsPrfParameterSpec(SecretKey secret, String keyAlg, + String label, byte[] seed, int outputLength, + String prfHashAlg, int prfHashLength, int prfBlockSize) { + if ((label == null) || (seed == null)) { throw new NullPointerException("label and seed must not be null"); } @@ -79,6 +107,7 @@ public TlsPrfParameterSpec(SecretKey secret, String label, throw new IllegalArgumentException("outputLength must be positive"); } this.secret = secret; + this.keyAlg = keyAlg; this.label = label; this.seed = seed.clone(); this.outputLength = outputLength; @@ -87,6 +116,17 @@ public TlsPrfParameterSpec(SecretKey secret, String label, this.prfBlockSize = prfBlockSize; } + /** + * Returns the key algorithm name to use when generating the SecretKey, + * or null if there is no keyAlg. + * + * @return the key algorithm name, or null if there is no + * secret. + */ + public String getKeyAlg() { + return keyAlg; + } + /** * Returns the secret to use in the PRF calculation, or null if there is no * secret. diff --git a/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java b/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java index 29bbf099b7241..7d64639869423 100644 --- a/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java +++ b/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java @@ -1495,11 +1495,11 @@ public List getRequestedServerNames() { } /* - * deriveKey is used for switching between Keys/Data. Will redo - * if we ever introduce additional types. + * keyAlg is used for switching between Keys/Data. If keyAlg is + * non-null, we are producing a Key, otherwise data. */ public Object exportKeyingMaterial( - boolean deriveKey, String label, byte[] context, int length) + String keyAlg, String label, byte[] context, int length) throws SSLKeyException { // Global preconditions @@ -1580,7 +1580,7 @@ public Object exportKeyingMaterial( md = MessageDigest.getInstance(hashAlg.toString()); emptyHash = md.digest(); } catch (NoSuchAlgorithmException nsae) { - throw new RuntimeException( + throw new ProviderException( "Hash algorithm " + cipherSuite.hashAlg.name + " is not available", nsae); } @@ -1611,8 +1611,8 @@ public Object exportKeyingMaterial( hash, length); // ...now the final expand. - return (deriveKey ? - hkdf.deriveKey("TlsExporterKeyingMaterial", + return ((keyAlg != null) ? + hkdf.deriveKey(keyAlg, HKDFParameterSpec.expandOnly(derivedSecret, hkdfInfo, length)) : hkdf.deriveData( @@ -1699,12 +1699,21 @@ public Object exportKeyingMaterial( try { @SuppressWarnings("deprecation") TlsPrfParameterSpec spec = new TlsPrfParameterSpec( - masterSecret, label, seed, length, + masterSecret, keyAlg, label, seed, length, hashAlg.name, hashAlg.hashLength, hashAlg.blockSize); KeyGenerator kg = KeyGenerator.getInstance(prfAlg); kg.init(spec); SecretKey key = kg.generateKey(); - return (deriveKey ? key : key.getEncoded()); + if (keyAlg != null) { + return key; + } else { + byte[] b = key.getEncoded(); + if (b == null) { + throw new UnsupportedOperationException( + "Could not extract encoding from SecretKey"); + } + return b; + } } catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException e) { throw new SSLKeyException("Could not generate Exporter/PRF", e); @@ -1723,9 +1732,17 @@ public Object exportKeyingMaterial( * algorithms defined in RFCs 5705/8446. */ @Override - public SecretKey exportKeyingMaterialKey( + public SecretKey exportKeyingMaterialKey(String keyAlg, String label, byte[] context, int length) throws SSLKeyException { - return (SecretKey)exportKeyingMaterial(true, label, context, length); + + Objects.requireNonNull(keyAlg, "keyAlg can not be null"); + if (keyAlg.isEmpty()) { + throw new IllegalArgumentException( + "keyAlg is empty"); + } + + return (SecretKey) exportKeyingMaterial(keyAlg, label, context, + length); } /** @@ -1735,7 +1752,7 @@ public SecretKey exportKeyingMaterialKey( @Override public byte[] exportKeyingMaterialData( String label, byte[] context, int length) throws SSLKeyException { - return (byte[])exportKeyingMaterial(false, label, context, length); + return (byte[])exportKeyingMaterial(null, label, context, length); } /** Returns a string representation of this SSL session */ diff --git a/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11TlsPrfGenerator.java b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11TlsPrfGenerator.java index 32f99b3fd5a59..2e7d8d3b6fd7c 100644 --- a/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11TlsPrfGenerator.java +++ b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11TlsPrfGenerator.java @@ -155,7 +155,9 @@ protected SecretKey engineGenerateKey() { token.p11.C_SignUpdate(session.id(), 0, seed, 0, seed.length); byte[] out = token.p11.C_SignFinal (session.id(), spec.getOutputLength()); - return new SecretKeySpec(out, "TlsPrf"); + String keyAlg = spec.getKeyAlg(); + return new SecretKeySpec(out, + (keyAlg == null) ? "TlsPrf" : keyAlg); } catch (PKCS11Exception e) { throw new ProviderException("Could not calculate PRF", e); } finally { @@ -181,7 +183,9 @@ protected SecretKey engineGenerateKey() { token.p11.C_SignUpdate(session.id(), 0, seed, 0, seed.length); byte[] out = token.p11.C_SignFinal (session.id(), spec.getOutputLength()); - return new SecretKeySpec(out, "TlsPrf"); + String keyAlg = spec.getKeyAlg(); + return new SecretKeySpec(out, + (keyAlg == null) ? "TlsPrf" : keyAlg); } catch (PKCS11Exception e) { throw new ProviderException("Could not calculate PRF", e); } finally { @@ -201,7 +205,9 @@ protected SecretKey engineGenerateKey() { session = token.getOpSession(); token.p11.C_DeriveKey(session.id(), new CK_MECHANISM(mechanism, params), keyID, null); - return new SecretKeySpec(out, "TlsPrf"); + String keyAlg = spec.getKeyAlg(); + return new SecretKeySpec(out, + (keyAlg == null) ? "TlsPrf" : keyAlg); } catch (PKCS11Exception e) { throw new ProviderException("Could not calculate PRF", e); } finally { diff --git a/test/jdk/javax/net/ssl/ExtendedSSLSession/ExportKeyingMaterialTests.java b/test/jdk/javax/net/ssl/ExtendedSSLSession/ExportKeyingMaterialTests.java index 89ddf237a8424..476c36cf0b1b3 100644 --- a/test/jdk/javax/net/ssl/ExtendedSSLSession/ExportKeyingMaterialTests.java +++ b/test/jdk/javax/net/ssl/ExtendedSSLSession/ExportKeyingMaterialTests.java @@ -250,8 +250,10 @@ private static void runExporterTests( // non-extractable keys if .equals() doesn't work. // Inputs exactly equal. - clientKey = clientSession.exportKeyingMaterialKey("hello", bytes, 128); - serverKey = serverSession.exportKeyingMaterialKey("hello", bytes, 128); + clientKey = clientSession.exportKeyingMaterialKey( + "TlsExporterKeyingMaterial", "hello", bytes, 128); + serverKey = serverSession.exportKeyingMaterialKey( + "TlsExporterKeyingMaterial", "hello", bytes, 128); assertEquals(clientKey, serverKey, "Key: Equal inputs but exporters are not equal"); log("Key: Equal inputs test passed"); @@ -265,10 +267,10 @@ private static void runExporterTests( log("Data: Equal inputs test passed"); // Different labels, now use exportKeyingMaterialData() for coverage - clientKey = clientSession.exportKeyingMaterialKey("hello", - bytes, 128); - serverKey = serverSession.exportKeyingMaterialKey("goodbye", - bytes, 128); + clientKey = clientSession.exportKeyingMaterialKey( + "TlsExporterKeyingMaterial", "hello", bytes, 128); + serverKey = serverSession.exportKeyingMaterialKey( + "TlsExporterKeyingMaterial", "goodbye", bytes, 128); assertNotEquals(clientKey, serverKey, "Key: Different labels but exporters same"); log("Key: Different labels test passed"); @@ -282,10 +284,10 @@ private static void runExporterTests( log("Data: Different labels test passed"); // Different output sizes - clientKey = clientSession.exportKeyingMaterialKey("hello", - bytes, 128); - serverKey = serverSession.exportKeyingMaterialKey("hello", - bytes, 127); + clientKey = clientSession.exportKeyingMaterialKey( + "TlsExporterKeyingMaterial", "hello", bytes, 128); + serverKey = serverSession.exportKeyingMaterialKey( + "TlsExporterKeyingMaterial", "hello", bytes, 127); assertNotEquals(clientKey, serverKey, "Key: Different output sizes but exporters same"); log("Key: Different output size test passed"); @@ -301,10 +303,10 @@ private static void runExporterTests( log("Data: Different output size test passed"); // Different context values - clientKey = clientSession.exportKeyingMaterialKey("hello", - bytes, 128); - serverKey = serverSession.exportKeyingMaterialKey("hello", - bytesDiff, 128); + clientKey = clientSession.exportKeyingMaterialKey( + "TlsExporterKeyingMaterial", "hello", bytes, 128); + serverKey = serverSession.exportKeyingMaterialKey( + "TlsExporterKeyingMaterial", "hello", bytesDiff, 128); assertNotEquals(clientKey, serverKey, "Key: Different context but exporters same"); log("Key: Different context test passed"); @@ -318,10 +320,10 @@ private static void runExporterTests( log("Data: Different context test passed"); // Different context sizes - clientKey = clientSession.exportKeyingMaterialKey("hello", - bytes, 128); - serverKey = serverSession.exportKeyingMaterialKey("hello", - bytesDiffSize, 128); + clientKey = clientSession.exportKeyingMaterialKey( + "TlsExporterKeyingMaterial", "hello", bytes, 128); + serverKey = serverSession.exportKeyingMaterialKey( + "TlsExporterKeyingMaterial", "hello", bytesDiffSize, 128); assertNotEquals(clientKey, serverKey, "Key: Different context sizes but exporters same"); log("Key: Different context sizes test passed"); @@ -335,10 +337,10 @@ private static void runExporterTests( log("Data: Different context sizes test passed"); // No context, but otherwise the same - clientKey = clientSession.exportKeyingMaterialKey("hello", - null, 128); - serverKey = serverSession.exportKeyingMaterialKey("hello", - null, 128); + clientKey = clientSession.exportKeyingMaterialKey( + "TlsExporterKeyingMaterial", "hello", null, 128); + serverKey = serverSession.exportKeyingMaterialKey( + "TlsExporterKeyingMaterial", "hello", null, 128); assertEquals(clientKey, serverKey, "Key: No context and exporters are not the same"); log("Key: No context test passed"); @@ -352,10 +354,10 @@ private static void runExporterTests( log("Data: No context test passed"); // Smaller key size - clientKey = clientSession.exportKeyingMaterialKey("hello", - bytes, 1); - serverKey = serverSession.exportKeyingMaterialKey("hello", - bytes, 1); + clientKey = clientSession.exportKeyingMaterialKey( + "TlsExporterKeyingMaterial", "hello", bytes, 1); + serverKey = serverSession.exportKeyingMaterialKey( + "TlsExporterKeyingMaterial", "hello", bytes, 1); assertEquals(clientKey, serverKey, "Key: Smaller key size should be the same"); log("Key: Smaller key size test passed"); @@ -370,6 +372,20 @@ private static void runExporterTests( // Check error conditions + try { + clientSession.exportKeyingMaterialKey(null, "hello", bytes, 32); + throw new Exception("null keyAlg accepted"); + } catch (NullPointerException e) { + log("null keyAlg test passed"); + } + + try { + clientSession.exportKeyingMaterialKey("", "hello", bytes, 32); + throw new Exception("empty keyAlg accepted"); + } catch (IllegalArgumentException e) { + log("empty keyAlg test passed"); + } + try { clientSession.exportKeyingMaterialData("hello", bytes, -1); throw new Exception("negative length accepted"); From 638985b9c3b50eed4914487aa0d0b66cf30cc5d3 Mon Sep 17 00:00:00 2001 From: Bradford Wetmore Date: Fri, 16 May 2025 16:59:46 -0700 Subject: [PATCH 19/27] Updated copyright dates. --- .../share/classes/com/sun/crypto/provider/TlsPrfGenerator.java | 2 +- .../classes/sun/security/internal/spec/TlsPrfParameterSpec.java | 2 +- .../share/classes/sun/security/pkcs11/P11TlsPrfGenerator.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/java.base/share/classes/com/sun/crypto/provider/TlsPrfGenerator.java b/src/java.base/share/classes/com/sun/crypto/provider/TlsPrfGenerator.java index 16c53fc58c6e8..7aa7387d1422e 100644 --- a/src/java.base/share/classes/com/sun/crypto/provider/TlsPrfGenerator.java +++ b/src/java.base/share/classes/com/sun/crypto/provider/TlsPrfGenerator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 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 diff --git a/src/java.base/share/classes/sun/security/internal/spec/TlsPrfParameterSpec.java b/src/java.base/share/classes/sun/security/internal/spec/TlsPrfParameterSpec.java index eab5c089c0f1c..62d53286dd530 100644 --- a/src/java.base/share/classes/sun/security/internal/spec/TlsPrfParameterSpec.java +++ b/src/java.base/share/classes/sun/security/internal/spec/TlsPrfParameterSpec.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 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 diff --git a/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11TlsPrfGenerator.java b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11TlsPrfGenerator.java index 2e7d8d3b6fd7c..def91a0ec11d8 100644 --- a/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11TlsPrfGenerator.java +++ b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11TlsPrfGenerator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 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 From cde5edb9f956e450b1d6bc17fe0c0be40e0d17bd Mon Sep 17 00:00:00 2001 From: Bradford Wetmore Date: Mon, 19 May 2025 13:24:58 -0700 Subject: [PATCH 20/27] get*() no longer needed, backout error (oops!) --- .../sun/security/ssl/SSLSessionImpl.java | 23 +------------------ 1 file changed, 1 insertion(+), 22 deletions(-) diff --git a/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java b/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java index 7d64639869423..2bc00e597457e 100644 --- a/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java +++ b/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java @@ -252,7 +252,7 @@ final class SSLSessionImpl extends ExtendedSSLSession { this.localCerts = baseSession.localCerts; this.peerCerts = baseSession.peerCerts; this.statusResponses = baseSession.statusResponses; - this.exporterMasterSecret = baseSession.exporterMasterSecret; + this.resumptionMasterSecret = baseSession.resumptionMasterSecret; this.context = baseSession.context; this.negotiatedMaxFragLen = baseSession.negotiatedMaxFragLen; this.maximumPacketSize = baseSession.maximumPacketSize; @@ -723,27 +723,6 @@ SecretKey getMasterSecret() { return masterSecret; } - /** - * Returns the exporter master secret - */ - SecretKey getExporterMasterSecret() { - return exporterMasterSecret; - } - - /** - * Returns the client's RandomCookie - */ - RandomCookie getClientRandom() { - return clientRandom; - } - - /** - * Returns the server's RandomCookie - */ - RandomCookie getServerRandom() { - return serverRandom; - } - SecretKey getResumptionMasterSecret() { return resumptionMasterSecret; } From 8189e17f40836b2be28de6a710562ff3a5f1bf55 Mon Sep 17 00:00:00 2001 From: Bradford Wetmore Date: Tue, 20 May 2025 15:39:09 -0700 Subject: [PATCH 21/27] Private Codereview comment: Don't allow use of null keyAlgs, plus some minor cleanups --- .../sun/crypto/provider/TlsPrfGenerator.java | 4 +-- .../javax/net/ssl/ExtendedSSLSession.java | 10 ++++--- .../internal/spec/TlsPrfParameterSpec.java | 15 ++++++---- .../sun/security/ssl/SSLSessionImpl.java | 29 +++++++++---------- .../security/pkcs11/P11SecretKeyFactory.java | 2 +- .../security/pkcs11/P11TlsPrfGenerator.java | 12 ++------ 6 files changed, 34 insertions(+), 38 deletions(-) diff --git a/src/java.base/share/classes/com/sun/crypto/provider/TlsPrfGenerator.java b/src/java.base/share/classes/com/sun/crypto/provider/TlsPrfGenerator.java index 7aa7387d1422e..1c5726de9cd3a 100644 --- a/src/java.base/share/classes/com/sun/crypto/provider/TlsPrfGenerator.java +++ b/src/java.base/share/classes/com/sun/crypto/provider/TlsPrfGenerator.java @@ -163,9 +163,7 @@ SecretKey engineGenerateKey0(boolean tls12) { spec.getPRFBlockSize()) : doTLS10PRF(secret, labelBytes, spec.getSeed(), n)); try { - String keyAlg = spec.getKeyAlg(); - return new SecretKeySpec(prfBytes, - keyAlg == null ? "TlsPrf" : keyAlg); + return new SecretKeySpec(prfBytes, spec.getKeyAlg()); } finally { Arrays.fill(prfBytes, (byte)0); } diff --git a/src/java.base/share/classes/javax/net/ssl/ExtendedSSLSession.java b/src/java.base/share/classes/javax/net/ssl/ExtendedSSLSession.java index 6c1c2bef8098c..5854debae6441 100644 --- a/src/java.base/share/classes/javax/net/ssl/ExtendedSSLSession.java +++ b/src/java.base/share/classes/javax/net/ssl/ExtendedSSLSession.java @@ -194,7 +194,7 @@ public List getStatusResponses() { * names. * @param label the label bytes used in the EKM calculation. * {@code label} will be converted to a {@code byte[]} - * before the operation begins + * before the operation begins. * @param context the context bytes used in the EKM calculation, or null * @param length the number of bytes of EKM material needed * @@ -213,7 +213,8 @@ public List getStatusResponses() { */ public SecretKey exportKeyingMaterialKey(String keyAlg, String label, byte[] context, int length) throws SSLKeyException { - throw new UnsupportedOperationException(); + throw new UnsupportedOperationException( + "Underlying provider does not implement the method"); } /** @@ -244,7 +245,7 @@ public SecretKey exportKeyingMaterialKey(String keyAlg, * * @param label the label bytes used in the EKM calculation. * {@code label} will be converted to a {@code byte[]} - * before the operation begins + * before the operation begins. * @param context the context bytes used in the EKM calculation, or null * @param length the number of bytes of EKM material needed * @@ -263,6 +264,7 @@ public SecretKey exportKeyingMaterialKey(String keyAlg, */ public byte[] exportKeyingMaterialData( String label, byte[] context, int length) throws SSLKeyException { - throw new UnsupportedOperationException(); + throw new UnsupportedOperationException( + "Underlying provider does not implement the method"); } } diff --git a/src/java.base/share/classes/sun/security/internal/spec/TlsPrfParameterSpec.java b/src/java.base/share/classes/sun/security/internal/spec/TlsPrfParameterSpec.java index 62d53286dd530..c7a2be9b3f45f 100644 --- a/src/java.base/share/classes/sun/security/internal/spec/TlsPrfParameterSpec.java +++ b/src/java.base/share/classes/sun/security/internal/spec/TlsPrfParameterSpec.java @@ -73,7 +73,7 @@ public class TlsPrfParameterSpec implements AlgorithmParameterSpec { public TlsPrfParameterSpec(SecretKey secret, String label, byte[] seed, int outputLength, String prfHashAlg, int prfHashLength, int prfBlockSize) { - this(secret, null, label, seed, outputLength, prfHashAlg, + this(secret, "TlsPrf", label, seed, outputLength, prfHashAlg, prfHashLength, prfBlockSize); } @@ -93,15 +93,20 @@ public TlsPrfParameterSpec(SecretKey secret, String label, * @param prfBlockSize the input block size of the TLS PRF hash algorithm. * Used only for TLS 1.2+. * - * @throws NullPointerException if label or seed is null - * @throws IllegalArgumentException if outputLength is negative + * @throws NullPointerException if keyAlg, label or seed is null + * @throws IllegalArgumentException if outputLength is negative or + * keyAlg is empty */ public TlsPrfParameterSpec(SecretKey secret, String keyAlg, String label, byte[] seed, int outputLength, String prfHashAlg, int prfHashLength, int prfBlockSize) { - if ((label == null) || (seed == null)) { - throw new NullPointerException("label and seed must not be null"); + if ((keyAlg == null) || (label == null) || (seed == null)) { + throw new NullPointerException("keyAlg, label or seed" + + "must not be null"); + } + if (keyAlg.isEmpty()) { + throw new IllegalArgumentException("keyAlg can not be empty"); } if (outputLength <= 0) { throw new IllegalArgumentException("outputLength must be positive"); diff --git a/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java b/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java index 2bc00e597457e..52f0f082e6df6 100644 --- a/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java +++ b/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java @@ -24,7 +24,6 @@ */ package sun.security.ssl; -import jdk.internal.access.SharedSecrets; import sun.security.provider.X509Factory; import java.io.IOException; @@ -49,7 +48,6 @@ import javax.crypto.spec.HKDFParameterSpec; import javax.crypto.spec.SecretKeySpec; import javax.net.ssl.*; -import javax.security.auth.DestroyFailedException; import sun.security.ssl.CipherSuite.HashAlg; import sun.security.internal.spec.TlsPrfParameterSpec; @@ -57,7 +55,6 @@ import static sun.security.ssl.ProtocolVersion.*; import sun.security.util.KeyUtil; - /** * Implements the SSL session interface, and exposes the session context * which is maintained by SSL servers. @@ -313,7 +310,6 @@ final class SSLSessionImpl extends ExtendedSSLSession { * < length in bytes> PSK identity * Anonymous * < 1 byte > - */ SSLSessionImpl(HandshakeContext hc, ByteBuffer buf) throws IOException { @@ -1491,6 +1487,13 @@ public Object exportKeyingMaterial( // Calculations are primarily based on protocol version. if (protocolVersion.useTLS13PlusSpec()) { + + // Unlikely, but check anyway. + if (exporterMasterSecret == null) { + throw new RuntimeException( + "Exporter master secret not captured"); + } + // TLS 1.3+ using HKDF-based calcs. // TLS 1.3 (RFC 8446) @@ -1527,12 +1530,6 @@ public Object exportKeyingMaterial( "context length outside range"); } - // Unlikely, but check anyway. - if (exporterMasterSecret == null) { - throw new RuntimeException( - "Exporter master secret not captured"); - } - // Do RFC 8446:7.1-7.5 calculations /* @@ -1605,6 +1602,12 @@ public Object exportKeyingMaterial( throw new SSLKeyException("Couldn't generate Exporter/HKDF", e); } } else if (protocolVersion.useTLS10PlusSpec()) { + + // Unlikely, but check if randoms were not captured. + if (clientRandom == null || serverRandom == null) { + throw new RuntimeException("Random nonces not captured"); + } + // RFC 7505 using PRF-based calcs. // TLS 1/1.1/1.2 (RFCs 2246/4346/5246) or // DTLS 1.0/1.2 (RFCs 4347/6347) @@ -1616,7 +1619,6 @@ public Object exportKeyingMaterial( // export any key material based on the new master secret for any // subsequent application-level authentication ... it MUST // disable [RFC5705] ... - if (!useExtendedMasterSecret) { throw new SSLKeyException( "Exporters require extended master secrets"); @@ -1629,11 +1631,6 @@ public Object exportKeyingMaterial( "label length outside range"); } - // Unlikely, but check if randoms were not captured. - if (clientRandom == null || serverRandom == null) { - throw new RuntimeException("Random nonces not captured"); - } - // context length must fit in 2 unsigned bytes. if ((context != null) && (context.length > 0xFFFF)) { throw new IllegalArgumentException( diff --git a/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11SecretKeyFactory.java b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11SecretKeyFactory.java index f6f4cc73b758b..80705d7d4cb6e 100644 --- a/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11SecretKeyFactory.java +++ b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11SecretKeyFactory.java @@ -276,8 +276,8 @@ static final class P12MacPBEKeyInfo extends PBEKeyInfo { putKeyInfo(new TLSKeyInfo("TlsClientAppTrafficSecret")); putKeyInfo(new TLSKeyInfo("TlsClientHandshakeTrafficSecret")); putKeyInfo(new TLSKeyInfo("TlsEarlySecret")); - putKeyInfo(new TLSKeyInfo("TlsExporterMasterSecret")); putKeyInfo(new TLSKeyInfo("TlsExporterKeyingMaterial")); + putKeyInfo(new TLSKeyInfo("TlsExporterMasterSecret")); putKeyInfo(new TLSKeyInfo("TlsFinishedSecret")); putKeyInfo(new TLSKeyInfo("TlsHandshakeSecret")); putKeyInfo(new TLSKeyInfo("TlsKey")); diff --git a/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11TlsPrfGenerator.java b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11TlsPrfGenerator.java index def91a0ec11d8..f6691a3d279b2 100644 --- a/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11TlsPrfGenerator.java +++ b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11TlsPrfGenerator.java @@ -155,9 +155,7 @@ protected SecretKey engineGenerateKey() { token.p11.C_SignUpdate(session.id(), 0, seed, 0, seed.length); byte[] out = token.p11.C_SignFinal (session.id(), spec.getOutputLength()); - String keyAlg = spec.getKeyAlg(); - return new SecretKeySpec(out, - (keyAlg == null) ? "TlsPrf" : keyAlg); + return new SecretKeySpec(out, spec.getKeyAlg()); } catch (PKCS11Exception e) { throw new ProviderException("Could not calculate PRF", e); } finally { @@ -183,9 +181,7 @@ protected SecretKey engineGenerateKey() { token.p11.C_SignUpdate(session.id(), 0, seed, 0, seed.length); byte[] out = token.p11.C_SignFinal (session.id(), spec.getOutputLength()); - String keyAlg = spec.getKeyAlg(); - return new SecretKeySpec(out, - (keyAlg == null) ? "TlsPrf" : keyAlg); + return new SecretKeySpec(out, spec.getKeyAlg()); } catch (PKCS11Exception e) { throw new ProviderException("Could not calculate PRF", e); } finally { @@ -205,9 +201,7 @@ protected SecretKey engineGenerateKey() { session = token.getOpSession(); token.p11.C_DeriveKey(session.id(), new CK_MECHANISM(mechanism, params), keyID, null); - String keyAlg = spec.getKeyAlg(); - return new SecretKeySpec(out, - (keyAlg == null) ? "TlsPrf" : keyAlg); + return new SecretKeySpec(out, spec.getKeyAlg()); } catch (PKCS11Exception e) { throw new ProviderException("Could not calculate PRF", e); } finally { From 33baa38185a10aef2470a045a4afbc2ef782a459 Mon Sep 17 00:00:00 2001 From: Bradford Wetmore Date: Tue, 20 May 2025 15:53:52 -0700 Subject: [PATCH 22/27] Missed one change --- .../sun/security/internal/spec/TlsPrfParameterSpec.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/java.base/share/classes/sun/security/internal/spec/TlsPrfParameterSpec.java b/src/java.base/share/classes/sun/security/internal/spec/TlsPrfParameterSpec.java index c7a2be9b3f45f..f6885cceea1a0 100644 --- a/src/java.base/share/classes/sun/security/internal/spec/TlsPrfParameterSpec.java +++ b/src/java.base/share/classes/sun/security/internal/spec/TlsPrfParameterSpec.java @@ -122,11 +122,9 @@ public TlsPrfParameterSpec(SecretKey secret, String keyAlg, } /** - * Returns the key algorithm name to use when generating the SecretKey, - * or null if there is no keyAlg. + * Returns the key algorithm name to use when generating the SecretKey. * - * @return the key algorithm name, or null if there is no - * secret. + * @return the key algorithm name */ public String getKeyAlg() { return keyAlg; From dae583e8c96b8a0b6bb0d4658183e5e316f154f6 Mon Sep 17 00:00:00 2001 From: Bradford Wetmore Date: Tue, 20 May 2025 16:45:56 -0700 Subject: [PATCH 23/27] Minor bug --- .../sun/security/internal/spec/TlsPrfParameterSpec.java | 4 ++-- .../share/classes/sun/security/ssl/SSLSessionImpl.java | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/java.base/share/classes/sun/security/internal/spec/TlsPrfParameterSpec.java b/src/java.base/share/classes/sun/security/internal/spec/TlsPrfParameterSpec.java index f6885cceea1a0..5d3b0872e4c4c 100644 --- a/src/java.base/share/classes/sun/security/internal/spec/TlsPrfParameterSpec.java +++ b/src/java.base/share/classes/sun/security/internal/spec/TlsPrfParameterSpec.java @@ -102,8 +102,8 @@ public TlsPrfParameterSpec(SecretKey secret, String keyAlg, String prfHashAlg, int prfHashLength, int prfBlockSize) { if ((keyAlg == null) || (label == null) || (seed == null)) { - throw new NullPointerException("keyAlg, label or seed" + - "must not be null"); + throw new NullPointerException( + "keyAlg, label or seed must not be null"); } if (keyAlg.isEmpty()) { throw new IllegalArgumentException("keyAlg can not be empty"); diff --git a/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java b/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java index 52f0f082e6df6..a92bc2334cdce 100644 --- a/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java +++ b/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java @@ -1675,7 +1675,8 @@ public Object exportKeyingMaterial( try { @SuppressWarnings("deprecation") TlsPrfParameterSpec spec = new TlsPrfParameterSpec( - masterSecret, keyAlg, label, seed, length, + masterSecret, (keyAlg == null) ? "TlsKey" : keyAlg, + label, seed, length, hashAlg.name, hashAlg.hashLength, hashAlg.blockSize); KeyGenerator kg = KeyGenerator.getInstance(prfAlg); kg.init(spec); From c92d1e896e2105a55a5dfa0fbc8c74097fbca6f4 Mon Sep 17 00:00:00 2001 From: Bradford Wetmore Date: Tue, 20 May 2025 18:17:22 -0700 Subject: [PATCH 24/27] Added PKCS11 testing --- .../ExportKeyingMaterialTests.java | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/test/jdk/javax/net/ssl/ExtendedSSLSession/ExportKeyingMaterialTests.java b/test/jdk/javax/net/ssl/ExtendedSSLSession/ExportKeyingMaterialTests.java index 476c36cf0b1b3..ae548ccb3d3a9 100644 --- a/test/jdk/javax/net/ssl/ExtendedSSLSession/ExportKeyingMaterialTests.java +++ b/test/jdk/javax/net/ssl/ExtendedSSLSession/ExportKeyingMaterialTests.java @@ -28,9 +28,10 @@ * @test * @bug 8341346 * @summary Add support for exporting TLS Keying Material - * @library /javax/net/ssl/templates /test/lib + * @library /javax/net/ssl/templates /test/lib /test/jdk/sun/security/pkcs11 * @build SSLEngineTemplate * @run main/othervm ExportKeyingMaterialTests + * @run main/othervm ExportKeyingMaterialTests PKCS11 */ import java.security.Security; @@ -101,6 +102,11 @@ public static void main(String[] args) throws Exception { // Turn off the disabled Algorithms so we can also test SSLv3/TLSv1/etc. Security.setProperty("jdk.tls.disabledAlgorithms", ""); + if ((args.length > 0) && (args[0].equals("PKCS11"))) { + Security.insertProviderAt( + PKCS11Test.getSunPKCS11(PKCS11Test.getNssConfig()), 1); + } + // Exercise all of the triggers which capture data // in the various key exchange algorithms. @@ -355,17 +361,17 @@ private static void runExporterTests( // Smaller key size clientKey = clientSession.exportKeyingMaterialKey( - "TlsExporterKeyingMaterial", "hello", bytes, 1); + "TlsExporterKeyingMaterial", "hello", bytes, 5); serverKey = serverSession.exportKeyingMaterialKey( - "TlsExporterKeyingMaterial", "hello", bytes, 1); + "TlsExporterKeyingMaterial", "hello", bytes, 5); assertEquals(clientKey, serverKey, "Key: Smaller key size should be the same"); log("Key: Smaller key size test passed"); clientBytes = clientSession.exportKeyingMaterialData("hello", - bytes, 1); + bytes, 5); serverBytes = serverSession.exportKeyingMaterialData("hello", - bytes, 1); + bytes, 5); assertEqualsByteArray(clientBytes, serverBytes, "Data: Smaller key size should be the same"); log("Data: Smaller key size test passed"); From 67480e99a1028369f97f77e86e969be2c4ba2d49 Mon Sep 17 00:00:00 2001 From: Bradford Wetmore Date: Fri, 23 May 2025 14:19:47 -0700 Subject: [PATCH 25/27] Remove TlsExporterKeyingMaterial for now. Can add later if needed. --- .../security/pkcs11/P11SecretKeyFactory.java | 1 - .../ExportKeyingMaterialTests.java | 28 +++++++++---------- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11SecretKeyFactory.java b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11SecretKeyFactory.java index 80705d7d4cb6e..a2c7a0727692c 100644 --- a/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11SecretKeyFactory.java +++ b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11SecretKeyFactory.java @@ -276,7 +276,6 @@ static final class P12MacPBEKeyInfo extends PBEKeyInfo { putKeyInfo(new TLSKeyInfo("TlsClientAppTrafficSecret")); putKeyInfo(new TLSKeyInfo("TlsClientHandshakeTrafficSecret")); putKeyInfo(new TLSKeyInfo("TlsEarlySecret")); - putKeyInfo(new TLSKeyInfo("TlsExporterKeyingMaterial")); putKeyInfo(new TLSKeyInfo("TlsExporterMasterSecret")); putKeyInfo(new TLSKeyInfo("TlsFinishedSecret")); putKeyInfo(new TLSKeyInfo("TlsHandshakeSecret")); diff --git a/test/jdk/javax/net/ssl/ExtendedSSLSession/ExportKeyingMaterialTests.java b/test/jdk/javax/net/ssl/ExtendedSSLSession/ExportKeyingMaterialTests.java index ae548ccb3d3a9..5712e759c5f71 100644 --- a/test/jdk/javax/net/ssl/ExtendedSSLSession/ExportKeyingMaterialTests.java +++ b/test/jdk/javax/net/ssl/ExtendedSSLSession/ExportKeyingMaterialTests.java @@ -257,9 +257,9 @@ private static void runExporterTests( // Inputs exactly equal. clientKey = clientSession.exportKeyingMaterialKey( - "TlsExporterKeyingMaterial", "hello", bytes, 128); + "Generic", "hello", bytes, 128); serverKey = serverSession.exportKeyingMaterialKey( - "TlsExporterKeyingMaterial", "hello", bytes, 128); + "Generic", "hello", bytes, 128); assertEquals(clientKey, serverKey, "Key: Equal inputs but exporters are not equal"); log("Key: Equal inputs test passed"); @@ -274,9 +274,9 @@ private static void runExporterTests( // Different labels, now use exportKeyingMaterialData() for coverage clientKey = clientSession.exportKeyingMaterialKey( - "TlsExporterKeyingMaterial", "hello", bytes, 128); + "Generic", "hello", bytes, 128); serverKey = serverSession.exportKeyingMaterialKey( - "TlsExporterKeyingMaterial", "goodbye", bytes, 128); + "Generic", "goodbye", bytes, 128); assertNotEquals(clientKey, serverKey, "Key: Different labels but exporters same"); log("Key: Different labels test passed"); @@ -291,9 +291,9 @@ private static void runExporterTests( // Different output sizes clientKey = clientSession.exportKeyingMaterialKey( - "TlsExporterKeyingMaterial", "hello", bytes, 128); + "Generic", "hello", bytes, 128); serverKey = serverSession.exportKeyingMaterialKey( - "TlsExporterKeyingMaterial", "hello", bytes, 127); + "Generic", "hello", bytes, 127); assertNotEquals(clientKey, serverKey, "Key: Different output sizes but exporters same"); log("Key: Different output size test passed"); @@ -310,9 +310,9 @@ private static void runExporterTests( // Different context values clientKey = clientSession.exportKeyingMaterialKey( - "TlsExporterKeyingMaterial", "hello", bytes, 128); + "Generic", "hello", bytes, 128); serverKey = serverSession.exportKeyingMaterialKey( - "TlsExporterKeyingMaterial", "hello", bytesDiff, 128); + "Generic", "hello", bytesDiff, 128); assertNotEquals(clientKey, serverKey, "Key: Different context but exporters same"); log("Key: Different context test passed"); @@ -327,9 +327,9 @@ private static void runExporterTests( // Different context sizes clientKey = clientSession.exportKeyingMaterialKey( - "TlsExporterKeyingMaterial", "hello", bytes, 128); + "Generic", "hello", bytes, 128); serverKey = serverSession.exportKeyingMaterialKey( - "TlsExporterKeyingMaterial", "hello", bytesDiffSize, 128); + "Generic", "hello", bytesDiffSize, 128); assertNotEquals(clientKey, serverKey, "Key: Different context sizes but exporters same"); log("Key: Different context sizes test passed"); @@ -344,9 +344,9 @@ private static void runExporterTests( // No context, but otherwise the same clientKey = clientSession.exportKeyingMaterialKey( - "TlsExporterKeyingMaterial", "hello", null, 128); + "Generic", "hello", null, 128); serverKey = serverSession.exportKeyingMaterialKey( - "TlsExporterKeyingMaterial", "hello", null, 128); + "Generic", "hello", null, 128); assertEquals(clientKey, serverKey, "Key: No context and exporters are not the same"); log("Key: No context test passed"); @@ -361,9 +361,9 @@ private static void runExporterTests( // Smaller key size clientKey = clientSession.exportKeyingMaterialKey( - "TlsExporterKeyingMaterial", "hello", bytes, 5); + "Generic", "hello", bytes, 5); serverKey = serverSession.exportKeyingMaterialKey( - "TlsExporterKeyingMaterial", "hello", bytes, 5); + "Generic", "hello", bytes, 5); assertEquals(clientKey, serverKey, "Key: Smaller key size should be the same"); log("Key: Smaller key size test passed"); From d0a0a7bf30590661f84f0aa93718c596e3cd2a06 Mon Sep 17 00:00:00 2001 From: Bradford Wetmore Date: Thu, 29 May 2025 18:07:04 -0700 Subject: [PATCH 26/27] Last minute codereview comments --- .../sun/crypto/provider/TlsPrfGenerator.java | 2 +- .../javax/net/ssl/ExtendedSSLSession.java | 10 ++++++++-- .../internal/spec/TlsPrfParameterSpec.java | 3 +-- .../sun/security/ssl/SSLSessionImpl.java | 4 ++-- .../ExportKeyingMaterialTests.java | 18 ++++++++++++++++++ 5 files changed, 30 insertions(+), 7 deletions(-) diff --git a/src/java.base/share/classes/com/sun/crypto/provider/TlsPrfGenerator.java b/src/java.base/share/classes/com/sun/crypto/provider/TlsPrfGenerator.java index 1c5726de9cd3a..6ca019ae87b45 100644 --- a/src/java.base/share/classes/com/sun/crypto/provider/TlsPrfGenerator.java +++ b/src/java.base/share/classes/com/sun/crypto/provider/TlsPrfGenerator.java @@ -147,7 +147,7 @@ protected void engineInit(int keysize, SecureRandom random) { throw new InvalidParameterException(MSG); } - SecretKey engineGenerateKey0(boolean tls12) { + protected SecretKey engineGenerateKey0(boolean tls12) { if (spec == null) { throw new IllegalStateException( "TlsPrfGenerator must be initialized"); diff --git a/src/java.base/share/classes/javax/net/ssl/ExtendedSSLSession.java b/src/java.base/share/classes/javax/net/ssl/ExtendedSSLSession.java index 5854debae6441..980ff77d83a5a 100644 --- a/src/java.base/share/classes/javax/net/ssl/ExtendedSSLSession.java +++ b/src/java.base/share/classes/javax/net/ssl/ExtendedSSLSession.java @@ -173,7 +173,7 @@ public List getStatusResponses() { * values depending on whether {@code context} is null or non-null/empty. * RFC 8446 (TLSv1.3) treats a null context as non-null/empty. *

- * The {@code label} {@code String} will be converted to bytes using + * {@code label} will be converted to bytes using * the {@link java.nio.charset.StandardCharsets#UTF_8} * character encoding. * @@ -203,6 +203,9 @@ public List getStatusResponses() { * {@code length} is non-positive, or if the {@code label} or * {@code context} length can not be accommodated * @throws NullPointerException if {@code keyAlg} or {@code label} is null + * @throws IllegalStateException if this session does not have the + * necessary key generation material (for example, a session + * under construction during handshaking) * @throws UnsupportedOperationException if the underlying provider * does not implement the operation * @@ -225,7 +228,7 @@ public SecretKey exportKeyingMaterialKey(String keyAlg, * values depending on whether {@code context} is null or non-null/empty. * RFC 8446 (TLSv1.3) treats a null context as non-null/empty. *

- * The {@code label} {@code String} will be converted to bytes using + * {@code label} will be converted to bytes using * the {@link java.nio.charset.StandardCharsets#UTF_8} * character encoding. *

@@ -254,6 +257,9 @@ public SecretKey exportKeyingMaterialKey(String keyAlg, * or if the {@code label} or {@code context} length can * not be accommodated * @throws NullPointerException if {@code label} is null + * @throws IllegalStateException if this session does not have the + * necessary key generation material (for example, a session + * under construction during handshaking) * @throws UnsupportedOperationException if the underlying provider * does not implement the operation, or if the derived * keying material is not extractable diff --git a/src/java.base/share/classes/sun/security/internal/spec/TlsPrfParameterSpec.java b/src/java.base/share/classes/sun/security/internal/spec/TlsPrfParameterSpec.java index 5d3b0872e4c4c..14abf9f30dc14 100644 --- a/src/java.base/share/classes/sun/security/internal/spec/TlsPrfParameterSpec.java +++ b/src/java.base/share/classes/sun/security/internal/spec/TlsPrfParameterSpec.java @@ -81,8 +81,7 @@ public TlsPrfParameterSpec(SecretKey secret, String label, * Constructs a new TlsPrfParameterSpec. * * @param secret the secret to use in the calculation (or null) - * @param keyAlg the algorithm name that the generated SecretKey should - * have, or null if the default should be used + * @param keyAlg the algorithm name for the generated {@code SecretKey} * @param label the label to use in the calculation * @param seed the random seed to use in the calculation * @param outputLength the length in bytes of the output key to be produced diff --git a/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java b/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java index a92bc2334cdce..4f2cf4ac2a955 100644 --- a/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java +++ b/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java @@ -1490,7 +1490,7 @@ public Object exportKeyingMaterial( // Unlikely, but check anyway. if (exporterMasterSecret == null) { - throw new RuntimeException( + throw new IllegalStateException( "Exporter master secret not captured"); } @@ -1605,7 +1605,7 @@ public Object exportKeyingMaterial( // Unlikely, but check if randoms were not captured. if (clientRandom == null || serverRandom == null) { - throw new RuntimeException("Random nonces not captured"); + throw new IllegalStateException("Random nonces not captured"); } // RFC 7505 using PRF-based calcs. diff --git a/test/jdk/javax/net/ssl/ExtendedSSLSession/ExportKeyingMaterialTests.java b/test/jdk/javax/net/ssl/ExtendedSSLSession/ExportKeyingMaterialTests.java index 5712e759c5f71..77d47d0747521 100644 --- a/test/jdk/javax/net/ssl/ExtendedSSLSession/ExportKeyingMaterialTests.java +++ b/test/jdk/javax/net/ssl/ExtendedSSLSession/ExportKeyingMaterialTests.java @@ -468,6 +468,14 @@ private static void runExporterTests( log("too large length test passed in TLSv1.3"); } + // Do a null/byte[0] comparison. Exporters should be the same. + clientBytes = clientSession.exportKeyingMaterialData("hello", + null, 128); + serverBytes = serverSession.exportKeyingMaterialData("hello", + new byte[0], 128); + assertEqualsByteArray(clientBytes, serverBytes, + "Data: null vs. empty context should be the same"); + break; case "TLSv1": @@ -485,6 +493,16 @@ private static void runExporterTests( log("large context passed in TLSv1/TLSv1.1/TLSv1.2"); } + // Do a null/byte[0] comparison. Should NOT be the same. + clientBytes = clientSession.exportKeyingMaterialData( + "hello", null, 128); + serverBytes = serverSession.exportKeyingMaterialData( + "hello", new byte[0], 128); + assertNotEqualsByteArray(clientBytes, serverBytes, + "empty vs. null context but exporters same"); + log("Data: empty vs. null context, " + + "different exporters test passed"); + break; default: From d0cf6917f7fe0a4cb7c4e29a67222b0de72410c3 Mon Sep 17 00:00:00 2001 From: Bradford Wetmore Date: Fri, 30 May 2025 14:52:08 -0700 Subject: [PATCH 27/27] Resolved Merge Problem --- src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java b/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java index ff59aa9fe3b5c..5eb9f72af46e4 100644 --- a/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java +++ b/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java @@ -27,6 +27,7 @@ import java.io.IOException; import java.net.InetAddress; import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; import java.security.*; import java.security.cert.X509Certificate; import java.util.ArrayList;