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 连接,他们必须确保它得到充分验证!例如。如果它是已知证书,则将代码添加到 checkServerIdentity
以 pin 它。
正如你所描述的:)。覆盖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 与证书的替代名称不匹配