Path: blob/aarch64-shenandoah-jdk8u272-b10/jdk/test/javax/net/ssl/templates/SSLExplorer.java
38854 views
/*1* Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425import java.nio.ByteBuffer;26import java.nio.BufferUnderflowException;27import java.io.IOException;28import javax.net.ssl.*;29import java.util.*;3031import sun.misc.HexDumpEncoder;3233/**34* Instances of this class acts as an explorer of the network data of an35* SSL/TLS connection.36*/37public final class SSLExplorer {3839// Private constructor prevents construction outside this class.40private SSLExplorer() {41}4243/**44* The header size of TLS/SSL records.45* <P>46* The value of this constant is {@value}.47*/48public final static int RECORD_HEADER_SIZE = 0x05;4950/**51* Returns the required number of bytes in the {@code source}52* {@link ByteBuffer} necessary to explore SSL/TLS connection.53* <P>54* This method tries to parse as few bytes as possible from55* {@code source} byte buffer to get the length of an56* SSL/TLS record.57* <P>58* This method accesses the {@code source} parameter in read-only59* mode, and does not update the buffer's properties such as capacity,60* limit, position, and mark values.61*62* @param source63* a {@link ByteBuffer} containing64* inbound or outbound network data for an SSL/TLS connection.65* @throws BufferUnderflowException if less than {@code RECORD_HEADER_SIZE}66* bytes remaining in {@code source}67* @return the required size in byte to explore an SSL/TLS connection68*/69public final static int getRequiredSize(ByteBuffer source) {7071ByteBuffer input = source.duplicate();7273// Do we have a complete header?74if (input.remaining() < RECORD_HEADER_SIZE) {75throw new BufferUnderflowException();76}7778// Is it a handshake message?79byte firstByte = input.get();80byte secondByte = input.get();81byte thirdByte = input.get();82if ((firstByte & 0x80) != 0 && thirdByte == 0x01) {83// looks like a V2ClientHello84// return (((firstByte & 0x7F) << 8) | (secondByte & 0xFF)) + 2;85return RECORD_HEADER_SIZE; // Only need the header fields86} else {87return (((input.get() & 0xFF) << 8) | (input.get() & 0xFF)) + 5;88}89}9091/**92* Returns the required number of bytes in the {@code source} byte array93* necessary to explore SSL/TLS connection.94* <P>95* This method tries to parse as few bytes as possible from96* {@code source} byte array to get the length of an97* SSL/TLS record.98*99* @param source100* a byte array containing inbound or outbound network data for101* an SSL/TLS connection.102* @param offset103* the start offset in array {@code source} at which the104* network data is read from.105* @param length106* the maximum number of bytes to read.107*108* @throws BufferUnderflowException if less than {@code RECORD_HEADER_SIZE}109* bytes remaining in {@code source}110* @return the required size in byte to explore an SSL/TLS connection111*/112public final static int getRequiredSize(byte[] source,113int offset, int length) throws IOException {114115ByteBuffer byteBuffer =116ByteBuffer.wrap(source, offset, length).asReadOnlyBuffer();117return getRequiredSize(byteBuffer);118}119120/**121* Launch and explore the security capabilities from byte buffer.122* <P>123* This method tries to parse as few records as possible from124* {@code source} byte buffer to get the {@link SSLCapabilities}125* of an SSL/TLS connection.126* <P>127* Please NOTE that this method must be called before any handshaking128* occurs. The behavior of this method is not defined in this release129* if the handshake has begun, or has completed.130* <P>131* This method accesses the {@code source} parameter in read-only132* mode, and does not update the buffer's properties such as capacity,133* limit, position, and mark values.134*135* @param source136* a {@link ByteBuffer} containing137* inbound or outbound network data for an SSL/TLS connection.138*139* @throws IOException on network data error140* @throws BufferUnderflowException if not enough source bytes available141* to make a complete exploration.142*143* @return the explored {@link SSLCapabilities} of the SSL/TLS144* connection145*/146public final static SSLCapabilities explore(ByteBuffer source)147throws IOException {148149ByteBuffer input = source.duplicate();150151// Do we have a complete header?152if (input.remaining() < RECORD_HEADER_SIZE) {153throw new BufferUnderflowException();154}155156// Is it a handshake message?157byte firstByte = input.get();158byte secondByte = input.get();159byte thirdByte = input.get();160if ((firstByte & 0x80) != 0 && thirdByte == 0x01) {161// looks like a V2ClientHello162return exploreV2HelloRecord(input,163firstByte, secondByte, thirdByte);164} else if (firstByte == 22) { // 22: handshake record165return exploreTLSRecord(input,166firstByte, secondByte, thirdByte);167} else {168throw new SSLException("Not handshake record");169}170}171172/**173* Launch and explore the security capabilities from byte array.174* <P>175* Please NOTE that this method must be called before any handshaking176* occurs. The behavior of this method is not defined in this release177* if the handshake has begun, or has completed. Once handshake has178* begun, or has completed, the security capabilities can not and179* should not be launched with this method.180*181* @param source182* a byte array containing inbound or outbound network data for183* an SSL/TLS connection.184* @param offset185* the start offset in array {@code source} at which the186* network data is read from.187* @param length188* the maximum number of bytes to read.189*190* @throws IOException on network data error191* @throws BufferUnderflowException if not enough source bytes available192* to make a complete exploration.193* @return the explored {@link SSLCapabilities} of the SSL/TLS194* connection195*196* @see #explore(ByteBuffer)197*/198public final static SSLCapabilities explore(byte[] source,199int offset, int length) throws IOException {200ByteBuffer byteBuffer =201ByteBuffer.wrap(source, offset, length).asReadOnlyBuffer();202return explore(byteBuffer);203}204205/*206* uint8 V2CipherSpec[3];207* struct {208* uint16 msg_length; // The highest bit MUST be 1;209* // the remaining bits contain the length210* // of the following data in bytes.211* uint8 msg_type; // MUST be 1212* Version version;213* uint16 cipher_spec_length; // It cannot be zero and MUST be a214* // multiple of the V2CipherSpec length.215* uint16 session_id_length; // This field MUST be empty.216* uint16 challenge_length; // SHOULD use a 32-byte challenge217* V2CipherSpec cipher_specs[V2ClientHello.cipher_spec_length];218* opaque session_id[V2ClientHello.session_id_length];219* opaque challenge[V2ClientHello.challenge_length;220* } V2ClientHello;221*/222private static SSLCapabilities exploreV2HelloRecord(223ByteBuffer input, byte firstByte, byte secondByte,224byte thirdByte) throws IOException {225226// We only need the header. We have already had enough source bytes.227// int recordLength = (firstByte & 0x7F) << 8) | (secondByte & 0xFF);228try {229// Is it a V2ClientHello?230if (thirdByte != 0x01) {231throw new SSLException(232"Unsupported or Unrecognized SSL record");233}234235// What's the hello version?236byte helloVersionMajor = input.get();237byte helloVersionMinor = input.get();238239// 0x00: major version of SSLv20240// 0x02: minor version of SSLv20241//242// SNIServerName is an extension, SSLv20 doesn't support extension.243return new SSLCapabilitiesImpl((byte)0x00, (byte)0x02,244helloVersionMajor, helloVersionMinor,245Collections.<SNIServerName>emptyList());246} catch (BufferUnderflowException bufe) {247throw new SSLProtocolException(248"Invalid handshake record");249}250}251252/*253* struct {254* uint8 major;255* uint8 minor;256* } ProtocolVersion;257*258* enum {259* change_cipher_spec(20), alert(21), handshake(22),260* application_data(23), (255)261* } ContentType;262*263* struct {264* ContentType type;265* ProtocolVersion version;266* uint16 length;267* opaque fragment[TLSPlaintext.length];268* } TLSPlaintext;269*/270private static SSLCapabilities exploreTLSRecord(271ByteBuffer input, byte firstByte, byte secondByte,272byte thirdByte) throws IOException {273274// Is it a handshake message?275if (firstByte != 22) { // 22: handshake record276throw new SSLException("Not handshake record");277}278279// We need the record version to construct SSLCapabilities.280byte recordMajorVersion = secondByte;281byte recordMinorVersion = thirdByte;282283// Is there enough data for a full record?284int recordLength = getInt16(input);285if (recordLength > input.remaining()) {286throw new BufferUnderflowException();287}288289// We have already had enough source bytes.290try {291return exploreHandshake(input,292recordMajorVersion, recordMinorVersion, recordLength);293} catch (BufferUnderflowException bufe) {294throw new SSLProtocolException(295"Invalid handshake record");296}297}298299/*300* enum {301* hello_request(0), client_hello(1), server_hello(2),302* certificate(11), server_key_exchange (12),303* certificate_request(13), server_hello_done(14),304* certificate_verify(15), client_key_exchange(16),305* finished(20)306* (255)307* } HandshakeType;308*309* struct {310* HandshakeType msg_type;311* uint24 length;312* select (HandshakeType) {313* case hello_request: HelloRequest;314* case client_hello: ClientHello;315* case server_hello: ServerHello;316* case certificate: Certificate;317* case server_key_exchange: ServerKeyExchange;318* case certificate_request: CertificateRequest;319* case server_hello_done: ServerHelloDone;320* case certificate_verify: CertificateVerify;321* case client_key_exchange: ClientKeyExchange;322* case finished: Finished;323* } body;324* } Handshake;325*/326private static SSLCapabilities exploreHandshake(327ByteBuffer input, byte recordMajorVersion,328byte recordMinorVersion, int recordLength) throws IOException {329330// What is the handshake type?331byte handshakeType = input.get();332if (handshakeType != 0x01) { // 0x01: client_hello message333throw new IllegalStateException("Not initial handshaking");334}335336// What is the handshake body length?337int handshakeLength = getInt24(input);338339// Theoretically, a single handshake message might span multiple340// records, but in practice this does not occur.341if (handshakeLength > (recordLength - 4)) { // 4: handshake header size342throw new SSLException("Handshake message spans multiple records");343}344345input = input.duplicate();346input.limit(handshakeLength + input.position());347return exploreClientHello(input,348recordMajorVersion, recordMinorVersion);349}350351/*352* struct {353* uint32 gmt_unix_time;354* opaque random_bytes[28];355* } Random;356*357* opaque SessionID<0..32>;358*359* uint8 CipherSuite[2];360*361* enum { null(0), (255) } CompressionMethod;362*363* struct {364* ProtocolVersion client_version;365* Random random;366* SessionID session_id;367* CipherSuite cipher_suites<2..2^16-2>;368* CompressionMethod compression_methods<1..2^8-1>;369* select (extensions_present) {370* case false:371* struct {};372* case true:373* Extension extensions<0..2^16-1>;374* };375* } ClientHello;376*/377private static SSLCapabilities exploreClientHello(378ByteBuffer input,379byte recordMajorVersion,380byte recordMinorVersion) throws IOException {381382List<SNIServerName> snList = Collections.<SNIServerName>emptyList();383384// client version385byte helloMajorVersion = input.get();386byte helloMinorVersion = input.get();387388// ignore random389int position = input.position();390input.position(position + 32); // 32: the length of Random391392// ignore session id393ignoreByteVector8(input);394395// ignore cipher_suites396ignoreByteVector16(input);397398// ignore compression methods399ignoreByteVector8(input);400401if (input.remaining() > 0) {402snList = exploreExtensions(input);403}404405return new SSLCapabilitiesImpl(406recordMajorVersion, recordMinorVersion,407helloMajorVersion, helloMinorVersion, snList);408}409410/*411* struct {412* ExtensionType extension_type;413* opaque extension_data<0..2^16-1>;414* } Extension;415*416* enum {417* server_name(0), max_fragment_length(1),418* client_certificate_url(2), trusted_ca_keys(3),419* truncated_hmac(4), status_request(5), (65535)420* } ExtensionType;421*/422private static List<SNIServerName> exploreExtensions(ByteBuffer input)423throws IOException {424425int length = getInt16(input); // length of extensions426while (length > 0) {427int extType = getInt16(input); // extenson type428int extLen = getInt16(input); // length of extension data429430if (extType == 0x00) { // 0x00: type of server name indication431return exploreSNIExt(input, extLen);432} else { // ignore other extensions433ignoreByteVector(input, extLen);434}435436length -= extLen + 4;437}438439return Collections.<SNIServerName>emptyList();440}441442/*443* struct {444* NameType name_type;445* select (name_type) {446* case host_name: HostName;447* } name;448* } ServerName;449*450* enum {451* host_name(0), (255)452* } NameType;453*454* opaque HostName<1..2^16-1>;455*456* struct {457* ServerName server_name_list<1..2^16-1>458* } ServerNameList;459*/460private static List<SNIServerName> exploreSNIExt(ByteBuffer input,461int extLen) throws IOException {462463Map<Integer, SNIServerName> sniMap = new LinkedHashMap<>();464465int remains = extLen;466if (extLen >= 2) { // "server_name" extension in ClientHello467int listLen = getInt16(input); // length of server_name_list468if (listLen == 0 || listLen + 2 != extLen) {469throw new SSLProtocolException(470"Invalid server name indication extension");471}472473remains -= 2; // 0x02: the length field of server_name_list474while (remains > 0) {475int code = getInt8(input); // name_type476int snLen = getInt16(input); // length field of server name477if (snLen > remains) {478throw new SSLProtocolException(479"Not enough data to fill declared vector size");480}481byte[] encoded = new byte[snLen];482input.get(encoded);483484SNIServerName serverName;485switch (code) {486case StandardConstants.SNI_HOST_NAME:487if (encoded.length == 0) {488throw new SSLProtocolException(489"Empty HostName in server name indication");490}491serverName = new SNIHostName(encoded);492break;493default:494serverName = new UnknownServerName(code, encoded);495}496// check for duplicated server name type497if (sniMap.put(serverName.getType(), serverName) != null) {498throw new SSLProtocolException(499"Duplicated server name of type " +500serverName.getType());501}502503remains -= encoded.length + 3; // NameType: 1 byte504// HostName length: 2 bytes505}506} else if (extLen == 0) { // "server_name" extension in ServerHello507throw new SSLProtocolException(508"Not server name indication extension in client");509}510511if (remains != 0) {512throw new SSLProtocolException(513"Invalid server name indication extension");514}515516return Collections.<SNIServerName>unmodifiableList(517new ArrayList<>(sniMap.values()));518}519520private static int getInt8(ByteBuffer input) {521return input.get();522}523524private static int getInt16(ByteBuffer input) {525return ((input.get() & 0xFF) << 8) | (input.get() & 0xFF);526}527528private static int getInt24(ByteBuffer input) {529return ((input.get() & 0xFF) << 16) | ((input.get() & 0xFF) << 8) |530(input.get() & 0xFF);531}532533private static void ignoreByteVector8(ByteBuffer input) {534ignoreByteVector(input, getInt8(input));535}536537private static void ignoreByteVector16(ByteBuffer input) {538ignoreByteVector(input, getInt16(input));539}540541private static void ignoreByteVector24(ByteBuffer input) {542ignoreByteVector(input, getInt24(input));543}544545private static void ignoreByteVector(ByteBuffer input, int length) {546if (length != 0) {547int position = input.position();548input.position(position + length);549}550}551552private static class UnknownServerName extends SNIServerName {553UnknownServerName(int code, byte[] encoded) {554super(code, encoded);555}556}557558private static final class SSLCapabilitiesImpl extends SSLCapabilities {559private final static Map<Integer, String> versionMap = new HashMap<>(5);560561private final String recordVersion;562private final String helloVersion;563List<SNIServerName> sniNames;564565static {566versionMap.put(0x0002, "SSLv2Hello");567versionMap.put(0x0300, "SSLv3");568versionMap.put(0x0301, "TLSv1");569versionMap.put(0x0302, "TLSv1.1");570versionMap.put(0x0303, "TLSv1.2");571}572573SSLCapabilitiesImpl(byte recordMajorVersion, byte recordMinorVersion,574byte helloMajorVersion, byte helloMinorVersion,575List<SNIServerName> sniNames) {576577int version = (recordMajorVersion << 8) | recordMinorVersion;578this.recordVersion = versionMap.get(version) != null ?579versionMap.get(version) :580unknownVersion(recordMajorVersion, recordMinorVersion);581582version = (helloMajorVersion << 8) | helloMinorVersion;583this.helloVersion = versionMap.get(version) != null ?584versionMap.get(version) :585unknownVersion(helloMajorVersion, helloMinorVersion);586587this.sniNames = sniNames;588}589590@Override591public String getRecordVersion() {592return recordVersion;593}594595@Override596public String getHelloVersion() {597return helloVersion;598}599600@Override601public List<SNIServerName> getServerNames() {602if (!sniNames.isEmpty()) {603return Collections.<SNIServerName>unmodifiableList(sniNames);604}605606return sniNames;607}608609private static String unknownVersion(byte major, byte minor) {610return "Unknown-" + ((int)major) + "." + ((int)minor);611}612}613}614615616617