使用okHttp信任所有证书

152

为了测试目的,我尝试给我的okHttp客户端添加一个socket工厂,在设置代理时信任所有内容。这已经做过很多次了,但我的可信socket工厂实现似乎缺少了一些东西:

class TrustEveryoneManager implements X509TrustManager {
    @Override
    public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException { }

    @Override
    public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException { }

    @Override
    public java.security.cert.X509Certificate[] getAcceptedIssuers() {
        return null;
    }
}
OkHttpClient client = new OkHttpClient();

final InetAddress ipAddress = InetAddress.getByName("XX.XXX.XXX.XXX"); // some IP
client.setProxy(new Proxy(Proxy.Type.HTTP, new InetSocketAddress(ipAddress, 8888)));

SSLContext sslContext = SSLContext.getInstance("TLS");
TrustManager[] trustManagers = new TrustManager[]{new TrustEveryoneManager()};
sslContext.init(null, trustManagers, null);
client.setSslSocketFactory(sslContext.getSocketFactory);

我的应用程序没有发出任何请求,也没有记录任何异常,因此似乎在okHttp内部静默失败。进一步调查发现,在强制进行握手时,okHttp的Connection.upgradeToTls()中存在一个被忽略的异常。我收到的异常是:javax.net.ssl.SSLException: SSL handshake terminated: ssl=0x74b522b0: SSL_ERROR_ZERO_RETURN occurred. You should never see this.

以下代码生成一个SSLContext,可像魔法一样创建不会抛出任何异常的SSLSocketFactory:

protected SSLContext getTrustingSslContext() throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException {
    final SSLContextBuilder trustingSSLContextBuilder = SSLContexts.custom()
            .loadTrustMaterial(null, new TrustStrategy() {
                @Override
                public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException {
                    return true; // Accepts any ssl cert whether valid or not.
                }
            });
    return trustingSSLContextBuilder.build();
}
问题是我试图完全从我的应用程序中删除所有Apache HttpClient的依赖。使用Apache HttpClient的基础代码来产生SSLContext似乎很简单,但显然我错过了一些东西,因为我无法配置我的SSLContext以匹配这个。
是否有人能够制作一个不使用Apache HttpClient就能做到我想要的SSLContext实现?

2
“出于测试目的,我正在尝试向我的okHttp客户端添加一个信任所有东西的套接字工厂” - 你认为这是一个好主意的原因是什么? - CommonsWare
3
它只会在我完全信任的网络上调用。 - seato
这会导致一个 java.net.SocketTimeoutException: Read timed out - IgorGanapolsky
8个回答

318

以防万一,如果有人来到这里,对我有效的(唯一)解决方案是按照此处所述创建OkHttpClient

以下是代码:

private static OkHttpClient getUnsafeOkHttpClient() {
  try {
    // Create a trust manager that does not validate certificate chains
    final TrustManager[] trustAllCerts = new TrustManager[] {
        new X509TrustManager() {
          @Override
          public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException {
          }

          @Override
          public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException {
          }

          @Override
          public java.security.cert.X509Certificate[] getAcceptedIssuers() {
            return new java.security.cert.X509Certificate[]{};
          }
        }
    };

    // Install the all-trusting trust manager
    final SSLContext sslContext = SSLContext.getInstance("SSL");
    sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
    // Create an ssl socket factory with our all-trusting manager
    final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();

    OkHttpClient.Builder builder = new OkHttpClient.Builder();
    builder.sslSocketFactory(sslSocketFactory, (X509TrustManager)trustAllCerts[0]);
    builder.hostnameVerifier(new HostnameVerifier() {
      @Override
      public boolean verify(String hostname, SSLSession session) {
        return true;
      }
    });

    OkHttpClient okHttpClient = builder.build();
    return okHttpClient;
  } catch (Exception e) {
    throw new RuntimeException(e);
  }
}

21
为什么使用 SSL 而不是 TLS - IgorGanapolsky
1
非常感谢这个!Retrofit(使用OkHttp)的文档不足,这种代码示例一次又一次地为我节省了很多时间。 - Warpzit
8
请注意,这种方法在当前版本的OkHttp中不再起作用。从3.1.1开始,它似乎完全失效了。从3.1.2开始,X509TrustManager.getAcceptedIssuers()必须返回一个空数组,而不是null。有关更多信息,请参见此提交记录(向下滚动并查看RealTrustRootIndex.java下的注释)。 - jbxbergdev
17
我尝试过这个方法,但仍然遇到了“握手失败”的异常。有什么建议吗? - Esteban
3
示例来自OkHttpClient:https://github.com/square/okhttp/blob/master/samples/guide/src/main/java/okhttp3/recipes/CustomTrust.java - ilyamuromets
显示剩余12条评论

44

我为Kotlin编写了一个扩展函数。您可以将其粘贴到任何位置,并在创建OkHttpClient时导入它。

fun OkHttpClient.Builder.ignoreAllSSLErrors(): OkHttpClient.Builder {
    val naiveTrustManager = object : X509TrustManager {
        override fun getAcceptedIssuers(): Array<X509Certificate> = arrayOf()
        override fun checkClientTrusted(certs: Array<X509Certificate>, authType: String) = Unit
        override fun checkServerTrusted(certs: Array<X509Certificate>, authType: String) = Unit
    }

    val insecureSocketFactory = SSLContext.getInstance("TLSv1.2").apply {
        val trustAllCerts = arrayOf<TrustManager>(naiveTrustManager)
        init(null, trustAllCerts, SecureRandom())
    }.socketFactory

    sslSocketFactory(insecureSocketFactory, naiveTrustManager)
    hostnameVerifier(HostnameVerifier { _, _ -> true })
    return this
}

使用方法如下:

val okHttpClient = OkHttpClient.Builder().apply {
    // ...
    if (BuildConfig.DEBUG) //if it is a debug build ignore ssl errors
        ignoreAllSSLErrors()
    //...
}.build()

4
你实际上可以像这样使用它: OkHttpClient.Builder() .ignoreAllSSLErrors() .connectTimeout(api.timeout, TimeUnit.SECONDS) .writeTimeout(api.timeout, TimeUnit.SECONDS) 等等,毕竟它是一个建造者模式。 - Emanuel Moecklin
1
为什么在sonxurxo的回答中使用TLSv1.2而不是SSL? - Mahdi Moqadasi
@EmanuelMoecklin 简单明了,棒极了。 - Амирхон
仍然出现“SSLV3_ALERT_BAD_CERTIFICATE”。 - Oleksandr Nos
使用KMM项目和Ktor完美运作。感谢您的帮助。 - cross_flame
非常感谢这个解决方案对我起了作用。 - Javid Sattar

24

这是sonxurxo在Kotlin中的解决方案,如果有人需要的话。

private fun getUnsafeOkHttpClient(): OkHttpClient {
    // Create a trust manager that does not validate certificate chains
    val trustAllCerts = arrayOf<TrustManager>(object : X509TrustManager {
        override fun checkClientTrusted(chain: Array<out X509Certificate>?, authType: String?) {
        }

        override fun checkServerTrusted(chain: Array<out X509Certificate>?, authType: String?) {
        }

        override fun getAcceptedIssuers() = arrayOf<X509Certificate>()
    })

    // Install the all-trusting trust manager
    val sslContext = SSLContext.getInstance("SSL")
    sslContext.init(null, trustAllCerts, java.security.SecureRandom())
    // Create an ssl socket factory with our all-trusting manager
    val sslSocketFactory = sslContext.socketFactory

    return OkHttpClient.Builder()
        .sslSocketFactory(sslSocketFactory, trustAllCerts[0] as X509TrustManager)
        .hostnameVerifier { _, _ -> true }.build()
}


16

更新OkHttp 3.0后,getAcceptedIssuers()函数必须返回一个空数组而不是null


2
@Override public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[]{};//StackOverflow } - Ervin Zhang

15

以下方法已过时

sslSocketFactory(SSLSocketFactory sslSocketFactory)

考虑更新它

sslSocketFactory(SSLSocketFactory sslSocketFactory, X509TrustManager trustManager)

在Android10中,我遇到了“无法提取Android10平台上的信任管理器”的问题。提供了“trustManager”参数后,问题得到了解决。 - Rukmal Dias

13
SSLSocketFactory没有公开其X509TrustManager字段,而OkHttp需要该字段来构建干净的证书链。这种方法必须使用反射来提取信任管理器。应用程序应优先调用sslSocketFactory(SSLSocketFactory,X509TrustManager),以避免使用反射。

来源:OkHttp文档

OkHttpClient.Builder builder = new OkHttpClient.Builder();

builder.sslSocketFactory(sslContext.getSocketFactory(),
    new X509TrustManager() {
        @Override
        public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException {
        }

        @Override
        public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException {
        }

        @Override
        public java.security.cert.X509Certificate[] getAcceptedIssuers() {
            return new java.security.cert.X509Certificate[]{};
        }
    });

这对于自签名证书不起作用。会导致401未经授权错误。 - IgorGanapolsky
7
sslContext 是从哪里来的? - Anonsage
3
你如何初始化 sslContext?你该如何初始化 sslContext - kiltek
@IgorGanapolsky 有关自签名证书的任何解决方案吗?我也遇到了自签名证书的同样问题。 - Tejas Pandya

4

如果有人需要,这是Scala解决方案。

def anUnsafeOkHttpClient(): OkHttpClient = {
val manager: TrustManager =
  new X509TrustManager() {
    override def checkClientTrusted(x509Certificates: Array[X509Certificate], s: String) = {}

    override def checkServerTrusted(x509Certificates: Array[X509Certificate], s: String) = {}

    override def getAcceptedIssuers = Seq.empty[X509Certificate].toArray
  }
val trustAllCertificates =  Seq(manager).toArray

val sslContext = SSLContext.getInstance("SSL")
sslContext.init(null, trustAllCertificates, new java.security.SecureRandom())
val sslSocketFactory = sslContext.getSocketFactory()
val okBuilder = new OkHttpClient.Builder()
okBuilder.sslSocketFactory(sslSocketFactory, trustAllCertificates(0).asInstanceOf[X509TrustManager])
okBuilder.hostnameVerifier(new NoopHostnameVerifier)
okBuilder.build()

}


-16

在编程中,您绝不能试图覆盖证书验证!如果您需要进行测试,请使用内部/测试CA,并在设备或模拟器上安装CA根证书。如果您不知道如何设置CA,则可以使用BurpSuite或Charles Proxy。


42
哦,得了吧。如果你在一家公司工作,他们要求你通过VPN连接到他们的HTTPS服务器,那么你该如何在测试中模拟这种情况呢? - IgorGanapolsky
6
是的,这完全是胡扯。我们只在测试版本中编译这个,所以没有任何危险。 - Adam

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接