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

org.wildfly.security.ssl.SSLExplorer 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 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 SSLExplorer {

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

    /**
     * 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(); byte secondByte = 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 SSLConnectionInformationImpl 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 exploreV2HelloRecord(input, thirdByte); } else if (firstByte == 22) { // 22: handshake record return exploreTLSRecord(input, firstByte, secondByte, thirdByte); } else { throw ElytronMessages.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 SSLConnectionInformationImpl explore(byte[] source, int offset, int length) throws IOException { ByteBuffer byteBuffer = ByteBuffer.wrap(source, offset, length).asReadOnlyBuffer(); return explore(byteBuffer); } /* * uint8 V2CipherSpec[3]; * struct { * uint16 msg_length; // The highest bit MUST be 1; * // the remaining bits contain the length * // of the following data in bytes. * uint8 msg_type; // MUST be 1 * Version version; * uint16 cipher_spec_length; // It cannot be zero and MUST be a * // multiple of the V2CipherSpec length. * uint16 session_id_length; // This field MUST be empty. * uint16 challenge_length; // SHOULD use a 32-byte challenge * V2CipherSpec cipher_specs[V2ClientHello.cipher_spec_length]; * opaque session_id[V2ClientHello.session_id_length]; * opaque challenge[V2ClientHello.challenge_length; * } V2ClientHello; */ private static SSLConnectionInformationImpl exploreV2HelloRecord( ByteBuffer input, byte thirdByte) throws SSLException { // We only need the header. We have already had enough source bytes. // int recordLength = (firstByte & 0x7F) << 8) | (secondByte & 0xFF); try { // Is it a V2ClientHello? if (thirdByte != 0x01) { throw ElytronMessages.log.unsupportedSslRecord(); } // What's the hello version? byte helloVersionMajor = input.get(); byte helloVersionMinor = input.get(); int csLen = getInt16(input); // in units of 3 bytes input.getShort(); // session_id_length input.getShort(); // challenge_length List ciphers = new ArrayList<>(); while (csLen >= 3) { int lead = getInt8(input); int byte1 = getInt8(input); int byte2 = getInt8(input); if (lead == 0) { final MechanismDatabase.Entry entry = getCipherSuiteById(byte1, byte2); if (entry != null) ciphers.add(entry.getName()); } // skip any non-TLS cipher suites csLen -= 3; } // 0x00: major version of SSLv20 // 0x02: minor version of SSLv20 // // SNIServerName is an extension, SSLv20 doesn't support extension. return new SSLConnectionInformationImpl((byte)0x00, (byte)0x02, helloVersionMajor, helloVersionMinor, Collections.emptyList(), Collections.emptyList(), ciphers.isEmpty() ? Collections.emptyList() : ciphers); } catch (BufferUnderflowException ignored) { throw ElytronMessages.log.invalidHandshakeRecord(); } } /* * 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 SSLConnectionInformationImpl exploreTLSRecord( ByteBuffer input, byte firstByte, byte secondByte, byte thirdByte) throws SSLException { // Is it a handshake message? if (firstByte != 22) { // 22: handshake record throw ElytronMessages.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 ElytronMessages.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 SSLConnectionInformationImpl 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 ElytronMessages.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 ElytronMessages.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 SSLConnectionInformationImpl exploreClientHello( ByteBuffer input, byte recordMajorVersion, byte recordMinorVersion) throws SSLException { ExtensionInfo info = null; // client version byte helloMajorVersion = input.get(); byte helloMinorVersion = input.get(); // ignore random int position = input.position(); input.position(position + 32); // 32: the length of Random // ignore session id ignoreByteVector8(input); ArrayList ciphers = new ArrayList<>(); // ignore cipher_suites int csLen = getInt16(input); while (csLen > 0) { int byte1 = getInt8(input); int byte2 = getInt8(input); final MechanismDatabase.Entry entry = getCipherSuiteById(byte1, byte2); if (entry != null) ciphers.add(entry.getName()); csLen -= 2; } // ignore compression methods ignoreByteVector8(input); if (input.remaining() > 0) { info = exploreExtensions(input); } final List snList = info != null ? info.sni : Collections.emptyList(); final List alpnProtocols = info != null ? info.alpn : Collections.emptyList(); return new SSLConnectionInformationImpl( recordMajorVersion, recordMinorVersion, helloMajorVersion, helloMinorVersion, snList, alpnProtocols, ciphers.isEmpty() ? Collections.emptyList() : ciphers); } /* * 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 ElytronMessages.log.invalidTlsExt(); } rem -= 2; while (rem > 0) { int len = getInt8(input); if (len > rem) { throw ElytronMessages.log.notEnoughData(); } 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 ElytronMessages.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 ElytronMessages.log.notEnoughData(); } byte[] encoded = new byte[snLen]; input.get(encoded); SNIServerName serverName; switch (code) { case StandardConstants.SNI_HOST_NAME: if (encoded.length == 0) { throw ElytronMessages.log.emptyHostNameSni(); } 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 ElytronMessages.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 ElytronMessages.log.invalidTlsExt(); } if (remains != 0) { throw ElytronMessages.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); } } private static MechanismDatabase.Entry getCipherSuiteById(int byte1, int byte2) { MechanismDatabase.Entry entry = MechanismDatabase.getTLS13Instance().getCipherSuiteById(byte1, byte2); if (entry == null) { entry = MechanismDatabase.getInstance().getCipherSuiteById(byte1, byte2); } return entry; } 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; } } static final class SSLConnectionInformationImpl implements SSLConnectionInformation { private final String recordVersion; private final String helloVersion; private final List sniNames; private final List alpnProtocols; private final List ciphers; SSLConnectionInformationImpl(byte recordMajorVersion, byte recordMinorVersion, byte helloMajorVersion, byte helloMinorVersion, List sniNames, final List alpnProtocols, final List ciphers) { this.recordVersion = getVersionString(recordMajorVersion, recordMinorVersion); this.helloVersion = getVersionString(helloMajorVersion, helloMinorVersion); this.sniNames = sniNames; this.alpnProtocols = alpnProtocols; this.ciphers = ciphers; } private static String getVersionString(final byte helloMajorVersion, final byte helloMinorVersion) { switch (helloMajorVersion) { case 0x00: { switch (helloMinorVersion) { case 0x02: return "SSLv2Hello"; default: return unknownVersion(helloMajorVersion, helloMinorVersion); } } case 0x03: { switch (helloMinorVersion) { case 0x00: return "SSLv3"; case 0x01: return "TLSv1"; case 0x02: return "TLSv1.1"; case 0x03: return "TLSv1.2"; case 0x04: return "TLSv1.3"; default: return unknownVersion(helloMajorVersion, helloMinorVersion); } } default: return unknownVersion(helloMajorVersion, helloMinorVersion); } } @Override public String getRecordVersion() { return recordVersion; } @Override public String getHelloVersion() { return helloVersion; } @Override public List getSNIServerNames() { return Collections.unmodifiableList(sniNames); } @Override public List getProtocols() { return Collections.unmodifiableList(alpnProtocols); } @Override public List getCipherSuites() { return Collections.unmodifiableList(ciphers); } private static String unknownVersion(byte major, byte minor) { return "Unknown-" + (major & 0xff) + "." + (minor & 0xff); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy