我如何使Android Volley执行HTTPS请求,使用由未知CA自签名的证书



在提出问题之前,我找到了一些链接,我逐一检查,没有一个给我一个解决方案:

  • well-kown CA 使用volley的HTTPS请求
  • 接受所有SSL证书没有对等证书例外- Volley和Android使用自签名证书
  • node . js (socket . io) 套接字。io + SSL +自签名CA证书在连接
  • 时出现错误
  • 自签名证书"手动"导入:Android SSL HTTP请求使用自签名证书和CA

到目前为止我发现的唯一链接是这个,它给出了两种方法:使用Android Volley进行HTTPS请求

  • 1º指示将一些类导入到您的应用程序,当确实有其他类必须导入时,并且这些类使用的是来自"apache.org"的废弃库
  • 2º一个核所有SSL证书的例子(非常糟糕的主意…)


我也找到了这个博客,它有很多解释,但最后,我意识到这些例子使用的是来自"apache.org"的废弃库,而且,博客本身没有Android Volley的内容。https://nelenkov.blogspot.mx/2011/12/using-custom-certificate-trust-store-on.html


还有这个来自Android的链接和"未知证书颁发机构"部分的代码,它给出了一个关于解决方案的好主意,但代码本身,在其结构中缺乏一些东西(Android Studio抱怨…):https://developer.android.com/training/articles/security-ssl.html

但链接中的这句话,似乎是解决问题的核心概念。

"系统使用TrustManager来验证来自服务器的证书,并且通过从具有一个或多个ca的KeyStore创建一个证书,这些证书将是该TrustManager信任的唯一ca。给定新的TrustManager,该示例初始化了一个新的SSLContext,它提供了一个SSLSocketFactory,您可以使用该SSLSocketFactory覆盖HttpsURLConnection中的默认SSLSocketFactory。这样,连接将使用您的ca进行证书验证。"



现在,这是我的问题:我有一个使用自签名证书的web服务器,我已经根据它的证书创建了一个"BKS信任库"。我已经导入了de BKS信任存储到我的Android应用程序,现在,我有以下代码在我的应用程序(我只是在这里张贴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());
}

之后,在另一个类方法中,我有RequestQueue,使用我在类构造函数上配置的HttpClientStack:

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 Studio的Android Monitor中看到以下消息:

09-17 21:57:13.842 20617-20617/com.domain。myapp D/错误。回应:com.android.volley.NoConnectionError:javax.net.ssl.SSLHandshakeException:certpathvalidatoreexception:没有找到证书路径的信任锚。

听完这些解释,我有以下问题:

  • 必须配置什么才能使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());
    }
  }
...
...
}

不管怎样,我还是有问题。

Response: com.android.volley.NoConnectionError: javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException:证书路径未找到信任锚。

我通过以下代码在我的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);
    }
}

我过去遇到过类似的问题,解决方法是在服务器端上安装中间证书颁发机构

这里值得注意的是,在大多数桌面浏览器中访问此服务器不会导致完全未知的CA或自签名服务器证书所导致的错误。这是因为大多数桌面浏览器都会随着时间缓存可信的中间ca。一旦浏览器从一个站点访问并了解了中间CA,下一次就不需要将中间CA包含在证书链中。

一些站点故意为用于提供资源的辅助web服务器做此操作。例如,它们的主HTML页面可能由具有完整证书链的服务器提供服务,但用于图像、CSS或JavaScript等资源的服务器可能不包括CA,这可能是为了节省带宽。不幸的是,有时这些服务器可能会提供一个web服务,你试图从你的Android应用程序调用,这是不宽容的。

配置服务器将中间CA包含在服务器链中。大多数ca提供了关于如何为所有通用web服务器执行此操作的文档。这是唯一的方法,如果你需要网站与默认的Android浏览器工作,至少通过Android 4.2。

您可以按照这里提到的步骤执行缺少中间证书颁发机构

另一个例子什么是中级证书?

通知你trust-anchor-not-found-for-android-ssl-connection

浏览器可能接受根证书授权,但Android SDK可能不会这样做,因为浏览器缓存相同的。浏览器将缓存中间证书,并在不同站点之间使用它们。因此,如果您缺少中间证书,则随机用户将收到信任错误,而其他用户则不会。中间证书缓存在Firefox中吗?

您应该接受所有SSL证书。要告诉volley信任您的SSL,一个选项是以下代码,它基于https://developer.android.com/training/articles/security-ssl.html

// trust the SSL certificate in our smarter server
    CertificateFactory cf = CertificateFactory.getInstance("X.509");
    InputStream caInput = ctx.getAssets().open("smarter_ssl.crt");
    Certificate ca;
    try {
        ca = cf.generateCertificate(caInput);
    } 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);
    SSLSocketFactory i = context.getSocketFactory();
    // Tell volley to use a SocketFactory from our SSLContext
    RequestQueue requestQueue = Volley.newRequestQueue(ctx.getApplicationContext(), new HurlStack(null, context.getSocketFactory()));

将。pfx ssl证书转换为。crt,执行:https://www.ibm.com/support/knowledgecenter/SSVP8U_9.7.0/com.ibm.drlive.doc/topics/r_extratsslcert.html

.crt文件应该作为文本放在assets文件夹中

相关内容

  • 没有找到相关文章

最新更新