mirror of
https://github.com/openjdk/jdk.git
synced 2025-08-28 07:14:30 +02:00
8298381: Improve handling of session tickets for multiple SSLContexts
Reviewed-by: xuelei, ascarpino, serb
This commit is contained in:
parent
eab1e6260d
commit
debe5879aa
3 changed files with 78 additions and 76 deletions
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 1999, 2022, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 1999, 2023, Oracle and/or its affiliates. All rights reserved.
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
@ -71,9 +71,6 @@ public abstract class SSLContextImpl extends SSLContextSpi {
|
||||||
private volatile StatusResponseManager statusResponseManager;
|
private volatile StatusResponseManager statusResponseManager;
|
||||||
|
|
||||||
private final ReentrantLock contextLock = new ReentrantLock();
|
private final ReentrantLock contextLock = new ReentrantLock();
|
||||||
final HashMap<Integer,
|
|
||||||
SessionTicketExtension.StatelessKey> keyHashMap = new HashMap<>();
|
|
||||||
|
|
||||||
|
|
||||||
SSLContextImpl() {
|
SSLContextImpl() {
|
||||||
ephemeralKeyManager = new EphemeralKeyManager();
|
ephemeralKeyManager = new EphemeralKeyManager();
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 1999, 2022, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 1999, 2023, Oracle and/or its affiliates. All rights reserved.
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
@ -28,7 +28,11 @@ package sun.security.ssl;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Enumeration;
|
import java.util.Enumeration;
|
||||||
|
import java.util.Iterator;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Random;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import javax.net.ssl.SSLSession;
|
import javax.net.ssl.SSLSession;
|
||||||
import javax.net.ssl.SSLSessionContext;
|
import javax.net.ssl.SSLSessionContext;
|
||||||
|
|
||||||
|
@ -69,6 +73,11 @@ final class SSLSessionContextImpl implements SSLSessionContext {
|
||||||
private int cacheLimit; // the max cache size
|
private int cacheLimit; // the max cache size
|
||||||
private int timeout; // timeout in seconds
|
private int timeout; // timeout in seconds
|
||||||
|
|
||||||
|
// The current session ticket encryption key ID (only used in server context)
|
||||||
|
private int currentKeyID;
|
||||||
|
// Session ticket encryption keys and IDs map (only used in server context)
|
||||||
|
private final Map<Integer, SessionTicketExtension.StatelessKey> keyHashMap;
|
||||||
|
|
||||||
// Default setting for stateless session resumption support (RFC 5077)
|
// Default setting for stateless session resumption support (RFC 5077)
|
||||||
private boolean statelessSession = true;
|
private boolean statelessSession = true;
|
||||||
|
|
||||||
|
@ -80,6 +89,14 @@ final class SSLSessionContextImpl implements SSLSessionContext {
|
||||||
// use soft reference
|
// use soft reference
|
||||||
sessionCache = Cache.newSoftMemoryCache(cacheLimit, timeout);
|
sessionCache = Cache.newSoftMemoryCache(cacheLimit, timeout);
|
||||||
sessionHostPortCache = Cache.newSoftMemoryCache(cacheLimit, timeout);
|
sessionHostPortCache = Cache.newSoftMemoryCache(cacheLimit, timeout);
|
||||||
|
if (server) {
|
||||||
|
keyHashMap = new ConcurrentHashMap<>();
|
||||||
|
// Should be "randomly generated" according to RFC 5077,
|
||||||
|
// but doesn't necessarily has to be a true random number.
|
||||||
|
currentKeyID = new Random(System.nanoTime()).nextInt();
|
||||||
|
} else {
|
||||||
|
keyHashMap = Map.of();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stateless sessions when available, but there is a cache
|
// Stateless sessions when available, but there is a cache
|
||||||
|
@ -170,6 +187,51 @@ final class SSLSessionContextImpl implements SSLSessionContext {
|
||||||
return cacheLimit;
|
return cacheLimit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void cleanupStatelessKeys() {
|
||||||
|
Iterator<Map.Entry<Integer, SessionTicketExtension.StatelessKey>> it =
|
||||||
|
keyHashMap.entrySet().iterator();
|
||||||
|
while (it.hasNext()) {
|
||||||
|
Map.Entry<Integer, SessionTicketExtension.StatelessKey> entry = it.next();
|
||||||
|
SessionTicketExtension.StatelessKey k = entry.getValue();
|
||||||
|
if (k.isInvalid(this)) {
|
||||||
|
it.remove();
|
||||||
|
try {
|
||||||
|
k.key.destroy();
|
||||||
|
} catch (Exception e) {
|
||||||
|
// Suppress
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Package-private, used only from SessionTicketExtension.KeyState::getCurrentKey.
|
||||||
|
SessionTicketExtension.StatelessKey getKey(HandshakeContext hc) {
|
||||||
|
SessionTicketExtension.StatelessKey ssk = keyHashMap.get(currentKeyID);
|
||||||
|
if (ssk != null && !ssk.isExpired()) {
|
||||||
|
return ssk;
|
||||||
|
}
|
||||||
|
synchronized (this) {
|
||||||
|
// If the current key is no longer expired, it was already
|
||||||
|
// updated by a concurrent request, and we can return.
|
||||||
|
ssk = keyHashMap.get(currentKeyID);
|
||||||
|
if (ssk != null && !ssk.isExpired()) {
|
||||||
|
return ssk;
|
||||||
|
}
|
||||||
|
int newID = currentKeyID + 1;
|
||||||
|
ssk = new SessionTicketExtension.StatelessKey(hc, newID);
|
||||||
|
keyHashMap.put(Integer.valueOf(newID), ssk);
|
||||||
|
currentKeyID = newID;
|
||||||
|
}
|
||||||
|
// Check for and delete invalid keys every time we create a new stateless key.
|
||||||
|
cleanupStatelessKeys();
|
||||||
|
return ssk;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Package-private, used only from SessionTicketExtension.KeyState::getKey.
|
||||||
|
SessionTicketExtension.StatelessKey getKey(int id) {
|
||||||
|
return keyHashMap.get(id);
|
||||||
|
}
|
||||||
|
|
||||||
// package-private method, used ONLY by ServerHandshaker
|
// package-private method, used ONLY by ServerHandshaker
|
||||||
SSLSessionImpl get(byte[] id) {
|
SSLSessionImpl get(byte[] id) {
|
||||||
return (SSLSessionImpl)getSession(id);
|
return (SSLSessionImpl)getSession(id);
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2019, 2022, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 2019, 2023, Oracle and/or its affiliates. All rights reserved.
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
@ -36,6 +36,7 @@ import javax.crypto.KeyGenerator;
|
||||||
import javax.crypto.SecretKey;
|
import javax.crypto.SecretKey;
|
||||||
import javax.crypto.spec.GCMParameterSpec;
|
import javax.crypto.spec.GCMParameterSpec;
|
||||||
import javax.net.ssl.SSLProtocolException;
|
import javax.net.ssl.SSLProtocolException;
|
||||||
|
import javax.net.ssl.SSLSessionContext;
|
||||||
|
|
||||||
import static sun.security.ssl.SSLExtension.CH_SESSION_TICKET;
|
import static sun.security.ssl.SSLExtension.CH_SESSION_TICKET;
|
||||||
import static sun.security.ssl.SSLExtension.SH_SESSION_TICKET;
|
import static sun.security.ssl.SSLExtension.SH_SESSION_TICKET;
|
||||||
|
@ -74,7 +75,6 @@ final class SessionTicketExtension {
|
||||||
// Time in milliseconds until key is changed for encrypting session state
|
// Time in milliseconds until key is changed for encrypting session state
|
||||||
private static final int TIMEOUT_DEFAULT = 3600 * 1000;
|
private static final int TIMEOUT_DEFAULT = 3600 * 1000;
|
||||||
private static final int keyTimeout;
|
private static final int keyTimeout;
|
||||||
private static int currentKeyID = new SecureRandom().nextInt();
|
|
||||||
private static final int KEYLEN = 256;
|
private static final int KEYLEN = 256;
|
||||||
|
|
||||||
static {
|
static {
|
||||||
|
@ -115,7 +115,8 @@ final class SessionTicketExtension {
|
||||||
final SecretKey key;
|
final SecretKey key;
|
||||||
final int num;
|
final int num;
|
||||||
|
|
||||||
StatelessKey(HandshakeContext hc, int newNum) {
|
// package-private, used only by SSLContextImpl
|
||||||
|
StatelessKey(HandshakeContext hc, int num) {
|
||||||
SecretKey k = null;
|
SecretKey k = null;
|
||||||
try {
|
try {
|
||||||
KeyGenerator kg = KeyGenerator.getInstance("AES");
|
KeyGenerator kg = KeyGenerator.getInstance("AES");
|
||||||
|
@ -126,8 +127,7 @@ final class SessionTicketExtension {
|
||||||
}
|
}
|
||||||
key = k;
|
key = k;
|
||||||
timeout = System.currentTimeMillis() + keyTimeout;
|
timeout = System.currentTimeMillis() + keyTimeout;
|
||||||
num = newNum;
|
this.num = num;
|
||||||
hc.sslContext.keyHashMap.put(Integer.valueOf(num), this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if key needs to be changed
|
// Check if key needs to be changed
|
||||||
|
@ -136,7 +136,8 @@ final class SessionTicketExtension {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if this key is ready for deletion.
|
// Check if this key is ready for deletion.
|
||||||
boolean isInvalid(long sessionTimeout) {
|
boolean isInvalid(SSLSessionContext sslSessionContext) {
|
||||||
|
int sessionTimeout = sslSessionContext.getSessionTimeout() * 1000;
|
||||||
return ((System.currentTimeMillis()) > (timeout + sessionTimeout));
|
return ((System.currentTimeMillis()) > (timeout + sessionTimeout));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -145,9 +146,11 @@ final class SessionTicketExtension {
|
||||||
|
|
||||||
// Get a key with a specific key number
|
// Get a key with a specific key number
|
||||||
static StatelessKey getKey(HandshakeContext hc, int num) {
|
static StatelessKey getKey(HandshakeContext hc, int num) {
|
||||||
StatelessKey ssk = hc.sslContext.keyHashMap.get(num);
|
SSLSessionContextImpl serverCache =
|
||||||
|
(SSLSessionContextImpl)hc.sslContext.engineGetServerSessionContext();
|
||||||
|
StatelessKey ssk = serverCache.getKey(num);
|
||||||
|
|
||||||
if (ssk == null || ssk.isInvalid(getSessionTimeout(hc))) {
|
if (ssk == null || ssk.isInvalid(serverCache)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return ssk;
|
return ssk;
|
||||||
|
@ -155,69 +158,9 @@ final class SessionTicketExtension {
|
||||||
|
|
||||||
// Get the current valid key, this will generate a new key if needed
|
// Get the current valid key, this will generate a new key if needed
|
||||||
static StatelessKey getCurrentKey(HandshakeContext hc) {
|
static StatelessKey getCurrentKey(HandshakeContext hc) {
|
||||||
StatelessKey ssk = hc.sslContext.keyHashMap.get(currentKeyID);
|
SSLSessionContextImpl serverCache =
|
||||||
|
(SSLSessionContextImpl)hc.sslContext.engineGetServerSessionContext();
|
||||||
if (ssk != null && !ssk.isExpired()) {
|
return serverCache.getKey(hc);
|
||||||
return ssk;
|
|
||||||
}
|
|
||||||
return nextKey(hc);
|
|
||||||
}
|
|
||||||
|
|
||||||
// This method locks when the first getCurrentKey() finds it to be too
|
|
||||||
// old and create a new key to replace the current key. After the new
|
|
||||||
// key established, the lock can be released so following
|
|
||||||
// operations will start using the new key.
|
|
||||||
// The first operation will take a longer code path by generating the
|
|
||||||
// next key and cleaning up old keys.
|
|
||||||
private static StatelessKey nextKey(HandshakeContext hc) {
|
|
||||||
StatelessKey ssk;
|
|
||||||
|
|
||||||
synchronized (hc.sslContext.keyHashMap) {
|
|
||||||
// If the current key is no longer expired, it was already
|
|
||||||
// updated by a previous operation, and we can return.
|
|
||||||
ssk = hc.sslContext.keyHashMap.get(currentKeyID);
|
|
||||||
if (ssk != null && !ssk.isExpired()) {
|
|
||||||
return ssk;
|
|
||||||
}
|
|
||||||
int newNum;
|
|
||||||
if (currentKeyID == Integer.MAX_VALUE) {
|
|
||||||
newNum = 0;
|
|
||||||
} else {
|
|
||||||
newNum = currentKeyID + 1;
|
|
||||||
}
|
|
||||||
// Get new key
|
|
||||||
ssk = new StatelessKey(hc, newNum);
|
|
||||||
currentKeyID = newNum;
|
|
||||||
// Release lock since the new key is ready to be used.
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clean up any old keys, then return the current key
|
|
||||||
cleanup(hc);
|
|
||||||
return ssk;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deletes any invalid SessionStateKeys.
|
|
||||||
static void cleanup(HandshakeContext hc) {
|
|
||||||
int sessionTimeout = getSessionTimeout(hc);
|
|
||||||
|
|
||||||
StatelessKey ks;
|
|
||||||
for (Object o : hc.sslContext.keyHashMap.keySet().toArray()) {
|
|
||||||
Integer i = (Integer)o;
|
|
||||||
ks = hc.sslContext.keyHashMap.get(i);
|
|
||||||
if (ks.isInvalid(sessionTimeout)) {
|
|
||||||
try {
|
|
||||||
ks.key.destroy();
|
|
||||||
} catch (Exception e) {
|
|
||||||
// Suppress
|
|
||||||
}
|
|
||||||
hc.sslContext.keyHashMap.remove(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static int getSessionTimeout(HandshakeContext hc) {
|
|
||||||
return hc.sslContext.engineGetServerSessionContext().
|
|
||||||
getSessionTimeout() * 1000;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue