Androidでの自己署名SSL受け入れ
-
07-07-2019 - |
質問
AndroidのJavaで自己署名証明書を受け入れるにはどうすればよいですか
コードサンプルは完璧でしょう。
インターネットのあちこちを調べましたが、ソリューションを見つけたと主張する人もいますが、機能しないか、バックアップするサンプルコードがありません。
解決
exchangeItにこの機能があり、WebDavを介してMicrosoft Exchangeに接続します。 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がエンドポイントを信頼できる方法で、証明書を含むカスタムキーストアを提供する必要があります。
このようなカスタムキーストアの形式は「BKS」です。 BouncyCastle から、こちら。
自己署名証明書も必要です。名前は 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>
-
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; } } }
今では、 ** DefaultHttpClient **
と同じように ** MyHttpClient **
のインスタンスを使用して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);
}
Androidでは、 HttpProtocolParams
は HttpVersion
ではなく ProtocolVersion
を受け入れます。
ProtocolVersion pv = new ProtocolVersion("HTTP", 1, 1);
HttpProtocolParams.setVersion(params, pv);
@Chris-まだコメントを追加できないため、これを回答として投稿しています。 webViewを使用しているときに、あなたのアプローチが機能するかどうか疑問に思っています。 Android 2.3では取得できません。代わりに白い画面が表示されます。
さらに検索した結果、 webViewでSSLエラーを処理するための簡単な修正は私にとって魅力的でした。
ハンドラーでは、特別な開発モードになっているかどうかを確認し、handler.proceed()を呼び出します。それ以外の場合は、handler.cancel()を呼び出します。これにより、ローカルWebサイトで自己署名証明書に対して開発を行うことができます。