org.openjsse.sun.security.ssl.PreSharedKeyExtension Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of openjsse Show documentation
Show all versions of openjsse Show documentation
OpenJSSE delivers a TLS 1.3 JSSE provider for Java SE 8
/*
* Copyright (c) 2015, 2018, 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.
*/
/*
* Copyright 2019 Azul Systems, Inc.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
package org.openjsse.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 org.openjsse.sun.security.ssl.ClientAuthType.CLIENT_AUTH_REQUIRED;
import org.openjsse.sun.security.ssl.ClientHello.ClientHelloMessage;
import org.openjsse.sun.security.ssl.SSLExtension.ExtensionConsumer;
import org.openjsse.sun.security.ssl.SSLExtension.SSLExtensionSpec;
import org.openjsse.sun.security.ssl.SSLHandshake.HandshakeMessage;
import static org.openjsse.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 identities;
final List binders;
CHPreSharedKeySpec(List identities, List 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 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 (!Objects.equals(identityAlg, 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 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 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 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 identities) {
List 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();
}
}
}