如何解决“java.security.cert.CertificateException:没有主题替代名称”错误?

我有一个Java Web服务客户端,它通过HTTPS使用Web服务。

import javax.xml.ws.Service; @WebServiceClient(name = "ISomeService", targetNamespace = "http://tempuri.org/", wsdlLocation = "...") public class ISomeService extends Service { public ISomeService() { super(__getWsdlLocation(), ISOMESERVICE_QNAME); } 

当我连接到服务URL( https://AAA.BBB.CCC.DDD:9443/ISomeService )时,我得到exceptionjava.security.cert.CertificateException: No subject alternative names present

为了解决这个问题,我首先运行了openssl s_client -showcerts -connect AAA.BBB.CCC.DDD:9443 > certs.txt并在certs.txt文件中获得了以下内容:

 CONNECTED(00000003) --- Certificate chain 0 s:/CN=someSubdomain.someorganisation.com i:/CN=someSubdomain.someorganisation.com -----BEGIN CERTIFICATE----- XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX -----END CERTIFICATE----- --- Server certificate subject=/CN=someSubdomain.someorganisation.com issuer=/CN=someSubdomain.someorganisation.com --- No client certificate CA names sent --- SSL handshake has read 489 bytes and written 236 bytes --- New, TLSv1/SSLv3, Cipher is RC4-MD5 Server public key is 512 bit Compression: NONE Expansion: NONE SSL-Session: Protocol : TLSv1 Cipher : RC4-MD5 Session-ID: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX Session-ID-ctx: Master-Key: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX Key-Arg : None Start Time: 1382521838 Timeout : 300 (sec) Verify return code: 21 (unable to verify the first certificate) --- 

AFAIK,现在我需要

  1. 提取-----BEGIN CERTIFICATE----------END CERTIFICATE-----之间的certs.txt部分,
  2. 修改它,以便证书名称等于AAA.BBB.CCC.DDD
  3. 然后使用keytool -importcert -file fileWithModifiedCertificate (其中fileWithModifiedCertificate是操作1和2的结果)导入结果。

它是否正确?

如果是这样,我如何才能使来自步骤1的证书与基于IP的地址( AAA.BBB.CCC.DDD )一起工作?

更新1(23.10.2013 15:37 MSK):在回答类似的问题时 ,我读了以下内容:

如果您不控制该服务器,请使用其主机名(前提是至less有一个CN与现有证书中的主机名相匹配)。

究竟“使用”是什么意思?

我通过使用此处介绍的方法禁用HTTPS检查来解决此问题 :

我把下面的代码放到ISomeService类中:

 static { disableSslVerification(); } private static void disableSslVerification() { try { // Create a trust manager that does not validate certificate chains TrustManager[] trustAllCerts = new TrustManager[] {new X509TrustManager() { public java.security.cert.X509Certificate[] getAcceptedIssuers() { return null; } public void checkClientTrusted(X509Certificate[] certs, String authType) { } public void checkServerTrusted(X509Certificate[] certs, String authType) { } } }; // Install the all-trusting trust manager SSLContext sc = SSLContext.getInstance("SSL"); sc.init(null, trustAllCerts, new java.security.SecureRandom()); HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory()); // Create all-trusting host name verifier HostnameVerifier allHostsValid = new HostnameVerifier() { public boolean verify(String hostname, SSLSession session) { return true; } }; // Install the all-trusting host verifier HttpsURLConnection.setDefaultHostnameVerifier(allHostsValid); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } catch (KeyManagementException e) { e.printStackTrace(); } } 

由于我使用https://AAA.BBB.CCC.DDD:9443/ISomeService仅用于testing目的,这是一个足够好的解决scheme。

我有同样的问题,并解决这个代码。 我把这个代码放在我的webservices的第一个调用之前。

 javax.net.ssl.HttpsURLConnection.setDefaultHostnameVerifier( new javax.net.ssl.HostnameVerifier(){ public boolean verify(String hostname, javax.net.ssl.SSLSession sslSession) { return hostname.equals("localhost"); } }); 

这很简单,工作正常。

这是原始的来源。

证书标识的validation是针对客户请求的。

当您的客户端使用https://xxx.xxx.xxx.xxx/something (其中xxx.xxx.xxx.xxx是IP地址)时,将根据此IP地址检查证书标识(理论上,只使用IP SAN延期)。

如果您的证书没有IP SAN,但是DNS SAN(或者如果没有DNS SAN,主题DN中的通用名称),则可以通过使您的客户端使用具有该主机名的URL(或主机名如果有多个可能的值,证书将是有效的)。 例如,如果您的证书具有www.example.com的名称,请使用https://www.example.com/something

当然,您需要使用该主机名来parsing该IP地址。

另外,如果有任何DNS SAN,主题DN中的CN将被忽略,因此在这种情况下使用与其中一个DNS SAN相匹配的名称。

要导入证书:

  1. 从服务器提取证书,例如openssl s_client -showcerts -connect AAA.BBB.CCC.DDD:9443 > certs.txt这将提取PEM格式的证书。
  2. 将cert转换为DER格式,因为这是keytool所期望的,例如openssl x509 -in certs.txt -out certs.der -outform DER
  3. 现在你想把这个证书导入系统默认的“cacert”文件。 find您的Java安装的系统默认“cacerts”文件。 看看如何获取默认java安装cacerts的位置?
  4. 将证书导入该cacerts文件: sudo keytool -importcert -file certs.der -keystore <path-to-cacerts>默认cacerts密码是'changeit'。

如果证书是针对FQDN发布的,而您尝试通过Java代码中的IP地址进行连接,则应该在您的代码中修复此问题,而不是弄乱证书本身。 将您的代码更改为通过FQDN进行连接。 如果FQDN在您的开发计算机上无法parsing,只需将其添加到您的主机文件中,或者使用可以parsing此FQDN的DNS服务器configuration您的计算机。

我得到这个错误的问题是通过使用完整的URL“qatest.ourCompany.com/webService”而不是“qatest / webService”来解决的。 原因是我们的安全证书有一个通配符,即“* .ourCompany.com”。 一旦我把完整的地址,exception消失了。 希望这可以帮助。

在C:\ Windows \ System32 \ drivers \ etc文件夹中的hosts文件中添加您的IP地址。 还要添加IP地址的IP和域名。 例如:aaa.bbb.ccc.ddd abc@def.com

我已经通过以下方式解决了这个问题。

1.创build一个class级。 这个类有一些空的实现

 class MyTrustManager implements X509TrustManager { public java.security.cert.X509Certificate[] getAcceptedIssuers() { return null; } public void checkClientTrusted(X509Certificate[] certs, String authType) { } public void checkServerTrusted(X509Certificate[] certs, String authType) { } @Override public void checkClientTrusted(java.security.cert.X509Certificate[] paramArrayOfX509Certificate, String paramString) throws CertificateException { // TODO Auto-generated method stub } @Override public void checkServerTrusted(java.security.cert.X509Certificate[] paramArrayOfX509Certificate, String paramString) throws CertificateException { // TODO Auto-generated method stub } 

2.创build一个方法

 private static void disableSSL() { try { TrustManager[] trustAllCerts = new TrustManager[] { new MyTrustManager() }; // Install the all-trusting trust manager SSLContext sc = SSLContext.getInstance("SSL"); sc.init(null, trustAllCerts, new java.security.SecureRandom()); HostnameVerifier allHostsValid = new HostnameVerifier() { public boolean verify(String hostname, SSLSession session) { return true; } }; HttpsURLConnection.setDefaultHostnameVerifier(allHostsValid); HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory()); } catch (Exception e) { e.printStackTrace(); } } 

  1. 调用引发exception的disableSSL()方法。 它工作正常。

你可能不想禁用所有的sslvalidation,所以你可以通过这个禁用hostNamevalidation,这是比可选的一点可怕:

 HttpsURLConnection.setDefaultHostnameVerifier( SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);