如何解决“ java.security.cert.CertificateException:不存在使用者替代名称”错误?


108

我有一个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)时,出现异常java.security.cert.CertificateException: No subject alternative names present

要修复它,我首先运行openssl s_client -showcerts -connect AAA.BBB.CCC.DDD:9443 > certs.txt并在file中得到以下内容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. 提取的部分certs.txt之间-----BEGIN CERTIFICATE----------END CERTIFICATE-----
  2. 对其进行修改,以使证书名称等于AAA.BBB.CCC.DDD
  3. 然后使用导入结果keytool -importcert -file fileWithModifiedCertificatefileWithModifiedCertificate操作1和2的结果在哪里)。

这样对吗?

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

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

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

“使用”到底是什么意思?

Answers:


153

我通过使用此处介绍的方法禁用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仅将用作测试目的,所以这是一个足够好的解决方案。


上面提到的链接中提到的方法似乎已被阻止(nakov.com/blog/2009/07/16/…)。任何人都可以更新链接吗?
约翰

我尝试在执行此过程时禁用验证,但是针对SSL协议的浏览器验证(Poodle漏洞)为我提供了:ssl_error_no_cypher_overlap。有任何想法吗?
will824 2015年

您好,我收到org.springframework.web.client.HttpClientErrorException:禁止使用403

73
禁用HTTPS检查不是“解决方案”。您应该说我找到了一个“补丁”。
2016年

2
这将导致Pre_prod和Production上的漏洞。1.在Windows主机文件上输入主机名2.或在证书的“使用者替代名称”字段中添加IP或FQDN名称
Shankar

34

我有同样的问题,并用这段代码解决了。我将此代码放在第一次调用Web服务之前。

javax.net.ssl.HttpsURLConnection.setDefaultHostnameVerifier(
new javax.net.ssl.HostnameVerifier(){

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

这很简单,而且效果很好。

是原始来源。


3
或者return hostname.equals("localhost");,如果您要这样做,则可以用替换verify()函数的整个主体。该if完全是多余的。
CVn 2015年

这是一个简单快速的修复程序,使我们能够在没有适当证书的供应商测试环境中进行测试。太感谢了!
dacDave

对我有用的@ JuanM.Hidalgo,将代码放在调用之前HttpsURLConnection connection = (HttpsURLConnection) obj.openConnection();。另外,这会忽略每个证书吗?自从我看到这种解决方法易受安全性的影响。谢谢!
ThunderWiring '16

这个答案解决了我的SSL证书异常和非LDH字符问题,我可以看到打开的JDK中报告了一个错误,bugs.openjdk.java.net / browse
Akhil S Kamath

28

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

我们在变更日志中找到了一个提示:

变更日志

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

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

25

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

当客户端使用时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

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

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


1
我无法通过访问该服务http://www.example.com/someservice。为了使证书能够与基于IP的地址(https://AAA.BBB.CCC.DDD:9443/ISomeService)配合使用,是否需要将所有CN字段都设置为AAA.BBB.CCC.DDD(在上面的文件中someSubdomain.someorganisation.com由替换AAA.BBB.CCC.DDD)并导入生成的证书文件是否正确?
Mentiflectax

如果您无法控制服务器,则不能对CN或证书做任何事情。
布鲁诺

出于测试目的访问IP地址,您可以临时修改/etc/hosts文件或等效文件
tyoc213

1
在我的代码中,我正在将请求发送到公共IP,但是证书CN是主机名。因此,在我的代码中,我用主机名替换了IP,并配置了/ etc / hosts将此主机名与IP关联。解决了 !
Leopold Gault

15

导入证书:

  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”。

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


“如果FQDN在您的开发机上无法解析,只需将其添加到您的主机文件中即可” –帮助。非常感谢!
Woland

我做了同样的事情,但是问题仍然存在。使用这种方法还能做其他事情吗?
pkgajulapalli

9

我以正确的方式解决了此问题,方法是在证书中添加主题alt名称,而不是像在此其他答案所示的那样对代码进行任何更改或禁用SSL。如果您清楚地看到异常,则说明“缺少主题名称”,因此正确的方法应该是添加它们

请查看此链接以逐步了解

上面的错误表示您的JKS文件缺少尝试访问该应用程序所需的域。您将需要使用Open SSL和密钥工具来添加多个域

  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/emailAddress=postmaster@example.com' -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中。

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

原始答案发布在此链接上


4

您可能不希望禁用所有ssl验证,因此您可以通过此方法禁用hostName验证,这比其他方法要容易一些:

HttpsURLConnection.setDefaultHostnameVerifier(
    SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);

[编辑]

正如conapart3所述,SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER现在已弃用,因此可能会在以后的版本中将其删除,因此您将来可能会被迫推出自己的版本,尽管我仍会说我会避免使用所有已关闭所有验证功能的解决方案。


2
SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER现在已弃用。
conapart3

3

我使用完整网址“ qatest.ourCompany.com/webService”而不是仅使用“ qatest / webService”解决了出现此错误的问题。原因是我们的安全证书具有通配符,即“ * .ourCompany.com”。一旦我输入了完整的地址,例外就消失了。希望这可以帮助。


2

已经在https://stackoverflow.com/a/53491151/1909708中回答了它。

这将失败,因为证书的通用名(CN在certification中Subject)或任何其他名称(Subject Alternative Name在证书中)都与目标主机名或IP地址不匹配。

例如,从JVM,当尝试连接到IP地址(WW.XX.YY.ZZ)而不是DNS名称(https://stackoverflow.com)时,HTTPS连接将失败,因为存储在java truststore中的证书cacerts需要公用名(或证书替代名称,例如stackexchange.com或* .stackoverflow.com等)以匹配目标地址。

请检查:https : //docs.oracle.com/javase/8/docs/technotes/guides/security/jsse/JSSERefGuide.html#HostnameVerifier

    HttpsURLConnection urlConnection = (HttpsURLConnection) new URL("https://WW.XX.YY.ZZ/api/verify").openConnection();
    urlConnection.setSSLSocketFactory(socketFactory());
    urlConnection.setDoOutput(true);
    urlConnection.setRequestMethod("GET");
    urlConnection.setUseCaches(false);
    urlConnection.setHostnameVerifier(new HostnameVerifier() {
        @Override
        public boolean verify(String hostname, SSLSession sslSession) {
            return true;
        }
    });
    urlConnection.getOutputStream();

上面,传递了一个HostnameVerifier始终返回的实现对象true

new HostnameVerifier() {
        @Override
        public boolean verify(String hostname, SSLSession sslSession) {
            return true;
        }
    }

1

对于Spring Boot RestTemplate

  • 添加org.apache.httpcomponents.httpcore依赖
  • 使用NoopHostnameVerifier了SSL工厂:

    SSLContext sslContext = new SSLContextBuilder()
            .loadTrustMaterial(new URL("file:pathToServerKeyStore"), storePassword)
    //        .loadKeyMaterial(new URL("file:pathToClientKeyStore"), storePassword, storePassword)
            .build();
    SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE);
    CloseableHttpClient client = HttpClients.custom().setSSLSocketFactory(socketFactory).build();
    HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(client);
    RestTemplate restTemplate = new RestTemplate(factory);

0

如果得到了相同的错误消息,我想到了这个问题。但是,在我的情况下,我们有两个URL,它们具有不同的子域(http://example1.xxx.com/someservicehttp://example2.yyy.com/someservice),它们被定向到同一服务器。该服务器的* .xxx.com域只有一个通配符证书。通过第二个域使用服务时,找到的证书(* .xxx.com)与请求的域(* .yyy.com)不匹配,并且发生错误。

在这种情况下,我们不应尝试通过降低SSL安全性来修复此类错误消息,而应检查服务器及其上的证书。


0
public class RESTfulClientSSL {

    static TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager() {
        @Override
        public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
            // TODO Auto-generated method stub
        }

        @Override
        public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
            // TODO Auto-generated method stub
        }

        @Override
        public X509Certificate[] getAcceptedIssuers() {
            // TODO Auto-generated method stub
            return null;
        }
    }};

    public class NullHostNameVerifier implements HostnameVerifier {
        /*
         * (non-Javadoc)
         *
         * @see javax.net.ssl.HostnameVerifier#verify(java.lang.String,
         * javax.net.ssl.SSLSession)
         */
        @Override
        public boolean verify(String arg0, SSLSession arg1) {
            // TODO Auto-generated method stub
            return true;
        }
    }

    public static void main(String[] args) {

        HttpURLConnection connection = null;
        try {

            HttpsURLConnection.setDefaultHostnameVerifier(new RESTfulwalkthroughCer().new NullHostNameVerifier());
            SSLContext sc = SSLContext.getInstance("SSL");
            sc.init(null, trustAllCerts, new SecureRandom());
            HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());


            String uriString = "https://172.20.20.12:9443/rest/hr/exposed/service";
            URL url = new URL(uriString);
            connection = (HttpURLConnection) url.openConnection();
            connection.setRequestMethod("GET");
            //connection.setRequestMethod("POST");

            BASE64Encoder encoder = new BASE64Encoder();
            String username = "admin";
            String password = "admin";
            String encodedCredential = encoder.encode((username + ":" + password).getBytes());
            connection.setRequestProperty("Authorization", "Basic " + encodedCredential);

            connection.connect();
            BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
            int responseCode = connection.getResponseCode();
            if (responseCode == HttpURLConnection.HTTP_OK) {
                StringBuffer stringBuffer = new StringBuffer();
                String line = "";
                while ((line = reader.readLine()) != null) {
                    stringBuffer.append(line);
                }
                String content = stringBuffer.toString();
                System.out.println(content);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (connection != null) {
                connection.disconnect();
            }
        }
    }
}

1
请在代码中添加说明文字。请参阅stackoverflow.com/help/how-to-answer
jasie

1
这是一个安全漏洞。这就是首先禁用证书验证的方式。复制粘贴此解决方案的每个人都将在其软件中创建一个安全漏洞。
Marek Puchalski

0

我在springboot中经历了2种方式的SSL。我已经将所有正确的配置服务配置为tomcat服务器和服务调用方RestTemplate。但我收到错误消息“ java.security.cert.CertificateException:不存在使用者替代名称”

经过解决方案后,我发现JVM需要此证书,否则会产生握手错误。

现在,如何将其添加到JVM。

转到jre / lib / security / cacerts文件。我们需要将服务器证书文件添加到jvm的cacerts文件中。

通过Windows中的命令行将服务器证书添加到cacerts文件的命令。

C:\ Program Files \ Java \ jdk1.8.0_191 \ jre \ lib \ security> keytool -import -noprompt -trustcacerts -alias sslserver -file E:\ spring_cloud_sachin \ ssl_keys \ sslserver.cer -keystore cacerts -storepass changeit

检查服务器证书是否已安装:

C:\ Program Files \ Java \ jdk1.8.0_191 \ jre \ lib \ security> keytool -list -keystore cacerts

您可以看到已安装证书的列表:

有关更多详细信息:https : //sachin4java.blogspot.com/2019/08/javasecuritycertcertificateexception-no.html


0

在证书中添加主机条目以及与CN对应的ip

CN = someSubdomain.someorganisation.com

现在,使用您尝试访问URL的CN名称更新ip。

它为我工作。


0

此代码将像charm一样工作,并在其余的代码中使用restTemple对象。

  RestTemplate restTemplate = new RestTemplate();   
  TrustStrategy acceptingTrustStrategy = new TrustStrategy() {
            @Override
            public boolean isTrusted(java.security.cert.X509Certificate[] x509Certificates, String s) {
                return true;
            }

        };

        SSLContext sslContext = null;
        try {
            sslContext = org.apache.http.ssl.SSLContexts.custom().loadTrustMaterial(null, acceptingTrustStrategy)
                    .build();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (KeyManagementException e) {
            e.printStackTrace();
        } catch (KeyStoreException e) {
            e.printStackTrace();
        }
        SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory(sslContext, new NoopHostnameVerifier());
        CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(csf).build();
        HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
        requestFactory.setHttpClient(httpClient);

        restTemplate.setRequestFactory(requestFactory);
}

0

当您同时具有CN和使用者备用名称(SAN)的证书时,如果您基于CN内容提出请求,则该特定内容也必须存在于SAN下,否则将因相关错误而失败。

就我而言,CN有一些东西,SAN还有其他东西。我必须使用SAN URL,然后一切正常。


-3

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


-5

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

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.创建方法

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. 在抛出异常的地方调用disableSSL()方法。工作正常。
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.