Подтвердить что ты не робот

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

Я пытаюсь создать настройку сервера/клиента TLS с помощью Node.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') ]

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

Если я удалю

rejectUnauthorized: true,

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

Теперь я прочитал, что могу использовать свойство ca для указания любого ЦС, которому я хочу доверять Node.js. Документация модуля TLS подразумевает, что достаточно добавить сертификат сервера в массив ca, а затем все должно быть хорошо.

Если я это сделаю, эта ошибка исчезла, но я получаю новую:

Hostname/IP doesn't match certificate 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

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

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

Я пробовал

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

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

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

4b9b3361

Ответ 1

Недавно появилось дополнение к node.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) на функцию return ing (not throw ing) ошибку, если есть проблема, и undefined, если она действительна.

Ответ 2

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

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

Ответ 3

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

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

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

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

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

Ответ 4

У Митара было ошибочное предположение, что checkServerIdentity должен возвращать "истину" при успехе, но на самом деле он должен возвращать "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, но редактирование было отклонено, поэтому я создал отдельный ответ.

Ответ 5

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