Java中有Java x509certificate ClientHello解析器吗?

The*_*Dr. 2 java networking ssl-certificate x509certificate

我得到了一个字节数据包,它们是ClientHelloSSL 协议数据包。

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

Cas*_*lia 6

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

如果您最终自己这样做,需要注意的关键事项是:

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

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

这里有一些 Java 代码,可能有助于您入门。 请注意,它在 Java 中使用字节缓冲区,因为在解码此类二进制协议时需要处理无符号数据类型。

// 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;
}
Run Code Online (Sandbox Code Playgroud)

有关其余代码,请参阅此SslClientHelloDecoder.java文件。

希望这可以帮助!