我是如何解决java.security.cert.CertPathValidatorException异常的

目录

问题来了

昨天,我还在我的工位上愉快的敲的代码,有位开发组的同事Z给我发消息。

开发组同事Z:大哥,这个PKI的登录功能是你做的吗?

我:是呀!N年前的事了。。。

开发组同事Z:PKI登录功能出问题了!有位客户使用他的PKI登录我们做的系统,登录时报错,换了几台电脑也不行。但是他使用他的PKI能登录其他的系统。能帮忙看一下吗,我找过好多同事看过了,他们看了下,都不知道如何入手

我:报错是报什么错?能具体的描述下吗?

开发组同事Z:访问系统时,能弹出证书的选择框,选择完证书后,Chrome浏览器的报错信息是:ERR_SSL_PROTOCOL_ERROR

我:行吧,我待会过去瞧瞧

问题分析

之前在配置tomcat的SSL时,我已经把客户证书的根证书已经导入到服务器信任证书列表内(配置见附录),“能弹出证书的选择框”说明服务器端是能识别出客户证书,但是为什么在登录的过程中,就报错了,可能有如下的几种原因:

  1. 服务器端的证书和服务器端的信任证书配置有误;(我一直以为是服务器端证书中的IP地址和服务器的IP不一致,后来才发现这个是不可能的)
  2. 登录过程中,tomcat能读取到客户端的证书,在程序处理的过程中,拒绝该证书;(客户端的证书的格式不被服务器端接受)
  3. 客户端证书过期了?(通过上面的描述好像不是这个错误)
  4. tomcat或者JDK有问题?(这个不太可能,其他用户使用PKI能登录呀)

解决问题

博主当时也是有点懵逼,不知道该怎么解决这个问题。好在博主有敢于尝试的精神,决定一个一个试一下。

重新安装服务器端证书

首先使用keytool工具生成服务器端证书,再使用keytool导入客户的根证书到服务器端的信任证书库中。然后把服务器端证书和服务器端的信任证书库放入tomcat,配置、重启tomcat(这里省略了具体的配置过程,具体的配置步骤见附录)。经过一番尝试,发现选择完证书还是报错。又经过一番折腾,还是没有找过是啥原因,正在灰心丧气的时候,灵机一动,程序员不是应该从日志中找错误吗?于是从网上找了一下java中如何开启SSL的日志信息。

日志带来曙光

在tomcat中加入JVM参数:-Djavax.net.debug=SSL,handshake,data,trustmanager,重启tomcat。使用客户的证书重新登录,看到后台打印出了很多的日志信息,浏览了一遍后,发现了如下的异常:

http-nio-8443-exec-4, fatal error: 46: General SSLEngine problem
sun.security.validator.ValidatorException: PKIX path validation failed: java.security.cert.CertPathValidatorException: algorithm constraints check failed
%% Invalidated:  [Session-7, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256]
http-nio-8443-exec-4, SEND TLSv1.2 ALERT:  fatal, description = certificate_unknown
http-nio-8443-exec-4, WRITE: TLSv1.2 Alert, length = 2
http-nio-8443-exec-4, fatal: engine already closed.  Rethrowing javax.net.ssl.SSLHandshakeException: General SSLEngine problem
http-nio-8443-exec-4, called closeOutbound()
http-nio-8443-exec-4, closeOutboundInternal()

拿着这个异常信息java.security.cert.CertPathValidatorException: algorithm constraints check failed去google上搜索了一下,找到了如下的这两个网站上的解决方法:

于是乎,按照上面的描述修改了JDK的配置信息,重启tomcat,使用证书登录,居然能正常登录了。心中暗自高兴了好一会儿,满满的成就感,想不到居然修改了一点配置就解决了……在高兴之余,想着搞清楚这到底是怎么一回事!接下来,容我慢慢道来。

刨根到底

到底是做了什么神奇的操作,就解决这问题了呢!首先找到JDK安装目录下的这个文件:

$JAVA_HOME/jre/lib/security/java.security

然后修改其中的两项配置(为了确保不出报错,把这两项禁用的算法全置为空):

# 处理证书路径时禁用的算法
jdk.certpath.disabledAlgorithms=
# 处理SSL/TLS时禁用的算法
jdk.tls.disabledAlgorithms=

然后重启tomcat就行了。

先看一下禁用的算法的语法,如下:

# 总体的语法
禁用的算法1, 禁用的算法2, 禁用的算法3...
# 禁用的每一项算法语法
算法名称 keySize 操作符 数值值

例如:DSA表示禁用DSA算法,RSA keySize < 2048表示禁用密钥长度小于2048的RSA算法,RSA keySize > 1024, RSA keySize < 2048表示禁用密钥长度大于1024小于2048的RSA算法。

后来,仔细看了一下日志,发现在客户证书的信息有这么一行:

Signature Algorithm: SHA1withRSA, OID = 1.2.***.11***9.1.1.*

Key:  Sun RSA public key, 1023 bits

客户证书的密钥长度居然是1023,而JDK8中为了安全性默认禁用了密钥长度小于2048的算法。最终修改的配置如下:

jdk.certpath.disabledAlgorithms=MD2, RSA keySize < 512

问题就这样解决了,哈哈!!!

总结

有时遇到问题,真的是让人摸不到头脑,这时别着急,把自己能想到的方法先尝试一遍,说不定就能行了呢。遇到问题首先可以借助日志来分析问题(就像上面那样打开JDK的SSL的调试日志的开关),通过日志大概就能定位到时哪里出问题了。在实际的开发中,很多程序猿基本上不会记录日志,稍微好一点的可能会在控制台使用System.out.println(xxxx)输出日志,不习惯使用像log4j这样的日志框架来记录日志。有人会说,出了问题,我可以直接debug呀!你有没想过在生产环境中,你还能用IDE来进行调试吗?

附录

tomcat的SSL配置

编辑conf/server.xml文件加入如下的配置:

<Connector port="8443" protocol="org.apache.coyote.http11.Http11NioProtocol" SSLEnabled="true"
               maxThreads="150" scheme="https" secure="true"
               clientAuth="true" sslProtocol="TLS"
               keystoreFile="${catalina.base}/server.ks" keystorePass="123456"
               truststoreFile ="${catalina.base}/server_trust.ks" 
               truststorePass="123456"/>

说明:

  • clientAuth为true表示开启SSL双向认证
  • keystoreFile指定服务器端的证书位置
  • truststoreFile指定服务器端信任证书库

服务器端证书配置

  1. 生成服务器端证书

    keytool -genkeypair -v -alias server -keyalg RSA -validity 3650 -keystore ./server.ks  -storepass 123456 -keypass 123456 -dname "CN=192.168.2.89,OU=zfx,O=zfx,L=gz,ST=gd,C=cn"
  2. 导出服务器端证书

    keytool -exportcert -alias server  -keystore ./server.ks  -file ./server.cer  -storepass 123456
  3. 导入服务器端证书到服务器信任证书列表

    keytool -importcert -alias serverca  -keystore ./server_trust.ks  -file ./server.cer  -storepass 123456
  4. 导入客户根证书到服务器信任证书列表

    keytool -importcert -alias urootca -keystore ./server_trust.ks  -file ./uroot.cer  -storepass 123456

使用如下的命令查看信任的证书信息:

keytool -list -keystore ./server_trust.ks -storepass 123456

Keytool命令常用参数

  • -genkeypair在用户主目录中创建一个默认文件”.keystore”,还会产生一个mykey的别名,mykey中包含用户的公钥、私钥和证书(在没有指定生成位置的情况下,keystore会存在用户系统默认目录)
  • -alias 产生别名 每个keystore都关联这一个独一无二的alias,这个alias通常不区分大小写
  • -keystore 指定密钥库的路径(产生的各类信息将不在.keystore文件中)
  • -keyalg 指定密钥的算法 (如 RSA,DSA,默认值为:DSA)
  • -validity 指定创建的证书有效期多少天(默认 90)
  • -keysize 指定密钥长度 (默认 1024
  • -storepass 指定密钥库的密码(获取keystore信息所需的密码)
  • -keypass 指定别名条目的密码(私钥的密码)
  • -dname 指定证书发行者信息 其中: “CN=名字与姓氏,OU=组织单位名称,O=组织名称,L=城市或区域名 称,ST=州或省份名称,C=单位的两字母国家代码”
  • -list 显示密钥库中的证书信息如:keytool -list -v –keystore path/to/keystore -storepass password
  • -v 显示密钥库中的证书详细信息
  • -exportcert 导出指定别名的证书,如:keytool – exportcert -alias theAlias -keystore path/to/keystore -file path/to/keystore/cert -storepass pass
  • -file 参数指定导出到文件的文件名
  • -delete 删除密钥库中某条目 keytool -delete -alias theAlias -keystore path/to/keystore –storepass pass
  • -printcert 控制台打印证书的详细信息,如:keytool -printcert -file path/to/keystore/cert -v
  • -keypasswd 修改密钥库中指定条目口令 keytool -keypasswd -alias theAlias -keypass oldPass -new newPass -storepass keystorePass -keystore path/to/keystore
  • -storepasswd 修改keystore口令 keytool -storepasswd -keystore path/to/keystore -storepass oldPass -new newPass
  • -importcert 将已签名数字证书导入密钥库 keytool -importcert -alias certAlias -keystore path/to/keystore -file path/to/keystore/cert
    原文作者:张风闲
    原文地址: https://www.cnblogs.com/zhangfengxian/p/java-CertPathValidatorException-algorithm-constraints-check-failed.html
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞