OkHttp javax.net.ssl.SSLPeerUnverifiedException:主机名 domain.com 未验证

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/31917988/
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-11 11:46:16  来源:igfitidea点击:

OkHttp javax.net.ssl.SSLPeerUnverifiedException: Hostname domain.com not verified

javaandroidsslokhttp

提问by just_user

I've been trying for days to get this working. I'm trying to connect to my server over httpswith a self signed certificate. I don't think there is any pages or examples that I haven't read by now.

我一直在努力让这个工作好几天。我正在尝试使用自签名证书通过https连接到我的服务器。我认为现在没有任何页面或示例我没有读过。

What I have done:

我做了什么:

  1. Created bks keystore by following this tutorial: http://blog.crazybob.org/2010/02/android-trusting-ssl-certificates.html
  1. 按照本教程创建 bks 密钥库:http: //blog.crazybob.org/2010/02/android-trusting-ssl-certificates.html

It uses openssl s_client -connect domain.com:443to get the certificate from the server. Then creates a bks keystore using bouncy castle.

它用于openssl s_client -connect domain.com:443从服务器获取证书。然后使用充气城堡创建一个 bks 密钥库。

  1. Reading created keystore from raw folder adding it to sslfactory and and then to OkHttpClient. Like this:

    public ApiService() {
        mClient = new OkHttpClient();
        mClient.setConnectTimeout(TIMEOUT_SECONDS, TimeUnit.SECONDS);
        mClient.setReadTimeout(TIMEOUT_SECONDS, TimeUnit.SECONDS);
        mClient.setCache(getCache());
        mClient.setCertificatePinner(getPinnedCerts());
        mClient.setSslSocketFactory(getSSL());
    }
    
    protected SSLSocketFactory getSSL() {
        try {
            KeyStore trusted = KeyStore.getInstance("BKS");
            InputStream in = Beadict.getAppContext().getResources().openRawResource(R.raw.mytruststore);
            trusted.load(in, "pwd".toCharArray());
            SSLContext sslContext = SSLContext.getInstance("TLS");
            TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
            trustManagerFactory.init(trusted);
            sslContext.init(null, trustManagerFactory.getTrustManagers(), null);
            return sslContext.getSocketFactory();
        } catch(Exception e) {
            e.printStackTrace();
        }
        return null;
    }
    
    public CertificatePinner getPinnedCerts() {
        return new CertificatePinner.Builder()
                .add("domain.com", "sha1/theSha=")
                .build();
    }
    
  2. This for some reason this always generates a SSLPeerUnverifiedExceptionwith or without the keystore. And with or without the CertificatePinner.

    javax.net.ssl.SSLPeerUnverifiedException: Hostname domain.com not verified: 0         
     W/System.err﹕ certificate: sha1/theSha=
     W/System.err﹕ DN: 1.2.840.113549.1.9.1=#1610696e666f40626561646963742e636f6d,CN=http://domain.com,OU=development,O=domain,L=Valencia,ST=Valencia,C=ES
     W/System.err﹕ subjectAltNames: []
     W/System.err﹕ at com.squareup.okhttp.internal.http.SocketConnector.connectTls(SocketConnector.java:124)
     W/System.err﹕ at com.squareup.okhttp.Connection.connect(Connection.java:143)
     W/System.err﹕ at com.squareup.okhttp.Connection.connectAndSetOwner(Connection.java:185)
     W/System.err﹕ at com.squareup.okhttp.OkHttpClient.connectAndSetOwner(OkHttpClient.java:128)
     W/System.err﹕ at com.squareup.okhttp.internal.http.HttpEngine.nextConnection(HttpEngine.java:341)
     W/System.err﹕ at com.squareup.okhttp.internal.http.HttpEngine.connect(HttpEngine.java:330)
     W/System.err﹕ at com.squareup.okhttp.internal.http.HttpEngine.sendRequest(HttpEngine.java:248)
     W/System.err﹕ at com.squareup.okhttp.Call.getResponse(Call.java:273)
     W/System.err﹕ at com.squareup.okhttp.Call$ApplicationInterceptorChain.proceed(Call.java:230)
     W/System.err﹕ at com.squareup.okhttp.Call.getResponseWithInterceptorChain(Call.java:201)
     W/System.err﹕ at com.squareup.okhttp.Call.execute(Call.java:81)
     ...
    
  1. 从原始文件夹中读取创建的密钥库,将其添加到 sslfactory,然后添加到 OkHttpClient。像这样:

    public ApiService() {
        mClient = new OkHttpClient();
        mClient.setConnectTimeout(TIMEOUT_SECONDS, TimeUnit.SECONDS);
        mClient.setReadTimeout(TIMEOUT_SECONDS, TimeUnit.SECONDS);
        mClient.setCache(getCache());
        mClient.setCertificatePinner(getPinnedCerts());
        mClient.setSslSocketFactory(getSSL());
    }
    
    protected SSLSocketFactory getSSL() {
        try {
            KeyStore trusted = KeyStore.getInstance("BKS");
            InputStream in = Beadict.getAppContext().getResources().openRawResource(R.raw.mytruststore);
            trusted.load(in, "pwd".toCharArray());
            SSLContext sslContext = SSLContext.getInstance("TLS");
            TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
            trustManagerFactory.init(trusted);
            sslContext.init(null, trustManagerFactory.getTrustManagers(), null);
            return sslContext.getSocketFactory();
        } catch(Exception e) {
            e.printStackTrace();
        }
        return null;
    }
    
    public CertificatePinner getPinnedCerts() {
        return new CertificatePinner.Builder()
                .add("domain.com", "sha1/theSha=")
                .build();
    }
    
  2. 出于某种原因,这总是会生成一个SSLPeerUnverifiedException带或不带密钥库的信息。不管有没有CertificatePinner.

    javax.net.ssl.SSLPeerUnverifiedException: Hostname domain.com not verified: 0         
     W/System.err﹕ certificate: sha1/theSha=
     W/System.err﹕ DN: 1.2.840.113549.1.9.1=#1610696e666f40626561646963742e636f6d,CN=http://domain.com,OU=development,O=domain,L=Valencia,ST=Valencia,C=ES
     W/System.err﹕ subjectAltNames: []
     W/System.err﹕ at com.squareup.okhttp.internal.http.SocketConnector.connectTls(SocketConnector.java:124)
     W/System.err﹕ at com.squareup.okhttp.Connection.connect(Connection.java:143)
     W/System.err﹕ at com.squareup.okhttp.Connection.connectAndSetOwner(Connection.java:185)
     W/System.err﹕ at com.squareup.okhttp.OkHttpClient.connectAndSetOwner(OkHttpClient.java:128)
     W/System.err﹕ at com.squareup.okhttp.internal.http.HttpEngine.nextConnection(HttpEngine.java:341)
     W/System.err﹕ at com.squareup.okhttp.internal.http.HttpEngine.connect(HttpEngine.java:330)
     W/System.err﹕ at com.squareup.okhttp.internal.http.HttpEngine.sendRequest(HttpEngine.java:248)
     W/System.err﹕ at com.squareup.okhttp.Call.getResponse(Call.java:273)
     W/System.err﹕ at com.squareup.okhttp.Call$ApplicationInterceptorChain.proceed(Call.java:230)
     W/System.err﹕ at com.squareup.okhttp.Call.getResponseWithInterceptorChain(Call.java:201)
     W/System.err﹕ at com.squareup.okhttp.Call.execute(Call.java:81)
     ...
    

What am I doing wrong?

我究竟做错了什么?

采纳答案by just_user

I finally got this working with a mix of multiple answers.

我终于得到了这个混合多个答案的工作。

First, the certificates was made wrongly, not sure how. But by creating them using the script in this answermade them work. What was needed was a server certificate and a key. Then the client needed another certificate.

首先,证书是错误的,不知道是怎么做的。但是通过使用此答案中的脚本创建它们使它们起作用。需要的是服务器证书和密钥。然后客户端需要另一个证书。

To use the certificate in android I converted the .pem file to a .crt file like this:

要在 android 中使用证书,我将 .pem 文件转换为 .crt 文件,如下所示:

openssl x509 -outform der -in client.pem  -out client.crt

In android I added the certificate to my OkHttp client like the following:

在 android 中,我将证书添加到我的 OkHttp 客户端,如下所示:

public ApiService() {
    mClient = new OkHttpClient();
    mClient.setConnectTimeout(TIMEOUT_SECONDS, TimeUnit.SECONDS);
    mClient.setReadTimeout(TIMEOUT_SECONDS, TimeUnit.SECONDS);
    mClient.setCache(getCache());
    mClient.setSslSocketFactory(getSSL());
}

protected SSLSocketFactory getSSL() {
    try {
        CertificateFactory cf = CertificateFactory.getInstance("X.509");
        InputStream cert = getAppContext().getResources().openRawResource(R.raw.client);
        Certificate ca = cf.generateCertificate(cert);
        cert.close();

        // creating a KeyStore containing our trusted CAs
        String keyStoreType = KeyStore.getDefaultType();
        KeyStore keyStore = KeyStore.getInstance(keyStoreType);
        keyStore.load(null, null);
        keyStore.setCertificateEntry("ca", ca);

        return new AdditionalKeyStore(keyStore);
    } catch(Exception e) {
        e.printStackTrace();
    }
    return null;
}

The last part with new AdditionalKeyStore()is taken from this very well written answer. Which adds a fallback keystore.

最后一部分new AdditionalKeyStore()来自这个写得很好的答案。这增加了一个后备密钥库。

I hope this might help anyone else! This is the simplest way to get HTTPS working with a self-signed certificate that I have found. Other ways include having a BouncyCastle keystore which seems excessive to me.

我希望这可以帮助其他人!这是使 HTTPS 与我发现的自签名证书一起工作的最简单方法。其他方法包括拥有一个对我来说似乎过多的 BouncyCastle 密钥库。

回答by Jake Hall

I had the same problem, however I needed my application to work on several staging environments, all of which had self signed certs. To make matters worse, they could change those certs on the fly.

我遇到了同样的问题,但是我需要我的应用程序在几个临时环境中工作,所有这些环境都有自签名证书。更糟糕的是,他们可以即时更改这些证书。

To fix this, when connecting to staging only, I added a SSLSocketFactory which trusted all certs. This fixed the java error, however it left me with the okhttp exception noted in this ticket.

为了解决这个问题,当仅连接到登台时,我添加了一个信任所有证书的 SSLSocketFactory。这修复了 java 错误,但是它给我留下了这张票中提到的 okhttp 异常。

To avoid this error, I needed to add one more customization to my okHttpClient. This fixed the error for me.

为了避免这个错误,我需要为我的 okHttpClient 添加更多的自定义。这为我修复了错误。

okHttpClient.setHostnameVerifier(new HostnameVerifier() {
            @Override
            public boolean verify(String hostname, SSLSession session) {
                return true;
            }
        });

回答by KayKay

This issue is solved by setting setHostNameVerifierto okHttpBuilder. Make sure verify method should return true.

这个问题可以通过设置setHostNameVerifier来解决okHttpBuilder。确保验证方法应返回 true。

Sample:

样本:

okHttpClient.setHostnameVerifier(new HostnameVerifier() {
    @Override
    public boolean verify(String hostname, SSLSession session) {
        return true;
    }
});

OkHttpClient.Builder builder = new OkHttpClient.Builder();
    builder.hostnameVerifier(new HostnameVerifier() {
        @Override
        public boolean verify(String hostname, SSLSession session) {
            return true;
        }
    });
OkHttpClient client = builder.build();

回答by Rahul Goel

Please check if CN name on the client cert is added to the Subject alternative name. I had the same issue

请检查客户端证书上的 CN 名称是否已添加到主题备用名称。我遇到过同样的问题

回答by qrtLs

During cert generation the subjectAltNamemust be set if the uri is an ip to not fall through validation.

在证书生成期间,subjectAltName如果 uri 是一个不通过验证的 ip,则必须设置。

"In some cases, the URI is specified as an IP address rather than a hostname. In this case, the iPAddress subjectAltName must be present in the certificate and must exactly match the IP in the URI." RFC(mentioned by Bas in comment)

“在某些情况下,URI 被指定为 IP 地址而不是主机名。在这种情况下,iPAddress subjectAltName 必须存在于证书中,并且必须与 URI 中的 IP 完全匹配。” RFC(巴斯在评论中提到)

Instead of fiddeling client side with HostnameVerifieror else, reiusse the self-signed cert (which we have control over) via:

而不是摆弄客户端HostnameVerifier或其他,通过以下方式重新使用自签名证书(我们可以控制):

openssl req \
-newkey rsa:2048 \
-nodes \
-x509 \
-days 36500 -nodes \
-addext "subjectAltName = IP.1:1.2.3.4" \
-keyout /etc/ssl/private/nginx-selfsigned2.key \
-out /etc/ssl/certs/nginx-selfsigned2.crt

Addon, if on android one also needs to trust the cert:

插件,如果在 android 上还需要信任证书:

the crt is pem format and can be imported into android via
<?xml version="1.0" encoding="utf-8"?>
    <base-config cleartextTrafficPermitted="false">
        <trust-anchors>
            <certificates src="@raw/nginx_selfsigned2" />
            <certificates src="system" />
        </trust-anchors>
    </base-config>
</network-security-config>

Thus we verify the cert is from a trusted source And previously by hostname verification (via SAN) ensured the server we talk to presents the right cert for his ip.

因此,我们验证证书来自受信任的来源,之前通过主机名验证(通过 SAN)确保我们与之交谈的服务器为其 ip 提供正确的证书。

more here: https://developer.android.com/training/articles/security-confighttps://developer.android.com/training/articles/security-ssl.html#SelfSigned

更多信息:https: //developer.android.com/training/articles/security-config https://developer.android.com/training/articles/security-ssl.html#SelfSigned