/*
 * Copyright (c) 2015, 2019, 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.nio.ByteBuffer;
import java.security.*;
import java.text.MessageFormat;
import java.util.List;
import java.util.ArrayList;
import java.util.Locale;
import java.util.Arrays;
import java.util.Collection;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.net.ssl.SSLPeerUnverifiedException;
import static sun.security.ssl.ClientAuthType.CLIENT_AUTH_REQUIRED;
import sun.security.ssl.ClientHello.ClientHelloMessage;
import sun.security.ssl.SSLExtension.ExtensionConsumer;
import sun.security.ssl.SSLExtension.SSLExtensionSpec;
import sun.security.ssl.SSLHandshake.HandshakeMessage;
import sun.security.ssl.SessionTicketExtension.SessionTicketSpec;
import sun.security.util.HexDumpEncoder;

import static sun.security.ssl.SSLExtension.*;

Pack of the "pre_shared_key" extension.
/** * Pack of the "pre_shared_key" extension. */
final class PreSharedKeyExtension { static final HandshakeProducer chNetworkProducer = new CHPreSharedKeyProducer(); static final ExtensionConsumer chOnLoadConsumer = new CHPreSharedKeyConsumer(); static final HandshakeAbsence chOnLoadAbsence = new CHPreSharedKeyOnLoadAbsence(); static final HandshakeConsumer chOnTradeConsumer = new CHPreSharedKeyUpdate(); static final HandshakeAbsence chOnTradAbsence = new CHPreSharedKeyOnTradeAbsence(); static final SSLStringizer chStringizer = new CHPreSharedKeyStringizer(); static final HandshakeProducer shNetworkProducer = new SHPreSharedKeyProducer(); static final ExtensionConsumer shOnLoadConsumer = new SHPreSharedKeyConsumer(); static final HandshakeAbsence shOnLoadAbsence = new SHPreSharedKeyAbsence(); static final SSLStringizer shStringizer = new SHPreSharedKeyStringizer(); private static final class PskIdentity { final byte[] identity; final int obfuscatedAge; PskIdentity(byte[] identity, int obfuscatedAge) { this.identity = identity; this.obfuscatedAge = obfuscatedAge; } int getEncodedLength() { return 2 + identity.length + 4; } void writeEncoded(ByteBuffer m) throws IOException { Record.putBytes16(m, identity); Record.putInt32(m, obfuscatedAge); } @Override public String toString() { return "{" + Utilities.toHexString(identity) + ", " + obfuscatedAge + "}"; } } private static final class CHPreSharedKeySpec implements SSLExtensionSpec { final List<PskIdentity> identities; final List<byte[]> binders; CHPreSharedKeySpec(List<PskIdentity> identities, List<byte[]> binders) { this.identities = identities; this.binders = binders; } CHPreSharedKeySpec(HandshakeContext context, ByteBuffer m) throws IOException { // struct { // PskIdentity identities<7..2^16-1>; // PskBinderEntry binders<33..2^16-1>; // } OfferedPsks; if (m.remaining() < 44) { throw context.conContext.fatal(Alert.ILLEGAL_PARAMETER, "Invalid pre_shared_key extension: " + "insufficient data (length=" + m.remaining() + ")"); } int idEncodedLength = Record.getInt16(m); if (idEncodedLength < 7) { throw context.conContext.fatal(Alert.ILLEGAL_PARAMETER, "Invalid pre_shared_key extension: " + "insufficient identities (length=" + idEncodedLength + ")"); } identities = new ArrayList<>(); int idReadLength = 0; while (idReadLength < idEncodedLength) { byte[] id = Record.getBytes16(m); if (id.length < 1) { throw context.conContext.fatal(Alert.ILLEGAL_PARAMETER, "Invalid pre_shared_key extension: " + "insufficient identity (length=" + id.length + ")"); } int obfuscatedTicketAge = Record.getInt32(m); PskIdentity pskId = new PskIdentity(id, obfuscatedTicketAge); identities.add(pskId); idReadLength += pskId.getEncodedLength(); } if (m.remaining() < 35) { throw context.conContext.fatal(Alert.ILLEGAL_PARAMETER, "Invalid pre_shared_key extension: " + "insufficient binders data (length=" + m.remaining() + ")"); } int bindersEncodedLen = Record.getInt16(m); if (bindersEncodedLen < 33) { throw context.conContext.fatal(Alert.ILLEGAL_PARAMETER, "Invalid pre_shared_key extension: " + "insufficient binders (length=" + bindersEncodedLen + ")"); } binders = new ArrayList<>(); int bindersReadLength = 0; while (bindersReadLength < bindersEncodedLen) { byte[] binder = Record.getBytes8(m); if (binder.length < 32) { throw context.conContext.fatal(Alert.ILLEGAL_PARAMETER, "Invalid pre_shared_key extension: " + "insufficient binder entry (length=" + binder.length + ")"); } binders.add(binder); bindersReadLength += 1 + binder.length; } } int getIdsEncodedLength() { int idEncodedLength = 0; for (PskIdentity curId : identities) { idEncodedLength += curId.getEncodedLength(); } return idEncodedLength; } int getBindersEncodedLength() { int binderEncodedLength = 0; for (byte[] curBinder : binders) { binderEncodedLength += 1 + curBinder.length; } return binderEncodedLength; } byte[] getEncoded() throws IOException { int idsEncodedLength = getIdsEncodedLength(); int bindersEncodedLength = getBindersEncodedLength(); int encodedLength = 4 + idsEncodedLength + bindersEncodedLength; byte[] buffer = new byte[encodedLength]; ByteBuffer m = ByteBuffer.wrap(buffer); Record.putInt16(m, idsEncodedLength); for (PskIdentity curId : identities) { curId.writeEncoded(m); } Record.putInt16(m, bindersEncodedLength); for (byte[] curBinder : binders) { Record.putBytes8(m, curBinder); } return buffer; } @Override public String toString() { MessageFormat messageFormat = new MessageFormat( "\"PreSharedKey\": '{'\n" + " \"identities\": '{'\n" + "{0}\n" + " '}'" + " \"binders\": \"{1}\",\n" + "'}'", Locale.ENGLISH); Object[] messageFields = { Utilities.indent(identitiesString()), Utilities.indent(bindersString()) }; return messageFormat.format(messageFields); } String identitiesString() { HexDumpEncoder hexEncoder = new HexDumpEncoder(); StringBuilder result = new StringBuilder(); for (PskIdentity curId : identities) { result.append(" {\n"+ Utilities.indent( hexEncoder.encode(curId.identity), " ") + "\n }\n"); } return result.toString(); } String bindersString() { StringBuilder result = new StringBuilder(); for (byte[] curBinder : binders) { result.append("{" + Utilities.toHexString(curBinder) + "}\n"); } return result.toString(); } } private static final class CHPreSharedKeyStringizer implements SSLStringizer { @Override public String toString(ByteBuffer buffer) { try { // As the HandshakeContext parameter of CHPreSharedKeySpec // constructor is used for fatal alert only, we can use // null HandshakeContext here as we don't care about exception. // // Please take care of this code if the CHPreSharedKeySpec // constructor is updated in the future. return (new CHPreSharedKeySpec(null, buffer)).toString(); } catch (Exception ex) { // For debug logging only, so please swallow exceptions. return ex.getMessage(); } } } private static final class SHPreSharedKeySpec implements SSLExtensionSpec { final int selectedIdentity; SHPreSharedKeySpec(int selectedIdentity) { this.selectedIdentity = selectedIdentity; } SHPreSharedKeySpec(HandshakeContext context, ByteBuffer m) throws IOException { if (m.remaining() < 2) { throw context.conContext.fatal(Alert.ILLEGAL_PARAMETER, "Invalid pre_shared_key extension: " + "insufficient selected_identity (length=" + m.remaining() + ")"); } this.selectedIdentity = Record.getInt16(m); } byte[] getEncoded() { return new byte[] { (byte)((selectedIdentity >> 8) & 0xFF), (byte)(selectedIdentity & 0xFF) }; } @Override public String toString() { MessageFormat messageFormat = new MessageFormat( "\"PreSharedKey\": '{'\n" + " \"selected_identity\" : \"{0}\",\n" + "'}'", Locale.ENGLISH); Object[] messageFields = { Utilities.byte16HexString(selectedIdentity) }; return messageFormat.format(messageFields); } } private static final class SHPreSharedKeyStringizer implements SSLStringizer { @Override public String toString(ByteBuffer buffer) { try { // As the HandshakeContext parameter of SHPreSharedKeySpec // constructor is used for fatal alert only, we can use // null HandshakeContext here as we don't care about exception. // // Please take care of this code if the SHPreSharedKeySpec // constructor is updated in the future. return (new SHPreSharedKeySpec(null, buffer)).toString(); } catch (Exception ex) { // For debug logging only, so please swallow exceptions. return ex.getMessage(); } } } private static final class CHPreSharedKeyConsumer implements ExtensionConsumer { // Prevent instantiation of this class. private CHPreSharedKeyConsumer() { // blank } @Override public void consume(ConnectionContext context, HandshakeMessage message, ByteBuffer buffer) throws IOException { ClientHelloMessage clientHello = (ClientHelloMessage) message; ServerHandshakeContext shc = (ServerHandshakeContext)context; // Is it a supported and enabled extension? if (!shc.sslConfig.isAvailable(SSLExtension.CH_PRE_SHARED_KEY)) { if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Ignore unavailable pre_shared_key extension"); } return; // ignore the extension } // Parse the extension. CHPreSharedKeySpec pskSpec = null; try { pskSpec = new CHPreSharedKeySpec(shc, buffer); } catch (IOException ioe) { throw shc.conContext.fatal(Alert.UNEXPECTED_MESSAGE, ioe); } // The "psk_key_exchange_modes" extension should have been loaded. if (!shc.handshakeExtensions.containsKey( SSLExtension.PSK_KEY_EXCHANGE_MODES)) { throw shc.conContext.fatal(Alert.ILLEGAL_PARAMETER, "Client sent PSK but not PSK modes, or the PSK " + "extension is not the last extension"); } // error if id and binder lists are not the same length if (pskSpec.identities.size() != pskSpec.binders.size()) { throw shc.conContext.fatal(Alert.ILLEGAL_PARAMETER, "PSK extension has incorrect number of binders"); } if (shc.isResumption) { // resumingSession may not be set SSLSessionContextImpl sessionCache = (SSLSessionContextImpl) shc.sslContext.engineGetServerSessionContext(); int idIndex = 0; SSLSessionImpl s = null; for (PskIdentity requestedId : pskSpec.identities) { // If we are keeping state, see if the identity is in the cache if (requestedId.identity.length == SessionId.MAX_LENGTH) { s = sessionCache.get(requestedId.identity); } // See if the identity is a stateless ticket if (s == null && requestedId.identity.length > SessionId.MAX_LENGTH && sessionCache.statelessEnabled()) { ByteBuffer b = new SessionTicketSpec(requestedId.identity). decrypt(shc); if (b != null) { try { s = new SSLSessionImpl(shc, b); } catch (IOException | RuntimeException e) { s = null; } } if (b == null || s == null) { if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Stateless session ticket invalid"); } } } if (s != null && canRejoin(clientHello, shc, s)) { if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("Resuming session: ", s); } // binder will be checked later shc.resumingSession = s; shc.handshakeExtensions.put(SH_PRE_SHARED_KEY, new SHPreSharedKeySpec(idIndex)); // for the index break; } ++idIndex; } if (idIndex == pskSpec.identities.size()) { // no resumable session shc.isResumption = false; shc.resumingSession = null; } } // update the context shc.handshakeExtensions.put( SSLExtension.CH_PRE_SHARED_KEY, pskSpec); } } private static boolean canRejoin(ClientHelloMessage clientHello, ServerHandshakeContext shc, SSLSessionImpl s) { boolean result = s.isRejoinable() && (s.getPreSharedKey() != null); // Check protocol version if (result && s.getProtocolVersion() != shc.negotiatedProtocol) { if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake,verbose")) { SSLLogger.finest("Can't resume, incorrect protocol version"); } result = false; } // Make sure that the server handshake context's localSupportedSignAlgs // field is populated. This is particularly important when // client authentication was used in an initial session and it is // now being resumed. if (shc.localSupportedSignAlgs == null) { shc.localSupportedSignAlgs = SignatureScheme.getSupportedAlgorithms( shc.algorithmConstraints, shc.activeProtocols); } // Validate the required client authentication. if (result && (shc.sslConfig.clientAuthType == CLIENT_AUTH_REQUIRED)) { try { s.getPeerPrincipal(); } catch (SSLPeerUnverifiedException e) { if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake,verbose")) { SSLLogger.finest( "Can't resume, " + "client authentication is required"); } result = false; } // Make sure the list of supported signature algorithms matches Collection<SignatureScheme> sessionSigAlgs = s.getLocalSupportedSignatureSchemes(); if (result && !shc.localSupportedSignAlgs.containsAll(sessionSigAlgs)) { if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("Can't resume. Session uses different " + "signature algorithms"); } result = false; } } // ensure that the endpoint identification algorithm matches the // one in the session String identityAlg = shc.sslConfig.identificationProtocol; if (result && identityAlg != null) { String sessionIdentityAlg = s.getIdentificationProtocol(); if (!identityAlg.equalsIgnoreCase(sessionIdentityAlg)) { if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake,verbose")) { SSLLogger.finest("Can't resume, endpoint id" + " algorithm does not match, requested: " + identityAlg + ", cached: " + sessionIdentityAlg); } result = false; } } // Ensure cipher suite can be negotiated if (result && (!shc.isNegotiable(s.getSuite()) || !clientHello.cipherSuites.contains(s.getSuite()))) { if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake,verbose")) { SSLLogger.finest( "Can't resume, unavailable session cipher suite"); } result = false; } return result; } private static final class CHPreSharedKeyUpdate implements HandshakeConsumer { // Prevent instantiation of this class. private CHPreSharedKeyUpdate() { // blank } @Override public void consume(ConnectionContext context, HandshakeMessage message) throws IOException { ServerHandshakeContext shc = (ServerHandshakeContext)context; if (!shc.isResumption || shc.resumingSession == null) { // not resuming---nothing to do return; } CHPreSharedKeySpec chPsk = (CHPreSharedKeySpec) shc.handshakeExtensions.get(SSLExtension.CH_PRE_SHARED_KEY); SHPreSharedKeySpec shPsk = (SHPreSharedKeySpec) shc.handshakeExtensions.get(SSLExtension.SH_PRE_SHARED_KEY); if (chPsk == null || shPsk == null) { throw shc.conContext.fatal(Alert.INTERNAL_ERROR, "Required extensions are unavailable"); } byte[] binder = chPsk.binders.get(shPsk.selectedIdentity); // set up PSK binder hash HandshakeHash pskBinderHash = shc.handshakeHash.copy(); byte[] lastMessage = pskBinderHash.removeLastReceived(); ByteBuffer messageBuf = ByteBuffer.wrap(lastMessage); // skip the type and length messageBuf.position(4); // read to find the beginning of the binders ClientHelloMessage.readPartial(shc.conContext, messageBuf); int length = messageBuf.position(); messageBuf.position(0); pskBinderHash.receive(messageBuf, length); checkBinder(shc, shc.resumingSession, pskBinderHash, binder); } } private static void checkBinder(ServerHandshakeContext shc, SSLSessionImpl session, HandshakeHash pskBinderHash, byte[] binder) throws IOException { SecretKey psk = session.getPreSharedKey(); if (psk == null) { throw shc.conContext.fatal(Alert.INTERNAL_ERROR, "Session has no PSK"); } SecretKey binderKey = deriveBinderKey(shc, psk, session); byte[] computedBinder = computeBinder(shc, binderKey, session, pskBinderHash); if (!Arrays.equals(binder, computedBinder)) { throw shc.conContext.fatal(Alert.ILLEGAL_PARAMETER, "Incorect PSK binder value"); } } // Class that produces partial messages used to compute binder hash static final class PartialClientHelloMessage extends HandshakeMessage { private final ClientHello.ClientHelloMessage msg; private final CHPreSharedKeySpec psk; PartialClientHelloMessage(HandshakeContext ctx, ClientHello.ClientHelloMessage msg, CHPreSharedKeySpec psk) { super(ctx); this.msg = msg; this.psk = psk; } @Override SSLHandshake handshakeType() { return msg.handshakeType(); } private int pskTotalLength() { return psk.getIdsEncodedLength() + psk.getBindersEncodedLength() + 8; } @Override int messageLength() { if (msg.extensions.get(SSLExtension.CH_PRE_SHARED_KEY) != null) { return msg.messageLength(); } else { return msg.messageLength() + pskTotalLength(); } } @Override void send(HandshakeOutStream hos) throws IOException { msg.sendCore(hos); // complete extensions int extsLen = msg.extensions.length(); if (msg.extensions.get(SSLExtension.CH_PRE_SHARED_KEY) == null) { extsLen += pskTotalLength(); } hos.putInt16(extsLen - 2); // write the complete extensions for (SSLExtension ext : SSLExtension.values()) { byte[] extData = msg.extensions.get(ext); if (extData == null) { continue; } // the PSK could be there from an earlier round if (ext == SSLExtension.CH_PRE_SHARED_KEY) { continue; } int extID = ext.id; hos.putInt16(extID); hos.putBytes16(extData); } // partial PSK extension int extID = SSLExtension.CH_PRE_SHARED_KEY.id; hos.putInt16(extID); byte[] encodedPsk = psk.getEncoded(); hos.putInt16(encodedPsk.length); hos.write(encodedPsk, 0, psk.getIdsEncodedLength() + 2); } } private static final class CHPreSharedKeyProducer implements HandshakeProducer { // Prevent instantiation of this class. private CHPreSharedKeyProducer() { // blank } @Override public byte[] produce(ConnectionContext context, HandshakeMessage message) throws IOException { // The producing happens in client side only. ClientHandshakeContext chc = (ClientHandshakeContext)context; if (!chc.isResumption || chc.resumingSession == null) { if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("No session to resume."); } return null; } // Make sure the list of supported signature algorithms matches Collection<SignatureScheme> sessionSigAlgs = chc.resumingSession.getLocalSupportedSignatureSchemes(); if (!chc.localSupportedSignAlgs.containsAll(sessionSigAlgs)) { if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("Existing session uses different " + "signature algorithms"); } return null; } // The session must have a pre-shared key SecretKey psk = chc.resumingSession.getPreSharedKey(); if (psk == null) { if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("Existing session has no PSK."); } return null; } // The PSK ID can only be used in one connections, but this method // may be called twice in a connection if the server sends HRR. // ID is saved in the context so it can be used in the second call. if (chc.pskIdentity == null) { chc.pskIdentity = chc.resumingSession.consumePskIdentity(); } if (chc.pskIdentity == null) { if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "PSK has no identity, or identity was already used"); } return null; } //The session cannot be used again. Remove it from the cache. SSLSessionContextImpl sessionCache = (SSLSessionContextImpl) chc.sslContext.engineGetClientSessionContext(); sessionCache.remove(chc.resumingSession.getSessionId()); if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Found resumable session. Preparing PSK message."); } List<PskIdentity> identities = new ArrayList<>(); int ageMillis = (int)(System.currentTimeMillis() - chc.resumingSession.getTicketCreationTime()); int obfuscatedAge = ageMillis + chc.resumingSession.getTicketAgeAdd(); identities.add(new PskIdentity(chc.pskIdentity, obfuscatedAge)); SecretKey binderKey = deriveBinderKey(chc, psk, chc.resumingSession); ClientHelloMessage clientHello = (ClientHelloMessage)message; CHPreSharedKeySpec pskPrototype = createPskPrototype( chc.resumingSession.getSuite().hashAlg.hashLength, identities); HandshakeHash pskBinderHash = chc.handshakeHash.copy(); byte[] binder = computeBinder(chc, binderKey, pskBinderHash, chc.resumingSession, chc, clientHello, pskPrototype); List<byte[]> binders = new ArrayList<>(); binders.add(binder); CHPreSharedKeySpec pskMessage = new CHPreSharedKeySpec(identities, binders); chc.handshakeExtensions.put(CH_PRE_SHARED_KEY, pskMessage); return pskMessage.getEncoded(); } private CHPreSharedKeySpec createPskPrototype( int hashLength, List<PskIdentity> identities) { List<byte[]> binders = new ArrayList<>(); byte[] binderProto = new byte[hashLength]; int i = identities.size(); while (i-- > 0) { binders.add(binderProto); } return new CHPreSharedKeySpec(identities, binders); } } private static byte[] computeBinder( HandshakeContext context, SecretKey binderKey, SSLSessionImpl session, HandshakeHash pskBinderHash) throws IOException { pskBinderHash.determine( session.getProtocolVersion(), session.getSuite()); pskBinderHash.update(); byte[] digest = pskBinderHash.digest(); return computeBinder(context, binderKey, session, digest); } private static byte[] computeBinder( HandshakeContext context, SecretKey binderKey, HandshakeHash hash, SSLSessionImpl session, HandshakeContext ctx, ClientHello.ClientHelloMessage hello, CHPreSharedKeySpec pskPrototype) throws IOException { PartialClientHelloMessage partialMsg = new PartialClientHelloMessage(ctx, hello, pskPrototype); SSLEngineOutputRecord record = new SSLEngineOutputRecord(hash); HandshakeOutStream hos = new HandshakeOutStream(record); partialMsg.write(hos); hash.determine(session.getProtocolVersion(), session.getSuite()); hash.update(); byte[] digest = hash.digest(); return computeBinder(context, binderKey, session, digest); } private static byte[] computeBinder(HandshakeContext context, SecretKey binderKey, SSLSessionImpl session, byte[] digest) throws IOException { try { CipherSuite.HashAlg hashAlg = session.getSuite().hashAlg; HKDF hkdf = new HKDF(hashAlg.name); byte[] label = ("tls13 finished").getBytes(); byte[] hkdfInfo = SSLSecretDerivation.createHkdfInfo( label, new byte[0], hashAlg.hashLength); SecretKey finishedKey = hkdf.expand( binderKey, hkdfInfo, hashAlg.hashLength, "TlsBinderKey"); String hmacAlg = "Hmac" + hashAlg.name.replace("-", ""); try { Mac hmac = Mac.getInstance(hmacAlg); hmac.init(finishedKey); return hmac.doFinal(digest); } catch (NoSuchAlgorithmException | InvalidKeyException ex) { throw context.conContext.fatal(Alert.INTERNAL_ERROR, ex); } } catch (GeneralSecurityException ex) { throw context.conContext.fatal(Alert.INTERNAL_ERROR, ex); } } private static SecretKey deriveBinderKey(HandshakeContext context, SecretKey psk, SSLSessionImpl session) throws IOException { try { CipherSuite.HashAlg hashAlg = session.getSuite().hashAlg; HKDF hkdf = new HKDF(hashAlg.name); byte[] zeros = new byte[hashAlg.hashLength]; SecretKey earlySecret = hkdf.extract(zeros, psk, "TlsEarlySecret"); byte[] label = ("tls13 res binder").getBytes(); MessageDigest md = MessageDigest.getInstance(hashAlg.name); byte[] hkdfInfo = SSLSecretDerivation.createHkdfInfo( label, md.digest(new byte[0]), hashAlg.hashLength); return hkdf.expand(earlySecret, hkdfInfo, hashAlg.hashLength, "TlsBinderKey"); } catch (GeneralSecurityException ex) { throw context.conContext.fatal(Alert.INTERNAL_ERROR, ex); } } private static final class CHPreSharedKeyOnLoadAbsence implements HandshakeAbsence { @Override public void absent(ConnectionContext context, HandshakeMessage message) throws IOException { if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Handling pre_shared_key absence."); } ServerHandshakeContext shc = (ServerHandshakeContext)context; // Resumption is only determined by PSK, when enabled shc.resumingSession = null; shc.isResumption = false; } }
The absence processing if the extension is not present in a ClientHello handshake message.
/** * The absence processing if the extension is not present in * a ClientHello handshake message. */
private static final class CHPreSharedKeyOnTradeAbsence implements HandshakeAbsence { @Override public void absent(ConnectionContext context, HandshakeMessage message) throws IOException { // The producing happens in server side only. ServerHandshakeContext shc = (ServerHandshakeContext)context; // A client is considered to be attempting to negotiate using this // specification if the ClientHello contains a "supported_versions" // extension with 0x0304 contained in its body. Such a ClientHello // message MUST meet the following requirements: // - If not containing a "pre_shared_key" extension, it MUST // contain both a "signature_algorithms" extension and a // "supported_groups" extension. if (shc.negotiatedProtocol.useTLS13PlusSpec() && (!shc.handshakeExtensions.containsKey( SSLExtension.CH_SIGNATURE_ALGORITHMS) || !shc.handshakeExtensions.containsKey( SSLExtension.CH_SUPPORTED_GROUPS))) { throw shc.conContext.fatal(Alert.MISSING_EXTENSION, "No supported_groups or signature_algorithms extension " + "when pre_shared_key extension is not present"); } } } private static final class SHPreSharedKeyConsumer implements ExtensionConsumer { // Prevent instantiation of this class. private SHPreSharedKeyConsumer() { // blank } @Override public void consume(ConnectionContext context, HandshakeMessage message, ByteBuffer buffer) throws IOException { // The consuming happens in client side only. ClientHandshakeContext chc = (ClientHandshakeContext)context; // Is it a response of the specific request? if (!chc.handshakeExtensions.containsKey( SSLExtension.CH_PRE_SHARED_KEY)) { throw chc.conContext.fatal(Alert.UNEXPECTED_MESSAGE, "Server sent unexpected pre_shared_key extension"); } SHPreSharedKeySpec shPsk = new SHPreSharedKeySpec(chc, buffer); if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Received pre_shared_key extension: ", shPsk); } if (shPsk.selectedIdentity != 0) { throw chc.conContext.fatal(Alert.ILLEGAL_PARAMETER, "Selected identity index is not in correct range."); } if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Resuming session: ", chc.resumingSession); } } } private static final class SHPreSharedKeyAbsence implements HandshakeAbsence { @Override public void absent(ConnectionContext context, HandshakeMessage message) throws IOException { ClientHandshakeContext chc = (ClientHandshakeContext)context; if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("Handling pre_shared_key absence."); } // The server refused to resume, or the client did not // request 1.3 resumption. chc.resumingSession = null; chc.isResumption = false; } } private static final class SHPreSharedKeyProducer implements HandshakeProducer { // Prevent instantiation of this class. private SHPreSharedKeyProducer() { // blank } @Override public byte[] produce(ConnectionContext context, HandshakeMessage message) throws IOException { ServerHandshakeContext shc = (ServerHandshakeContext)context; SHPreSharedKeySpec psk = (SHPreSharedKeySpec) shc.handshakeExtensions.get(SH_PRE_SHARED_KEY); if (psk == null) { return null; } return psk.getEncoded(); } } }