8341346: Add support for exporting TLS Keying Material
Reviewed-by: hchao, jnimeh, weijun
This commit is contained in:
parent
0df8c9684b
commit
2926435d22
@ -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
|
||||
@ -147,7 +147,7 @@ abstract class TlsPrfGenerator extends KeyGeneratorSpi {
|
||||
throw new InvalidParameterException(MSG);
|
||||
}
|
||||
|
||||
SecretKey engineGenerateKey0(boolean tls12) {
|
||||
protected SecretKey engineGenerateKey0(boolean tls12) {
|
||||
if (spec == null) {
|
||||
throw new IllegalStateException(
|
||||
"TlsPrfGenerator must be initialized");
|
||||
@ -163,7 +163,7 @@ abstract class TlsPrfGenerator extends KeyGeneratorSpi {
|
||||
spec.getPRFBlockSize()) :
|
||||
doTLS10PRF(secret, labelBytes, spec.getSeed(), n));
|
||||
try {
|
||||
return new SecretKeySpec(prfBytes, "TlsPrf");
|
||||
return new SecretKeySpec(prfBytes, spec.getKeyAlg());
|
||||
} finally {
|
||||
Arrays.fill(prfBytes, (byte)0);
|
||||
}
|
||||
|
@ -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,113 @@ public abstract class ExtendedSSLSession implements SSLSession {
|
||||
public List<byte[]> getStatusResponses() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates Exported Keying Material (EKM) calculated according to the
|
||||
* algorithms defined in RFCs 5705/8446.
|
||||
* <P>
|
||||
* 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.
|
||||
* <P>
|
||||
* {@code label} 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
|
||||
*
|
||||
* @implSpec The default implementation throws
|
||||
* {@code UnsupportedOperationException}.
|
||||
*
|
||||
* @param keyAlg the algorithm of the resultant {@code SecretKey} object.
|
||||
* See the SecretKey Algorithms section in the
|
||||
* <a href="{@docRoot}/../specs/security/standard-names.html#secretkey-algorithms">
|
||||
* Java Security Standard Algorithm Names Specification</a>
|
||||
* 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.
|
||||
* @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
|
||||
* @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 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
|
||||
*
|
||||
* @return a {@code SecretKey} that contains {@code length} bytes of the
|
||||
* EKM material
|
||||
*
|
||||
* @since 25
|
||||
*/
|
||||
public SecretKey exportKeyingMaterialKey(String keyAlg,
|
||||
String label, byte[] context, int length) throws SSLKeyException {
|
||||
throw new UnsupportedOperationException(
|
||||
"Underlying provider does not implement the method");
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates Exported Keying Material (EKM) calculated according to the
|
||||
* algorithms defined in RFCs 5705/8446.
|
||||
* <P>
|
||||
* 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.
|
||||
* <P>
|
||||
* {@code label} will be converted to bytes using
|
||||
* the {@link java.nio.charset.StandardCharsets#UTF_8}
|
||||
* character encoding.
|
||||
* <P>
|
||||
* Depending on the chosen underlying key derivation mechanism, the
|
||||
* raw bytes might not be extractable/exportable. In such cases, the
|
||||
* {@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
|
||||
* Security (TLS)
|
||||
* @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.
|
||||
* @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
|
||||
* @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 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
|
||||
*
|
||||
* @return a byte array of size {@code length} that contains the EKM
|
||||
* material
|
||||
* @since 25
|
||||
*/
|
||||
public byte[] exportKeyingMaterialData(
|
||||
String label, byte[] context, int length) throws SSLKeyException {
|
||||
throw new UnsupportedOperationException(
|
||||
"Underlying provider does not implement the method");
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
@ -45,6 +45,7 @@ import javax.crypto.SecretKey;
|
||||
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,13 +73,45 @@ public class TlsPrfParameterSpec implements AlgorithmParameterSpec {
|
||||
public TlsPrfParameterSpec(SecretKey secret, 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");
|
||||
this(secret, "TlsPrf", 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 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
|
||||
* @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 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 ((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");
|
||||
}
|
||||
this.secret = secret;
|
||||
this.keyAlg = keyAlg;
|
||||
this.label = label;
|
||||
this.seed = seed.clone();
|
||||
this.outputLength = outputLength;
|
||||
@ -87,6 +120,15 @@ public class TlsPrfParameterSpec implements AlgorithmParameterSpec {
|
||||
this.prfBlockSize = prfBlockSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the key algorithm name to use when generating the SecretKey.
|
||||
*
|
||||
* @return the key algorithm name
|
||||
*/
|
||||
public String getKeyAlg() {
|
||||
return keyAlg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the secret to use in the PRF calculation, or null if there is no
|
||||
* secret.
|
||||
|
@ -745,6 +745,12 @@ final class Finished {
|
||||
"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");
|
||||
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);
|
||||
@ -1099,6 +1105,12 @@ final class Finished {
|
||||
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");
|
||||
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();
|
||||
|
@ -27,28 +27,32 @@ package sun.security.ssl;
|
||||
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;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.zip.Adler32;
|
||||
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.ExtendedSSLSession;
|
||||
import javax.net.ssl.SNIHostName;
|
||||
import javax.net.ssl.SNIServerName;
|
||||
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 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.*;
|
||||
import sun.security.util.KeyUtil;
|
||||
import sun.security.provider.X509Factory;
|
||||
import sun.security.ssl.X509Authentication.X509Possession;
|
||||
|
||||
@ -99,6 +103,9 @@ final class SSLSessionImpl extends ExtendedSSLSession {
|
||||
private Collection<SignatureScheme> peerSupportedSignAlgs; //for certificate
|
||||
private boolean useDefaultPeerSignAlgs = false;
|
||||
private List<byte[]> statusResponses;
|
||||
private SecretKey exporterMasterSecret; // TLSv1.3+ exporter info
|
||||
private RandomCookie clientRandom; // TLSv1.2- exporter info
|
||||
private RandomCookie serverRandom;
|
||||
private SecretKey resumptionMasterSecret;
|
||||
private SecretKey preSharedKey;
|
||||
private byte[] pskIdentity;
|
||||
@ -612,6 +619,15 @@ final class SSLSessionImpl extends ExtendedSSLSession {
|
||||
masterSecret = secret;
|
||||
}
|
||||
|
||||
void setExporterMasterSecret(SecretKey secret) {
|
||||
exporterMasterSecret = secret;
|
||||
}
|
||||
|
||||
void setRandoms(RandomCookie client, RandomCookie server) {
|
||||
clientRandom = client;
|
||||
serverRandom = server;
|
||||
}
|
||||
|
||||
void setResumptionMasterSecret(SecretKey secret) {
|
||||
resumptionMasterSecret = secret;
|
||||
}
|
||||
@ -1397,6 +1413,269 @@ final class SSLSessionImpl extends ExtendedSSLSession {
|
||||
return requestedServerNames;
|
||||
}
|
||||
|
||||
/*
|
||||
* keyAlg is used for switching between Keys/Data. If keyAlg is
|
||||
* non-null, we are producing a Key, otherwise data.
|
||||
*/
|
||||
public Object exportKeyingMaterial(
|
||||
String keyAlg, String label, byte[] context, int length)
|
||||
throws SSLKeyException {
|
||||
|
||||
// Global preconditions
|
||||
|
||||
Objects.requireNonNull(label, "label can not be null");
|
||||
if (length < 1) {
|
||||
throw new IllegalArgumentException(
|
||||
"length must be positive");
|
||||
}
|
||||
|
||||
// Calculations are primarily based on protocol version.
|
||||
if (protocolVersion.useTLS13PlusSpec()) {
|
||||
|
||||
// Unlikely, but check anyway.
|
||||
if (exporterMasterSecret == null) {
|
||||
throw new IllegalStateException(
|
||||
"Exporter master secret not captured");
|
||||
}
|
||||
|
||||
// TLS 1.3+ using HKDF-based calcs.
|
||||
// TLS 1.3 (RFC 8446)
|
||||
|
||||
// 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");
|
||||
}
|
||||
|
||||
// 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)
|
||||
*/
|
||||
|
||||
try {
|
||||
// Use the ciphersuite's hashAlg for these calcs.
|
||||
HashAlg hashAlg = cipherSuite.hashAlg;
|
||||
KDF hkdf = KDF.getInstance(hashAlg.hkdfAlgorithm);
|
||||
|
||||
// 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 ProviderException(
|
||||
"Hash algorithm " + cipherSuite.hashAlg.name +
|
||||
" is not available", nsae);
|
||||
}
|
||||
|
||||
// ...then the hkdfInfo...
|
||||
byte[] hkdfInfo = SSLSecretDerivation.createHkdfInfo(
|
||||
hkdfInfoLabel, 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.deriveKey("TlsKey",
|
||||
HKDFParameterSpec.expandOnly(exporterMasterSecret,
|
||||
hkdfInfo, hashAlg.hashLength));
|
||||
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.
|
||||
return ((keyAlg != null) ?
|
||||
hkdf.deriveKey(keyAlg,
|
||||
HKDFParameterSpec.expandOnly(derivedSecret,
|
||||
hkdfInfo, length)) :
|
||||
hkdf.deriveData(
|
||||
HKDFParameterSpec.expandOnly(derivedSecret,
|
||||
hkdfInfo, length)));
|
||||
} finally {
|
||||
KeyUtil.destroySecretKeys(derivedSecret);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// For whatever reason, we couldn't generate. Wrap and return.
|
||||
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 IllegalStateException("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)
|
||||
|
||||
// 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
|
||||
// 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");
|
||||
}
|
||||
|
||||
// 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");
|
||||
}
|
||||
|
||||
// context length must fit in 2 unsigned bytes.
|
||||
if ((context != null) && (context.length > 0xFFFF)) {
|
||||
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, (keyAlg == null) ? "TlsKey" : keyAlg,
|
||||
label, seed, length,
|
||||
hashAlg.name, hashAlg.hashLength, hashAlg.blockSize);
|
||||
KeyGenerator kg = KeyGenerator.getInstance(prfAlg);
|
||||
kg.init(spec);
|
||||
SecretKey key = kg.generateKey();
|
||||
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);
|
||||
}
|
||||
} 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.
|
||||
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 SecretKey exportKeyingMaterialKey(String keyAlg,
|
||||
String label, byte[] context, int length) throws SSLKeyException {
|
||||
|
||||
Objects.requireNonNull(keyAlg, "keyAlg can not be null");
|
||||
if (keyAlg.isEmpty()) {
|
||||
throw new IllegalArgumentException(
|
||||
"keyAlg is empty");
|
||||
}
|
||||
|
||||
return (SecretKey) exportKeyingMaterial(keyAlg, label, context,
|
||||
length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate Exported Key Material (EKM) calculated according to the
|
||||
* algorithms defined in RFCs 5705/8446.
|
||||
*/
|
||||
@Override
|
||||
public byte[] exportKeyingMaterialData(
|
||||
String label, byte[] context, int length) throws SSLKeyException {
|
||||
return (byte[])exportKeyingMaterial(null, label, context, length);
|
||||
}
|
||||
|
||||
/** Returns a string representation of this SSL session */
|
||||
@Override
|
||||
public String toString() {
|
||||
|
@ -356,6 +356,9 @@ final class ServerHello {
|
||||
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 @@ final class ServerHello {
|
||||
chc.sslConfig.maximumPacketSize);
|
||||
}
|
||||
|
||||
chc.handshakeSession.setRandoms(chc.clientHelloRandom,
|
||||
chc.serverHelloRandom);
|
||||
|
||||
//
|
||||
// update
|
||||
//
|
||||
|
@ -276,6 +276,7 @@ final class P11SecretKeyFactory extends SecretKeyFactorySpi {
|
||||
putKeyInfo(new TLSKeyInfo("TlsClientAppTrafficSecret"));
|
||||
putKeyInfo(new TLSKeyInfo("TlsClientHandshakeTrafficSecret"));
|
||||
putKeyInfo(new TLSKeyInfo("TlsEarlySecret"));
|
||||
putKeyInfo(new TLSKeyInfo("TlsExporterMasterSecret"));
|
||||
putKeyInfo(new TLSKeyInfo("TlsFinishedSecret"));
|
||||
putKeyInfo(new TLSKeyInfo("TlsHandshakeSecret"));
|
||||
putKeyInfo(new TLSKeyInfo("TlsKey"));
|
||||
|
@ -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
|
||||
@ -155,7 +155,7 @@ final class P11TlsPrfGenerator extends KeyGeneratorSpi {
|
||||
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");
|
||||
return new SecretKeySpec(out, spec.getKeyAlg());
|
||||
} catch (PKCS11Exception e) {
|
||||
throw new ProviderException("Could not calculate PRF", e);
|
||||
} finally {
|
||||
@ -181,7 +181,7 @@ final class P11TlsPrfGenerator extends KeyGeneratorSpi {
|
||||
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");
|
||||
return new SecretKeySpec(out, spec.getKeyAlg());
|
||||
} catch (PKCS11Exception e) {
|
||||
throw new ProviderException("Could not calculate PRF", e);
|
||||
} finally {
|
||||
@ -201,7 +201,7 @@ final class P11TlsPrfGenerator extends KeyGeneratorSpi {
|
||||
session = token.getOpSession();
|
||||
token.p11.C_DeriveKey(session.id(),
|
||||
new CK_MECHANISM(mechanism, params), keyID, null);
|
||||
return new SecretKeySpec(out, "TlsPrf");
|
||||
return new SecretKeySpec(out, spec.getKeyAlg());
|
||||
} catch (PKCS11Exception e) {
|
||||
throw new ProviderException("Could not calculate PRF", e);
|
||||
} finally {
|
||||
|
@ -0,0 +1,573 @@
|
||||
/*
|
||||
* 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
|
||||
* 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 /test/lib /test/jdk/sun/security/pkcs11
|
||||
* @build SSLEngineTemplate
|
||||
* @run main/othervm ExportKeyingMaterialTests
|
||||
* @run main/othervm ExportKeyingMaterialTests PKCS11
|
||||
*/
|
||||
|
||||
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;
|
||||
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.
|
||||
* <P>
|
||||
* The test creates two SSLEngines, simulating a client and server.
|
||||
* The "transport" layer consists two byte buffers: think of them
|
||||
* as directly connected pipes.
|
||||
* <P>
|
||||
* 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.
|
||||
* <P>
|
||||
* When this application runs, notice that several messages
|
||||
* (wrap/unwrap) pass before any application data is consumed or
|
||||
* produced.
|
||||
*/
|
||||
public class ExportKeyingMaterialTests extends SSLEngineTemplate {
|
||||
|
||||
private String protocol;
|
||||
private String ciphersuite;
|
||||
|
||||
protected ExportKeyingMaterialTests(String protocol, String ciphersuite)
|
||||
throws Exception {
|
||||
super();
|
||||
this.protocol = protocol;
|
||||
this.ciphersuite = ciphersuite;
|
||||
}
|
||||
|
||||
/*
|
||||
* Configure the engines.
|
||||
*/
|
||||
private void configureEngines(SSLEngine clientEngine,
|
||||
SSLEngine serverEngine) {
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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.
|
||||
|
||||
// Use appropriate protocol/ciphersuite combos for TLSv1.3
|
||||
new ExportKeyingMaterialTests(
|
||||
"TLSv1.3", "TLS_AES_128_GCM_SHA256").runTest();
|
||||
new ExportKeyingMaterialTests(
|
||||
"TLSv1.3", "TLS_AES_256_GCM_SHA384").runTest();
|
||||
new ExportKeyingMaterialTests(
|
||||
"TLSv1.3", "TLS_CHACHA20_POLY1305_SHA256").runTest();
|
||||
|
||||
// Try the various GCM suites for TLSv1.2
|
||||
new ExportKeyingMaterialTests(
|
||||
"TLSv1.2", "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384").runTest();
|
||||
new ExportKeyingMaterialTests(
|
||||
"TLSv1.2", "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256").runTest();
|
||||
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 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 ExportKeyingMaterialTests(
|
||||
"TLSv1.1", "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA").runTest();
|
||||
new ExportKeyingMaterialTests(
|
||||
"TLSv1.1", "TLS_DHE_RSA_WITH_AES_256_CBC_SHA").runTest();
|
||||
new ExportKeyingMaterialTests(
|
||||
"TLSv1.1", "TLS_RSA_WITH_AES_256_CBC_SHA").runTest();
|
||||
|
||||
new ExportKeyingMaterialTests(
|
||||
"TLSv1", "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA").runTest();
|
||||
new ExportKeyingMaterialTests(
|
||||
"TLSv1", "TLS_DHE_RSA_WITH_AES_256_CBC_SHA").runTest();
|
||||
new ExportKeyingMaterialTests(
|
||||
"TLSv1", "TLS_RSA_WITH_AES_256_CBC_SHA").runTest();
|
||||
|
||||
try {
|
||||
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) {
|
||||
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;
|
||||
|
||||
configureEngines(clientEngine, serverEngine);
|
||||
|
||||
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 clientSession,
|
||||
ExtendedSSLSession serverSession) throws Exception {
|
||||
|
||||
SecretKey clientKey, serverKey;
|
||||
|
||||
// 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);
|
||||
|
||||
// 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(
|
||||
"Generic", "hello", bytes, 128);
|
||||
serverKey = serverSession.exportKeyingMaterialKey(
|
||||
"Generic", "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,
|
||||
"Data: Equal inputs but exporters are not equal");
|
||||
log("Data: Equal inputs test passed");
|
||||
|
||||
// Different labels, now use exportKeyingMaterialData() for coverage
|
||||
clientKey = clientSession.exportKeyingMaterialKey(
|
||||
"Generic", "hello", bytes, 128);
|
||||
serverKey = serverSession.exportKeyingMaterialKey(
|
||||
"Generic", "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,
|
||||
"Data: Different labels but exporters same");
|
||||
log("Data: Different labels test passed");
|
||||
|
||||
// Different output sizes
|
||||
clientKey = clientSession.exportKeyingMaterialKey(
|
||||
"Generic", "hello", bytes, 128);
|
||||
serverKey = serverSession.exportKeyingMaterialKey(
|
||||
"Generic", "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",
|
||||
bytes, 127);
|
||||
assertEquals(clientBytes.length, 128, "client length != 128");
|
||||
assertEquals(serverBytes.length, 127, "server length != 127");
|
||||
assertNotEqualsByteArray(clientBytes, serverBytes,
|
||||
"Data: Different output sizes but exporters same");
|
||||
log("Data: Different output size test passed");
|
||||
|
||||
// Different context values
|
||||
clientKey = clientSession.exportKeyingMaterialKey(
|
||||
"Generic", "hello", bytes, 128);
|
||||
serverKey = serverSession.exportKeyingMaterialKey(
|
||||
"Generic", "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,
|
||||
"Data: Different context but exporters same");
|
||||
log("Data: Different context test passed");
|
||||
|
||||
// Different context sizes
|
||||
clientKey = clientSession.exportKeyingMaterialKey(
|
||||
"Generic", "hello", bytes, 128);
|
||||
serverKey = serverSession.exportKeyingMaterialKey(
|
||||
"Generic", "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,
|
||||
"Data: Different context sizes but exporters same");
|
||||
log("Data: Different context sizes test passed");
|
||||
|
||||
// No context, but otherwise the same
|
||||
clientKey = clientSession.exportKeyingMaterialKey(
|
||||
"Generic", "hello", null, 128);
|
||||
serverKey = serverSession.exportKeyingMaterialKey(
|
||||
"Generic", "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,
|
||||
"Data: No context and exporters are not the same");
|
||||
log("Data: No context test passed");
|
||||
|
||||
// Smaller key size
|
||||
clientKey = clientSession.exportKeyingMaterialKey(
|
||||
"Generic", "hello", bytes, 5);
|
||||
serverKey = serverSession.exportKeyingMaterialKey(
|
||||
"Generic", "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, 5);
|
||||
serverBytes = serverSession.exportKeyingMaterialData("hello",
|
||||
bytes, 5);
|
||||
assertEqualsByteArray(clientBytes, serverBytes,
|
||||
"Data: Smaller key size should be the same");
|
||||
log("Data: Smaller key size test passed");
|
||||
|
||||
// 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");
|
||||
} 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");
|
||||
} catch (NullPointerException e) {
|
||||
log("null label test passed");
|
||||
}
|
||||
|
||||
try {
|
||||
clientSession.exportKeyingMaterialData("", bytes, 128);
|
||||
throw new Exception("empty label accepted");
|
||||
} catch (IllegalArgumentException e) {
|
||||
log("empty label 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");
|
||||
}
|
||||
|
||||
// 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":
|
||||
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");
|
||||
}
|
||||
|
||||
// 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:
|
||||
throw new RuntimeException("Unknown protocol: " + clientSession.getProtocol());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user