| /* | |
| * Copyright (c) 1996, 2025, Oracle and/or its affiliates. All rights reserved. | |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | |
| * | |
| * This code is free software; you can redistribute it and/or modify it | |
| * under the terms of the GNU General Public License version 2 only, as | |
| * published by the Free Software Foundation. Oracle designates this | |
| * particular file as subject to the "Classpath" exception as provided | |
| * by Oracle in the LICENSE file that accompanied this code. | |
| * | |
| * 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. | |
| */ | |
| package sun.security.ssl; | |
| import java.io.IOException; | |
| import java.net.InetAddress; | |
| import java.nio.ByteBuffer; | |
| import java.nio.charset.StandardCharsets; | |
| import java.security.*; | |
| import java.security.cert.X509Certificate; | |
| import java.util.ArrayList; | |
| import java.util.Arrays; | |
| import java.util.Collection; | |
| import java.util.Collections; | |
| import java.util.List; | |
| import java.util.Objects; | |
| import java.util.Queue; | |
| import java.util.concurrent.ConcurrentHashMap; | |
| import java.util.concurrent.ConcurrentLinkedQueue; | |
| import java.util.concurrent.locks.ReentrantLock; | |
| import java.util.zip.Adler32; | |
| import javax.crypto.KDF; | |
| import javax.crypto.KeyGenerator; | |
| import javax.crypto.SecretKey; | |
| import javax.crypto.spec.HKDFParameterSpec; | |
| import javax.crypto.spec.SecretKeySpec; | |
| import javax.net.ssl.*; | |
| import sun.security.ssl.CipherSuite.HashAlg; | |
| import sun.security.internal.spec.TlsPrfParameterSpec; | |
| import static sun.security.ssl.CipherSuite.HashAlg.H_NONE; | |
| import static sun.security.ssl.ProtocolVersion.*; | |
| import sun.security.util.KeyUtil; | |
| import sun.security.provider.X509Factory; | |
| import sun.security.ssl.X509Authentication.X509Possession; | |
| /** | |
| * Implements the SSL session interface, and exposes the session context | |
| * which is maintained by SSL servers. | |
| * | |
| * <P> Servers have the ability to manage the sessions associated with | |
| * their authentication context(s). They can do this by enumerating the | |
| * IDs of the sessions which are cached, examining those sessions, and then | |
| * perhaps invalidating a given session so that it can't be used again. | |
| * If servers do not explicitly manage the cache, sessions will linger | |
| * until memory is low enough that the runtime environment purges cache | |
| * entries automatically to reclaim space. | |
| * | |
| * <P><em> The only reason this class is not package-private is that | |
| * there's no other public way to get at the server session context which | |
| * is associated with any given authentication context. </em> | |
| * | |
| * @author David Brownell | |
| */ | |
| final class SSLSessionImpl extends ExtendedSSLSession { | |
| /* | |
| * The state of a single session, as described in section 7.1 | |
| * of the SSLv3 spec. | |
| */ | |
| private final ProtocolVersion protocolVersion; | |
| private final SessionId sessionId; | |
| private X509Certificate[] peerCerts; | |
| private CipherSuite cipherSuite; | |
| private SecretKey masterSecret; | |
| final boolean useExtendedMasterSecret; | |
| /* | |
| * Information not part of the SSLv3 protocol spec, but used | |
| * to support session management policies. | |
| */ | |
| private final long creationTime; | |
| private long lastUsedTime = 0; | |
| private final String host; | |
| private final int port; | |
| private SSLSessionContextImpl context; | |
| private boolean invalidated; | |
| private X509Certificate[] localCerts; | |
| private PrivateKey localPrivateKey; | |
| private final Collection<SignatureScheme> localSupportedSignAlgs; | |
| private Collection<SignatureScheme> peerSupportedSignAlgs; //for certificate | |
| private boolean useDefaultPeerSignAlgs = false; | |
| private List<byte[]> statusResponses; | |
| private SecretKey exporterMasterSecret; // TLSv1.3+ exporter info | |
| private RandomCookie clientRandom; // TLSv1.2- exporter info | |
| private RandomCookie serverRandom; | |
| private SecretKey resumptionMasterSecret; | |
| private SecretKey preSharedKey; | |
| private byte[] pskIdentity; | |
| private final long ticketCreationTime = System.currentTimeMillis(); | |
| private int ticketAgeAdd; | |
| private int negotiatedMaxFragLen = -1; | |
| private int maximumPacketSize; | |
| private final Queue<SSLSessionImpl> childSessions = | |
| new ConcurrentLinkedQueue<>(); | |
| /* | |
| * Is the session currently re-established with a session-resumption | |
| * abbreviated initial handshake? | |
| * | |
| * Note that currently we only set this variable in client side. | |
| */ | |
| private boolean isSessionResumption = false; | |
| /* | |
| * Use of session caches is globally enabled/disabled. | |
| */ | |
| private static final boolean defaultRejoinable = true; | |
| // server name indication | |
| final SNIServerName serverNameIndication; | |
| private final List<SNIServerName> requestedServerNames; | |
| // Counter used to create unique nonces in NewSessionTicket | |
| 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. | |
| private final String identificationProtocol; | |
| private final ReentrantLock sessionLock = new ReentrantLock(); | |
| /* | |
| * Create a new non-rejoinable session, using the default (null) | |
| * cipher spec. This constructor returns a session which could | |
| * be used either by a client or by a server, as a connection is | |
| * first opened and before handshaking begins. | |
| */ | |
| SSLSessionImpl() { | |
| this.protocolVersion = ProtocolVersion.NONE; | |
| this.cipherSuite = CipherSuite.C_NULL; | |
| this.sessionId = new SessionId(false, null); | |
| this.host = null; | |
| this.port = -1; | |
| this.localSupportedSignAlgs = Collections.emptySet(); | |
| this.serverNameIndication = null; | |
| this.requestedServerNames = Collections.emptyList(); | |
| this.useExtendedMasterSecret = false; | |
| this.creationTime = System.currentTimeMillis(); | |
| this.identificationProtocol = null; | |
| this.boundValues = new ConcurrentHashMap<>(); | |
| } | |
| /* | |
| * Create a new session, using a given cipher spec. This will | |
| * be rejoinable if session caching is enabled; the constructor | |
| * is intended mostly for use by serves. | |
| */ | |
| SSLSessionImpl(HandshakeContext hc, CipherSuite cipherSuite) { | |
| this(hc, cipherSuite, | |
| new SessionId(defaultRejoinable, hc.sslContext.getSecureRandom())); | |
| } | |
| /* | |
| * Record a new session, using a given cipher spec and session ID. | |
| */ | |
| SSLSessionImpl(HandshakeContext hc, CipherSuite cipherSuite, SessionId id) { | |
| this(hc, cipherSuite, id, System.currentTimeMillis()); | |
| } | |
| /* | |
| * Record a new session, using a given cipher spec, session ID, | |
| * and creation time. | |
| * Note: For the unmodifiable collections and lists we are creating new | |
| * collections as inputs to avoid potential deep nesting of | |
| * unmodifiable collections that can cause StackOverflowErrors | |
| * (see JDK-6323374). | |
| */ | |
| SSLSessionImpl(HandshakeContext hc, | |
| CipherSuite cipherSuite, SessionId id, long creationTime) { | |
| this.protocolVersion = hc.negotiatedProtocol; | |
| this.cipherSuite = cipherSuite; | |
| this.sessionId = id; | |
| this.host = hc.conContext.transport.getPeerHost(); | |
| this.port = hc.conContext.transport.getPeerPort(); | |
| this.localSupportedSignAlgs = hc.localSupportedCertSignAlgs == null ? | |
| Collections.emptySet() : | |
| Collections.unmodifiableCollection( | |
| new ArrayList<>(hc.localSupportedCertSignAlgs)); | |
| this.serverNameIndication = hc.negotiatedServerName; | |
| this.requestedServerNames = List.copyOf(hc.getRequestedServerNames()); | |
| if (hc.sslConfig.isClientMode) { | |
| this.useExtendedMasterSecret = | |
| (hc.handshakeExtensions.get( | |
| SSLExtension.CH_EXTENDED_MASTER_SECRET) != null) && | |
| (hc.handshakeExtensions.get( | |
| SSLExtension.SH_EXTENDED_MASTER_SECRET) != null); | |
| } else { | |
| this.useExtendedMasterSecret = | |
| (hc.handshakeExtensions.get( | |
| SSLExtension.CH_EXTENDED_MASTER_SECRET) != null) && | |
| (!hc.negotiatedProtocol.useTLS13PlusSpec()); | |
| } | |
| this.creationTime = creationTime; | |
| this.identificationProtocol = hc.sslConfig.identificationProtocol; | |
| this.boundValues = new ConcurrentHashMap<>(); | |
| if (SSLLogger.isOn && SSLLogger.isOn("session")) { | |
| SSLLogger.finest("Session initialized: " + this); | |
| } | |
| } | |
| SSLSessionImpl(SSLSessionImpl baseSession, SessionId newId) { | |
| this.protocolVersion = baseSession.getProtocolVersion(); | |
| this.cipherSuite = baseSession.cipherSuite; | |
| this.sessionId = newId; | |
| this.host = baseSession.getPeerHost(); | |
| this.port = baseSession.getPeerPort(); | |
| this.localSupportedSignAlgs = | |
| baseSession.localSupportedSignAlgs == null ? | |
| Collections.emptySet() : baseSession.localSupportedSignAlgs; | |
| this.peerSupportedSignAlgs = | |
| baseSession.peerSupportedSignAlgs == null ? | |
| Collections.emptySet() : baseSession.peerSupportedSignAlgs; | |
| this.serverNameIndication = baseSession.serverNameIndication; | |
| this.requestedServerNames = baseSession.getRequestedServerNames(); | |
| this.masterSecret = baseSession.getMasterSecret(); | |
| this.useExtendedMasterSecret = baseSession.useExtendedMasterSecret; | |
| this.creationTime = baseSession.getCreationTime(); | |
| this.lastUsedTime = System.currentTimeMillis(); | |
| this.identificationProtocol = baseSession.getIdentificationProtocol(); | |
| this.localCerts = baseSession.localCerts; | |
| this.peerCerts = baseSession.peerCerts; | |
| this.statusResponses = baseSession.statusResponses; | |
| this.resumptionMasterSecret = baseSession.resumptionMasterSecret; | |
| this.context = baseSession.context; | |
| this.negotiatedMaxFragLen = baseSession.negotiatedMaxFragLen; | |
| this.maximumPacketSize = baseSession.maximumPacketSize; | |
| this.boundValues = baseSession.boundValues; | |
| if (SSLLogger.isOn && SSLLogger.isOn("session")) { | |
| SSLLogger.finest("Session initialized: " + this); | |
| } | |
| } | |
| /** | |
| * Reassemble new session ticket. | |
| * <p> | |
| * < 2 bytes > protocolVersion | |
| * < 2 bytes > cipherSuite | |
| * < 1 byte > localSupportedSignAlgs entries | |
| * < 2 bytes per entries > localSupportedSignAlgs | |
| * select (protocolVersion) | |
| * case TLS13Plus: | |
| * < 2 bytes > preSharedKey length | |
| * < length in bytes > preSharedKey | |
| * case non-TLS13Plus: | |
| * < 2 bytes > masterSecretKey length | |
| * < length in bytes> masterSecretKey | |
| * < 1 byte > useExtendedMasterSecret | |
| * < 1 byte > identificationProtocol length | |
| * < length in bytes > identificationProtocol | |
| * < 1 byte > serverNameIndication length | |
| * < length in bytes > serverNameIndication | |
| * < 1 byte > Number of requestedServerNames entries | |
| * For each entry { | |
| * < 1 byte > ServerName length | |
| * < length in bytes > ServerName | |
| * } | |
| * < 4 bytes > maximumPacketSize | |
| * < 4 bytes > negotiatedMaxFragSize | |
| * < 4 bytes > creationTime | |
| * < 1 byte > Length of peer host | |
| * < length in bytes > peer host | |
| * < 2 bytes> peer port | |
| * < 1 byte > Number of Peer Certificate entries | |
| * For each entry { | |
| * < 4 bytes > Peer certificate length | |
| * < length in bytes> Peer certificate | |
| * } | |
| * < 1 byte > Number of Local Certificate entries | |
| * For each entry { | |
| * < 1 byte > Local Certificate algorithm length | |
| * < length in bytes> Local Certificate algorithm | |
| * < 4 bytes > Certificate checksum | |
| * } | |
| */ | |
| SSLSessionImpl(HandshakeContext hc, ByteBuffer buf) throws IOException { | |
| int len; | |
| byte[] b; | |
| boundValues = new ConcurrentHashMap<>(); | |
| this.protocolVersion = | |
| ProtocolVersion.valueOf(Record.getInt16(buf)); | |
| // The CH session id may reset this if it's provided | |
| this.sessionId = new SessionId(true, | |
| hc.sslContext.getSecureRandom()); | |
| this.cipherSuite = | |
| CipherSuite.valueOf(Record.getInt16(buf)); | |
| // Local Supported signature algorithms | |
| List<SignatureScheme> list = new ArrayList<>(); | |
| len = Record.getInt8(buf); | |
| while (len-- > 0) { | |
| list.add(SignatureScheme.valueOf( | |
| Record.getInt16(buf))); | |
| } | |
| this.localSupportedSignAlgs = Collections.unmodifiableCollection(list); | |
| if (protocolVersion.useTLS13PlusSpec()) { | |
| // PSK | |
| b = Record.getBytes16(buf); | |
| if (b.length > 0) { | |
| this.preSharedKey = new SecretKeySpec(b, "TlsMasterSecret"); | |
| } else { | |
| this.preSharedKey = null; | |
| } | |
| this.useExtendedMasterSecret = false; | |
| } else { | |
| // Master secret | |
| b = Record.getBytes16(buf); | |
| if (b.length > 0) { | |
| this.masterSecret = new SecretKeySpec(b, "TlsMasterSecret"); | |
| } else { | |
| this.masterSecret = null; | |
| } | |
| // Extended master secret usage. | |
| this.useExtendedMasterSecret = (Record.getInt8(buf) != 0); | |
| } | |
| // Identification Protocol | |
| b = Record.getBytes8(buf); | |
| if (b.length == 0) { | |
| identificationProtocol = null; | |
| } else { | |
| identificationProtocol = new String(b); | |
| } | |
| // SNI | |
| b = Record.getBytes8(buf); | |
| if (b.length == 0) { | |
| serverNameIndication = null; | |
| } else { | |
| serverNameIndication = new SNIHostName(b); | |
| } | |
| // List of SNIServerName | |
| len = Record.getInt16(buf); | |
| if (len == 0) { | |
| this.requestedServerNames = Collections.emptyList(); | |
| } else { | |
| requestedServerNames = new ArrayList<>(); | |
| while (len > 0) { | |
| b = Record.getBytes8(buf); | |
| requestedServerNames.add(new SNIHostName(new String(b))); | |
| len--; | |
| } | |
| } | |
| maximumPacketSize = buf.getInt(); | |
| negotiatedMaxFragLen = buf.getInt(); | |
| // Get creation time | |
| this.creationTime = buf.getLong(); | |
| // Get Peer host & port | |
| b = Record.getBytes8(buf); | |
| if (b.length == 0) { | |
| this.host = ""; | |
| } else { | |
| this.host = new String(b); | |
| } | |
| this.port = Record.getInt16(buf); | |
| // Peer certs. | |
| len = Record.getInt8(buf); | |
| if (len == 0) { | |
| this.peerCerts = null; | |
| } else { | |
| this.peerCerts = new X509Certificate[len]; | |
| for (int i = 0; len > i; i++) { | |
| b = new byte[buf.getInt()]; | |
| buf.get(b); | |
| try { | |
| this.peerCerts[i] = X509Factory.cachedGetX509Cert(b); | |
| } catch (Exception e) { | |
| throw new IOException(e); | |
| } | |
| } | |
| } | |
| // Restore local certificates if cert algorithm(s) present. | |
| len = Record.getInt8(buf); | |
| if (len == 0) { | |
| this.localCerts = null; | |
| } else { | |
| String[] certAlgs = new String[len]; | |
| int[] certCheckSums = new int[len]; | |
| for (int i = 0; len > i; i++) { | |
| certAlgs[i] = new String(Record.getBytes8(buf)); | |
| certCheckSums[i] = Record.getInt32(buf); | |
| } | |
| SSLPossession pos = X509Authentication.createPossession( | |
| hc, certAlgs); | |
| boolean same = false; | |
| if (pos instanceof X509Possession x509Pos | |
| && x509Pos.popCerts != null | |
| && x509Pos.popCerts.length == len) { | |
| // Make sure we got the exact same cert chain. | |
| for (int i = 0; i < x509Pos.popCerts.length; i++) { | |
| try { | |
| byte[] encoded = x509Pos.popCerts[i].getEncoded(); | |
| String popAlg = x509Pos.popCerts[i] | |
| .getPublicKey().getAlgorithm(); | |
| if (certCheckSums[i] == getChecksum(encoded) | |
| && certAlgs[i].equals(popAlg)) { | |
| // Use certs from cache. | |
| x509Pos.popCerts[i] = | |
| X509Factory.cachedGetX509Cert(encoded); | |
| same = true; | |
| } else { | |
| same = false; | |
| break; | |
| } | |
| } catch (Exception e) { | |
| throw new IOException(e); | |
| } | |
| } | |
| } | |
| if (same) { | |
| this.localCerts = ((X509Possession) pos).popCerts; | |
| if (SSLLogger.isOn && SSLLogger.isOn("ssl,session")) { | |
| SSLLogger.fine("Restored " + len | |
| + " local certificates from session ticket" | |
| + " for algorithms " + Arrays.toString(certAlgs)); | |
| } | |
| } else { | |
| this.localCerts = null; | |
| this.invalidated = true; | |
| if (SSLLogger.isOn && SSLLogger.isOn("ssl,session")) { | |
| SSLLogger.warning("Local certificates can not be restored " | |
| + "from session ticket " | |
| + "for algorithms " + Arrays.toString(certAlgs)); | |
| } | |
| } | |
| } | |
| this.context = (SSLSessionContextImpl) | |
| hc.sslContext.engineGetServerSessionContext(); | |
| this.lastUsedTime = System.currentTimeMillis(); | |
| } | |
| // Some situations we cannot provide a stateless ticket, but after it | |
| // has been negotiated | |
| boolean isStatelessable() { | |
| // If there is no getMasterSecret with TLS1.2 or under, do not resume. | |
| if (!protocolVersion.useTLS13PlusSpec() && | |
| getMasterSecret().getEncoded() == null) { | |
| if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { | |
| SSLLogger.finest("No MasterSecret, cannot make stateless" + | |
| " ticket"); | |
| } | |
| return false; | |
| } | |
| if (boundValues != null && boundValues.size() > 0) { | |
| if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { | |
| SSLLogger.finest("There are boundValues, cannot make" + | |
| " stateless ticket"); | |
| } | |
| return false; | |
| } | |
| return true; | |
| } | |
| /** | |
| * Write out a SSLSessionImpl in a byte array for a stateless session ticket | |
| */ | |
| byte[] write() throws Exception { | |
| byte[] b; | |
| HandshakeOutStream hos = new HandshakeOutStream(null); | |
| hos.putInt16(protocolVersion.id); | |
| hos.putInt16(cipherSuite.id); | |
| // Local Supported signature algorithms | |
| hos.putInt8(localSupportedSignAlgs.size()); | |
| for (SignatureScheme s : localSupportedSignAlgs) { | |
| hos.putInt16(s.id); | |
| } | |
| // PreSharedKey is only needed by TLSv1.3, | |
| // masterSecret is only needed by pre-TLSv1.3. | |
| if (protocolVersion.useTLS13PlusSpec()) { | |
| // PSK | |
| if (preSharedKey == null) { | |
| hos.putInt16(0); | |
| } else { | |
| hos.putBytes16(preSharedKey.getEncoded()); | |
| } | |
| } else { | |
| // Master Secret | |
| if (getMasterSecret() == null) { | |
| hos.putInt16(0); | |
| } else { | |
| hos.putBytes16(masterSecret.getEncoded()); | |
| } | |
| hos.putInt8(useExtendedMasterSecret ? 1 : 0); | |
| } | |
| // Identification Protocol | |
| if (identificationProtocol == null) { | |
| hos.putInt8(0); | |
| } else { | |
| hos.putInt8(identificationProtocol.length()); | |
| hos.write(identificationProtocol.getBytes(), 0, | |
| identificationProtocol.length()); | |
| } | |
| // SNI | |
| if (serverNameIndication == null) { | |
| hos.putInt8(0); | |
| } else { | |
| b = serverNameIndication.getEncoded(); | |
| hos.putInt8(b.length); | |
| hos.write(b, 0, b.length); | |
| } | |
| // List of SNIServerName | |
| hos.putInt16(requestedServerNames.size()); | |
| if (requestedServerNames.size() > 0) { | |
| for (SNIServerName sn : requestedServerNames) { | |
| b = sn.getEncoded(); | |
| hos.putInt8(b.length); | |
| hos.write(b, 0, b.length); | |
| } | |
| } | |
| // Buffer sizes | |
| hos.putInt32(maximumPacketSize); | |
| hos.putInt32(negotiatedMaxFragLen); | |
| // Creation time | |
| ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES); | |
| hos.writeBytes(buffer.putLong(creationTime).array()); | |
| // Peer Host & Port | |
| if (host == null || host.length() == 0) { | |
| hos.putInt8(0); | |
| } else { | |
| hos.putInt8(host.length()); | |
| hos.writeBytes(host.getBytes()); | |
| } | |
| hos.putInt16(port); | |
| // Peer certs. | |
| if (peerCerts == null || peerCerts.length == 0) { | |
| hos.putInt8(0); | |
| } else { | |
| hos.putInt8(peerCerts.length); | |
| for (X509Certificate c : peerCerts) { | |
| b = c.getEncoded(); | |
| hos.putInt32(b.length); | |
| hos.writeBytes(b); | |
| } | |
| } | |
| // Local certificates' algorithms and checksums. | |
| // We don't include the complete local certificates in a session ticket | |
| // to decrease the size of ClientHello message. | |
| if (localCerts == null || localCerts.length == 0) { | |
| hos.putInt8(0); | |
| } else { | |
| hos.putInt8(localCerts.length); | |
| for (X509Certificate c : localCerts) { | |
| hos.putBytes8(c.getPublicKey().getAlgorithm().getBytes()); | |
| hos.putInt32(getChecksum(c.getEncoded())); | |
| } | |
| } | |
| return hos.toByteArray(); | |
| } | |
| private static int getChecksum(byte[] input) { | |
| Adler32 adler32 = new Adler32(); | |
| adler32.update(input); | |
| return (int) adler32.getValue(); | |
| } | |
| void setMasterSecret(SecretKey secret) { | |
| masterSecret = secret; | |
| } | |
| void setExporterMasterSecret(SecretKey secret) { | |
| exporterMasterSecret = secret; | |
| } | |
| void setRandoms(RandomCookie client, RandomCookie server) { | |
| clientRandom = client; | |
| serverRandom = server; | |
| } | |
| void setResumptionMasterSecret(SecretKey secret) { | |
| resumptionMasterSecret = secret; | |
| } | |
| void setPreSharedKey(SecretKey key) { | |
| preSharedKey = key; | |
| } | |
| void addChild(SSLSessionImpl session) { | |
| childSessions.add(session); | |
| } | |
| void setTicketAgeAdd(int ticketAgeAdd) { | |
| this.ticketAgeAdd = ticketAgeAdd; | |
| } | |
| void setPskIdentity(byte[] pskIdentity) { | |
| this.pskIdentity = pskIdentity; | |
| } | |
| byte[] incrTicketNonceCounter() { | |
| return new byte[] {ticketNonceCounter++}; | |
| } | |
| boolean isPSKable() { | |
| return (ticketNonceCounter > 0); | |
| } | |
| /** | |
| * Returns the master secret ... treat with extreme caution! | |
| */ | |
| SecretKey getMasterSecret() { | |
| return masterSecret; | |
| } | |
| SecretKey getResumptionMasterSecret() { | |
| return resumptionMasterSecret; | |
| } | |
| SecretKey getPreSharedKey() { | |
| sessionLock.lock(); | |
| try { | |
| return preSharedKey; | |
| } finally { | |
| sessionLock.unlock(); | |
| } | |
| } | |
| SecretKey consumePreSharedKey() { | |
| sessionLock.lock(); | |
| try { | |
| return preSharedKey; | |
| } finally { | |
| preSharedKey = null; | |
| sessionLock.unlock(); | |
| } | |
| } | |
| int getTicketAgeAdd() { | |
| return ticketAgeAdd; | |
| } | |
| String getIdentificationProtocol() { | |
| return this.identificationProtocol; | |
| } | |
| /* PSK identities created from new_session_ticket messages should only | |
| * be used once. This method will return the identity and then clear it | |
| * so it cannot be used again. | |
| */ | |
| byte[] consumePskIdentity() { | |
| sessionLock.lock(); | |
| try { | |
| return pskIdentity; | |
| } finally { | |
| pskIdentity = null; | |
| sessionLock.unlock(); | |
| } | |
| } | |
| byte[] getPskIdentity() { | |
| return pskIdentity; | |
| } | |
| public boolean isPSK() { | |
| return (pskIdentity != null && pskIdentity.length > 0); | |
| } | |
| void setPeerCertificates(X509Certificate[] peer) { | |
| if (peerCerts == null) { | |
| peerCerts = peer; | |
| } | |
| } | |
| void setLocalCertificates(X509Certificate[] local) { | |
| localCerts = local; | |
| } | |
| void setLocalPrivateKey(PrivateKey privateKey) { | |
| localPrivateKey = privateKey; | |
| } | |
| void setPeerSupportedSignatureAlgorithms( | |
| Collection<SignatureScheme> signatureSchemes) { | |
| peerSupportedSignAlgs = signatureSchemes; | |
| } | |
| // TLS 1.2 only | |
| // | |
| // Per RFC 5246, If the client supports only the default hash | |
| // and signature algorithms, it MAY omit the | |
| // signature_algorithms extension. If the client does not | |
| // support the default algorithms, or supports other hash | |
| // and signature algorithms (and it is willing to use them | |
| // for verifying messages sent by the server, i.e., server | |
| // certificates and server key exchange), it MUST send the | |
| // signature_algorithms extension, listing the algorithms it | |
| // is willing to accept. | |
| private static final ArrayList<SignatureScheme> defaultPeerSupportedSignAlgs = | |
| new ArrayList<>(Arrays.asList(SignatureScheme.RSA_PKCS1_SHA1, | |
| SignatureScheme.DSA_SHA1, | |
| SignatureScheme.ECDSA_SHA1)); | |
| void setUseDefaultPeerSignAlgs() { | |
| useDefaultPeerSignAlgs = true; | |
| peerSupportedSignAlgs = defaultPeerSupportedSignAlgs; | |
| } | |
| // Returns the connection session. | |
| SSLSessionImpl finish() { | |
| if (useDefaultPeerSignAlgs) { | |
| peerSupportedSignAlgs = Collections.emptySet(); | |
| } | |
| return this; | |
| } | |
| /** | |
| * Provide status response data obtained during the SSL handshake. | |
| * | |
| * @param responses a {@link List} of responses in binary form. | |
| */ | |
| void setStatusResponses(List<byte[]> responses) { | |
| if (responses != null && !responses.isEmpty()) { | |
| statusResponses = responses; | |
| } else { | |
| statusResponses = Collections.emptyList(); | |
| } | |
| } | |
| /** | |
| * Returns true iff this session may be resumed ... sessions are | |
| * usually resumable. Security policies may suggest otherwise, | |
| * for example sessions that haven't been used for a while (say, | |
| * a working day) won't be resumable, and sessions might have a | |
| * maximum lifetime in any case. | |
| */ | |
| boolean isRejoinable() { | |
| // TLS 1.3 can have no session id | |
| if (protocolVersion.useTLS13PlusSpec()) { | |
| return (!invalidated && isLocalAuthenticationValid()); | |
| } | |
| return sessionId != null && sessionId.length() != 0 && | |
| !invalidated && isLocalAuthenticationValid(); | |
| } | |
| @Override | |
| public boolean isValid() { | |
| sessionLock.lock(); | |
| try { | |
| return isRejoinable(); | |
| } finally { | |
| sessionLock.unlock(); | |
| } | |
| } | |
| /** | |
| * Check if the authentication used when establishing this session | |
| * is still valid. Returns true if no authentication was used | |
| */ | |
| private boolean isLocalAuthenticationValid() { | |
| if (localPrivateKey != null) { | |
| try { | |
| // if the private key is no longer valid, getAlgorithm() | |
| // should throw an exception | |
| // (e.g. Smartcard has been removed from the reader) | |
| localPrivateKey.getAlgorithm(); | |
| } catch (Exception e) { | |
| invalidate(); | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| /** | |
| * Returns the ID for this session. The ID is fixed for the | |
| * duration of the session; neither it, nor its value, changes. | |
| */ | |
| @Override | |
| public byte[] getId() { | |
| return sessionId.getId(); | |
| } | |
| /** | |
| * For server sessions, this returns the set of sessions which | |
| * are currently valid in this process. For client sessions, | |
| * this returns null. | |
| */ | |
| @Override | |
| public SSLSessionContext getSessionContext() { | |
| return context; | |
| } | |
| SessionId getSessionId() { | |
| return sessionId; | |
| } | |
| /** | |
| * Returns the cipher spec in use on this session | |
| */ | |
| CipherSuite getSuite() { | |
| return cipherSuite; | |
| } | |
| /** | |
| * Resets the cipher spec in use on this session | |
| */ | |
| void setSuite(CipherSuite suite) { | |
| cipherSuite = suite; | |
| if (SSLLogger.isOn && SSLLogger.isOn("session")) { | |
| SSLLogger.finest("Negotiating session: " + this); | |
| } | |
| } | |
| /** | |
| * Return true if the session is currently re-established with a | |
| * session-resumption abbreviated initial handshake. | |
| */ | |
| boolean isSessionResumption() { | |
| return isSessionResumption; | |
| } | |
| /** | |
| * Resets whether the session is re-established with a session-resumption | |
| * abbreviated initial handshake. | |
| */ | |
| void setAsSessionResumption(boolean flag) { | |
| isSessionResumption = flag; | |
| } | |
| /** | |
| * Returns the name of the cipher suite in use on this session | |
| */ | |
| @Override | |
| public String getCipherSuite() { | |
| return getSuite().name; | |
| } | |
| ProtocolVersion getProtocolVersion() { | |
| return protocolVersion; | |
| } | |
| /** | |
| * Returns the standard name of the protocol in use on this session | |
| */ | |
| @Override | |
| public String getProtocol() { | |
| return getProtocolVersion().name; | |
| } | |
| /** | |
| * {@return the hashcode for this session} | |
| */ | |
| @Override | |
| public int hashCode() { | |
| return sessionId.hashCode(); | |
| } | |
| /** | |
| * Returns true if sessions have same ids, false otherwise. | |
| */ | |
| @Override | |
| public boolean equals(Object obj) { | |
| if (obj == this) { | |
| return true; | |
| } | |
| return obj instanceof SSLSessionImpl other | |
| && sessionId != null | |
| && sessionId.equals(other.getSessionId()); | |
| } | |
| /** | |
| * Return the cert chain presented by the peer in the | |
| * java.security.cert format. | |
| * Note: This method can be used only when using certificate-based | |
| * cipher suites; using it with non-certificate-based cipher suites | |
| * will throw an SSLPeerUnverifiedException. | |
| * | |
| * @return array of peer X.509 certs, with the peer's own cert | |
| * first in the chain, and with the "root" CA last. | |
| */ | |
| @Override | |
| public java.security.cert.Certificate[] getPeerCertificates() | |
| throws SSLPeerUnverifiedException { | |
| // | |
| // clone to preserve integrity of session ... caller can't | |
| // change record of peer identity even by accident, much | |
| // less do it intentionally. | |
| // | |
| if (peerCerts == null) { | |
| throw new SSLPeerUnverifiedException("peer not authenticated"); | |
| } | |
| // Certs are immutable objects, therefore we don't clone them. | |
| // But do need to clone the array, so that nothing is inserted | |
| // into peerCerts. | |
| return peerCerts.clone(); | |
| } | |
| /** | |
| * Return the cert chain presented to the peer in the | |
| * java.security.cert format. | |
| * Note: This method is useful only when using certificate-based | |
| * cipher suites. | |
| * | |
| * @return array of peer X.509 certs, with the peer's own cert | |
| * first in the chain, and with the "root" CA last. | |
| */ | |
| @Override | |
| public java.security.cert.Certificate[] getLocalCertificates() { | |
| // | |
| // clone to preserve integrity of session ... caller can't | |
| // change record of peer identity even by accident, much | |
| // less do it intentionally. | |
| return (localCerts == null ? null : localCerts.clone()); | |
| } | |
| /** | |
| * Return the cert chain presented by the peer. | |
| * Note: This method can be used only when using certificate-based | |
| * cipher suites; using it with non-certificate-based cipher suites | |
| * will throw an SSLPeerUnverifiedException. | |
| * | |
| * @return array of peer X.509 certs, with the peer's own cert | |
| * first in the chain, and with the "root" CA last. | |
| */ | |
| public X509Certificate[] getCertificateChain() | |
| throws SSLPeerUnverifiedException { | |
| /* | |
| * clone to preserve integrity of session ... caller can't | |
| * change record of peer identity even by accident, much | |
| * less do it intentionally. | |
| */ | |
| if (peerCerts != null) { | |
| return peerCerts.clone(); | |
| } else { | |
| throw new SSLPeerUnverifiedException("peer not authenticated"); | |
| } | |
| } | |
| /** | |
| * Return a List of status responses presented by the peer. | |
| * Note: This method can be used only when using certificate-based | |
| * server authentication; otherwise an empty {@code List} will be returned. | |
| * | |
| * @return an unmodifiable {@code List} of byte arrays, each consisting | |
| * of a DER-encoded OCSP response (see RFC 6960). If no responses have | |
| * been presented by the server or non-certificate based server | |
| * authentication is used then an empty {@code List} is returned. | |
| */ | |
| @Override | |
| public List<byte[]> getStatusResponses() { | |
| if (statusResponses == null || statusResponses.isEmpty()) { | |
| return Collections.emptyList(); | |
| } else { | |
| // Clone both the list and the contents | |
| List<byte[]> responses = new ArrayList<>(statusResponses.size()); | |
| for (byte[] respBytes : statusResponses) { | |
| responses.add(respBytes.clone()); | |
| } | |
| return Collections.unmodifiableList(responses); | |
| } | |
| } | |
| /** | |
| * Returns the identity of the peer which was established as part of | |
| * defining the session. | |
| * | |
| * @return the peer's principal. Returns an X500Principal of the | |
| * end-entity certificate for X509-based cipher suites. | |
| * | |
| * @throws SSLPeerUnverifiedException if the peer's identity has not | |
| * been verified | |
| */ | |
| @Override | |
| public Principal getPeerPrincipal() | |
| throws SSLPeerUnverifiedException | |
| { | |
| if (peerCerts == null) { | |
| throw new SSLPeerUnverifiedException("peer not authenticated"); | |
| } | |
| return peerCerts[0].getSubjectX500Principal(); | |
| } | |
| /** | |
| * Returns the principal that was sent to the peer during handshaking. | |
| * | |
| * @return the principal sent to the peer. Returns an X500Principal | |
| * of the end-entity certificate for X509-based cipher suites. | |
| * If no principal was sent, then null is returned. | |
| */ | |
| @Override | |
| public Principal getLocalPrincipal() { | |
| return ((localCerts == null || localCerts.length == 0) ? null : | |
| localCerts[0].getSubjectX500Principal()); | |
| } | |
| /* | |
| * Return the time the ticket for this session was created. | |
| */ | |
| public long getTicketCreationTime() { | |
| return ticketCreationTime; | |
| } | |
| /** | |
| * Returns the time this session was created. | |
| */ | |
| @Override | |
| public long getCreationTime() { | |
| return creationTime; | |
| } | |
| /** | |
| * Returns the last time this session was used to initialize | |
| * a connection. | |
| */ | |
| @Override | |
| public long getLastAccessedTime() { | |
| return (lastUsedTime != 0) ? lastUsedTime : creationTime; | |
| } | |
| void setLastAccessedTime(long time) { | |
| lastUsedTime = time; | |
| } | |
| /** | |
| * Returns the network address of the session's peer. This | |
| * implementation does not insist that connections between | |
| * different ports on the same host must necessarily belong | |
| * to different sessions, though that is of course allowed. | |
| */ | |
| public InetAddress getPeerAddress() { | |
| try { | |
| return InetAddress.getByName(host); | |
| } catch (java.net.UnknownHostException e) { | |
| return null; | |
| } | |
| } | |
| @Override | |
| public String getPeerHost() { | |
| return host; | |
| } | |
| /** | |
| * Need to provide the port info for caching sessions based on | |
| * host and port. Accessed by SSLSessionContextImpl | |
| */ | |
| @Override | |
| public int getPeerPort() { | |
| return port; | |
| } | |
| void setContext(SSLSessionContextImpl ctx) { | |
| if (context == null) { | |
| context = ctx; | |
| } | |
| } | |
| /** | |
| * Invalidate a session. Active connections may still exist, but | |
| * no connections will be able to rejoin this session. | |
| */ | |
| @Override | |
| public void invalidate() { | |
| sessionLock.lock(); | |
| try { | |
| if (context != null) { | |
| context.remove(sessionId); | |
| context = null; | |
| } | |
| if (invalidated) { | |
| return; | |
| } | |
| invalidated = true; | |
| if (SSLLogger.isOn && SSLLogger.isOn("session")) { | |
| SSLLogger.finest("Invalidated session: " + this); | |
| } | |
| for (SSLSessionImpl child : childSessions) { | |
| child.invalidate(); | |
| } | |
| } finally { | |
| sessionLock.unlock(); | |
| } | |
| } | |
| /* | |
| * Table of application-specific session data indexed by an application | |
| * key. | |
| */ | |
| private final ConcurrentHashMap<String, Object> boundValues; | |
| /** | |
| * Assigns a session value. Session change events are given if | |
| * appropriate, to any original value as well as the new value. | |
| */ | |
| @Override | |
| public void putValue(String key, Object value) { | |
| if ((key == null) || (value == null)) { | |
| throw new IllegalArgumentException("arguments can not be null"); | |
| } | |
| Object oldValue = boundValues.put(key, value); | |
| if (oldValue instanceof SSLSessionBindingListener) { | |
| SSLSessionBindingEvent e; | |
| e = new SSLSessionBindingEvent(this, key); | |
| ((SSLSessionBindingListener)oldValue).valueUnbound(e); | |
| } | |
| if (value instanceof SSLSessionBindingListener) { | |
| SSLSessionBindingEvent e; | |
| e = new SSLSessionBindingEvent(this, key); | |
| ((SSLSessionBindingListener)value).valueBound(e); | |
| } | |
| if (protocolVersion.useTLS13PlusSpec()) { | |
| updateNST = true; | |
| } | |
| } | |
| /** | |
| * Returns the specified session value. | |
| */ | |
| @Override | |
| public Object getValue(String key) { | |
| if (key == null) { | |
| throw new IllegalArgumentException("argument can not be null"); | |
| } | |
| return boundValues.get(key); | |
| } | |
| /** | |
| * Removes the specified session value, delivering a session changed | |
| * event as appropriate. | |
| */ | |
| @Override | |
| public void removeValue(String key) { | |
| if (key == null) { | |
| throw new IllegalArgumentException("argument can not be null"); | |
| } | |
| Object value = boundValues.remove(key); | |
| if (value instanceof SSLSessionBindingListener) { | |
| SSLSessionBindingEvent e; | |
| e = new SSLSessionBindingEvent(this, key); | |
| ((SSLSessionBindingListener)value).valueUnbound(e); | |
| } | |
| if (protocolVersion.useTLS13PlusSpec()) { | |
| updateNST = true; | |
| } | |
| } | |
| /** | |
| * Lists the names of the session values. | |
| */ | |
| @Override | |
| public String[] getValueNames() { | |
| return boundValues.keySet().toArray(new String[0]); | |
| } | |
| /** | |
| * Use large packet sizes now or follow RFC 2246 packet sizes (2^14) | |
| * until changed. | |
| * <P> | |
| * In the TLS specification (section 6.2.1, RFC2246), it is not | |
| * recommended that the plaintext has more than 2^14 bytes. | |
| * However, some TLS implementations violate the specification. | |
| * This is a workaround for interoperability with these stacks. | |
| * <P> | |
| * Application could accept large fragments up to 2^15 bytes by | |
| * setting the system property jsse.SSLEngine.acceptLargeFragments | |
| * to "true". | |
| */ | |
| private boolean acceptLargeFragments = | |
| Utilities.getBooleanProperty( | |
| "jsse.SSLEngine.acceptLargeFragments", false); | |
| /** | |
| * Expand the buffer size of both SSL/TLS network packet and | |
| * application data. | |
| */ | |
| void expandBufferSizes() { | |
| sessionLock.lock(); | |
| try { | |
| acceptLargeFragments = true; | |
| } finally { | |
| sessionLock.unlock(); | |
| } | |
| } | |
| /** | |
| * Gets the current size of the largest SSL/TLS packet that is expected | |
| * when using this session. | |
| */ | |
| @Override | |
| public int getPacketBufferSize() { | |
| sessionLock.lock(); | |
| try { | |
| // Use the bigger packet size calculated from maximumPacketSize | |
| // and negotiatedMaxFragLen. | |
| int packetSize = 0; | |
| if (negotiatedMaxFragLen > 0) { | |
| packetSize = cipherSuite.calculatePacketSize( | |
| negotiatedMaxFragLen, protocolVersion, | |
| protocolVersion.isDTLS); | |
| } | |
| if (maximumPacketSize > 0) { | |
| return Math.max(maximumPacketSize, packetSize); | |
| } | |
| if (packetSize != 0) { | |
| return packetSize; | |
| } | |
| if (protocolVersion.isDTLS) { | |
| return DTLSRecord.maxRecordSize; | |
| } else { | |
| return acceptLargeFragments ? | |
| SSLRecord.maxLargeRecordSize : SSLRecord.maxRecordSize; | |
| } | |
| } finally { | |
| sessionLock.unlock(); | |
| } | |
| } | |
| /** | |
| * Gets the current size of the largest application data that is | |
| * expected when using this session. | |
| */ | |
| @Override | |
| public int getApplicationBufferSize() { | |
| sessionLock.lock(); | |
| try { | |
| // Use the bigger fragment size calculated from maximumPacketSize | |
| // and negotiatedMaxFragLen. | |
| int fragmentSize = 0; | |
| if (maximumPacketSize > 0) { | |
| fragmentSize = cipherSuite.calculateFragSize( | |
| maximumPacketSize, protocolVersion, | |
| protocolVersion.isDTLS); | |
| } | |
| if (negotiatedMaxFragLen > 0) { | |
| return Math.max(negotiatedMaxFragLen, fragmentSize); | |
| } | |
| if (fragmentSize != 0) { | |
| return fragmentSize; | |
| } | |
| if (protocolVersion.isDTLS) { | |
| return Record.maxDataSize; | |
| } else { | |
| int maxPacketSize = acceptLargeFragments ? | |
| SSLRecord.maxLargeRecordSize : SSLRecord.maxRecordSize; | |
| return (maxPacketSize - SSLRecord.headerSize); | |
| } | |
| } finally { | |
| sessionLock.unlock(); | |
| } | |
| } | |
| /** | |
| * Sets the negotiated maximum fragment length, as specified by the | |
| * max_fragment_length ClientHello extension in RFC 6066. | |
| * | |
| * @param negotiatedMaxFragLen | |
| * the negotiated maximum fragment length, or {@code -1} if | |
| * no such length has been negotiated. | |
| */ | |
| void setNegotiatedMaxFragSize( | |
| int negotiatedMaxFragLen) { | |
| sessionLock.lock(); | |
| try { | |
| this.negotiatedMaxFragLen = negotiatedMaxFragLen; | |
| } finally { | |
| sessionLock.unlock(); | |
| } | |
| } | |
| /** | |
| * Get the negotiated maximum fragment length, as specified by the | |
| * max_fragment_length ClientHello extension in RFC 6066. | |
| * | |
| * @return the negotiated maximum fragment length, or {@code -1} if | |
| * no such length has been negotiated. | |
| */ | |
| int getNegotiatedMaxFragSize() { | |
| sessionLock.lock(); | |
| try { | |
| return negotiatedMaxFragLen; | |
| } finally { | |
| sessionLock.unlock(); | |
| } | |
| } | |
| void setMaximumPacketSize(int maximumPacketSize) { | |
| sessionLock.lock(); | |
| try { | |
| this.maximumPacketSize = maximumPacketSize; | |
| } finally { | |
| sessionLock.unlock(); | |
| } | |
| } | |
| int getMaximumPacketSize() { | |
| sessionLock.lock(); | |
| try { | |
| return maximumPacketSize; | |
| } finally { | |
| sessionLock.unlock(); | |
| } | |
| } | |
| /** | |
| * Gets an array of supported signature algorithm names that the local | |
| * side is willing to verify. | |
| */ | |
| @Override | |
| public String[] getLocalSupportedSignatureAlgorithms() { | |
| return SignatureScheme.getAlgorithmNames(localSupportedSignAlgs); | |
| } | |
| /** | |
| * Gets an array of supported signature schemes that the local side is | |
| * willing to verify. | |
| */ | |
| public Collection<SignatureScheme> getLocalSupportedSignatureSchemes() { | |
| return localSupportedSignAlgs; | |
| } | |
| /** | |
| * Gets an array of supported signature algorithms that the peer is | |
| * able to verify. | |
| */ | |
| @Override | |
| public String[] getPeerSupportedSignatureAlgorithms() { | |
| return SignatureScheme.getAlgorithmNames(peerSupportedSignAlgs); | |
| } | |
| /** | |
| * Obtains a <code>List</code> containing all {@link SNIServerName}s | |
| * of the requested Server Name Indication (SNI) extension. | |
| */ | |
| @Override | |
| public List<SNIServerName> getRequestedServerNames() { | |
| return requestedServerNames; | |
| } | |
| /* | |
| * keyAlg is used for switching between Keys/Data. If keyAlg is | |
| * non-null, we are producing a Key, otherwise data. | |
| */ | |
| public Object exportKeyingMaterial( | |
| String keyAlg, String label, byte[] context, int length) | |
| throws SSLKeyException { | |
| // Global preconditions | |
| Objects.requireNonNull(label, "label can not be null"); | |
| if (length < 1) { | |
| throw new IllegalArgumentException( | |
| "length must be positive"); | |
| } | |
| // Calculations are primarily based on protocol version. | |
| if (protocolVersion.useTLS13PlusSpec()) { | |
| // Unlikely, but check anyway. | |
| if (exporterMasterSecret == null) { | |
| throw new IllegalStateException( | |
| "Exporter master secret not captured"); | |
| } | |
| // TLS 1.3+ using HKDF-based calcs. | |
| // TLS 1.3 (RFC 8446) | |
| // Check the label/context lengths: | |
| // struct { | |
| // uint16 length = Length; | |
| // opaque label<7..255> = "tls13 " + Label; | |
| // opaque context<0..255> = Context; | |
| // } HkdfLabel; | |
| // label can have 249 bytes (+6 for "tls13 "), and context 255 | |
| // RFC 8446 allows for length of 2^16-1 (65536), but RFC 5869 | |
| // states: | |
| // | |
| // L length of output keying material in octets | |
| // (<= 255*HashLen) | |
| if (length > (255 * cipherSuite.hashAlg.hashLength )) { | |
| throw new IllegalArgumentException( | |
| "length is too large"); | |
| } | |
| byte[] hkdfInfoLabel = | |
| ("tls13 " + label).getBytes(StandardCharsets.UTF_8); | |
| if ((hkdfInfoLabel.length < 7) || hkdfInfoLabel.length > 255) { | |
| throw new IllegalArgumentException( | |
| "label length outside range"); | |
| } | |
| // If no context (null) is provided, RFC 8446 requires an empty | |
| // context be used, unlike RFC 5705. | |
| context = (context != null ? context : new byte[0]); | |
| if (context.length > 255) { | |
| throw new IllegalArgumentException( | |
| "context length outside range"); | |
| } | |
| // Do RFC 8446:7.1-7.5 calculations | |
| /* | |
| * TLS-Exporter(label, context_value, key_length) = | |
| * HKDF-Expand-Label(Derive-Secret(Secret, label, ""), | |
| * "exporter", Hash(context_value), key_length) | |
| * | |
| * Derive-Secret(Secret, Label, Messages) = | |
| * HKDF-Expand-Label(Secret, Label, | |
| * Transcript-Hash(Messages), Hash.length) | |
| */ | |
| try { | |
| // Use the ciphersuite's hashAlg for these calcs. | |
| HashAlg hashAlg = cipherSuite.hashAlg; | |
| KDF hkdf = KDF.getInstance(hashAlg.hkdfAlgorithm); | |
| // First calculate the inner Derive-Secret(Secret, label, "") | |
| MessageDigest md; | |
| byte[] emptyHash; | |
| // Create the "" digest... | |
| try { | |
| md = MessageDigest.getInstance(hashAlg.toString()); | |
| emptyHash = md.digest(); | |
| } catch (NoSuchAlgorithmException nsae) { | |
| throw new ProviderException( | |
| "Hash algorithm " + cipherSuite.hashAlg.name + | |
| " is not available", nsae); | |
| } | |
| // ...then the hkdfInfo... | |
| byte[] hkdfInfo = SSLSecretDerivation.createHkdfInfo( | |
| hkdfInfoLabel, emptyHash, hashAlg.hashLength); | |
| // ...then the "inner" HKDF-Expand-Label() to get the | |
| // derivedSecret that is used as the Secret in the "outer" | |
| // HKDF-Expand-Label(). | |
| SecretKey derivedSecret = hkdf.deriveKey("TlsKey", | |
| HKDFParameterSpec.expandOnly(exporterMasterSecret, | |
| hkdfInfo, hashAlg.hashLength)); | |
| try { | |
| // Now do the "outer" HKDF-Expand-Label. | |
| // HKDF-Expand-Label(derivedSecret, "exporter", | |
| // Hash(context_value), key_length) | |
| // If a context was supplied, use it, otherwise, use the | |
| // previous hashed value of ""... | |
| byte[] hash = ((context.length > 0) ? | |
| md.digest(context) : emptyHash); | |
| // ...now the hkdfInfo... | |
| hkdfInfo = SSLSecretDerivation.createHkdfInfo( | |
| ("tls13 exporter").getBytes(StandardCharsets.UTF_8), | |
| hash, length); | |
| // ...now the final expand. | |
| return ((keyAlg != null) ? | |
| hkdf.deriveKey(keyAlg, | |
| HKDFParameterSpec.expandOnly(derivedSecret, | |
| hkdfInfo, length)) : | |
| hkdf.deriveData( | |
| HKDFParameterSpec.expandOnly(derivedSecret, | |
| hkdfInfo, length))); | |
| } finally { | |
| KeyUtil.destroySecretKeys(derivedSecret); | |
| } | |
| } catch (Exception e) { | |
| // For whatever reason, we couldn't generate. Wrap and return. | |
| throw new SSLKeyException("Couldn't generate Exporter/HKDF", e); | |
| } | |
| } else if (protocolVersion.useTLS10PlusSpec()) { | |
| // Unlikely, but check if randoms were not captured. | |
| if (clientRandom == null || serverRandom == null) { | |
| throw new IllegalStateException("Random nonces not captured"); | |
| } | |
| // RFC 7505 using PRF-based calcs. | |
| // TLS 1/1.1/1.2 (RFCs 2246/4346/5246) or | |
| // DTLS 1.0/1.2 (RFCs 4347/6347) | |
| // Note: In RFC 7627: | |
| // | |
| // If a client or server chooses to continue with a full handshake | |
| // without the extended master secret extension ... they MUST NOT | |
| // export any key material based on the new master secret for any | |
| // subsequent application-level authentication ... it MUST | |
| // disable [RFC5705] ... | |
| if (!useExtendedMasterSecret) { | |
| throw new SSLKeyException( | |
| "Exporters require extended master secrets"); | |
| } | |
| // Check for a "disambiguating label string" (i.e. non-empty). | |
| // Don't see a max length restriction. | |
| if (label.isEmpty()) { | |
| throw new IllegalArgumentException( | |
| "label length outside range"); | |
| } | |
| // context length must fit in 2 unsigned bytes. | |
| if ((context != null) && (context.length > 0xFFFF)) { | |
| throw new IllegalArgumentException( | |
| "Only 16-bit context lengths supported"); | |
| } | |
| // Perform RFC 5705 calculations using the internal SunJCE PRF. | |
| String prfAlg; | |
| HashAlg hashAlg; | |
| if (protocolVersion == TLS12) { | |
| prfAlg = "SunTls12Prf"; | |
| hashAlg = cipherSuite.hashAlg; | |
| } else { // all other cases | |
| prfAlg = "SunTlsPrf"; | |
| hashAlg = H_NONE; | |
| } | |
| // Make a seed with randoms and optional context | |
| // Note that if context is null, it is omitted from the calc | |
| byte[] clientRandomBytes = clientRandom.randomBytes; | |
| byte[] serverRandomBytes = serverRandom.randomBytes; | |
| byte[] seed = new byte[ | |
| clientRandomBytes.length + serverRandomBytes.length + | |
| ((context != null) ? (2 + context.length) : 0)]; | |
| int pos = 0; | |
| System.arraycopy( | |
| clientRandomBytes, 0, seed, pos, clientRandomBytes.length); | |
| pos += clientRandomBytes.length; | |
| System.arraycopy( | |
| serverRandomBytes, 0, seed, pos, serverRandomBytes.length); | |
| pos += serverRandomBytes.length; | |
| if (context != null) { | |
| // RFC 5705, "If no context is provided, ..." | |
| seed[pos++] = (byte) ((context.length >> 8) & 0xFF); | |
| seed[pos++] = (byte) ((context.length) & 0xFF); | |
| System.arraycopy( | |
| context, 0, seed, pos, context.length); | |
| } | |
| // Call the PRF function. | |
| try { | |
| @SuppressWarnings("deprecation") | |
| TlsPrfParameterSpec spec = new TlsPrfParameterSpec( | |
| masterSecret, (keyAlg == null) ? "TlsKey" : keyAlg, | |
| label, seed, length, | |
| hashAlg.name, hashAlg.hashLength, hashAlg.blockSize); | |
| KeyGenerator kg = KeyGenerator.getInstance(prfAlg); | |
| kg.init(spec); | |
| SecretKey key = kg.generateKey(); | |
| if (keyAlg != null) { | |
| return key; | |
| } else { | |
| byte[] b = key.getEncoded(); | |
| if (b == null) { | |
| throw new UnsupportedOperationException( | |
| "Could not extract encoding from SecretKey"); | |
| } | |
| return b; | |
| } | |
| } catch (NoSuchAlgorithmException | | |
| InvalidAlgorithmParameterException e) { | |
| throw new SSLKeyException("Could not generate Exporter/PRF", e); | |
| } | |
| } else { | |
| // SSLv3 is vulnerable to a triple handshake attack and can't be | |
| // mitigated by RFC 7627. Don't support this or any other | |
| // unknown protocol. | |
| throw new SSLKeyException( | |
| "Exporters not supported in " + protocolVersion); | |
| } | |
| } | |
| /** | |
| * Generate Exported Key Material (EKM) calculated according to the | |
| * algorithms defined in RFCs 5705/8446. | |
| */ | |
| @Override | |
| public SecretKey exportKeyingMaterialKey(String keyAlg, | |
| String label, byte[] context, int length) throws SSLKeyException { | |
| Objects.requireNonNull(keyAlg, "keyAlg can not be null"); | |
| if (keyAlg.isEmpty()) { | |
| throw new IllegalArgumentException( | |
| "keyAlg is empty"); | |
| } | |
| return (SecretKey) exportKeyingMaterial(keyAlg, label, context, | |
| length); | |
| } | |
| /** | |
| * Generate Exported Key Material (EKM) calculated according to the | |
| * algorithms defined in RFCs 5705/8446. | |
| */ | |
| @Override | |
| public byte[] exportKeyingMaterialData( | |
| String label, byte[] context, int length) throws SSLKeyException { | |
| return (byte[])exportKeyingMaterial(null, label, context, length); | |
| } | |
| /** Returns a string representation of this SSL session */ | |
| @Override | |
| public String toString() { | |
| return "Session(" + creationTime + "|" + getCipherSuite() + ")"; | |
| } | |
| } |