Android で Java の自己署名証明書を受け入れるにはどうすればよいですか?
コードサンプルは完璧です。
私はインターネット上のあらゆる場所を調べましたが、解決策を見つけたと主張する人もいますが、機能しないか、バックアップするサンプル コードがありません。
Android で Java の自己署名証明書を受け入れるにはどうすればよいですか?
コードサンプルは完璧です。
私はインターネット上のあらゆる場所を調べましたが、解決策を見つけたと主張する人もいますが、機能しないか、バックアップするサンプル コードがありません。
WebDav 経由で Microsoft Exchange に接続する exchangeIt にこの機能があります。SSL 経由で自己署名証明書に接続する HttpClient を作成するコードを次に示します。
SchemeRegistry schemeRegistry = new SchemeRegistry();
// http scheme
schemeRegistry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
// https scheme
schemeRegistry.register(new Scheme("https", new EasySSLSocketFactory(), 443));
HttpParams params = new BasicHttpParams();
params.setParameter(ConnManagerPNames.MAX_TOTAL_CONNECTIONS, 30);
params.setParameter(ConnManagerPNames.MAX_CONNECTIONS_PER_ROUTE, new ConnPerRouteBean(30));
params.setParameter(HttpProtocolParams.USE_EXPECT_CONTINUE, false);
HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
ClientConnectionManager cm = new ThreadSafeClientConnManager(params, schemeRegistry);
EasySSLSocketFactory はここにあり、EasyX509TrustManager はここにあります。
exchangeIt のコードはオープン ソースであり、問題がある場合はこちらの googlecode でホストされています。私はもう積極的に取り組んでいませんが、コードは機能するはずです。
Android 2.2 以降、プロセスが少し変更されていることに注意してください。これを確認して、上記のコードを機能させてください。
EJP が正しくコメントしているように、「この手法は根本的に安全ではないことに注意してください。少なくとも 1 つのピアが認証されない限り、SSL は安全ではありません。RFC 2246 を参照してください。」
そうは言っても、追加のクラスを使用しない別の方法は次のとおりです。
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.X509TrustManager;
private void trustEveryone() {
try {
HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier(){
public boolean verify(String hostname, SSLSession session) {
return true;
}});
SSLContext context = SSLContext.getInstance("TLS");
context.init(null, new X509TrustManager[]{new X509TrustManager(){
public void checkClientTrusted(X509Certificate[] chain,
String authType) throws CertificateException {}
public void checkServerTrusted(X509Certificate[] chain,
String authType) throws CertificateException {}
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}}}, new SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(
context.getSocketFactory());
} catch (Exception e) { // should never happen
e.printStackTrace();
}
}
昨日、当社の RESTful API を HTTPS に移行しているときに、この問題に直面しましたが、自己署名 SSL 証明書を使用していました。
私はどこでも探しましたが、私が見つけたすべての「正しい」マーク付きの回答は、証明書の検証を無効にすることで構成され、SSLのすべての意味を明らかにオーバーライドしました。
私はついに解決策にたどり着きました:
ローカル キーストアの作成
アプリで自己署名証明書を検証できるようにするには、Android がエンドポイントを信頼できるように、カスタム キーストアに証明書を提供する必要があります。
このようなカスタム キーストアの形式は BouncyCastle の「BKS」であるため、ここからダウンロードできる1.46 バージョンの BouncyCastleProvider が必要です。
自己署名証明書も必要です。名前はself_cert.pem
.
キーストアを作成するコマンドは次のとおりです。
<!-- language: lang-sh -->
$ keytool -import -v -trustcacerts -alias 0 \
-file *PATH_TO_SELF_CERT.PEM* \
-keystore *PATH_TO_KEYSTORE* \
-storetype BKS \
-provider org.bouncycastle.jce.provider.BouncyCastleProvider \
-providerpath *PATH_TO_bcprov-jdk15on-146.jar* \
-storepass *STOREPASS*
PATH_TO_KEYSTORE
キーストアが作成されるファイルを指します。存在してはなりません。
PATH_TO_bcprov-jdk15on-146.jar.JAR
ダウンロードした .jar ライブラリへのパスです。
STOREPASS
は、新しく作成したキーストアのパスワードです。
新しく作成したキーストアをコピーしますPATH_TO_KEYSTORE
( res/raw/certs.bks
certs.bksは単なるファイル名です。任意の名前を使用できます)
でキーを作成しres/values/strings.xml
ます
<!-- language: lang-xml -->
<resources>
...
<string name="store_pass">*STOREPASS*</string>
...
</resources>
継承する this クラスを作成しますDefaultHttpClient
import android.content.Context;
import android.util.Log;
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.SSLSocketFactory;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.params.HttpParams;
import java.io.IOException;
import java.io.InputStream;
import java.security.*;
public class MyHttpClient extends DefaultHttpClient {
private static Context appContext = null;
private static HttpParams params = null;
private static SchemeRegistry schmReg = null;
private static Scheme httpsScheme = null;
private static Scheme httpScheme = null;
private static String TAG = "MyHttpClient";
public MyHttpClient(Context myContext) {
appContext = myContext;
if (httpScheme == null || httpsScheme == null) {
httpScheme = new Scheme("http", PlainSocketFactory.getSocketFactory(), 80);
httpsScheme = new Scheme("https", mySSLSocketFactory(), 443);
}
getConnectionManager().getSchemeRegistry().register(httpScheme);
getConnectionManager().getSchemeRegistry().register(httpsScheme);
}
private SSLSocketFactory mySSLSocketFactory() {
SSLSocketFactory ret = null;
try {
final KeyStore ks = KeyStore.getInstance("BKS");
final InputStream inputStream = appContext.getResources().openRawResource(R.raw.certs);
ks.load(inputStream, appContext.getString(R.string.store_pass).toCharArray());
inputStream.close();
ret = new SSLSocketFactory(ks);
} catch (UnrecoverableKeyException ex) {
Log.d(TAG, ex.getMessage());
} catch (KeyStoreException ex) {
Log.d(TAG, ex.getMessage());
} catch (KeyManagementException ex) {
Log.d(TAG, ex.getMessage());
} catch (NoSuchAlgorithmException ex) {
Log.d(TAG, ex.getMessage());
} catch (IOException ex) {
Log.d(TAG, ex.getMessage());
} catch (Exception ex) {
Log.d(TAG, ex.getMessage());
} finally {
return ret;
}
}
}
のインスタンスを使用し**MyHttpClient**
て**DefaultHttpClient**
HTTPS クエリを作成すると、自己署名 SSL 証明書が使用され、正しく検証されます。
HttpResponse httpResponse;
HttpPost httpQuery = new HttpPost("https://yourserver.com");
... set up your query ...
MyHttpClient myClient = new MyHttpClient(myContext);
try{
httpResponse = myClient.(peticionHttp);
// Check for 200 OK code
if (httpResponse.getStatusLine().getStatusCode() == HttpURLConnection.HTTP_OK) {
... do whatever you want with your response ...
}
}catch (Exception ex){
Log.d("httpError", ex.getMessage());
}
何かを見逃していない限り、このページの他の回答は危険であり、SSL をまったく使用しないのと機能的に同等です。証明書が期待どおりのものであることを確認するためのさらなるチェックを行わずに自己署名証明書を信頼すると、誰でも自己署名証明書を作成して、サーバーになりすますことができます。その時点で、本当のセキュリティはありません。
(完全な SSL スタックを作成せずに) これを行う唯一の正当な方法は、証明書の検証プロセス中に信頼される追加のトラステッド アンカーを追加することです。どちらもトラスト アンカー証明書をアプリにハードコーディングし、それを OS が提供するトラステッド アンカーに追加する必要があります (そうしないと、実際の証明書を取得した場合にサイトに接続できなくなります)。
私はこれを行う2つの方法を知っています:
http://www.ibm.com/developerworks/java/library/j-customssl/#8の説明に従って、カスタム トラスト ストアを作成します。
X509TrustManager のカスタム インスタンスを作成し、getAcceptedIssuers メソッドをオーバーライドして、証明書を含む配列を返します。
public X509Certificate[] getAcceptedIssuers()
{
X509Certificate[] trustedAnchors =
super.getAcceptedIssuers();
/* Create a new array with room for an additional trusted certificate. */
X509Certificate[] myTrustedAnchors = new X509Certificate[trustedAnchors.length + 1];
System.arraycopy(trustedAnchors, 0, myTrustedAnchors, 0, trustedAnchors.length);
/* Load your certificate.
Thanks to http://stackoverflow.com/questions/11857417/x509trustmanager-override-without-allowing-all-certs
for this bit.
*/
InputStream inStream = new FileInputStream("fileName-of-cert");
CertificateFactory cf = CertificateFactory.getInstance("X.509");
X509Certificate cert = (X509Certificate)cf.generateCertificate(inStream);
inStream.close();
/* Add your anchor cert as the last item in the array. */
myTrustedAnchors[trustedAnchors.length] = cert;
return myTrustedAnchors;
}
このコードは完全にテストされておらず、コンパイルすらできない可能性がありますが、少なくとも正しい方向に導く必要があることに注意してください。
Brian Yarger の答えは、より大きな createSocket メソッドのオーバーロードを次のように変更すると、Android 2.2 でも機能します。自己署名 SSL が機能するようになるまで、しばらく時間がかかりました。
public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException, UnknownHostException {
return getSSLContext().getSocketFactory().createSocket(socket, host, port, autoClose);
}
@Chris - コメントを(まだ)追加できないため、これを回答として投稿します。webViewを使用するときにあなたのアプローチが機能するはずなのかどうか疑問に思っています。Android 2.3ではそうすることができません-代わりに、白い画面が表示されます。
さらに検索した後、webView で SSL エラーを処理するためのこの簡単な修正に出会いました。これは私にとって魅力的でした。
ハンドラーで、特別な開発モードになっているかどうかを確認し、handler.proceed() を呼び出します。それ以外の場合は、handler.cancel() を呼び出します。これにより、ローカル Web サイトで自己署名証明書に対して開発を行うことができます。
AndroidHttpProtocolParams
ではProtocolVersion
、HttpVersion
.
ProtocolVersion pv = new ProtocolVersion("HTTP", 1, 1);
HttpProtocolParams.setVersion(params, pv);