如何修复“java.security.cert.CertificateException:不存在主题替代名称”错误?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/19540289/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-12 18:12:37  来源:igfitidea点击:

How to fix the "java.security.cert.CertificateException: No subject alternative names present" error?

javasslhttpscertificatessl-certificate

提问by Dmitrii Pisarenko

I have a Java web service client, which consumes a web service via HTTPS.

我有一个 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);
    }

When I connect to the service URL (https://AAA.BBB.CCC.DDD:9443/ISomeService), I get the exception java.security.cert.CertificateException: No subject alternative names present.

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

To fix it, I first ran openssl s_client -showcerts -connect AAA.BBB.CCC.DDD:9443 > certs.txtand got following content in file certs.txt:

为了修复它,我首先运行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, now I need to

AFAIK,现在我需要

  1. extract the part of certs.txtbetween -----BEGIN CERTIFICATE-----and -----END CERTIFICATE-----,
  2. modify it so that the certificate name is equal to AAA.BBB.CCC.DDDand
  3. then import the result using keytool -importcert -file fileWithModifiedCertificate(where fileWithModifiedCertificateis the result of operations 1 and 2).
  1. 提取的部分certs.txt之间-----BEGIN CERTIFICATE----------END CERTIFICATE-----
  2. 修改它,使证书名称等于AAA.BBB.CCC.DDD
  3. 然后使用keytool -importcert -file fileWithModifiedCertificate(其中fileWithModifiedCertificate是操作 1 和 2 的结果)导入结果。

Is this correct?

这样对吗?

If so, how exactly can I make the certificate from step 1 work with IP-based adddress (AAA.BBB.CCC.DDD) ?

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

Update 1 (23.10.2013 15:37 MSK):In an answer to a similar question, I read the following:

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

If you're not in control of that server, use its host name (provided that there is at least a CN matching that host name in the existing cert).

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

What exactly does "use" mean?

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

采纳答案by Dmitrii Pisarenko

I fixed the problem by disabling HTTPS checks using the approach presented here:

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

I put following code into the the ISomeServiceclass:

我将以下代码放入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();
    }
}

Since I'm using the https://AAA.BBB.CCC.DDD:9443/ISomeServicefor testing purposes only, it's a good enough solution.

由于我https://AAA.BBB.CCC.DDD:9443/ISomeService仅将用于测试目的,因此这是一个足够好的解决方案。

回答by Bruno

The verification of the certificate identity is performed against what the client requests.

证书身份的验证是根据客户端的请求执行的。

When your client uses https://xxx.xxx.xxx.xxx/something(where xxx.xxx.xxx.xxxis an IP address), the certificate identity is checked against this IP address (in theory, only using an IP SAN extension).

当您的客户端使用https://xxx.xxx.xxx.xxx/something(其中xxx.xxx.xxx.xxx是 IP 地址)时,会根据此 IP 地址检查证书身份(理论上,仅使用 IP SAN 扩展)。

If your certificate has no IP SAN, but DNS SANs (or if no DNS SAN, a Common Name in the Subject DN), you can get this to work by making your client use a URL with that host name instead (or a host name for which the cert would be valid, if there are multiple possible values). For example, if you cert has a name for www.example.com, use https://www.example.com/something.

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

Of course, you'll need that host name to resolve to that IP address.

当然,您需要该主机名才能解析为该 IP 地址。

In addition, if there are any DNS SANs, the CN in the Subject DN will be ignored, so use a name that matches one of the DNS SANs in this case.

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

回答by juanmhidalgo

I've the same problem and solved with this code. I put this code before the first call to my 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");
    }
});

It's simple and works fine.

它很简单而且工作正常。

Hereis the original source.

这里是原始来源。

回答by Shahriar

my problem with getting this error was resolved by using the full URL "qatest.ourCompany.com/webService" instead of just "qatest/webService". Reason was that our security certificate had a wildcard i.e. "*.ourCompany.com". Once I put in the full address the exception went away. Hope this helps.

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

回答by Prakash Soni

Add your IP address in the hosts file.which is in the folder of C:\Windows\System32\drivers\etc. Also add IP and Domain Name of the IP address. example: aaa.bbb.ccc.ddd [email protected]

在 hosts 文件中添加您的 IP 地址。该文件位于 C:\Windows\System32\drivers\etc 文件夹中。还要添加 IP 地址的 IP 和域名。示例:aaa.bbb.ccc.ddd [email protected]

回答by andreycpp

To import the cert:

导入证书:

  1. Extract the cert from the server, e.g. openssl s_client -showcerts -connect AAA.BBB.CCC.DDD:9443 > certs.txtThis will extract certs in PEM format.
  2. Convert the cert into DER format as this is what keytool expects, e.g. openssl x509 -in certs.txt -out certs.der -outform DER
  3. Now you want to import this cert into the system default 'cacert' file. Locate the system default 'cacerts' file for your Java installation. Take a look at How to obtain the location of cacerts of the default java installation?
  4. Import the certs into that cacerts file: sudo keytool -importcert -file certs.der -keystore <path-to-cacerts>Default cacerts password is 'changeit'.
  1. 从服务器中openssl s_client -showcerts -connect AAA.BBB.CCC.DDD:9443 > certs.txt提取证书,例如这将提取 PEM 格式的证书。
  2. 将证书转换为 DER 格式,因为这是 keytool 所期望的,例如 openssl x509 -in certs.txt -out certs.der -outform DER
  3. 现在您想将此证书导入系统默认的“cacert”文件。为您的 Java 安装找到系统默认的“cacerts”文件。看看如何获取默认java安装的cacerts的位置?
  4. 将证书导入该 cacerts 文件:sudo keytool -importcert -file certs.der -keystore <path-to-cacerts>默认 cacerts 密码是“changeit”。

If the cert is issued for an FQDN and you're trying to connect by IP address in your Java code, then this should probably be fixed in your code rather than messing with certificate itself. Change your code to connect by FQDN. If FQDN is not resolvable on your dev machine, simply add it to your hosts file, or configure your machine with DNS server that can resolve this FQDN.

如果证书是为 FQDN 颁发的,并且您尝试通过 Java 代码中的 IP 地址进行连接,那么这可能应该在您的代码中修复,而不是弄乱证书本身。更改您的代码以通过 FQDN 连接。如果 FQDN 在您的开发机器上无法解析,只需将其添加到您的主机文件,或使用可以解析此 FQDN 的 DNS 服务器配置您的机器。

回答by Aditya Bhuyan

I have solved the issue by the following way.

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

1. Creating a class . The class has some empty implementations

1. 创建一个类。该类有一些空的实现

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. Creating a method

2. 创建方法

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. Call the disableSSL() method where the exception is thrown. It worked fine.
  1. 在抛出异常的地方调用 disableSSL() 方法。它工作得很好。

回答by lifesoordinary

You may not want to disable all ssl Verificatication and so you can just disable the hostName verification via this which is a bit less scary than the alternative:

您可能不想禁用所有 ssl 验证,因此您可以通过此禁用主机名验证,这比替代方案更不可怕:

HttpsURLConnection.setDefaultHostnameVerifier(
    SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);

[EDIT]

[编辑]

As mentioned by conapart3 SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIERis now deprecated, so it may be removed in a later version, so you may be forced in the future to roll your own, although I would still say I would steer away from any solutions where all verification is turned off.

正如 conapart3 所提到的,SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER现在已弃用,因此它可能会在更高版本中被删除,因此您将来可能会被迫推出自己的产品,尽管我仍然会说我会避开任何关闭所有验证的解决方案。

回答by Abhishek Galoda

I fixed this issue in a right way by adding the subject alt names in certificate rather than making any changes in code or disabling SSL unlike what other answers suggest here. If you see clearly the exception says the "Subject alt names are missing" so the right way should be to add them

我通过在证书中添加主题替代名称以正确的方式解决了这个问题,而不是在代码中进行任何更改或禁用 SSL,这与此处其他答案建议的不同。如果您清楚地看到异常显示“缺少主题替代名称”,那么正确的方法应该是添加它们

Please look at this link to understand step by step.

请查看此链接以逐步了解

The above error means that your JKS file is missing the required domain on which you are trying to access the application.You will need to Use Open SSL and the key tool to add multiple domains

上述错误意味着您的 JKS 文件缺少您尝试访问应用程序所需的域。您将需要使用 Open SSL 和关键工具添加多个域

  1. Copy the openssl.cnf into a current directory
  2. echo '[ subject_alt_name ]' >> openssl.cnf
  3. echo 'subjectAltName = DNS:example.mydomain1.com, DNS:example.mydomain2.com, DNS:example.mydomain3.com, DNS: localhost'>> openssl.cnf
  4. openssl req -x509 -nodes -newkey rsa:2048 -config openssl.cnf -extensions subject_alt_name -keyout private.key -out self-signed.pem -subj '/C=gb/ST=edinburgh/L=edinburgh/O=mygroup/OU=servicing/CN=www.example.com/[email protected]' -days 365
  5. Export the public key (.pem) file to PKS12 format. This will prompt you for password

    openssl pkcs12 -export -keypbe PBE-SHA1-3DES -certpbe PBE-SHA1-3DES -export -in
    self-signed.pem -inkey private.key -name myalias -out keystore.p12
    
  6. Create a.JKS from self-signed PEM (Keystore)

    keytool -importkeystore -destkeystore keystore.jks -deststoretype PKCS12 -srcstoretype PKCS12 -srckeystore keystore.p12
    
  7. Generate a Certificate from above Keystore or JKS file

    keytool -export -keystore keystore.jks -alias myalias -file selfsigned.crt
    
  8. Since the above certificate is Self Signed and is not validated by CA, it needs to be added in Truststore(Cacerts file in below location for MAC, for Windows, find out where your JDK is installed.)

    sudo keytool -importcert -file selfsigned.crt -alias myalias -keystore /Library/Java/JavaVirtualMachines/jdk1.8.0_171.jdk/Contents/Home/jre/lib/security/cacerts
    
  1. 将 openssl.cnf 复制到当前目录
  2. echo '[ subject_alt_name ]' >> openssl.cnf
  3. echo 'subjectAltName = DNS:example.mydomain1.com, DNS:example.mydomain2.com, DNS:example.mydomain3.com, DNS: localhost'>> openssl.cnf
  4. openssl req -x509 -nodes -newkey rsa:2048 -config openssl.cnf -extensions subject_alt_name -keyout private.key -out self-signed.pem -subj '/C=gb/ST=edinburgh/L=edinburgh/O=mygroup/OU=servicing/CN=www.example.com/[email protected]' -days 365
  5. 将公钥 (.pem) 文件导出为 PKS12 格式。这将提示您输入密码

    openssl pkcs12 -export -keypbe PBE-SHA1-3DES -certpbe PBE-SHA1-3DES -export -in
    self-signed.pem -inkey private.key -name myalias -out keystore.p12
    
  6. 从自签名 PEM(密钥库)创建 a.JKS

    keytool -importkeystore -destkeystore keystore.jks -deststoretype PKCS12 -srcstoretype PKCS12 -srckeystore keystore.p12
    
  7. 从上面的密钥库或 JKS 文件生成证书

    keytool -export -keystore keystore.jks -alias myalias -file selfsigned.crt
    
  8. 由于上面的证书是自签名的并且没有经过 CA 验证,所以需要在 Truststore 中添加它(对于 MAC,在下面位置的 Cacerts 文件,对于 Windows,找出您的 JDK 安装位置。)

    sudo keytool -importcert -file selfsigned.crt -alias myalias -keystore /Library/Java/JavaVirtualMachines/jdk1.8.0_171.jdk/Contents/Home/jre/lib/security/cacerts
    

Original answer posted on this link here.

原始答案张贴在此链接上

回答by Markus

This is an old question, yet I had the same problem when moving from JDK 1.8.0_144 to jdk 1.8.0_191

这是一个老问题,但是从 JDK 1.8.0_144 移动到 jdk 1.8.0_191 时我遇到了同样的问题

We found a hint in the changelog:

我们在变更日志中发现了一个提示:

Changelog

变更日志

we added the following additional system property, which helped in our case to solve this issue:

我们添加了以下附加系统属性,这在我们的案例中有助于解决此问题:

-Dcom.sun.jndi.ldap.object.disableEndpointIdentification=true