私は、自己署名SSL証明書を備えたサーバーを持っている顧客のために働いています。
ラップされたOkHttpクライアントを使用して、Retrofit + CustomClientを使用しています。
RestAdapter restAdapter = new RestAdapter.Builder().setEndpoint(Config.BASE_URL + Config.API_VERSION)
.setClient(new CustomClient(new OkClient(), context))
.build();
OkHttpはデフォルトで自己署名SSL証明書サーバーの呼び出しをサポートしていますか?
ところで。どのクライアントがデフォルトでレトロフィットを使用していますか? OkHttpだと思っていましたが、もう少し調べてみると、OkHttp依存関係をインポートする必要があることに気付きました
はい、そうです。
Retrofitを使用すると、ニーズに合わせて構成されたカスタムHTTPクライアントを設定できます。
自己署名SSL証明書については、議論があります こちら 。リンクには、自己署名SLLをAndroidのDefaultHttpClient
に追加し、このクライアントをRetrofitにロードするためのコードサンプルが含まれています。
自己署名SSLを受け入れるためにOkHttpClient
が必要な場合は、setSslSocketFactory(SSLSocketFactory sslSocketFactory)
メソッドを介してカスタムjavax.net.ssl.SSLSocketFactory
インスタンスを渡す必要があります。
ソケットファクトリを取得する最も簡単な方法は、 here で説明したように、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());
OkHttpClient client = new OkHttpClient.Builder()
.sslSocketFactory(sslContext.getSocketFactory())
.build();
client
は、KeyStore
の証明書を使用するように設定されました。ただし、システムがデフォルトでそれらを信頼している場合でも、KeyStore
内の証明書のみを信頼し、他のものは信頼しません。 (自己署名証明書のみがKeyStore
にあり、HTTPS経由でGoogleメインページに接続しようとすると、SSLHandshakeException
が返されます)。
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;
}
Androidを使用している場合は、res/raw
フォルダーに入れてContext
インスタンスから取得できます。
fis = context.getResources().openRawResource(R.raw.your_keystore_filename);
キーストアの作成方法については、いくつかの議論があります。たとえば、 here
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);
アプリから取得する2つのメソッドOkHttpClient 3.キーストアからの自己署名証明書を認識するインスタンス(Androidプロジェクトで準備されたpkcs12証明書ファイルを使用します "raw"リソースフォルダー):
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;
}
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);
}
}
私はこの投稿がかなり古いことを知っていますが、私が書いている時点でのOkHttpの最新の更新である3.12.1
バージョンで私のために働いたソリューションを共有したいと思います。
まず、TrustStoreに追加される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;
}
これで、キーストアの自己署名証明書でビルドされた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;
}
非常に推奨されていませんhostnameVerifier
で常にtrueを返し、中間攻撃の人のリスクを回避することを忘れないでください。
私は同じ問題を抱えていて、次のように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("av-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に追加する必要がある場合は、 here が解決策です。