web-dev-qa-db-ja.com

Androidでの自己署名SSL受け入れ

AndroidでJavaで自己署名証明書を受け入れるにはどうすればよいですか?

コードサンプルは完璧でしょう。

私はインターネットのいたるところを見てきましたが、解決策を見つけたと主張する人もいますが、機能しないか、バックアップするサンプルコードがありません。

52
Faisal Abid

私は、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は here で、EasyX509TrustManagerは here です。

ExchangeItのコードはオープンソースであり、問​​題がある場合はgooglecode here でホストされます。私はもう積極的に作業していませんが、コードは動作するはずです。

Android 2.2以降、プロセスが少し変更されているため、上記のコードが機能するように this を確認してください。

37
Brian Yarger

EJPが正しくコメントしたように、「読者はこの技術は根本的に安全ではないことに注意すべきです。少なくとも1つのピアが認証されない限り、SSLは安全ではありません。RFC2246を参照してください。」

そうは言っても、追加のクラスを使用しない別の方法を次に示します。

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();
    }
}
37
Chris Boyle

昨日、私たちの会社のRESTful APIをHTTPSに移行しながら、自己署名SSL証明書を使用してこの問題に直面しました。

私はどこでも探しましたが、見つけたすべての「正しい」マークされた答えは、証明書の検証を無効にすることで構成され、SSLのすべての意味を明らかに無効にしました。

私は最終的に解決策に来ました:

  1. ローカルキーストアの作成

    アプリが自己署名証明書を検証できるようにするには、Androidがエンドポイントを信頼できるように、カスタムキーストアに証明書を提供する必要があります。

このようなカスタムキーストアの形式は BouncyCastle からの「BKS」であるため、 here をダウンロードできるBouncyCastleProviderの1.46バージョンが必要です。

自己署名証明書も必要です。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は、新しく作成されたキーストアパスワードです。

  1. アプリケーションにキーストアを含める

新しく作成したキーストアをPATH_TO_KEYSTOREからres/raw/certs.bksにコピーします(certs.bksは単なるファイル名です。任意の名前を使用できます)

res/values/strings.xmlにキーを作成します

<!-- language: lang-xml -->

    <resources>
    ...
        <string name="store_pass">*STOREPASS*</string>
    ...
    </resources>
  1. 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をまったく使用しないことと機能的に同等です。証明書が期待するものであることを確認するためのさらなるチェックを行わずに自己署名証明書を信頼する場合、誰でも自己署名証明書を作成し、サーバーのふりをすることができます。その時点では、実際のセキュリティはありません。

onlyこれを行うための正当な方法(完全なSSLスタックを作成せずに)は、証明書検証プロセス中に信頼される追加の信頼できるアンカーを追加することです。どちらも、信頼できるアンカー証明書をアプリにハードコーディングし、OSが提供する信頼できるアンカーに追加します(または、実際の証明書を取得するとサイトに接続できなくなります)。

私はこれを行う2つの方法を知っています:

  1. http://www.ibm.com/developerworks/Java/library/j-customssl/#8 の説明に従って、カスタムトラストストアを作成します。

  2. 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;
    }
    

このコードは完全にテストされておらず、コンパイルすらできない場合がありますが、少なくとも正しい方向に導く必要があることに注意してください。

15
dgatwood

Brian Yargerの答えは、Android 2.2でも機能します。大きなcreateSocketメソッドオーバーロードを次のように変更した場合も同様です。自己署名SSLを機能させるのに時間がかかりました。

public Socket createSocket(Socket socket, String Host, int port, boolean autoClose) throws IOException, UnknownHostException {
    return getSSLContext().getSocketFactory().createSocket(socket, Host, port, autoClose);
}
4
Dmitry S.

@Chris-まだコメントを追加できないため、これを回答として投稿しています。 webViewを使用するときに、あなたのアプローチが機能するかどうか疑問に思っています。 Android 2.3-代わりに白い画面が表示されるだけです。

さらに検索した後、私はこれに遭遇しました webViewでSSLエラーを処理するための簡単な修正 これは私にとって魅力のように機能しました。

ハンドラーでは、特別な開発モードになっているかどうかを確認し、handler.proceed()を呼び出します。それ以外の場合は、handler.cancel()を呼び出します。これにより、ローカルWebサイトで自己署名証明書に対して開発を行うことができます。

0
Yaakov

Androidでは、HttpProtocolParamsProtocolVersionではなくHttpVersionを受け入れます。

ProtocolVersion pv = new ProtocolVersion("HTTP", 1, 1);
HttpProtocolParams.setVersion(params, pv);
0
James