OkHttp javax.net.ssl.SSLPeerUnverifiedException: 主机名 domain.com 未验证
OkHttp javax.net.ssl.SSLPeerUnverifiedException: Hostname domain.com not verified
我已经尝试了好几天才能让它正常工作。我正在尝试使用自签名证书通过 https 连接到我的服务器。我认为现在没有任何页面或示例是我未读过的。
我做了什么:
它使用openssl s_client -connect domain.com:443
从服务器获取证书。然后使用充气城堡创建一个 bks 密钥库。
正在从原始文件夹中读取创建的密钥库,将其添加到 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();
}
出于某种原因,这总是会生成一个 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)
...
我做错了什么?
我终于在混合了多个答案的情况下解决了这个问题。
首先,证书制作错误,不知道是怎么弄的。但是通过使用 this answer 中的脚本创建它们,它们就可以工作了。需要的是服务器证书和密钥。然后客户需要另一个证书。
为了使用 android 中的证书,我将 .pem 文件转换为 .crt 文件,如下所示:
openssl x509 -outform der -in client.pem -out client.crt
在 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;
}
带new AdditionalKeyStore()
的最后部分取自this very well written answer。它添加了一个后备密钥库。
希望这对其他人有帮助!这是让 HTTPS 使用我找到的自签名证书的最简单方法。其他方法包括拥有一个 BouncyCastle 密钥库,这对我来说似乎太过分了。
我遇到了同样的问题,但是我需要我的应用程序在多个暂存环境中工作,所有这些环境都有自签名证书。更糟糕的是,他们可以即时更改这些证书。
为了解决这个问题,当仅连接到分段时,我添加了一个信任所有证书的 SSLSocketFactory。这修复了 java 错误,但是它给我留下了这张票中提到的 okhttp 异常。
为避免此错误,我需要向我的 okHttpClient 添加更多自定义项。这为我修复了错误。
okHttpClient.setHostnameVerifier(new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
return true;
}
});
此问题已通过将 setHostNameVerifier
设置为 okHttpBuilder
来解决。确保验证方法应该 return 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();
请检查客户端证书上的 CN 名称是否已添加到主题备用名称中。我有同样的问题
在证书生成期间,如果 uri 是一个 ip,则必须设置 subjectAltName
才能不通过验证。
"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)
与其用 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
插件,如果在 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>
因此我们验证证书来自受信任的来源并且之前通过主机名验证(通过 SAN)确保我们与之交谈的服务器为他的 ip 提供正确的证书。
更多信息:https://developer.android.com/training/articles/security-config
https://developer.android.com/training/articles/security-ssl.html#SelfSigned
如果您在 res/xml
中使用 network_security_config
解决 Cleartext HTTP traffic not permitted
并且您更改了 domain/IP 地址,请记住您应该将 includeSubdomains
值更改为新的地址。
这对我有用。
<network-security-config>
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">yourDomain.com</domain>
</domain-config>
</network-security-config>
我已经尝试了好几天才能让它正常工作。我正在尝试使用自签名证书通过 https 连接到我的服务器。我认为现在没有任何页面或示例是我未读过的。
我做了什么:
它使用openssl s_client -connect domain.com:443
从服务器获取证书。然后使用充气城堡创建一个 bks 密钥库。
正在从原始文件夹中读取创建的密钥库,将其添加到 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(); }
出于某种原因,这总是会生成一个
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) ...
我做错了什么?
我终于在混合了多个答案的情况下解决了这个问题。
首先,证书制作错误,不知道是怎么弄的。但是通过使用 this answer 中的脚本创建它们,它们就可以工作了。需要的是服务器证书和密钥。然后客户需要另一个证书。
为了使用 android 中的证书,我将 .pem 文件转换为 .crt 文件,如下所示:
openssl x509 -outform der -in client.pem -out client.crt
在 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;
}
带new AdditionalKeyStore()
的最后部分取自this very well written answer。它添加了一个后备密钥库。
希望这对其他人有帮助!这是让 HTTPS 使用我找到的自签名证书的最简单方法。其他方法包括拥有一个 BouncyCastle 密钥库,这对我来说似乎太过分了。
我遇到了同样的问题,但是我需要我的应用程序在多个暂存环境中工作,所有这些环境都有自签名证书。更糟糕的是,他们可以即时更改这些证书。
为了解决这个问题,当仅连接到分段时,我添加了一个信任所有证书的 SSLSocketFactory。这修复了 java 错误,但是它给我留下了这张票中提到的 okhttp 异常。
为避免此错误,我需要向我的 okHttpClient 添加更多自定义项。这为我修复了错误。
okHttpClient.setHostnameVerifier(new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
return true;
}
});
此问题已通过将 setHostNameVerifier
设置为 okHttpBuilder
来解决。确保验证方法应该 return 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();
请检查客户端证书上的 CN 名称是否已添加到主题备用名称中。我有同样的问题
在证书生成期间,如果 uri 是一个 ip,则必须设置 subjectAltName
才能不通过验证。
"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)
与其用 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
插件,如果在 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>
因此我们验证证书来自受信任的来源并且之前通过主机名验证(通过 SAN)确保我们与之交谈的服务器为他的 ip 提供正确的证书。
更多信息:https://developer.android.com/training/articles/security-config https://developer.android.com/training/articles/security-ssl.html#SelfSigned
如果您在 res/xml
中使用 network_security_config
解决 Cleartext HTTP traffic not permitted
并且您更改了 domain/IP 地址,请记住您应该将 includeSubdomains
值更改为新的地址。
这对我有用。
<network-security-config>
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">yourDomain.com</domain>
</domain-config>
</network-security-config>