主机名/ IP不匹配证书的altname

时间:2021-10-19 18:06:16

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

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

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.

从客户端代码,节点。js抛出一个错误,告诉我DEPTH_ZERO_SELF_SIGNED_CERT。据我所知,这是由于它是一个自签名的证书,并且没有其他任何一方信任这个证书。

If I remove

如果我删除

rejectUnauthorized: true,

as well, the error is gone - but connection.authorized is equal to false which 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.

同样,错误也消失了——但是连接。授权等于false,这实际上意味着我的连接没有加密。无论如何,使用getPeerCertificate(),我可以访问服务器发送的证书。由于我想强制执行一个加密的连接,我理解我不能删除这一行。

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

现在我读到,我可以使用ca属性来指定任何我想要节点的ca。js信任。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 not provide your name as CN, but the host name you would like to use.

文档说明。在创建CSR时,我通常会被问到一些问题,比如国家、国家、……和普通的名字(CN)。正如有人告诉您的SSL证书“在web上”,您不提供您的名称作为CN,而是希望使用的主机名。

And this is probably where I fail.

这可能是我失败的地方。

I tried

我试着

  • localhost
  • 本地主机
  • 192.168.178.31
  • 192.168.178.31
  • eisbaer
  • eisbaer
  • eisbaer.fritz.box
  • 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?

知道我做错了什么吗?

5 个解决方案

#1


14  

Recently there was an addition to node.js which 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又增加了一个新功能。它允许用自定义函数覆盖主机名检查。它被添加到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.

这将接受任何服务器标识,但仍然加密连接并验证密钥。

Note that in previous versions (e.g. v0.11.14), the checkServerIdentity was to return a boolean indicating 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 undefined if there is it's valid.

注意,在以前的版本(例如v0.11.14)中,checkServerIdentity返回一个指示服务器有效性的布尔值。在v4.3.1之前,如果有问题返回错误(而不是抛出错误),并且如果有效,则未定义错误。

#2


9  

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

在tls。第112-141行,您可以看到,如果在调用connect时使用的主机名是一个IP地址,则会忽略证书的CN,只使用san。

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

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

#3


7  

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 address type, without falling back on the CN.

如果您正在使用一个IP地址进行连接,那么将根据IP地址类型的san检查IP地址,而不会回到CN上。

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

这至少是符合HTTP / 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中的这个答案完全相同,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地址的证书。使用主机名连接更好。

#4


4  

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的答案中进行编辑,但是编辑被拒绝了,所以我创建了一个独立的答案。

#5


0  

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地址。

#1


14  

Recently there was an addition to node.js which 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又增加了一个新功能。它允许用自定义函数覆盖主机名检查。它被添加到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.

这将接受任何服务器标识,但仍然加密连接并验证密钥。

Note that in previous versions (e.g. v0.11.14), the checkServerIdentity was to return a boolean indicating 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 undefined if there is it's valid.

注意,在以前的版本(例如v0.11.14)中,checkServerIdentity返回一个指示服务器有效性的布尔值。在v4.3.1之前,如果有问题返回错误(而不是抛出错误),并且如果有效,则未定义错误。

#2


9  

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

在tls。第112-141行,您可以看到,如果在调用connect时使用的主机名是一个IP地址,则会忽略证书的CN,只使用san。

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

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

#3


7  

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 address type, without falling back on the CN.

如果您正在使用一个IP地址进行连接,那么将根据IP地址类型的san检查IP地址,而不会回到CN上。

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

这至少是符合HTTP / 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中的这个答案完全相同,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地址的证书。使用主机名连接更好。

#4


4  

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的答案中进行编辑,但是编辑被拒绝了,所以我创建了一个独立的答案。

#5


0  

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地址。