2012-12-30 187 views
17

我试图创建一个使用Node.js的0.8.8使用自签名证书的TLS服务器/客户端安装。主机名/ IP不匹配证书的备用名

基本服务器代码看起来像

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现在基本可信的,因此,现在没事,但证书被做对于另一台主机而不是我使用的主机。

我创建使用

​​

的文档暗示的证书。在创建企业社会责任时,我会问到一些常见的问题,例如国家,州,......和通用名称(CN)。正如您在“网络上”所告诉您的SSL证书所做的那样,而不是会提供您的名称作为CN,但是您希望使用的主机名。

而这可能是我失败的地方。

我试图

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

其中最后两个是当地的名字和我的机器的完全合格的本地名称。

任何想法我在做什么错在这里?

+0

重要提示:这是'Node v0.8.8'。 'rejectUnauthorized'现在默认为'true'。 – Breedly

+0

如果您使用'rejectUnauthorized:false',则默认情况下您的连接[仍然是加密的](https://stackoverflow.com/a/16311147/4068278)。它根本不验证您连接的主机的身份,这可能会让您容易受到SSL上的MITM攻击。 –

回答

13

最近有一个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

+0

被困在v0.10中,我[猴子补丁](https://gist.github.com/ntrrgc/c5cbff08d0033e00f503)'tls'模块跳过这个检查。这是一个非常丑陋的解决方案,因为它是全球性的,但它总比没有好。 – Alicia

+6

set NODE_TLS_REJECT_UNAUTHORIZED = 0 env var –

+6

只是为了警告人们:'NODE_TLS_REJECT_UNAUTHORIZED'允许你连接到任何人,不管它是否是你想要的服务器。连接是加密的,但它不是*授权*,这通常是非常关键的事情。而且,将它设置为全局变量是一个非常笨手段的方法。只需在'tls'连接选项中将'rejectUnauthorized'设置为false即可。 – Breedly

8

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

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

+0

我有这个相同的问题,但我有本地主机和127.0的SAN。0.1。无论我与主机名('localhost')还是IP地址('127.0.0.1')连接,都无关紧要。对于我来说,openssl s_client连接给定所有相同的参数,而tls.connect()失败。 – Bryan

0

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

+4

但它应该(必须)与IP地址一起工作,不是吗?出于各种原因,我不想设置域名。 –

7

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

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

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

这与this answer in Java中的问题完全相同,该问题也提供了通过OpenSSL放置定制SAN的方法(请参阅this document too)。

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

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

我想只是为了在米塔尔的答案编辑,但编辑被拒绝,所以我创建了一个分开的答案。

相关问题