【问题标题】:Node.js Hostname/IP doesn't match certificate's altnamesNode.js 主机名/IP 与证书的替代名称不匹配
【发布时间】: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 上回滚,现在一切正常。

标签: node.js


【解决方案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 并没有完全处理它出于某种原因正确。比较stackoverflow.com/a/31862256/179583stackoverflow.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
                          }
                      });
              

              【讨论】:

                猜你喜欢
                • 2017-09-14
                • 2021-07-07
                • 2020-12-19
                • 2018-11-05
                • 2017-07-23
                • 2020-10-03
                相关资源
                最近更新 更多