Имя хоста / IP не соответствует altname сертификата

я пытаюсь создать настройку сервера / клиента TLS с помощью узла.js 0.8.8 с самозаверяющим сертификатом.

основной код сервера выглядит как

var tlsServer = tls.createServer({
  key: fs.readFileSync('server-key.pem'),
  cert: fs.readFileSync('server-cert.pem')
}, function (connection) {
  // [...]
});
tlsServer.listen(3000);

теперь, когда я пытаюсь подключиться к этому серверу, я использую следующий код:

var connection = tls.connect({
  host: '192.168.178.31',
  port: 3000,

  rejectUnauthorized: true,
  ca: [ fs.readFileSync('server-cert.pem') ]
}, function () {
  console.log(connection.authorized);
  console.log(connection.authorizationError);
  console.log(connection.getPeerCertificate());
});

если я удалю строку

ca: [ fs.readFileSync('server-cert.pem') ]

из кода на стороне клиента, узел.js бросает сообщение об ошибке DEPTH_ZERO_SELF_SIGNED_CERT. Насколько я понимаю, это связано с тем, что это самозаверяющий сертификат и нет другой стороны, которая доверяет этому сертификату.

если я удалить

rejectUnauthorized: true,

кроме того, ошибка исчезла - но connection.authorized равна false, который фактически означает, что мое соединение не зашифровано. Во всяком случае, используя getPeerCertificate() Я могу получить доступ к сертификата, отправленного сервером. Поскольку я хочу обеспечить зашифрованное соединение, я понимаю, что я не могу удалить эту строку.

теперь я прочитал, что могу использовать ca свойство для указания любого CA, который я хочу узел.JS на доверии. The документация модуля TLS подразумевает, что достаточно добавить сертификат сервера в ca массив, и тогда все должно быть в порядке.

если я это сделаю, эта ошибка ушла, но я:

Hostname/IP doesn't match certificate's altnames

для меня это означает, что CA теперь в основном доверен, поэтому теперь все в порядке, но сертификат был сделан для другого хоста, чем тот, который я использую.

я создал сертификат используя

$ openssl genrsa -out server-key.pem 2048
$ openssl req -new -key server-key.pem -out server-csr.pem
$ openssl x509 -req -in server-csr.pem -signkey server-key.pem -out server-cert.pem

как следует из документации. При создании КСО мне задают обычные вопросы, такие как для страны, государства, ... и общее имя (CN). Как вам говорят "в интернете" для SSL-сертификата вы делаете не укажите свое имя как CN, но имя хоста, которое вы хотели бы использовать.

и это, вероятно, где я терплю неудачу.

I пробовал

  • localhost
  • 192.168.178.31
  • eisbaer
  • eisbaer.fritz.box

где последние два являются локальным именем и полным локальным именем моей машины.

есть идеи, что я делаю неправильно здесь?

5 ответов


недавно был дополнение к узлу.js что позволяет переопределить проверку имени хоста с помощью пользовательской функции. Он был добавлен в v0.11.14 и будет доступен в следующем стабильном выпуске (0.12). Теперь вы можете сделать что-то вроде:

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

теперь это будет принимать любой идентификатор сервера, но по-прежнему шифровать соединение и проверять ключи.

Примечание, что в предыдущих версиях (например,v0.11.14), то checkServerIdentity должен был возвратить boolean С указанием действия сервера. Это было изменено (до v4.3.1) к функции returning (не throwing) ошибка, если есть проблема и undefined если это действительно.


на tls.js, строки 112-141, вы можете увидеть, что если имя хоста используется при вызове connect - Это IP-адрес, CN сертификата игнорируется, и используются только SANs.

поскольку мой сертификат не использует SANs, проверка не выполняется.


Если вы используете имя хоста для подключения, имя хоста будет проверено на предмет альтернативных имен типа DNS, если таковые имеются, и вернуться на CN в теме различающееся имя в противном случае.

Если вы используете IP-адрес для подключения, IP-адрес будет проверен против SANs IP-адрес тип, не падая назад на CN.

Это по крайней мере то, что реализации соответствует HTTP через TLS спецификация (т. е. HTTPS). Некоторые браузеры более терпимы.

Это точно такая же проблема, как в этот ответ на Java, который также дает метод для установки пользовательских SANs через OpenSSL (см. этот документ тоже).

вообще говоря, если это не для тестового ЦС, довольно сложно управлять сертификатами, которые полагаются на IP-адреса. Соединение с именем хоста лучше.


У Mitar было неправильное предположение, что checkServerIdentity должен возвращать "true" при успехе, но на самом деле он должен возвращать "undefined" при успехе. Любые другие значения рассматриваются как описания ошибок.

вот такой код является правильным:

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

Я попытался просто отредактировать ответ Mitar, но редактирование было отклонено, поэтому я создал отдельный ответ.


то, что вы делаете неправильно, используя IP-адрес вместо доменного имени. Создайте доменное имя и вставьте его в DNS-сервер (или просто в файл hosts), создайте самозаверяющий сертификат с доменным именем в качестве общего имени и подключитесь к доменному имени, а не к IP-адресу.