在Node.js中,tls模块使用OpenSSL来提供TLS/SSL,实现加密过的流通讯。TLS/SSL会在传输层上对网络连接进行加密,防止传输数据被窃听和篡改。tls模块创建的TLS服务器和客户端与net模块相似,但对方法进行了扩展,如:对公钥、私钥和证书的设置等。
tls.Server继承自net.Server,二者在功能上比较相似。但tls.Server创建服务器时,使用的是安全连接。
1. 初始化服务器
初始化服务器可以使用构造函数tls.Server或工厂方法tls.createServer。与创始TCP服务器不同,创建TLS服务器时,需要传服务器私钥和证书等文件。
var tls = require('tls');
var fs = require('fs');
//使用服务端私钥和证书创建服务器
var options = {
key: fs.readFileSync('./ssl/itbilu-key.pem'),
cert: fs.readFileSync('./ssl/itbilu-cert.pem'),
requestCert: true,
// 可接收的客户端自签名证书认证
ca: [ fs.readFileSync('./ssl/client-cert.pem') ]
};
//使用pfx或p12文件创建
/*
var options = {
pfx: fs.readFileSync('./ssl/itbilu.pfx')
};
*/
var server = tls.createServer(options);
在以上示例中,分别从PEM私钥和pfx证书创建了TLS服务端,创建TLS服务端详细参数如下:
tls.createServer(options[, secureConnectionListener])
创建一个新的tls.Server。参数connectionListener会自动设置为secureConnection 事件的监听器,也可以创建服务器后单独添加对该事件的监听。
pfx:包含私钥、证书和服务器的CA证书(PFX 或 PKCS12 格式)字符串或缓存Buffer。(key,cert和ca互斥)。key:包含服务器私钥(PEM 格式)字符串或缓存Buffer。(可以是keys的数组)(必传)。passphrase:私钥或pfx的密码字符串cert:包含服务器证书key(PEM 格式)字符串或缓存Buffer。(可以是certs的数组)(必传)。ca:信任的证书(PEM 格式)的字符串/缓存数组,用来授权连接。如果忽略这个参数,将会使用"root" CAs,如 :VeriSign。crl:不是PEM编码CRLs(Certificate Revocation List,证书撤销列表)的字符串就是字符串列表ciphers:要使用或排除的密码(cipher)字符串ecdhCurve:是否包含ECDH秘钥,或false禁用ECDH。 默认prime256v1,更多可参考 RFC 4492 。dhparam:DH参数文件,用于DHE秘钥。使用openssl dhparam命令来创建。如果加载文件失败,该参数会被抛弃。handshakeTimeout: 握手超时时长,如果SSL/TLS握手事件超过这个参数,会断开连接。超时后,tls.Server对象会触发'clientError' 事件。默认是 120 秒。honorCipherOrder:当选择一个密码(cipher)时,使用服务器配置。该参数默认不可用,配合
ciphers参数连接使用,可减轻 BEAST 攻击。requestCert:如果设为true,服务器会要求连接的客户端发送证书,并尝试验证证书。默认:false。rejectUnauthorized:如果为true,服务器将会拒绝不在 CAs 授权列表内的连接。仅requestCert参数为true时这个参数才有效。默认:false。checkServerIdentity(servername, cert):提供一个重写的方法来检查证书对应的主机名。如果验证失败,返回error;如果验证通过,返回undefined。NPNProtocols:一个包含NPN协议的Buffer数组(协议需按优先级排序)。SNICallback(servername, cb):如果客户端支持SNI TLS扩展会调用这个方法。该方法接受2个参数:servername和cb。SNICallback回调函数格式为cb(null, ctx),其中ctx是SecureContext实例(可以用tls.createSecureContext(...)来获取相应的 SecureContext上下文)。如果SNICallback没有提供,将会使用高级的 API(参见下文).sessionTimeout:整数,设定了服务器创建TLS会话标示符(TLS session identifiers)和TLS会话凭证(TLS session tickets)后的超时时间(单位:秒)。更多请参考:SSL_CTX_set_timeout。ticketKeys:一个 48 字节的Buffer实例。由 16 字节的前缀,16 字节的hmac key,16 字节的AES key组成。可用来接受 tls 服务器实例上的 tls会话凭证(tls session tickets)。注: 自动在集群
cluster进程间共享。sessionIdContext:会话恢复(session resumption)的标示符字符串。如果requestCert为true,则默认值为命令行生成的 MD5 哈希值,否则不提供该参数默认值。secureProtocol:SSL 使用的方法。如:SSLv3_method强制 SSL 版本为3。可传入的值取决于你所安装的 OpenSSL 中的常量,参考:SSL_METHODS。secureOptions:服务器配置项。例如设置SSL_OP_NO_SSLv3可用禁用 SSLv3 协议。所有可用参数请参考:SSL_CTX_set_options
2. 监听连接
TLS服务器与TCP服务器一样,也需要将其绑定到TCP端口或socket套接字上。下面我们使用端口绑定,更多绑定选项可参考net.Server。
在上面创建TLS服务器时,我们没有传入默认的监听方法,因此需要添加对客户端连接事件的监听。当有TLS客户端连接进入时,tls.Server会发射一个'secureConnection'事件,我们可以通过监听此事件来处理客户端请求。
//添加'secureConnection'事件监听
server.on('secureConnection', function (clientStream){
console.log('收到了客户端的连接')
});
//将TLS服务器绑定到3333端口上
server.listen(3333);
3. 与客户端交互数据
在'secureConnection'事件的回调函数中,会传入一个tls.TLSSocket对象实例,该实例与net.Socket实例类似。该实例是一个可读写的Stream流,从客户端读取数据或是向客户端发送数据都,是基于对这个Stream的操作。
server.on('secureConnection', function (tlsSocket){
console.log('收到了客户端的连接')
//tlsSocket是一个Stream,监听'data'事件可查看客户端数据
tlsSocket.on('data', function(data){
console.log('收到客户端数据:%s', data);
});
//向客户端写入数据
tlsSocket.write('Hello client -- from niefengjun.cn')
});
4. 断开连接
调用tls.Server对象的end方法可断开TLS服务器与客户端的连接。与TCP服务器一样,该方法也可以接收一个参数,参数为字符串或缓冲区Buffer,这些数据将在发送完毕后断开连接。
server.on('secureConnection', function (tlsSocket){
console.log('收到了客户端的连接')
//tlsSocket是一个Stream,监听'data'事件可查看客户端数据
tlsSocket.on('data', function(data){
//客户端发来exit时,将断开服务器与客户端的连接
if(data.toString().trim().toLowerCase() === 'exit'){
server.end('bye ~ ');
} else {
console.log('收到客户端数据:%s', data);
}
});
//向客户端写入数据
tlsSocket.write('Hello client -- from niefengjun.cn')
});
5. 运行服务端
完整代码整理如下:
var tls = require('tls');
var fs = require('fs');
//使用服务端私钥和证书创建服务器
var options = {
key: fs.readFileSync('./ssl/itbilu-key.pem'),
cert: fs.readFileSync('./ssl/itbilu-cert.pem'),
requestCert: true,
// 可接收的客户端自签名证书认证
ca: [ fs.readFileSync('./ssl/client-cert.pem') ]
};
//使用pfx或p12文件创建
/*
var options = {
pfx: fs.readFileSync('./ssl/itbilu.pfx')
};
*/
var server = tls.createServer(options);
//添加'secureConnection'事件监听
server.on('secureConnection', function (tlsSocket){
console.log('收到了客户端的连接,该连接:',
tlsSocket.authorized ? '已认证' : '未认证');
//tlsSocket是一个Stream,监听'data'事件可查看客户端数据
tlsSocket.on('data', function(data){
//客户端发来exit时,将断开服务器与客户端的连接
if(data.toString().trim().toLowerCase() === 'exit'){
tlsSocket.end('bye ~ ');
} else {
console.log('收到客户端数据:%s', data);
}
});
//向客户端写入数据
tlsSocket.write('Hello client -- from niefengjun.cn')
});
//tls.Server继承自net.Server,所在'connection'事件依然可用
server.on('connection', function (socket){
console.log('收到非安全连接')
});
//将TLS服务器绑定到3333端口上
server.listen(3333, function() {
console.log('TLS 服务器已绑定');
});
将以上代码保存运行后,可以通过 openssl s_client来连接服务器测试:
openssl s_client -connect 127.0.0.1:3333
