|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
|
|
package sun.security.ssl; |
|
|
|
import java.io.IOException; |
|
import java.nio.ByteBuffer; |
|
import java.security.CryptoPrimitive; |
|
import java.security.GeneralSecurityException; |
|
import java.text.MessageFormat; |
|
import java.util.Arrays; |
|
import java.util.Collections; |
|
import java.util.EnumSet; |
|
import java.util.LinkedList; |
|
import java.util.List; |
|
import java.util.Locale; |
|
import java.util.Map; |
|
import javax.net.ssl.SSLProtocolException; |
|
import sun.security.ssl.DHKeyExchange.DHECredentials; |
|
import sun.security.ssl.DHKeyExchange.DHEPossession; |
|
import sun.security.ssl.ECDHKeyExchange.ECDHECredentials; |
|
import sun.security.ssl.ECDHKeyExchange.ECDHEPossession; |
|
import sun.security.ssl.KeyShareExtension.CHKeyShareSpec; |
|
import sun.security.ssl.SSLExtension.ExtensionConsumer; |
|
import sun.security.ssl.SSLExtension.SSLExtensionSpec; |
|
import sun.security.ssl.SSLHandshake.HandshakeMessage; |
|
import sun.security.ssl.SupportedGroupsExtension.NamedGroup; |
|
import sun.security.ssl.SupportedGroupsExtension.NamedGroupType; |
|
import sun.security.ssl.SupportedGroupsExtension.SupportedGroups; |
|
import sun.security.util.HexDumpEncoder; |
|
|
|
|
|
|
|
*/ |
|
final class KeyShareExtension { |
|
static final HandshakeProducer chNetworkProducer = |
|
new CHKeyShareProducer(); |
|
static final ExtensionConsumer chOnLoadConsumer = |
|
new CHKeyShareConsumer(); |
|
static final SSLStringizer chStringizer = |
|
new CHKeyShareStringizer(); |
|
|
|
static final HandshakeProducer shNetworkProducer = |
|
new SHKeyShareProducer(); |
|
static final ExtensionConsumer shOnLoadConsumer = |
|
new SHKeyShareConsumer(); |
|
static final HandshakeAbsence shOnLoadAbsence = |
|
new SHKeyShareAbsence(); |
|
static final SSLStringizer shStringizer = |
|
new SHKeyShareStringizer(); |
|
|
|
static final HandshakeProducer hrrNetworkProducer = |
|
new HRRKeyShareProducer(); |
|
static final ExtensionConsumer hrrOnLoadConsumer = |
|
new HRRKeyShareConsumer(); |
|
static final HandshakeProducer hrrNetworkReproducer = |
|
new HRRKeyShareReproducer(); |
|
static final SSLStringizer hrrStringizer = |
|
new HRRKeyShareStringizer(); |
|
|
|
|
|
|
|
*/ |
|
private static final class KeyShareEntry { |
|
final int namedGroupId; |
|
final byte[] keyExchange; |
|
|
|
private KeyShareEntry(int namedGroupId, byte[] keyExchange) { |
|
this.namedGroupId = namedGroupId; |
|
this.keyExchange = keyExchange; |
|
} |
|
|
|
private byte[] getEncoded() { |
|
byte[] buffer = new byte[keyExchange.length + 4]; |
|
// 2: named group id |
|
|
|
ByteBuffer m = ByteBuffer.wrap(buffer); |
|
try { |
|
Record.putInt16(m, namedGroupId); |
|
Record.putBytes16(m, keyExchange); |
|
} catch (IOException ioe) { |
|
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
|
SSLLogger.warning( |
|
"Unlikely IOException", ioe); |
|
} |
|
} |
|
|
|
return buffer; |
|
} |
|
|
|
private int getEncodedSize() { |
|
return keyExchange.length + 4; |
|
// +2: key exchange length |
|
} |
|
|
|
@Override |
|
public String toString() { |
|
MessageFormat messageFormat = new MessageFormat( |
|
"\n'{'\n" + |
|
" \"named group\": {0}\n" + |
|
" \"key_exchange\": '{'\n" + |
|
"{1}\n" + |
|
" '}'\n" + |
|
"'}',", Locale.ENGLISH); |
|
|
|
HexDumpEncoder hexEncoder = new HexDumpEncoder(); |
|
Object[] messageFields = { |
|
NamedGroup.nameOf(namedGroupId), |
|
Utilities.indent(hexEncoder.encode(keyExchange), " ") |
|
}; |
|
|
|
return messageFormat.format(messageFields); |
|
} |
|
} |
|
|
|
|
|
|
|
*/ |
|
static final class CHKeyShareSpec implements SSLExtensionSpec { |
|
final List<KeyShareEntry> clientShares; |
|
|
|
private CHKeyShareSpec(List<KeyShareEntry> clientShares) { |
|
this.clientShares = clientShares; |
|
} |
|
|
|
private CHKeyShareSpec(ByteBuffer buffer) throws IOException { |
|
// struct { |
|
// KeyShareEntry client_shares<0..2^16-1>; |
|
|
|
if (buffer.remaining() < 2) { |
|
throw new SSLProtocolException( |
|
"Invalid key_share extension: " + |
|
"insufficient data (length=" + buffer.remaining() + ")"); |
|
} |
|
|
|
int listLen = Record.getInt16(buffer); |
|
if (listLen != buffer.remaining()) { |
|
throw new SSLProtocolException( |
|
"Invalid key_share extension: " + |
|
"incorrect list length (length=" + listLen + ")"); |
|
} |
|
|
|
List<KeyShareEntry> keyShares = new LinkedList<>(); |
|
while (buffer.hasRemaining()) { |
|
int namedGroupId = Record.getInt16(buffer); |
|
byte[] keyExchange = Record.getBytes16(buffer); |
|
if (keyExchange.length == 0) { |
|
throw new SSLProtocolException( |
|
"Invalid key_share extension: empty key_exchange"); |
|
} |
|
|
|
keyShares.add(new KeyShareEntry(namedGroupId, keyExchange)); |
|
} |
|
|
|
this.clientShares = Collections.unmodifiableList(keyShares); |
|
} |
|
|
|
@Override |
|
public String toString() { |
|
MessageFormat messageFormat = new MessageFormat( |
|
"\"client_shares\": '['{0}\n']'", Locale.ENGLISH); |
|
|
|
StringBuilder builder = new StringBuilder(512); |
|
for (KeyShareEntry entry : clientShares) { |
|
builder.append(entry.toString()); |
|
} |
|
|
|
Object[] messageFields = { |
|
Utilities.indent(builder.toString()) |
|
}; |
|
|
|
return messageFormat.format(messageFields); |
|
} |
|
} |
|
|
|
private static final class CHKeyShareStringizer implements SSLStringizer { |
|
@Override |
|
public String toString(ByteBuffer buffer) { |
|
try { |
|
return (new CHKeyShareSpec(buffer)).toString(); |
|
} catch (IOException ioe) { |
|
|
|
return ioe.getMessage(); |
|
} |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
private static final |
|
class CHKeyShareProducer implements HandshakeProducer { |
|
|
|
private CHKeyShareProducer() { |
|
// blank |
|
} |
|
|
|
@Override |
|
public byte[] produce(ConnectionContext context, |
|
HandshakeMessage message) throws IOException { |
|
|
|
ClientHandshakeContext chc = (ClientHandshakeContext)context; |
|
|
|
|
|
if (!chc.sslConfig.isAvailable(SSLExtension.CH_KEY_SHARE)) { |
|
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
|
SSLLogger.fine( |
|
"Ignore unavailable key_share extension"); |
|
} |
|
return null; |
|
} |
|
|
|
List<NamedGroup> namedGroups; |
|
if (chc.serverSelectedNamedGroup != null) { |
|
|
|
namedGroups = Arrays.asList(chc.serverSelectedNamedGroup); |
|
} else { |
|
namedGroups = chc.clientRequestedNamedGroups; |
|
if (namedGroups == null || namedGroups.isEmpty()) { |
|
|
|
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
|
SSLLogger.warning( |
|
"Ignore key_share extension, no supported groups"); |
|
} |
|
return null; |
|
} |
|
} |
|
|
|
List<KeyShareEntry> keyShares = new LinkedList<>(); |
|
for (NamedGroup ng : namedGroups) { |
|
SSLKeyExchange ke = SSLKeyExchange.valueOf(ng); |
|
if (ke == null) { |
|
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
|
SSLLogger.warning( |
|
"No key exchange for named group " + ng.name); |
|
} |
|
continue; |
|
} |
|
|
|
SSLPossession[] poses = ke.createPossessions(chc); |
|
for (SSLPossession pos : poses) { |
|
|
|
chc.handshakePossessions.add(pos); |
|
if (!(pos instanceof ECDHEPossession) && |
|
!(pos instanceof DHEPossession)) { |
|
|
|
continue; |
|
} |
|
|
|
keyShares.add(new KeyShareEntry(ng.id, pos.encode())); |
|
} |
|
|
|
// One key share entry only. Too much key share entries makes |
|
|
|
if (!keyShares.isEmpty()) { |
|
break; |
|
} |
|
} |
|
|
|
int listLen = 0; |
|
for (KeyShareEntry entry : keyShares) { |
|
listLen += entry.getEncodedSize(); |
|
} |
|
byte[] extData = new byte[listLen + 2]; |
|
ByteBuffer m = ByteBuffer.wrap(extData); |
|
Record.putInt16(m, listLen); |
|
for (KeyShareEntry entry : keyShares) { |
|
m.put(entry.getEncoded()); |
|
} |
|
|
|
|
|
chc.handshakeExtensions.put(SSLExtension.CH_KEY_SHARE, |
|
new CHKeyShareSpec(keyShares)); |
|
|
|
return extData; |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
private static final class CHKeyShareConsumer implements ExtensionConsumer { |
|
|
|
private CHKeyShareConsumer() { |
|
// blank |
|
} |
|
|
|
@Override |
|
public void consume(ConnectionContext context, |
|
HandshakeMessage message, ByteBuffer buffer) throws IOException { |
|
|
|
ServerHandshakeContext shc = (ServerHandshakeContext)context; |
|
|
|
if (shc.handshakeExtensions.containsKey(SSLExtension.CH_KEY_SHARE)) { |
|
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
|
SSLLogger.fine( |
|
"The key_share extension has been loaded"); |
|
} |
|
return; |
|
} |
|
|
|
|
|
if (!shc.sslConfig.isAvailable(SSLExtension.CH_KEY_SHARE)) { |
|
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
|
SSLLogger.fine( |
|
"Ignore unavailable key_share extension"); |
|
} |
|
return; |
|
} |
|
|
|
|
|
CHKeyShareSpec spec; |
|
try { |
|
spec = new CHKeyShareSpec(buffer); |
|
} catch (IOException ioe) { |
|
shc.conContext.fatal(Alert.UNEXPECTED_MESSAGE, ioe); |
|
return; |
|
} |
|
|
|
List<SSLCredentials> credentials = new LinkedList<>(); |
|
for (KeyShareEntry entry : spec.clientShares) { |
|
NamedGroup ng = NamedGroup.valueOf(entry.namedGroupId); |
|
if (ng == null || !SupportedGroups.isActivatable( |
|
shc.sslConfig.algorithmConstraints, ng)) { |
|
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
|
SSLLogger.fine( |
|
"Ignore unsupported named group: " + |
|
NamedGroup.nameOf(entry.namedGroupId)); |
|
} |
|
continue; |
|
} |
|
|
|
if (ng.type == NamedGroupType.NAMED_GROUP_ECDHE) { |
|
try { |
|
ECDHECredentials ecdhec = |
|
ECDHECredentials.valueOf(ng, entry.keyExchange); |
|
if (ecdhec != null) { |
|
if (!shc.algorithmConstraints.permits( |
|
EnumSet.of(CryptoPrimitive.KEY_AGREEMENT), |
|
ecdhec.popPublicKey)) { |
|
SSLLogger.warning( |
|
"ECDHE key share entry does not " + |
|
"comply to algorithm constraints"); |
|
} else { |
|
credentials.add(ecdhec); |
|
} |
|
} |
|
} catch (IOException | GeneralSecurityException ex) { |
|
SSLLogger.warning( |
|
"Cannot decode named group: " + |
|
NamedGroup.nameOf(entry.namedGroupId)); |
|
} |
|
} else if (ng.type == NamedGroupType.NAMED_GROUP_FFDHE) { |
|
try { |
|
DHECredentials dhec = |
|
DHECredentials.valueOf(ng, entry.keyExchange); |
|
if (dhec != null) { |
|
if (!shc.algorithmConstraints.permits( |
|
EnumSet.of(CryptoPrimitive.KEY_AGREEMENT), |
|
dhec.popPublicKey)) { |
|
SSLLogger.warning( |
|
"DHE key share entry does not " + |
|
"comply to algorithm constraints"); |
|
} else { |
|
credentials.add(dhec); |
|
} |
|
} |
|
} catch (IOException | GeneralSecurityException ex) { |
|
SSLLogger.warning( |
|
"Cannot decode named group: " + |
|
NamedGroup.nameOf(entry.namedGroupId)); |
|
} |
|
} |
|
} |
|
|
|
if (!credentials.isEmpty()) { |
|
shc.handshakeCredentials.addAll(credentials); |
|
} else { |
|
|
|
shc.handshakeProducers.put( |
|
SSLHandshake.HELLO_RETRY_REQUEST.id, |
|
SSLHandshake.HELLO_RETRY_REQUEST); |
|
} |
|
|
|
|
|
shc.handshakeExtensions.put(SSLExtension.CH_KEY_SHARE, spec); |
|
} |
|
} |
|
|
|
|
|
|
|
*/ |
|
static final class SHKeyShareSpec implements SSLExtensionSpec { |
|
final KeyShareEntry serverShare; |
|
|
|
SHKeyShareSpec(KeyShareEntry serverShare) { |
|
this.serverShare = serverShare; |
|
} |
|
|
|
private SHKeyShareSpec(ByteBuffer buffer) throws IOException { |
|
// struct { |
|
// KeyShareEntry server_share; |
|
|
|
if (buffer.remaining() < 5) { |
|
throw new SSLProtocolException( |
|
"Invalid key_share extension: " + |
|
"insufficient data (length=" + buffer.remaining() + ")"); |
|
} |
|
|
|
int namedGroupId = Record.getInt16(buffer); |
|
byte[] keyExchange = Record.getBytes16(buffer); |
|
|
|
if (buffer.hasRemaining()) { |
|
throw new SSLProtocolException( |
|
"Invalid key_share extension: unknown extra data"); |
|
} |
|
|
|
this.serverShare = new KeyShareEntry(namedGroupId, keyExchange); |
|
} |
|
|
|
@Override |
|
public String toString() { |
|
MessageFormat messageFormat = new MessageFormat( |
|
"\"server_share\": '{'\n" + |
|
" \"named group\": {0}\n" + |
|
" \"key_exchange\": '{'\n" + |
|
"{1}\n" + |
|
" '}'\n" + |
|
"'}',", Locale.ENGLISH); |
|
|
|
HexDumpEncoder hexEncoder = new HexDumpEncoder(); |
|
Object[] messageFields = { |
|
NamedGroup.nameOf(serverShare.namedGroupId), |
|
Utilities.indent( |
|
hexEncoder.encode(serverShare.keyExchange), " ") |
|
}; |
|
|
|
return messageFormat.format(messageFields); |
|
} |
|
} |
|
|
|
private static final class SHKeyShareStringizer implements SSLStringizer { |
|
@Override |
|
public String toString(ByteBuffer buffer) { |
|
try { |
|
return (new SHKeyShareSpec(buffer)).toString(); |
|
} catch (IOException ioe) { |
|
|
|
return ioe.getMessage(); |
|
} |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
private static final class SHKeyShareProducer implements HandshakeProducer { |
|
|
|
private SHKeyShareProducer() { |
|
// blank |
|
} |
|
|
|
@Override |
|
public byte[] produce(ConnectionContext context, |
|
HandshakeMessage message) throws IOException { |
|
|
|
ServerHandshakeContext shc = (ServerHandshakeContext)context; |
|
|
|
|
|
CHKeyShareSpec kss = |
|
(CHKeyShareSpec)shc.handshakeExtensions.get( |
|
SSLExtension.CH_KEY_SHARE); |
|
if (kss == null) { |
|
|
|
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
|
SSLLogger.warning( |
|
"Ignore, no client key_share extension"); |
|
} |
|
return null; |
|
} |
|
|
|
|
|
if (!shc.sslConfig.isAvailable(SSLExtension.SH_KEY_SHARE)) { |
|
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
|
SSLLogger.warning( |
|
"Ignore, no available server key_share extension"); |
|
} |
|
return null; |
|
} |
|
|
|
|
|
if ((shc.handshakeCredentials == null) || |
|
shc.handshakeCredentials.isEmpty()) { |
|
|
|
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
|
SSLLogger.warning( |
|
"No available client key share entries"); |
|
} |
|
return null; |
|
} |
|
|
|
KeyShareEntry keyShare = null; |
|
for (SSLCredentials cd : shc.handshakeCredentials) { |
|
NamedGroup ng = null; |
|
if (cd instanceof ECDHECredentials) { |
|
ng = ((ECDHECredentials)cd).namedGroup; |
|
} else if (cd instanceof DHECredentials) { |
|
ng = ((DHECredentials)cd).namedGroup; |
|
} |
|
|
|
if (ng == null) { |
|
continue; |
|
} |
|
|
|
SSLKeyExchange ke = SSLKeyExchange.valueOf(ng); |
|
if (ke == null) { |
|
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
|
SSLLogger.warning( |
|
"No key exchange for named group " + ng.name); |
|
} |
|
continue; |
|
} |
|
|
|
SSLPossession[] poses = ke.createPossessions(shc); |
|
for (SSLPossession pos : poses) { |
|
if (!(pos instanceof ECDHEPossession) && |
|
!(pos instanceof DHEPossession)) { |
|
|
|
continue; |
|
} |
|
|
|
|
|
shc.handshakeKeyExchange = ke; |
|
shc.handshakePossessions.add(pos); |
|
keyShare = new KeyShareEntry(ng.id, pos.encode()); |
|
break; |
|
} |
|
|
|
if (keyShare != null) { |
|
for (Map.Entry<Byte, HandshakeProducer> me : |
|
ke.getHandshakeProducers(shc)) { |
|
shc.handshakeProducers.put( |
|
me.getKey(), me.getValue()); |
|
} |
|
|
|
|
|
break; |
|
} |
|
} |
|
|
|
if (keyShare == null) { |
|
|
|
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
|
SSLLogger.warning( |
|
"No available server key_share extension"); |
|
} |
|
return null; |
|
} |
|
|
|
byte[] extData = keyShare.getEncoded(); |
|
|
|
|
|
SHKeyShareSpec spec = new SHKeyShareSpec(keyShare); |
|
shc.handshakeExtensions.put(SSLExtension.SH_KEY_SHARE, spec); |
|
|
|
return extData; |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
private static final class SHKeyShareConsumer implements ExtensionConsumer { |
|
|
|
private SHKeyShareConsumer() { |
|
// blank |
|
} |
|
|
|
@Override |
|
public void consume(ConnectionContext context, |
|
HandshakeMessage message, ByteBuffer buffer) throws IOException { |
|
|
|
ClientHandshakeContext chc = (ClientHandshakeContext)context; |
|
if (chc.clientRequestedNamedGroups == null || |
|
chc.clientRequestedNamedGroups.isEmpty()) { |
|
|
|
chc.conContext.fatal(Alert.UNEXPECTED_MESSAGE, |
|
"Unexpected key_share extension in ServerHello"); |
|
return; |
|
} |
|
|
|
|
|
if (!chc.sslConfig.isAvailable(SSLExtension.SH_KEY_SHARE)) { |
|
chc.conContext.fatal(Alert.UNEXPECTED_MESSAGE, |
|
"Unsupported key_share extension in ServerHello"); |
|
return; |
|
} |
|
|
|
|
|
SHKeyShareSpec spec; |
|
try { |
|
spec = new SHKeyShareSpec(buffer); |
|
} catch (IOException ioe) { |
|
chc.conContext.fatal(Alert.UNEXPECTED_MESSAGE, ioe); |
|
return; |
|
} |
|
|
|
KeyShareEntry keyShare = spec.serverShare; |
|
NamedGroup ng = NamedGroup.valueOf(keyShare.namedGroupId); |
|
if (ng == null || !SupportedGroups.isActivatable( |
|
chc.sslConfig.algorithmConstraints, ng)) { |
|
chc.conContext.fatal(Alert.UNEXPECTED_MESSAGE, |
|
"Unsupported named group: " + |
|
NamedGroup.nameOf(keyShare.namedGroupId)); |
|
return; |
|
} |
|
|
|
SSLKeyExchange ke = SSLKeyExchange.valueOf(ng); |
|
if (ke == null) { |
|
chc.conContext.fatal(Alert.UNEXPECTED_MESSAGE, |
|
"No key exchange for named group " + ng.name); |
|
return; |
|
} |
|
|
|
SSLCredentials credentials = null; |
|
if (ng.type == NamedGroupType.NAMED_GROUP_ECDHE) { |
|
try { |
|
ECDHECredentials ecdhec = |
|
ECDHECredentials.valueOf(ng, keyShare.keyExchange); |
|
if (ecdhec != null) { |
|
if (!chc.algorithmConstraints.permits( |
|
EnumSet.of(CryptoPrimitive.KEY_AGREEMENT), |
|
ecdhec.popPublicKey)) { |
|
chc.conContext.fatal(Alert.UNEXPECTED_MESSAGE, |
|
"ECDHE key share entry does not " + |
|
"comply to algorithm constraints"); |
|
} else { |
|
credentials = ecdhec; |
|
} |
|
} |
|
} catch (IOException | GeneralSecurityException ex) { |
|
chc.conContext.fatal(Alert.UNEXPECTED_MESSAGE, |
|
"Cannot decode named group: " + |
|
NamedGroup.nameOf(keyShare.namedGroupId)); |
|
} |
|
} else if (ng.type == NamedGroupType.NAMED_GROUP_FFDHE) { |
|
try { |
|
DHECredentials dhec = |
|
DHECredentials.valueOf(ng, keyShare.keyExchange); |
|
if (dhec != null) { |
|
if (!chc.algorithmConstraints.permits( |
|
EnumSet.of(CryptoPrimitive.KEY_AGREEMENT), |
|
dhec.popPublicKey)) { |
|
chc.conContext.fatal(Alert.UNEXPECTED_MESSAGE, |
|
"DHE key share entry does not " + |
|
"comply to algorithm constraints"); |
|
} else { |
|
credentials = dhec; |
|
} |
|
} |
|
} catch (IOException | GeneralSecurityException ex) { |
|
chc.conContext.fatal(Alert.UNEXPECTED_MESSAGE, |
|
"Cannot decode named group: " + |
|
NamedGroup.nameOf(keyShare.namedGroupId)); |
|
} |
|
} else { |
|
chc.conContext.fatal(Alert.UNEXPECTED_MESSAGE, |
|
"Unsupported named group: " + |
|
NamedGroup.nameOf(keyShare.namedGroupId)); |
|
} |
|
|
|
if (credentials == null) { |
|
chc.conContext.fatal(Alert.UNEXPECTED_MESSAGE, |
|
"Unsupported named group: " + ng.name); |
|
} |
|
|
|
|
|
chc.handshakeKeyExchange = ke; |
|
chc.handshakeCredentials.add(credentials); |
|
chc.handshakeExtensions.put(SSLExtension.SH_KEY_SHARE, spec); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
private static final class SHKeyShareAbsence implements HandshakeAbsence { |
|
@Override |
|
public void absent(ConnectionContext context, |
|
HandshakeMessage message) throws IOException { |
|
|
|
ClientHandshakeContext chc = (ClientHandshakeContext)context; |
|
|
|
|
|
if (SSLLogger.isOn && SSLLogger.isOn("handshake")) { |
|
SSLLogger.fine( |
|
"No key_share extension in ServerHello, " + |
|
"cleanup the key shares if necessary"); |
|
} |
|
chc.handshakePossessions.clear(); |
|
} |
|
} |
|
|
|
|
|
|
|
*/ |
|
static final class HRRKeyShareSpec implements SSLExtensionSpec { |
|
final int selectedGroup; |
|
|
|
HRRKeyShareSpec(NamedGroup serverGroup) { |
|
this.selectedGroup = serverGroup.id; |
|
} |
|
|
|
private HRRKeyShareSpec(ByteBuffer buffer) throws IOException { |
|
// struct { |
|
// NamedGroup selected_group; |
|
|
|
if (buffer.remaining() != 2) { |
|
throw new SSLProtocolException( |
|
"Invalid key_share extension: " + |
|
"improper data (length=" + buffer.remaining() + ")"); |
|
} |
|
|
|
this.selectedGroup = Record.getInt16(buffer); |
|
} |
|
|
|
@Override |
|
public String toString() { |
|
MessageFormat messageFormat = new MessageFormat( |
|
"\"selected group\": '['{0}']'", Locale.ENGLISH); |
|
|
|
Object[] messageFields = { |
|
NamedGroup.nameOf(selectedGroup) |
|
}; |
|
return messageFormat.format(messageFields); |
|
} |
|
} |
|
|
|
private static final class HRRKeyShareStringizer implements SSLStringizer { |
|
@Override |
|
public String toString(ByteBuffer buffer) { |
|
try { |
|
return (new HRRKeyShareSpec(buffer)).toString(); |
|
} catch (IOException ioe) { |
|
|
|
return ioe.getMessage(); |
|
} |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
private static final |
|
class HRRKeyShareProducer implements HandshakeProducer { |
|
|
|
private HRRKeyShareProducer() { |
|
// blank |
|
} |
|
|
|
@Override |
|
public byte[] produce(ConnectionContext context, |
|
HandshakeMessage message) throws IOException { |
|
|
|
ServerHandshakeContext shc = (ServerHandshakeContext) context; |
|
|
|
|
|
if (!shc.sslConfig.isAvailable(SSLExtension.HRR_KEY_SHARE)) { |
|
shc.conContext.fatal(Alert.UNEXPECTED_MESSAGE, |
|
"Unsupported key_share extension in HelloRetryRequest"); |
|
return null; |
|
} |
|
|
|
if (shc.clientRequestedNamedGroups == null || |
|
shc.clientRequestedNamedGroups.isEmpty()) { |
|
|
|
shc.conContext.fatal(Alert.UNEXPECTED_MESSAGE, |
|
"Unexpected key_share extension in HelloRetryRequest"); |
|
return null; |
|
} |
|
|
|
NamedGroup selectedGroup = null; |
|
for (NamedGroup ng : shc.clientRequestedNamedGroups) { |
|
if (SupportedGroups.isActivatable( |
|
shc.sslConfig.algorithmConstraints, ng)) { |
|
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
|
SSLLogger.fine( |
|
"HelloRetryRequest selected named group: " + |
|
ng.name); |
|
} |
|
|
|
selectedGroup = ng; |
|
break; |
|
} |
|
} |
|
|
|
if (selectedGroup == null) { |
|
shc.conContext.fatal(Alert.UNEXPECTED_MESSAGE, |
|
new IOException("No common named group")); |
|
return null; |
|
} |
|
|
|
byte[] extdata = new byte[] { |
|
(byte)((selectedGroup.id >> 8) & 0xFF), |
|
(byte)(selectedGroup.id & 0xFF) |
|
}; |
|
|
|
|
|
shc.serverSelectedNamedGroup = selectedGroup; |
|
shc.handshakeExtensions.put(SSLExtension.HRR_KEY_SHARE, |
|
new HRRKeyShareSpec(selectedGroup)); |
|
|
|
return extdata; |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
private static final |
|
class HRRKeyShareReproducer implements HandshakeProducer { |
|
|
|
private HRRKeyShareReproducer() { |
|
// blank |
|
} |
|
|
|
@Override |
|
public byte[] produce(ConnectionContext context, |
|
HandshakeMessage message) throws IOException { |
|
|
|
ServerHandshakeContext shc = (ServerHandshakeContext) context; |
|
|
|
|
|
if (!shc.sslConfig.isAvailable(SSLExtension.HRR_KEY_SHARE)) { |
|
shc.conContext.fatal(Alert.UNEXPECTED_MESSAGE, |
|
"Unsupported key_share extension in HelloRetryRequest"); |
|
return null; |
|
} |
|
|
|
CHKeyShareSpec spec = (CHKeyShareSpec)shc.handshakeExtensions.get( |
|
SSLExtension.CH_KEY_SHARE); |
|
if (spec != null && spec.clientShares != null && |
|
spec.clientShares.size() == 1) { |
|
int namedGroupId = spec.clientShares.get(0).namedGroupId; |
|
|
|
byte[] extdata = new byte[] { |
|
(byte)((namedGroupId >> 8) & 0xFF), |
|
(byte)(namedGroupId & 0xFF) |
|
}; |
|
|
|
return extdata; |
|
} |
|
|
|
return null; |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
private static final |
|
class HRRKeyShareConsumer implements ExtensionConsumer { |
|
|
|
private HRRKeyShareConsumer() { |
|
// blank |
|
} |
|
|
|
@Override |
|
public void consume(ConnectionContext context, |
|
HandshakeMessage message, ByteBuffer buffer) throws IOException { |
|
|
|
ClientHandshakeContext chc = (ClientHandshakeContext)context; |
|
|
|
|
|
if (!chc.sslConfig.isAvailable(SSLExtension.HRR_KEY_SHARE)) { |
|
chc.conContext.fatal(Alert.UNEXPECTED_MESSAGE, |
|
"Unsupported key_share extension in HelloRetryRequest"); |
|
return; |
|
} |
|
|
|
if (chc.clientRequestedNamedGroups == null || |
|
chc.clientRequestedNamedGroups.isEmpty()) { |
|
|
|
chc.conContext.fatal(Alert.UNEXPECTED_MESSAGE, |
|
"Unexpected key_share extension in HelloRetryRequest"); |
|
return; |
|
} |
|
|
|
|
|
HRRKeyShareSpec spec; |
|
try { |
|
spec = new HRRKeyShareSpec(buffer); |
|
} catch (IOException ioe) { |
|
chc.conContext.fatal(Alert.UNEXPECTED_MESSAGE, ioe); |
|
return; |
|
} |
|
|
|
NamedGroup serverGroup = NamedGroup.valueOf(spec.selectedGroup); |
|
if (serverGroup == null) { |
|
chc.conContext.fatal(Alert.UNEXPECTED_MESSAGE, |
|
"Unsupported HelloRetryRequest selected group: " + |
|
NamedGroup.nameOf(spec.selectedGroup)); |
|
return; |
|
} |
|
|
|
if (!chc.clientRequestedNamedGroups.contains(serverGroup)) { |
|
chc.conContext.fatal(Alert.UNEXPECTED_MESSAGE, |
|
"Unexpected HelloRetryRequest selected group: " + |
|
serverGroup.name); |
|
return; |
|
} |
|
|
|
// update the context |
|
|
|
// When sending the new ClientHello, the client MUST replace the |
|
// original "key_share" extension with one containing only a new |
|
// KeyShareEntry for the group indicated in the selected_group |
|
// field of the triggering HelloRetryRequest. |
|
|
|
chc.serverSelectedNamedGroup = serverGroup; |
|
chc.handshakeExtensions.put(SSLExtension.HRR_KEY_SHARE, spec); |
|
} |
|
} |
|
} |