我有一个客户端证书,其主题中包含非英文字符,当我使用 ngx_http_ssl_module 的属性 $ssl_client_s_dn 时,该值被转义两次。

实际价值 = “L=D’UNCONGÉ”

\xC3 再次编码得到 \x5CC3 ,我需要避免这种情况

实际值 = “L=D’UNCONG\x5CC3\x5C89”

预期值 = “L=D’UNCONG\xC3\x89”

有人可以告诉我我做错了什么吗?我该如何避免这种情况。

3

  • 我不明白您的预期值应该如何出现。我理解实际值出现的原因是因为有一个开头和结尾的双引号,带有撇号(我假设是撇号而不是单引号?对吗?)请使用更正的值进行更新


    – 

  • 不,我指的是证书中 É 的编码,我也更新了问题。简而言之,É 应该编码为 xC3\x89,但我使用 $ssl_client_s_dn 访问 nginx 时得到的值是 \x5CC3\x5C89


    – 

  • 感谢您的澄清,我理解双重编码部分,但需要澄清令人困惑的额外引号……谢谢大家。现在让我思考一些实际的答案……


    – 


最佳答案
1

这个谜题有很多部分。

首先,我要说的是,您提到的“预期值”包括十六进制数字常量(格式:\xHH)。只有在这是默认编码的情况下,这才是“预期”的。

Nginx 本身使用大多数字符串文字和数字常量(如十六进制、八进制或 Unicode 表示),在配置中没有任何问题;读取标头时也是如此 – 所以我最初对这种“双重转义”的不寻常格式感到困惑。

我查看了有关证书可分辨名称 (DN) 编码的 RFC。证书字符串的格式与双重编码的 URL 字符串等相比有细微的差别,但这种差异很重要。

请注意 RFC,特别是第 5 节末尾的示例表,并查看“引用”值与 UTF-8:
https:

RFC 指出,证书 UTF-8“É”(0xC389)在引用时为 \C3\89。根据您报告的实际值,这确认 => Nginx 正在从证书本身读取引用值。

然后 Nginx 对 \C3\89 中的“\”进行转义,这就解释了为什么它会变成“实际值”中的 \x5CC3\x5C89。

然而,这种不寻常的表示(可能)仅限于 Nginx 本身的日志 – 取决于您的配置,它可能不是 $ssl_client_s_dn 的实际值。

默认情况下,Nginx 会转义日志中的字符串,但不会转义变量中的值。关于日志格式,您可以通过向 log_format 指令添加选项来防止转义:

#find this in the http {} block of Nginx
# 'main' is just an example name
  log_format main ... # 'main' is a just a name, yours might be different

#make it so
  log_format main escape=none ... # keep your name, not 'main' 

将变量 $ssl_client_s_dn 附加到 log_format 值中的适当位置,以便可以检查。如有必要,请研究 Nginx 日志模块以了解如何执行此操作

如果您是 Nginx 新手,可能需要解开很多东西…所以让我回顾一下要点,经过仔细考虑后,希望您可以正确地定制配置以适应确切的用例:

  1. 这个问题是特定于实现的,也就是说它没有通用的解决方案。这里没有一些简单的基本问题 – 这是一个由众多相关因素组成的复杂案例。

  2. 防止逃逸日志将帮助您更好地理解从证书中收到的值;请确保至少记录 $ssl_client_s_dn,直到您确定一切按预期工作。

  3. 如果最终用户以外的某个代理/服务器/服务将客户端证书发送到 Nginx,请记住这是主要依赖关系 – 它们如何构建证书编码决定了 Nginx 如何读取证书主题 DN。

  4. 请注意,仅凭这一点就可以解决上游破坏标头值的情况 – 如果您在标头中将 $ssl_client_s_dn 的值发送到 App/Uptream,请确保不要将其括在引号中,因为 Nginx 不需要对其进行转义,并且上游可能也不需要对其进行转义。如果已经这样做了,请将其更改为将其括在引号中。虽然我更喜欢不加引号的方法,但这个简单的更改可以“修复”App/Upstream 的问题:

     location /blah {
         ...
         #try this first, replacing X-Subject-DN with the correct Header name
         proxy_set_header X-Subject-DN $ssl_client_s_dn;
    
         #then try this, do not use both else duplicate Header
         proxy_set_header X-Subject-DN "$ssl_client_s_dn";
     }
    
  5. 我做了很多假设。您可能根本不会将证书主题 DN 转发到另一个资源,您可能只在 Nginx 中对其进行评估,您可能已经使用escape=none更新了 log_format ,可能的情况有很多……

在所有这些情况下,请记住每个组件都可以转义处理的值,或者仅在日志中转义。了解每个组件的配置方式以及它们如何处理字符串文字、UTF-8 多字节字符和/或数字常量是关键!

至少考虑以下因素:

a => 客户端证书的发送者,初始证书数据的格式

b => Nginx 通常使用它收到的确切证书主题 DN,但除非您覆盖此行为,否则它会默认在其自己的日志中转义值。

c => App/Upstream(如果您在 Header 中发送 $ssl_client_s_dn)可能会转义 Header 值,或者它可能保持不变并且仅在日志(如 Nginx)中转义它…

这些都是基本概念——如何为这个问题提供解决方案呢?

如果需要,有一种方法可以在发送到上游时修改 $ssl_client_s_dn 中设置的证书 DN 的值。

#place in Nginx http block only
map $ssl_client_s_dn $client_dn_fix {
    default $ssl_client_s_dn;
    "L=D'UNCONG\C3\89" "L=D'UNCONGÉ"; #Actual Value you mention
    "L=D'UNCONG\xC3\x89" "hex-constant";
    "L=D'UNCONG\x5CC3\x5C89" "escaped-hex-const";
    "L=D'UNCONGÉ" "original";
    "L=D'UNCONG\x5CxC3\x5Cx89" "double-escaped";
    "~L=D'UNCONG[\x5C]C3[\x5C]89" "regex-cert-escaped";
    "~L=D'UNCONG[\x5C]xC3[\x5C]x89" "regex-hex-escaped";
}

#then add to the Location {} block as appropriate
location /blah {
    ...
    proxy_set_header X-Subject-DN $client_dn_fix;
    ...
}

显然这里的关键是 $client_dn_fix 中的值,它证明了 Nginx 在 $ssl_client_s_dn 中的内容。记录 $client_dn_fix 和 $ssl_client_s_dn 的值。

当查看日志时,您将能够在检查 $client_dn_fix 的值时看到 $ssl_client_s_dn 的真实值。

使用此 Map 的结果(以 $client_dn_fix 表示)后,您可以将“十六进制转义”或“原始”等值替换为类似“L=D’UNCONGÉ”(UTF-8 文字)或“L=D’UNCONG\xC3\x89”的字符串,以便上游收到您所说的“预期值”。

这里有很多可能的方法来实施这些概念,这取决于您真正需要什么。