Node.js tls模块使用OpenSSL实现TLS/SSL安全通讯--构建TLS服务器

 2015年09月04日    1496     声明


在Node.js中,tls模块使用OpenSSL来提供TLS/SSL,实现加密过的流通讯。TLS/SSL会在传输层上对网络连接进行加密,防止传输数据被窃听和篡改。tls模块创建的TLS服务器和客户端与net模块相似,但对方法进行了扩展,如:对公钥、私钥和证书的设置等。

  1. 初始化服务器
  2. 监听连接
  3. 与客户端交互数据
  4. 断开连接
  5. 运行服务端


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, certca互斥)。

  • 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

  • dhparamDH参数文件,用于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个参数:servernamecbSNICallback回调函数格式为cb(null, ctx),其中ctxSecureContext实例(可以用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)的标示符字符串。如果 requestCerttrue,则默认值为命令行生成的 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 itbilu.com')
});


4. 断开连接

调用tls.TLSSockt对象的end方法可断开TLS服务器与客户端的连接。与TCP Scoket一样,该方法也可以接收一个参数,参数为字符串或缓冲区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 itbilu.com')
});


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 itbilu.com')
});

//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


构建tls客户端,敬请期待……