SSL证书验证:javax.net.ssl.SSLHandshakeException

9

我正在尝试通过 Jersey Client 调用一个 HTTPS REST API。在开发过程中,我遇到了以下错误:

Exception in thread "main" com.sun.jersey.api.client.ClientHandlerException: javax.net.ssl.SSLHandshakeException: java.security.cert.CertificateException: No name matching mvn.signify.abc.com found
    at com.sun.jersey.client.urlconnection.URLConnectionClientHandler.handle(URLConnectionClientHandler.java:149)
    at com.sun.jersey.api.client.Client.handle(Client.java:648)
    at com.sun.jersey.api.client.WebResource.handle(WebResource.java:670)
    at com.sun.jersey.api.client.WebResource.access$200(WebResource.java:74)
    at com.sun.jersey.api.client.WebResource$Builder.get(WebResource.java:503)
    at com.lftechnology.sbworkbench.utility.utils.PingFederateUtility.main(PingFederateUtility.java:32)
Caused by: javax.net.ssl.SSLHandshakeException: java.security.cert.CertificateException: No name matching mvn.signify.abc.com found

我花了一点时间在Google上搜索,发现有很多解决方法都可以解决这个问题。

  1. 使用Jersey Client进行HTTPS
  2. https://gist.github.com/outbounder/1069465
  3. 如何解决"java.security.cert.CertificateException: No subject alternative names present"错误?
  4. http://www.mkyong.com/webservices/jax-ws/java-security-cert-certificateexception-no-name-matching-localhost-found/
  5. http://java.globinch.com/enterprise-java/security/fix-java-security-certificate-exception-no-matching-localhost-found/

它们来自不同的领域,但都有一个通用的解决方案。

场景

我目前在开发环境中使用自己创建的自签名证书。因此,这个问题必然会出现。

问题

上述解决方案重点在于跳过/允许验证所有证书。

但是当我将其移动到生产环境时,我可以从可信源获得有效签名证书。

  1. 那么这些解决方案对我在生产中的使用有帮助吗?
  2. 跳过SSL验证是否可以接受?
  3. 还有其他替代方法可以为开发和生产环境实现共同的解决方案吗?

P.S.

我使用的解决方案是,

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();
}

我随后结合 Jersey 使其正常工作。目前工作得非常好。

所以,问题又来了。这个解决方案是否适用于生产环境?不过,如果您不想修改返回的实体,则最好以只读模式获取实体。这将允许Hibernate放弃关联的分离状态,该状态用于检测实体状态修改的脏检查机制。此外,只读实体在刷新期间被忽略。


3
跳过SSL验证可以吗?最好不要......或者你没有听说过苹果的GOTO FAIL漏洞? - Jack
3
再问一遍问题,这个解决方案能在生产环境中使用吗?不行,这是不负责任和极其疏忽的。 - jww
3个回答

14
我目前在开发环境中使用自行创建的自签名证书。...javax.net.ssl.SSLHandshakeException: java.security.cert.CertificateException: No name matching dev.ppc.lftechnology.com found。
看起来该自签名证书存在问题。
以下是我用于创建自签名证书和证书请求的OpenSSL CONF文件,可用于测试。将其保存为example-com.conf。在[alternate_names]下更改DNS名称以适应您的喜好。您甚至可以将localhost、localhost.localdomain和127.0.0.1放在那里进行测试。
如果您想创建一个自签名证书,请使用:
openssl req -config example-com.conf -new -x509 -newkey rsa:2048 \
    -nodes -keyout example-com.key.pem -days 365 -out example-com.cert.pem

如果您希望创建一个由可信机构签名的签名请求(CSR),那么请使用以下方法:

openssl req -config example-com.conf -new -newkey rsa:2048 \
    -nodes -keyout example-com.key.pem -days 365 -out example-com.req.pem

自签名证书和签名请求的区别在于 -x509 选项。使用 -x509,会创建一个自签名证书。没有 -x509 则会创建一个请求。
如果您想打印您的自签名证书或请求以查看其中实际内容,则可以使用:
openssl x509 -in example-com.cert.pem -text -noout
openssl req -in example-com.req.pem -text -noout

如果您想测试服务器,请使用 s_client 命令:
openssl s_client -connect <server>:<port> -CAfile <trust-anchor.pem>

上述命令应该以类似于Verify OK (0)的消息结束。如果您没有收到Verify OK (0),请修复您的测试环境。一旦OpenSSL成功完成,则成为您的基准。


[ req ]
default_bits        = 2048
default_keyfile     = server-key.pem
distinguished_name  = subject
req_extensions      = req_extensions
x509_extensions     = cert_extensions
string_mask         = utf8only

[ subject ]
countryName         = Country Name (2 letter code)
countryName_default     = US

stateOrProvinceName     = State or Province Name (full name)
stateOrProvinceName_default = NY

localityName            = Locality Name (eg, city)
localityName_default        = New York

organizationName         = Organization Name (eg, company)
organizationName_default    = Example, LLC

# Use a friendly name here. Its presented to the user.
#   The server's DNS name show up in Subject Alternate Names. Plus, 
#   DNS names here is deprecated by both IETF and CA/Browser Forums.
commonName          = Common Name (e.g. server FQDN or YOUR name)
commonName_default      = Example Company

emailAddress            = Email Address
emailAddress_default        = test@example.com

[ cert_extensions ]

subjectKeyIdentifier        = hash
authorityKeyIdentifier  = keyid,issuer

basicConstraints        = CA:FALSE
keyUsage            = digitalSignature, keyEncipherment
# extendedKeyUsage  = serverAuth
subjectAltName          = @alternate_names
nsComment           = "OpenSSL Generated Certificate"

[ req_extensions ]

subjectKeyIdentifier        = hash

basicConstraints        = CA:FALSE
keyUsage            = digitalSignature, keyEncipherment
# extendedKeyUsage  = serverAuth
subjectAltName          = @alternate_names
nsComment           = "OpenSSL Generated Certificate"

[ alternate_names ]

DNS.1       = example.com
DNS.2       = www.example.com
DNS.3       = mail.example.com
DNS.4       = ftp.example.com

# Add these if you need them. But usually you don't want them or
#   need them in production. You may need them for development.
# DNS.5       = localhost
# DNS.6       = localhost.localdomain
# DNS.7       = 127.0.0.1

跳过SSL验证是可以的吗?

不行。这非常不负责任。如果您不打算正确使用PKIX,那么为什么要使用它呢?

这让人想起:世界上最危险的代码:验证非浏览器软件中的SSL证书


HostnameVerifier allHostsValid = new HostnameVerifier() {
    public boolean verify(String hostname, SSLSession session) {
        return true;
    }
};

最好将自签名证书加载到密钥库中(或加载您的私有CA),然后将其传递给SSLContext.init。这样,一切就如预期的那样工作,无需信任所有内容或从verify返回true

Bruno和EJP有很多关于该主题的答案。


有哪些其他替代方法可以实现开发和生产环境的公共解决方案?

使用一个链回受信任根的规范证书。

对于测试,可以创建一个自签名证书。或者,创建一个证书请求,并由内部PKI中的内部CA签署。在这种情况下,需要信任自签名证书或信任内部CA。

对于生产,可以使用由CA Zoo成员之一签名的证书,以便外部组织也信任它。StartComCACert提供免费的Class 1证书。

Class 1证书通常是域验证,并且不允许通配符。虽然Class 1是免费发放的,但他们因吊销而收费,因为这是成本所在。

如果需要通配符,则通常需要购买Class 2或更高级别的证书。


谢谢@jww,这是一个很棒的答案。我稍微更新了一下关于我提到的解决方案的问题。 - Runcorn
感谢您宝贵的见解,@jww。 - Runcorn
嗨@jww,对于本地主机{DNS},它运行良好。但是当我提供像192.168.1.56这样的本地IP地址时,它无法工作。请帮帮我。 - Srinivasu
@Srinivasu - 添加类似于 DNS.5 = 192.168.1.56 的内容。如果仍然无法工作,则与浏览器有关。但我还没有找到浏览器对IP地址(与DNS名称相比)感到不满的确切原因。Google的Ryan Sleevi在draft-ietf-websec-key-pinning的评论中解释了这一点,但我还没有消化它。 - jww
@jww 浏览器没有任何问题,Jersey出现了问题。 - Srinivasu

6

@jww正确回答了这个问题

跳过SSL验证可以吗?不行。那非常不负责任。

然而,在某些情况下,您可能无法控制服务器以安装有效证书。如果服务器属于其他人,并且您信任该服务器,则更好的解决方案是使用“白名单”仅验证受信任服务器的证书,否则使用正常验证。

public static class WhitelistHostnameVerifier implements HostnameVerifier {
    private static final HostnameVerifier defaultHostnameVerifier = HttpsURLConnection.getDefaultHostnameVerifier();
    private Set<String> trustedHosts;

    public WhitelistHostnameVerifier(Set<String> trustedHosts) {
        this.trustedHosts = trustedHosts;
    }

    @Override
    public boolean verify(String hostname, SSLSession session) {
        if (trustedHosts.contains(hostname)) {
            return true;
        } else {
            return defaultHostnameVerifier.verify(hostname, session);
        }
    }
}

只需要安装一次:

HttpsURLConnection.setDefaultHostnameVerifier(
    new WhitelistHostnameVerifier(Sets.newHashSet("trustedhost.mydomain.com")));

如果您要禁用安全检查,请不要全局禁用...

1

在创建自签名证书时,Java版本1.7.0_60-b19的Java Keytool存在标签软件错误。请参考这些说明。

https://www.sslshopper.com/article-how-to-create-a-self-signed-certificate-using-java-keytool.html

当系统提示“你的名字是什么?”时,你应该输入常见名称或(服务器的完全限定域名),而不是输入你的名字。
[root@localhost ~]# keytool -genkey -keyalg RSA -alias myalias -keystore keystore.jks -storepass XXXXXX -validity 360 -keysize 2048
What is your first and last name?
  [Unknown]:  Angus MacGyver
What is the name of your organizational unit?
  [Unknown]:  My Department
What is the name of your organization?
  [Unknown]:  My Company
What is the name of your City or Locality?
  [Unknown]:  My City
What is the name of your State or Province?
  [Unknown]:  My State
What is the two-letter country code for this unit?
  [Unknown]:  US
Is CN=Angus MacGyver, OU=My Department, O=My Company, L=My City, ST=My State, C=US correct?
  [no]:  yes
Enter key password for <selfsigned>
        (RETURN if same as keystore password):XXXXXX
Re-enter new password:XXXXXX

您可以通过调用以下内容来验证“CN”(通用名称)属性是否正确设置:
[root@localhost ~]# keytool -v -list -keystore keystore.jks

Find Java version:

[root@localhost ~]# java -version
java version "1.7.0_60"
Java(TM) SE Runtime Environment (build 1.7.0_60-b19)
Java HotSpot(TM) Client VM (build 24.60-b09, mixed mode, sharing)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接