web-dev-qa-db-ja.com

Android Java.security.cert.CertPathValidatorException:証明書パスのトラストアンカーが見つかりません

Androidアプリが認証と承認を行う3つのホストがあります。最終ホストはREST AP​​Iです。 Oauth認証および承認プロセスを初めて使用すると、問題なく機能します。

ただし、ユーザーがログインしてREST AP​​Iによって提供されるサービスにアクセスしてアプリを強制終了し、再度アプリを開くと、この問題が発生します。 この時点では、認証および承認プロセスは実行されず、REST AP​​Iのみが実行されます。Java.security.cert.CertPathValidatorExceptionが発生しましたが、最初の使用(ログインしてからアプリを使用)時に機能していました。

誰かがこの例外の背後にあるシナリオとアプリの問題点を説明できますか?これは、 this SO answer に従って、認証例外が無視される場合に機能します。

SSLSocketFactory sslSocketFactory = null;

        try {
            TrustManagerFactory tmf = TrustManagerFactory.getInstance(
                    TrustManagerFactory.getDefaultAlgorithm());
            // Initialise the TMF as you normally would, for example:
            try {
                tmf.init((KeyStore)null);
            } catch(KeyStoreException e) {
                e.printStackTrace();
            }
            TrustManager[] trustManagers = tmf.getTrustManagers();

            final X509TrustManager origTrustmanager = (X509TrustManager)trustManagers[0];

            // Create a trust manager that does not validate certificate chains
            TrustManager[] wrappedTrustManagers = new TrustManager[]{
                    new X509TrustManager() {
                        public Java.security.cert.X509Certificate[] getAcceptedIssuers() {
                            return origTrustmanager.getAcceptedIssuers();
                        }

                        public void checkClientTrusted(X509Certificate[] certs, String authType) {
                            try {
                                origTrustmanager.checkClientTrusted(certs, authType);
                            } catch(CertificateException e) {
                                e.printStackTrace();
                            }
                        }

                        public void checkServerTrusted(X509Certificate[] certs, String authType) {
                            try {
                                origTrustmanager.checkServerTrusted(certs, authType);
                            } catch(CertificateException e) {
                                e.printStackTrace();
                            }
                        }
                    }
            };
            //TrustManager[] trustAllCerts = TrustManagerFactory.getInstance("SSL").getTrustManagers();

            // Install the all-trusting trust manager
            final SSLContext sslContext = SSLContext.getInstance("TLS");
            sslContext.init(null, wrappedTrustManagers, new Java.security.SecureRandom());
            // Create an ssl socket factory with our all-trusting manager
            sslSocketFactory = sslContext.getSocketFactory();
        } catch (NoSuchAlgorithmException | KeyManagementException e) {
            e.printStackTrace();
        }
        return sslSocketFactory;

HttpリクエストにOkhttp 3を使用しています。提案は問題の解決に役立ちます。上記のコードスニペットを使用する場合、セキュリティ違反ですか?アプリのセキュリティに影響しますか?

11

Android他の人のための開発者サイト)に従ってシナリオとソリューションについてのアイデアを与えるためにこれに答えています。カスタムトラストマネージャーを使用してこれを解決しました。

問題はサーバー証明書にあり、中間認証局を見逃しています。ただし、最初のフローでは証明書パスが何らかの形で完了し、結果は証明書パスの検証に成功しました。

Android開発者向けサイト にこれに対する解決策があります。このサーバー証明書を信頼するカスタム信頼マネージャーを使用することを提案するか、サーバーチェーンに中間CAを含めるようにサーバーに提案します。

カスタム信頼マネージャー。ソース: https://developer.Android.com/training/articles/security-ssl.html#UnknownCa

_// Load CAs from an InputStream
// (could be from a resource or ByteArrayInputStream or ...)
CertificateFactory cf = CertificateFactory.getInstance("X.509");
// From https://www.washington.edu/itconnect/security/ca/load-der.crt
InputStream caInput = new BufferedInputStream(new FileInputStream("load-der.crt"));
Certificate ca;
try {
    ca = cf.generateCertificate(caInput);
    System.out.println("ca=" + ((X509Certificate) ca).getSubjectDN());
} finally {
    caInput.close();
}

// 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 okhttp to use a SocketFactory from our SSLContext
OkHttpClient okHttpClient client = new OkHttpClient.Builder().sslSocketFactory(context.getSocketFactory()).build();
_

UPDATE:中間認証局がサーバー側から証明書チェーンに追加された後、私の問題は解決しました。これが最良のソリューションです。証明書をアプリにバンドルするには、証明書の有効期限が切れる、または証明書管理に関連するその他の問題が発生したときにアプリを更新する必要があります。

UPDATE:03/09/2017私が見つけた証明書ファイルをロードする最も簡単な方法は、rawリソースの使用です。

_InputStream caInput = new BufferedInputStream(context
                .getResources().openRawResource(R.raw.certfilename));
_

certfilenameは、resources/rawフォルダーにある証明書ファイルです。また、okhttpの sslSocketFactory(SSLSocketFactory sslSocketFactory) は推奨されておらず、okhttp APIドキュメントで推奨されるアプローチを使用できます。

また、サーバーから証明書を取得する場合は、opensslを使用することをお勧めします。

_openssl s_client -connect {server-address}:{port} -showcerts
_

私はそれをfirefoxから取得し、ウイルスガードによって変更される状況に直面したためです。

22