Node.js Hostname / IP stimmt nicht mit den Altnamen des Zertifikats überein


71

Ich habe Code:

var r = require('request');
r({
  method: 'POST',
  url: 'https://api.dropbox.com'},
  function() { console.log(arguments)  } )

Wenn ich es mit Node 0.9.4 auf dem Desktop ausführe, wird dies in der Konsole angezeigt:

{ '0': [Error: Hostname/IP doesn't match certificate's altnames] }

Wenn ich es auf Netbook mit Node 0.6.12 ausführe, funktioniert alles ohne Fehler (302 Antwort - ich denke, es ist richtig).

In Frage Node.js Hostname / IP stimmt nicht mit den Altnamen der Zertifikate überein . Rojuinex schreibt: "Ja, Browserproblem ... Entschuldigung". Was bedeutet "Browserproblem"?

UPD. Dieses Problem wurde nach dem Rollback auf Node v0.8 behoben


Das war eine Antwort auf den ersten Kommentar.
SLaks

Vielen Dank, ich verstehe über "Browser-Problem")
mr0re1

Aber ich verstehe nicht, warum es auf Knoten 0.6.12 gut funktioniert, aber auf Knoten 0.9.4 wirft es einen Fehler.
mr0re1

Verwenden Sie den instabilen Zweig des Knotens (0.9.x) aus einem bestimmten Grund? Im Allgemeinen ist es eine gute Idee, die stabilen Versionen des Knotens (gerade Versionsnummern, 0.6.x, 0.8.x) für Nicht-Entwicklungscode zu verwenden. Die von Ihnen verwendete Anforderungsbibliothek weist möglicherweise Probleme mit dem instabilen Knotenzweig (0.9.x) auf.
Smithclay

Ich rolle auf Node 0.8.1 zurück, jetzt funktionieren alle einwandfrei.
mr0re1

Antworten:


85

Seit 0.9.2 (einschließlich 0.10.x) validiert node.js jetzt standardmäßig Zertifikate. Aus diesem Grund kann es beim Upgrade auf node.js 0.8 strenger werden. (HT: https://github.com/mscdex/node-imap/issues/181#issuecomment-14781480 )

Sie können dies mit der {rejectUnauthorized:false}Option vermeiden , dies hat jedoch schwerwiegende Auswirkungen auf die Sicherheit . Alles, was Sie an den Peer senden, wird weiterhin verschlüsselt, aber es wird viel einfacher, einen Man-in-the-Middle-Angriff durchzuführen, dh Ihre Daten werden an den Peer verschlüsselt, aber der Peer selbst ist nicht der Server, von dem Sie glauben, dass er es ist!

Es ist besser, zuerst zu diagnostizieren, warum das Zertifikat nicht autorisiert ist, und zu prüfen, ob dies stattdessen behoben werden kann.


1
Die Verwendung von Node v6.5 und das Hinzufügen von RejectUnauthorized hat es für mich getan:tls.connect({host: host, port: 443, rejectUnauthorized: false});
Adrian

1
Verwenden Sie anstelle von reverseUnauthorized: false und deaktivieren Sie SSL und seine Vorteile. Verwenden Sie besser checkServerIdentity: () => undefined, um die IP-Prüfung zu überspringen.
Tamir Adler

@ TamirAdler Danke, aber ist das in der Praxis ganz anders? rejectUnauthorizeddeaktiviert SSL nicht, deaktiviert jedoch die Überprüfung von Serverzertifikaten. Das Bereitstellen eines Rückrufs, der alle Server überprüft, scheint ungefähr gleich zu sein, nicht wahr?
Natevw

Ah, es kann einen kleinen Unterschied geben: Nicht im Fall von selbstsignierten Zertifikaten, sondern wie im Fall des OP, wo es sich um ein Zertifikat einer vertrauenswürdigen Zertifizierungsstelle handelt, aber node.js aus irgendeinem Grund nicht ganz richtig damit umgeht. Vergleichen Sie stackoverflow.com/a/31862256/179583 und stackoverflow.com/a/50553422/179583 - anscheinend kann das Bestehen checkServerIdentity() {}nur einen Teil der Prüfungen deaktivieren, aber nicht alle? Das heißt, wenn man die TLS-Verbindung voll ausnutzen will, muss man sicherstellen, dass sie vollständig validiert ist! Wenn es sich beispielsweise um ein bekanntes Zertifikat handelt, fügen Sie Code hinzu, checkServerIdentityum es zu pinnen .
Natevw

1
Also wie du beschrieben hast :). Um die checkServerIdentity-Methode zu überschreiben, wird ein Teil der Prüfung entfernt (im Grunde genommen kann die Prüfung des Server-Hostnamens manuell in der Überschreibungsmethode hinzugefügt werden), der Client überprüft jedoch weiterhin die Zertifizierungsstelle des Servers und vermeidet die "Man-in-the-" mittlerer "Angriff.
Tamir Adler

34

Eine leicht aktualisierte Antwort (da ich unter verschiedenen Umständen auf dieses Problem gestoßen bin.)

Wenn Sie über SSL eine Verbindung zu einem Server herstellen, legt der Server zunächst ein Zertifikat mit der Aufschrift "Ich bin api.dropbox.com" vor. Das Zertifikat hat einen "Betreff" und der Betreff einen "CN" (kurz für "Common Name"). Das Zertifikat kann auch einen oder mehrere "subjectAltNames" haben. Wenn node.js eine Verbindung zu einem Server herstellt, ruft node.js dieses Zertifikat ab und überprüft dann, ob der Domänenname, zu dem eine Verbindung hergestellt werden soll (api.dropbox.com), entweder mit dem CN des Betreffs oder einem der Altnamen übereinstimmt. Beachten Sie, dass in Knoten 0.10.x, wenn Sie eine Verbindung über eine IP herstellen, die IP-Adresse in den Altnamen angegeben werden muss - node.js versucht nicht, die IP anhand des CN zu überprüfen.

Wenn Sie das rejectUnauthorizedFlag auf "false" setzen, wird diese Prüfung umgangen. Erstens, wenn der Server Ihnen andere Anmeldeinformationen als erwartet gibt, ist etwas faul, und zweitens werden auch andere Prüfungen umgangen - es ist keine gute Idee, wenn Sie dies tun Ich verbinde mich über das Internet.

Wenn Sie node> = 0.11.x verwenden, können Sie auch eine checkServerIdentity: function(host, cert)Funktion für das tls-Modul angeben , die zurückgegeben werden soll, undefinedwenn Sie die Verbindung zulassen und andernfalls eine Ausnahme auslösen möchten (obwohl ich nicht weiß, ob requestdieses Flag als Proxy verwendet wird Es kann praktisch sein, eine solche Funktion zu deklarieren und console.log(host, cert);herauszufinden, was zum Teufel los ist.


Ein Upvote für dieses auch von mir, jetzt wo ich es hier bemerkt habe! In den Kommentaren zu meiner eigenen Antwort finden Sie einige Diskussionen. Ich glaube nicht, dass die blinde Rückkehr undefinedvom Rückruf der Serveridentität die Sicherheit so stark verbessert, aber das ist nicht das, was diese Antwort vorschlägt, iiuc. +1 auf den Vorschlag, die Informationen zum Debuggen zu protokollieren (und dann Code zu schreiben, um sie sorgfältig "von Hand" zu überprüfen, wenn das Serverzertifikat und / oder der CA-Speicher des Clients absolut nicht repariert werden können).
Natevw

30

So beheben Sie das Problem für das Paket http-proxy

1) HTTP (localhost) greift auf HTTPS zu Um dieses Problem zu beheben, setzen Sie changeOrigin auf true.

const proxy = httpProxy.createProxyServer();

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

2) HTTPS Zugriff auf HTTPS Sie sollten ein SSL-Zertifikat einschließen

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);

Dies löste es für mich, wenn ich proxy.web benutze, danke.
Tiberiu-Ionuț Stan

12

Ich hatte das gleiche Problem bei der Verwendung des Anforderungsmoduls zum Proxy von POST-Anforderungen von einem anderen Ort. Dies lag daran, dass ich die Host-Eigenschaft im Header belassen habe (ich habe den Header von der ursprünglichen Anforderung kopiert).


Dies ist ein großartiger Punkt, danke, dass Sie dies zur Liste der möglichen Ursachen
hinzugefügt haben

DANKESCHÖN. Am Ende habe ich tatsächlich ein ordnungsgemäßes Zertifikat repariert, aber es hat mich gestört, die Hauptursache des Problems nicht zu verstehen, hauptsächlich, warum dies nur ein Problem war, als meine interne API von meinem Proxy-Lambda aufgerufen wurde, aber weil der Host-Header beibehalten wurde.
JHH

Vielen Dank! Diese Antwort half mir zu erkennen, dass mein Problem darin bestand, dass ich das Stück "https" in die Domain aufgenommen habe, sodass ich die Domain einfach so geändert habe: domain = "search-domain.region.es.amazonaws.com" Entfernen des "https: //"
Annie

10

Ich weiß, das ist alt, ABER für alle anderen, die suchen:

Entfernen Sie https: // aus dem Hostnamen und fügen Sie stattdessen Port 443 hinzu.

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

8

Nachdem überprüft wurde, ob das Zertifikat von einer bekannten Zertifizierungsstelle ausgestellt wurde, werden die alternativen Antragstellernamen oder der allgemeine Name überprüft, um sicherzustellen, dass der Hostname übereinstimmt. Dies ist in der Funktion checkServerIdentity . Wenn das Zertifikat alternative Betreffnamen enthält und der Hostname nicht aufgeführt ist, wird die beschriebene Fehlermeldung angezeigt:

Hostname / IP stimmt nicht mit den Altnamen des Zertifikats überein

Wenn Sie über das CA-Zertifikat verfügen, mit dem das von Ihnen verwendete Zertifikat generiert wird (normalerweise bei Verwendung von selbstsignierten Zertifikaten), kann dies bereitgestellt werden

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
});

Dadurch wird überprüft, ob das Zertifikat von der bereitgestellten Zertifizierungsstelle ausgestellt wurde. Die Überprüfung des Hostnamens wird jedoch weiterhin durchgeführt. Die Angabe der Zertifizierungsstelle reicht aus, wenn das Zertifikat den Hostnamen in den alternativen Betreffnamen enthält. Wenn dies nicht der Fall ist und Sie auch die Überprüfung des Hostnamens überspringen möchten, können Sie eine Noop-Funktion für übergebencheckServerIdentity

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

Die andere Möglichkeit, dies unter anderen Umständen zu beheben, besteht darin, es NODE_TLS_REJECT_UNAUTHORIZED=0als Umgebungsvariable zu verwenden

NODE_TLS_REJECT_UNAUTHORIZED=0 node server.js

WARNUNG: Dies ist aus Sicherheitsgründen eine schlechte Idee


6
Dies entspricht dem Festlegen der {rejectUnauthorized:false}Option und birgt auch erhebliche Sicherheitsbedenken.
ivandov

1

Wir haben dieses Problem nicht, wenn wir unsere Clientanforderung mit der localhostZieladresse ( hostoder hostnameauf node.js) testen und unser allgemeiner Servername CN = localhostim Serverzertifikat enthalten ist. Aber selbst wenn wir localhostfür 127.0.0.1oder eine andere IP ändern, erhalten wir Fehler Hostname/IP doesn't match certificate's altnamesauf node.js oder SSL handshake failedauf QT.

Ich hatte das gleiche Problem mit meinem Serverzertifikat auf meiner Client-Anfrage. Um es auf meiner Client Node.js App zu lösen, musste ich eine mit dem folgenden Wert subjectAltNameauf meine setzen server_extension:

[ server_extension ]
       .
       .
       .

subjectAltName          = @alt_names_server

[alt_names_server]
IP.1 = x.x.x.x

und dann verwende ich, -extensionwenn ich das Zertifikat erstelle und signiere.

Beispiel:

In meinem Fall exportiere ich zuerst die Konfigurationsdatei des Ausstellers, da diese Datei Folgendes enthält server_extension:

export OPENSSL_CONF=intermed-ca.cnf

Also erstelle und signiere ich mein Serverzertifikat:

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`   

Es funktioniert gut auf Clients, die auf node.js mit https-Anforderungen basieren, aber es funktioniert nicht mit Clients, die auf QT QSsl basieren sslConfiguration.setPeerVerifyMode(QSslSocket::VerifyPeer), wenn wir QSslSocket::VerifyNonees definieren , es sei denn, wir verwenden es, es funktioniert nicht. Wenn wir VerifyNonees verwenden, überprüft unsere App das Peer-Zertifikat nicht, sodass es jedes Zertifikat akzeptiert. Um das Problem zu lösen, muss ich den allgemeinen Namen meines Servers in seinem Zertifikat ändern und seinen Wert für die IP-Adresse ersetzen, auf der mein Server ausgeführt wird.

zum Beispiel:

CN = 127.0.0.1


0

Wenn Sie einer Subdomain vertrauen möchten, z. B. aaa.localhost, machen Sie es bitte nicht so mkcert localhost *.localhost 127.0.0.1, dies funktioniert nicht, da einige Browser keine Wildcard-Subdomain akzeptieren.

Vielleicht versuchen mkcert localhost aaa.localhost 127.0.0.1.


0

Ich habe dies beim Streaming von einer Lambda-Funktion in AWS zu ElasticSearch erhalten. Ich schlug meinen Kopf gegen eine Wand und versuchte es herauszufinden. Am Ende beim Einstellen von request.headers['Host']Ich habe in der https://Domain für ES hinzugefügt - das Ändern in [es-domain-name].eu-west-1.es.amazonaws.com(ohne https: //) hat sofort funktioniert. Unten ist der Code, den ich verwendet habe, um es zum Laufen zu bringen. Hoffentlich kann jeder andere seinen Kopf gegen eine Wand schlagen ...

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);
            }
        );
    });
}

0

Für Entwickler, die die Fetch-API in einer Node.js-App verwenden, ist dies der Grund , warum dies mit reverseUnauthorized funktioniert .

Beachten Sie, dass die Verwendung rejectUnauthorizedgefährlich ist, da sie potenzielle Sicherheitsrisiken birgt, da sie ein problematisches Zertifikat umgeht.

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
}
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.