From 59ec2ee65145cdec4b4ec0102f6ccef48eb95d47 Mon Sep 17 00:00:00 2001 From: Vadim Zhestikov Date: Thu, 16 Apr 2026 13:49:15 -0700 Subject: [PATCH] Stream: add PP2_CLIENT_CERT_SESS and PP2_CLIENT_CERT_CONN flags. Set the PP2_CLIENT_CERT_SESS bit (0x04) in the client field of the PP2_TYPE_SSL TLV whenever the ssl_cn sub-TLV is configured. Presence of ssl_cn implies the CN was extracted from a client certificate, so the session has a verified certificate. Set PP2_CLIENT_CERT_CONN (0x02) additionally when the downstream connection is TLS and the TLS session was not reused (!SSL_session_reused), meaning the certificate was presented in this specific connection. Update the ssl_tlv and ssl_verify tests: servers that configure ssl_cn now expect client flags 0x05 instead of 0x01. --- src/stream/ngx_stream_proxy_module.c | 31 +++++++++++++++++++++---- t/stream_proxy_protocol_v2_ssl_tlv.t | 3 ++- t/stream_proxy_protocol_v2_ssl_verify.t | 3 ++- 3 files changed, 31 insertions(+), 6 deletions(-) diff --git a/src/stream/ngx_stream_proxy_module.c b/src/stream/ngx_stream_proxy_module.c index e18fee826..6c58c7d34 100644 --- a/src/stream/ngx_stream_proxy_module.c +++ b/src/stream/ngx_stream_proxy_module.c @@ -864,9 +864,9 @@ static ngx_array_t * ngx_stream_proxy_build_tlvs(ngx_stream_session_t *s, ngx_array_t *conf_tlvs, size_t *sizep) { - u_char *blob, *q; + u_char *blob, *q, client_flags; ngx_int_t verify_i; - ngx_uint_t i, n_ssl, has_ssl_verify; + ngx_uint_t i, n_ssl, has_ssl_verify, has_ssl_cn; uint32_t verify; size_t ssl_sub_total; ngx_str_t *vals; @@ -889,6 +889,7 @@ ngx_stream_proxy_build_tlvs(ngx_stream_session_t *s, ngx_array_t *conf_tlvs, *sizep = 0; n_ssl = 0; has_ssl_verify = 0; + has_ssl_cn = 0; verify = 0xFFFFFFFF; /* default: not verified */ ssl_sub_total = 0; ctlv = conf_tlvs->elts; @@ -914,6 +915,11 @@ ngx_stream_proxy_build_tlvs(ngx_stream_session_t *s, ngx_array_t *conf_tlvs, n_ssl++; ssl_sub_total += 3 + vals[i].len; /* sub-TLV: type(1)+len(2)+val */ + /* ssl_cn presence drives PP2_CLIENT_CERT_SESS / CERT_CONN flags */ + if (ctlv[i].type == 0x22) { + has_ssl_cn = 1; + } + } else { *sizep += 3 + vals[i].len; /* regular TLV wire size */ } @@ -948,8 +954,25 @@ ngx_stream_proxy_build_tlvs(ngx_stream_session_t *s, ngx_array_t *conf_tlvs, q = blob; - /* PP2_CLIENT_SSL (0x01): client connected over SSL/TLS */ - *q++ = 0x01; + /* + * PP2_CLIENT_SSL (0x01): client connected over SSL/TLS + * PP2_CLIENT_CERT_CONN (0x02): certificate verified in this connection + * PP2_CLIENT_CERT_SESS (0x04): certificate verified in this session + */ + client_flags = 0x01; /* PP2_CLIENT_SSL */ + + if (has_ssl_cn) { + client_flags |= 0x04; /* PP2_CLIENT_CERT_SESS */ +#if (NGX_SSL) + if (s->connection->ssl != NULL + && !SSL_session_reused(s->connection->ssl->connection)) + { + client_flags |= 0x02; /* PP2_CLIENT_CERT_CONN */ + } +#endif + } + + *q++ = client_flags; /* verify field in network byte order */ *q++ = (u_char) (verify >> 24); diff --git a/t/stream_proxy_protocol_v2_ssl_tlv.t b/t/stream_proxy_protocol_v2_ssl_tlv.t index 3eacc1c7c..d9a804b6e 100644 --- a/t/stream_proxy_protocol_v2_ssl_tlv.t +++ b/t/stream_proxy_protocol_v2_ssl_tlv.t @@ -118,6 +118,7 @@ $t->waitforsocket('127.0.0.1:' . port(8083)); # ssl_version [36]: type 0x21, len 7, value "TLSv1.3" # ssl_cipher [46]: type 0x23, len 22, value "TLS_AES_256_GCM_SHA384" # ssl_cn [71]: type 0x22, len 11, value "example.com" +# client byte = PP2_CLIENT_SSL|PP2_CLIENT_CERT_SESS = 0x05 (ssl_cn is set) # payload at offset 85 my $d1 = stream('127.0.0.1:' . port(8080))->io('hello'); @@ -125,7 +126,7 @@ my $d1 = stream('127.0.0.1:' . port(8080))->io('hello'); is(unpack('n', substr($d1, 14, 2)), 69, 'ssl_tlv len field'); is(unpack('C', substr($d1, 28, 1)), 0x20, 'ssl_tlv outer type'); is(unpack('n', substr($d1, 29, 2)), 54, 'ssl_tlv outer length'); -is(unpack('C', substr($d1, 31, 1)), 0x01, 'ssl_tlv client flags'); +is(unpack('C', substr($d1, 31, 1)), 0x05, 'ssl_tlv client flags'); is(unpack('N', substr($d1, 32, 4)), 0xFFFFFFFF, 'ssl_tlv verify'); is(unpack('C', substr($d1, 36, 1)), 0x21, 'ssl_version type'); diff --git a/t/stream_proxy_protocol_v2_ssl_verify.t b/t/stream_proxy_protocol_v2_ssl_verify.t index 15974c34f..550dd8fd8 100644 --- a/t/stream_proxy_protocol_v2_ssl_verify.t +++ b/t/stream_proxy_protocol_v2_ssl_verify.t @@ -76,6 +76,7 @@ $t->waitforsocket('127.0.0.1:' . port(8082)); # verify field = 42 = 0x0000002A (big-endian) # ssl_version [36]: type 0x21, len 7, value "TLSv1.3" # ssl_cn [46]: type 0x22, len 11, value "example.com" +# client byte = PP2_CLIENT_SSL|PP2_CLIENT_CERT_SESS = 0x05 (ssl_cn is set) # payload at offset 60 my $d1 = stream('127.0.0.1:' . port(8080))->io('hello'); @@ -83,7 +84,7 @@ my $d1 = stream('127.0.0.1:' . port(8080))->io('hello'); is(unpack('n', substr($d1, 14, 2)), 44, 'ssl_verify len field'); is(unpack('C', substr($d1, 28, 1)), 0x20, 'ssl_verify outer type'); is(unpack('n', substr($d1, 29, 2)), 29, 'ssl_verify outer length'); -is(unpack('C', substr($d1, 31, 1)), 0x01, 'ssl_verify client flags'); +is(unpack('C', substr($d1, 31, 1)), 0x05, 'ssl_verify client flags'); is(unpack('N', substr($d1, 32, 4)), 42, 'ssl_verify verify field'); is(unpack('C', substr($d1, 36, 1)), 0x21, 'ssl_verify ssl_version type');