Java中是否有Java x509certificate ClientHello解析器?

时间:2015-12-07 11:00:53

标签: java networking ssl-certificate x509certificate

我收到了一个字节数据包,它是SSL协议数据包的ClientHello

在我开始自己编写代码之前,通过遍历整个字节来获取每个字段值的代码,我想知道是否有用于获取这些字节的任何Java对象(来自java.security)并解析数据,以便我可以使用SSL协议字段并使用它们?

1 个答案:

答案 0 :(得分:3)

据我所知,Java中的java.security包不提供您所寻找的任何功能。 GitHub上可能还有其他示例/库。

如果你最终自己这样做,关键的事情是:

  • SSLv2 vs SSLv3 vs TLSv1 ClientHello格式
  • 您的问题标题提到证书;客户端证书当前ClientHello消息;它们仅在握手过程中稍后发送,以响应来自服务器的CertificateRequest消息。

解码SSL / TLS消息通常涉及读取(并重新读取)相关RFC,并通过其在数据包内的位置确定每个字节值表示的内容。设计为二进制协议,为了提高效率,梳理您想要的字段可能比您认为必要的更复杂。但是,了解这些领域是非常值得的。

这里有一些Java代码可能有助于您入门。 注意它在Java中使用字节缓冲区,因为在解码这样的二进制协议时必须处理 unsigned 数据类型。

// 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:
    //
    //  http://tools.ietf.org/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;
}

请参阅此SslClientHelloDecoder.java文件以了解其余代码。

希望这有帮助!