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

Posted

技术标签:

【中文标题】Node.js 主机名/IP 与证书的替代名称不匹配【英文标题】:Node.js Hostname/IP doesn't match certificate's altnames 【发布时间】:2012-12-25 03:20:36 【问题描述】:

我有代码:

var r = require('request');
r(
  method: 'POST',
  url: 'https://api.dropbox.com',
  function()  console.log(arguments)   )

当我使用 Node 0.9.4 在桌面上运行它时,我在控制台中得到了这个:

 '0': [Error: Hostname/IP doesn't match certificate's altnames] 

当我在使用 Node 0.6.12 的上网本上运行它时,一切正常,没有错误(302 响应 - 我认为它是正确的)。

在问题Node.js hostname/IP doesnt match certificates altnames,Rojuinex 写道:“是的,浏览器问题......对不起”。 “浏览器问题”是什么意思?

UPD。在 Node v0.8 上回滚后,此问题已解决

【问题讨论】:

那是对第一条评论的回应。 谢谢,我了解“浏览器问题”) 但我不明白为什么在节点 0.6.12 上它工作正常,但在节点 0.9.4 上却抛出错误。 您是否出于特定原因使用 node (0.9.x) 的不稳定分支?一般来说,非开发代码使用node的稳定版本(偶版本号,0.6.x,0.8.x)是个好主意。您使用的请求库可能存在不稳定节点分支 (0.9.x) 的问题。 我在 Node 0.8.1 上回滚,现在一切正常。 【参考方案1】:

从 0.9.2(包括 0.10.x)开始,node.js 现在默认验证证书。这就是为什么当你升级到 node.js 0.8 之后你会看到它变得更加严格。 (HT:https://github.com/mscdex/node-imap/issues/181#issuecomment-14781480)

您可以使用rejectUnauthorized:false 选项避免这种情况,但这会产生严重的安全隐患。您发送给对等方的任何内容仍将被加密,但安装中间人攻击变得更加容易,即您的数据将被加密给对等方但对等方本身不是你认为的服务器!

最好先诊断证书未授权的原因,然后看看是否可以修复。

【讨论】:

使用节点 v6.5 并添加rejectUnauthorized 为我做到了:tls.connect(host: host, port: 443, rejectUnauthorized: false); 不要使用rejectUnauthorized:false,并禁用SSL及其好处,最好使用checkServerIdentity: () => undefined,跳过ip检查。 @TamirAdler 谢谢,但在实践中有很大不同吗? rejectUnauthorized 不会禁用 SSL,但会禁用服务器证书验证。提供一个验证任何/所有服务器的回调似乎都差不多,不是吗? 啊,可能有一点不同:不是在自签名证书的情况下,而是在 OP 的情况下,它是来自受信任的 CA 的证书,但 node.js 并没有完全处理它出于某种原因正确。比较***.com/a/31862256/179583 和***.com/a/50553422/179583——显然通过checkServerIdentity() 可能只禁用部分检查而不是全部检查?也就是说,如果要充分利用 TLS 连接,他们必须确保它得到充分验证!例如。如果它是已知证书,则将代码添加到 checkServerIdentitypin 它。 正如你所描述的:)。覆盖checkServerIdentity方法,就是去掉部分检查(基本上是服务器主机名检查,可以在覆盖方法中手动添加),但是客户端还是检查服务器的CA,避免“Man-in-the-中”攻击。【参考方案2】:

一个稍微更新的答案(因为我在不同的情况下遇到了这个问题。)

当您使用 SSL 连接到服务器时,服务器所做的第一件事就是提供一个证书,上面写着“我是 api.dropbox.com”。证书有一个“主题”,该主题有一个“CN”(“通用名称”的缩写)。证书也可能有一个或多个“subjectAltNames”。当 node.js 连接到服务器时,node.js 会获取此证书,然后验证它认为它正在连接的域名 (api.dropbox.com) 是否与主题的 CN 或其中一个替代名称匹配。请注意,在节点 0.10.x 中,如果您使用 IP 连接,则 IP 地址必须在 altnames 中 - node.js 不会尝试根据 CN 验证 IP。

rejectUnauthorized 标志设置为 false 将绕过此检查,但首先,如果服务器给您的凭据与您预期的不同,那么会发生一些可疑的事情,其次这也会绕过其他检查 - 它是如果您通过 Internet 连接,这不是一个好主意。

如果你使用 node >= 0.11.x,你还可以为 tls 模块指定一个checkServerIdentity: function(host, cert) 函数,如果你想允许连接,它应该返回undefined,否则抛出异常(虽然我不'不知道 request 是否会将此标志代理到 tls 为您。)声明这样的函数和 console.log(host, cert); 可以很方便地弄清楚到底发生了什么。

【讨论】:

我也为这个投了赞成票,现在我在这里注意到了!在我自己的答案中查看 cmets 中的一些讨论 — 我不认为 盲目 从服务器身份回调返回 undefined 可以大大提高安全性,但那不是这个答案是提议,iiuc。 +1 建议记录调试信息(然后编写代码以“手动”仔细检查它,以防服务器的证书和/或客户端的 CA 存储绝对无法修复)。【参考方案3】:

解决包http-proxy的问题

1) HTTP (localhost) 访问 HTTPS 要解决此问题,请将 changeOrigin 设置为 true。

const proxy = httpProxy.createProxyServer();

proxy.web(req, res, 
  changeOrigin: true,
  target: https://example.com:3000,
);

2)HTTPS访问HTTPS你应该包括SSL证书

httpProxy.createServer(
  ssl: 
    key: fs.readFileSync('valid-ssl-key.pem', 'utf8'),
    cert: fs.readFileSync('valid-ssl-cert.pem', 'utf8')
  ,
  target: 'https://example.com:3000',
  secure: true
).listen(443);

【讨论】:

这解决了我,当使用proxy.web时,谢谢。【参考方案4】:

我在使用请求模块从其他地方代理 POST 请求时遇到了同样的问题,这是因为我将主机属性留在了标头中(我正在从原始请求中复制标头)。

【讨论】:

这是一个很好的观点,感谢您将其添加到可能的原因列表中 谢谢。我实际上最终修复了一个正确的证书,但由于不了解问题的根本原因而感到困扰,主要是为什么当我的内部 API 从我的代理 lambda 调用时这只是一个问题,但这是因为主机头被保留了。 谢谢!这个答案帮助我意识到我的问题是我在域中包含了“https”部分,所以我只是将域更改为如下所示: domain = "search-domain.region.es.amazonaws.com" 删除"https://"【参考方案5】:

在其他情况下解决此问题的另一种方法是使用NODE_TLS_REJECT_UNAUTHORIZED=0 作为环境变量

NODE_TLS_REJECT_UNAUTHORIZED=0 node server.js

警告:这在安全方面是个坏主意

【讨论】:

这样做与设置rejectUnauthorized:false 选项相同,并且存在重大安全问题。【参考方案6】:

我知道这是旧的,但对于其他人来说:

从主机名中删除 https:// 并添加端口 443。


  method: 'POST',
  hostname: 'api.dropbox.com',
  port: 443

【讨论】:

【参考方案7】:

验证证书是由已知的证书颁发机构 (CA) 颁发后,将检查主题备用名称或通用名称,以验证主机名是否匹配。这是在checkServerIdentity function。如果证书具有主题备用名称并且未列出主机名,您将看到描述的错误消息:

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

如果您拥有用于生成您正在使用的证书的 CA 证书(通常是使用自签名证书时的情况),则可以提供

var r = require('request');

var opts = 
    method: "POST",
    ca: fs.readFileSync("ca.cer")
;

r('https://api.dropbox.com', opts, function (error, response, body) 
    // do something
);

这将验证证书是否由提供的 CA 颁发,但仍会执行主机名验证。如果证书包含主题备用名称中的主机名,只需提供 CA 就足够了。如果没有,并且您还想跳过主机名验证,您可以为checkServerIdentity 传递一个 noop 函数

var r = require('request');

var opts = 
    method: "POST",
    ca: fs.readFileSync("ca.cer"),
    agentOptions:  checkServerIdentity: function()  
;

r('https://api.dropbox.com', opts, function (error, response, body) 
    // do something
);

【讨论】:

【参考方案8】:

如果我们使用localhost 目标地址(host 或 node.js 上的hostname)测试客户端请求,并且我们的服务器公用名在服务器证书中为 CN = localhost,则不会出现此问题。但即使我们将localhost 更改为127.0.0.1 或任何其他IP,我们也会在node.js 上得到错误Hostname/IP doesn't match certificate's altnames 或在QT 上得到SSL handshake failed

在我的客户端请求中,我的服务器证书也遇到了同样的问题。要在我的客户端 node.js 应用程序上解决它,我需要在我的 server_extension 上添加一个 subjectAltName,并使用以下值:

[ server_extension ]
       .
       .
       .

subjectAltName          = @alt_names_server

[alt_names_server]
IP.1 = x.x.x.x

然后我在创建和签署证书时使用-extension

示例:

我的情况中,我首先导出发行者的配置文件,因为该文件包含server_extension

export OPENSSL_CONF=intermed-ca.cnf

所以我创建并签署了我的服务器证书:

openssl ca \
    -in server.req.pem \
    -out server.cert.pem \
    -extensions server_extension \
    -startdate `date +%y%m%d000000Z -u -d -2day` \
    -enddate `date +%y%m%d000000Z -u -d +2years+1day`   

它在基于 node.js 的带有 https 请求的客户端上运行良好,但是当我们定义 sslConfiguration.setPeerVerifyMode(QSslSocket::VerifyPeer) 时它不适用于基于 QT QSsl 的客户端,除非我们使用 QSslSocket::VerifyNone 它不会工作。如果我们使用VerifyNone,它将使我们的应用程序不检查对等证书,因此它将接受任何证书。因此,为了解决这个问题,我需要更改其证书上的服务器公用名,并将其值替换为我的服务器正在运行的 IP 地址。

例如:

CN = 127.0.0.1

【讨论】:

【参考方案9】:

我在从 AWS 中的 Lambda 函数流式传输到 ElasticSearch 时得到了这个。把我的头撞在墙上试图弄清楚。最后,在设置 request.headers['Host'] 时,我将 https:// 添加到 ES 域中 - 将其更改为 [es-domain-name].eu-west-1.es.amazonaws.com(没有 https://)立即生效。下面是我用来让它工作的代码,希望能避免其他人把头撞到墙上......

import path from 'path';
import AWS from 'aws-sdk';

const  region, esEndpoint  = process.env;
const endpoint = new AWS.Endpoint(esEndpoint);
const httpClient = new AWS.HttpClient();
const credentials = new AWS.EnvironmentCredentials('AWS');

/**
 * Sends a request to Elasticsearch
 *
 * @param string httpMethod - The HTTP method, e.g. 'GET', 'PUT', 'DELETE', etc
 * @param string requestPath - The HTTP path (relative to the Elasticsearch domain), e.g. '.kibana'
 * @param string [payload] - An optional javascript object that will be serialized to the HTTP request body
 * @returns Promise Promise - object with the result of the HTTP response
 */
export function sendRequest ( httpMethod, requestPath, payload ) 
    const request = new AWS.HttpRequest(endpoint, region);

    request.method = httpMethod;
    request.path = path.join(request.path, requestPath);
    request.body = payload;
    request.headers['Content-Type'] = 'application/json';
    request.headers['Host'] = '[es-domain-name].eu-west-1.es.amazonaws.com';
    request.headers['Content-Length'] = Buffer.byteLength(request.body);
    const signer = new AWS.Signers.V4(request, 'es');
    signer.addAuthorization(credentials, new Date());

    return new Promise((resolve, reject) => 
        httpClient.handleRequest(
            request,
            null,
            response => 
                const  statusCode, statusMessage, headers  = response;
                let body = '';
                response.on('data', chunk => 
                    body += chunk;
                );
                response.on('end', () => 
                    const data = 
                        statusCode,
                        statusMessage,
                        headers
                    ;
                    if (body) 
                        data.body = JSON.parse(body);
                    
                    resolve(data);
                );
            ,
            err => 
                reject(err);
            
        );
    );

【讨论】:

【参考方案10】:

如果您要信任子域,例如 aaa.localhost, 请不要像mkcert localhost *.localhost 127.0.0.1那样这样做,因为某些浏览器不接受通配符子域,这将不起作用。

也许可以试试mkcert localhost aaa.localhost 127.0.0.1

【讨论】:

【参考方案11】:

对于在 Node.js 应用程序中使用 Fetch API 的开发人员,这就是我使用 rejectUnauthorized 使其工作的方式。

请记住,使用rejectUnauthorized 是危险的,因为它会为您带来潜在的安全风险,因为它会绕过有问题的证书。

const fetch = require("node-fetch");
const https = require('https');

const httpsAgent = new https.Agent(
  rejectUnauthorized: false,
);

async function getData() 
  const resp = await fetch(
    "https://myexampleapi.com/endpoint",
    
      agent: httpsAgent,
    ,
  )
  const data = await resp.json()
  return data

【讨论】:

【参考方案12】:

这在使用 nodemailer 时对我有用:

var transporter = nodemailer.createTransport(
            host: 'mail.site.com',
            port: 25,
            secure: false,
            auth: 
                user: 'info@site.com',
                pass: 'YOUR_PASSWORD'
            ,
            tls: 
                rejectUnauthorized: false
            
        );

【讨论】:

以上是关于Node.js 主机名/IP 与证书的替代名称不匹配的主要内容,如果未能解决你的问题,请参考以下文章

NPM 没有安装包。主机名/IP 地址与证书的替代名称不匹配:

API 网关 - ALB:主机名/IP 与证书的替代名称不匹配

Postman调试接口报错

如何使用 Express/got 代理/流式传输 HTTPS 请求?

Node.js没有主机名验证的TLS连接

使用 Nodejs 调用需要证书的 SOAP 服务