在实际开发中,经常遇到使用证书登录业务系统的需求。即用户登录系统前需要插入自己的usb key证书,登录时输入key的口令完成登录。其实前面那篇ssl文章已经介绍过,通过配置ssl客户端认证,就可以实现上述需求。但由于ssl本身的一些限制,很多场合下并不适用,因此仍需要在应用层实现证书登录过程。当然,证书登录的密码学原理与ssl一致,也就是可以理解成将ssl的流程在应用层实现一遍。因此应用系统实现证书登录,从数据库到客户端,需要以下几个步骤:
首先是在应用系统的数据库用户表中加入证书字段。这个字段数据类型一般是字符串类型,存储了用户注册时登记的个人证书(只含有公钥)的base64数据。一般只需要一个字段,但如果需支持多证书登录,则可能要设置多个字段。相应的在用户管理功能里,需要加上对用户证书的管理,包括新增、修改及删除等。
其次是在应用系统的服务端实现对证书验证的支持。参考ssl的客户端验证流程,服务端需要对客户端发过来的签名进行验证。数字签名的验证过程不用多说,这里具体介绍讲几个要注意的地方。
一是数字签名的原文。为防止重放攻击,客户端产生数字签名的原文应该由服务端和客户端通过某种约定产生。如果是使用随机数,则随机数应该由服务端产生并返回给客户端进行数字签名,服务端验证签名时使用自己保存的随机数备份。这一点很重要,如果服务端验证签名时用客户端发送过来的随机数作为明文,那这样的设计对防止重放攻击没有意义。这样的设计就要求客户端登录服务器前至少有一次通信,用于随机数的请求和传输,并且服务端需要保存客户端登录请求状态,以维护数字签名与对应的随机数原文。流程如下图:
如果应用系统是bs结构,可以通过session维护客户端的登录请求状态。但如果应用是cs结构,或者采用完全无状态服务(如web services),则实现上述过程比较困难。这时可以采用客户端生成原文并发送给服务端的方式,但这个原文需要进行设计,如在随机数基础上增加一些业务场景信息。而服务端在验证签名前,也必须先验证原文是否合规有效,是否之前被使用过。以最大程度杜绝被重放攻击的可能,流程如下。
二是签名结果是否包含证书问题。数字签名结果有包含签名所使用证书和不包含两种情况。如果包含证书,则服务端可以直接验证签名,并通过证书在数据库用户表定位相应的用户;否则,客户端登录时还需要传递用户名或id等标识信息,服务端凭借用户标识查询到用户证书,并进行验证。
三是服务端对签名验证的兼容性问题。如果客户端和服务端都使用同一套密码服务接口,如cryptoapi或某第三方提供的sdk,那客户端签名在服务端是可以直接验证的。但很多情况下,客户端和服务端使用了不同的密码服务接口,比如客户端使用基于cryptoapi的控件签名,服务端则是用java库的密码接口验证。那这时需要进行兼容性设置或开发,比如扩展服务端java验证方法,使得它可以识别验证客户端的签名。虽然这有一定的挑战性,但它是完全可以实现的,因为不同密码服务都支持pkcs7签名标准。而且从密码学本身讲,不会因为实现环境的不同而产生原理的差别。
最后就是在客户端实现数字签名了。这里用cryptoapi或capicom组件什么的生成数字签名即可。但在实际应用中,大部分业务系统是bs结构,也就是需要在网页上实现数字签名的生成,这就涉及到在网页上使用控件的老问题了。遇到此类问题,一般的选择就是使用activex控件技术。大家都知道,activex并不是w3c的技术标准,而只是微软的自留地。如果使用了activex控件,那就只能使用ie或ie内核模式的浏览器了(ps:对activex的支持也就是ie的性能总是落后于其他浏览器的主要原因)。但由于html5的流行及其他一些因素,现在的业务趋势是chrome浏览器已成为标配,大部分用户要求bs应用支持chrome/firefox等非ie浏览器。这种情况下无法使用activex控件。虽然chrome有自己的插件标准npapi和ppapi,但由于安全原因,npapi已被谷歌放弃,在chrome 42以上版本已经不再支持npapi插件。而对于ppapi,个人认为开发还是比较复杂,涉及到的安全限制较多,目前不建议使用此技术开发插件。根据自己的开发经验,这里推荐使用伪协议和socket服务监听的方式解决此问题。因为这已经不是这篇文章的主题,故不在此展开。对此有兴趣的同学可以私信或在评论区交流。
数字签名生成后,客户端就可以将数据提交给服务端,应根据具体情况提交相应的数据,具体可参考下表。
至此,通过从数据库、中间层到客户端的开发,应用系统已支持证书登录了。这里面介绍的是基本的实现思路,并不是很难,在具体开发时可能有其他问题和需求要解决。大家有任何问题欢迎在评论区交流。