Java OkHttp 是否支持接受自签名 SSL 证书?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/23103174/
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
Does OkHttp support accepting self-signed SSL certs?
提问by cesards
I'm working for a customer who has a server with self-signed SSL cert.
我正在为拥有带有自签名 SSL 证书的服务器的客户工作。
I'm using Retrofit + CustomClient using wrapped OkHttp client:
我正在使用 Retrofit + CustomClient 使用包装的 OkHttp 客户端:
RestAdapter restAdapter = new RestAdapter.Builder().setEndpoint(Config.BASE_URL + Config.API_VERSION)
.setClient(new CustomClient(new OkClient(), context))
.build();
Does OkHttp support calling Self-Signed SSL cert server by default?
OkHttp 是否默认支持调用自签名 SSL 证书服务器?
By the way. Which client is using Retrofit by default? I thought it was OkHttp but when I researched a bit more I realized I needed to import OkHttp dependencies
顺便一提。哪个客户端默认使用 Retrofit?我以为是 OkHttp,但是当我进一步研究时,我意识到我需要导入 OkHttp 依赖项
采纳答案by Andrey Makarov
Yes, It does.
是的,它确实。
Retrofit allows you to set your custom HTTP client, that is configured to your needs.
Retrofit 允许您设置自定义 HTTP 客户端,根据您的需要进行配置。
As for self signed SSL certs there is a discussion here. The link contains code samples to add self signed SLL to Android's DefaultHttpClient
and to load this client to Retrofit.
至于自签名 SSL 证书,这里有一个讨论。该链接包含代码示例,用于将自签名 SLL 添加到 AndroidDefaultHttpClient
并将此客户端加载到 Retrofit。
If you need OkHttpClient
to accept self signed SSL, you need to pass it custom javax.net.ssl.SSLSocketFactory
instance via setSslSocketFactory(SSLSocketFactory sslSocketFactory)
method.
如果您需要OkHttpClient
接受自签名 SSL,则需要javax.net.ssl.SSLSocketFactory
通过setSslSocketFactory(SSLSocketFactory sslSocketFactory)
方法将自定义实例传递给它。
The easiest method to get a socket factory is to get one from javax.net.ssl.SSLContext
as discussed here.
获取套接字工厂的最简单方法是从这里javax.net.ssl.SSLContext
讨论的获取一个。
Here is a sample for configuring OkHttpClient:
下面是一个配置 OkHttpClient 的示例:
OkHttpClient client = new OkHttpClient();
KeyStore keyStore = readKeyStore(); //your method to obtain KeyStore
SSLContext sslContext = SSLContext.getInstance("SSL");
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(keyStore);
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(keyStore, "keystore_pass".toCharArray());
sslContext.init(keyManagerFactory.getKeyManagers(),trustManagerFactory.getTrustManagers(), new SecureRandom());
client.setSslSocketFactory(sslContext.getSocketFactory());
Updated code for okhttp3 (using builder):
okhttp3 的更新代码(使用构建器):
OkHttpClient client = new OkHttpClient.Builder()
.sslSocketFactory(sslContext.getSocketFactory())
.build();
the client
here is now configured to use certificates from your KeyStore
. However it will only trust the certificates in your KeyStore
and will not trust anything else, even if your system trust them by default. (If you have only self signed certs in your KeyStore
and try to connect to Google main page via HTTPS you will get SSLHandshakeException
).
在client
这里现在配置为从您使用证书KeyStore
。但是,它只会信任您的证书,KeyStore
而不会信任其他任何东西,即使您的系统默认信任它们。(如果您只有自签名证书KeyStore
并尝试通过 HTTPS 连接到 Google 主页,您将获得SSLHandshakeException
)。
You can obtain KeyStore
instance from file as seen in docs:
您可以KeyStore
从文件中获取实例,如文档所示:
KeyStore readKeyStore() {
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
// get user password and file input stream
char[] password = getPassword();
java.io.FileInputStream fis = null;
try {
fis = new java.io.FileInputStream("keyStoreName");
ks.load(fis, password);
} finally {
if (fis != null) {
fis.close();
}
}
return ks;
}
If you are on android you can put it in res/raw
folder and get it from a Context
instance using
如果你在 android 上,你可以把它放在res/raw
文件夹中并Context
使用它从实例中
获取它
fis = context.getResources().openRawResource(R.raw.your_keystore_filename);
There are several discussions on how to create your keystore. For example here
有几个关于如何创建密钥库的讨论。例如这里
回答by Bruno Carrier
The following piece of code allows you to create an OkHttp client that can be used with Retrofit. Mailmustdie's answer is "better" in the sense that it is more secure, but the code snippet below is faster to implement
下面的一段代码允许你创建一个可以与 Retrofit 一起使用的 OkHttp 客户端。Mailmustdie 的答案是“更好”,因为它更安全,但下面的代码片段实现速度更快
import com.squareup.okhttp.Headers;
import com.squareup.okhttp.MediaType;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.RequestBody;
import com.squareup.okhttp.ResponseBody;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import okio.BufferedSink;
import retrofit.client.Header;
import retrofit.client.OkClient;
import retrofit.client.Request;
import retrofit.client.Response;
import retrofit.mime.TypedInput;
import retrofit.mime.TypedOutput;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
public class TrustingOkClient extends OkClient {
static final int CONNECT_TIMEOUT_MILLIS = 15 * 1000; // 15s
static final int READ_TIMEOUT_MILLIS = 20 * 1000; // 20s
private static OkHttpClient generateDefaultOkHttp() {
OkHttpClient client = new OkHttpClient();
client.setConnectTimeout(CONNECT_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
client.setReadTimeout(READ_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
final TrustManager[] certs = new TrustManager[]{new X509TrustManager() {
@Override
public X509Certificate[] getAcceptedIssuers() {
return null;
}
@Override
public void checkServerTrusted(final X509Certificate[] chain,
final String authType) throws CertificateException {
}
@Override
public void checkClientTrusted(final X509Certificate[] chain,
final String authType) throws CertificateException {
}
}};
SSLContext ctx = null;
try {
ctx = SSLContext.getInstance("TLS");
ctx.init(null, certs, new SecureRandom());
} catch (final java.security.GeneralSecurityException ex) {
}
try {
final HostnameVerifier hostnameVerifier = new HostnameVerifier() {
@Override
public boolean verify(final String hostname,
final SSLSession session) {
return true;
}
};
client.setHostnameVerifier(hostnameVerifier);
client.setSslSocketFactory(ctx.getSocketFactory());
} catch (final Exception e) {
}
return client;
}
private final OkHttpClient client;
public TrustingOkClient() {
this(generateDefaultOkHttp());
}
public TrustingOkClient(OkHttpClient client) {
if (client == null) throw new NullPointerException("client == null");
this.client = client;
}
@Override public Response execute(Request request) throws IOException {
return parseResponse(client.newCall(createRequest(request)).execute());
}
static com.squareup.okhttp.Request createRequest(Request request) {
com.squareup.okhttp.Request.Builder builder = new com.squareup.okhttp.Request.Builder()
.url(request.getUrl())
.method(request.getMethod(), createRequestBody(request.getBody()));
List<Header> headers = request.getHeaders();
for (int i = 0, size = headers.size(); i < size; i++) {
Header header = headers.get(i);
String value = header.getValue();
if (value == null) value = "";
builder.addHeader(header.getName(), value);
}
return builder.build();
}
static Response parseResponse(com.squareup.okhttp.Response response) {
return new Response(response.request().urlString(), response.code(), response.message(),
createHeaders(response.headers()), createResponseBody(response.body()));
}
private static RequestBody createRequestBody(final TypedOutput body) {
if (body == null) {
return null;
}
final MediaType mediaType = MediaType.parse(body.mimeType());
return new RequestBody() {
@Override public MediaType contentType() {
return mediaType;
}
@Override public void writeTo(BufferedSink sink) throws IOException {
body.writeTo(sink.outputStream());
}
@Override public long contentLength() {
return body.length();
}
};
}
private static TypedInput createResponseBody(final ResponseBody body) {
try {
if (body.contentLength() == 0) {
return null;
}
return new TypedInput() {
@Override public String mimeType() {
MediaType mediaType = body.contentType();
return mediaType == null ? null : mediaType.toString();
}
@Override public long length() {
try {
return body.contentLength();
} catch (Exception exception) {
System.out.println(exception.toString());
}
throw new Error("createResponseBody has invalid length for its response");
}
@Override public InputStream in() throws IOException {
return body.byteStream();
}
};
} catch (Exception exception) {
System.out.println(exception.toString());
}
throw new Error("createResponseBody has invalid content length for its response");
}
private static List<Header> createHeaders(Headers headers) {
int size = headers.size();
List<Header> headerList = new ArrayList<Header>(size);
for (int i = 0; i < size; i++) {
headerList.add(new Header(headers.name(i), headers.value(i)));
}
return headerList;
}
}
回答by Gugelhupf
For okhttp3.OkHttpClient Version com.squareup.okhttp3:okhttp:3.2.0 you have to use the code below :
对于 okhttp3.OkHttpClient 版本 com.squareup.okhttp3:okhttp:3.2.0 你必须使用下面的代码:
import okhttp3.Call;
import okhttp3.Cookie;
import okhttp3.CookieJar;
import okhttp3.Headers;
import okhttp3.HttpUrl;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.ResponseBody;
......
OkHttpClient.Builder clientBuilder = client.newBuilder().readTimeout(LOGIN_TIMEOUT_SEC, TimeUnit.SECONDS);
boolean allowUntrusted = true;
if ( allowUntrusted) {
Log.w(TAG,"**** Allow untrusted SSL connection ****");
final TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager() {
@Override
public X509Certificate[] getAcceptedIssuers() {
X509Certificate[] cArrr = new X509Certificate[0];
return cArrr;
}
@Override
public void checkServerTrusted(final X509Certificate[] chain,
final String authType) throws CertificateException {
}
@Override
public void checkClientTrusted(final X509Certificate[] chain,
final String authType) throws CertificateException {
}
}};
SSLContext sslContext = SSLContext.getInstance("SSL");
sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
clientBuilder.sslSocketFactory(sslContext.getSocketFactory());
HostnameVerifier hostnameVerifier = new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
Log.d(TAG, "Trust Host :" + hostname);
return true;
}
};
clientBuilder.hostnameVerifier( hostnameVerifier);
}
final Call call = clientBuilder.build().newCall(request);
回答by Zon
Two methods from our app to get OkHttpClient 3.0instance that recognizes your self-signed certificates from your keystore (uses prepared pkcs12 certificate file in your Android project "raw"resources folder):
从我们的应用程序获取OkHttpClient 3.0实例的两种方法,该实例可识别您的密钥库中的自签名证书(使用 Android 项目“原始”资源文件夹中准备好的 pkcs12 证书文件):
private static OkHttpClient getSSLClient(Context context) throws
NoSuchAlgorithmException,
KeyStoreException,
KeyManagementException,
CertificateException,
IOException {
OkHttpClient client;
SSLContext sslContext;
SSLSocketFactory sslSocketFactory;
TrustManager[] trustManagers;
TrustManagerFactory trustManagerFactory;
X509TrustManager trustManager;
trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(readKeyStore(context));
trustManagers = trustManagerFactory.getTrustManagers();
if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
throw new IllegalStateException("Unexpected default trust managers:" + Arrays.toString(trustManagers));
}
trustManager = (X509TrustManager) trustManagers[0];
sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, new TrustManager[]{trustManager}, null);
sslSocketFactory = sslContext.getSocketFactory();
client = new OkHttpClient.Builder()
.sslSocketFactory(sslSocketFactory, trustManager)
.build();
return client;
}
/**
* Get keys store. Key file should be encrypted with pkcs12 standard. It can be done with standalone encrypting java applications like "keytool". File password is also required.
*
* @param context Activity or some other context.
* @return Keys store.
* @throws KeyStoreException
* @throws CertificateException
* @throws NoSuchAlgorithmException
* @throws IOException
*/
private static KeyStore readKeyStore(Context context) throws
KeyStoreException,
CertificateException,
NoSuchAlgorithmException,
IOException {
KeyStore keyStore;
char[] PASSWORD = "12345678".toCharArray();
ArrayList<InputStream> certificates;
int certificateIndex;
InputStream certificate;
certificates = new ArrayList<>();
certificates.add(context.getResources().openRawResource(R.raw.ssl_pkcs12));
keyStore = KeyStore.getInstance("pkcs12");
for (Certificate certificate : certificates) {
try {
keyStore.load(certificate, PASSWORD);
} finally {
if (certificate != null) {
certificate.close();
}
}
}
return keyStore;
}
回答by jocull
Against Retrofit 1.9 I was able to accept any certificate with the following strategy: use at your own risk! Accepting any certificate is dangerous and you should understand the consequences. Some relevant parts come from org.apache.http.ssl
, so you may require some imports here.
针对 Retrofit 1.9 我能够接受任何具有以下策略的证书:使用风险自负!接受任何证书都是危险的,您应该了解后果。一些相关部分来自org.apache.http.ssl
,因此您可能需要在此处导入一些内容。
// ...
Client httpClient = getHttpClient();
RestAdapter adapter = new RestAdapter.Builder()
.setClient(httpClient)
// ... the rest of your builder setup
.build();
// ...
private Client getHttpClient() {
try {
// Allow self-signed (and actually any) SSL certificate to be trusted in this context
TrustStrategy acceptingTrustStrategy = (X509Certificate[] chain, String authType) -> true;
SSLContext sslContext = org.apache.http.ssl.SSLContexts.custom()
.loadTrustMaterial(null, acceptingTrustStrategy)
.build();
sslContext.getSocketFactory();
SSLSocketFactory sf = sslContext.getSocketFactory();
OkHttpClient client = new OkHttpClient();
client.setSslSocketFactory(sf);
return new OkClient(client);
} catch (Exception e) {
throw new RuntimeException("Failed to create new HTTP client", e);
}
}
回答by Domenico
I know that this post is quite old, bui i want to share the solution that worked for me with the latest update of OkHttp, the 3.12.1
version in the time i'm writing.
我知道这篇文章已经很旧了,但我想分享对我有用的解决方案,其中包含3.12.1
我正在编写的最新版本的 OkHttp 更新。
First of all you need to obtain the KeyStore object that will be then added to the TrustManager:
首先,您需要获取随后将添加到 TrustManager 的 KeyStore 对象:
/**
* @param context The Android context to be used for retrieving the keystore from raw resource
* @return the KeyStore read or null on error
*/
private static KeyStore readKeyStore(Context context) {
char[] password = "keystore_password".toCharArray();
// for non-android usage:
// try(FileInputStream is = new FileInputStream(keystoreName)) {
try(InputStream is = context.getResources().openRawResource(R.raw.keystore)) {
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
ks.load(is, password);
return ks;
} catch (CertificateException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (KeyStoreException e) {
e.printStackTrace();
}
return null;
}
Now you can get the builded OkHttpClient
with the self-signed certificate in your keystore:
现在,您可以OkHttpClient
使用密钥库中的自签名证书进行构建:
/**
* @param context The Android context used to obtain the KeyStore
* @return the builded OkHttpClient or null on error
*/
public static OkHttpClient getOkHttpClient(Context context) {
try {
TrustManagerFactory trustManagerFactory = TrustManagerFactory
.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(readKeyStore(context));
X509TrustManager trustManager = (X509TrustManager) trustManagerFactory.getTrustManagers()[0];
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, new TrustManager[]{trustManager}, null);
return new OkHttpClient.Builder()
.hostnameVerifier((hostname, session) -> {
HostnameVerifier hv = HttpsURLConnection.getDefaultHostnameVerifier();
/* Never return true without verifying the hostname, otherwise you will be vulnerable
to man in the middle attacks. */
return hv.verify("your_hostname_here", session);
})
.sslSocketFactory(sslContext.getSocketFactory(), trustManager)
.build();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (KeyStoreException e) {
e.printStackTrace();
} catch (KeyManagementException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
Remember that it is highly discouragedto return always true in the hostnameVerifier
to avoid risk of man in the middle attacks.
请记住,强烈建议不要在 中始终返回 truehostnameVerifier
以避免中间人攻击的风险。
回答by Manuel Schmitzberger
I had the same problem and I fixed it with the okhttpclient as follow:
我遇到了同样的问题,我用okhttp客户端修复了它,如下所示:
1.) Add the certificate
file to src/main/res/raw/
, which includes this content:
1.) 将certificate
文件添加到src/main/res/raw/
,其中包含以下内容:
-----BEGIN CERTIFICATE-----
...=
-----END CERTIFICATE-----
2.) Instanciate the okHttpClient:
2.) 实例化 okHttpClient:
OkHttpClient client = new OkHttpClient.Builder()
.sslSocketFactory(getSslContext(context).getSocketFactory())
.build();
3.) Here is the used getSslContext(Context context)
method:
3.)这里是使用的getSslContext(Context context)
方法:
SSLContext getSslContext(Context context) throws Exception {
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType()); // "BKS"
ks.load(null, null);
InputStream is = context.getResources().openRawResource(R.raw.certificate);
String certificate = Converter.convertStreamToString(is);
// generate input stream for certificate factory
InputStream stream = IOUtils.toInputStream(certificate);
// CertificateFactory
CertificateFactory cf = CertificateFactory.getInstance("X.509");
// certificate
Certificate ca;
try {
ca = cf.generateCertificate(stream);
} finally {
is.close();
}
ks.setCertificateEntry("my-ca", ca);
// TrustManagerFactory
String algorithm = TrustManagerFactory.getDefaultAlgorithm();
TrustManagerFactory tmf = TrustManagerFactory.getInstance(algorithm);
// Create a TrustManager that trusts the CAs in our KeyStore
tmf.init(ks);
// Create a SSLContext with the certificate that uses tmf (TrustManager)
sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, tmf.getTrustManagers(), new SecureRandom());
return sslContext;
}
If there is the need to add multiple certificates to the SslContext, hereis the solution.
如果需要在 SslContext 中添加多个证书,这里是解决方案。
回答by GR Envoy
Another thing to note, if you pre-install the CA on the device, you can make regular https calls with OKHttp, and no special ssl hoops. The key is to add the network security configs to your manifest.
还有一点要注意,如果你在设备上预装了 CA,你可以用 OKHttp 进行常规的 https 调用,而没有特殊的 ssl 箍。关键是将网络安全配置添加到您的清单中。
The key for me to know to do this was that I was getting the following exception.
我知道这样做的关键是我遇到了以下异常。
"Trust anchor for certification path not found."
“未找到认证路径的信任锚。”
Here is a good article from Google about how to configure it. https://developer.android.com/training/articles/security-config
这是来自 Google 的一篇关于如何配置它的好文章。 https://developer.android.com/training/articles/security-config
Here is an example of my network_security_config.xml
这是我的 network_security_config.xml 的示例
<?xml version="1.0" encoding="UTF-8" ?>
<network-security-config>
<base-config cleartextTrafficPermitted="false">
<trust-anchors>
<certificates src="user"/>
<certificates src="system"/>
</trust-anchors>
</base-config>
</network-security-config>