网络服务与安全
一、了解
在网络世界中,数据在服务器段和客户端之间传输,由于是明文传输的的内容,一旦在网络被人监控,数据就可能一览无余地展示在中间的窃听者面前,所以我们需要将数据进行加密后在进行网络传输,这样即使数据被揭去和窃听,窃听者也无法知道数据的真实内容是什么。但是对于我们的应用层协议而言,比如http,ftp等,我们仍希望能够透明地处理数据,而无需操心网络传输过程中的安全问题。于是就开始推出了SSL安全协议,作为一种安全协议,它在传输层提供对网络连接加密的功能。对于应用层而言,它就是透明的,数据在传递到应用层前就已经完成了加密与解密的过程。最初的SSL应用在Web上,被服务器端和浏览器端同时支持,随后IETF将其标准化,称为TLS安全传输协议。
node在网络安全上提供了3个模块,分别是crypto,tls,https。其中crypto主要用于加密解密,SHA1、MD5等加密算法都是其中的体现。真正用于网络的是另外两个模块,tls模块提供了与net模块类似的功能,区别在于它建立在TSL/SSL加密的TCP连接上。对于https而言,它完全与http模块接口一致,区别也仅仅在于它建立于安全的连接之上。
二、TSL/SSL
1. 密钥
TSL/SSL是一个公钥与私钥的结构,它是一个非常对称的结构,每一服务器端和客户端都有自己的公钥与私钥。公钥用于加密传输的数据,私钥用于解密接受到的数据,公钥与私钥是配对的,通过公钥加密的数据,同能通过私钥才能解密,所以在建立安全传输之前,客户端与服务器端之间需要交换公钥(协商加密)。
流程:
客户端发送数据—->服务器端公钥加密—->服务器端私钥解密—->服务端接收数据、处理响应
服务端处理客户端传来的数据,响应数据—->客户端公钥加密—->客户端私钥解密—->客户端接收响应数据、处理渲染页面
在node中采用的是openssl实现TLS/SSL:
1)在项目中添加server.key以及client.key私钥文件。
2)在项目目录中打开终端/cmder分别输入以下命令生成私钥:
- openssl genrsa -out server.key 1024
- openssl genrsa -out client.key 1024
3)生成公钥同上做法输入命令:
- openssl rsa -in server.key -pubout -out server.pem
- openssl rsa -in client.key -pubout -out client.pem
观察server.key与client.key中的私钥文件,再看经过私钥分别配出的公钥,发现server与client的公钥私钥都是不同的,当时在网络中依然可能存在被窃听的情况,典型的例子是
中间人攻击(充当双面间谍)客户端和服务器端在交换公钥的过程中,中间人对客户端扮演服务器端的角色,对服务器端扮演客户端的角色,因此它是让人感觉不到的,也是极为恐怖的。为了解决这种问题,数据传输的过程中还需对得到的公钥进行认证,以确定得到公钥的是出自目标服务器,而这种认证方式就是数字证书(CA,数字证书认证中心)。
区别于公钥,数字证书中包含服务器名称和主机名、服务器的公钥、签名颁发机构的名称、来自签名颁发机构的签名。在连接建立前,会通过证书中的签名确认收到公钥是来自目标服务器的,从而产生信任关系避免中间人攻击。
2. 数字证书
为了保证我们的数据安全,现在我们引入了一个第三方:CA(数字证书认证中心),认证中心的作用就是为站点颁发证书,且这个证书中具有CA通过自己的公钥和私钥实现的签名。
不过通过CA机构颁发的证书通常是一个繁琐的过程,还需要花费时间与金钱。面对这种情况很多人会选择自签名证书,也就是自己来扮演CA机构,给自己的服务器端颁发签名证书。
2.1 自签证
流程:
1)制作证书,同样在项目目录中打开终端/cmder,输入命令:
- openssl genrsa -out ca.key 1024
- openssl req -new -key ca.key -out ca.csr
注意:此过程需要填写相关的参数:
例子:
1
2
3
4
5
6
7
8
9
10
11
12
13 1Country Name (2 letter code) [AU]:cn
2State or Province Name (full name) [Some-State]:china
3Locality Name (eg, city) []:guangdong
4Organization Name (eg, company) [Internet Widgits Pty Ltd]:test
5Organizational Unit Name (eg, section) []:test
6Common Name (e.g. server FQDN or YOUR name) []:root /* 此处关键,表示此ca证书为根证书 */
7Email Address []:1131260522@qq.com
8
9Please enter the following 'extra' attributes
10to be sent with your certificate request
11A challenge password []:123456
12An optional company name []:test
13
- openssl x509 -req -in ca.csr -signkey ca.key -out ca.crt
上述步骤过后,就完成了扮演CA角色需要的文件。
2)服务器端向CA机构申请签名证书,在申请签名证书之前依然是要创建自己的CSR文件。
注意在这过程中Common Name要配置服务器域名,否则在后续的认证过程中会出现错误。
同样在项目目录中打开终端/cmder,输入命令:
openssl req -new -key server.key -out server.csr
注意:
同样这个过程也需要填写信息:
例子:
1
2
3
4
5
6
7
8
9
10
11
12
13 1Country Name (2 letter code) [AU]:cn
2State or Province Name (full name) [Some-State]:china
3Locality Name (eg, city) []:guangdong
4Organization Name (eg, company) [Internet Widgits Pty Ltd]:test
5Organizational Unit Name (eg, section) []:test
6Common Name (e.g. server FQDN or YOUR name) []:www.jstest.com /* 此处填写一个域名,注意如果是localhost填localhost即可,笔者此处的域名为wampserver配置出来的域名站点,目的为了更加清晰操作 */
7Email Address []:1131260522@qq.com
8
9Please enter the following 'extra' attributes
10to be sent with your certificate request
11A challenge password []:123456
12An optional company name []:test
13
3)签名
签名过程需要CA的证书以及私钥参与,最终颁发一个带有CA签名的证书
同样在项目目录中打开终端/cmder,输入命令:
- openssl x509 -req -CA ca.crt -CAkey ca.key -CAcreateserial -in server.csr -out server.crt
最终流程
图示:
解释:
服务端在发起安全连接前会去获取服务端的证书,并通过CA的证书验证服务器端证书的真伪。
CA机构将证书颁发给服务端后,证书在请求的过程中会被发送给客户端,客户端需要通过CA的的证书验证真伪,如果是知名的CA机构,它的证书一般装在浏览器中,如果是自己扮演CA机构,颁发自签名证书则不能享有这个福利,也就是说客户端需要发送请求获取CA证书才能进行验证。
3. TSL 服务
(1)创建服务器端
将所有的证书备齐后,通过Node中的tls模块来创建一个安全的TCP服务,来一个简单的echo服务:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 1/* server.js */
2var tls = require('tls');
3var fs = require('fs');
4
5var options = {
6 key: fs.readFileSync('./server.key'),
7 cert: fs.readFileSync('./server.crt'),
8 request: true,
9 ca: [fs.readFileSync('./ca.crt')]
10}
11var server = tls.createServer(options,function(stream){
12 console.log('server connected',stream.authorized?'authorized':'unauthorized');
13 stream.write('welcome!\n');
14 stream.setEncoding('utf8');
15 stream.pipe(stream);
16})
17server.listen(8000,function(){
18 console.log('server is created')
19})
20
启动上述的服务器,通过另外的后台窗口输入命令,可观察证书是否正常:
输入:openssl s_client -connect 127.0.0.1:8000
(2)TSL 客户端
完整体系,使用Node来模拟客户端,如net模块一样,tls模块也提供了connect()方法来构建客户端。在构建客户端前,需要为客户端生成属于自己的私钥与签名:
openssl req -new -key server.key -out server.csr
注意:
同样这个过程也需要填写信息:
例子:
1
2
3
4
5
6
7
8
9
10
11
12
13 1Country Name (2 letter code) [AU]:cn
2State or Province Name (full name) [Some-State]:china
3Locality Name (eg, city) []:guangdong
4Organization Name (eg, company) [Internet Widgits Pty Ltd]:test
5Organizational Unit Name (eg, section) []:test
6Common Name (e.g. server FQDN or YOUR name) []: ***** /* 此处填写本机客户端,即计算机名称 */
7Email Address []:1131260522@qq.com
8
9Please enter the following 'extra' attributes
10to be sent with your certificate request
11A challenge password []:123456
12An optional company name []:test
13
-
openssl x509 -req -CA ca.crt -CAkey ca.key -CAcreateserial -in server.csr -out server.crt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 1/* client.js */
2var tls = require('tls');
3var fs = require('fs');
4var options = {
5 host:'www.jstest.com',
6 key: fs.readFileSync('./client.key'),
7 cert: fs.readFileSync('./client.crt'),
8 ca: [fs.readFileSync('./ca.crt')]
9};
10var stream = tls.connect(8000, options, function () {
11 console.log('client connected', stream.authorized ? 'authorized' : 'unauthorized');
12 process.stdin.pipe(stream);
13});
14stream.setEncoding('utf8');
15stream.on('data', function (data) {
16 console.log(data);
17});
18stream.on('end', function () {
19 server.close();
20});
21
启动服务器以及客户端,观察结果:
成功!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
三、https 服务
https服务就是工作在TLS/SSL上的http,创建一个https服务已是一个简单不过的事情。
1. 准备证书
https服务需要用到的私钥以及签名证书,我么可以直接用上面的私钥以及证书进行实例。
2. 创建 https服务器端
区别于http,创建一个https服务只比创建一个http多一个选项配置options,其余地方非常类似。
1
2
3
4
5
6
7
8
9
10
11
12
13 1var https = require('https');
2var fs = require('fs');
3/* 配置私钥以及证书 */
4var options = {
5 key: fs.readFileSync('./server.key'),
6 cert: fs.readFileSync('./server.crt')
7};
8https.createServer(options, function (req, res) {
9 res.writeHead(200);
10 res.end("hello world\n");
11}).listen(8000);
12
13
3. 创建 https 客户端
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 1var https = require('https');
2var fs = require('fs');
3var options = {
4 host: 'www.jstest.com', /* 一定要书写正确,否则 Game Over */
5 port: 8000,
6 path: '/',
7 method: 'GET',
8 key: fs.readFileSync('./client.key'),
9 cert: fs.readFileSync('./client.crt'),
10 ca: [fs.readFileSync('./ca.crt')]
11};
12options.agent = new https.Agent(options);
13var req = https.request(options, function (res) {
14 res.setEncoding('utf-8');
15 res.on('data', function (d) {
16 console.log(d);
17 });
18});
19req.end();
20req.on('error', function (e) {
21 console.log(e);
22});
23
启动服务器以及客户端,观察结果:
到浏览器搜索:https://www.jstest.com:8000,查看结果:
成功!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
至于如何配置Chrome信任自签CA,在此不作论述………