Node.js主机名/ IP与证书的altnames不匹配

时间:2013-01-10 16:47:13

标签: node.js

我有代码:

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] }

当我在Netbook上使用Node 0.6.12运行它时,它都可以正常工作(302响应 - 我认为是正确的。)

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

UPD。在回滚节点v0.8

后解决了此问题

11 个答案:

答案 0 :(得分:58)

由于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}选项来避免此问题,但这会产生严重的安全隐患。您发送给对等方的任何内容仍然会被加密,但它会更容易 更容易发起中间人攻击,即您的数据将被加密到对等方但对方本身不是你认为它的服务器!

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

答案 1 :(得分:21)

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

当您使用SSL连接到服务器时,服务器所做的第一件事就是出现一个证明“我是api.dropbox.com”的证书。证书具有“主题”,主题具有“CN”(“通用名称”的缩写)。证书也可以具有一个或多个“subjectAltNames”。当node.js连接到服务器时,node.js获取此证书,然后验证它认为它连接到的域名(api.dropbox.com)是否与主题的CN或其中一个altnames匹配。请注意,在节点0.10.x中,如果使用IP进行连接,则IP地址必须位于altnames中 - node.js不会尝试针对CN验证IP。

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

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

答案 2 :(得分:15)

如果您使用的是http-proxy软件包,则当您的服务器是HTTP(例如localhost)且目标是HTTPS时会发生这种情况。要解决此问题,请将changeOrigin设置为true。

const proxy = httpProxy.createProxyServer();

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

如果您的服务器是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);

答案 3 :(得分:8)

我知道这是旧的,但对于其他任何人看:

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

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

答案 4 :(得分:4)

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

NODE_TLS_REJECT_UNAUTHORIZED=0 node server.js

答案 5 :(得分:3)

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

答案 6 :(得分:2)

在验证证书是由已知的证书颁发机构(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
});

答案 7 :(得分:1)

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

我在客户端请求中遇到了与服务器证书相同的问题。要在我的客户端node.js应用上解决此问题,我需要在subjectAltName上添加server_extension,其值为:

[ 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`   
  

在基于具有https请求的node.js的客户端上运行正常但在我们定义sslConfiguration.setPeerVerifyMode(QSslSocket::VerifyPeer)时它不能与基于QT QSsl的客户端一起使用,除非我们使用QSslSocket::VerifyNone它赢了# 39;工作。如果我们使用VerifyNone,它将使我们的应用不会检查对等证书,以便它接受任何证书。因此,要解决此问题,我需要在其证书上更改我的服务器公用名,并替换其运行我的服务器的IP地址的值。

例如:

CN = 127.0.0.1

答案 8 :(得分:0)

如果您要信任一个子域,例如aaa.localhost, 请不要像mkcert localhost *.localhost 127.0.0.1那样做,因为某些浏览器不接受通配符子域,所以此操作将无效。

也许尝试mkcert localhost aaa.localhost 127.0.0.1

答案 9 :(得分:0)

当从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 :(得分:0)

对于在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
}