如何修复“java.security.cert.CertificateException:没有主题替代名称出现”错误?

时间:2013-10-23 11:23:27

标签: java ssl https certificate ssl-certificate

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

当我连接到服务网址(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并在文件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 fileWithModifiedCertificate导入结果(其中fileWithModifiedCertificate是操作1和2的结果)。
  4. 这是对的吗?

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

    更新1(2013年10月23日15:37 MSK):在回答similar question时,我读了以下内容:

      

    如果您不控制该服务器,请使用其主机名(提供   至少有一个CN匹配现有的主机名   CERT)。

    究竟是什么"使用"意思?

21 个答案:

答案 0 :(得分:132)

我通过使用here提供的方法禁用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进行测试,因此这是一个很好的解决方案。

答案 1 :(得分:28)

我遇到了同样的问题并使用此代码解决了问题。 我在第一次调用我的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");
    }
});

它很简单,工作正常。

Here是原始来源。

答案 2 :(得分:20)

证书身份的验证是针对客户端请求进行的。

当您的客户端使用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匹配的名称。

答案 3 :(得分:15)

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

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

Changelog

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

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

答案 4 :(得分:12)

导入证书:

  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'文件。找到系统默认' cacerts'用于Java安装的文件。看看How to obtain the location of cacerts of the default java installation?
  4. 将证书导入该cacerts文件:sudo keytool -importcert -file certs.der -keystore <path-to-cacerts>默认cacerts密码为&#39; changeit&#39;。
  5. 如果为FQDN颁发了证书,并且您尝试通过Java代码中的IP地址进行连接,那么这应该在您的代码中修复,而不是弄乱证书本身。将您的代码更改为通过FQDN连接。如果您的开发计算机上无法解析FQDN,只需将其添加到您的hosts文件,或使用可以解析此FQDN的DNS服务器配置您的计算机。

答案 5 :(得分:3)

我通过使用完整的网址&#34; qatest.ourCompany.com/webService"解决了我收到此错误的问题。而不只是&#34; qatest / webService&#34;。原因是我们的安全证书有一个通配符,即&#34; *。ourCompany.com&#34;。一旦我输入完整地址,异常就消失了。希望这会有所帮助。

答案 6 :(得分:3)

您可能不想禁用所有ssl Verificatication,因此您可以通过此方法禁用hostName验证,这比其他选项更加可怕:

HttpsURLConnection.setDefaultHostnameVerifier(
    SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);

[编辑]

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

答案 7 :(得分:3)

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

请查看此link to understand step by step

上述错误表示您的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中(对于Windows,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
    

原始答案发布于 on this link here

答案 8 :(得分:1)

我还面临着带有自签名证书的相同问题。通过参考上述几种解决方案,我尝试使用正确的CN(即服务器的IP地址)重新生成证书。但是对于我来说仍然无效。 最后,我尝试通过以下提到的命令向其添加SAN地址来重新生成证书

**keytool -genkey -keyalg RSA -keystore keystore.jks -keysize 2048 -alias <IP_ADDRESS> -ext san=ip:<IP_ADDRESS>**

之后,我启动服务器并通过下面提到的openssl命令下载了客户端证书

**openssl s_client -showcerts -connect <IP_ADDRESS>:443 < /dev/null | openssl x509 -outform PEM > myCert.pem**

然后我通过以下提到的命令将此客户端证书导入到我的客户端计算机的Java默认密钥库文件(证书)中

**keytool -import -trustcacerts -keystore /home/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.242.b08-1.el7.x86_64/jre/lib/security/cacerts -alias <IP_ADDRESS> -file ./mycert.pem**

答案 9 :(得分:1)

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

之所以失败,是因为证书公用名(证书CN中的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;
        }
    }

答案 10 :(得分:0)

正如之前有人指出的那样,我在创建 RestTemplate 对象之前添加了以下代码(使用 lambda),并且它工作正常。 IT 仅适用于我的内部测试课程,因此我将为生产代码提供更好的解决方案。

javax.net.ssl.HttpsURLConnection.setDefaultHostnameVerifier(
            (hostname, sslSession) -> true);

答案 11 :(得分:0)

我已经解决了

MqttException(0)-javax.net.ssl.SSLHandshakeException:否 证书上的subjectAltNames匹配

在服务器证书(具有CN = example.com)中添加一个(可以添加多个)备用使用者名称的错误,该证书名称随后打印部分证书,如下所示:

Subject Alternative Name:
DNS: example.com

我在Windows上使用KeyExplorer生成服务器证书。 您可以按照this link添加备用主题名称(紧随其后的是添加名称)。

答案 12 :(得分:0)

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

就我而言,CN还有其他东西,SAN还有其他东西。我必须使用SAN URL,然后它才能正常工作。

答案 13 :(得分:0)

此代码将像魅力一样工作,并将restTemple对象用于其余代码。

tarantool

答案 14 :(得分:0)

对于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);
    

答案 15 :(得分:0)

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

CN = someSubdomain.someorganisation.com

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

对我有用。

答案 16 :(得分:0)

我在springboot中经历了两种方式的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> 密钥工具-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

答案 17 :(得分:0)

awk  '
!NF && !file{
  next
}
/^END/{
  file=""
  close(file)
  next
}
/^\[site[0-9]+\]/{
  gsub(/\]|\[|[[:space:]]+$/,"")
  file=$0".txt"
  next
}
{
  print > (file)
}
'   Input_file

答案 18 :(得分:0)

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

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

答案 19 :(得分:-2)

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

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()方法,抛出异常。它运作良好。

答案 20 :(得分:-2)

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