web-dev-qa-db-ja.com

不明なCAが自己署名した証明書を使用して、Android VolleyがHTTPSリクエストを実行する方法を教えてください。

質問をする前に、いくつかのリンクを1つずつ確認しましたが、どれも解決策にはなりませんでした。

私が今までに見つけた唯一のリンクはこれです。これは2つのアプローチを提供します: Android Volley

  • 1ºアプリにいくつかのクラスをインポートするように指示します。実際、インポートする必要がある別のクラスがあり、クラスは「Apache.org」から非推奨のライブラリを使用しています
  • 2ºすべてのSSL認証をNUKEする例(かなり悪い考え...)


私もこのブログを見つけました。たくさんの説明がありますが、結局、例では「Apache.org」の非推奨のライブラリを使用していること、そしてブログ自体にはAndroid Volley。 https://nelenkov.blogspot.mx/2011/12/using-custom-certificate-trust-store-on.html のコンテンツ


Androidからのこのリンクと「不明な認証局」セクションのコードもあります。これはソリューションについての良いアイデアを提供しますが、コード自体はその中に何かが欠けています構造(Android Studio complaining ...): https://developer.Android.com/training/articles/security-ssl.html

しかし、リンクからのこの引用は、問題を解決するための中心的な概念のようです。

"TrustManagerは、システムがサーバーからの証明書を検証するために使用するものであり、1つ以上のCAを持つキーストアから証明書を作成することによって、これらが唯一のCAになりますこのTrustManagerによって信頼されます。この例では、新しいTrustManagerを指定して、HttpsURLConnectionのデフォルトのSSLSocketFactoryをオーバーライドするために使用できるSSLSocketFactoryを提供する新しいSSLContextを初期化します。このようにして、接続は証明書の検証にCAを使用します。 "



そして、これが私の問題です:自己署名証明書を使用しているウェブサーバーがあり、「BKSトラストストア」の証明書に基づいています。 Android APPにde BKSトラストストアをインポートしました。今、私のアプリに次のコードがあります(MainActivityをここに投稿しています。これは、これに関連する唯一のクラスです)今までのところ、私はそう思う):

package com.domain.myapp;


import Android.content.Context;
import Android.content.Intent;
import Android.os.Bundle;
import Android.support.v7.app.AppCompatActivity;
import Android.util.Log;
import Android.view.View;

import Android.widget.EditText;
import Android.widget.Toast;

import com.Android.volley.Request;
import com.Android.volley.RequestQueue;
import com.Android.volley.Response;
import com.Android.volley.VolleyError;
import com.Android.volley.toolbox.HurlStack;
import com.Android.volley.toolbox.StringRequest;
import com.Android.volley.toolbox.Volley;

import Java.io.InputStream;
import Java.security.KeyStore;
import Java.util.HashMap;
import Java.util.Map;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManagerFactory;


public class LoginScreen extends AppCompatActivity {

Context ctx          = null;
InputStream inStream = null;
HurlStack hurlStack  = null;

EditText username    = null;
EditText password    = null;
String loginStatus   = null;

public LoginScreen() {

    try {
        TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        KeyStore ks = KeyStore.getInstance("BKS");
        inStream = ctx.getApplicationContext().getResources().openRawResource(R.raw.mytruststore);
        ks.load(inStream, null);
        inStream.close();
        tmf.init(ks);
        SSLContext sslContext = SSLContext.getInstance("TLS");
        sslContext.init(null, tmf.getTrustManagers(), null);
        final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
        hurlStack = new HurlStack(null, sslSocketFactory);
    } catch (Exception e){
        Log.d("Exception:",e.toString());
    }
}

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_login_screen);
    username = (EditText) findViewById(R.id.user);
    password = (EditText) findViewById(R.id.passwd);
}

public void login(View view) {

    RequestQueue queue = Volley.newRequestQueue(this, hurlStack);
    final String url = "https://myserver.domain.com/app/login";

    StringRequest postRequest = new StringRequest(Request.Method.POST, url,
            new Response.Listener<String>()
            {
                @Override
                public void onResponse(String response) {
                    Log.d("Response", response);
                    loginStatus = "OK";
                }
            },
            new Response.ErrorListener()
            {
                @Override
                public void onErrorResponse(VolleyError error) {
                    Log.d("Error.Response", String.valueOf(error));
                    loginStatus = "NOK";
                }
            }
    ) {
        @Override
        protected Map<String, String> getParams()
        {
            Map<String, String>  params = new HashMap<String, String>();
            params.put("username", String.valueOf(user));
            params.put("domain", String.valueOf(passwd));

            return params;
        }
    };
    queue.add(postRequest);

    if (loginStatus == "OK") {
        Intent intent = new Intent(LoginScreen.this, OptionScreen.class);
        startActivity(intent);
    } else {
        Toast.makeText(getApplicationContext(), "Login failed",Toast.LENGTH_SHORT).show();
    }
}

}

コンストラクタークラスについては、コードを自由にコピーし、コードの各部分から何を理解できるかについていくつかコメントを付けました。

try {
// I have a TrustManagerFactory object
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
// I have a KeyStore considering BKS (BOUNCY CASTLE) KeyStore object
KeyStore ks = KeyStore.getInstance("BKS");
// I have configured a inputStream using my TrustStore file as a Raw Resource
inStream = ctx.getApplicationContext().getResources().openRawResource(R.raw.mytruststore);
// I have loaded my Raw Resource into the KeyStore object
ks.load(inStream, null);
inStream.close();
// I have initialiazed my Trust Manager Factory, using my Key Store Object
tmf.init(ks);
// I have created a new SSL Context object
SSLContext sslContext = SSLContext.getInstance("TLS");
// I have initialized my new SSL Context, with the configured Trust Managers found on my Trust Store
sslContext.init(null, tmf.getTrustManagers(), null);
// I have configured a HttpClientStack, using my brand new Socket Context
final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
hurlStack = new HurlStack(null, sslSocketFactory);
} catch (Exception e){
Log.d("Exception:",e.toString());
}

この後、別のクラスメソッドで、クラスコンストラクターで構成したHttpClientStackを使用して、RequestQueueを取得します。

RequestQueue queue = Volley.newRequestQueue(this, hurlStack);
final String url = "https://myserver.domain.com/app/login";
StringRequest postRequest = new StringRequest(Request.Method.POST, url,new Response.Listener<String>()
    {
    ...
    ...
    }

アプリを実行して、WebServerが予期するユーザーとパスワードを指定すると、Android Monitor from Android Studioで次のメッセージが表示されます。

09-17 21:57:13.842 20617-20617/com.domain.myapp D/Error.Response:com.Android.volley.NoConnectionError:javax.net.ssl.SSLHandshakeException:Java.security.cert.CertPathValidatorException:Trust anchor for証明書パスが見つかりません。

このすべての説明の後、私は次の質問をします:

  • Androidがクラスのコンストラクターで構成したカスタムTrustManagerのCAからのSSL証明書を受け入れるようにするには、他に何を構成する必要がありますか?

許してください、でも私はAndroidプログラミングとJavaの初心者なので、ひどい間違いをしています...

何か助けていただければ幸いです。

[〜#〜]更新[〜#〜]

クラスのコンストラクターを改善し、ステートメントをより適切にグループ化し、KeyManagerFactoryを使用しました。これは、このプロセスで非常に重要であるようです。ここに行く:

public class LoginScreen extends AppCompatActivity {

...
...

  public LoginScreen() {

    try {
        inStream = this.getApplicationContext().getResources().openRawResource(R.raw.mytruststore);

        KeyStore ks = KeyStore.getInstance("BKS");
        ks.load(inStream, "bks*password".toCharArray());
        inStream.close();

        KeyManagerFactory kmf = KeyManagerFactory.getInstance("X509");
        kmf.init(ks, "bks*password".toCharArray());

        TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509");
        tmf.init(ks);

        SSLContext sslContext = SSLContext.getInstance("TLS");
        sslContext.init(kmf.getKeyManagers(),tmf.getTrustManagers(), null);
        SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
        hurlStack = new HurlStack(null, sslSocketFactory);
    } catch (Exception e){
        Log.d("Exception:",e.toString());
    }

  }

...
...

}

とにかく、まだ問題があります。

応答:com.Android.volley.NoConnectionError:javax.net.ssl.SSLHandshakeException:Java.security.cert.CertPathValidatorException:証明書パスの信頼アンカーが見つかりません。

14
codermx

私は次のコードで私のvolleyクラスに新しいrequestQueueを作成することでhttpsを実装しました

public RequestQueue getRequestQueue() {
    if (mRequestQueue == null) {
        mRequestQueue = Volley.newRequestQueue(getApplicationContext(), new HurlStack(null, newSslSocketFactory()));

    }

    return mRequestQueue;
}

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 = getApplicationContext().getResources().openRawResource(R.raw.keystore);
        try {
            // Initialize the keystore with the provided trusted certificates
            // Provide the password of the keystore
            trusted.load(in, KEYSTORE_PASSWORD);
        } finally {
            in.close();
        }

        String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
        TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
        tmf.init(trusted);

        SSLContext context = SSLContext.getInstance("TLS");
        context.init(null, tmf.getTrustManagers(), null);

        SSLSocketFactory sf = context.getSocketFactory();
        return sf;
    } catch (Exception e) {
        throw new AssertionError(e);
    }
}
5
Assem Mahrous

私は過去に同様の問題に直面しましたが、機能した同じ問題の解決策は、中間認証局サーバー側にインストールすることでした

ここで注目に値するのは、ほとんどのデスクトップブラウザでこのサーバーにアクセスしても、完全に不明なCAや自己署名サーバー証明書のようなエラーは発生しないことです。これは、ほとんどのデスクトップブラウザが信頼できる中間CAを長期間キャッシュするためです。ブラウザーが1つのサイトから中間CAにアクセスして学習すると、次回は中間CAを証明書チェーンに含める必要がなくなります。

一部のサイトでは、リソースの提供に使用されるセカンダリWebサーバーに対して意図的にこれを行っています。たとえば、メインのHTMLページに完全な証明書チェーンを備えたサーバーがあり、画像、CSS、JavaScriptなどのリソース用のサーバーがあり、おそらく帯域幅を節約するためにCAが含まれていない場合があります。残念ながら、これらのサーバーがAndroidアプリから呼び出すことを試みているWebサービスを提供している場合があります。これはそれほど寛容ではありません。

サーバーチェーンに中間CAを含めるようにサーバーを構成します。ほとんどのCAは、すべての一般的なWebサーバーに対してこれを行う方法に関するドキュメントを提供しています。これは、サイトでデフォルトのAndroidブラウザから少なくともAndroid 4.2。

ここに記載されている手順に従うことができます 中間認証局がありません

別の例 中間証明書とは

FYI trust-anchor-not-found-for-Android-ssl-connection

ブラウザーはルート認証局を受け入れることができますが、Androidブラウザーは同じようにキャッシュするため、SDKは同じことを行わない場合があります。ブラウザーは中間証明書をキャッシュし、それらを使用しますそのため、中間証明書がない場合、ランダムユーザーは信頼エラーを受け取りますが、他のユーザーはそうしません 中間証明書はFirefoxにキャッシュされますか?

2
Anurag Singh