Hostname / IP doesn't match certificate's altname

倖福魔咒の 提交于 2019-11-29 21:47:32
Mitar

Recently there was an addition to node.js which allows overriding hostname check with a custom function. It was added to v0.11.14 and will be available in the next stable release (0.12). Now you can do something like:

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

This will now accept any server identity, but still encrypt the connection and verify keys.

Note that in previous versions (e.g. v0.11.14), the checkServerIdentity was to return a boolean indicating the validity of the server. That has been changed (before v4.3.1) to the function returning (not throwing) an error if there is a problem and undefined if there is it's valid.

Golo Roden

In tls.js, lines 112-141, you can see that if the host name used when calling connect is an IP address, the certificate's CN is ignored and only the SANs are being used.

As my certificate doesn't use SANs, verification fails.

Bruno

If you're using a host name to connect, the host name will be checked against the Subject Alternative Names of DNS type, if any, and fall back on the CN in the Subject Distinguished Name otherwise.

If you're using an IP address to connect, the IP address will be be checked against the SANs of IP address type, without falling back on the CN.

This is at least what implementations compliant with the HTTP over TLS specification (i.e. HTTPS) do. Some browser are a bit more tolerant.

This is exactly the same problem as in this answer in Java, which also gives a method to put custom SANs via OpenSSL (see this document too).

Generally speaking, unless it's for a test CA, it's quite hard to manage certificates that rely on IP addresses. Connecting with a host name is better.

Mitar had a wrong assumption that checkServerIdentity should return 'true' at success, but actually it should return 'undefined' at success. Any other values are treated as error descriptions.

So such a code is correct:

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

I tried just to make edit in the Mitar's answer but the edit was rejected, so I created a separated answer.

What you're doing wrong is using an IP address instead of a domain name. Create a domain name and stick it in a DNS server (or just in a hosts file), create a self-signed certificate with the domain name as the Common Name, and connect to the domain name rather than the IP address.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!