一、双向证书认证原理
普通的http协议可以通过用户名+口令的方式通过认证,用户名和口令是用post或者get方法发送过去的,而且是明文发送。也就是说,随便从网络上一抓包就能获取你的用户名和密码。解决方案是,使用javascript将用户名和口令经过MD5混淆后发送,只发送MD5值,但这不是本文要讨论的话题。
另一种更为有效的解决方法是https,也就是安全的http。采用https的服务器必须要有一个证明自己身份的证书,也就是服务器证书,通过该证书,可以使客户端确认服务器的身份。一般是后缀名为.pfx、.p12、.pem等形式。这些证书都由统一的证书机构颁发,并附带一个根证书(ROOT.cer),可以用来验证服务器证书的合法性。具体的验证过程涉及到公钥密码学,这里暂不讨论。需要知道的事实是,根证书是公开的,但除证书颁发机构外,其他人不能任意制作可以被根证书验证通过的证书。客户端的浏览器要访问https服务时,必须在自己的浏览器中导入根证书,这样才能通过验证。如果验证不通过,浏览器将会给出告警提示。
使用了https以后,浏览器与服务器之间的所有通讯都是被加密过的,因此即使是输入用户名和密码也不会被通过网络监听获取到。然而如果本机存在木马,则有可能在输入用户名和口令时被键盘记录,这仍然不够安全。更安全的方式是,客户端也使用证书,由服务器验证客户端证书的合法性,这样就避免了口令的输入,而证书中也包含了一定的用户信息(证书主题),因此可以通过证书主题来确定用户的身份,并去数据库中查找该用户,过程是与用户名口令登陆是一样的。由于证书是不可伪造的,因此不存在泄露或仿冒的风险。这样,双向的证书认证就可以通过证书建立起来。当然客户端要事先准备一个合法的证书,也就是与服务器证书为同一个CA中心颁发的证书。
客户端证书和服务器证书的关系:本质上完全相同,都属于用户证书,只不过服务器证书是颁发给服务器的IP地址(或者域名)的用户证书,也就是说服务器的用户名就是IP地址。
二、https与SSL/TLS
SSL(Secure Sockets Layer 安全套接层),及其继任者传输层安全(Transport Layer Security,TLS)是为网络通信提供安全及数据完整性的一种安全协议。TLS与SSL在传输层对网络连接进行加密。SSL建立在TCP和应用层协议之间,任何应用层协议都可以借助SSL来实现安全连接。https实际上也是基于SSL/TLS来实现的。https协议的开始阶段就是由SSL建立起一个安全的连接,之后在上面跑http。具体过程如下(摘自百度百科):
1)客户端向服务器发送一个开始信息“Hello”以便开始一个新的会话连接;
2)服务器根据客户的信息确定是否需要生成新的主密钥,如需要则服务器在响应客户的“Hello”信息时将包含生成主密钥所需的信息;
3)客户根据收到的服务器响应信息,产生一个主密钥,并用服务器的公开密钥加密后传给服务器;
4)服务器恢复该主密钥,并返回给客户一个用主密钥认证的信息,以此让客户认证服务器。
使用SSL证书建立SSL连接,则相应的公钥和私钥都是从证书中取得的。
几乎所有的http服务器(apache、tomcat、ngix,node)都实现了https,而它们均是通过openssl库来实现的。
当https服务器请求客户端证书时,浏览器就会提示用户选择证书(如果只存在一个符合条件的证书则不需要选择)
三、USBkey和证书
USBkey就是客户端证书存储的地方。用户证书颁发出来后一般是.pfx的一个文件,如果直接导入浏览器也可以实现双向证书认证。导入导出浏览器时均需要输入口令。如果口令被窃取则其他用户也可将证书导出并导入到自己的浏览器中,这样很不安全。如果证书被刷进USBkey,则不可以被导出,而私钥的运算也是在USBkey中完成的,不会在使用过程中泄露。USBkey中证书的运算必须在输入PIN后才能进行,获得了USBkey后如果不知道PIN码仍然不能完成身份仿冒。因此现在的网银均使用的是USBkey证书,这具有很高的安全性,只要USBkey和PIN码不同时丢失,证书就不会被泄露。
四、Node.js里的实现
Node.js的https模块实现了https服务器,调用createServer方法可以创建一个https服务器。
var https = require('https')
, fs = require('fs')
, express = require('express');
var options = {
pfx: fs.readFileSync(__dirname+'/cert/server.p12'),/*服务器证书*/
passphrase:'123456',/*服务器证书的口令*/
ca: fs.readFileSync(__dirname+'/cert/root.cer'),/*根证书*/
requestCert: true/*请求客户端证书*/
};
var app = express();
app.get('/',function(req,res){
var cert = req.connection.getPeerCertificate();
res.write('cert subject is '+JSON.stringify(cert.subject)+'
');
if(cert.subject&&cert.subject['CN']){
var user = cert.subject['CN'];
res.write('user is'+user+ '
');
}
console.log('OK');
res.end('OK');
});
var server = https.createServer(options, app).listen(443,function(){
console.log('Https server listening on port 443');
});
验证通过后直接开始https的通讯,而不会请求客户端证书。
https的req(也就是了express的req)对象可以通过
var cert = req.connection.getPeerCertificate().subject
得到证书主题,可以通过取得证书主题中的某个字段来获取用户名(不同的CA颁发的证书主题格式不一样,但也有一定的规范)。大多数证书主题中的“CN(common name)”字段代表的是用户名。
这样就能实现双向证书认证,如果客户端证书是刷在USBkey里面的,则浏览器会自动调用Key驱动来要求输入PIN码。
由于测试证书认证需要用到服务器、客户端和根证书,证书的制作可以用openssl来完成,具体的制作方法可以在网上找找。
附件是已经制作好的证书,包含了一个服务器证书,一个根证书,四个用户证书。使用时需要将四个用户证书均导入浏览器。
http://download.csdn.net/detail/chuanqi305/6815843