Java7集成BouncyCastle支持TLS1.3后SMTP发信证书错误解决方案
问题描述
应用当前运行在Java 1.7.0_79-b15版本环境下,由于对接的第三方应用将TLS版本升级至1.3,无法直接升级JDK版本获得原生TLS 1.3支持,因此引入Bouncy Castle组件满足对接要求。引入Bouncy Castle后已可正常连接第三方系统,但此前基于Java默认安全机制正常运行的邮件服务无法正常工作,报错信息如下:
Caused by: javax.mail.MessagingException: IOException while sending message; nested exception is: org.bouncycastle.tls.TlsFatalAlert: certificate_unknown(46) at com.sun.mail.smtp.SMTPTransport.sendMessage(SMTPTransport.java:625) at javax.mail.Transport.send0(Transport.java:169) at javax.mail.Transport.send(Transport.java:98) at org.apache.commons.mail.Email.sendMimeMessage(Email.java:1232) ... 72 more Caused by: org.bouncycastle.tls.TlsFatalAlert: certificate_unknown(46) at org.bouncycastle.jsse.provider.ProvSSLSocketDirect.checkServerTrusted(Unknown Source) at org.bouncycastle.jsse.provider.ProvTlsClient$1.notifyServerCertificate(Unknown Source) at org.bouncycastle.tls.TlsUtils.processServerCertificate(Unknown Source) at org.bouncycastle.tls.TlsClientProtocol.handleServerCertificate(Unknown Source) at org.bouncycastle.tls.TlsClientProtocol.handleHandshakeMessage(Unknown Source) at org.bouncycastle.tls.TlsProtocol.processHandshakeQueue(Unknown Source) at org.bouncycastle.tls.TlsProtocol.processRecord(Unknown Source) at org.bouncycastle.tls.RecordStream.readRecord(Unknown Source) at org.bouncycastle.tls.TlsProtocol.safeReadRecord(Unknown Source) at org.bouncycastle.tls.TlsProtocol.blockForHandshake(Unknown Source) at org.bouncycastle.tls.TlsClientProtocol.connect(Unknown Source) at org.bouncycastle.jsse.provider.ProvSSLSocketDirect.startHandshake(Unknown Source) at org.bouncycastle.jsse.provider.ProvSSLSocketDirect.startHandshake(Unknown Source) at sun.net.www.protocol.https.HttpsClient.afterConnect(HttpsClient.java:563) at sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:185) at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1301) at sun.net.www.protocol.https.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:254) at java.net.URL.openStream(URL.java:1037) at javax.activation.URLDataSource.getInputStream(URLDataSource.java:107) at javax.activation.DataHandler.writeTo(DataHandler.java:304) at javax.mail.internet.MimeBodyPart.writeTo(MimeBodyPart.java:1350) at javax.mail.internet.MimeBodyPart.writeTo(MimeBodyPart.java:845) at javax.mail.internet.MimeMultipart.writeTo(MimeMultipart.java:361) at com.sun.mail.handlers.multipart_mixed.writeTo(multipart_mixed.java:85) at javax.activation.ObjectDataContentHandler.writeTo(DataHandler.java:883) at javax.activation.DataHandler.writeTo(DataHandler.java:316) at javax.mail.internet.MimeBodyPart.writeTo(MimeBodyPart.java:1350) at javax.mail.internet.MimeBodyPart.writeTo(MimeBodyPart.java:845) at javax.mail.internet.MimeMultipart.writeTo(MimeMultipart.java:361) at com.sun.mail.handlers.multipart_mixed.writeTo(multipart_mixed.java:85) at javax.activation.ObjectDataContentHandler.writeTo(DataHandler.java:883) at javax.activation.DataHandler.writeTo(DataHandler.java:316) at javax.mail.internet.MimeBodyPart.writeTo(MimeBodyPart.java:1350) at javax.mail.internet.MimeMessage.writeTo(MimeMessage.java:1683) at com.sun.mail.smtp.SMTPTransport.sendMessage(SMTPTransport.java:585) ... 75 more Caused by: java.security.cert.CertificateException: Unable to find certificate chain. at org.bouncycastle.jsse.provider.ProvX509TrustManager.validateChain(Unknown Source) at org.bouncycastle.jsse.provider.ProvX509TrustManager.checkTrusted(Unknown Source) at org.bouncycastle.jsse.provider.ProvX509TrustManager.checkServerTrusted(Unknown Source)
核心诉求:通过SMTP发送邮件时是否可以禁用Bouncy Castle的证书校验流程?是否存在其他可行的问题修复方案?
解决方案
不建议全局禁用Bouncy Castle(以下简称BC)的证书校验,该操作会导致所有走BC的TLS连接失去服务端身份校验能力,存在极高的中间人攻击风险,生产环境绝对禁止使用。可按以下优先级选择修复方案:
- 优先方案:隔离BC的使用范围,避免影响邮件服务
报错的核心原因是注册BC安全提供者时将其放在了提供者列表的最高优先级,导致所有SSL/TLS连接(包括SMTP连接、邮件内嵌HTTPS资源拉取连接)都默认走BC的SSL实现,而BC默认的信任管理器不会加载JDK自带cacerts中的受信根证书,因此无法校验邮件服务器的证书链,抛出certificate_unknown错误。
调整BC注册逻辑,不要使用Security.insertProviderAt(new BouncyCastleJsseProvider(), 1)这类将BC设置为最高优先级提供者的代码,仅在对接需要TLS 1.3的第三方服务时,单独为对应连接指定使用BC的SSLContext,其余业务(包括邮件服务)默认走JDK原生SunJSSE实现,二者完全隔离不会互相影响。
针对JavaMail的SMTP连接,可显式配置参数强制使用JDK原生SSL提供者,不受全局安全配置影响:Properties props = new Properties(); // SMTP非SSL端口使用STARTTLS时配置 props.put("mail.smtp.ssl.provider", "com.sun.net.ssl.internal.ssl.Provider"); // SMTPS直接SSL连接时配置 props.put("mail.smtps.ssl.provider", "com.sun.net.ssl.internal.ssl.Provider"); - 备选方案:为BC配置完整的信任根证书
如果业务场景必须全局启用BC作为最高优先级SSL提供者,初始化BC的SSLContext时,手动将JDK默认cacerts信任库中的根证书、企业内部私有根证书导入BC的信任管理器,让BC可以正常校验包括邮件服务器在内的所有公网/内部服务证书链,从根源解决证书找不到的问题。 - 测试环境临时方案:仅针对SMTP连接跳过证书校验
如果仅需在测试环境临时验证邮件连通性,可单独为SMTP连接配置信任所有证书的规则,不要修改BC的全局校验逻辑:Properties props = new Properties(); // 信任所有SMTP服务器证书 props.put("mail.smtp.ssl.trust", "*"); props.put("mail.smtps.ssl.trust", "*");
注意:上述跳过证书校验的配置仅可用于本地/测试环境,生产环境使用会导致邮件传输过程存在被窃听、篡改的风险。
内容的提问来源于stack exchange,提问作者Anthony Guillen
相关产品推荐
相关产品推荐

