22

我正在尝试使用带有自签名证书的 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。TLS模块的文档暗示将服务器证书添加到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

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

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

4

5 回答 5

17

最近有一个添加到 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)更改为 function returning(not throwing)错误。undefined

于 2014-12-18T16:19:07.130 回答
9

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

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

于 2012-12-30T08:16:27.753 回答
7

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

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

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

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

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

于 2012-12-30T11:35:36.633 回答
3

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

于 2016-01-27T20:07:09.103 回答
-1

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

于 2012-12-30T08:45:17.363 回答