The Dr.
The Dr.

Reputation: 576

Is there any Java x509certificate ClientHello parser in Java?

I got a packet of bytes which are the ClientHello of SSL protocol packet.

Before I'm starting to code by myself, a code which is going through all over the bytes to get each field value, I wonder if there is any Java Object (from java.security) which is used to get these bytes and parse the data so I would be able to take the SSL protocol fields and use them?

Upvotes: 2

Views: 1103

Answers (1)

Castaglia
Castaglia

Reputation: 3089

As far as I know, the java.security package in Java does not provide any functionality along the lines of what you're looking for. There may be other examples/libraries on GitHub.

If you end up doing this yourself, key things to be aware of are:

  • SSLv2 vs SSLv3 vs TLSv1 ClientHello formats
  • Your question title mentions certificates; client certificates are not present ClientHello messages; they are only sent later in the handshake process, in response to a CertificateRequest message from the server.

Decoding SSL/TLS messages usually involves reading (and re-reading) the relevant RFCs, and figuring out what each byte value signifies, by its position within the packet. Being designed as a binary protocol, for efficiency, it can be more complicated to tease out the fields you want than you might think necessary. But it's well-worth gaining an understanding of these fields.

Here's some Java code which might help to get you started. Note that it uses byte buffers in Java, as it is necessary to deal with unsigned data types when decoding binary protocols like this.

// These values are defined in the IETF TLS cipher suite registry; see:
//
//   http://www.iana.org/assignments/tls-parameters/tls-parameters.xml#tls-parameters-3
private static final int RC4_MD5_HEX = 0x0004;
private static final int RC4_SHA_HEX = 0x0005;

private static final short HANDSHAKE_CONTENT_TYPE = 22;
private static final short CLIENTHELLO_MESSAGE_TYPE = 1;

private static final short SSLV2_CLIENTHELLO = 128;
protected boolean doDecode(IoSession session,
                           IoBufferEx original,
                           ProtocolDecoderOutput out)
    throws Exception {

    // Need at least 2 bytes to differentiate between SSLv2 ClientHello
    // messages and SSLv3/TLSv1/+ messages.
    //
    // For SSLv2 ClientHello, we need:
    //
    //  length header (short)
    //  content type (byte)
    //
    // For more details, see:
    //
    //  https://datatracker.ietf.org/doc/html/draft-hickman-netscape-ssl-00
    //
    // Otherwise, we need:
    //
    //  content type (byte)
    //  version (short)
    //  length (short)
    //
    // So wait for at least 5 bytes, to cover either case.

    if (original.remaining() < 5) {
        return false;
    }

    // Make a copy, so that we can read things non-destructively
    IoBufferEx dup = original.duplicate();

    // If not a Handshake record, be done.  Note that we have to
    // successfully handle SSLv2 ClientHello formats as well.

    short contentType = dup.getUnsigned();
    if (contentType == HANDSHAKE_CONTENT_TYPE) {
        // Skip the ProtocolVersion here; we will get it later
        dup.skip(2);

        int recordSize = dup.getUnsignedShort();

        // Now wait until we have the entire record
        if (original.remaining() < (5 + recordSize)) {
            // Keep buffering
            return false;
        }

    } else if (contentType == SSLV2_CLIENTHELLO) {
        short len = dup.getUnsigned();

        // Decode the length
        int recordSize = ((contentType & 0x7f) << 8 | len);

        // Now wait until we have the entire record
        if (original.remaining() < (2 + recordSize)) {
            // Keep buffering
            return false;
        }

    } else { 
        // We're only interested in Handshake records
        out.write(original.getSlice(original.remaining()));
        return true;
    }

    // For the format of the ClientHello message, see RFC 5246,
    // Section 7.4.1.2.

    short messageType = dup.getUnsigned();
    if (messageType != CLIENTHELLO_MESSAGE_TYPE) {
        // We're only interested in ClientHello messages
        out.write(original.getSlice(original.remaining()));
        return true;
    }

    if (contentType == HANDSHAKE_CONTENT_TYPE) {
        // If we're not an SSLv2 ClientHello, then skip the ClientHello
        // message size.
        dup.skip(3);

        // Use the ClientHello ProtocolVersion
        SslVersion version = SslVersion.decode(dup.getUnsignedShort());

        // Skip ClientRandom
        dup.skip(32);

        // Skip SessionID
        int sessionIDSize = dup.getUnsigned();
        dup.skip(sessionIDSize);

        // Now we get to what we're really after: the ciphersuites supported
        // by the client.
        int cipherSuiteSize = dup.getUnsignedShort();

        // cipherSuiteSize is the number of bytes; each cipher is specified
        // using a short (2 bytes).  Thus the cipher suite count is the half
        // the cipher suite size.
        int cipherSuiteCount = cipherSuiteSize / 2;

        // Iterate through each of the ciphersuites

        for (int i = 0; i < cipherSuiteCount; i++) {
            int cipher = dup.getUnsignedShort();

            if (cipher == RC4_SHA_HEX) {
                ciphers.add(SslCipherSelectionFilter.RC4_SHA);

            } else if (cipher == RC4_MD5_HEX) {
                ciphers.add(SslCipherSelectionFilter.RC4_MD5);
            }
        }

    } else {
        // SSLv2 ClientHello.
        // Use the ClientHello ProtocolVersion
        SslVersion version = SslVersion.decode(dup.getUnsignedShort());

        // Determine cipher specs size
        short msb = dup.getUnsigned();
        short lsb = dup.getUnsigned();
        int cipherSuiteSize = ((msb << 8) | lsb);

        // Skip the sessionID size
        dup.skip(2);

        // Skip the challenge size
        dup.skip(2);

        // Now we get to what we're really after: the ciphersuites supported
        // by the client.

        // cipherSuiteSize is the number of bytes; each cipher is specified
        // using a medium int (3 bytes).
        int cipherSuiteCount = cipherSuiteSize / 3;

        // Iterate through each of the ciphersuites, looking for 
        // SSL_RSA_WITH_RC4_128_MD5.  (It's the only one supported in
        // SSLv2 ClientHellos).

        for (int i = 0; i < cipherSuiteCount; i++) {
            int cipherKind = dup.getUnsignedMediumInt();

            if (cipherKind == SSLV2_RC4_MD5_HEX) {
                appletCiphers.add(SslCipherSelectionFilter.RC4_MD5);
            }
        }
    }

    out.write(original.getSlice(original.remaining()));
    return true;
}

See this SslClientHelloDecoder.java file for the rest of the code.

Hope this helps!

Upvotes: 6

Related Questions