Node.Js TLS(SSL) HTTPS双向验证

来源:互联网 发布:小学生的网络安全教育 编辑:程序博客网 时间:2024/06/05 19:53

考虑到数据传输的安全及保密,决定采用TLS(SSL)协议,既节省了设计安全协议的时间,也容易与外部系统协同工作。作为工业标准的TLS(SSL)协议已经有许多成熟的解决方案,故不打算自行开发,而是使用开源社区中广泛使用的OpenSSL。

由于TLS(SSL)是基于非对称的加密体系,所以在开发前需要准备用于加密解密及验证的私钥及数字证书。这里分别为CA、服务器、客户端分别准备1套密钥及证书。

CA机构的数字证书是整个TLS(SSL)协议的根证书,用于给服务器及客户端证书签名及验证其真伪。由于不作商业用途,不打算使用权威CA机构的数字证书,而且是使用自签名的CA证书。生成方法如下:

    openssl genrsa -out ca-key.pem -des 1024    openssl req -new -key ca-key.pem -out ca-csr.pem    openssl x509 -req -days 3650 -in ca-csr.pem -signkey ca-key.pem -out ca-cert.pem

执行上述3个命令之后,得到3个文件,其中“ca-key.pem”为CA的私钥,“ca-cert.pem”为CA的自签名证书。有了CA证书和私钥之后就可以使用它来签发服务器及客户端的证书了。如果想查看生成证书的详细信息,可以使用以下命令查看:

openssl x509 -noout -text -in ca-cert.pem

在TLS(SSL)协议中,客户端在与服务器连接时,除需要使用CA证书来验证服务器证书的真伪之外,还需要验证服务器证书是否与服务器域名或IP地址相匹配。但由于服务器可能有多个IP地址及多个DNS服务器,所以在生成证书前需要对其进行设置,这里将设置写入一个文件中,方便之后生成证书的工作,配置内容文件如下:

[req]    distinguished_name = req_distinguished_name    req_extensions = v3_req    [req_distinguished_name]    countryName = Country Name (2 letter code)    countryName_default = CN    stateOrProvinceName = State or Province Name (full name)    stateOrProvinceName_default = BeiJing    localityName = Locality Name (eg, city)    localityName_default = YaYunCun    organizationalUnitName= Organizational Unit Name (eg, section)    organizationalUnitName_default= Domain Control Validated    commonName = Internet Widgits Ltd    commonName_max= 64    [ v3_req ]    # Extensions to add to a certificate request    basicConstraints = CA:FALSE    keyUsage = nonRepudiation, digitalSignature, keyEncipherment    subjectAltName = @alt_names    [alt_names]    DNS.1 = ns1.dns.com    DNS.2 = ns2.dns.com    DNS.3 = ns3.dns.com    IP.1 = 192.168.1.84    IP.2 = 127.0.0.1    IP.3 = 127.0.0.2    
“req_distinguished_name”部分的内容就是证书的主体内容,包括证书持有者的相关信息。重点是“alt_name”部分的内容,其中的“DNS.1”所指定的是使用该证书的主机所使用的DNS,还有“IP.1”所指定的是使用该证书的主机的IP地址,这里可以指定多个DNS和IP,方便有多DNS和多IP的主机能够使用同一个该证书来完成验证。

假设上述配置文件为“openssl.cnf”,则生成服务器私钥及证书的方法如下:

openssl genrsa -out server-key.pem 1024openssl req -new -key server-key.pem -config openssl.cnf -out server-csr.pemopenssl x509 -req -days 730 -CA ca-cert.pem -CAkey ca-key.pem -CAcreateserial -in server-csr.pem -out server-cert.pem -extensions v3_req -extfile openssl.cnf

如果查看刚才生成的服务器证书,可以看到设置的DNS及IP信息。还可以将服务器私钥、证书以及CA证书打包成一个单独的.pfx或.p12文件:

openssl pkcs12 -export -in server-cert.pem -inkey server-key.pem -certfile ca-cert.pem -out server.pfx
openssl pkcs12 -export -in client-cert.pem -inkey client-key.pem -certfile ca-cert.pem -out client.p12    

这样携带和使用都更加方便。最后就是生成客户端证书:

openssl genrsa -out client-key.pemopenssl req -new -key client-key.pem -out client-csr.pemopenssl x509 -req -days 365 -CA ca-cert.pem -CAkey ca-key.pem -CAcreateserial -in client-csr.pem -out client-cert.pem

上面所生成的证书中,CA证书的有效期是10年,服务器证书有效期是2年,客户端证书有效期是1年。


测试:

openssl s_client -connect 127.0.0.1:8000 -cert client-cert.pem -key client-key.pem -tls1 -CAfile ca-cert.pem -state -showcerts

TLS(SSL)方式


tls服务器程序:

var tls = require('tls');    var fs = require('fs');    var options = {        key: fs.readFileSync('../key/server-key.pem'),        cert: fs.readFileSync('../key/server-cert.pem'),        // This is necessary only if using the client certificate authentication.        requestCert: true,        rejectUnauthorized: true,//    passphrase:'test',        // This is necessary only if the client uses the self-signed certificate.        ca: [ fs.readFileSync('../key/ca-cert.pem') ]        };    var server = tls.createServer(options, function(cleartextStream) {        console.log('server connected',            cleartextStream.authorized ? 'authorized' : 'unauthorized');        cleartextStream.write('this message is come from server!');        cleartextStream.setEncoding('utf8');        cleartextStream.pipe(cleartextStream);        cleartextStream.on('data', function(data) {        console.log(data);        });    });    server.listen(8000, function() {        console.log('server bound');        });    

tls客户端程序:

var tls = require('tls');    var fs = require('fs');    var options = {        // These are necessary only if using the client certificate authentication        key: fs.readFileSync('../key/client-key.pem'),        cert: fs.readFileSync('../key/client-cert.pem'),        rejectUnauthorized: true,        // This is necessary only if the server uses the self-signed certificate        ca: [ fs.readFileSync('../key/ca-cert.pem') ]        };    var cleartextStream = tls.connect(8000, '127.0.0.1', options, function() {        console.log('client connected',            cleartextStream.authorized ? 'authorized' : 'unauthorized');        cleartextStream.setEncoding('utf8');        if(!cleartextStream.authorized){        console.log('cert auth error: ', cleartextStream.authorizationError);        }    //    console.log(cleartextStream.getPeerCertificate());    });    cleartextStream.setEncoding('utf8');    cleartextStream.on('data', function(data) {        console.log(data);        cleartextStream.write('Hello,this message is come from client!');        cleartextStream.end();        });    cleartextStream.on('end', function() {        console.log('disconnected');        });    cleartextStream.on('error', function(exception) {        console.log(exception);        });

先运行服务器,再运行客户端,就可以看到相互发送的信息对方的控制台上显示出来,表明连接成功。由于服务器和客户端程序中都指定了“rejectUnauthorized: true”,即如果证书验证失败则拒绝连接。所以如果服务器能收到客户端的信息,而客户端又能收到服务器的信息,则表明双向验证成功。

使用https方式

https服务器端程序(express.js):

var express = require('express')    ,fs=require('fs');    var options = {        key: fs.readFileSync('../key/server-key.pem'),        cert: fs.readFileSync('../key/server-cert.pem'),        ca: [ fs.readFileSync('../key/ca-cert.pem') ],        requestCert:        true,        rejectUnauthorized: false        };    var app = module.exports = express.createServer(options);    // Configuration    app.configure(function(){        app.use(app.router);        });    app.configure('development', function(){        app.use(express.errorHandler({ dumpExceptions: true, showStack: true }));    });    app.configure('production', function(){        app.use(express.errorHandler());        });    // Routes    app.all('/', function(req, res){        if (req.client.authorized) {        res.writeHead(200, {"Content-Type":"application/json"});    res.end('{"status":"authorized"}');    //        console.log(req.client);    console.log("Authorized Client ", req.client.socket.remoteAddress);    } else {        res.writeHead(401, {"Content-Type":"application/json"});    res.end('{"status":"denied"}');    console.log("Denied Client " , req.client.socket.remoteAddress);    }    });    app.listen(5558);    console.log("Express server listening on port %d in %s mode", app.address().port, app.settings.env);    //curl -v -s -k --key key/client-key.pem --cert key/client-cert.pem https://localhost:5558

参考:http://www.hacksparrow.com/express-js-https-server-client-example.html


https客户端程序:(client.js)
var https = require('https');    var fs = require('fs');    var options = {        host: '192.168.1.84',        port: 5558,        path: '/',        method: 'GET',        // These are necessary only if using the client certificate authentication        key: fs.readFileSync('../key/client-key.pem'),        cert: fs.readFileSync('../key/client-cert.pem'),        rejectUnauthorized: true,        // This is necessary only if the server uses the self-signed certificate        ca: [ fs.readFileSync('../key/ca-cert.pem') ],        agent: false        };    var req = https.request(options, function(res) {        console.log('server authorize status: '+res.connection.authorized);        res.on('data', function(d) {        console.log('client authorize status: '+ d);        });    });    req.end();    req.on('error', function(e) {        console.error(e);        });
注意:agent: false   这个属性一定要加上,不然证书不起作用

原创粉丝点击