OkHttp是否支持接受自签名SSL证书?

ces*_*rds 61 java retrofit okhttp

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

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

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

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

顺便说说.哪个客户端默认使用Retrofit?我认为这是OkHttp,但当我研究了一点时,我意识到我需要导入OkHttp依赖项

And*_*rov 79

是的,它确实.

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

至于自签名SSL证书,这里有一个讨论.该链接包含用于将自签名SLL添加到Android DefaultHttpClient并将此客户端加载到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());
Run Code Online (Sandbox Code Playgroud)

更新了okhttp3的代码(使用构建器):

    OkHttpClient client = new OkHttpClient.Builder()
            .sslSocketFactory(sslContext.getSocketFactory())
            .build();
Run Code Online (Sandbox Code Playgroud)

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

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

如果你在Android上,你可以把它放在res/raw文件夹中,并从一个Context实例使用它

fis = context.getResources().openRawResource(R.raw.your_keystore_filename);
Run Code Online (Sandbox Code Playgroud)

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

  • 来自OkHttpBuilder的方法"sslSocketFactory(SSLSocketFactory)"现已弃用,我们应该使用"sslSoocketFactory(SSLSocketFactory,X506TrustManager)".这可以防止应用程序进行一些反射.资料来源:https://square.github.io/okhttp/3.x/okhttp/okhttp3/OkHttpClient.Builder.html#sslSocketFactory-javax.net.ssl.SSLSocketFactory-javax.net.ssl.X509TrustManager- (2认同)

GR *_*voy 12

还有一点要注意,如果你在设备上预装了 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>
Run Code Online (Sandbox Code Playgroud)

  • 希望我能+10000!我搜索了数百个类似问题的其他答案,这是唯一解决该问题的答案。我使用的是 Android 29,这仍然是需要的(至少是用户信任)。就我而言,我在 Intranet 上访问内部站点,设备上安装了公司颁发的根证书。 (2认同)

Gug*_*upf 9

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

  • Downvoted,因为这会禁用主机名验证,删除中间保护的人. (6认同)
  • 此解决方案仅用于在没有有效 SSL 证书的情况下进行调试。不适用于生产代码! (2认同)
  • OkHttpClient.Builder clientBuilder.sslSocketFactory() *已弃用*并且不再适用于 Android 10。请不要再使用我的建议。 (2认同)
  • @Gugelhupf 只需将 `clientBuilder.sslSocketFactory(sslContext.getSocketFactory());` 更改为 `clientBuilder.sslSocketFactory(sslContext.getSocketFactory(), trustAllCerts[0]);` 即可再次工作。仅不推荐使用第一个重载。 (2认同)

Zon*_*Zon 5

从我们的应用程序获取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;
}
Run Code Online (Sandbox Code Playgroud)


Man*_*ger 5

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

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

-----BEGIN CERTIFICATE-----
...=
-----END CERTIFICATE-----
Run Code Online (Sandbox Code Playgroud)

2.) 实例化 okHttpClient:

OkHttpClient client = new OkHttpClient.Builder()
                .sslSocketFactory(getSslContext(context).getSocketFactory())
                .build();
Run Code Online (Sandbox Code Playgroud)

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

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