diff --git a/src/java.base/share/classes/sun/security/ssl/SSLContextImpl.java b/src/java.base/share/classes/sun/security/ssl/SSLContextImpl.java index a9f97c76cb9..04bca94f0c0 100644 --- a/src/java.base/share/classes/sun/security/ssl/SSLContextImpl.java +++ b/src/java.base/share/classes/sun/security/ssl/SSLContextImpl.java @@ -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. * * 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 final ReentrantLock contextLock = new ReentrantLock(); - final HashMap keyHashMap = new HashMap<>(); - SSLContextImpl() { ephemeralKeyManager = new EphemeralKeyManager(); diff --git a/src/java.base/share/classes/sun/security/ssl/SSLSessionContextImpl.java b/src/java.base/share/classes/sun/security/ssl/SSLSessionContextImpl.java index cb62d599df1..eb9eb75debe 100644 --- a/src/java.base/share/classes/sun/security/ssl/SSLSessionContextImpl.java +++ b/src/java.base/share/classes/sun/security/ssl/SSLSessionContextImpl.java @@ -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. * * 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.Collections; import java.util.Enumeration; +import java.util.Iterator; 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.SSLSessionContext; @@ -69,6 +73,11 @@ final class SSLSessionContextImpl implements SSLSessionContext { private int cacheLimit; // the max cache size 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 keyHashMap; + // Default setting for stateless session resumption support (RFC 5077) private boolean statelessSession = true; @@ -80,6 +89,14 @@ final class SSLSessionContextImpl implements SSLSessionContext { // use soft reference sessionCache = 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 @@ -170,6 +187,51 @@ final class SSLSessionContextImpl implements SSLSessionContext { return cacheLimit; } + private void cleanupStatelessKeys() { + Iterator> it = + keyHashMap.entrySet().iterator(); + while (it.hasNext()) { + Map.Entry 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 SSLSessionImpl get(byte[] id) { return (SSLSessionImpl)getSession(id); diff --git a/src/java.base/share/classes/sun/security/ssl/SessionTicketExtension.java b/src/java.base/share/classes/sun/security/ssl/SessionTicketExtension.java index 19663a6b465..acf2b175f55 100644 --- a/src/java.base/share/classes/sun/security/ssl/SessionTicketExtension.java +++ b/src/java.base/share/classes/sun/security/ssl/SessionTicketExtension.java @@ -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. * * 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.spec.GCMParameterSpec; 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.SH_SESSION_TICKET; @@ -74,7 +75,6 @@ final class SessionTicketExtension { // Time in milliseconds until key is changed for encrypting session state private static final int TIMEOUT_DEFAULT = 3600 * 1000; private static final int keyTimeout; - private static int currentKeyID = new SecureRandom().nextInt(); private static final int KEYLEN = 256; static { @@ -115,7 +115,8 @@ final class SessionTicketExtension { final SecretKey key; final int num; - StatelessKey(HandshakeContext hc, int newNum) { + // package-private, used only by SSLContextImpl + StatelessKey(HandshakeContext hc, int num) { SecretKey k = null; try { KeyGenerator kg = KeyGenerator.getInstance("AES"); @@ -126,8 +127,7 @@ final class SessionTicketExtension { } key = k; timeout = System.currentTimeMillis() + keyTimeout; - num = newNum; - hc.sslContext.keyHashMap.put(Integer.valueOf(num), this); + this.num = num; } // Check if key needs to be changed @@ -136,7 +136,8 @@ final class SessionTicketExtension { } // 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)); } } @@ -145,9 +146,11 @@ final class SessionTicketExtension { // Get a key with a specific key number 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 ssk; @@ -155,69 +158,9 @@ final class SessionTicketExtension { // Get the current valid key, this will generate a new key if needed static StatelessKey getCurrentKey(HandshakeContext hc) { - StatelessKey ssk = hc.sslContext.keyHashMap.get(currentKeyID); - - if (ssk != null && !ssk.isExpired()) { - 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; + SSLSessionContextImpl serverCache = + (SSLSessionContextImpl)hc.sslContext.engineGetServerSessionContext(); + return serverCache.getKey(hc); } }