J'essaie de créer une configuration de serveur/client TLS à l'aide de Node.js 0.8.8 avec un certificat auto-signé.
Le code serveur essentiel ressemble à
var tlsServer = tls.createServer({
key: fs.readFileSync('server-key.pem'),
cert: fs.readFileSync('server-cert.pem')
}, function (connection) {
// [...]
});
tlsServer.listen(3000);
Maintenant, lorsque j'essaie de me connecter à ce serveur, j'utilise le code suivant:
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());
});
Si je supprime la ligne
ca: [ fs.readFileSync('server-cert.pem') ]
à partir du code côté client, Node.js génère une erreur me disant DEPTH_ZERO_SELF_SIGNED_CERT
. Si je comprends bien, cela est dû au fait qu'il s'agit d'un certificat auto-signé et qu'aucune autre partie ne fait confiance à ce certificat.
Si je retire
rejectUnauthorized: true,
de plus, l'erreur a disparu - mais connection.authorized
est égal à false
ce qui signifie effectivement que ma connexion n'est pas chiffrée. Quoi qu'il en soit, en utilisant getPeerCertificate()
je peux accéder au certificat envoyé par le serveur. Comme je souhaite appliquer une connexion cryptée, je comprends que je ne peux pas supprimer cette ligne.
Maintenant, je lis que je peux utiliser la propriété ca
pour spécifier toute autorité de certification à laquelle je veux que Node.js fasse confiance. Le documentation du module TLS implique qu'il suffit d'ajouter le certificat de serveur au tableau ca
, et alors tout devrait bien se passer.
Si je fais cela, cette erreur a disparu, mais j'en reçois une nouvelle:
Hostname/IP doesn't match certificate's altnames
Pour moi, cela signifie que l'autorité de certification est désormais fondamentalement approuvée, ce qui ne pose aucun problème, mais le certificat a été créé pour un autre hôte que celui que j'utilise.
J'ai créé le certificat en utilisant
$ 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
comme l'indique la documentation. Lors de la création de la CSR, on me pose les questions habituelles, telles que le pays, l'état, ... et le nom commun (CN). Comme on vous dit "sur le Web" pour un certificat SSL, vous ne fournissez pas votre nom en tant que CN, mais le nom d'hôte que vous souhaitez utiliser.
Et c'est probablement là que j'échoue.
J'ai essayé
localhost
192.168.178.31
eisbaer
eisbaer.fritz.box
où les deux derniers sont le nom local et le nom local complet de ma machine.
Une idée de ce que je fais mal ici?
Récemment, il y avait un ajout à node.js qui permet de remplacer la vérification du nom d'hôte avec une fonction personnalisée. Il a été ajouté à la v0.11.14 et sera disponible dans la prochaine version stable (0.12). Vous pouvez maintenant faire quelque chose comme:
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) {
//...
});
Cela acceptera désormais toute identité de serveur, mais chiffrera toujours la connexion et vérifiera les clés.
Notez que dans les versions précédentes (par exemple v0.11.14
), le checkServerIdentity
devait renvoyer un boolean
indiquant la validité du serveur. Cela a été changé (avant v4.3.1
) à la fonction return
ing (pas throw
ing) une erreur s'il y a un problème et undefined
s'il y a c'est valide.
Dans tls.js, lignes 112-141 , vous pouvez voir que si le nom d'hôte utilisé lors de l'appel de connect
est une adresse IP, le CN du certificat est ignoré et seuls les SAN sont en cours utilisé.
Comme mon certificat n'utilise pas de SAN, la vérification échoue.
Si vous utilisez un nom d'hôte pour vous connecter, le nom d'hôte sera comparé aux autres noms de sujet du type DNS, le cas échéant, et retombera sur le CN dans le nom distinctif du sujet dans le cas contraire.
Si vous utilisez une adresse IP pour vous connecter, l'adresse IP sera vérifiée par rapport aux SAN de type adresse IP , sans retomber sur le CN .
C'est au moins ce que font les implémentations conformes à la spécification HTTP sur TLS (c'est-à-dire HTTPS). Certains navigateurs sont un peu plus tolérants.
C'est exactement le même problème que dans cette réponse en Java , qui donne également une méthode pour mettre des SAN personnalisés via OpenSSL (voir ce document aussi ).
De manière générale, à moins qu'il ne s'agisse d'une autorité de certification de test, il est assez difficile de gérer les certificats qui dépendent des adresses IP. La connexion avec un nom d'hôte est meilleure.
Mitar avait une fausse hypothèse selon laquelle checkServerIdentity devrait retourner "vrai" en cas de succès, mais en réalité, il devrait retourner "non défini" en cas de succès. Toutes les autres valeurs sont traitées comme des descriptions d'erreur.
Un tel code est donc 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) {
//...
});
J'ai juste essayé de faire une modification dans la réponse de Mitar mais la modification a été rejetée, j'ai donc créé une réponse séparée.