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

时间:2012-12-30 06:24:21

标签: node.js ssl openssl ssl-certificate csr

我正在尝试使用带有自签名证书的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

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

知道我在这里做错了吗?

5 个答案:

答案 0 :(得分:16)

最近有一个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之前)更改为函数return(不throw}错误,如果存在问题,则undefined更改错误。有效。

答案 1 :(得分:9)

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

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

答案 2 :(得分:7)

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

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

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

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

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

答案 3 :(得分:4)

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

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

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

答案 4 :(得分:-1)

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