HttpClient
libを使用してHTTPS接続を確立しようとしていますが、問題は、証明書が Verisign 、 GlobalSIgn など、Android信頼できる証明書のセットにリストされ、javax.net.ssl.SSLException: Not trusted server certificate
を取得し続けます。
すべての証明書を単純に受け入れるソリューションを見てきましたが、ユーザーに尋ねたい場合はどうすればよいですか?
ブラウザーのダイアログに似たダイアログを取得して、ユーザーが続行するかどうかを決定できるようにします。できれば、ブラウザと同じ証明書ストアを使用したいと思います。何か案は?
最初に行う必要があるのは、検証のレベルを設定することです。そのようなレベルはそれほど多くありません:
メソッドsetHostnameVerifier()は新しいライブラリApacheでは廃止されましたが、Android SDKのバージョンでは正常です。そして、ALLOW_ALL_HOSTNAME_VERIFIER
を取得し、メソッドファクトリSSLSocketFactory.setHostnameVerifier()
に設定します。
次に、プロトコルのファクトリーをhttpsに設定する必要があります。これを行うには、単にSchemeRegistry.register()
メソッドを呼び出します。
次に、DefaultHttpClient
を使用してSingleClientConnManager
を作成する必要があります。また、以下のコードでは、デフォルトでメソッドHttpsURLConnection.setDefaultHostnameVerifier()
によってフラグ(ALLOW_ALL_HOSTNAME_VERIFIER
)も使用されることがわかります。
以下のコードは私のために働く:
HostnameVerifier hostnameVerifier = org.Apache.http.conn.ssl.SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER;
DefaultHttpClient client = new DefaultHttpClient();
SchemeRegistry registry = new SchemeRegistry();
SSLSocketFactory socketFactory = SSLSocketFactory.getSocketFactory();
socketFactory.setHostnameVerifier((X509HostnameVerifier) hostnameVerifier);
registry.register(new Scheme("https", socketFactory, 443));
SingleClientConnManager mgr = new SingleClientConnManager(client.getParams(), registry);
DefaultHttpClient httpClient = new DefaultHttpClient(mgr, client.getParams());
// Set verifier
HttpsURLConnection.setDefaultHostnameVerifier(hostnameVerifier);
// Example send http request
final String url = "https://encrypted.google.com/";
HttpPost httpPost = new HttpPost(url);
HttpResponse response = httpClient.execute(httpPost);
Androidプラットフォームによって信頼されていると見なされない証明機関からのセキュリティで保護された接続を実現するには、次の主要な手順が必要です。
多くのユーザーからリクエストされたように、私は ブログ記事 ここから最も重要な部分をミラーリングしました:
Java.net.ssl.HttpsURLConnection
の代わりに Apache HttpClient を使用することをお勧めします(理解しやすく、パフォーマンスが向上します)エンドポイント証明書からルートCAまでのチェーンを構築するすべての証明書を取得する必要があります。これは、(存在する場合)中間CA証明書とルートCA証明書も意味します。エンドポイント証明書を取得する必要はありません。
BouncyCastle Provider をダウンロードし、既知の場所に保存します。また、keytoolコマンド(通常、JREインストールのbinフォルダーの下にある)を起動できることを確認します。
次に、取得した証明書を(エンドポイント証明書をインポートしないで)BouncyCastle形式のキーストアにインポートします。
私はそれをテストしませんでしたが、証明書をインポートする順序は重要だと思います。つまり、最初に最下位の中間CA証明書をインポートし、次にルートCA証明書までインポートします。
次のコマンドを使用すると、パスワードmysecretを持つ新しいキーストア(まだない場合)が作成され、中間CA証明書がインポートされます。また、BouncyCastleプロバイダーを定義しました。このプロバイダーは、ファイルシステムとキーストア形式で見つけることができます。チェーン内の各証明書に対してこのコマンドを実行します。
keytool -importcert -v -trustcacerts -file "path_to_cert/interm_ca.cer" -alias IntermediateCA -keystore "res/raw/myKeystore.bks" -provider org.bouncycastle.jce.provider.BouncyCastleProvider -providerpath "path_to_bouncycastle/bcprov-jdk16-145.jar" -storetype BKS -storepass mysecret
証明書がキーストアに正しくインポートされたかどうかを確認します。
keytool -list -keystore "res/raw/myKeystore.bks" -provider org.bouncycastle.jce.provider.BouncyCastleProvider -providerpath "path_to_bouncycastle/bcprov-jdk16-145.jar" -storetype BKS -storepass mysecret
チェーン全体を出力する必要があります:
RootCA, 22.10.2010, trustedCertEntry, Thumbprint (MD5): 24:77:D9:A8:91:D1:3B:FA:88:2D:C2:FF:F8:CD:33:93
IntermediateCA, 22.10.2010, trustedCertEntry, Thumbprint (MD5): 98:0F:C3:F8:39:F7:D8:05:07:02:0D:E3:14:5B:29:43
Androidアプリのres/raw/
の下に、キーストアを生のリソースとしてコピーできるようになりました
まず、HTTPS接続にキーストアを使用するカスタムApache HttpClientを作成する必要があります。
public class MyHttpClient extends DefaultHttpClient {
final Context context;
public MyHttpClient(Context context) {
this.context = context;
}
@Override
protected ClientConnectionManager createClientConnectionManager() {
SchemeRegistry registry = new SchemeRegistry();
registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
// Register for port 443 our SSLSocketFactory with our keystore
// to the ConnectionManager
registry.register(new Scheme("https", newSslSocketFactory(), 443));
return new SingleClientConnManager(getParams(), registry);
}
private SSLSocketFactory newSslSocketFactory() {
try {
// Get an instance of the Bouncy Castle KeyStore format
KeyStore trusted = KeyStore.getInstance("BKS");
// Get the raw resource, which contains the keystore with
// your trusted certificates (root and any intermediate certs)
InputStream in = context.getResources().openRawResource(R.raw.mykeystore);
try {
// Initialize the keystore with the provided trusted certificates
// Also provide the password of the keystore
trusted.load(in, "mysecret".toCharArray());
} finally {
in.close();
}
// Pass the keystore to the SSLSocketFactory. The factory is responsible
// for the verification of the server certificate.
SSLSocketFactory sf = new SSLSocketFactory(trusted);
// Hostname verification from certificate
// http://hc.Apache.org/httpcomponents-client-ga/tutorial/html/connmgmt.html#d4e506
sf.setHostnameVerifier(SSLSocketFactory.STRICT_HOSTNAME_VERIFIER);
return sf;
} catch (Exception e) {
throw new AssertionError(e);
}
}
}
カスタムHttpClientを作成しました。これで安全な接続に使用できます。たとえば、RESTリソースに対してGET呼び出しを行う場合。
// Instantiate the custom HttpClient
DefaultHttpClient client = new MyHttpClient(getApplicationContext());
HttpGet get = new HttpGet("https://www.mydomain.ch/rest/contacts/23");
// Execute the GET call and obtain the response
HttpResponse getResponse = client.execute(get);
HttpEntity responseEntity = getResponse.getEntity();
それでおしまい ;)
デバイスにないサーバーにカスタム/自己署名証明書がある場合、以下のクラスを使用してそれをロードし、Androidのクライアント側で使用できます。
証明書*.crt
を/res/raw
に配置して、R.raw.*
から利用できるようにします
以下のクラスを使用して、その証明書を使用するソケットファクトリを持つHTTPClient
またはHttpsURLConnection
を取得します。
package com.example.customssl;
import Android.content.Context;
import org.Apache.http.client.HttpClient;
import org.Apache.http.conn.scheme.PlainSocketFactory;
import org.Apache.http.conn.scheme.Scheme;
import org.Apache.http.conn.scheme.SchemeRegistry;
import org.Apache.http.conn.ssl.AllowAllHostnameVerifier;
import org.Apache.http.conn.ssl.SSLSocketFactory;
import org.Apache.http.impl.client.DefaultHttpClient;
import org.Apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.Apache.http.params.BasicHttpParams;
import org.Apache.http.params.HttpParams;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManagerFactory;
import Java.io.IOException;
import Java.io.InputStream;
import Java.net.URL;
import Java.security.KeyStore;
import Java.security.KeyStoreException;
import Java.security.NoSuchAlgorithmException;
import Java.security.cert.Certificate;
import Java.security.cert.CertificateException;
import Java.security.cert.CertificateFactory;
public class CustomCAHttpsProvider {
/**
* Creates a {@link org.Apache.http.client.HttpClient} which is configured to work with a custom authority
* certificate.
*
* @param context Application Context
* @param certRawResId R.raw.id of certificate file (*.crt). Should be stored in /res/raw.
* @param allowAllHosts If true then client will not check server against Host names of certificate.
* @return Http Client.
* @throws Exception If there is an error initializing the client.
*/
public static HttpClient getHttpClient(Context context, int certRawResId, boolean allowAllHosts) throws Exception {
// build key store with ca certificate
KeyStore keyStore = buildKeyStore(context, certRawResId);
// init ssl socket factory with key store
SSLSocketFactory sslSocketFactory = new SSLSocketFactory(keyStore);
// skip hostname security check if specified
if (allowAllHosts) {
sslSocketFactory.setHostnameVerifier(new AllowAllHostnameVerifier());
}
// basic http params for client
HttpParams params = new BasicHttpParams();
// normal scheme registry with our ssl socket factory for "https"
SchemeRegistry schemeRegistry = new SchemeRegistry();
schemeRegistry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
schemeRegistry.register(new Scheme("https", sslSocketFactory, 443));
// create connection manager
ThreadSafeClientConnManager cm = new ThreadSafeClientConnManager(params, schemeRegistry);
// create http client
return new DefaultHttpClient(cm, params);
}
/**
* Creates a {@link javax.net.ssl.HttpsURLConnection} which is configured to work with a custom authority
* certificate.
*
* @param urlString remote url string.
* @param context Application Context
* @param certRawResId R.raw.id of certificate file (*.crt). Should be stored in /res/raw.
* @param allowAllHosts If true then client will not check server against Host names of certificate.
* @return Http url connection.
* @throws Exception If there is an error initializing the connection.
*/
public static HttpsURLConnection getHttpsUrlConnection(String urlString, Context context, int certRawResId,
boolean allowAllHosts) throws Exception {
// build key store with ca certificate
KeyStore keyStore = buildKeyStore(context, certRawResId);
// Create a TrustManager that trusts the CAs in our KeyStore
String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
tmf.init(keyStore);
// Create an SSLContext that uses our TrustManager
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, tmf.getTrustManagers(), null);
// Create a connection from url
URL url = new URL(urlString);
HttpsURLConnection urlConnection = (HttpsURLConnection) url.openConnection();
urlConnection.setSSLSocketFactory(sslContext.getSocketFactory());
// skip hostname security check if specified
if (allowAllHosts) {
urlConnection.setHostnameVerifier(new AllowAllHostnameVerifier());
}
return urlConnection;
}
private static KeyStore buildKeyStore(Context context, int certRawResId) throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException {
// init a default key store
String keyStoreType = KeyStore.getDefaultType();
KeyStore keyStore = KeyStore.getInstance(keyStoreType);
keyStore.load(null, null);
// read and add certificate authority
Certificate cert = readCert(context, certRawResId);
keyStore.setCertificateEntry("ca", cert);
return keyStore;
}
private static Certificate readCert(Context context, int certResourceId) throws CertificateException, IOException {
// read certificate resource
InputStream caInput = context.getResources().openRawResource(certResourceId);
Certificate ca;
try {
// generate a certificate
CertificateFactory cf = CertificateFactory.getInstance("X.509");
ca = cf.generateCertificate(caInput);
} finally {
caInput.close();
}
return ca;
}
}
キーポイント:
Certificate
オブジェクトは.crt
ファイルから生成されます。KeyStore
が作成されます。keyStore.setCertificateEntry("ca", cert)
は、別名「ca」の下の鍵ストアに証明書を追加しています。コードを変更して、証明書(中間CAなど)を追加します。SSLSocketFactory
またはHTTPClient
で使用できるHttpsURLConnection
を生成することです。SSLSocketFactory
は、ホスト名の検証などをスキップするなど、さらに構成できます。詳細情報: http://developer.Android.com/training/articles/security-ssl.html
Httpsを使用してAndroidアプリをRESTfulサービスに接続しようとしていらいらしました。また、証明書のチェックを完全に無効にすることを提案するすべての回答について少しイライラしていました。その場合、httpsのポイントは何ですか?
しばらくこのトピックについてグーグル検索した後、私は最終的に this 外部jarが不要なソリューションを見つけました。ただAndroid APIです。 2014年7月に投稿したAndrew Smithに感謝します。
/**
* Set up a connection to myservice.domain using HTTPS. An entire function
* is needed to do this because myservice.domain has a self-signed certificate.
*
* The caller of the function would do something like:
* HttpsURLConnection urlConnection = setUpHttpsConnection("https://littlesvr.ca");
* InputStream in = urlConnection.getInputStream();
* And read from that "in" as usual in Java
*
* Based on code from:
* https://developer.Android.com/training/articles/security-ssl.html#SelfSigned
*/
public static HttpsURLConnection setUpHttpsConnection(String urlString)
{
try
{
// Load CAs from an InputStream
// (could be from a resource or ByteArrayInputStream or ...)
CertificateFactory cf = CertificateFactory.getInstance("X.509");
// My CRT file that I put in the assets folder
// I got this file by following these steps:
// * Go to https://littlesvr.ca using Firefox
// * Click the padlock/More/Security/View Certificate/Details/Export
// * Saved the file as littlesvr.crt (type X.509 Certificate (PEM))
// The MainActivity.context is declared as:
// public static Context context;
// And initialized in MainActivity.onCreate() as:
// MainActivity.context = getApplicationContext();
InputStream caInput = new BufferedInputStream(MainActivity.context.getAssets().open("littlesvr.crt"));
Certificate ca = cf.generateCertificate(caInput);
System.out.println("ca=" + ((X509Certificate) ca).getSubjectDN());
// Create a KeyStore containing our trusted CAs
String keyStoreType = KeyStore.getDefaultType();
KeyStore keyStore = KeyStore.getInstance(keyStoreType);
keyStore.load(null, null);
keyStore.setCertificateEntry("ca", ca);
// Create a TrustManager that trusts the CAs in our KeyStore
String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
tmf.init(keyStore);
// Create an SSLContext that uses our TrustManager
SSLContext context = SSLContext.getInstance("TLS");
context.init(null, tmf.getTrustManagers(), null);
// Tell the URLConnection to use a SocketFactory from our SSLContext
URL url = new URL(urlString);
HttpsURLConnection urlConnection = (HttpsURLConnection)url.openConnection();
urlConnection.setSSLSocketFactory(context.getSocketFactory());
return urlConnection;
}
catch (Exception ex)
{
Log.e(TAG, "Failed to establish SSL connection to server: " + ex.toString());
return null;
}
}
私のモックアップアプリでうまく機能しました。
一番の答えは私にはうまくいきませんでした。いくつかの調査の後、「Android Developer」で必要な情報を見つけました: https://developer.Android.com/training/articles/security-ssl.html#SelfSigned
X509TrustManagerの空の実装を作成すると、トリックが行われました。
private static class MyTrustManager implements X509TrustManager
{
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType)
throws CertificateException
{
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType)
throws CertificateException
{
}
@Override
public X509Certificate[] getAcceptedIssuers()
{
return null;
}
}
...
HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
try
{
// Create an SSLContext that uses our TrustManager
SSLContext context = SSLContext.getInstance("TLS");
TrustManager[] tmlist = {new MyTrustManager()};
context.init(null, tmlist, null);
conn.setSSLSocketFactory(context.getSocketFactory());
}
catch (NoSuchAlgorithmException e)
{
throw new IOException(e);
} catch (KeyManagementException e)
{
throw new IOException(e);
}
conn.setRequestMethod("GET");
int rcode = conn.getResponseCode();
TustManagerのこの空の実装は単なる例であり、本稼働環境で使用すると深刻なセキュリティ上の脅威が発生することに注意してください!
HttpClient
は廃止されるため、Googleでは HTTP/HTTPS接続のAndroid Volley の使用を推奨しています。だから、あなたは正しい選択を知っています:)。
また、NEVER NUKE SSL Certificates(NEVER !!!)。
SSL証明書を無効にすることは、securityを促進するSSLの目的に完全に反します。あなたが来るすべてのSSL証明書を爆破することを計画している場合、SSLを使用する意味はありません。より良い解決策は、SSLを使用しないか、より良い解決策となるでしょう。HTTP/ HTTPS接続にAndroid Volleyを使用して、アプリでカスタムTrustManager
を作成することです。
Gist は、基本的なLoginAppで作成され、HTTPS接続を実行し、サーバー側で自己署名証明書を使用し、アプリで受け入れられます。
また、別の Gist が役立ちます。これは、サーバーでセットアップするための自己署名SSL証明書を作成し、アプリで証明書を使用する場合にも役立ちます。 非常に重要:上記のスクリプトで生成された.crtファイルを、Androidから「raw」ディレクトリにコピーする必要があります_プロジェクト。
この問題を回避するために、キーストアに追加の証明書を追加する方法は次のとおりです。 HTTPS経由でHttpClientを使用してすべての証明書を信頼する
ユーザーに尋ねるようなプロンプトは表示されませんが、ユーザーが「信頼されていないサーバー証明書」エラーに遭遇する可能性は低くなります。
SSL証明書を作成する最も簡単な方法
Firefoxを開きます(Chromeでも可能ですが、FFの方が簡単です)
自己署名SSL証明書を使用して開発サイトにアクセスしてください。
証明書(サイト名の横)をクリックします
「詳細」をクリックします
「証明書の表示」をクリックします
「詳細」をクリックします
「エクスポート...」をクリックします
「X.509 Certificate whith chain(PEM)」を選択し、フォルダーと名前を選択して保存し、「保存」をクリックします
コマンドラインに移動し、pemファイルをダウンロードしたディレクトリに移動して、「openssl x509 -inform PEM -outform DM -in .pem -out .crt」を実行します。
.crtファイルをAndroidデバイス内の/ sdcardフォルダーのルートにコピーしますAndroidデバイス内の[設定]> [セキュリティ]> [ストレージからインストール]。
証明書を検出し、デバイスに追加できるようにします。開発サイトを参照します。
初めてセキュリティ例外を確認するように求められます。それで全部です。
証明書は、Android(ブラウザー、Chrome、Opera、Dolphin ...)にインストールされているブラウザーで動作するはずです。
別のドメインから静的ファイルを提供している場合(私たちはすべてページスピードの愚痴です)、そのドメインの証明書も追加する必要があることに注意してください。
これらの修正は、SDK 16、リリース4.1.2を対象とする開発プラットフォームでは機能しなかったため、回避策を見つけました。
私のアプリは " http://www.example.com/page.php?data=somedata "を使用してサーバーにデータを保存します
最近page.phpが " https://www.secure-example.com/page.php "に移動し、 "javax.net.ssl.SSLException:Not trusted server certificate"を取得し続けています。
1ページのみのすべての証明書を受け入れる代わりに、 このガイドから 「 http://www.example.com/page .php "
<?php
caronte ("https://www.secure-example.com/page.php");
function caronte($url) {
// build curl request
$ch = curl_init();
foreach ($_POST as $a => $b) {
$post[htmlentities($a)]=htmlentities($b);
}
curl_setopt($ch, CURLOPT_URL,$url);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS,http_build_query($post));
// receive server response ...
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$server_output = curl_exec ($ch);
curl_close ($ch);
echo $server_output;
}
?>
Android上の特定の証明書を信頼するために、小さなライブラリ ssl-utils-Android を作成しました。
アセットディレクトリからファイル名を指定するだけで、任意の証明書をロードできます。
使用法:
OkHttpClient client = new OkHttpClient();
SSLContext sslContext = SslUtils.getSslContextForCertificateFile(context, "BPClass2RootCA-sha2.cer");
client.setSslSocketFactory(sslContext.getSocketFactory());
これは、A、ndroid 2.xでのSNI(サーバー名識別)サポートの欠如に起因する問題です。この問題に1週間苦労していましたが、次の質問に出くわしました。これは問題の良い背景を提供するだけでなく、セキュリティホールのない実用的かつ効果的なソリューションを提供します。
たぶんこれは役立つでしょう...自己署名証明書を使用するJavaクライアントで動作します(証明書のチェックはありません)。それはまったく安全ではないので、注意して開発ケースにのみ使用してください!!
Apache HttpClient 4.0でSSL証明書エラーを無視する方法
HttpClientライブラリを追加するだけでAndroidで動作することを願っています...幸運!!