85

我正在为拥有带有自签名 SSL 证书的服务器的客户工作。

我正在使用 Retrofit + CustomClient 使用包装好的 OkHttp 客户端:

RestAdapter restAdapter = new RestAdapter.Builder().setEndpoint(Config.BASE_URL + Config.API_VERSION)
    .setClient(new CustomClient(new OkClient(), context))
    .build();

OkHttp 是否默认支持调用自签名 SSL 证书服务器?

顺便一提。哪个客户端默认使用 Retrofit?我以为是 OkHttp,但当我研究更多时,我意识到我需要导入 OkHttp 依赖项

4

9 回答 9

97

是的,它确实。

Retrofit 允许您设置自定义 HTTP 客户端,该客户端已根据您的需要进行配置。

至于自签名 SSL 证书,这里有一个讨论。该链接包含将自签名 SSL 添加到 AndroidDefaultHttpClient并将此客户端加载到 Retrofit 的代码示例。

如果您需要OkHttpClient接受自签名 SSL,则需要javax.net.ssl.SSLSocketFactory通过方法传递自定义实例setSslSocketFactory(SSLSocketFactory sslSocketFactory)

获取套接字工厂的最简单方法是从javax.net.ssl.SSLContext此处获取

下面是一个配置 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());

更新了 okhttp3 的代码(使用 builder):

    OkHttpClient client = new OkHttpClient.Builder()
            .sslSocketFactory(sslContext.getSocketFactory())
            .build();

这里client现在配置为使用您的KeyStore. KeyStore但是,即使您的系统默认信任它们,它也只会信任您的证书而不会信任其他任何东西。(如果您只有自签名证书KeyStore 并尝试通过 HTTPS 连接到 Google 主页,您将获得SSLHandshakeException)。

您可以KeyStore从文件中获取实例,如docs所示:

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

如果您在 android 上,您可以将其放在res/raw文件夹中并Context使用

fis = context.getResources().openRawResource(R.raw.your_keystore_filename);

关于如何创建密钥库有几个讨论。例如这里

于 2014-06-25T06:36:58.257 回答
14

另外需要注意的是,如果您在设备上预安装 CA,您可以使用 OKHttp 进行常规 https 调用,而无需特殊的 ssl 箍。关键是将网络安全配置添加到您的清单中。

我知道这样做的关键是我遇到了以下异常。

"未找到证书路径的信任锚。 "

这是来自 Google 的一篇关于如何配置它的好文章。 https://developer.android.com/training/articles/security-config

这是我的 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>
于 2020-02-06T19:50:55.873 回答
12

对于 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);
于 2016-05-24T07:25:24.833 回答
6

从我们的应用程序中获取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;
}
于 2017-05-03T06:54:30.113 回答
3

我遇到了同样的问题,我用okhttp客户端修复了它,如下所示:

1.) 将certificate文件添加到src/main/res/raw/,其中包括以下内容:

-----BEGIN CERTIFICATE-----
...=
-----END CERTIFICATE-----

2.)实例化okHttpClient:

OkHttpClient client = new OkHttpClient.Builder()
                .sslSocketFactory(getSslContext(context).getSocketFactory())
                .build();

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

如果需要向 SslContext 添加多个证书,这里是解决方案。

于 2019-04-03T14:22:24.137 回答
1

针对 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);
    }
}
于 2018-06-26T17:10:22.400 回答
0

我知道这篇文章已经很老了,但是我想与 OkHttp 的最新更新分享对我有用的解决方案,这是3.12.1我写作时的版本。

首先,您需要获取 KeyStore 对象,然后将其添加到 TrustManager:

/**
 *  @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;
}

现在,您可以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;
}

请记住,非常不鼓励在 the 中返回始终为 truehostnameVerifier以避免中间人攻击的风险。

于 2019-01-11T10:02:51.483 回答
0

我从以下位置找到答案:

https://github.com/square/okhttp/blob/master/samples/guide/src/main/java/okhttp3/recipes/CustomTrust.java

它用于HandshakeCertificates添加证书。

 HandshakeCertificates certificates = new HandshakeCertificates.Builder()
        .addTrustedCertificate(letsEncryptCertificateAuthority)
        .addTrustedCertificate(entrustRootCertificateAuthority)
        .addTrustedCertificate(comodoRsaCertificationAuthority)
        // Uncomment if standard certificates are also required.
        //.addPlatformTrustedCertificates()
        .build();

    client = new OkHttpClient.Builder()
            .sslSocketFactory(certificates.sslSocketFactory(), certificates.trustManager())
            .build();

如果您在商店中有信任证书,则可以按如下方式使用它:

.......
List<X509Certificate> certificates = getCertificatesFromTrustStore();
        
Builder certificateBuilder =  new HandshakeCertificates.Builder();        
for (X509Certificate x509Certificate : certificates) {
    certificateBuilder.addTrustedCertificate(x509Certificate);
}
HandshakeCertificates handshakeCertificates =  certificateBuilder.build();
.......
//To get certificates from a keystore
private List<X509Certificate> getCertificatesFromTrustStore() throws Exception {
        KeyStore truststore = KeyStore.getInstance("JKS");
        truststore.load(new FileInputStream("d:\certs.jsk"), "mypassword".toCharArray());
        
        PKIXParameters params = new PKIXParameters(truststore);
        Set<TrustAnchor> trustAnchors = params.getTrustAnchors();
        LOG.debug("{} certificates found in {} which will be used", trustAnchors.size(), trustStorePath);
        
        List<X509Certificate> certificates = trustAnchors.stream()
                  .map(TrustAnchor::getTrustedCert)
                  .collect(Collectors.toList());
        return certificates;
    }
于 2021-04-01T19:02:40.320 回答
-4

以下代码允许您创建一个可以与 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;
}

}
于 2016-02-01T04:31:14.053 回答