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

jus*_*ser 18 java ssl android okhttp

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

我做了什么:

  1. 按照本教程创建了bks密钥库:http://blog.crazybob.org/2010/02/android-trusting-ssl-certificates.html

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

  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();
    }
    
    Run Code Online (Sandbox Code Playgroud)
  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$1.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)
     ...
    
    Run Code Online (Sandbox Code Playgroud)

我究竟做错了什么?

Jak*_*all 20

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

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

为了避免这个错误,我需要为okHttpClient添加一个自定义.这为我修复了错误.

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

  • 这不安全!我知道我没有在我的问题中指明这一点.我知道它有效,我早先用过它.但我现在都长大了,想要完全安全的https.:) (16认同)
  • 当然,这并不安全-明确绕过https证书验证。仅应在要避免ssl验证的预生产环境中使用它,而不要在生产版本中使用。 (3认同)
  • `setHostnameVerifier`未编译 (2认同)

jus*_*ser 13

我终于让这个工作混合了多个答案.

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

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

openssl x509 -outform der -in client.pem  -out client.crt
Run Code Online (Sandbox Code Playgroud)

在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;
}
Run Code Online (Sandbox Code Playgroud)

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

我希望这可以帮助其他人!这是让HTTPS使用我找到的自签名证书的最简单方法.其他方式包括拥有一个BouncyCastle密钥库,这对我来说似乎太过分了.


小智 10

通过设置setHostNameVerifier来解决此问题okHttpBuilder.确保验证方法应该返回true.

样品:

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();
Run Code Online (Sandbox Code Playgroud)

  • 这有多安全?如果返回true,是否不接受所有证书?如果是这样,应该避免! (2认同)
  • @Otziii是的,这不安全。我不知道为什么人们会继续建议这种“解决方案”。检查我上面的答案,这提供了一个更安全的选择。 (2认同)

小智 6

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


qrt*_*tLs 5

在证书生成过程中,subjectAltName如果 uri 是 IP,则必须进行设置,以免通过验证。

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

与其摆弄客户端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
Run Code Online (Sandbox Code Playgroud)

插件,如果在 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>
Run Code Online (Sandbox Code Playgroud)

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

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