blob: 4bcf8dce2626df15f2afbcd2efb5351878058e45 [file] [log] [blame]
/*
* 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.Objects;
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 static sun.security.ssl.SSLExtension.*;
/**
* 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\" : \"{0}\",\n" +
" \"binders\" : \"{1}\",\n" +
"'}'",
Locale.ENGLISH);
Object[] messageFields = {
Utilities.indent(identitiesString()),
Utilities.indent(bindersString())
};
return messageFormat.format(messageFields);
}
String identitiesString() {
StringBuilder result = new StringBuilder();
for (PskIdentity curId : identities) {
result.append(curId.toString() + "\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() throws IOException {
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;
for (PskIdentity requestedId : pskSpec.identities) {
SSLSessionImpl s = sessionCache.pull(requestedId.identity);
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.sslConfig,
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 (!MessageDigest.isEqual(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];
for (PskIdentity curId : identities) {
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 = JsseJce.getMac(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.
*/
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();
}
}
}