主机名/IP 与证书的替代名称不匹配

Posted

技术标签:

【中文标题】主机名/IP 与证书的替代名称不匹配【英文标题】:Hostname / IP doesn't match certificate's altname 【发布时间】:2012-12-14 20:33:42 【问题描述】:

我正在尝试使用带有自签名证书的 Node.js 0.8.8 创建 TLS 服务器/客户端设置。

基本的服务器代码如下所示

var tlsServer = tls.createServer(
  key: fs.readFileSync('server-key.pem'),
  cert: fs.readFileSync('server-cert.pem')
, function (connection) 
  // [...]
);
tlsServer.listen(3000);

现在,当我尝试连接到此服务器时,我使用以下代码:

var connection = tls.connect(
  host: '192.168.178.31',
  port: 3000,

  rejectUnauthorized: true,
  ca: [ fs.readFileSync('server-cert.pem') ]
, function () 
  console.log(connection.authorized);
  console.log(connection.authorizationError);
  console.log(connection.getPeerCertificate());
);

如果我删除该行

ca: [ fs.readFileSync('server-cert.pem') ]

从客户端代码,Node.js 抛出一个错误,告诉我DEPTH_ZERO_SELF_SIGNED_CERT。据我了解,这是因为它是一个自签名证书,并且没有其他方信任此证书。

如果我删除

rejectUnauthorized: true,

同样,错误消失了——但connection.authorized 等于false,这实际上意味着我的连接未加密。无论如何,使用getPeerCertificate()我可以访问服务器发送的证书。由于我想强制执行加密连接,我知道我可能不会删除此行。

现在我读到我可以使用 ca 属性来指定我希望 Node.js 信任的任何 CA。 documentation of the TLS module 表示将服务器证书添加到ca 数组就足够了,然后一切都应该没问题。

如果我这样做,这个错误就会消失,但我会得到一个新的:

Hostname/IP doesn't match certificate's altnames

对我来说,这意味着 CA 现在基本上是受信任的,因此现在没关系,但证书是为我使用的主机之外的另一台主机制作的。

我使用

创建了证书
$ openssl genrsa -out server-key.pem 2048
$ openssl req -new -key server-key.pem -out server-csr.pem
$ openssl x509 -req -in server-csr.pem -signkey server-key.pem -out server-cert.pem

正如文档所暗示的那样。在创建 CSR 时,我会被问到一些常见的问题,例如国家、州、... 和通用名称 (CN)。正如您在“网络上”被告知的 SSL 证书一样,您确实提供您的 CN 名称,而是您想要使用的主机名。

这可能是我失败的地方。

我试过了

localhost 192.168.178.31 eisbaer eisbaer.fritz.box

最后两个是我的机器的本地名称和完全限定的本地名称。

知道我在这里做错了什么吗?

【问题讨论】:

重要提示:这是Node v0.8.8rejectUnauthorized 现在默认为 true 如果你使用rejectUnauthorized: false,你的连接默认是still encrypted。它根本不会验证您连接的主机的身份,这可能会使您容易受到 SSL 上的 MITM 攻击。 【参考方案1】:

最近有一个addition to node.js 允许使用自定义函数覆盖主机名检查。它已添加到 v0.11.14,并将在下一个稳定版本 (0.12) 中提供。现在您可以执行以下操作:

var options = 
  host: '192.168.178.31',
  port: 3000,
  ca: [ fs.readFileSync('server-cert.pem') ],
  checkServerIdentity: function (host, cert) 
    return undefined;
  
;
options.agent = new https.Agent(options);
var req = https.request(options, function (res) 
  //...
);

这将接受任何服务器身份,但仍会加密连接并验证密钥。

注意,在以前的版本中(例如v0.11.14),checkServerIdentity 是返回一个boolean 指示服务器的有效性。这已更改(在v4.3.1 之前)为函数returning(不是throwing)如果有问题则错误,如果有问题则undefined 有效。

【讨论】:

卡在 v0.10 中,我有 monkey-patched tls 模块可以跳过此检查。这是一个非常丑陋的修复,因为它是全球性的,但总比没有好。 设置 NODE_TLS_REJECT_UNAUTHORIZED=0 环境变量 只是为了警告人们:NODE_TLS_REJECT_UNAUTHORIZED 允许您连接到任何人,无论它是否是您想要的服务器。连接是加密的,但它没有授权,这通常是非常关键的事情。而且,将其设置为全局变量是一种非常笨拙的方法。只需在 tls 连接选项中将 rejectUnauthorized 设置为 false。 对于后人,(v4.3.1 docs)[nodejs.org/dist/latest-v4.x/docs/api/…表示如果服务器身份有问题,您应该returnError,如果服务器通过,则应该undefined身份检查。不确定此更改何时发生。【参考方案2】:

Mitar 有一个错误的假设,即 checkServerIdentity 应该在成功时返回“真”,但实际上它应该在成功时返回“未定义”。任何其他值都被视为错误描述。

所以这样的代码是正确的:

var options = 
  host: '192.168.178.31',
  port: 3000,
  ca: [ fs.readFileSync('server-cert.pem') ],
  checkServerIdentity: function (host, cert) 
    // It can be useful to resolve both parts to IP or to Hostname (with some synchronous resolver (I wander why they did not add done() callback as the third parameter)).
    // Be carefull with SNI (when many names are bound to the same IP).
    if (host != cert.subject.CN)
      return 'Incorrect server identity';// Return error in case of failed checking.
      // Return undefined value in case of successful checking.
      // I.e. you could use empty function body to accept all CN's.
  
;
options.agent = new https.Agent(options);
var req = https.request(options, function (res) 
  //...
);

我试图在 Mitar 的答案中进行编辑,但编辑被拒绝,所以我创建了一个单独的答案。

【讨论】:

【参考方案3】:

如果您使用主机名进行连接,则会根据 DNS 类型的主题备用名称检查主机名(如果有),否则将使用主题可分辨名称中的 CN。

如果您使用 IP 地址进行连接,则会根据 IP 地址 类型的 SAN 检查 IP 地址,而不会退回到 CN。

这至少是符合 HTTP over TLS 规范(即 HTTPS)的实现所做的。有些浏览器更宽容一些。

这与this answer in Java 中的问题完全相同,它还提供了一种通过 OpenSSL 放置自定义 SAN 的方法(请参阅this document too)。

一般来说,除非是用于测试 CA,否则很难管理依赖于 IP 地址的证书。使用主机名连接会更好。

【讨论】:

【参考方案4】:

在tls.js, lines 112-141 中,您可以看到如果调用connect 时使用的主机名是IP 地址,则忽略证书的CN,只使用SAN。

由于我的证书不使用 SAN,验证失败。

【讨论】:

我遇到了同样的问题,但我确实有 SAN 用于 localhost 和 127.0.0.1。不管我是连接主机名('localhost')还是 IP 地址('127.0.0.1')。对我来说,openssl s_client 连接,给定所有相同的参数,而 tls.connect() 失败。【参考方案5】:

您做错的是使用 IP 地址而不是域名。创建一个域名并将其粘贴在 DNS 服务器中(或仅在主机文件中),创建一个以域名作为 Common Name 的自签名证书,并连接到域名而不是 IP 地址。

【讨论】:

但它应该(必须?)也适用于 IP 地址,不是吗?由于各种原因,我不想设置域名。

以上是关于主机名/IP 与证书的替代名称不匹配的主要内容,如果未能解决你的问题,请参考以下文章

Node.js 主机名/IP 与证书的替代名称不匹配

API 网关 - ALB:主机名/IP 与证书的替代名称不匹配

Postman调试接口报错

如何使用 Express/got 代理/流式传输 HTTPS 请求?

主机名与对等方提供的证书主题不匹配,但完全匹配

卷曲,证书的所有者与主机名不匹配 [关闭]