Path: blob/master/test/jdk/javax/net/ssl/templates/SSLExplorer.java
41152 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.*;3031/**32* Instances of this class acts as an explorer of the network data of an33* SSL/TLS connection.34*/35public final class SSLExplorer {3637// Private constructor prevents construction outside this class.38private SSLExplorer() {39}4041/**42* The header size of TLS/SSL records.43* <P>44* The value of this constant is {@value}.45*/46public final static int RECORD_HEADER_SIZE = 0x05;4748/**49* Returns the required number of bytes in the {@code source}50* {@link ByteBuffer} necessary to explore SSL/TLS connection.51* <P>52* This method tries to parse as few bytes as possible from53* {@code source} byte buffer to get the length of an54* SSL/TLS record.55* <P>56* This method accesses the {@code source} parameter in read-only57* mode, and does not update the buffer's properties such as capacity,58* limit, position, and mark values.59*60* @param source61* a {@link ByteBuffer} containing62* inbound or outbound network data for an SSL/TLS connection.63* @throws BufferUnderflowException if less than {@code RECORD_HEADER_SIZE}64* bytes remaining in {@code source}65* @return the required size in byte to explore an SSL/TLS connection66*/67public final static int getRequiredSize(ByteBuffer source) {6869ByteBuffer input = source.duplicate();7071// Do we have a complete header?72if (input.remaining() < RECORD_HEADER_SIZE) {73throw new BufferUnderflowException();74}7576// Is it a handshake message?77byte firstByte = input.get();78byte secondByte = input.get();79byte thirdByte = input.get();80if ((firstByte & 0x80) != 0 && thirdByte == 0x01) {81// looks like a V2ClientHello82// return (((firstByte & 0x7F) << 8) | (secondByte & 0xFF)) + 2;83return RECORD_HEADER_SIZE; // Only need the header fields84} else {85return (((input.get() & 0xFF) << 8) | (input.get() & 0xFF)) + 5;86}87}8889/**90* Returns the required number of bytes in the {@code source} byte array91* necessary to explore SSL/TLS connection.92* <P>93* This method tries to parse as few bytes as possible from94* {@code source} byte array to get the length of an95* SSL/TLS record.96*97* @param source98* a byte array containing inbound or outbound network data for99* an SSL/TLS connection.100* @param offset101* the start offset in array {@code source} at which the102* network data is read from.103* @param length104* the maximum number of bytes to read.105*106* @throws BufferUnderflowException if less than {@code RECORD_HEADER_SIZE}107* bytes remaining in {@code source}108* @return the required size in byte to explore an SSL/TLS connection109*/110public final static int getRequiredSize(byte[] source,111int offset, int length) throws IOException {112113ByteBuffer byteBuffer =114ByteBuffer.wrap(source, offset, length).asReadOnlyBuffer();115return getRequiredSize(byteBuffer);116}117118/**119* Launch and explore the security capabilities from byte buffer.120* <P>121* This method tries to parse as few records as possible from122* {@code source} byte buffer to get the {@link SSLCapabilities}123* of an SSL/TLS connection.124* <P>125* Please NOTE that this method must be called before any handshaking126* occurs. The behavior of this method is not defined in this release127* if the handshake has begun, or has completed.128* <P>129* This method accesses the {@code source} parameter in read-only130* mode, and does not update the buffer's properties such as capacity,131* limit, position, and mark values.132*133* @param source134* a {@link ByteBuffer} containing135* inbound or outbound network data for an SSL/TLS connection.136*137* @throws IOException on network data error138* @throws BufferUnderflowException if not enough source bytes available139* to make a complete exploration.140*141* @return the explored {@link SSLCapabilities} of the SSL/TLS142* connection143*/144public final static SSLCapabilities explore(ByteBuffer source)145throws IOException {146147ByteBuffer input = source.duplicate();148149// Do we have a complete header?150if (input.remaining() < RECORD_HEADER_SIZE) {151throw new BufferUnderflowException();152}153154// Is it a handshake message?155byte firstByte = input.get();156byte secondByte = input.get();157byte thirdByte = input.get();158if ((firstByte & 0x80) != 0 && thirdByte == 0x01) {159// looks like a V2ClientHello160return exploreV2HelloRecord(input,161firstByte, secondByte, thirdByte);162} else if (firstByte == 22) { // 22: handshake record163return exploreTLSRecord(input,164firstByte, secondByte, thirdByte);165} else {166throw new SSLException("Not handshake record");167}168}169170/**171* Launch and explore the security capabilities from byte array.172* <P>173* Please NOTE that this method must be called before any handshaking174* occurs. The behavior of this method is not defined in this release175* if the handshake has begun, or has completed. Once handshake has176* begun, or has completed, the security capabilities can not and177* should not be launched with this method.178*179* @param source180* a byte array containing inbound or outbound network data for181* an SSL/TLS connection.182* @param offset183* the start offset in array {@code source} at which the184* network data is read from.185* @param length186* the maximum number of bytes to read.187*188* @throws IOException on network data error189* @throws BufferUnderflowException if not enough source bytes available190* to make a complete exploration.191* @return the explored {@link SSLCapabilities} of the SSL/TLS192* connection193*194* @see #explore(ByteBuffer)195*/196public final static SSLCapabilities explore(byte[] source,197int offset, int length) throws IOException {198ByteBuffer byteBuffer =199ByteBuffer.wrap(source, offset, length).asReadOnlyBuffer();200return explore(byteBuffer);201}202203/*204* uint8 V2CipherSpec[3];205* struct {206* uint16 msg_length; // The highest bit MUST be 1;207* // the remaining bits contain the length208* // of the following data in bytes.209* uint8 msg_type; // MUST be 1210* Version version;211* uint16 cipher_spec_length; // It cannot be zero and MUST be a212* // multiple of the V2CipherSpec length.213* uint16 session_id_length; // This field MUST be empty.214* uint16 challenge_length; // SHOULD use a 32-byte challenge215* V2CipherSpec cipher_specs[V2ClientHello.cipher_spec_length];216* opaque session_id[V2ClientHello.session_id_length];217* opaque challenge[V2ClientHello.challenge_length;218* } V2ClientHello;219*/220private static SSLCapabilities exploreV2HelloRecord(221ByteBuffer input, byte firstByte, byte secondByte,222byte thirdByte) throws IOException {223224// We only need the header. We have already had enough source bytes.225// int recordLength = (firstByte & 0x7F) << 8) | (secondByte & 0xFF);226try {227// Is it a V2ClientHello?228if (thirdByte != 0x01) {229throw new SSLException(230"Unsupported or Unrecognized SSL record");231}232233// What's the hello version?234byte helloVersionMajor = input.get();235byte helloVersionMinor = input.get();236237// 0x00: major version of SSLv20238// 0x02: minor version of SSLv20239//240// SNIServerName is an extension, SSLv20 doesn't support extension.241return new SSLCapabilitiesImpl((byte)0x00, (byte)0x02,242helloVersionMajor, helloVersionMinor,243Collections.<SNIServerName>emptyList());244} catch (BufferUnderflowException bufe) {245throw new SSLProtocolException(246"Invalid handshake record");247}248}249250/*251* struct {252* uint8 major;253* uint8 minor;254* } ProtocolVersion;255*256* enum {257* change_cipher_spec(20), alert(21), handshake(22),258* application_data(23), (255)259* } ContentType;260*261* struct {262* ContentType type;263* ProtocolVersion version;264* uint16 length;265* opaque fragment[TLSPlaintext.length];266* } TLSPlaintext;267*/268private static SSLCapabilities exploreTLSRecord(269ByteBuffer input, byte firstByte, byte secondByte,270byte thirdByte) throws IOException {271272// Is it a handshake message?273if (firstByte != 22) { // 22: handshake record274throw new SSLException("Not handshake record");275}276277// We need the record version to construct SSLCapabilities.278byte recordMajorVersion = secondByte;279byte recordMinorVersion = thirdByte;280281// Is there enough data for a full record?282int recordLength = getInt16(input);283if (recordLength > input.remaining()) {284throw new BufferUnderflowException();285}286287// We have already had enough source bytes.288try {289return exploreHandshake(input,290recordMajorVersion, recordMinorVersion, recordLength);291} catch (BufferUnderflowException bufe) {292throw new SSLProtocolException(293"Invalid handshake record");294}295}296297/*298* enum {299* hello_request(0), client_hello(1), server_hello(2),300* certificate(11), server_key_exchange (12),301* certificate_request(13), server_hello_done(14),302* certificate_verify(15), client_key_exchange(16),303* finished(20)304* (255)305* } HandshakeType;306*307* struct {308* HandshakeType msg_type;309* uint24 length;310* select (HandshakeType) {311* case hello_request: HelloRequest;312* case client_hello: ClientHello;313* case server_hello: ServerHello;314* case certificate: Certificate;315* case server_key_exchange: ServerKeyExchange;316* case certificate_request: CertificateRequest;317* case server_hello_done: ServerHelloDone;318* case certificate_verify: CertificateVerify;319* case client_key_exchange: ClientKeyExchange;320* case finished: Finished;321* } body;322* } Handshake;323*/324private static SSLCapabilities exploreHandshake(325ByteBuffer input, byte recordMajorVersion,326byte recordMinorVersion, int recordLength) throws IOException {327328// What is the handshake type?329byte handshakeType = input.get();330if (handshakeType != 0x01) { // 0x01: client_hello message331throw new IllegalStateException("Not initial handshaking");332}333334// What is the handshake body length?335int handshakeLength = getInt24(input);336337// Theoretically, a single handshake message might span multiple338// records, but in practice this does not occur.339if (handshakeLength > (recordLength - 4)) { // 4: handshake header size340throw new SSLException("Handshake message spans multiple records");341}342343input = input.duplicate();344input.limit(handshakeLength + input.position());345return exploreClientHello(input,346recordMajorVersion, recordMinorVersion);347}348349/*350* struct {351* uint32 gmt_unix_time;352* opaque random_bytes[28];353* } Random;354*355* opaque SessionID<0..32>;356*357* uint8 CipherSuite[2];358*359* enum { null(0), (255) } CompressionMethod;360*361* struct {362* ProtocolVersion client_version;363* Random random;364* SessionID session_id;365* CipherSuite cipher_suites<2..2^16-2>;366* CompressionMethod compression_methods<1..2^8-1>;367* select (extensions_present) {368* case false:369* struct {};370* case true:371* Extension extensions<0..2^16-1>;372* };373* } ClientHello;374*/375private static SSLCapabilities exploreClientHello(376ByteBuffer input,377byte recordMajorVersion,378byte recordMinorVersion) throws IOException {379380List<SNIServerName> snList = Collections.<SNIServerName>emptyList();381382// client version383byte helloMajorVersion = input.get();384byte helloMinorVersion = input.get();385386// ignore random387int position = input.position();388input.position(position + 32); // 32: the length of Random389390// ignore session id391ignoreByteVector8(input);392393// ignore cipher_suites394ignoreByteVector16(input);395396// ignore compression methods397ignoreByteVector8(input);398399if (input.remaining() > 0) {400snList = exploreExtensions(input);401}402403return new SSLCapabilitiesImpl(404recordMajorVersion, recordMinorVersion,405helloMajorVersion, helloMinorVersion, snList);406}407408/*409* struct {410* ExtensionType extension_type;411* opaque extension_data<0..2^16-1>;412* } Extension;413*414* enum {415* server_name(0), max_fragment_length(1),416* client_certificate_url(2), trusted_ca_keys(3),417* truncated_hmac(4), status_request(5), (65535)418* } ExtensionType;419*/420private static List<SNIServerName> exploreExtensions(ByteBuffer input)421throws IOException {422423int length = getInt16(input); // length of extensions424while (length > 0) {425int extType = getInt16(input); // extenson type426int extLen = getInt16(input); // length of extension data427428if (extType == 0x00) { // 0x00: type of server name indication429return exploreSNIExt(input, extLen);430} else { // ignore other extensions431ignoreByteVector(input, extLen);432}433434length -= extLen + 4;435}436437return Collections.<SNIServerName>emptyList();438}439440/*441* struct {442* NameType name_type;443* select (name_type) {444* case host_name: HostName;445* } name;446* } ServerName;447*448* enum {449* host_name(0), (255)450* } NameType;451*452* opaque HostName<1..2^16-1>;453*454* struct {455* ServerName server_name_list<1..2^16-1>456* } ServerNameList;457*/458private static List<SNIServerName> exploreSNIExt(ByteBuffer input,459int extLen) throws IOException {460461Map<Integer, SNIServerName> sniMap = new LinkedHashMap<>();462463int remains = extLen;464if (extLen >= 2) { // "server_name" extension in ClientHello465int listLen = getInt16(input); // length of server_name_list466if (listLen == 0 || listLen + 2 != extLen) {467throw new SSLProtocolException(468"Invalid server name indication extension");469}470471remains -= 2; // 0x02: the length field of server_name_list472while (remains > 0) {473int code = getInt8(input); // name_type474int snLen = getInt16(input); // length field of server name475if (snLen > remains) {476throw new SSLProtocolException(477"Not enough data to fill declared vector size");478}479byte[] encoded = new byte[snLen];480input.get(encoded);481482SNIServerName serverName;483switch (code) {484case StandardConstants.SNI_HOST_NAME:485if (encoded.length == 0) {486throw new SSLProtocolException(487"Empty HostName in server name indication");488}489serverName = new SNIHostName(encoded);490break;491default:492serverName = new UnknownServerName(code, encoded);493}494// check for duplicated server name type495if (sniMap.put(serverName.getType(), serverName) != null) {496throw new SSLProtocolException(497"Duplicated server name of type " +498serverName.getType());499}500501remains -= encoded.length + 3; // NameType: 1 byte502// HostName length: 2 bytes503}504} else if (extLen == 0) { // "server_name" extension in ServerHello505throw new SSLProtocolException(506"Not server name indication extension in client");507}508509if (remains != 0) {510throw new SSLProtocolException(511"Invalid server name indication extension");512}513514return Collections.<SNIServerName>unmodifiableList(515new ArrayList<>(sniMap.values()));516}517518private static int getInt8(ByteBuffer input) {519return input.get();520}521522private static int getInt16(ByteBuffer input) {523return ((input.get() & 0xFF) << 8) | (input.get() & 0xFF);524}525526private static int getInt24(ByteBuffer input) {527return ((input.get() & 0xFF) << 16) | ((input.get() & 0xFF) << 8) |528(input.get() & 0xFF);529}530531private static void ignoreByteVector8(ByteBuffer input) {532ignoreByteVector(input, getInt8(input));533}534535private static void ignoreByteVector16(ByteBuffer input) {536ignoreByteVector(input, getInt16(input));537}538539private static void ignoreByteVector24(ByteBuffer input) {540ignoreByteVector(input, getInt24(input));541}542543private static void ignoreByteVector(ByteBuffer input, int length) {544if (length != 0) {545int position = input.position();546input.position(position + length);547}548}549550private static class UnknownServerName extends SNIServerName {551UnknownServerName(int code, byte[] encoded) {552super(code, encoded);553}554}555556private static final class SSLCapabilitiesImpl extends SSLCapabilities {557private final static Map<Integer, String> versionMap = new HashMap<>(5);558559private final String recordVersion;560private final String helloVersion;561List<SNIServerName> sniNames;562563static {564versionMap.put(0x0002, "SSLv2Hello");565versionMap.put(0x0300, "SSLv3");566versionMap.put(0x0301, "TLSv1");567versionMap.put(0x0302, "TLSv1.1");568versionMap.put(0x0303, "TLSv1.2");569}570571SSLCapabilitiesImpl(byte recordMajorVersion, byte recordMinorVersion,572byte helloMajorVersion, byte helloMinorVersion,573List<SNIServerName> sniNames) {574575int version = (recordMajorVersion << 8) | recordMinorVersion;576this.recordVersion = versionMap.get(version) != null ?577versionMap.get(version) :578unknownVersion(recordMajorVersion, recordMinorVersion);579580version = (helloMajorVersion << 8) | helloMinorVersion;581this.helloVersion = versionMap.get(version) != null ?582versionMap.get(version) :583unknownVersion(helloMajorVersion, helloMinorVersion);584585this.sniNames = sniNames;586}587588@Override589public String getRecordVersion() {590return recordVersion;591}592593@Override594public String getHelloVersion() {595return helloVersion;596}597598@Override599public List<SNIServerName> getServerNames() {600if (!sniNames.isEmpty()) {601return Collections.<SNIServerName>unmodifiableList(sniNames);602}603604return sniNames;605}606607private static String unknownVersion(byte major, byte minor) {608return "Unknown-" + ((int)major) + "." + ((int)minor);609}610}611}612613614615