Skip to content

TLCP client fails when multiple handshake messages are sent in one record and rejects empty BasicConstraints #1908

Description

@HTHou

Environment

  • GmSSL: 3.2.0, built from source
  • Client command: gmssl tlcp_client
  • Server: Kona JDK/KonaSSL based TLCP HTTPS server, Jetty 12
  • Protocol/cipher suite: TLCPv1.1, TLS_ECC_SM4_GCM_SM3
  • Server certificate chain: TLCP signing certificate, encryption certificate, CA certificate

Reproduce

gmssl tlcp_client \
  -host 127.0.0.1 \
  -port 18081 \
  -server_name localhost \
  -cacert ca.pem \
  -cipher_suite TLS_ECC_SM4_GCM_SM3 \
  -get /ping \
  -verbose

The server sends the TLCP server handshake flight in one TLS record, containing multiple handshake messages:

ServerHello
Certificate
ServerKeyExchange
ServerHelloDone

Actual behavior

The client fails during the handshake. From debugging, tls_record_get_handshake() appears to expect one handshake message to occupy the whole record. When a record contains more than one handshake message, the remaining bytes cause the following handshake state to fail with unexpected_message.

After locally working around this by splitting handshake messages from the same record, certificate verification continues but fails on BasicConstraints parsing/checking:

src/x509_ext.c:3019:x509_exts_check()
src/x509_ext.c:2141:x509_basic_constraints_from_der()
src/x509_cer.c:1882:x509_cert_check()
src/x509_cer.c:2166:x509_certs_verify_tlcp()
src/tls_vrf.c:411:tls_cert_chain_verify()
src/tlcp.c:874:tlcp_recv_server_certificate()
Alert: bad_certificate (42)

The entity certificates contain an empty BasicConstraints sequence, which should mean cA = false and no pathLenConstraint:

Extension
    extnID: BasicConstraints (2.5.29.19)
    BasicConstraints

Expected behavior

  1. The TLCP client should accept valid TLS/TLCP record framing where one record may contain multiple handshake messages. TLS record boundaries should not be assumed to match handshake message boundaries.
  2. BasicConstraints ::= SEQUENCE { cA BOOLEAN DEFAULT FALSE, pathLenConstraint INTEGER OPTIONAL }; an empty sequence should be accepted as an end-entity certificate with cA = false and absent pathLenConstraint.

Local verification

I made a local test patch with two changes:

  1. In the TLCP client receive path, buffer and split multiple handshake messages contained in one record before processing the next handshake state.
  2. In x509_basic_constraints_from_der(), treat an empty BasicConstraints sequence as cA = false and pathLenConstraint = absent.

With this local patch, the same command succeeds:

Connection established
Protocol: TLCP
CipherSuite: TLS_ECC_SM4_GCM_SM3
Certificate.subject: commonName: localhost
Certificate.issuer: commonName: timecho-gm-ca

HTTP/1.1 200 OK
{"code":200,"message":"SUCCESS_STATUS"}

There may also be a stricter compatibility question around CA certificates whose BasicConstraints is not marked critical. If that strictness is intentional, please ignore that part; the two issues above are the main interoperability blockers I observed.


中文版

环境

  • GmSSL:3.2.0,从源码编译
  • 客户端命令:gmssl tlcp_client
  • 服务端:基于 Kona JDK/KonaSSL 的 TLCP HTTPS 服务端,Jetty 12
  • 协议和密码套件:TLCPv1.1TLS_ECC_SM4_GCM_SM3
  • 服务端证书链:TLCP 签名证书、加密证书、CA 证书

复现方式

gmssl tlcp_client \
  -host 127.0.0.1 \
  -port 18081 \
  -server_name localhost \
  -cacert ca.pem \
  -cipher_suite TLS_ECC_SM4_GCM_SM3 \
  -get /ping \
  -verbose

服务端会在同一个 TLS record 中发送 TLCP 服务端握手消息,里面连续包含多个 handshake message:

ServerHello
Certificate
ServerKeyExchange
ServerHelloDone

实际行为

客户端在握手过程中失败。通过调试看,tls_record_get_handshake() 似乎假设一个 handshake message 会占满整个 record。当一个 record 中包含多个 handshake message 时,剩余字节会导致后续握手状态失败,并报 unexpected_message

本地临时修改 TLCP client 接收逻辑,让它可以从同一个 record 中拆分并缓存多个 handshake message 后,握手可以继续,但随后会在证书校验阶段因为 BasicConstraints 解析/检查失败而发送 bad_certificate

src/x509_ext.c:3019:x509_exts_check()
src/x509_ext.c:2141:x509_basic_constraints_from_der()
src/x509_cer.c:1882:x509_cert_check()
src/x509_cer.c:2166:x509_certs_verify_tlcp()
src/tls_vrf.c:411:tls_cert_chain_verify()
src/tlcp.c:874:tlcp_recv_server_certificate()
Alert: bad_certificate (42)

实体证书中的 BasicConstraints 是一个空 SEQUENCE,含义应当是 cA = false,且没有 pathLenConstraint

Extension
    extnID: BasicConstraints (2.5.29.19)
    BasicConstraints

期望行为

  1. TLCP client 应该支持合法的 TLS/TLCP record 分帧方式:一个 record 中可以包含多个 handshake message。TLS record 边界不应被假设为 handshake message 边界。
  2. BasicConstraints ::= SEQUENCE { cA BOOLEAN DEFAULT FALSE, pathLenConstraint INTEGER OPTIONAL };空 SEQUENCE 应该被接受,并表示实体证书 cA = falsepathLenConstraint 不存在。

本地验证

我本地做了一个测试补丁,包含两处修改:

  1. 在 TLCP client 接收服务端握手消息时,如果同一个 record 中包含多个 handshake message,就拆分并缓存剩余 handshake 数据,供下一个握手状态继续消费。
  2. x509_basic_constraints_from_der() 中,把空的 BasicConstraints sequence 解析为 cA = falsepathLenConstraint = absent

加上这个本地补丁后,同样的命令可以成功:

Connection established
Protocol: TLCP
CipherSuite: TLS_ECC_SM4_GCM_SM3
Certificate.subject: commonName: localhost
Certificate.issuer: commonName: timecho-gm-ca

HTTP/1.1 200 OK
{"code":200,"message":"SUCCESS_STATUS"}

另外,CA 证书中的 BasicConstraints 未标记为 critical 时,GmSSL 当前也会比较严格地拒绝。这个点如果是有意保持严格校验,可以先忽略;上面两个问题是我这次观察到的主要互通阻塞点。

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions