2012-12-30 41 views
17

Sto tentando di creare un'installazione server/client TLS utilizzando Node.js 0.8.8 con un certificato autofirmato.Nome host/IP non corrisponde al nome alternativo del certificato

Il codice del server essenziale sembra

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

Ora, quando provo a connettermi al server io uso il seguente codice:

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

Se rimuovo la linea

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

dal codice lato client, Node.js genera un errore che mi dice DEPTH_ZERO_SELF_SIGNED_CERT. Per quanto ne so, questo è dovuto al fatto che si tratta di un certificato autofirmato e che non esiste un'altra parte che si fida di questo certificato.

Se rimuovo

rejectUnauthorized: true, 

così, l'errore è andato - ma connection.authorized è uguale a false che significa efficacemente che la mia connessione non è crittografata. Ad ogni modo, usando getPeerCertificate() posso accedere al certificato inviato dal server. Poiché voglio applicare una connessione crittografata, capisco che non posso rimuovere questa linea.

Ora ho letto che posso usare la proprietà ca per specificare qualsiasi CA di cui voglio che Node.js consideri attendibile. Il numero documentation of the TLS module implica che è sufficiente aggiungere il certificato del server all'array ca e quindi tutto dovrebbe andare bene.

Se lo faccio, questo errore è andato, ma ho uno nuovo:

Hostname/IP doesn't match certificate's altnames 

Per me questo significa che il CA è ormai sostanzialmente attendibile, quindi va bene ora, ma il certificato è stato fatto per un host diverso da quello che uso.

ho creato il certificato utilizzando

$ 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 

come suggerisce la documentazione. Quando creo il CSR mi vengono poste le solite domande, come per paese, stato, ... e nome comune (CN). Come ti viene detto "on the web" per un certificato SSL che fai non inserisci il tuo nome come CN, ma il nome host che desideri utilizzare.

E questo è probabilmente il punto in cui non riesco.

ho cercato

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

in cui gli ultimi due sono il nome locale e il nome locale completo della mia macchina.

Qualche idea su cosa sto facendo male qui?

+0

Nota importante: questo è 'Nodo v0.8.8'. 'rejectUnauthorized' ora è impostato su' true'. – Breedly

+0

La connessione è [ancora crittografata] (https://stackoverflow.com/a/16311147/4068278) per impostazione predefinita se si utilizza 'rejectUnauthorized: false'. Semplicemente non verifica l'identità dell'host a cui ti stai connettendo, il che potrebbe renderti vulnerabile agli attacchi MITM su SSL. –

risposta

13

Recentemente c'era uno addition to node.js che consente di sovrascrivere il controllo hostname con una funzione personalizzata. È stato aggiunto alla v0.11.14 e sarà disponibile nella prossima versione stabile (0.12). Ora si può fare qualcosa di simile:

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

Questo sarà ora accettare qualsiasi identità del server, ma ancora crittografare la connessione e verificare le chiavi.

Nota che nelle versioni precedenti (ad esempio v0.11.14), il checkServerIdentity è stato quello di restituire un boolean che indica la validità del server. Ciò è stato modificato (prima dello v4.3.1) nella funzione return (non throw ing), se c'è un problema e undefined se è valido.

+0

Essendo bloccato in v0.10, ho [scimmia-patch] (https://gist.github.com/ntrrgc/c5cbff08d0033e00f503) 'tls' modulo per saltare questo controllo. È una soluzione molto brutta in quanto è globale, ma è ancora meglio di niente. – Alicia

+6

set NODE_TLS_REJECT_UNAUTHORIZED = 0 env var –

+6

Solo per avvisare le persone: 'NODE_TLS_REJECT_UNAUTHORIZED' consente di connettersi a chiunque, indipendentemente dal fatto che sia il server previsto o meno. La connessione è crittografata, ma non è * autorizzata *, che è spesso una cosa molto critica. E impostarlo come variabile globale è un metodo piuttosto pesante. Basta impostare 'rejectUnauthorized' su false nelle opzioni di connessione' tls'. – Breedly

8

In tls.js, lines 112-141, è possibile notare che se il nome host utilizzato quando si chiama connect è un indirizzo IP, il CN del certificato viene ignorato e vengono utilizzate solo le SAN.

Poiché il mio certificato non utilizza SAN, la verifica ha esito negativo.

+0

Sto avendo lo stesso problema, ma ho SAN per localhost e 127.0.0.1. Non importa se mi collego a hostname ('localhost') o indirizzo IP ('127.0.0.1'). Per me, openssl s_client si connette, dato tutti gli stessi parametri, mentre tls.connect() fallisce. – Bryan

0

Quello che stai facendo male è usare un indirizzo IP invece di un nome di dominio. Creare un nome di dominio e incollarlo in un server DNS (o solo in un file host), creare un certificato autofirmato con il nome di dominio come Nome comune e connettersi al nome del dominio anziché all'indirizzo IP.

+4

Ma dovrebbe (deve?) Funzionare anche con indirizzi IP, non dovrebbe? Non voglio impostare nomi di dominio, per vari motivi. –

7

Se si sta utilizzando un nome host per la connessione, il nome host verrà confrontato con il Nome alternativo oggetto del tipo DNS, se presente, e riporterà sulla CN nel Nome distinto oggetto in caso contrario.

Se si sta utilizzando un indirizzo IP per la connessione, l'indirizzo IP verrà confrontato con le SAN del tipo indirizzo IP, senza ricorrere al CN.

Questo è almeno l'implementazione conforme alla specifica HTTP over TLS (ad esempio HTTPS). Alcuni browser sono un po 'più tolleranti.

Questo è esattamente lo stesso problema di this answer in Java, che fornisce anche un metodo per inserire SAN personalizzate tramite OpenSSL (vedere this document too).

In generale, a meno che non si tratti di una CA di prova, è piuttosto difficile gestire i certificati che si basano su indirizzi IP. La connessione con un nome host è migliore.

4

Mitar aveva erroneamente assunto che checkServerIdentity restituisse 'true' in caso di successo, ma in realtà dovrebbe restituire 'indefinito' in caso di successo. Qualsiasi altro valore viene considerato come una descrizione di errore.

Quindi un tale codice è corretto:

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

ho cercato solo di fare modificare nella risposta del Mitar ma la modifica è stata respinta, così ho creato una risposta separata.