HTTPS#
OkHttp嘗試平衡兩個相互競爭的要素:
-
連通性(Connectivity):連接到盡可能多的服務器。這包括運行最新版本 boringssl 的服務器和不太過時的老版本 OpenSSL 的服務器。
-
連接的安全性(Security):這包括遠程web服務器證書驗證,和對私密數據交換的強加密。
在與HTTPS服務器協商一個連接時,OkHttp需要知道提供哪種TLS版本(TLS versions)和密碼套件(cipher suites)。一個希望最大化連通性的客戶端會包含廢棄的TLS版本和弱設計的密碼套件。一個希望最大化安全性的嚴格的客戶端將會限制只使用最新的TLS版本和最強的密碼套件。
具體的安全性 vs 連通性的決定是由ConnectionSpec實現的。OkHttp包含三種內置的連接策略:
MODERN_TLS是連接到最新的HTTPS服務器的安全配置。COMPATIBEL_TLS是連接到過時的HTTPS服務器的安全配置。CLEARTEXT是用於http://開頭的URL的非安全配置。
默認情況下,OkHttp將會嘗試MODERN_TLS連接,如果當前配置失敗,會退回到COMPATIBLE_TLS連接。
每種連接策略中,具體的TLS版本和密碼套件在每個版本中都可能會變。例如,在OkHttp 2.2中,我們禁用了SSL 3.0,以應對POODLE攻擊;在Ok Http2.3中我們禁用了RC4。同你的桌面web瀏覽器一樣,使用最新的OkHttp版本是保證安全的最好方法。
你可以建立自己的連接策略,使用自定義的TLS版本和密碼套件。例如,下面的配置限制在三種被高度重視的密碼套件。它的缺陷是需要Android 5.0以上,和相應的新web服務器。
ConnectionSpec spec = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
.tlsVersions(TlsVersion.TLS_1_2)
.cipherSuites(
CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
CipherSuite.TLS_DHE_RSA_WITH_AES_128_GCM_SHA256)
.build();
OkHttpClient client = ...
client.setConnectionSpecs(Collections.singletonList(spec));
證書鎖定(Certificate Pinning)##
默認情況下,OkHttp信任運行平台支持的證書頒發機構。這種策略最大化了連通性,但它受到對認證機構的攻擊的制約,例如2011年的DigiNotar攻擊。它也假定了你的HTTPS服務器的證書是由證書頒發機構簽名的。
使用CertificatePinner來約束哪些認證機構被信任。證書鎖定增加了安全性,但限制了你的服務器團隊升級TLS證書的能力。沒有來自服務器TLS管理員的祝福,不要使用證書鎖定!
public CertificatePinning() {
client = new OkHttpClient();
client.setCertificatePinner(
new CertificatePinner.Builder()
.add("publicobject.com", "sha1/DmxUShsZuNiqPQsX2Oi9uv2sCnw=")
.add("publicobject.com", "sha1/SXxoaOSEzPC6BgGmxAt/EAcsajw=")
.add("publicobject.com", "sha1/blhOM3W9V/bVQhsWAcLYwPU6n24=")
.add("publicobject.com", "sha1/T5x9IXmcrQ7YuQxXnxoCmeeQ84c=")
.build());
}
public void run() throws Exception {
Request request = new Request.Builder()
.url("https://publicobject.com/robots.txt")
.build();
Response response = client.newCall(request).execute();
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
for (Certificate certificate : response.handshake().peerCertificates()) {
System.out.println(CertificatePinner.pin(certificate));
}
}
自定義受信任的證書##
完整的代碼展示了如何用你自己的設置替換運行平台支持的認證機構。同上,沒有來自服務器TLS管理員的祝福,不要使用證書鎖定!
private final OkHttpClient client;
public CustomTrust() {
client = new OkHttpClient();
SSLContext sslContext = sslContextForTrustedCertificates(trustedCertificatesInputStream());
client.setSslSocketFactory(sslContext.getSocketFactory());
}
public void run() throws Exception {
Request request = new Request.Builder()
.url("https://publicobject.com/helloworld.txt")
.build();
Response response = client.newCall(request).execute();
System.out.println(response.body().string());
}
private InputStream trustedCertificatesInputStream() {
... // Full source omitted. See sample.
}
public SSLContext sslContextForTrustedCertificates(InputStream in) {
... // Full source omitted. See sample.
}
