node-tls/ssl
Node - TLS/SSL
在网络中,数据在服务端和客户端之间传递,由于是明文传递的内容,是很容易被第三方窃取的。当时NetSpace提出了SSL(Secure Sockets Layer, 安全套接层)。SSL作为一种安全协议,它在传输层提供对网络连接加密的功能。对于应用层而言,它是透明的,数据在传递到应用层之前就已经完成了加解密的过程。最初的SSL应用在Web上,被服务器端和浏览器端同时支持,随后IETF将其标准化,成为TLS(Transport Layer Security,安全传输层协议)。
Node在网络安全上提供了3个模块,分别为crypto、tls、https、其中crypto主要用于加解密;tls模块提供了与net模块类似的功能,区别在于它建立在TLS/SSL加密的TCP连接上;https与http接口一致,区别也仅在于它建立在安全的连接之上。
TLS/SSL是一个公钥与私钥的非对称性结构。每个服务器和客户端都有自己的公私钥。公钥用来加密要传输的数据,私钥用来加密收到的数据。公私钥是成配对的,通过公钥加密的数据,只能通过它对应的私钥来解密。故在建立安全传输之前,客户端和服务端需要互换公钥。客户端发送数据时需要服务端的公钥加密传输,服务器通过自己的私钥去解密数据。服务器发送、客户端接收,同理。
Node底层采用的openssl实现TLS/SSL的,为此要生成公私钥,可以通过openssl完成。
生成私钥
1 | openssl genrsa -out server.key 1024 |
生成公钥
1 | openssl rsa -in server.key -pubout -out server.pem |
公私钥的对称加密虽好,但网络中依然可能存在中间人攻击窃密。在客户端与服务端交换公钥的过程中,中间人对客户端扮演服务端的角色,对服务端扮演客户端的角色,这样客户端与服务器端几乎感受不到中间人的存在。为了解决这个问题,TSL/SSL引入数字证书来进行验证。这个证书是第三方CA(Certificate Authority, 数字证书认证中心)颁发给站点的,且这个证书中具有CA通过自己的公钥和私钥实现的签名。为了得到签名证书,服务器端需要通过自己的私钥生成CSR(Cdrtificate Signing Request,证书签名请求)文件。CA机构通过这个文件颁发属于该服务器端的签名证书。通过CA机构颁发证书需要付出一定的精力和费用,对于中小型企业而言,采用自签名证书是中不错的选择。所谓自签名证书,就是自己扮演CA机构,给自己的服务器端颁发证书。下面重点说下自签名证书颁发的流程:
1 | 生成私钥 |
上述步骤完成了扮演CA角色需要的文件,接下来是服务端向CA机构申请证书
1 | # 生成CSR文件 |
CA机构将证书颁发给服务器端后,证书在请求的过程中会发送给客户端,客户端通过CA的证书验证真伪。如果是知名的CA机构,他们的证书一般预装在浏览器中。如果是自签名证书,客户端需要获取到CA的证书才能进行验证。
TLS服务
构建证书都备齐之后,我们试通过Node的tls模块来创建一个安全的简单的服务:TlsServer.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18let tls = require('tls');
let fs = require('fs');
let options = {
key: fs.readFileSync('./keys/server.key'),
cert: fs.readFileSync('./keys/server.crt'),
requestCert: true,
ca: [fs.readFileSync('./keys/ca.crt')]
};
let server = tls.createServer(options, function (socket) {
console.log('server connected', socket.authorized);
socket.write('Welcome\n');
socket.setEncoding('utf-8');
socket.pipe(socket);
});
server.listen(8000, function () {
console.log('server start');
});TlsClient.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22let tls = require('tls');
let fs = require('fs');
let options = {
key: fs.readFileSync('./keys/client.key'),
cert: fs.readFileSync('./keys/client.crt'),
rejectUnauthorized: false, // 自签名证书rejectUnauthorized为false
ca : [fs.readFileSync('./keys/ca.crt')]
};
let client = tls.connect(8000, options, function () {
console.log('client connected', client.authorized);
process.stdin.pipe(client);
});
client.setEncoding('utf-8');
client.on('data', function (data) {
console.log('client data', data);
});
client.on('end', function () {
console.log('client end');
});HTTPS
HTTPS服务就是工作在TLS/SSL上的HTTP。了解了TLS后,创建HTTPS就简单了。接下来让我们了解下HTTPS是如何构建的:HttpsServer.js
1
2
3
4
5
6
7
8
9
10
11
12const https = require('https');
const fs = require('fs');
const options = {
key: fs.readFileSync('./keys/server.key'),
cert: fs.readFileSync('./keys/server.crt'),
ca: [fs.readFileSync('./keys/ca.crt')]
};
https.createServer(options, function (req, res) {
res.writeHead(200);
res.end('Hello Https\n');
}).listen(8000);HttpsClient.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24const https = require('https');
const fs = require('fs');
const options = {
hostname: 'localhost',
port: 8000,
method: 'GET',
path: '/',
rejectUnauthorized: false, // 自签名证书rejectUnauthorized为false
key: fs.readFileSync('./keys/client.key'),
cert: fs.readFileSync('./keys/client.crt'),
ca: [fs.readFileSync('./keys/ca.crt')]
};
let req = https.request(options, function (res) {
res.setEncoding('utf-8');
res.on('data', function (d) {
console.log('client data', d) ;
});
});
req.on('error', function (err) {
console.log('client error', err);
});
req.end();