|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
|
|
package sun.security.ssl; |
|
|
|
import java.io.IOException; |
|
import java.nio.ByteBuffer; |
|
import java.text.MessageFormat; |
|
import java.util.*; |
|
|
|
import sun.security.ssl.SSLHandshake.HandshakeMessage; |
|
import sun.security.util.HexDumpEncoder; |
|
|
|
|
|
|
|
*/ |
|
final class SSLExtensions { |
|
private final HandshakeMessage handshakeMessage; |
|
private Map<SSLExtension, byte[]> extMap = new LinkedHashMap<>(); |
|
private int encodedLength; |
|
|
|
|
|
private final Map<Integer, byte[]> logMap = |
|
SSLLogger.isOn ? null : new LinkedHashMap<>(); |
|
|
|
SSLExtensions(HandshakeMessage handshakeMessage) { |
|
this.handshakeMessage = handshakeMessage; |
|
this.encodedLength = 2; |
|
} |
|
|
|
SSLExtensions(HandshakeMessage hm, |
|
ByteBuffer m, SSLExtension[] extensions) throws IOException { |
|
this.handshakeMessage = hm; |
|
|
|
int len = Record.getInt16(m); |
|
encodedLength = len + 2; |
|
while (len > 0) { |
|
int extId = Record.getInt16(m); |
|
int extLen = Record.getInt16(m); |
|
if (extLen > m.remaining()) { |
|
hm.handshakeContext.conContext.fatal(Alert.ILLEGAL_PARAMETER, |
|
"Error parsing extension (" + extId + |
|
"): no sufficient data"); |
|
} |
|
|
|
SSLHandshake handshakeType = hm.handshakeType(); |
|
if (SSLExtension.isConsumable(extId) && |
|
SSLExtension.valueOf(handshakeType, extId) == null) { |
|
hm.handshakeContext.conContext.fatal( |
|
Alert.UNSUPPORTED_EXTENSION, |
|
"extension (" + extId + |
|
") should not be presented in " + handshakeType.name); |
|
} |
|
|
|
boolean isSupported = false; |
|
for (SSLExtension extension : extensions) { |
|
if ((extension.id != extId) || |
|
(extension.onLoadConsumer == null)) { |
|
continue; |
|
} |
|
|
|
if (extension.handshakeType != handshakeType) { |
|
hm.handshakeContext.conContext.fatal( |
|
Alert.UNSUPPORTED_EXTENSION, |
|
"extension (" + extId + ") should not be " + |
|
"presented in " + handshakeType.name); |
|
} |
|
|
|
byte[] extData = new byte[extLen]; |
|
m.get(extData); |
|
extMap.put(extension, extData); |
|
if (logMap != null) { |
|
logMap.put(extId, extData); |
|
} |
|
|
|
isSupported = true; |
|
break; |
|
} |
|
|
|
if (!isSupported) { |
|
if (logMap != null) { |
|
|
|
byte[] extData = new byte[extLen]; |
|
m.get(extData); |
|
logMap.put(extId, extData); |
|
|
|
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
|
SSLLogger.fine( |
|
"Ignore unknown or unsupported extension", |
|
toString(extId, extData)); |
|
} |
|
} else { |
|
|
|
int pos = m.position() + extLen; |
|
m.position(pos); |
|
} |
|
} |
|
|
|
len -= extLen + 4; |
|
} |
|
} |
|
|
|
byte[] get(SSLExtension ext) { |
|
return extMap.get(ext); |
|
} |
|
|
|
|
|
|
|
*/ |
|
void consumeOnLoad(HandshakeContext context, |
|
SSLExtension[] extensions) throws IOException { |
|
for (SSLExtension extension : extensions) { |
|
if (context.negotiatedProtocol != null && |
|
!extension.isAvailable(context.negotiatedProtocol)) { |
|
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
|
SSLLogger.fine( |
|
"Ignore unsupported extension: " + extension.name); |
|
} |
|
continue; |
|
} |
|
|
|
if (!extMap.containsKey(extension)) { |
|
if (extension.onLoadAbsence != null) { |
|
extension.absentOnLoad(context, handshakeMessage); |
|
} else if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
|
SSLLogger.fine( |
|
"Ignore unavailable extension: " + extension.name); |
|
} |
|
continue; |
|
} |
|
|
|
|
|
if (extension.onLoadConsumer == null) { |
|
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
|
SSLLogger.warning( |
|
"Ignore unsupported extension: " + extension.name); |
|
} |
|
continue; |
|
} |
|
|
|
ByteBuffer m = ByteBuffer.wrap(extMap.get(extension)); |
|
extension.consumeOnLoad(context, handshakeMessage, m); |
|
|
|
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
|
SSLLogger.fine("Consumed extension: " + extension.name); |
|
} |
|
} |
|
} |
|
|
|
|
|
|
|
*/ |
|
void consumeOnTrade(HandshakeContext context, |
|
SSLExtension[] extensions) throws IOException { |
|
for (SSLExtension extension : extensions) { |
|
if (!extMap.containsKey(extension)) { |
|
if (extension.onTradeAbsence != null) { |
|
extension.absentOnTrade(context, handshakeMessage); |
|
} else if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
|
SSLLogger.fine( |
|
"Ignore unavailable extension: " + extension.name); |
|
} |
|
continue; |
|
} |
|
|
|
if (extension.onTradeConsumer == null) { |
|
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
|
SSLLogger.warning( |
|
"Ignore impact of unsupported extension: " + |
|
extension.name); |
|
} |
|
continue; |
|
} |
|
|
|
extension.consumeOnTrade(context, handshakeMessage); |
|
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
|
SSLLogger.fine("Populated with extension: " + extension.name); |
|
} |
|
} |
|
} |
|
|
|
|
|
|
|
*/ |
|
void produce(HandshakeContext context, |
|
SSLExtension[] extensions) throws IOException { |
|
for (SSLExtension extension : extensions) { |
|
if (extMap.containsKey(extension)) { |
|
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
|
SSLLogger.fine( |
|
"Ignore, duplicated extension: " + |
|
extension.name); |
|
} |
|
continue; |
|
} |
|
|
|
if (extension.networkProducer == null) { |
|
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
|
SSLLogger.warning( |
|
"Ignore, no extension producer defined: " + |
|
extension.name); |
|
} |
|
continue; |
|
} |
|
|
|
byte[] encoded = extension.produce(context, handshakeMessage); |
|
if (encoded != null) { |
|
extMap.put(extension, encoded); |
|
encodedLength += encoded.length + 4; |
|
// extension_data length(2) |
|
} else if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
|
|
|
SSLLogger.fine( |
|
"Ignore, context unavailable extension: " + |
|
extension.name); |
|
} |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
void reproduce(HandshakeContext context, |
|
SSLExtension[] extensions) throws IOException { |
|
for (SSLExtension extension : extensions) { |
|
if (extension.networkProducer == null) { |
|
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
|
SSLLogger.warning( |
|
"Ignore, no extension producer defined: " + |
|
extension.name); |
|
} |
|
continue; |
|
} |
|
|
|
byte[] encoded = extension.produce(context, handshakeMessage); |
|
if (encoded != null) { |
|
if (extMap.containsKey(extension)) { |
|
byte[] old = extMap.replace(extension, encoded); |
|
if (old != null) { |
|
encodedLength -= old.length + 4; |
|
} |
|
encodedLength += encoded.length + 4; |
|
} else { |
|
extMap.put(extension, encoded); |
|
encodedLength += encoded.length + 4; |
|
// extension_type (2) |
|
// extension_data length(2) |
|
} |
|
} else if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { |
|
|
|
SSLLogger.fine( |
|
"Ignore, context unavailable extension: " + |
|
extension.name); |
|
} |
|
} |
|
} |
|
|
|
// Note that TLS 1.3 may use empty extensions. Please consider it while |
|
|
|
int length() { |
|
if (extMap.isEmpty()) { |
|
return 0; |
|
} else { |
|
return encodedLength; |
|
} |
|
} |
|
|
|
// Note that TLS 1.3 may use empty extensions. Please consider it while |
|
|
|
void send(HandshakeOutStream hos) throws IOException { |
|
int extsLen = length(); |
|
if (extsLen == 0) { |
|
return; |
|
} |
|
hos.putInt16(extsLen - 2); |
|
|
|
for (SSLExtension ext : SSLExtension.values()) { |
|
byte[] extData = extMap.get(ext); |
|
if (extData != null) { |
|
hos.putInt16(ext.id); |
|
hos.putBytes16(extData); |
|
} |
|
} |
|
} |
|
|
|
@Override |
|
public String toString() { |
|
if (extMap.isEmpty() && (logMap == null || logMap.isEmpty())) { |
|
return "<no extension>"; |
|
} else { |
|
StringBuilder builder = new StringBuilder(512); |
|
if (logMap != null) { |
|
for (Map.Entry<Integer, byte[]> en : logMap.entrySet()) { |
|
SSLExtension ext = SSLExtension.valueOf( |
|
handshakeMessage.handshakeType(), en.getKey()); |
|
if (builder.length() != 0) { |
|
builder.append(",\n"); |
|
} |
|
if (ext != null) { |
|
builder.append( |
|
ext.toString(ByteBuffer.wrap(en.getValue()))); |
|
} else { |
|
builder.append(toString(en.getKey(), en.getValue())); |
|
} |
|
} |
|
|
|
return builder.toString(); |
|
} else { |
|
for (Map.Entry<SSLExtension, byte[]> en : extMap.entrySet()) { |
|
if (builder.length() != 0) { |
|
builder.append(",\n"); |
|
} |
|
builder.append( |
|
en.getKey().toString(ByteBuffer.wrap(en.getValue()))); |
|
} |
|
|
|
return builder.toString(); |
|
} |
|
} |
|
} |
|
|
|
private static String toString(int extId, byte[] extData) { |
|
MessageFormat messageFormat = new MessageFormat( |
|
"\"unknown extension ({0})\": '{'\n" + |
|
"{1}\n" + |
|
"'}'", |
|
Locale.ENGLISH); |
|
|
|
HexDumpEncoder hexEncoder = new HexDumpEncoder(); |
|
String encoded = hexEncoder.encodeBuffer(extData); |
|
|
|
Object[] messageFields = { |
|
extId, |
|
Utilities.indent(encoded) |
|
}; |
|
|
|
return messageFormat.format(messageFields); |
|
} |
|
} |