jdk/test/sun/security/ssl/templates/SSLExplorer.java
changeset 23073 323831f76669
parent 23072 cf836c6f207d
parent 23065 259559ac0ddf
child 23086 e61d91adef6f
equal deleted inserted replaced
23072:cf836c6f207d 23073:323831f76669
     1 /*
       
     2  * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
       
     3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
       
     4  *
       
     5  * This code is free software; you can redistribute it and/or modify it
       
     6  * under the terms of the GNU General Public License version 2 only, as
       
     7  * published by the Free Software Foundation.  Oracle designates this
       
     8  * particular file as subject to the "Classpath" exception as provided
       
     9  * by Oracle in the LICENSE file that accompanied this code.
       
    10  *
       
    11  * This code is distributed in the hope that it will be useful, but WITHOUT
       
    12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
       
    13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
       
    14  * version 2 for more details (a copy is included in the LICENSE file that
       
    15  * accompanied this code).
       
    16  *
       
    17  * You should have received a copy of the GNU General Public License version
       
    18  * 2 along with this work; if not, write to the Free Software Foundation,
       
    19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
       
    20  *
       
    21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
       
    22  * or visit www.oracle.com if you need additional information or have any
       
    23  * questions.
       
    24  */
       
    25 
       
    26 import java.nio.ByteBuffer;
       
    27 import java.nio.BufferUnderflowException;
       
    28 import java.io.IOException;
       
    29 import javax.net.ssl.*;
       
    30 import java.util.*;
       
    31 
       
    32 import sun.misc.HexDumpEncoder;
       
    33 
       
    34 /**
       
    35  * Instances of this class acts as an explorer of the network data of an
       
    36  * SSL/TLS connection.
       
    37  */
       
    38 public final class SSLExplorer {
       
    39 
       
    40     // Private constructor prevents construction outside this class.
       
    41     private SSLExplorer() {
       
    42     }
       
    43 
       
    44     /**
       
    45      * The header size of TLS/SSL records.
       
    46      * <P>
       
    47      * The value of this constant is {@value}.
       
    48      */
       
    49     public final static int RECORD_HEADER_SIZE = 0x05;
       
    50 
       
    51     /**
       
    52      * Returns the required number of bytes in the {@code source}
       
    53      * {@link ByteBuffer} necessary to explore SSL/TLS connection.
       
    54      * <P>
       
    55      * This method tries to parse as few bytes as possible from
       
    56      * {@code source} byte buffer to get the length of an
       
    57      * SSL/TLS record.
       
    58      * <P>
       
    59      * This method accesses the {@code source} parameter in read-only
       
    60      * mode, and does not update the buffer's properties such as capacity,
       
    61      * limit, position, and mark values.
       
    62      *
       
    63      * @param  source
       
    64      *         a {@link ByteBuffer} containing
       
    65      *         inbound or outbound network data for an SSL/TLS connection.
       
    66      * @throws BufferUnderflowException if less than {@code RECORD_HEADER_SIZE}
       
    67      *         bytes remaining in {@code source}
       
    68      * @return the required size in byte to explore an SSL/TLS connection
       
    69      */
       
    70     public final static int getRequiredSize(ByteBuffer source) {
       
    71 
       
    72         ByteBuffer input = source.duplicate();
       
    73 
       
    74         // Do we have a complete header?
       
    75         if (input.remaining() < RECORD_HEADER_SIZE) {
       
    76             throw new BufferUnderflowException();
       
    77         }
       
    78 
       
    79         // Is it a handshake message?
       
    80         byte firstByte = input.get();
       
    81         byte secondByte = input.get();
       
    82         byte thirdByte = input.get();
       
    83         if ((firstByte & 0x80) != 0 && thirdByte == 0x01) {
       
    84             // looks like a V2ClientHello
       
    85             // return (((firstByte & 0x7F) << 8) | (secondByte & 0xFF)) + 2;
       
    86             return RECORD_HEADER_SIZE;   // Only need the header fields
       
    87         } else {
       
    88             return (((input.get() & 0xFF) << 8) | (input.get() & 0xFF)) + 5;
       
    89         }
       
    90     }
       
    91 
       
    92     /**
       
    93      * Returns the required number of bytes in the {@code source} byte array
       
    94      * necessary to explore SSL/TLS connection.
       
    95      * <P>
       
    96      * This method tries to parse as few bytes as possible from
       
    97      * {@code source} byte array to get the length of an
       
    98      * SSL/TLS record.
       
    99      *
       
   100      * @param  source
       
   101      *         a byte array containing inbound or outbound network data for
       
   102      *         an SSL/TLS connection.
       
   103      * @param  offset
       
   104      *         the start offset in array {@code source} at which the
       
   105      *         network data is read from.
       
   106      * @param  length
       
   107      *         the maximum number of bytes to read.
       
   108      *
       
   109      * @throws BufferUnderflowException if less than {@code RECORD_HEADER_SIZE}
       
   110      *         bytes remaining in {@code source}
       
   111      * @return the required size in byte to explore an SSL/TLS connection
       
   112      */
       
   113     public final static int getRequiredSize(byte[] source,
       
   114             int offset, int length) throws IOException {
       
   115 
       
   116         ByteBuffer byteBuffer =
       
   117             ByteBuffer.wrap(source, offset, length).asReadOnlyBuffer();
       
   118         return getRequiredSize(byteBuffer);
       
   119     }
       
   120 
       
   121     /**
       
   122      * Launch and explore the security capabilities from byte buffer.
       
   123      * <P>
       
   124      * This method tries to parse as few records as possible from
       
   125      * {@code source} byte buffer to get the {@link SSLCapabilities}
       
   126      * of an SSL/TLS connection.
       
   127      * <P>
       
   128      * Please NOTE that this method must be called before any handshaking
       
   129      * occurs.  The behavior of this method is not defined in this release
       
   130      * if the handshake has begun, or has completed.
       
   131      * <P>
       
   132      * This method accesses the {@code source} parameter in read-only
       
   133      * mode, and does not update the buffer's properties such as capacity,
       
   134      * limit, position, and mark values.
       
   135      *
       
   136      * @param  source
       
   137      *         a {@link ByteBuffer} containing
       
   138      *         inbound or outbound network data for an SSL/TLS connection.
       
   139      *
       
   140      * @throws IOException on network data error
       
   141      * @throws BufferUnderflowException if not enough source bytes available
       
   142      *         to make a complete exploration.
       
   143      *
       
   144      * @return the explored {@link SSLCapabilities} of the SSL/TLS
       
   145      *         connection
       
   146      */
       
   147     public final static SSLCapabilities explore(ByteBuffer source)
       
   148             throws IOException {
       
   149 
       
   150         ByteBuffer input = source.duplicate();
       
   151 
       
   152         // Do we have a complete header?
       
   153         if (input.remaining() < RECORD_HEADER_SIZE) {
       
   154             throw new BufferUnderflowException();
       
   155         }
       
   156 
       
   157         // Is it a handshake message?
       
   158         byte firstByte = input.get();
       
   159         byte secondByte = input.get();
       
   160         byte thirdByte = input.get();
       
   161         if ((firstByte & 0x80) != 0 && thirdByte == 0x01) {
       
   162             // looks like a V2ClientHello
       
   163             return exploreV2HelloRecord(input,
       
   164                                     firstByte, secondByte, thirdByte);
       
   165         } else if (firstByte == 22) {   // 22: handshake record
       
   166             return exploreTLSRecord(input,
       
   167                                     firstByte, secondByte, thirdByte);
       
   168         } else {
       
   169             throw new SSLException("Not handshake record");
       
   170         }
       
   171     }
       
   172 
       
   173     /**
       
   174      * Launch and explore the security capabilities from byte array.
       
   175      * <P>
       
   176      * Please NOTE that this method must be called before any handshaking
       
   177      * occurs.  The behavior of this method is not defined in this release
       
   178      * if the handshake has begun, or has completed.  Once handshake has
       
   179      * begun, or has completed, the security capabilities can not and
       
   180      * should not be launched with this method.
       
   181      *
       
   182      * @param  source
       
   183      *         a byte array containing inbound or outbound network data for
       
   184      *         an SSL/TLS connection.
       
   185      * @param  offset
       
   186      *         the start offset in array {@code source} at which the
       
   187      *         network data is read from.
       
   188      * @param  length
       
   189      *         the maximum number of bytes to read.
       
   190      *
       
   191      * @throws IOException on network data error
       
   192      * @throws BufferUnderflowException if not enough source bytes available
       
   193      *         to make a complete exploration.
       
   194      * @return the explored {@link SSLCapabilities} of the SSL/TLS
       
   195      *         connection
       
   196      *
       
   197      * @see #explore(ByteBuffer)
       
   198      */
       
   199     public final static SSLCapabilities explore(byte[] source,
       
   200             int offset, int length) throws IOException {
       
   201         ByteBuffer byteBuffer =
       
   202             ByteBuffer.wrap(source, offset, length).asReadOnlyBuffer();
       
   203         return explore(byteBuffer);
       
   204     }
       
   205 
       
   206     /*
       
   207      * uint8 V2CipherSpec[3];
       
   208      * struct {
       
   209      *     uint16 msg_length;         // The highest bit MUST be 1;
       
   210      *                                // the remaining bits contain the length
       
   211      *                                // of the following data in bytes.
       
   212      *     uint8 msg_type;            // MUST be 1
       
   213      *     Version version;
       
   214      *     uint16 cipher_spec_length; // It cannot be zero and MUST be a
       
   215      *                                // multiple of the V2CipherSpec length.
       
   216      *     uint16 session_id_length;  // This field MUST be empty.
       
   217      *     uint16 challenge_length;   // SHOULD use a 32-byte challenge
       
   218      *     V2CipherSpec cipher_specs[V2ClientHello.cipher_spec_length];
       
   219      *     opaque session_id[V2ClientHello.session_id_length];
       
   220      *     opaque challenge[V2ClientHello.challenge_length;
       
   221      * } V2ClientHello;
       
   222      */
       
   223     private static SSLCapabilities exploreV2HelloRecord(
       
   224             ByteBuffer input, byte firstByte, byte secondByte,
       
   225             byte thirdByte) throws IOException {
       
   226 
       
   227         // We only need the header. We have already had enough source bytes.
       
   228         // int recordLength = (firstByte & 0x7F) << 8) | (secondByte & 0xFF);
       
   229         try {
       
   230             // Is it a V2ClientHello?
       
   231             if (thirdByte != 0x01) {
       
   232                 throw new SSLException(
       
   233                         "Unsupported or Unrecognized SSL record");
       
   234             }
       
   235 
       
   236             // What's the hello version?
       
   237             byte helloVersionMajor = input.get();
       
   238             byte helloVersionMinor = input.get();
       
   239 
       
   240             // 0x00: major version of SSLv20
       
   241             // 0x02: minor version of SSLv20
       
   242             //
       
   243             // SNIServerName is an extension, SSLv20 doesn't support extension.
       
   244             return new SSLCapabilitiesImpl((byte)0x00, (byte)0x02,
       
   245                         helloVersionMajor, helloVersionMinor,
       
   246                         Collections.<SNIServerName>emptyList());
       
   247         } catch (BufferUnderflowException bufe) {
       
   248             throw new SSLProtocolException(
       
   249                         "Invalid handshake record");
       
   250         }
       
   251     }
       
   252 
       
   253     /*
       
   254      * struct {
       
   255      *     uint8 major;
       
   256      *     uint8 minor;
       
   257      * } ProtocolVersion;
       
   258      *
       
   259      * enum {
       
   260      *     change_cipher_spec(20), alert(21), handshake(22),
       
   261      *     application_data(23), (255)
       
   262      * } ContentType;
       
   263      *
       
   264      * struct {
       
   265      *     ContentType type;
       
   266      *     ProtocolVersion version;
       
   267      *     uint16 length;
       
   268      *     opaque fragment[TLSPlaintext.length];
       
   269      * } TLSPlaintext;
       
   270      */
       
   271     private static SSLCapabilities exploreTLSRecord(
       
   272             ByteBuffer input, byte firstByte, byte secondByte,
       
   273             byte thirdByte) throws IOException {
       
   274 
       
   275         // Is it a handshake message?
       
   276         if (firstByte != 22) {        // 22: handshake record
       
   277             throw new SSLException("Not handshake record");
       
   278         }
       
   279 
       
   280         // We need the record version to construct SSLCapabilities.
       
   281         byte recordMajorVersion = secondByte;
       
   282         byte recordMinorVersion = thirdByte;
       
   283 
       
   284         // Is there enough data for a full record?
       
   285         int recordLength = getInt16(input);
       
   286         if (recordLength > input.remaining()) {
       
   287             throw new BufferUnderflowException();
       
   288         }
       
   289 
       
   290         // We have already had enough source bytes.
       
   291         try {
       
   292             return exploreHandshake(input,
       
   293                 recordMajorVersion, recordMinorVersion, recordLength);
       
   294         } catch (BufferUnderflowException bufe) {
       
   295             throw new SSLProtocolException(
       
   296                         "Invalid handshake record");
       
   297         }
       
   298     }
       
   299 
       
   300     /*
       
   301      * enum {
       
   302      *     hello_request(0), client_hello(1), server_hello(2),
       
   303      *     certificate(11), server_key_exchange (12),
       
   304      *     certificate_request(13), server_hello_done(14),
       
   305      *     certificate_verify(15), client_key_exchange(16),
       
   306      *     finished(20)
       
   307      *     (255)
       
   308      * } HandshakeType;
       
   309      *
       
   310      * struct {
       
   311      *     HandshakeType msg_type;
       
   312      *     uint24 length;
       
   313      *     select (HandshakeType) {
       
   314      *         case hello_request:       HelloRequest;
       
   315      *         case client_hello:        ClientHello;
       
   316      *         case server_hello:        ServerHello;
       
   317      *         case certificate:         Certificate;
       
   318      *         case server_key_exchange: ServerKeyExchange;
       
   319      *         case certificate_request: CertificateRequest;
       
   320      *         case server_hello_done:   ServerHelloDone;
       
   321      *         case certificate_verify:  CertificateVerify;
       
   322      *         case client_key_exchange: ClientKeyExchange;
       
   323      *         case finished:            Finished;
       
   324      *     } body;
       
   325      * } Handshake;
       
   326      */
       
   327     private static SSLCapabilities exploreHandshake(
       
   328             ByteBuffer input, byte recordMajorVersion,
       
   329             byte recordMinorVersion, int recordLength) throws IOException {
       
   330 
       
   331         // What is the handshake type?
       
   332         byte handshakeType = input.get();
       
   333         if (handshakeType != 0x01) {   // 0x01: client_hello message
       
   334             throw new IllegalStateException("Not initial handshaking");
       
   335         }
       
   336 
       
   337         // What is the handshake body length?
       
   338         int handshakeLength = getInt24(input);
       
   339 
       
   340         // Theoretically, a single handshake message might span multiple
       
   341         // records, but in practice this does not occur.
       
   342         if (handshakeLength > (recordLength - 4)) { // 4: handshake header size
       
   343             throw new SSLException("Handshake message spans multiple records");
       
   344         }
       
   345 
       
   346         input = input.duplicate();
       
   347         input.limit(handshakeLength + input.position());
       
   348         return exploreClientHello(input,
       
   349                                     recordMajorVersion, recordMinorVersion);
       
   350     }
       
   351 
       
   352     /*
       
   353      * struct {
       
   354      *     uint32 gmt_unix_time;
       
   355      *     opaque random_bytes[28];
       
   356      * } Random;
       
   357      *
       
   358      * opaque SessionID<0..32>;
       
   359      *
       
   360      * uint8 CipherSuite[2];
       
   361      *
       
   362      * enum { null(0), (255) } CompressionMethod;
       
   363      *
       
   364      * struct {
       
   365      *     ProtocolVersion client_version;
       
   366      *     Random random;
       
   367      *     SessionID session_id;
       
   368      *     CipherSuite cipher_suites<2..2^16-2>;
       
   369      *     CompressionMethod compression_methods<1..2^8-1>;
       
   370      *     select (extensions_present) {
       
   371      *         case false:
       
   372      *             struct {};
       
   373      *         case true:
       
   374      *             Extension extensions<0..2^16-1>;
       
   375      *     };
       
   376      * } ClientHello;
       
   377      */
       
   378     private static SSLCapabilities exploreClientHello(
       
   379             ByteBuffer input,
       
   380             byte recordMajorVersion,
       
   381             byte recordMinorVersion) throws IOException {
       
   382 
       
   383         List<SNIServerName> snList = Collections.<SNIServerName>emptyList();
       
   384 
       
   385         // client version
       
   386         byte helloMajorVersion = input.get();
       
   387         byte helloMinorVersion = input.get();
       
   388 
       
   389         // ignore random
       
   390         int position = input.position();
       
   391         input.position(position + 32);  // 32: the length of Random
       
   392 
       
   393         // ignore session id
       
   394         ignoreByteVector8(input);
       
   395 
       
   396         // ignore cipher_suites
       
   397         ignoreByteVector16(input);
       
   398 
       
   399         // ignore compression methods
       
   400         ignoreByteVector8(input);
       
   401 
       
   402         if (input.remaining() > 0) {
       
   403             snList = exploreExtensions(input);
       
   404         }
       
   405 
       
   406         return new SSLCapabilitiesImpl(
       
   407                 recordMajorVersion, recordMinorVersion,
       
   408                 helloMajorVersion, helloMinorVersion, snList);
       
   409     }
       
   410 
       
   411     /*
       
   412      * struct {
       
   413      *     ExtensionType extension_type;
       
   414      *     opaque extension_data<0..2^16-1>;
       
   415      * } Extension;
       
   416      *
       
   417      * enum {
       
   418      *     server_name(0), max_fragment_length(1),
       
   419      *     client_certificate_url(2), trusted_ca_keys(3),
       
   420      *     truncated_hmac(4), status_request(5), (65535)
       
   421      * } ExtensionType;
       
   422      */
       
   423     private static List<SNIServerName> exploreExtensions(ByteBuffer input)
       
   424             throws IOException {
       
   425 
       
   426         int length = getInt16(input);           // length of extensions
       
   427         while (length > 0) {
       
   428             int extType = getInt16(input);      // extenson type
       
   429             int extLen = getInt16(input);       // length of extension data
       
   430 
       
   431             if (extType == 0x00) {      // 0x00: type of server name indication
       
   432                 return exploreSNIExt(input, extLen);
       
   433             } else {                    // ignore other extensions
       
   434                 ignoreByteVector(input, extLen);
       
   435             }
       
   436 
       
   437             length -= extLen + 4;
       
   438         }
       
   439 
       
   440         return Collections.<SNIServerName>emptyList();
       
   441     }
       
   442 
       
   443     /*
       
   444      * struct {
       
   445      *     NameType name_type;
       
   446      *     select (name_type) {
       
   447      *         case host_name: HostName;
       
   448      *     } name;
       
   449      * } ServerName;
       
   450      *
       
   451      * enum {
       
   452      *     host_name(0), (255)
       
   453      * } NameType;
       
   454      *
       
   455      * opaque HostName<1..2^16-1>;
       
   456      *
       
   457      * struct {
       
   458      *     ServerName server_name_list<1..2^16-1>
       
   459      * } ServerNameList;
       
   460      */
       
   461     private static List<SNIServerName> exploreSNIExt(ByteBuffer input,
       
   462             int extLen) throws IOException {
       
   463 
       
   464         Map<Integer, SNIServerName> sniMap = new LinkedHashMap<>();
       
   465 
       
   466         int remains = extLen;
       
   467         if (extLen >= 2) {     // "server_name" extension in ClientHello
       
   468             int listLen = getInt16(input);     // length of server_name_list
       
   469             if (listLen == 0 || listLen + 2 != extLen) {
       
   470                 throw new SSLProtocolException(
       
   471                     "Invalid server name indication extension");
       
   472             }
       
   473 
       
   474             remains -= 2;     // 0x02: the length field of server_name_list
       
   475             while (remains > 0) {
       
   476                 int code = getInt8(input);      // name_type
       
   477                 int snLen = getInt16(input);    // length field of server name
       
   478                 if (snLen > remains) {
       
   479                     throw new SSLProtocolException(
       
   480                         "Not enough data to fill declared vector size");
       
   481                 }
       
   482                 byte[] encoded = new byte[snLen];
       
   483                 input.get(encoded);
       
   484 
       
   485                 SNIServerName serverName;
       
   486                 switch (code) {
       
   487                     case StandardConstants.SNI_HOST_NAME:
       
   488                         if (encoded.length == 0) {
       
   489                             throw new SSLProtocolException(
       
   490                                 "Empty HostName in server name indication");
       
   491                         }
       
   492                         serverName = new SNIHostName(encoded);
       
   493                         break;
       
   494                     default:
       
   495                         serverName = new UnknownServerName(code, encoded);
       
   496                 }
       
   497                 // check for duplicated server name type
       
   498                 if (sniMap.put(serverName.getType(), serverName) != null) {
       
   499                     throw new SSLProtocolException(
       
   500                             "Duplicated server name of type " +
       
   501                             serverName.getType());
       
   502                 }
       
   503 
       
   504                 remains -= encoded.length + 3;  // NameType: 1 byte
       
   505                                                 // HostName length: 2 bytes
       
   506             }
       
   507         } else if (extLen == 0) {     // "server_name" extension in ServerHello
       
   508             throw new SSLProtocolException(
       
   509                         "Not server name indication extension in client");
       
   510         }
       
   511 
       
   512         if (remains != 0) {
       
   513             throw new SSLProtocolException(
       
   514                         "Invalid server name indication extension");
       
   515         }
       
   516 
       
   517         return Collections.<SNIServerName>unmodifiableList(
       
   518                                             new ArrayList<>(sniMap.values()));
       
   519     }
       
   520 
       
   521     private static int getInt8(ByteBuffer input) {
       
   522         return input.get();
       
   523     }
       
   524 
       
   525     private static int getInt16(ByteBuffer input) {
       
   526         return ((input.get() & 0xFF) << 8) | (input.get() & 0xFF);
       
   527     }
       
   528 
       
   529     private static int getInt24(ByteBuffer input) {
       
   530         return ((input.get() & 0xFF) << 16) | ((input.get() & 0xFF) << 8) |
       
   531                 (input.get() & 0xFF);
       
   532     }
       
   533 
       
   534     private static void ignoreByteVector8(ByteBuffer input) {
       
   535         ignoreByteVector(input, getInt8(input));
       
   536     }
       
   537 
       
   538     private static void ignoreByteVector16(ByteBuffer input) {
       
   539         ignoreByteVector(input, getInt16(input));
       
   540     }
       
   541 
       
   542     private static void ignoreByteVector24(ByteBuffer input) {
       
   543         ignoreByteVector(input, getInt24(input));
       
   544     }
       
   545 
       
   546     private static void ignoreByteVector(ByteBuffer input, int length) {
       
   547         if (length != 0) {
       
   548             int position = input.position();
       
   549             input.position(position + length);
       
   550         }
       
   551     }
       
   552 
       
   553     private static class UnknownServerName extends SNIServerName {
       
   554         UnknownServerName(int code, byte[] encoded) {
       
   555             super(code, encoded);
       
   556         }
       
   557     }
       
   558 
       
   559     private static final class SSLCapabilitiesImpl extends SSLCapabilities {
       
   560         private final static Map<Integer, String> versionMap = new HashMap<>(5);
       
   561 
       
   562         private final String recordVersion;
       
   563         private final String helloVersion;
       
   564         List<SNIServerName> sniNames;
       
   565 
       
   566         static {
       
   567             versionMap.put(0x0002, "SSLv2Hello");
       
   568             versionMap.put(0x0300, "SSLv3");
       
   569             versionMap.put(0x0301, "TLSv1");
       
   570             versionMap.put(0x0302, "TLSv1.1");
       
   571             versionMap.put(0x0303, "TLSv1.2");
       
   572         }
       
   573 
       
   574         SSLCapabilitiesImpl(byte recordMajorVersion, byte recordMinorVersion,
       
   575                 byte helloMajorVersion, byte helloMinorVersion,
       
   576                 List<SNIServerName> sniNames) {
       
   577 
       
   578             int version = (recordMajorVersion << 8) | recordMinorVersion;
       
   579             this.recordVersion = versionMap.get(version) != null ?
       
   580                         versionMap.get(version) :
       
   581                         unknownVersion(recordMajorVersion, recordMinorVersion);
       
   582 
       
   583             version = (helloMajorVersion << 8) | helloMinorVersion;
       
   584             this.helloVersion = versionMap.get(version) != null ?
       
   585                         versionMap.get(version) :
       
   586                         unknownVersion(helloMajorVersion, helloMinorVersion);
       
   587 
       
   588             this.sniNames = sniNames;
       
   589         }
       
   590 
       
   591         @Override
       
   592         public String getRecordVersion() {
       
   593             return recordVersion;
       
   594         }
       
   595 
       
   596         @Override
       
   597         public String getHelloVersion() {
       
   598             return helloVersion;
       
   599         }
       
   600 
       
   601         @Override
       
   602         public List<SNIServerName> getServerNames() {
       
   603             if (!sniNames.isEmpty()) {
       
   604                 return Collections.<SNIServerName>unmodifiableList(sniNames);
       
   605             }
       
   606 
       
   607             return sniNames;
       
   608         }
       
   609 
       
   610         private static String unknownVersion(byte major, byte minor) {
       
   611             return "Unknown-" + ((int)major) + "." + ((int)minor);
       
   612         }
       
   613     }
       
   614 }
       
   615