All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.wildfly.security.ssl.SNISSLExplorer Maven / Gradle / Ivy

Go to download

This artifact provides a single jar that contains all classes required to use remote EJB and JMS, including all dependencies. It is intended for use by those not using maven, maven users should just import the EJB and JMS BOM's instead (shaded JAR's cause lots of problems with maven, as it is very easy to inadvertently end up with different versions on classes on the class path).

There is a newer version: 34.0.0.Final
Show newest version
/*
* Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
*   - Redistributions of source code must retain the above copyright
*     notice, this list of conditions and the following disclaimer.
*
*   - Redistributions in binary form must reproduce the above copyright
*     notice, this list of conditions and the following disclaimer in the
*     documentation and/or other materials provided with the distribution.
*
*   - Neither the name of Oracle or the names of its
*     contributors may be used to endorse or promote products derived
*     from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
* IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

package org.wildfly.security.ssl;

import static org.wildfly.security.ssl.ElytronMessages.log;

import java.nio.ByteBuffer;
import java.nio.BufferUnderflowException;
import java.io.IOException;

import javax.net.ssl.SNIHostName;
import javax.net.ssl.SNIServerName;
import javax.net.ssl.SSLException;
import javax.net.ssl.StandardConstants;

import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;


/**
 * Instances of this class acts as an explorer of the network data of an
 * SSL/TLS connection.
 */
final class SNISSLExplorer {

    // Private constructor prevents construction outside this class.
    private SNISSLExplorer() {
    }

    /**
     * The header size of TLS/SSL records.
     * 

* The value of this constant is {@value}. */ public static final int RECORD_HEADER_SIZE = 0x05; /** * Returns the required number of bytes in the {@code source} * {@link ByteBuffer} necessary to explore SSL/TLS connection. *

* This method tries to parse as few bytes as possible from * {@code source} byte buffer to get the length of an * SSL/TLS record. *

* This method accesses the {@code source} parameter in read-only * mode, and does not update the buffer's properties such as capacity, * limit, position, and mark values. * * @param source * a {@link ByteBuffer} containing * inbound or outbound network data for an SSL/TLS connection. * @throws BufferUnderflowException if less than {@code RECORD_HEADER_SIZE} * bytes remaining in {@code source} * @return the required size in byte to explore an SSL/TLS connection */ public static int getRequiredSize(ByteBuffer source) { ByteBuffer input = source.duplicate(); // Do we have a complete header? if (input.remaining() < RECORD_HEADER_SIZE) { throw new BufferUnderflowException(); } // Is it a handshake message? byte firstByte = input.get(); input.get(); byte thirdByte = input.get(); if ((firstByte & 0x80) != 0 && thirdByte == 0x01) { // looks like a V2ClientHello // return (((firstByte & 0x7F) << 8) | (secondByte & 0xFF)) + 2; return RECORD_HEADER_SIZE; // Only need the header fields } else { return ((input.get() & 0xFF) << 8 | input.get() & 0xFF) + 5; } } /** * Returns the required number of bytes in the {@code source} byte array * necessary to explore SSL/TLS connection. *

* This method tries to parse as few bytes as possible from * {@code source} byte array to get the length of an * SSL/TLS record. * * @param source * a byte array containing inbound or outbound network data for * an SSL/TLS connection. * @param offset * the start offset in array {@code source} at which the * network data is read from. * @param length * the maximum number of bytes to read. * * @throws BufferUnderflowException if less than {@code RECORD_HEADER_SIZE} * bytes remaining in {@code source} * @return the required size in byte to explore an SSL/TLS connection */ public static int getRequiredSize(byte[] source, int offset, int length) throws IOException { ByteBuffer byteBuffer = ByteBuffer.wrap(source, offset, length).asReadOnlyBuffer(); return getRequiredSize(byteBuffer); } /** * Launch and explore the security capabilities from byte buffer. *

* This method tries to parse as few records as possible from * {@code source} byte buffer to get the capabilities * of an SSL/TLS connection. *

* Please NOTE that this method must be called before any handshaking * occurs. The behavior of this method is not defined in this release * if the handshake has begun, or has completed. *

* This method accesses the {@code source} parameter in read-only * mode, and does not update the buffer's properties such as capacity, * limit, position, and mark values. * * @param source * a {@link ByteBuffer} containing * inbound or outbound network data for an SSL/TLS connection. * * @throws IOException on network data error * @throws BufferUnderflowException if not enough source bytes available * to make a complete exploration. * * @return the explored capabilities of the SSL/TLS * connection */ public static List explore(ByteBuffer source) throws SSLException { ByteBuffer input = source.duplicate(); // Do we have a complete header? if (input.remaining() < RECORD_HEADER_SIZE) { throw new BufferUnderflowException(); } // Is it a handshake message? byte firstByte = input.get(); byte secondByte = input.get(); byte thirdByte = input.get(); if ((firstByte & 0x80) != 0 && thirdByte == 0x01) { // looks like a V2ClientHello return Collections.emptyList(); } else if (firstByte == 22) { // 22: handshake record return exploreTLSRecord(input, firstByte, secondByte, thirdByte); } else { throw log.notHandshakeRecord(); } } /** * Launch and explore the security capabilities from byte array. *

* Please NOTE that this method must be called before any handshaking * occurs. The behavior of this method is not defined in this release * if the handshake has begun, or has completed. Once handshake has * begun, or has completed, the security capabilities can not and * should not be launched with this method. * * @param source * a byte array containing inbound or outbound network data for * an SSL/TLS connection. * @param offset * the start offset in array {@code source} at which the * network data is read from. * @param length * the maximum number of bytes to read. * * @throws IOException on network data error * @throws BufferUnderflowException if not enough source bytes available * to make a complete exploration. * @return the explored capabilities of the SSL/TLS * connection * * @see #explore(ByteBuffer) */ public static List explore(byte[] source, int offset, int length) throws IOException { ByteBuffer byteBuffer = ByteBuffer.wrap(source, offset, length).asReadOnlyBuffer(); return explore(byteBuffer); } /* * struct { * uint8 major; * uint8 minor; * } ProtocolVersion; * * enum { * change_cipher_spec(20), alert(21), handshake(22), * application_data(23), (255) * } ContentType; * * struct { * ContentType type; * ProtocolVersion version; * uint16 length; * opaque fragment[TLSPlaintext.length]; * } TLSPlaintext; */ private static List exploreTLSRecord( ByteBuffer input, byte firstByte, byte secondByte, byte thirdByte) throws SSLException { // Is it a handshake message? if (firstByte != 22) { // 22: handshake record throw log.notHandshakeRecord(); } // Is there enough data for a full record? int recordLength = getInt16(input); if (recordLength > input.remaining()) { throw new BufferUnderflowException(); } // We have already had enough source bytes. try { return exploreHandshake(input, secondByte, thirdByte, recordLength); } catch (BufferUnderflowException ignored) { throw log.invalidHandshakeRecord(); } } /* * enum { * hello_request(0), client_hello(1), server_hello(2), * certificate(11), server_key_exchange (12), * certificate_request(13), server_hello_done(14), * certificate_verify(15), client_key_exchange(16), * finished(20) * (255) * } HandshakeType; * * struct { * HandshakeType msg_type; * uint24 length; * select (HandshakeType) { * case hello_request: HelloRequest; * case client_hello: ClientHello; * case server_hello: ServerHello; * case certificate: Certificate; * case server_key_exchange: ServerKeyExchange; * case certificate_request: CertificateRequest; * case server_hello_done: ServerHelloDone; * case certificate_verify: CertificateVerify; * case client_key_exchange: ClientKeyExchange; * case finished: Finished; * } body; * } Handshake; */ private static List exploreHandshake( ByteBuffer input, byte recordMajorVersion, byte recordMinorVersion, int recordLength) throws SSLException { // What is the handshake type? byte handshakeType = input.get(); if (handshakeType != 0x01) { // 0x01: client_hello message throw log.expectedClientHello(); } // What is the handshake body length? int handshakeLength = getInt24(input); // Theoretically, a single handshake message might span multiple // records, but in practice this does not occur. if (handshakeLength > recordLength - 4) { // 4: handshake header size throw log.multiRecordSSLHandshake(); } input = input.duplicate(); input.limit(handshakeLength + input.position()); return exploreClientHello(input, recordMajorVersion, recordMinorVersion); } /* * struct { * uint32 gmt_unix_time; * opaque random_bytes[28]; * } Random; * * opaque SessionID<0..32>; * * uint8 CipherSuite[2]; * * enum { null(0), (255) } CompressionMethod; * * struct { * ProtocolVersion client_version; * Random random; * SessionID session_id; * CipherSuite cipher_suites<2..2^16-2>; * CompressionMethod compression_methods<1..2^8-1>; * select (extensions_present) { * case false: * struct {}; * case true: * Extension extensions<0..2^16-1>; * }; * } ClientHello; */ private static List exploreClientHello( ByteBuffer input, byte recordMajorVersion, byte recordMinorVersion) throws SSLException { ExtensionInfo info = null; // client version input.get(); //helloMajorVersion input.get(); //helloMinorVersion // ignore random int position = input.position(); input.position(position + 32); // 32: the length of Random // ignore session id ignoreByteVector8(input); // ignore cipher_suites int csLen = getInt16(input); while (csLen > 0) { getInt8(input); getInt8(input); csLen -= 2; } // ignore compression methods ignoreByteVector8(input); if (input.remaining() > 0) { info = exploreExtensions(input); } return info != null ? info.sni : Collections.emptyList(); } /* * struct { * ExtensionType extension_type; * opaque extension_data<0..2^16-1>; * } Extension; * * enum { * server_name(0), max_fragment_length(1), * client_certificate_url(2), trusted_ca_keys(3), * truncated_hmac(4), status_request(5), (65535) * } ExtensionType; */ private static ExtensionInfo exploreExtensions(ByteBuffer input) throws SSLException { List sni = Collections.emptyList(); List alpn = Collections.emptyList(); int length = getInt16(input); // length of extensions while (length > 0) { int extType = getInt16(input); // extension type int extLen = getInt16(input); // length of extension data if (extType == 0x00) { // 0x00: type of server name indication sni = exploreSNIExt(input, extLen); } else if (extType == 0x10) { // 0x10: type of alpn alpn = exploreALPN(input, extLen); } else { // ignore other extensions ignoreByteVector(input, extLen); } length -= extLen + 4; } return new ExtensionInfo(sni, alpn); } /* * opaque ProtocolName<1..2^8-1>; * * struct { * ProtocolName protocol_name_list<2..2^16-1> * } ProtocolNameList; * */ private static List exploreALPN(ByteBuffer input, int extLen) throws SSLException { final ArrayList strings = new ArrayList<>(); int rem = extLen; if (extLen >= 2) { int listLen = getInt16(input); if (listLen == 0 || listLen + 2 != extLen) { throw log.invalidTlsExt(); // TODO Check Type } rem -= 2; while (rem > 0) { int len = getInt8(input); if (len > rem) { throw log.notEnoughData(); // TODO Check Type } byte[] b = new byte[len]; input.get(b); strings.add(new String(b, StandardCharsets.UTF_8)); rem -= len + 1; } } return strings.isEmpty() ? Collections.emptyList() : strings; } /* * struct { * NameType name_type; * select (name_type) { * case host_name: HostName; * } name; * } ServerName; * * enum { * host_name(0), (255) * } NameType; * * opaque HostName<1..2^16-1>; * * struct { * ServerName server_name_list<1..2^16-1> * } ServerNameList; */ private static List exploreSNIExt(ByteBuffer input, int extLen) throws SSLException { Map sniMap = new LinkedHashMap<>(); int remains = extLen; if (extLen >= 2) { // "server_name" extension in ClientHello int listLen = getInt16(input); // length of server_name_list if (listLen == 0 || listLen + 2 != extLen) { throw log.invalidTlsExt(); } remains -= 2; // 0x02: the length field of server_name_list while (remains > 0) { int code = getInt8(input); // name_type int snLen = getInt16(input); // length field of server name if (snLen > remains) { throw log.notEnoughData(); // TODO Check Type } byte[] encoded = new byte[snLen]; input.get(encoded); SNIServerName serverName; switch (code) { case StandardConstants.SNI_HOST_NAME: if (encoded.length == 0) { throw log.emptyHostNameSni(); // TODO Check Type } serverName = new SNIHostName(encoded); break; default: serverName = new UnknownServerName(code, encoded); } // check for duplicated server name type if (sniMap.put(serverName.getType(), serverName) != null) { throw log.duplicatedSniServerName(serverName.getType()); } remains -= encoded.length + 3; // NameType: 1 byte // HostName length: 2 bytes } } else if (extLen == 0) { // "server_name" extension in ServerHello throw log.invalidTlsExt(); } if (remains != 0) { throw log.invalidTlsExt(); } return Collections.unmodifiableList(new ArrayList<>(sniMap.values())); } private static int getInt8(ByteBuffer input) { return input.get(); } private static int getInt16(ByteBuffer input) { return (input.get() & 0xFF) << 8 | input.get() & 0xFF; } private static int getInt24(ByteBuffer input) { return (input.get() & 0xFF) << 16 | (input.get() & 0xFF) << 8 | input.get() & 0xFF; } private static void ignoreByteVector8(ByteBuffer input) { ignoreByteVector(input, getInt8(input)); } private static void ignoreByteVector(ByteBuffer input, int length) { if (length != 0) { int position = input.position(); input.position(position + length); } } static final class UnknownServerName extends SNIServerName { UnknownServerName(int code, byte[] encoded) { super(code, encoded); } } static final class ExtensionInfo { final List sni; final List alpn; ExtensionInfo(final List sni, final List alpn) { this.sni = sni; this.alpn = alpn; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy