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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/14088787/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-02 16:51:13  来源:igfitidea点击:

Hostname / IP doesn't match certificate's altname

node.jssslopensslssl-certificatecsr

提问by Golo Roden

I am trying to create a TLS server / client setup using Node.js 0.8.8 with a self-signed certificate.

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

The essential server code looks like

基本的服务器代码看起来像

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

Now when I try to connect to this server I use the following code:

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

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());
});

If I remove the line

如果我删除该行

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

from the client-side code, Node.js throws an error telling me DEPTH_ZERO_SELF_SIGNED_CERT. As far as I understand it this is due to the fact that it is a self-signed cert and there is no other party who trusts this certificate.

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

If I remove

如果我删除

rejectUnauthorized: true,

as well, the error is gone - but connection.authorizedis equal to falsewhich effectively means that my connection is not encrypted. Anyway, using getPeerCertificate()I can access the certificate sent by the server. As I want to enforce an encrypted connection, I understand that I may not remove this line.

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

Now I read that I can use the caproperty to specify any CA that I want Node.js to trust. The documentation of the TLS moduleimplies that it's enough to add the server certificate to the caarray, and then everything should be fine.

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

If I do that, this error is gone, but I get a new one:

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

Hostname/IP doesn't match certificate's altnames

To me this means that the CA is now basically trusted, hence that's okay now, but the certificate was made for another host than the one I use.

对我来说,这意味着 CA 现在基本上是可信的,因此现在可以了,但是证书是为另一台主机制作的,而不是我使用的主机。

I created the certificate using

我使用创建了证书

$ 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

as the documentation implies. When creating the CSR I am asked the usual questions, such as for country, state, ... and common name (CN). As you are told "on the web" for an SSL certificate you do notprovide your name as CN, but the host name you would like to use.

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

And this is probably where I fail.

这可能是我失败的地方。

I tried

我试过

  • localhost
  • 192.168.178.31
  • eisbaer
  • eisbaer.fritz.box
  • localhost
  • 192.168.178.31
  • eisbaer
  • eisbaer.fritz.box

where the last two are the local name and the fully qualified local name of my machine.

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

Any idea what I am doing wrong here?

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

采纳答案by Mitar

Recently there was an addition to node.jswhich allows overriding hostname check with a custom function. It was added to v0.11.14 and will be available in the next stable release (0.12). Now you can do something like:

最近,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) {
  //...
});

This will now accept any server identity, but still encrypt the connection and verify keys.

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

Notethat in previous versions (e.g. v0.11.14), the checkServerIdentitywas to return a booleanindicating the validity of the server. That has been changed (before v4.3.1) to the function returning (not throwing) an error if there is a problem and undefinedif there is it's valid.

请注意,在以前的版本(例如v0.11.14)中,将checkServerIdentity返回一个boolean指示服务器有效性的 。如果出现问题并且有效,则已将其更改为(之前v4.3.1)功能return(而不是throwing)错误undefined

回答by Golo Roden

In tls.js, lines 112-141, you can see that if the host name used when calling connectis an IP address, the certificate's CN is ignored and only the SANs are being used.

tls.js 的第 112-141 行,您可以看到,如果调用时使用的主机名connect是 IP 地址,则证书的 CN 将被忽略,仅使用 SAN。

As my certificate doesn't use SANs, verification fails.

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

回答by Bruno

If you're using a host name to connect, the host name will be checked against the Subject Alternative Names of DNS type, if any, and fall back on the CN in the Subject Distinguished Name otherwise.

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

If you're using an IP address to connect, the IP address will be be checked against the SANs of IP addresstype, without falling back on the CN.

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

This is at least what implementations compliant with the HTTP over TLSspecification (i.e. HTTPS) do. Some browser are a bit more tolerant.

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

This is exactly the same problem as in this answer in Java, which also gives a method to put custom SANs via OpenSSL (see this document too).

这与Java中的这个答案完全相同,它也提供了一种通过 OpenSSL 放置自定义 SAN 的方法(也请参阅此文档)。

Generally speaking, unless it's for a test CA, it's quite hard to manage certificates that rely on IP addresses. Connecting with a host name is better.

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

回答by Dzenly

Mitar had a wrong assumption that checkServerIdentity should return 'true' at success, but actually it should return 'undefined' at success. Any other values are treated as error descriptions.

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

So such a code is correct:

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

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) {
  //...
});

I tried just to make edit in the Mitar's answer but the edit was rejected, so I created a separated answer.

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

回答by Mike Scott

What you're doing wrong is using an IP address instead of a domain name. Create a domain name and stick it in a DNS server (or just in a hosts file), create a self-signed certificate with the domain name as the Common Name, and connect to the domain name rather than the IP address.

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