stability: 3 - stable
node.js可以使用require('tls')
来访问tls/ssl模块:
const tls = require('tls');
tls
模块使用openssl来提供传输层安全性(transport layer security,tls)和安全套接层(secure socket layer,ssl):加密过的流通讯。
tls/ssl是一种公钥/私钥基础架构,每个客户端和服务端都需要一个私钥。私钥的创建方法如下:
openssl genrsa -out ryans-key.pem 2048
你还需要为所有服务器和某些客户端添加证书。证书由认证中心(certificate authority)签名,或者自签名。获得证书第一步是创建一个证书签名请求"certificate signing request" (csr)文件。证书的创建方法如下:
openssl req -new -sha256 -key ryans-key.pem -out ryans-csr.pem
使用csr创建一个自签名的证书:
openssl x509 -req -in ryans-csr.pem -signkey ryans-key.pem -out ryans-cert.pem
或者你可以发送csr给认证中心(certificate authority)来签名。
(todo: 创建ca的文档,感兴趣的读者可以在node源码test/fixtures/keys/makefile
里查看)
创建.pfx或.p12,可以这么做:
openssl pkcs12 -export -in agent5-cert.pem -inkey agent5-key.pem \
-certfile ca-cert.pem -out agent5.pfx
in
: 签名证书inkey
: 关联的私钥certfile
: 是将所有证书颁发机构 (ca) 证书连接到单个文件中,例如,cat ca1-cert.pem ca2-cert.pem > ca-cert.pem
node.js默认遵循sslv2和sslv3协议,不过这些协议被禁用。因为他们不太可靠,很容易受到威胁,参见cve-2014-3566。某些情况下,旧版本客户端/服务器(比如 ie6)可能会产生问题。如果你想启用sslv2或sslv3 ,使用参数--enable-ssl2
或--enable-ssl3
运行node。node.js的未来版本中不会再默认编译sslv2 和sslv3。
有一个办法可以强制node进入仅使用sslv3或sslv2模式,分别指定secureprotocol
为'sslv3_method'
或'sslv2_method'
。
node.js使用的默认协议方法准确名字是autonegotiate_method
, 这个方法会尝试并协商客户端支持的从高到底协议。为了提供默认的安全级别,node.js(v0.10.33 版本之后)通过将secureoptions
设为ssl_op_no_sslv3|ssl_op_no_sslv2
,明确的禁用了sslv3和sslv2(除非你给secureprotocol
传值--enable-ssl3
,或--enable-ssl2
,或sslv3_method
)。
如果你设置了secureoptions
,我们不会重新这个参数。
改变这个行为的后果:
sslv3
客户端不能协商建立连接,会被拒绝。这种情况下,你的服务器会触发clienterror
事件。错误消息会包含错误版本数字('wrong version number'
)。clienterror
事件。错误消息会包含错误版本数字('wrong version number'
)。tls协议让客户端协商tls会话的某些方法内容。但是,会话协商需要服务器端响应的资源,这回让它成为阻断服务攻击(denial-of-service attacks)的潜在媒介。
为了降低这种情况的发生,重新协商被限制为每10分钟3次。当超出这个界限时,在tls.tlssocket实例上会触发错误。这个限制可设置:
tls.client_reneg_limit
: 重新协商limit,默认是3。
tls.client_reneg_window
: 重新协商窗口的时间,单位秒,默认是10分钟。除非你明确知道自己在干什么,否则不要改变默认值。
要测试你的服务器的话,使用openssl s_client -connect address:port
连接服务器,并敲r<cr>
(字母 r 键加回车)几次。
npn(next protocol negotiation 下次协议协商)和sni (server name indication 域名指示)都是tls握手扩展:
"forward secrecy"或"perfect forward secrecy-完全正向保密"协议描述了秘钥协商(比如秘钥交换)方法的特点。实际上这意味着及时你的服务器的秘钥有危险,通讯仅有可能被一类人窃听,他们必须设法获的每次会话都会生成的秘钥对。
完全正向保密是通过每次握手时为秘钥协商随机生成密钥对来完成(和所有会话一个key相反)。实现这个技术(提供完全正向保密-perfect forward secrecy)的方法被称为"ephemeral"。
通常目前有2个方法用于完成完全正向保密(perfect forward secrecy):
短暂(ephemeral)方法有性能缺点,因为生成 key 非常耗费资源。
返回支持的ssl密码名数组。
例子:
var ciphers = tls.getciphers();
console.log(ciphers); // ['aes128-sha', 'aes256-sha', ...]
创建一个新的tls.server。参数connectionlistener
会自动设置为secureconnection事件的监听器。参数options
对象有以下可能性:
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)字符串
为了减轻beast attacks ,推荐使用这个参数和之后会提到的honorcipherorder
参数来优化non-cbc密码(cipher)
默认:ecdhe-rsa-aes128-sha256:dhe-rsa-aes128-sha256:aes128-gcm-sha256:rc4:high:!md5:!anull
。格式上更多细节参见openssl cipher list format documentation
ecdhe-rsa-aes128-sha256
, dhe-rsa-aes128-sha256
和aes128-gcm-sha256
都是tls v1.2密码(cipher),当node.js连接openssl 1.0.1或更早版本(比如)时使用。注意,honorcipherorder
设置为enabled后,现在仍然可以和tls v1.2客户端协商弱密码(cipher),
rc4
可作为客户端和老版本tls协议通讯的备用方法。rc4
这些年受到怀疑,任何对信任敏感的对象都会考虑其威胁性。国家级别(state-level)的参与者拥有中断它的能力。
注意: 早些版本的修订建议,aes256-sha
作为可以接受的密码(cipher).unfortunately,aes256-sha
是一个cbc密码(cipher),容易受到beast attacks 攻击,不要使用它。
ecdhcurve
: 包含用来ecdh秘钥交换弧形(curve)名字符串,或者false禁用ecdh。
默认prime256v1
。更多细节参考rfc 4492 。
dhparam
: dh参数文件,用于dhe秘钥协商。使用openssl dhparam
命令来创建。如果加载文件失败,会悄悄的抛弃它。
handshaketimeout
: 如果ssl/tls握手事件超过这个参数,会放弃里连接。默认是120秒.
握手超时后,tls.server
对象会触发'clienterror
'事件。
honorcipherorder
: 当选择一个密码(cipher)时,使用服务器配置,而不是客户端的。
虽然这个参数默认不可用,还是推荐你用这个参数,和ciphers
参数连接使用,减轻beast攻击。
注意,如果使用了sslv2,服务器会发送自己的配置列表给客户端,客户端会挑选密码(cipher)。默认不支持sslv2,除非node.js配置了./configure --with-sslv2
。
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
module)工作进程间共享。
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响应服务器的简单例子:
var tls = require('tls');
var fs = require('fs');
var options = {
key: fs.readfilesync('server-key.pem'),
cert: fs.readfilesync('server-cert.pem'),
// this is necessary only if using the client certificate authentication.
requestcert: true,
// this is necessary only if the client uses the self-signed certificate.
ca: [ fs.readfilesync('client-cert.pem') ]
};
var server = tls.createserver(options, function(socket) {
console.log('server connected',
socket.authorized ? 'authorized' : 'unauthorized');
socket.write("welcome!\n");
socket.setencoding('utf8');
socket.pipe(socket);
});
server.listen(8000, function() {
console.log('server bound');
});
或者:
var tls = require('tls');
var fs = require('fs');
var options = {
pfx: fs.readfilesync('server.pfx'),
// this is necessary only if using the client certificate authentication.
requestcert: true,
};
var server = tls.createserver(options, function(socket) {
console.log('server connected',
socket.authorized ? 'authorized' : 'unauthorized');
socket.write("welcome!\n");
socket.setencoding('utf8');
socket.pipe(socket);
});
server.listen(8000, function() {
console.log('server bound');
});
你可以通过openssl s_client
连接服务器来测试:
openssl s_client -connect 127.0.0.1:8000
创建一个新的客户端连接到指定的端口和主机(port
and host
)(老版本api),或者options.port
和options.host
(如果忽略host
,默认为 localhost
)。options
是一个包含以下值得对象:
host
: 客户端需要连接到的主机。
port
: 客户端需要连接到的端口。
socket
: 在指定的socket(而非新建)上建立安全连接。如果这个参数有值,将忽略host
和port
参数。
path
: 创建 到参数path
的unix socket连接。如果这个参数有值,将忽略host
和port
参数。
pfx
: 包含私钥,证书和客户端(pfx或pkcs12格式)的ca证书的字符串或buffer
缓存。
key
: 包含客户端(pem格式)的 私钥的字符串或buffer
缓存。可以是keys数组。
passphrase
: 私钥或pfx的密码字符串。
cert
: 包含客户端证书key(pem格式)字符串或缓存buffer
。(可以是certs的数组)。
ca
: 信任的证书(pem格式)的字符串/缓存数组。如果忽略这个参数,将会使用"root" cas ,比如verisign。用来授权连接。
rejectunauthorized
: 如果为true
,服务器证书根据cas列表授权列表验证。如果验证失败,触发'error'
事件;err.code
包含openssl错误代码。默认:true
。
npnprotocols
: npn协议的字符串或buffer
数组。buffer
必须有以下格式0x05hello0x05world
,第一个字节是下一个协议名字的长度。(传的数组通常非常简单,比如:['hello', 'world']
)。
servername
: sni(域名指示 server name indication) tls扩展的服务器名。
secureprotocol
: ssl使用的方法,例如,sslv3_method
强制ssl版本为3。可能的值定义于你所安装的openssl中的常量ssl_methods。
session
: 一个buffer
实例,包含tls会话.将参数callback
添加到'secureconnect'事件上,其效果如同监听器。
tls.connect()
返回一个tls.tlssocket对象。
这是一个简单的客户端应答服务器例子:
var tls = require('tls');
var fs = require('fs');
var options = {
// these are necessary only if using the client certificate authentication
key: fs.readfilesync('client-key.pem'),
cert: fs.readfilesync('client-cert.pem'),
// this is necessary only if the server uses the self-signed certificate
ca: [ fs.readfilesync('server-cert.pem') ]
};
var socket = tls.connect(8000, options, function() {
console.log('client connected',
socket.authorized ? 'authorized' : 'unauthorized');
process.stdin.pipe(socket);
process.stdin.resume();
});
socket.setencoding('utf8');
socket.on('data', function(data) {
console.log(data);
});
socket.on('end', function() {
server.close();
});
或者:
var tls = require('tls');
var fs = require('fs');
var options = {
pfx: fs.readfilesync('client.pfx')
};
var socket = tls.connect(8000, options, function() {
console.log('client connected',
socket.authorized ? 'authorized' : 'unauthorized');
process.stdin.pipe(socket);
process.stdin.resume();
});
socket.setencoding('utf8');
socket.on('data', function(data) {
console.log(data);
});
socket.on('end', function() {
server.close();
});
net.socket实例的封装,取代内部socket读写程序,执行透明的输入/输出数据的加密/解密。
从现有的tcp socket里构造一个新的tlssocket对象。
socket
一个net.socket的实例
options
一个包含以下属性的对象:
securecontext
: 来自tls.createsecurecontext( ... )
的可选tls上下文对象。
isserver
: 如果为true, tls socket将会在服务器模式(server-mode)初始化。
server
: 一个可选的net.server实例
requestcert
: 可选的,参见tls.createsecurepair
rejectunauthorized
: 可选的,参见tls.createsecurepair
npnprotocols
: 可选的,参见tls.createserver
snicallback
: 可选的,参见tls.createserver
session
: 可选的,一个buffer
实例,包含tls会话
requestocsp
: 可选的,如果为true
- ocsp状态请求扩展将会被添加到客户端hello,并且ocspresponse
事件将会在socket上建立安全通讯前触发。创建一个凭证(credentials)对象,包含字典有以下的key:
pfx
: 包含pfx或pkcs12加密的私钥,证书和服务器的ca证书(pfx或pkcs12格式)字符串或缓存buffer
。(key
,cert
和ca
互斥)。key
: 包含pem加密过的私钥的字符串。passphrase
: 私钥或pfx的密码字符串。 cert
: 包含pem加密过的证书的字符串。ca
: 信任的pem加密过的可信任的证书(pem格式)字符串/缓存数组。 crl
:pem加密过的crls(证书撤销列表)ciphers
: 要使用或排除的密码(cipher)字符串。更多格式上的细节参见http://www.openssl.org/docs/apps/ciphers.html#cipher_list_formathonorcipherorder
: 当选择一个密码(cipher) 时, 使用服务器配置,而不是客户端的。 更多细节参见tls
模块文档。如果没给 'ca' 细节,node.js 将会使用默认的公开信任的 cas 列表(参见http://mxr.mozilla.org/mozilla/source/security/nss/lib/ckfw/builtins/certdata.txt)。
创建一个新的安全对(secure pair)对象,包含2个流,其中一个读/写加密过的数据,另外一个读/写明文数据。通常加密端数据来自是从输入的加密数据流,另一端被当做初始加密流。
credentials
: 来自tls.createsecurecontext( ... )的安全上下文对象。
isserver
: 是否以服务器/客户端模式打开这个tls连接。
requestcert
: 是否服务器需要连接的客户端发送证书。仅适用于服务端连接。
rejectunauthorized
:非法证书时,是否服务器需要自动拒绝客户端。启用requestcert
后,才适用于服务器。tls.createsecurepair()
返回一个安全对(securepair)对象,包含明文cleartext
和密文encrypted
流 。
注意: cleartext
和tls.tlssocket拥有相同的 api。
通过tls.createsecurepair返回。
一旦安全对(securepair)成功建立一个安全连接,安全对(securepair)将会触发这个事件。
和检查服务器'secureconnection'事件一样,pair.cleartext.authorized必须检查确认是否适用的证书是授权过的。
这是net.server
的子类,拥有相同的方法。这个类接受适用tls或ssl的加密连接,而不是接受原始tcp连接。
function (tlssocket) {}
新的连接握手成功后回触发这个事件。参数是tls.tlssocket实例。它拥有常用的流方法和事件。
socket.authorized
是否客户端被证书(服务器提供)授权。如果socket.authorized
为false,socket.authorizationerror
是如何授权失败。值得一提的是,依赖于tls服务器的设置,你的未授权连接可能也会被接受。socket.authorizationerror
如何授权失败。值得一提的是,依赖于tls服务器的设置,你的未授权连接可能也会被接受。socket.npnprotocol
包含选择的npn协议的字符串。socket.servername
包含sni请求的服务器名的字符串。
function (exception, tlssocket) { }
在安全连接建立前,客户端连接触发'error'事件会转发到这里来。
tlssocket
是tls.tlssocket,错误是从这里触发的。
function (sessionid, sessiondata, callback) { }
创建tls会话的时候会触发。可能用来在外部存储器里存储会话。callback
必须最后调用,否则没法从安全连接发送/接收数据。
注意: 添加这个事件监听器仅会在连接连接时有效果。
function (sessionid, callback) { }
当客户端想恢复之前的tls会话时会触发。事件监听器可能会使用sessionid
到外部存储器里查找,一旦结束会触发callback(null, sessiondata)
。如果会话不能恢复(比如不存在这个会话),可能会调用callback(null, null)
。调用callback(err)
将会终止连接,并销毁socket。
注意: 添加这个事件监听器仅会在连接连接时有效果。
function (certificate, issuer, callback) { }
当客户端发送证书状态请求时会触发。你可以解析服务器当前的证书,来获取ocsp网址和证书id,获取ocsp响应调用callback(null, resp)
,其中resp
是 buffer
实例。证书(certificate
)和发行者(issuer
)都是初级表达式缓存(buffer
der-representations of the primary)和证书的发行者。它可以用来获取ocsp证书和ocsp终点网址。
可以调用callback(null, null)
,表示没有ocsp响应。
调用callback(err)
可能会导致调用socket.destroy(err)
。
典型流程:
ocsprequest
(通过 clienthello 里的状态信息扩展)。ocsprequest
事件监听器。certificate
或issuer
获取ocsp网址,并执行ocsp request到caocspresponse
, 并通过callback
参数送回到客户端注意: 如果证书是自签名的,或者如果发行者不再根证书列表里(你可以通过参数提供一个发行者)。issuer
就可能为null。
注意:添加这个事件监听器仅会在连接连接时有效果。
注意:你可以能想要使用npm模块(比如asn1.js)来解析证书。
在指定的端口和主机上开始接收连接。如果host
参数没传,服务接受通过ipv4地址(inaddr_any
)的直连。
这是异步函数。当服务器已经绑定后回调用最后一个参数callback
。
更多信息参见net.server
。
停止服务器,不再接收新连接。这是异步函数,当服务器触发'close'
事件后回最终关闭。
返回绑定的地址,地址家族名和服务器端口。更多信息参见net.server.address()
如果客户端请求sni主机名和传入的hostname
相匹配,将会用到安全上下文(secure context)。context
可以包含key
,cert
,ca
和/或tls.createsecurecontext
options
参数的其他任何属性。
设置这个属性可以在服务器的连接数达到最大值时拒绝连接。
当前服务器连接数。
稳定性: 0 - 抛弃. 使用 tls.tlssocket 替代.
这是一个加密的流
底层socket写字节访问器(byteswritten accessor)的代理,将会返回写到socket的全部字节数。包括 tls 的开销。
net.socket实例的封装,透明的加密写数据和所有必须的tls协商。
这个接口实现了一个双工流接口。它包含所有常用的流方法和事件。
新的连接成功握手后回触发这个事件。无论服务器证书是否授权,都会调用监听器。用于用户测试tlssocket.authorized
看看如果服务器证书已经被指定的cas签名。如果tlssocket.authorized === false
,可以在tlssocket.authorizationerror
里找到错误。如果使用了npn,你可以检tlssocket.npnprotocol
获取协商协议(negotiated protocol)。
function (response) { }
如果启用requestocsp
参赛会触发这个事件。response
是缓存对象,包含服务器的ocsp响应。
一般来说,response
是服务器ca签名的对象,它包含服务器撤销证书状态的信息。
静态boolean变量,一直是true
。可以用来区别tls socket和常规对象。
boolean变量,如果对等实体证书(peer's certificate)被指定的某个cas签名,返回true
,否则false
。
对等实体证书(peer's certificate)没有验证通过的原因。当tlssocket.authorized === false
时,这个属性才可用。
返回一个代表对等实体证书( peer's certificate)的对象。这个返回对象有一些属性和证书内容相对应。如果参数detailed
是true
,将会返回包含发行者issuer
完整链。如果false
,仅有顶级证书没有发行者issuer
属性。
例子:
{ subject:
{ c: 'uk',
st: 'acknack ltd',
l: 'rhys jones',
o: 'node.js',
ou: 'test tls certificate',
cn: 'localhost' },
issuerinfo:
{ c: 'uk',
st: 'acknack ltd',
l: 'rhys jones',
o: 'node.js',
ou: 'test tls certificate',
cn: 'localhost' },
issuer:
{ ... another certificate ... },
raw: < raw der buffer >,
valid_from: 'nov 11 09:52:22 2009 gmt',
valid_to: 'nov 6 09:52:22 2029 gmt',
fingerprint: '2a:7a:c2:dd:e5:f9:cc:53:72:35:99:7a:02:5a:71:38:52:ec:8a:df',
serialnumber: 'b9b0d332a1aa5635' }
如果peer没有提供证书,返回null
或空对象。
返回一个对象,它代表了密码名和当前连接的ssl/tls协议的版本。
例子:{ name: 'aes256-sha', version: 'tlsv1/sslv3' }
更多信息参见http://www.openssl.org/docs/ssl/ssl.html#dealing_with_ciphers里的ssl_cipher_get_name()和ssl_cipher_get_version() 。
初始化 tls 重新协商进程。参数options
可能包含以下内容:rejectunauthorized
,requestcert
(细节参见tls.createserver)。一旦重新协商成(renegotiation)功完成,将会执行callback(err)
,其中err
为null
。
注意:当安全连接建立后,可以用这来请求对等实体证书(peer's certificate)。
注意:作为服务器运行时,handshaketimeout
超时后,socket将会被销毁。
设置最大的tls碎片大小(默认最大值为:16384
,最小值为:512
)。成功的话,返回true
,否则返回false
。
小的碎片包会减少客户端的缓存延迟:大的碎片直到接收完毕后才能被tls层完全缓存,并且验证过完整性;大的碎片可能会有多次往返,并且可能会因为丢包或重新排序导致延迟。而小的碎片会增加额外的tls帧字节和cpu负载,这会减少cpu的吞吐量。
返回asn.1编码的tls会话,如果没有协商,会返回。连接到服务器时,可以用来加速握手的建立。
注意:仅和客户端tls socket打交道。仅在调试时有用,会话重用是,提供session
参数给tls.connect
。
返回tls会话票据(ticket),或如果没有协商(negotiated),返回undefined
。
返回绑定的地址,地址家族名和服务器端口。更多信息参见net.server.address()。返回三个属性, 比如:{ port: 12346, family: 'ipv4', address: '127.0.0.1' }
表示远程ip地址(字符串表示),例如:'74.125.127.100'
或'2001:4860:a005::68'
.
表示远程 ip 家族,'ipv4'
或'ipv6'
。
远程端口(数字表示),例如,443
。
本地ip地址(字符串表示)。
本地端口号(数字表示)。