8328608: Multiple NewSessionTicket support for TLS
Reviewed-by: djelinski
This commit is contained in:
parent
379f3db001
commit
0c2b175898
@ -1139,7 +1139,9 @@ final class Finished {
|
||||
|
||||
//
|
||||
// produce
|
||||
NewSessionTicket.t13PosthandshakeProducer.produce(shc);
|
||||
if (SSLConfiguration.serverNewSessionTicketCount > 0) {
|
||||
NewSessionTicket.t13PosthandshakeProducer.produce(shc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2018, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2018, 2024, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -25,11 +25,11 @@
|
||||
package sun.security.ssl;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.SecureRandom;
|
||||
import java.text.MessageFormat;
|
||||
import java.util.Arrays;
|
||||
import java.util.Locale;
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.net.ssl.SSLHandshakeException;
|
||||
@ -118,11 +118,6 @@ final class NewSessionTicket {
|
||||
this.ticket = Record.getBytes16(m);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SSLHandshake handshakeType() {
|
||||
return NEW_SESSION_TICKET;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int messageLength() {
|
||||
return 4 + // ticketLifetime
|
||||
@ -221,11 +216,6 @@ final class NewSessionTicket {
|
||||
this.extensions = new SSLExtensions(this, m, supportedExtensions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SSLHandshake handshakeType() {
|
||||
return NEW_SESSION_TICKET;
|
||||
}
|
||||
|
||||
int getTicketAgeAdd() {
|
||||
return ticketAgeAdd;
|
||||
}
|
||||
@ -301,7 +291,7 @@ final class NewSessionTicket {
|
||||
"tls13 resumption".getBytes(), nonce, hashAlg.hashLength);
|
||||
return hkdf.expand(resumptionMasterSecret, hkdfInfo,
|
||||
hashAlg.hashLength, "TlsPreSharedKey");
|
||||
} catch (GeneralSecurityException gse) {
|
||||
} catch (GeneralSecurityException gse) {
|
||||
throw new SSLHandshakeException("Could not derive PSK", gse);
|
||||
}
|
||||
}
|
||||
@ -332,8 +322,7 @@ final class NewSessionTicket {
|
||||
// Is this session resumable?
|
||||
if (!hc.handshakeSession.isRejoinable()) {
|
||||
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
|
||||
SSLLogger.fine(
|
||||
"No session ticket produced: " +
|
||||
SSLLogger.fine("No session ticket produced: " +
|
||||
"session is not resumable");
|
||||
}
|
||||
|
||||
@ -351,8 +340,7 @@ final class NewSessionTicket {
|
||||
if (pkemSpec == null ||
|
||||
!pkemSpec.contains(PskKeyExchangeMode.PSK_DHE_KE)) {
|
||||
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
|
||||
SSLLogger.fine(
|
||||
"No session ticket produced: " +
|
||||
SSLLogger.fine("No session ticket produced: " +
|
||||
"client does not support psk_dhe_ke");
|
||||
}
|
||||
|
||||
@ -363,8 +351,7 @@ final class NewSessionTicket {
|
||||
// using an allowable PSK exchange key mode.
|
||||
if (!hc.handshakeSession.isPSKable()) {
|
||||
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
|
||||
SSLLogger.fine(
|
||||
"No session ticket produced: " +
|
||||
SSLLogger.fine("No session ticket produced: " +
|
||||
"No session ticket allowed in this session");
|
||||
}
|
||||
|
||||
@ -375,76 +362,113 @@ final class NewSessionTicket {
|
||||
// get a new session ID
|
||||
SSLSessionContextImpl sessionCache = (SSLSessionContextImpl)
|
||||
hc.sslContext.engineGetServerSessionContext();
|
||||
SessionId newId = new SessionId(true,
|
||||
hc.sslContext.getSecureRandom());
|
||||
|
||||
SecretKey resumptionMasterSecret =
|
||||
hc.handshakeSession.getResumptionMasterSecret();
|
||||
if (resumptionMasterSecret == null) {
|
||||
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
|
||||
SSLLogger.fine(
|
||||
"No session ticket produced: " +
|
||||
"no resumption secret");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// construct the PSK and handshake message
|
||||
BigInteger nonce = hc.handshakeSession.incrTicketNonceCounter();
|
||||
byte[] nonceArr = nonce.toByteArray();
|
||||
SecretKey psk = derivePreSharedKey(
|
||||
hc.negotiatedCipherSuite.hashAlg,
|
||||
resumptionMasterSecret, nonceArr);
|
||||
|
||||
int sessionTimeoutSeconds = sessionCache.getSessionTimeout();
|
||||
if (sessionTimeoutSeconds > MAX_TICKET_LIFETIME) {
|
||||
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
|
||||
SSLLogger.fine(
|
||||
"No session ticket produced: " +
|
||||
"session timeout");
|
||||
SSLLogger.fine("No session ticket produced: " +
|
||||
"session timeout is too long");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
NewSessionTicketMessage nstm = null;
|
||||
// Send NewSessionTickets to the client based
|
||||
if (SSLConfiguration.serverNewSessionTicketCount > 0) {
|
||||
int i = 0;
|
||||
NewSessionTicketMessage nstm;
|
||||
while (i < SSLConfiguration.serverNewSessionTicketCount) {
|
||||
nstm = generateNST(hc, sessionCache);
|
||||
if (nstm == null) {
|
||||
break;
|
||||
}
|
||||
nstm.write(hc.handshakeOutput);
|
||||
i++;
|
||||
}
|
||||
|
||||
hc.handshakeOutput.flush();
|
||||
}
|
||||
/*
|
||||
* With large NST counts, a client that quickly closes after
|
||||
* TLS Finished completes can cause SocketExceptions such as:
|
||||
* Windows servers read-side throwing SocketException:
|
||||
* "An established connection was aborted by the software in
|
||||
* your host machine", which relates to error WSAECONNABORTED.
|
||||
* A SocketException caused by a "broken pipe" has been observed on
|
||||
* other systems.
|
||||
* These are very unlikely situations when client and server are on
|
||||
* different machines.
|
||||
*
|
||||
* RFC 8446 does not put requirements when an NST needs to be
|
||||
* sent, but it should be sent very soon after TLS Finished for
|
||||
* clients that will quickly resume to create more sessions.
|
||||
* TLS 1.3 is different from TLS 1.2, there is more data the client
|
||||
* should be aware of
|
||||
*/
|
||||
|
||||
// See note on TransportContext.needHandshakeFinishedStatus.
|
||||
//
|
||||
// Reset the needHandshakeFinishedStatus flag. The delivery
|
||||
// of this post-handshake message will indicate the FINISHED
|
||||
// handshake status. It is not needed to have a follow-on
|
||||
// SSLEngine.wrap() any longer.
|
||||
if (hc.conContext.needHandshakeFinishedStatus) {
|
||||
hc.conContext.needHandshakeFinishedStatus = false;
|
||||
}
|
||||
|
||||
// clean the post handshake context
|
||||
hc.conContext.finishPostHandshake();
|
||||
|
||||
// The message has been delivered.
|
||||
return null;
|
||||
}
|
||||
|
||||
private NewSessionTicketMessage generateNST(HandshakeContext hc,
|
||||
SSLSessionContextImpl sessionCache) throws IOException {
|
||||
|
||||
NewSessionTicketMessage nstm;
|
||||
SessionId newId = new SessionId(true,
|
||||
hc.sslContext.getSecureRandom());
|
||||
|
||||
// construct the PSK and handshake message
|
||||
byte[] nonce = hc.handshakeSession.incrTicketNonceCounter();
|
||||
|
||||
SSLSessionImpl sessionCopy =
|
||||
new SSLSessionImpl(hc.handshakeSession, newId);
|
||||
sessionCopy.setPreSharedKey(psk);
|
||||
new SSLSessionImpl(hc.handshakeSession, newId);
|
||||
sessionCopy.setPreSharedKey(derivePreSharedKey(
|
||||
hc.negotiatedCipherSuite.hashAlg,
|
||||
hc.handshakeSession.getResumptionMasterSecret(), nonce));
|
||||
sessionCopy.setPskIdentity(newId.getId());
|
||||
|
||||
// If a stateless ticket is allowed, attempt to make one
|
||||
if (hc.statelessResumption &&
|
||||
hc.handshakeSession.isStatelessable()) {
|
||||
nstm = new T13NewSessionTicketMessage(hc,
|
||||
sessionTimeoutSeconds,
|
||||
sessionCache.getSessionTimeout(),
|
||||
hc.sslContext.getSecureRandom(),
|
||||
nonceArr,
|
||||
nonce,
|
||||
new SessionTicketSpec().encrypt(hc, sessionCopy));
|
||||
// If ticket construction failed, switch to session cache
|
||||
if (!nstm.isValid()) {
|
||||
hc.statelessResumption = false;
|
||||
} else {
|
||||
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
|
||||
SSLLogger.fine(
|
||||
"Produced NewSessionTicket stateless " +
|
||||
SSLLogger.fine("Produced NewSessionTicket stateless " +
|
||||
"post-handshake message", nstm);
|
||||
}
|
||||
}
|
||||
return nstm;
|
||||
}
|
||||
|
||||
// If a session cache ticket is being used, make one
|
||||
if (!hc.statelessResumption ||
|
||||
!hc.handshakeSession.isStatelessable()) {
|
||||
nstm = new T13NewSessionTicketMessage(hc, sessionTimeoutSeconds,
|
||||
hc.sslContext.getSecureRandom(), nonceArr,
|
||||
newId.getId());
|
||||
nstm = new T13NewSessionTicketMessage(hc,
|
||||
sessionCache.getSessionTimeout(),
|
||||
hc.sslContext.getSecureRandom(), nonce,
|
||||
newId.getId());
|
||||
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
|
||||
SSLLogger.fine(
|
||||
"Produced NewSessionTicket post-handshake message",
|
||||
nstm);
|
||||
SSLLogger.fine("Produced NewSessionTicket " +
|
||||
"post-handshake message", nstm);
|
||||
}
|
||||
|
||||
// create and cache the new session
|
||||
@ -453,29 +477,13 @@ final class NewSessionTicket {
|
||||
hc.handshakeSession.addChild(sessionCopy);
|
||||
sessionCopy.setTicketAgeAdd(nstm.getTicketAgeAdd());
|
||||
sessionCache.put(sessionCopy);
|
||||
return nstm;
|
||||
}
|
||||
|
||||
// Output the handshake message.
|
||||
if (nstm != null) {
|
||||
// should never be null
|
||||
nstm.write(hc.handshakeOutput);
|
||||
hc.handshakeOutput.flush();
|
||||
|
||||
// See note on TransportContext.needHandshakeFinishedStatus.
|
||||
//
|
||||
// Reset the needHandshakeFinishedStatus flag. The delivery
|
||||
// of this post-handshake message will indicate the FINISHED
|
||||
// handshake status. It is not needed to have a follow-on
|
||||
// SSLEngine.wrap() any longer.
|
||||
if (hc.conContext.needHandshakeFinishedStatus) {
|
||||
hc.conContext.needHandshakeFinishedStatus = false;
|
||||
}
|
||||
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
|
||||
SSLLogger.fine("No NewSessionTicket created");
|
||||
}
|
||||
|
||||
// clean the post handshake context
|
||||
hc.conContext.finishPostHandshake();
|
||||
|
||||
// The message has been delivered.
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -497,8 +505,9 @@ final class NewSessionTicket {
|
||||
|
||||
ServerHandshakeContext shc = (ServerHandshakeContext)context;
|
||||
|
||||
// Is this session resumable?
|
||||
if (!shc.handshakeSession.isRejoinable()) {
|
||||
// Are new tickets allowed? If so, is this session resumable?
|
||||
if (SSLConfiguration.serverNewSessionTicketCount == 0 ||
|
||||
!shc.handshakeSession.isRejoinable()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -578,7 +587,6 @@ final class NewSessionTicket {
|
||||
"Discarding NewSessionTicket with lifetime " +
|
||||
nstm.ticketLifetime, nstm);
|
||||
}
|
||||
sessionCache.remove(hc.handshakeSession.getSessionId());
|
||||
return;
|
||||
}
|
||||
|
||||
@ -619,13 +627,19 @@ final class NewSessionTicket {
|
||||
sessionCopy.setPreSharedKey(psk);
|
||||
sessionCopy.setTicketAgeAdd(nstm.getTicketAgeAdd());
|
||||
sessionCopy.setPskIdentity(nstm.ticket);
|
||||
sessionCache.put(sessionCopy);
|
||||
sessionCache.put(sessionCopy, sessionCopy.isPSK());
|
||||
|
||||
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
|
||||
SSLLogger.fine("MultiNST PSK (Server): " +
|
||||
Utilities.toHexString(Arrays.copyOf(nstm.ticket, 16)));
|
||||
}
|
||||
|
||||
// clean the post handshake context
|
||||
hc.conContext.finishPostHandshake();
|
||||
}
|
||||
}
|
||||
|
||||
/* TLS 1.2 spec does not specify multiple NST behavior.*/
|
||||
private static final
|
||||
class T12NewSessionTicketConsumer implements SSLConsumer {
|
||||
// Prevent instantiation of this class.
|
||||
@ -674,8 +688,7 @@ final class NewSessionTicket {
|
||||
|
||||
hc.handshakeSession.setPskIdentity(nstm.ticket);
|
||||
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
|
||||
SSLLogger.fine("Consuming NewSessionTicket\n" +
|
||||
nstm.toString());
|
||||
SSLLogger.fine("Consuming NewSessionTicket\n" + nstm);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2015, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2015, 2024, 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
|
||||
@ -699,11 +699,13 @@ final class PreSharedKeyExtension {
|
||||
//The session cannot be used again. Remove it from the cache.
|
||||
SSLSessionContextImpl sessionCache = (SSLSessionContextImpl)
|
||||
chc.sslContext.engineGetClientSessionContext();
|
||||
sessionCache.remove(chc.resumingSession.getSessionId());
|
||||
sessionCache.remove(chc.resumingSession.getSessionId(), true);
|
||||
|
||||
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
|
||||
SSLLogger.fine(
|
||||
"Found resumable session. Preparing PSK message.");
|
||||
SSLLogger.fine(
|
||||
"MultiNST PSK (Client): " + Utilities.toHexString(Arrays.copyOf(chc.pskIdentity, 16)));
|
||||
}
|
||||
|
||||
List<PskIdentity> identities = new ArrayList<>();
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2018, 2023, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2018, 2024, 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
|
||||
@ -121,6 +121,11 @@ final class SSLConfiguration implements Cloneable {
|
||||
static final boolean enableDtlsResumeCookie = Utilities.getBooleanProperty(
|
||||
"jdk.tls.enableDtlsResumeCookie", true);
|
||||
|
||||
// Number of NewSessionTickets that will be sent by the server.
|
||||
static final int serverNewSessionTicketCount;
|
||||
// Default for NewSessionTickets
|
||||
static final int SERVER_NST_DEFAULT = 1;
|
||||
|
||||
// Is the extended_master_secret extension supported?
|
||||
static {
|
||||
boolean supportExtendedMasterSecret = Utilities.getBooleanProperty(
|
||||
@ -182,7 +187,7 @@ final class SSLConfiguration implements Cloneable {
|
||||
* - Otherwise it is set to a default value of 10.
|
||||
*/
|
||||
Integer inboundServerLen = GetIntegerAction.privilegedGetProperty(
|
||||
"jdk.tls.client.maxInboundCertificateChainLength");
|
||||
"jdk.tls.client.maxInboundCertificateChainLength");
|
||||
|
||||
// Default for jdk.tls.client.maxInboundCertificateChainLength is 10
|
||||
if (inboundServerLen == null || inboundServerLen < 0) {
|
||||
@ -191,6 +196,33 @@ final class SSLConfiguration implements Cloneable {
|
||||
} else {
|
||||
maxInboundServerCertChainLen = inboundServerLen;
|
||||
}
|
||||
|
||||
/*
|
||||
* jdk.tls.server.newSessionTicketCount system property
|
||||
* Sets the number of NewSessionTickets sent to a TLS 1.3 resumption
|
||||
* client. The value must be between 0 and 10. Default is defined by
|
||||
* SERVER_NST_DEFAULT.
|
||||
*/
|
||||
Integer nstServerCount = GetIntegerAction.privilegedGetProperty(
|
||||
"jdk.tls.server.newSessionTicketCount");
|
||||
if (nstServerCount == null || nstServerCount < 0 ||
|
||||
nstServerCount > 10) {
|
||||
serverNewSessionTicketCount = SERVER_NST_DEFAULT;
|
||||
if (nstServerCount != null && SSLLogger.isOn &&
|
||||
SSLLogger.isOn("ssl,handshake")) {
|
||||
SSLLogger.fine(
|
||||
"jdk.tls.server.newSessionTicketCount defaults to " +
|
||||
SERVER_NST_DEFAULT + " as the property was not " +
|
||||
"between 0 and 10");
|
||||
}
|
||||
} else {
|
||||
serverNewSessionTicketCount = nstServerCount;
|
||||
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
|
||||
SSLLogger.fine(
|
||||
"jdk.tls.server.newSessionTicketCount set to " +
|
||||
serverNewSessionTicketCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SSLConfiguration(SSLContextImpl sslContext, boolean isClientMode) {
|
||||
|
@ -416,11 +416,12 @@ final class SSLEngineImpl extends SSLEngine implements SSLTransport {
|
||||
HandshakeStatus currentHandshakeStatus) throws IOException {
|
||||
// Don't bother to kickstart if handshaking is in progress, or if the
|
||||
// connection is not duplex-open.
|
||||
if ((conContext.handshakeContext == null) &&
|
||||
conContext.protocolVersion.useTLS13PlusSpec() &&
|
||||
!conContext.isOutboundClosed() &&
|
||||
!conContext.isInboundClosed() &&
|
||||
!conContext.isBroken) {
|
||||
if (SSLConfiguration.serverNewSessionTicketCount > 0 &&
|
||||
conContext.handshakeContext == null &&
|
||||
conContext.protocolVersion.useTLS13PlusSpec() &&
|
||||
!conContext.isOutboundClosed() &&
|
||||
!conContext.isInboundClosed() &&
|
||||
!conContext.isBroken) {
|
||||
if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
|
||||
SSLLogger.finest("trigger NST");
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 1999, 2023, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 1999, 2024, 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
|
||||
@ -63,6 +63,7 @@ import sun.security.util.Cache;
|
||||
|
||||
final class SSLSessionContextImpl implements SSLSessionContext {
|
||||
private static final int DEFAULT_MAX_CACHE_SIZE = 20480;
|
||||
private static final int DEFAULT_MAX_QUEUE_SIZE = 10;
|
||||
// Default lifetime of a session. 24 hours
|
||||
static final int DEFAULT_SESSION_TIMEOUT = 86400;
|
||||
|
||||
@ -87,14 +88,17 @@ final class SSLSessionContextImpl implements SSLSessionContext {
|
||||
cacheLimit = getDefaults(server); // default cache size
|
||||
|
||||
// use soft reference
|
||||
sessionCache = Cache.newSoftMemoryCache(cacheLimit, timeout);
|
||||
sessionHostPortCache = Cache.newSoftMemoryCache(cacheLimit, timeout);
|
||||
if (server) {
|
||||
sessionCache = Cache.newSoftMemoryCache(cacheLimit, timeout);
|
||||
sessionHostPortCache = Cache.newSoftMemoryCache(cacheLimit, timeout);
|
||||
keyHashMap = new ConcurrentHashMap<>();
|
||||
// Should be "randomly generated" according to RFC 5077,
|
||||
// but doesn't necessarily has to be a true random number.
|
||||
// but doesn't necessarily have to be a true random number.
|
||||
currentKeyID = new Random(System.nanoTime()).nextInt();
|
||||
} else {
|
||||
sessionCache = Cache.newSoftMemoryCache(cacheLimit, timeout);
|
||||
sessionHostPortCache = Cache.newSoftMemoryQueue(cacheLimit, timeout,
|
||||
DEFAULT_MAX_QUEUE_SIZE);
|
||||
keyHashMap = Map.of();
|
||||
}
|
||||
}
|
||||
@ -277,12 +281,22 @@ final class SSLSessionContextImpl implements SSLSessionContext {
|
||||
// time it created, which is a little longer than the expected. So
|
||||
// please do check isTimedout() while getting entry from the cache.
|
||||
void put(SSLSessionImpl s) {
|
||||
put(s, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Put an entry in the cache
|
||||
* @param s SSLSessionImpl entry to be stored
|
||||
* @param canQueue True if multiple entries may exist under one
|
||||
* session entry.
|
||||
*/
|
||||
void put(SSLSessionImpl s, boolean canQueue) {
|
||||
sessionCache.put(s.getSessionId(), s);
|
||||
|
||||
// If no hostname/port info is available, don't add this one.
|
||||
if ((s.getPeerHost() != null) && (s.getPeerPort() != -1)) {
|
||||
sessionHostPortCache.put(
|
||||
getKey(s.getPeerHost(), s.getPeerPort()), s);
|
||||
getKey(s.getPeerHost(), s.getPeerPort()), s, canQueue);
|
||||
}
|
||||
|
||||
s.setContext(this);
|
||||
@ -290,11 +304,17 @@ final class SSLSessionContextImpl implements SSLSessionContext {
|
||||
|
||||
// package-private method, remove a cached SSLSession
|
||||
void remove(SessionId key) {
|
||||
remove(key, false);
|
||||
}
|
||||
void remove(SessionId key, boolean isClient) {
|
||||
SSLSessionImpl s = sessionCache.get(key);
|
||||
if (s != null) {
|
||||
sessionCache.remove(key);
|
||||
sessionHostPortCache.remove(
|
||||
// A client keeps the cache entry for queued NST resumption.
|
||||
if (!isClient) {
|
||||
sessionHostPortCache.remove(
|
||||
getKey(s.getPeerHost(), s.getPeerPort()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 1996, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 1996, 2024, 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
|
||||
@ -38,7 +38,6 @@ import java.util.Arrays;
|
||||
import java.util.Queue;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
@ -132,7 +131,11 @@ final class SSLSessionImpl extends ExtendedSSLSession {
|
||||
private final List<SNIServerName> requestedServerNames;
|
||||
|
||||
// Counter used to create unique nonces in NewSessionTicket
|
||||
private BigInteger ticketNonceCounter = BigInteger.ONE;
|
||||
private byte ticketNonceCounter = 1;
|
||||
|
||||
// This boolean is true when a new set of NewSessionTickets are needed after
|
||||
// the initial ones sent after the handshake.
|
||||
boolean updateNST = false;
|
||||
|
||||
// The endpoint identification algorithm used to check certificates
|
||||
// in this session.
|
||||
@ -492,7 +495,7 @@ final class SSLSessionImpl extends ExtendedSSLSession {
|
||||
// Length of pre-shared key algorithm (one byte)
|
||||
i = buf.get();
|
||||
b = new byte[i];
|
||||
buf.get(b, 0 , i);
|
||||
buf.get(b, 0, i);
|
||||
String alg = new String(b);
|
||||
// Get length of encoding
|
||||
i = Short.toUnsignedInt(buf.getShort());
|
||||
@ -501,8 +504,13 @@ final class SSLSessionImpl extends ExtendedSSLSession {
|
||||
buf.get(b);
|
||||
this.preSharedKey = new SecretKeySpec(b, alg);
|
||||
// Get identity len
|
||||
this.pskIdentity = new byte[buf.get()];
|
||||
buf.get(pskIdentity);
|
||||
i = buf.get();
|
||||
if (i > 0) {
|
||||
this.pskIdentity = new byte[buf.get()];
|
||||
buf.get(pskIdentity);
|
||||
} else {
|
||||
this.pskIdentity = null;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new SSLException("Failed local certs of session.");
|
||||
@ -715,14 +723,12 @@ final class SSLSessionImpl extends ExtendedSSLSession {
|
||||
this.pskIdentity = pskIdentity;
|
||||
}
|
||||
|
||||
BigInteger incrTicketNonceCounter() {
|
||||
BigInteger result = ticketNonceCounter;
|
||||
ticketNonceCounter = ticketNonceCounter.add(BigInteger.ONE);
|
||||
return result;
|
||||
byte[] incrTicketNonceCounter() {
|
||||
return new byte[] {ticketNonceCounter++};
|
||||
}
|
||||
|
||||
boolean isPSKable() {
|
||||
return (ticketNonceCounter.compareTo(BigInteger.ZERO) > 0);
|
||||
return (ticketNonceCounter > 0);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -781,6 +787,10 @@ final class SSLSessionImpl extends ExtendedSSLSession {
|
||||
return pskIdentity;
|
||||
}
|
||||
|
||||
public boolean isPSK() {
|
||||
return (pskIdentity != null && pskIdentity.length > 0);
|
||||
}
|
||||
|
||||
void setPeerCertificates(X509Certificate[] peer) {
|
||||
if (peerCerts == null) {
|
||||
peerCerts = peer;
|
||||
@ -1230,7 +1240,6 @@ final class SSLSessionImpl extends ExtendedSSLSession {
|
||||
* sessions can be shared across different protection domains.
|
||||
*/
|
||||
private final ConcurrentHashMap<SecureKey, Object> boundValues;
|
||||
boolean updateNST;
|
||||
|
||||
/**
|
||||
* Assigns a session value. Session change events are given if
|
||||
|
@ -1321,7 +1321,6 @@ public final class SSLSocketImpl
|
||||
}
|
||||
// Check if NewSessionTicket PostHandshake message needs to be sent
|
||||
if (conContext.conSession.updateNST) {
|
||||
conContext.conSession.updateNST = false;
|
||||
tryNewSessionTicket();
|
||||
}
|
||||
}
|
||||
@ -1556,15 +1555,17 @@ public final class SSLSocketImpl
|
||||
private void tryNewSessionTicket() throws IOException {
|
||||
// Don't bother to kickstart if handshaking is in progress, or if the
|
||||
// connection is not duplex-open.
|
||||
if (!conContext.sslConfig.isClientMode &&
|
||||
conContext.protocolVersion.useTLS13PlusSpec() &&
|
||||
conContext.handshakeContext == null &&
|
||||
!conContext.isOutboundClosed() &&
|
||||
!conContext.isInboundClosed() &&
|
||||
!conContext.isBroken) {
|
||||
if (SSLConfiguration.serverNewSessionTicketCount > 0 &&
|
||||
!conContext.sslConfig.isClientMode &&
|
||||
conContext.protocolVersion.useTLS13PlusSpec() &&
|
||||
conContext.handshakeContext == null &&
|
||||
!conContext.isOutboundClosed() &&
|
||||
!conContext.isInboundClosed() &&
|
||||
!conContext.isBroken) {
|
||||
if (SSLLogger.isOn && SSLLogger.isOn("ssl")) {
|
||||
SSLLogger.finest("trigger new session ticket");
|
||||
}
|
||||
conContext.conSession.updateNST = false;
|
||||
NewSessionTicket.t13PosthandshakeProducer.produce(
|
||||
new PostHandshakeContext(conContext));
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2002, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2002, 2024, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -25,8 +25,12 @@
|
||||
|
||||
package sun.security.util;
|
||||
|
||||
import javax.net.ssl.SSLSession;
|
||||
import java.lang.ref.ReferenceQueue;
|
||||
import java.lang.ref.SoftReference;
|
||||
import java.util.*;
|
||||
import java.lang.ref.*;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
* Abstract base class and factory for caches. A cache is a key-value mapping.
|
||||
@ -90,6 +94,15 @@ public abstract class Cache<K,V> {
|
||||
*/
|
||||
public abstract void put(K key, V value);
|
||||
|
||||
/**
|
||||
* Add V to the cache with the option to use a QueueCacheEntry if the
|
||||
* cache is configured for it. If the cache is not configured for a queue,
|
||||
* V will silently add the entry directly.
|
||||
*/
|
||||
public void put(K key, V value, boolean canQueue) {
|
||||
put(key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a value from the cache.
|
||||
*/
|
||||
@ -137,6 +150,11 @@ public abstract class Cache<K,V> {
|
||||
return new MemoryCache<>(true, size, timeout);
|
||||
}
|
||||
|
||||
public static <K,V> Cache<K,V> newSoftMemoryQueue(int size, int timeout,
|
||||
int maxQueueSize) {
|
||||
return new MemoryCache<>(true, size, timeout, maxQueueSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a new memory cache with the specified maximum size, unlimited
|
||||
* lifetime for entries, with the values held by standard references.
|
||||
@ -248,13 +266,12 @@ class NullCache<K,V> extends Cache<K,V> {
|
||||
|
||||
class MemoryCache<K,V> extends Cache<K,V> {
|
||||
|
||||
private static final float LOAD_FACTOR = 0.75f;
|
||||
|
||||
// XXXX
|
||||
// Debugging
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
private final Map<K, CacheEntry<K,V>> cacheMap;
|
||||
private int maxSize;
|
||||
final private int maxQueueSize;
|
||||
private long lifetime;
|
||||
private long nextExpirationTime = Long.MAX_VALUE;
|
||||
|
||||
@ -263,18 +280,25 @@ class MemoryCache<K,V> extends Cache<K,V> {
|
||||
private final ReferenceQueue<V> queue;
|
||||
|
||||
public MemoryCache(boolean soft, int maxSize) {
|
||||
this(soft, maxSize, 0);
|
||||
this(soft, maxSize, 0, 0);
|
||||
}
|
||||
|
||||
public MemoryCache(boolean soft, int maxSize, int lifetime) {
|
||||
this.maxSize = maxSize;
|
||||
this.lifetime = lifetime * 1000L;
|
||||
if (soft)
|
||||
this.queue = new ReferenceQueue<>();
|
||||
else
|
||||
this.queue = null;
|
||||
this(soft, maxSize, lifetime, 0);
|
||||
}
|
||||
|
||||
cacheMap = new LinkedHashMap<>(1, LOAD_FACTOR, true);
|
||||
public MemoryCache(boolean soft, int maxSize, int lifetime, int qSize) {
|
||||
this.maxSize = maxSize;
|
||||
this.maxQueueSize = qSize;
|
||||
this.lifetime = lifetime * 1000L;
|
||||
if (soft) {
|
||||
this.queue = new ReferenceQueue<>();
|
||||
} else {
|
||||
this.queue = null;
|
||||
}
|
||||
// LinkedHashMap is needed for its access order. 0.75f load factor is
|
||||
// default.
|
||||
cacheMap = new LinkedHashMap<>(1, 0.75f, true);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -338,6 +362,10 @@ class MemoryCache<K,V> extends Cache<K,V> {
|
||||
cnt++;
|
||||
} else if (nextExpirationTime > entry.getExpirationTime()) {
|
||||
nextExpirationTime = entry.getExpirationTime();
|
||||
// If this is a queue, check for some expired entries
|
||||
if (entry instanceof QueueCacheEntry<K,V> qe) {
|
||||
qe.getQueue().removeIf(e -> !e.isValid(time));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (DEBUG) {
|
||||
@ -367,18 +395,60 @@ class MemoryCache<K,V> extends Cache<K,V> {
|
||||
cacheMap.clear();
|
||||
}
|
||||
|
||||
public synchronized void put(K key, V value) {
|
||||
public void put(K key, V value) {
|
||||
put(key, value, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* This puts an entry into the cacheMap.
|
||||
*
|
||||
* If canQueue is true, V will be added using a QueueCacheEntry which
|
||||
* is added to cacheMap. If false, V is added to the cacheMap directly.
|
||||
* The caller must keep a consistent canQueue value, mixing them can
|
||||
* result in a queue being replaced with a single entry.
|
||||
*
|
||||
* This method is synchronized to avoid multiple QueueCacheEntry
|
||||
* overwriting the same key.
|
||||
*
|
||||
* @param key key to the cacheMap
|
||||
* @param value value to be stored
|
||||
* @param canQueue can the value be put into a QueueCacheEntry
|
||||
*/
|
||||
public synchronized void put(K key, V value, boolean canQueue) {
|
||||
emptyQueue();
|
||||
long expirationTime = (lifetime == 0) ? 0 :
|
||||
System.currentTimeMillis() + lifetime;
|
||||
long expirationTime =
|
||||
(lifetime == 0) ? 0 : System.currentTimeMillis() + lifetime;
|
||||
if (expirationTime < nextExpirationTime) {
|
||||
nextExpirationTime = expirationTime;
|
||||
}
|
||||
CacheEntry<K,V> newEntry = newEntry(key, value, expirationTime, queue);
|
||||
CacheEntry<K,V> oldEntry = cacheMap.put(key, newEntry);
|
||||
if (oldEntry != null) {
|
||||
oldEntry.invalidate();
|
||||
return;
|
||||
if (maxQueueSize == 0 || !canQueue) {
|
||||
CacheEntry<K,V> oldEntry = cacheMap.put(key, newEntry);
|
||||
if (oldEntry != null) {
|
||||
oldEntry.invalidate();
|
||||
}
|
||||
} else {
|
||||
CacheEntry<K, V> entry = cacheMap.get(key);
|
||||
switch (entry) {
|
||||
case QueueCacheEntry<K, V> qe -> {
|
||||
qe.putValue(newEntry);
|
||||
if (DEBUG) {
|
||||
System.out.println("QueueCacheEntry= " + qe);
|
||||
final AtomicInteger i = new AtomicInteger(1);
|
||||
qe.queue.stream().forEach(e ->
|
||||
System.out.println(i.getAndIncrement() + "= " + e));
|
||||
}
|
||||
}
|
||||
case null, default ->
|
||||
cacheMap.put(key, new QueueCacheEntry<>(key, newEntry,
|
||||
expirationTime, maxQueueSize));
|
||||
}
|
||||
|
||||
if (DEBUG) {
|
||||
System.out.println("Cache entry added: key=" +
|
||||
key.toString() + ", class=" +
|
||||
(entry != null ? entry.getClass().getName() : null));
|
||||
}
|
||||
}
|
||||
if (maxSize > 0 && cacheMap.size() > maxSize) {
|
||||
expungeExpiredEntries();
|
||||
@ -401,25 +471,37 @@ class MemoryCache<K,V> extends Cache<K,V> {
|
||||
if (entry == null) {
|
||||
return null;
|
||||
}
|
||||
long time = (lifetime == 0) ? 0 : System.currentTimeMillis();
|
||||
if (!entry.isValid(time)) {
|
||||
|
||||
if (lifetime > 0 && !entry.isValid(System.currentTimeMillis())) {
|
||||
cacheMap.remove(key);
|
||||
if (DEBUG) {
|
||||
System.out.println("Ignoring expired entry");
|
||||
}
|
||||
cacheMap.remove(key);
|
||||
return null;
|
||||
}
|
||||
|
||||
// If the value is a queue, return a queue entry.
|
||||
if (entry instanceof QueueCacheEntry<K, V> qe) {
|
||||
V result = qe.getValue(lifetime);
|
||||
if (qe.isEmpty()) {
|
||||
removeImpl(key);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
return entry.getValue();
|
||||
}
|
||||
|
||||
public synchronized void remove(Object key) {
|
||||
emptyQueue();
|
||||
removeImpl(key);
|
||||
}
|
||||
|
||||
private void removeImpl(Object key) {
|
||||
CacheEntry<K,V> entry = cacheMap.remove(key);
|
||||
if (entry != null) {
|
||||
entry.invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized V pull(Object key) {
|
||||
emptyQueue();
|
||||
CacheEntry<K,V> entry = cacheMap.remove(key);
|
||||
@ -550,9 +632,8 @@ class MemoryCache<K,V> extends Cache<K,V> {
|
||||
}
|
||||
}
|
||||
|
||||
private static class SoftCacheEntry<K,V>
|
||||
extends SoftReference<V>
|
||||
implements CacheEntry<K,V> {
|
||||
private static class SoftCacheEntry<K,V> extends SoftReference<V>
|
||||
implements CacheEntry<K,V> {
|
||||
|
||||
private K key;
|
||||
private long expirationTime;
|
||||
@ -589,6 +670,116 @@ class MemoryCache<K,V> extends Cache<K,V> {
|
||||
key = null;
|
||||
expirationTime = -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (get() instanceof SSLSession se)
|
||||
return HexFormat.of().formatHex(se.getId());
|
||||
return super.toString();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
/**
|
||||
* This CacheEntry<K,V> type allows multiple V entries to be stored in
|
||||
* one key in the cacheMap.
|
||||
*
|
||||
* This implementation is need for TLS clients that receive multiple
|
||||
* PSKs or NewSessionTickets for server resumption.
|
||||
*/
|
||||
private static class QueueCacheEntry<K,V> implements CacheEntry<K,V> {
|
||||
|
||||
// Limit the number of queue entries.
|
||||
private final int MAXQUEUESIZE;
|
||||
|
||||
final boolean DEBUG = false;
|
||||
private K key;
|
||||
private long expirationTime;
|
||||
final Queue<CacheEntry<K,V>> queue = new ConcurrentLinkedQueue<>();
|
||||
|
||||
QueueCacheEntry(K key, CacheEntry<K,V> entry, long expirationTime,
|
||||
int maxSize) {
|
||||
this.key = key;
|
||||
this.expirationTime = expirationTime;
|
||||
this.MAXQUEUESIZE = maxSize;
|
||||
queue.add(entry);
|
||||
}
|
||||
|
||||
public K getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
public V getValue() {
|
||||
return getValue(0);
|
||||
}
|
||||
|
||||
public V getValue(long lifetime) {
|
||||
long time = (lifetime == 0) ? 0 : System.currentTimeMillis();
|
||||
do {
|
||||
var entry = queue.poll();
|
||||
if (entry == null) {
|
||||
return null;
|
||||
}
|
||||
if (entry.isValid(time)) {
|
||||
return entry.getValue();
|
||||
}
|
||||
entry.invalidate();
|
||||
} while (!queue.isEmpty());
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public long getExpirationTime() {
|
||||
return expirationTime;
|
||||
}
|
||||
|
||||
public void setExpirationTime(long time) {
|
||||
expirationTime = time;
|
||||
}
|
||||
|
||||
public void putValue(CacheEntry<K,V> entry) {
|
||||
if (DEBUG) {
|
||||
System.out.println("Added to queue (size=" + queue.size() +
|
||||
"): " + entry.getKey().toString() + ", " + entry);
|
||||
}
|
||||
// Update the cache entry's expiration time to the latest entry.
|
||||
// The getValue() calls will remove expired tickets.
|
||||
expirationTime = entry.getExpirationTime();
|
||||
// Limit the number of queue entries, removing the oldest.
|
||||
if (queue.size() >= MAXQUEUESIZE) {
|
||||
queue.remove();
|
||||
}
|
||||
queue.add(entry);
|
||||
}
|
||||
|
||||
public boolean isValid(long currentTime) {
|
||||
boolean valid = (currentTime <= expirationTime) && !queue.isEmpty();
|
||||
if (!valid) {
|
||||
invalidate();
|
||||
}
|
||||
return valid;
|
||||
}
|
||||
|
||||
public boolean isValid() {
|
||||
return isValid(System.currentTimeMillis());
|
||||
}
|
||||
|
||||
public void invalidate() {
|
||||
clear();
|
||||
key = null;
|
||||
expirationTime = -1;
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
queue.forEach(CacheEntry::invalidate);
|
||||
queue.clear();
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return queue.isEmpty();
|
||||
}
|
||||
|
||||
public Queue<CacheEntry<K,V>> getQueue() {
|
||||
return queue;
|
||||
}
|
||||
}
|
||||
}
|
@ -39,10 +39,12 @@ public class CertMsgCheck {
|
||||
build();
|
||||
|
||||
// Initial client session
|
||||
TLSBase.Client client1 = new TLSBase.Client(true, false);
|
||||
TLSBase.Client client = new TLSBase.Client(true, false);
|
||||
client.connect();
|
||||
|
||||
server.getSession(client1).getSessionContext();
|
||||
server.done();
|
||||
// Close must be called to gather all the exceptions thrown
|
||||
client.close();
|
||||
server.close();
|
||||
|
||||
var eList = server.getExceptionList();
|
||||
System.out.println("Exception list size is " + eList.size());
|
||||
|
@ -47,6 +47,7 @@ public class CheckSessionContext {
|
||||
|
||||
// Initial client session
|
||||
TLSBase.Client client1 = new TLSBase.Client();
|
||||
client1.connect();
|
||||
if (server.getSession(client1).getSessionContext() == null) {
|
||||
throw new Exception("Context was null. Handshake failure.");
|
||||
} else {
|
||||
@ -66,6 +67,7 @@ public class CheckSessionContext {
|
||||
|
||||
// Resume the client session
|
||||
TLSBase.Client client2 = new TLSBase.Client();
|
||||
client2.connect();
|
||||
if (server.getSession(client2).getSessionContext() == null) {
|
||||
throw new Exception("Context was null on resumption");
|
||||
} else {
|
||||
@ -73,6 +75,5 @@ public class CheckSessionContext {
|
||||
}
|
||||
server.close(client2);
|
||||
client2.close();
|
||||
server.done();
|
||||
}
|
||||
}
|
||||
|
@ -25,13 +25,16 @@ import javax.net.ssl.*;
|
||||
import java.io.*;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.KeyStore;
|
||||
import java.security.cert.PKIXBuilderParameters;
|
||||
import java.security.cert.X509CertSelector;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* This is a base setup for creating a server and clients. All clients will
|
||||
@ -39,7 +42,7 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
* first. The idea is for the test code to be minimal as possible without
|
||||
* this library class being complicated.
|
||||
*
|
||||
* Server.done() must be called or the server will never exit and hang the test.
|
||||
* Server.close() must be called so the server will exit and end threading.
|
||||
*
|
||||
* After construction, reading and writing are allowed from either side,
|
||||
* or a combination write/read from both sides for verifying text.
|
||||
@ -52,24 +55,25 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
*/
|
||||
|
||||
abstract public class TLSBase {
|
||||
static String pathToStores = "../etc";
|
||||
static String pathToStores = "javax/net/ssl/etc";
|
||||
static String keyStoreFile = "keystore";
|
||||
static String trustStoreFile = "truststore";
|
||||
static String passwd = "passphrase";
|
||||
|
||||
static final String TESTROOT =
|
||||
System.getProperty("test.root", "../../../..");
|
||||
|
||||
SSLContext sslContext;
|
||||
// Server's port
|
||||
static int serverPort;
|
||||
// Name shown during read and write ops
|
||||
String name;
|
||||
public String name;
|
||||
|
||||
TLSBase() {
|
||||
String keyFilename =
|
||||
System.getProperty("test.src", "./") + "/" + pathToStores +
|
||||
"/" + keyStoreFile;
|
||||
String trustFilename =
|
||||
System.getProperty("test.src", "./") + "/" + pathToStores +
|
||||
"/" + trustStoreFile;
|
||||
|
||||
String keyFilename = TESTROOT + "/" + pathToStores + "/" + keyStoreFile;
|
||||
String trustFilename = TESTROOT + "/" + pathToStores + "/" +
|
||||
trustStoreFile;
|
||||
System.setProperty("javax.net.ssl.keyStore", keyFilename);
|
||||
System.setProperty("javax.net.ssl.keyStorePassword", passwd);
|
||||
System.setProperty("javax.net.ssl.trustStore", trustFilename);
|
||||
@ -78,26 +82,22 @@ abstract public class TLSBase {
|
||||
|
||||
// Base read operation
|
||||
byte[] read(SSLSocket sock) throws Exception {
|
||||
BufferedReader reader = new BufferedReader(
|
||||
new InputStreamReader(sock.getInputStream()));
|
||||
String s = reader.readLine();
|
||||
System.err.println("(read) " + name + ": " + s);
|
||||
return s.getBytes();
|
||||
BufferedInputStream is = new BufferedInputStream(sock.getInputStream());
|
||||
byte[] b = is.readNBytes(5);
|
||||
System.err.println("(read) " + Thread.currentThread().getName() + ": " + new String(b));
|
||||
return b;
|
||||
}
|
||||
|
||||
// Base write operation
|
||||
public void write(SSLSocket sock, byte[] data) throws Exception {
|
||||
PrintWriter out = new PrintWriter(
|
||||
new OutputStreamWriter(sock.getOutputStream()));
|
||||
out.println(new String(data));
|
||||
out.flush();
|
||||
System.err.println("(write)" + name + ": " + new String(data));
|
||||
sock.getOutputStream().write(data);
|
||||
System.err.println("(write)" + Thread.currentThread().getName() + ": " + new String(data));
|
||||
}
|
||||
|
||||
private static KeyManager[] getKeyManager(boolean empty) throws Exception {
|
||||
FileInputStream fis = null;
|
||||
if (!empty) {
|
||||
fis = new FileInputStream(System.getProperty("test.src", "./") +
|
||||
fis = new FileInputStream(System.getProperty("test.root", "./") +
|
||||
"/" + pathToStores + "/" + keyStoreFile);
|
||||
}
|
||||
// Load the keystore
|
||||
@ -113,7 +113,7 @@ abstract public class TLSBase {
|
||||
private static TrustManager[] getTrustManager(boolean empty) throws Exception {
|
||||
FileInputStream fis = null;
|
||||
if (!empty) {
|
||||
fis = new FileInputStream(System.getProperty("test.src", "./") +
|
||||
fis = new FileInputStream(System.getProperty("test.root", "./") +
|
||||
"/" + pathToStores + "/" + trustStoreFile);
|
||||
}
|
||||
// Load the keystore
|
||||
@ -150,6 +150,11 @@ abstract public class TLSBase {
|
||||
new ConcurrentHashMap<>();
|
||||
Thread t;
|
||||
List<Exception> exceptionList = new ArrayList<>();
|
||||
ExecutorService threadPool = Executors.newFixedThreadPool(1,
|
||||
r -> {
|
||||
Thread t = Executors.defaultThreadFactory().newThread(r);
|
||||
return t;
|
||||
});
|
||||
|
||||
Server(ServerBuilder builder) {
|
||||
super();
|
||||
@ -160,8 +165,10 @@ abstract public class TLSBase {
|
||||
TLSBase.getTrustManager(builder.tm), null);
|
||||
fac = sslContext.getServerSocketFactory();
|
||||
ssock = (SSLServerSocket) fac.createServerSocket(0);
|
||||
ssock.setReuseAddress(true);
|
||||
ssock.setNeedClientAuth(builder.clientauth);
|
||||
serverPort = ssock.getLocalPort();
|
||||
System.out.println("Server Port: " + serverPort);
|
||||
} catch (Exception e) {
|
||||
System.err.println("Failure during server initialization");
|
||||
e.printStackTrace();
|
||||
@ -171,117 +178,67 @@ abstract public class TLSBase {
|
||||
t = new Thread(() -> {
|
||||
try {
|
||||
while (true) {
|
||||
System.err.println("Server ready on port " +
|
||||
serverPort);
|
||||
SSLSocket c = (SSLSocket)ssock.accept();
|
||||
clientMap.put(c.getPort(), c);
|
||||
try {
|
||||
write(c, read(c));
|
||||
} catch (Exception e) {
|
||||
System.out.println("Caught " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
exceptionList.add(e);
|
||||
}
|
||||
SSLSocket sock = (SSLSocket)ssock.accept();
|
||||
threadPool.submit(new ServerThread(sock));
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
System.err.println("Server Down");
|
||||
ex.printStackTrace();
|
||||
} finally {
|
||||
threadPool.close();
|
||||
}
|
||||
});
|
||||
t.start();
|
||||
}
|
||||
|
||||
class ServerThread extends Thread {
|
||||
SSLSocket sock;
|
||||
|
||||
ServerThread(SSLSocket s) {
|
||||
this.sock = s;
|
||||
System.err.println("ServerThread("+sock.getPort()+")");
|
||||
clientMap.put(sock.getPort(), sock);
|
||||
}
|
||||
|
||||
public void run() {
|
||||
try {
|
||||
write(sock, read(sock));
|
||||
} catch (Exception e) {
|
||||
System.out.println("Caught " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
exceptionList.add(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Server() {
|
||||
this(new ServerBuilder());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param km - true for an empty key manager
|
||||
* @param tm - true for an empty trust manager
|
||||
*/
|
||||
Server(boolean km, boolean tm) {
|
||||
super();
|
||||
name = "server";
|
||||
public SSLSession getSession(Client client) throws Exception {
|
||||
System.err.println("getSession("+client.getPort()+")");
|
||||
SSLSocket clientSocket = clientMap.get(client.getPort());
|
||||
if (clientSocket == null) {
|
||||
throw new Exception("Server can't find client socket");
|
||||
}
|
||||
return clientSocket.getSession();
|
||||
}
|
||||
|
||||
void close(Client client) {
|
||||
try {
|
||||
sslContext = SSLContext.getInstance("TLS");
|
||||
sslContext.init(TLSBase.getKeyManager(km),
|
||||
TLSBase.getTrustManager(tm), null);
|
||||
fac = sslContext.getServerSocketFactory();
|
||||
ssock = (SSLServerSocket) fac.createServerSocket(0);
|
||||
ssock.setNeedClientAuth(true);
|
||||
serverPort = ssock.getLocalPort();
|
||||
System.err.println("close("+client.getPort()+")");
|
||||
clientMap.remove(client.getPort()).close();
|
||||
} catch (Exception e) {
|
||||
System.err.println("Failure during server initialization");
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
// Thread to allow multiple clients to connect
|
||||
t = new Thread(() -> {
|
||||
try {
|
||||
while (true) {
|
||||
System.err.println("Server ready on port " +
|
||||
serverPort);
|
||||
SSLSocket c = (SSLSocket)ssock.accept();
|
||||
clientMap.put(c.getPort(), c);
|
||||
try {
|
||||
write(c, read(c));
|
||||
} catch (Exception e) {
|
||||
System.out.println("Caught " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
exceptionList.add(e);
|
||||
}
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
System.err.println("Server Down");
|
||||
ex.printStackTrace();
|
||||
}
|
||||
});
|
||||
t.start();
|
||||
}
|
||||
|
||||
// Exit test to quit the test. This must be called at the end of the
|
||||
// test or the test will never end.
|
||||
void done() {
|
||||
try {
|
||||
t.join(5000);
|
||||
ssock.close();
|
||||
} catch (Exception e) {
|
||||
System.err.println(e.getMessage());
|
||||
e.printStackTrace();
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
// Read from the client
|
||||
byte[] read(Client client) throws Exception {
|
||||
SSLSocket s = clientMap.get(Integer.valueOf(client.getPort()));
|
||||
if (s == null) {
|
||||
System.err.println("No socket found, port " + client.getPort());
|
||||
}
|
||||
return read(s);
|
||||
}
|
||||
|
||||
// Write to the client
|
||||
void write(Client client, byte[] data) throws Exception {
|
||||
write(clientMap.get(client.getPort()), data);
|
||||
}
|
||||
|
||||
// Server writes to the client, then reads from the client.
|
||||
// Return true if the read & write data match, false if not.
|
||||
boolean writeRead(Client client, String s) throws Exception{
|
||||
write(client, s.getBytes());
|
||||
return (Arrays.compare(s.getBytes(), client.read()) == 0);
|
||||
}
|
||||
|
||||
// Get the SSLSession from the server side socket
|
||||
SSLSession getSession(Client c) {
|
||||
SSLSocket s = clientMap.get(Integer.valueOf(c.getPort()));
|
||||
return s.getSession();
|
||||
}
|
||||
|
||||
// Close client socket
|
||||
void close(Client c) throws IOException {
|
||||
SSLSocket s = clientMap.get(Integer.valueOf(c.getPort()));
|
||||
s.close();
|
||||
void close() throws InterruptedException {
|
||||
clientMap.values().stream().forEach(s -> {
|
||||
try {
|
||||
s.close();
|
||||
} catch (IOException e) {}
|
||||
});
|
||||
threadPool.awaitTermination(500, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
List<Exception> getExceptionList() {
|
||||
@ -312,11 +269,11 @@ abstract public class TLSBase {
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Client side will establish a connection from the constructor and wait.
|
||||
* Client side will establish a SSLContext instance.
|
||||
* It must be run after the Server constructor is called.
|
||||
*/
|
||||
static class Client extends TLSBase {
|
||||
SSLSocket sock;
|
||||
public SSLSocket socket;
|
||||
boolean km, tm;
|
||||
Client() {
|
||||
this(false, false);
|
||||
@ -330,55 +287,66 @@ abstract public class TLSBase {
|
||||
super();
|
||||
this.km = km;
|
||||
this.tm = tm;
|
||||
connect();
|
||||
}
|
||||
|
||||
// Connect to server. Maybe runnable in the future
|
||||
public SSLSocket connect() {
|
||||
try {
|
||||
sslContext = SSLContext.getInstance("TLS");
|
||||
sslContext.init(TLSBase.getKeyManager(km), TLSBase.getTrustManager(tm), null);
|
||||
sock = (SSLSocket)sslContext.getSocketFactory().createSocket();
|
||||
sock.connect(new InetSocketAddress(InetAddress.getLoopbackAddress(), serverPort));
|
||||
System.err.println("Client connected using port " +
|
||||
sock.getLocalPort());
|
||||
name = "client(" + sock.toString() + ")";
|
||||
write("Hello");
|
||||
read();
|
||||
socket = createSocket();
|
||||
} catch (Exception ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
return sock;
|
||||
}
|
||||
|
||||
// Read from the client socket
|
||||
byte[] read() throws Exception {
|
||||
return read(sock);
|
||||
Client(Client cl) {
|
||||
sslContext = cl.sslContext;
|
||||
socket = createSocket();
|
||||
}
|
||||
|
||||
// Write to the client socket
|
||||
void write(byte[] data) throws Exception {
|
||||
write(sock, data);
|
||||
}
|
||||
void write(String s) throws Exception {
|
||||
write(sock, s.getBytes());
|
||||
public SSLSocket createSocket() {
|
||||
try {
|
||||
return (SSLSocket) sslContext.getSocketFactory().createSocket();
|
||||
} catch (Exception ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Client writes to the server, then reads from the server.
|
||||
// Return true if the read & write data match, false if not.
|
||||
boolean writeRead(Server server, String s) throws Exception {
|
||||
write(s.getBytes());
|
||||
return (Arrays.compare(s.getBytes(), server.read(this)) == 0);
|
||||
public SSLSocket connect() {
|
||||
try {
|
||||
socket.connect(new InetSocketAddress(InetAddress.getLoopbackAddress(), serverPort));
|
||||
System.err.println("Client (" + Thread.currentThread().getName() + ") connected using port " +
|
||||
socket.getLocalPort() + " to " + socket.getPort());
|
||||
writeRead();
|
||||
} catch (Exception ex) {
|
||||
ex.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
return socket;
|
||||
}
|
||||
|
||||
// Get port from the socket
|
||||
int getPort() {
|
||||
return sock.getLocalPort();
|
||||
public SSLSession getSession() {
|
||||
return socket.getSession();
|
||||
}
|
||||
public void close() {
|
||||
try {
|
||||
socket.close();
|
||||
} catch (Exception ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
// Close socket
|
||||
void close() throws IOException {
|
||||
sock.close();
|
||||
public int getPort() {
|
||||
return socket.getLocalPort();
|
||||
}
|
||||
|
||||
private SSLSocket writeRead() {
|
||||
try {
|
||||
write(socket, "Hello".getBytes(StandardCharsets.ISO_8859_1));
|
||||
read(socket);
|
||||
} catch (Exception ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
return socket;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
173
test/jdk/sun/security/ssl/SSLSessionImpl/MultiNSTClient.java
Normal file
173
test/jdk/sun/security/ssl/SSLSessionImpl/MultiNSTClient.java
Normal file
@ -0,0 +1,173 @@
|
||||
/*
|
||||
* Copyright (c) 2024, 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @library /test/lib
|
||||
* @library /javax/net/ssl/templates
|
||||
* @bug 8242008
|
||||
* @summary Verifies multiple PSKs are used by JSSE
|
||||
* @run main/othervm MultiNSTClient -Djdk.tls.client.protocols=TLSv1.3 -Djdk.tls.server.newSessionTicketCount=1
|
||||
* @run main/othervm MultiNSTClient -Djdk.tls.client.protocols=TLSv1.3 -Djdk.tls.server.newSessionTicketCount=3
|
||||
* @run main/othervm MultiNSTClient -Djdk.tls.client.protocols=TLSv1.3 -Djdk.tls.server.newSessionTicketCount=10
|
||||
* @run main/othervm MultiNSTClient -Djdk.tls.client.protocols=TLSv1.3 -Djdk.tls.server.enableSessionTicketExtension=true -Djdk.tls.client.enableSessionTicketExtension=true
|
||||
* @run main/othervm MultiNSTClient -Djdk.tls.client.protocols=TLSv1.3 -Djdk.tls.server.enableSessionTicketExtension=false -Djdk.tls.client.enableSessionTicketExtension=true
|
||||
* @run main/othervm MultiNSTClient -Djdk.tls.client.protocols=TLSv1.3 -Djdk.tls.server.enableSessionTicketExtension=true -Djdk.tls.client.enableSessionTicketExtension=false
|
||||
* @run main/othervm MultiNSTClient -Djdk.tls.client.protocols=TLSv1.3 -Djdk.tls.server.enableSessionTicketExtension=false -Djdk.tls.client.enableSessionTicketExtension=false
|
||||
* @run main/othervm MultiNSTClient -Djdk.tls.client.protocols=TLSv1.2 -Djdk.tls.server.enableSessionTicketExtension=true -Djdk.tls.client.enableSessionTicketExtension=true
|
||||
*/
|
||||
|
||||
import jdk.test.lib.Utils;
|
||||
import jdk.test.lib.process.OutputAnalyzer;
|
||||
import jdk.test.lib.process.ProcessTools;
|
||||
|
||||
import javax.net.ssl.SSLSession;
|
||||
import java.util.Arrays;
|
||||
import java.util.HexFormat;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* This test verifies that multiple NSTs and PSKs are sent by a JSSE server.
|
||||
* Then JSSE client is able to store them all and resume the connection. It
|
||||
* requires specific text in the TLS debugging to verify the success.
|
||||
*/
|
||||
|
||||
public class MultiNSTClient {
|
||||
|
||||
static HexFormat hex = HexFormat.of();
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
|
||||
if (!args[0].equalsIgnoreCase("p")) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
Arrays.stream(args).forEach(a -> {
|
||||
sb.append(a);
|
||||
sb.append(" ");
|
||||
});
|
||||
String params = sb.toString();
|
||||
System.setProperty("test.java.opts",
|
||||
"-Dtest.src=" + System.getProperty("test.src") +
|
||||
" -Dtest.jdk=" + System.getProperty("test.jdk") +
|
||||
" -Dtest.root=" + System.getProperty("test.root") +
|
||||
" -Djavax.net.debug=ssl,handshake " + params
|
||||
);
|
||||
|
||||
boolean TLS13 = args[0].contains("1.3");
|
||||
|
||||
System.out.println("test.java.opts: " +
|
||||
System.getProperty("test.java.opts"));
|
||||
|
||||
ProcessBuilder pb = ProcessTools.createTestJavaProcessBuilder(
|
||||
Utils.addTestJavaOpts("MultiNSTClient", "p"));
|
||||
|
||||
OutputAnalyzer output = ProcessTools.executeProcess(pb);
|
||||
System.out.println("I'm here");
|
||||
boolean pass = true;
|
||||
try {
|
||||
List<String> list = output.stderrShouldContain("MultiNST PSK").
|
||||
asLines().stream().filter(s ->
|
||||
s.contains("MultiNST PSK")).toList();
|
||||
List<String> serverPSK = list.stream().filter(s ->
|
||||
s.contains("MultiNST PSK (Server)")).toList();
|
||||
List<String> clientPSK = list.stream().filter(s ->
|
||||
s.contains("MultiNST PSK (Client)")).toList();
|
||||
System.out.println("found list: " + list.size());
|
||||
System.out.println("found server: " + serverPSK.size());
|
||||
serverPSK.stream().forEach(s -> System.out.println("\t" + s));
|
||||
System.out.println("found client: " + clientPSK.size());
|
||||
clientPSK.stream().forEach(s -> System.out.println("\t" + s));
|
||||
for (int i = 0; i < 2; i++) {
|
||||
String svr = serverPSK.getFirst();
|
||||
String cli = clientPSK.getFirst();
|
||||
if (svr.regionMatches(svr.length() - 16, cli, cli.length() - 16, 16)) {
|
||||
System.out.println("entry " + (i + 1) + " match.");
|
||||
} else {
|
||||
System.out.println("entry " + (i + 1) + " server and client PSK didn't match:");
|
||||
System.out.println(" server: " + svr);
|
||||
System.out.println(" client: " + cli);
|
||||
pass = false;
|
||||
}
|
||||
}
|
||||
} catch (RuntimeException e) {
|
||||
System.out.println("No MultiNST PSK found.");
|
||||
pass = false;
|
||||
}
|
||||
|
||||
if (TLS13) {
|
||||
if (!pass) {
|
||||
throw new Exception("Test failed: " + params);
|
||||
}
|
||||
} else {
|
||||
if (pass) {
|
||||
throw new Exception("Test failed: " + params);
|
||||
}
|
||||
}
|
||||
System.out.println("Test Passed");
|
||||
return;
|
||||
}
|
||||
|
||||
TLSBase.Server server = new TLSBase.Server();
|
||||
|
||||
System.out.println("------ Start connection");
|
||||
TLSBase.Client initial = new TLSBase.Client();
|
||||
SSLSession initialSession = initial.connect().getSession();
|
||||
System.out.println("id = " + hex.formatHex(initialSession.getId()));
|
||||
System.out.println("session = " + initialSession);
|
||||
|
||||
System.out.println("------ getNewSession from original client");
|
||||
TLSBase.Client resumClient = new TLSBase.Client(initial);
|
||||
SSLSession resumption = resumClient.connect().getSession();
|
||||
System.out.println("id = " + hex.formatHex(resumption.getId()));
|
||||
System.out.println("session = " + resumption);
|
||||
if (!initialSession.toString().equalsIgnoreCase(resumption.toString())) {
|
||||
throw new Exception("Resumed session did not match");
|
||||
}
|
||||
|
||||
System.out.println("------ Second getNewSession from original client");
|
||||
TLSBase.Client resumClient2 = new TLSBase.Client(initial);
|
||||
resumption = resumClient2.connect().getSession();
|
||||
System.out.println("id = " + hex.formatHex(resumption.getId()));
|
||||
System.out.println("session = " + resumption);
|
||||
if (!initialSession.toString().equalsIgnoreCase(resumption.toString())) {
|
||||
throw new Exception("Resumed session did not match");
|
||||
}
|
||||
|
||||
System.out.println("------ New client connection");
|
||||
TLSBase.Client newConnection = new TLSBase.Client();
|
||||
SSLSession newSession = newConnection.connect().getSession();
|
||||
System.out.println("id = " + hex.formatHex(newSession.getId()));
|
||||
System.out.println("session = " + newSession);
|
||||
if (initialSession.toString().equalsIgnoreCase(newSession.toString())) {
|
||||
throw new Exception("new session is the same as the initial.");
|
||||
}
|
||||
|
||||
System.out.println("------ Closing connections");
|
||||
initial.close();
|
||||
resumClient.close();
|
||||
resumClient2.close();
|
||||
newConnection.close();
|
||||
server.close();
|
||||
System.out.println("------ End");
|
||||
System.exit(0);
|
||||
}
|
||||
}
|
@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Copyright (c) 2024, 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @library /test/lib
|
||||
* @library /javax/net/ssl/templates
|
||||
* @bug 8242008
|
||||
* @summary Verifies resumption fails with 0 NSTs and session creation off
|
||||
* @run main/othervm MultiNSTNoSessionCreation -Djdk.tls.client.protocols=TLSv1.3 -Djdk.tls.server.newSessionTicketCount=0
|
||||
* @run main/othervm MultiNSTNoSessionCreation -Djdk.tls.client.protocols=TLSv1.2 -Djdk.tls.server.newSessionTicketCount=0
|
||||
*/
|
||||
|
||||
import jdk.test.lib.Utils;
|
||||
import jdk.test.lib.process.OutputAnalyzer;
|
||||
import jdk.test.lib.process.ProcessTools;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* With no NSTs sent by the server, try to resume the session with
|
||||
* setEnabledSessionCreation(false). The test should get an exception and
|
||||
* fail to connect.
|
||||
*/
|
||||
|
||||
public class MultiNSTNoSessionCreation {
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
|
||||
if (!args[0].equalsIgnoreCase("p")) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
Arrays.stream(args).forEach(a -> sb.append(a).append(" "));
|
||||
String params = sb.toString();
|
||||
System.setProperty("test.java.opts",
|
||||
"-Dtest.src=" + System.getProperty("test.src") +
|
||||
" -Dtest.jdk=" + System.getProperty("test.jdk") +
|
||||
" -Dtest.root=" + System.getProperty("test.root") +
|
||||
" -Djavax.net.debug=ssl,handshake " + params);
|
||||
|
||||
System.out.println("test.java.opts: " +
|
||||
System.getProperty("test.java.opts"));
|
||||
|
||||
ProcessBuilder pb = ProcessTools.createTestJavaProcessBuilder(
|
||||
Utils.addTestJavaOpts("MultiNSTNoSessionCreation", "p"));
|
||||
|
||||
OutputAnalyzer output = ProcessTools.executeProcess(pb);
|
||||
try {
|
||||
if (output.stderrContains(
|
||||
"(PROTOCOL_VERSION): New session creation is disabled")) {
|
||||
return;
|
||||
}
|
||||
} catch (RuntimeException e) {
|
||||
throw new Exception("Error collecting data", e);
|
||||
}
|
||||
throw new Exception("Disabled creation msg not found");
|
||||
}
|
||||
|
||||
TLSBase.Server server = new TLSBase.Server();
|
||||
|
||||
System.out.println("------ Initial connection");
|
||||
TLSBase.Client initial = new TLSBase.Client();
|
||||
initial.connect();
|
||||
System.out.println(
|
||||
"------ Resume client w/ setEnableSessionCreation set to false");
|
||||
TLSBase.Client resumClient = new TLSBase.Client(initial);
|
||||
resumClient.socket.setEnableSessionCreation(false);
|
||||
resumClient.connect();
|
||||
|
||||
System.out.println("------ Closing connections");
|
||||
initial.close();
|
||||
resumClient.close();
|
||||
server.close();
|
||||
System.out.println("------ End");
|
||||
System.exit(0);
|
||||
}
|
||||
}
|
205
test/jdk/sun/security/ssl/SSLSessionImpl/MultiNSTParallel.java
Normal file
205
test/jdk/sun/security/ssl/SSLSessionImpl/MultiNSTParallel.java
Normal file
@ -0,0 +1,205 @@
|
||||
/*
|
||||
* Copyright (c) 2024, 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @library /test/lib
|
||||
* @library /javax/net/ssl/templates
|
||||
* @bug 8242008
|
||||
* @summary Verifies multiple PSKs are used by TLSv1.3
|
||||
* @run main/othervm MultiNSTParallel 10 -Djdk.tls.client.protocols=TLSv1.3
|
||||
*/
|
||||
|
||||
import jdk.test.lib.Utils;
|
||||
import jdk.test.lib.process.OutputAnalyzer;
|
||||
import jdk.test.lib.process.ProcessTools;
|
||||
|
||||
import javax.net.ssl.SSLSession;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HexFormat;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
|
||||
/**
|
||||
* This test verifies that parallel resumption connections successfully get
|
||||
* a PSK entry and not initiate a full handshake.
|
||||
*
|
||||
* Note: THe first argument after 'MultiNSTParallel' is the ticket count
|
||||
* The test will set 'jdk.tls.server.NewSessionTicketCount` to that number and
|
||||
* will start the same number of resumption client attempts. The ticket count
|
||||
* must be the same or larger than resumption attempts otherwise the queue runs
|
||||
* empty and the test will fail.
|
||||
*
|
||||
* Because this test runs parallel connections, the thread order finish is not
|
||||
* guaranteed. Each client NST id is checked with all server NSTs ids until
|
||||
* a match is found. When a match is found, it is removed from the list to
|
||||
* verify no NST was used more than once.
|
||||
*
|
||||
* TLS 1.2 spec does not specify multiple NST behavior.
|
||||
*/
|
||||
|
||||
public class MultiNSTParallel {
|
||||
|
||||
static HexFormat hex = HexFormat.of();
|
||||
final static CountDownLatch wait = new CountDownLatch(1);
|
||||
|
||||
static class ClientThread extends Thread {
|
||||
TLSBase.Client client;
|
||||
|
||||
ClientThread(TLSBase.Client c) {
|
||||
client = c;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
String name = Thread.currentThread().getName();
|
||||
SSLSession r;
|
||||
System.err.println("waiting " + Thread.currentThread().getName());
|
||||
try {
|
||||
wait.await();
|
||||
r = new TLSBase.Client(client).connect().getSession();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(name + ": " +e);
|
||||
}
|
||||
StringBuffer sb = new StringBuffer(100);
|
||||
sb.append("(").append(name).append(") id = ");
|
||||
sb.append(hex.formatHex(r.getId()));
|
||||
sb.append("\n(").append(name).append(") session = ").append(r);
|
||||
if (!client.getSession().toString().equalsIgnoreCase(r.toString())) {
|
||||
throw new RuntimeException("(" + name +
|
||||
") Resumed session did not match");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static boolean pass = true;
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
|
||||
if (!args[0].equalsIgnoreCase("p")) {
|
||||
int ticketCount = Integer.parseInt(args[0]);
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 1; i < args.length; i++) {
|
||||
sb.append(" ").append(args[i]);
|
||||
}
|
||||
String params = sb.toString();
|
||||
System.setProperty("test.java.opts",
|
||||
"-Dtest.src=" + System.getProperty("test.src") +
|
||||
" -Dtest.jdk=" + System.getProperty("test.jdk") +
|
||||
" -Dtest.root=" + System.getProperty("test.root") +
|
||||
" -Djavax.net.debug=ssl,handshake " +
|
||||
" -Djdk.tls.server.newSessionTicketCount=" + ticketCount +
|
||||
params);
|
||||
|
||||
boolean TLS13 = args[1].contains("1.3");
|
||||
|
||||
System.out.println("test.java.opts: " +
|
||||
System.getProperty("test.java.opts"));
|
||||
|
||||
ProcessBuilder pb = ProcessTools.createTestJavaProcessBuilder(
|
||||
Utils.addTestJavaOpts("MultiNSTParallel", "p"));
|
||||
|
||||
OutputAnalyzer output = ProcessTools.executeProcess(pb);
|
||||
try {
|
||||
List<String> list = output.stderrShouldContain("MultiNST PSK").
|
||||
asLines().stream().filter(s ->
|
||||
s.contains("MultiNST PSK")).toList();
|
||||
List<String> sp = list.stream().filter(s ->
|
||||
s.contains("MultiNST PSK (Server)")).toList();
|
||||
List<String> serverPSK = new ArrayList<>(sp.stream().toList());
|
||||
List<String> clientPSK = list.stream().filter(s ->
|
||||
s.contains("MultiNST PSK (Client)")).toList();
|
||||
System.out.println("found list: " + list.size());
|
||||
System.out.println("found server: " + serverPSK.size());
|
||||
serverPSK.stream().forEach(s -> System.out.println("\t" + s));
|
||||
System.out.println("found client: " + clientPSK.size());
|
||||
clientPSK.stream().forEach(s -> System.out.println("\t" + s));
|
||||
|
||||
// Must search all results as order is not guaranteed.
|
||||
clientPSK.stream().forEach(cli -> {
|
||||
for (int i = 0; i < serverPSK.size(); i++) {
|
||||
String svr = serverPSK.get(i);
|
||||
if (svr.regionMatches(svr.length() - 16, cli,
|
||||
cli.length() - 16, 16)) {
|
||||
System.out.println("entry " + (i + 1) + " match.");
|
||||
serverPSK.remove(i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
System.out.println("client entry (" + cli.substring(0, 16) +
|
||||
") not found in server list");
|
||||
pass = false;
|
||||
});
|
||||
} catch (RuntimeException e) {
|
||||
System.out.println("Error looking at PSK results.");
|
||||
throw new Exception(e);
|
||||
}
|
||||
|
||||
if (TLS13) {
|
||||
if (!pass) {
|
||||
throw new Exception("Test failed: " + params);
|
||||
}
|
||||
} else {
|
||||
if (pass) {
|
||||
throw new Exception("Test failed: " + params);
|
||||
}
|
||||
}
|
||||
System.out.println("Test Passed");
|
||||
return;
|
||||
}
|
||||
|
||||
int ticketCount = Integer.parseInt(
|
||||
System.getProperty("jdk.tls.server.newSessionTicketCount"));
|
||||
|
||||
TLSBase.Server server = new TLSBase.Server();
|
||||
|
||||
System.out.println("------ Start connection");
|
||||
TLSBase.Client initial = new TLSBase.Client();
|
||||
SSLSession initialSession = initial.getSession();
|
||||
System.out.println("id = " + hex.formatHex(initialSession.getId()));
|
||||
System.out.println("session = " + initialSession);
|
||||
|
||||
System.out.println("------ getNewSession from original client");
|
||||
|
||||
ArrayList<Thread> slist = new ArrayList<>(ticketCount);
|
||||
|
||||
System.out.println("tx " + ticketCount);
|
||||
for (int i = 0; ticketCount > i; i++) {
|
||||
Thread t = new ClientThread(initial);
|
||||
t.setName("Iteration " + i);
|
||||
slist.add(t);
|
||||
t.start();
|
||||
}
|
||||
|
||||
wait.countDown();
|
||||
for (Thread t : slist) {
|
||||
t.join(1000);
|
||||
System.err.println("released: " + t.getName());
|
||||
}
|
||||
|
||||
System.out.println("------ Closing connections");
|
||||
initial.close();
|
||||
server.close();
|
||||
System.out.println("------ End");
|
||||
System.exit(0);
|
||||
}
|
||||
}
|
145
test/jdk/sun/security/ssl/SSLSessionImpl/MultiNSTSequence.java
Normal file
145
test/jdk/sun/security/ssl/SSLSessionImpl/MultiNSTSequence.java
Normal file
@ -0,0 +1,145 @@
|
||||
/*
|
||||
* Copyright (c) 2024, 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @library /test/lib
|
||||
* @library /javax/net/ssl/templates
|
||||
* @bug 8242008
|
||||
* @summary Verifies sequence of used NST entries from the cache queue.
|
||||
* @run main/othervm MultiNSTSequence -Djdk.tls.server.newSessionTicketCount=2
|
||||
*/
|
||||
|
||||
import jdk.test.lib.Utils;
|
||||
import jdk.test.lib.process.OutputAnalyzer;
|
||||
import jdk.test.lib.process.ProcessTools;
|
||||
|
||||
import javax.net.ssl.SSLSession;
|
||||
import java.util.Arrays;
|
||||
import java.util.HexFormat;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* This test verifies that multiple NSTs take the oldest PSK from the
|
||||
* QueueCacheEntry stored in the TLS Session Cache.
|
||||
*
|
||||
* Note: Beyond 9 iterations the PSK id verification code becomes complicated
|
||||
* with a QueueCacheEntry limit set to retain only the 10 newest entries.
|
||||
*
|
||||
* TLS 1.2 spec does not specify multiple NST behavior.
|
||||
*/
|
||||
|
||||
public class MultiNSTSequence {
|
||||
|
||||
static HexFormat hex = HexFormat.of();
|
||||
static final int ITERATIONS = 9;
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
|
||||
if (!args[0].equalsIgnoreCase("p")) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
Arrays.stream(args).forEach(a -> sb.append(a).append(" "));
|
||||
String params = sb.toString();
|
||||
System.setProperty("test.java.opts",
|
||||
"-Dtest.src=" + System.getProperty("test.src") +
|
||||
" -Dtest.jdk=" + System.getProperty("test.jdk") +
|
||||
" -Dtest.root=" + System.getProperty("test.root") +
|
||||
" -Djavax.net.debug=ssl,handshake " + params
|
||||
);
|
||||
|
||||
System.out.println("test.java.opts: " +
|
||||
System.getProperty("test.java.opts"));
|
||||
|
||||
ProcessBuilder pb = ProcessTools.createTestJavaProcessBuilder(
|
||||
Utils.addTestJavaOpts("MultiNSTSequence", "p"));
|
||||
|
||||
OutputAnalyzer output = ProcessTools.executeProcess(pb);
|
||||
boolean pass = true;
|
||||
try {
|
||||
List<String> list = output.stderrShouldContain("MultiNST PSK").
|
||||
asLines().stream().filter(s ->
|
||||
s.contains("MultiNST PSK")).toList();
|
||||
List<String> serverPSK = list.stream().filter(s ->
|
||||
s.contains("MultiNST PSK (Server)")).toList();
|
||||
List<String> clientPSK = list.stream().filter(s ->
|
||||
s.contains("MultiNST PSK (Client)")).toList();
|
||||
System.out.println("found list: " + list.size());
|
||||
System.out.println("found server: " + serverPSK.size());
|
||||
serverPSK.stream().forEach(s -> System.out.println("\t" + s));
|
||||
System.out.println("found client: " + clientPSK.size());
|
||||
clientPSK.stream().forEach(s -> System.out.println("\t" + s));
|
||||
int i;
|
||||
for (i = 0; i < ITERATIONS; i++) {
|
||||
String svr = serverPSK.get(i);
|
||||
String cli = clientPSK.get(i);
|
||||
if (svr.regionMatches(svr.length() - 16, cli, cli.length() - 16, 16)) {
|
||||
System.out.println("entry " + (i + 1) + " match.");
|
||||
} else {
|
||||
System.out.println("entry " + (i + 1) + " server and client PSK didn't match:");
|
||||
System.out.println(" server: " + svr);
|
||||
System.out.println(" client: " + cli);
|
||||
pass = false;
|
||||
}
|
||||
}
|
||||
} catch (RuntimeException e) {
|
||||
System.out.println("Server and Client PSK usage order is not" +
|
||||
" the same.");
|
||||
pass = false;
|
||||
}
|
||||
|
||||
if (!pass) {
|
||||
throw new Exception("Test failed: " + params);
|
||||
}
|
||||
System.out.println("Test Passed");
|
||||
return;
|
||||
}
|
||||
|
||||
TLSBase.Server server = new TLSBase.Server();
|
||||
|
||||
System.out.println("------ Initial connection");
|
||||
TLSBase.Client initial = new TLSBase.Client();
|
||||
|
||||
SSLSession initialSession = initial.connect().getSession();
|
||||
System.out.println("id = " + hex.formatHex(initialSession.getId()));
|
||||
System.out.println("session = " + initialSession);
|
||||
|
||||
System.out.println("------ Resume client");
|
||||
for (int i = 0; i < ITERATIONS; i++) {
|
||||
SSLSession r = new TLSBase.Client(initial).connect().getSession();
|
||||
StringBuilder sb = new StringBuilder(100);
|
||||
sb.append("Iteration: ").append(i);
|
||||
sb.append("\tid = ").append(hex.formatHex(r.getId()));
|
||||
sb.append("\tsession = ").append(r);
|
||||
System.out.println(sb);
|
||||
if (!initialSession.toString().equalsIgnoreCase(r.toString())) {
|
||||
throw new Exception("Resumed session did not match");
|
||||
}
|
||||
}
|
||||
|
||||
System.out.println("------ Closing connections");
|
||||
initial.close();
|
||||
server.close();
|
||||
System.out.println("------ End");
|
||||
System.exit(0);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user