如何使用未知 CA 自签名的证书让 Android Volley 执行 HTTPS 请求?
Posted
技术标签:
【中文标题】如何使用未知 CA 自签名的证书让 Android Volley 执行 HTTPS 请求?【英文标题】:How can I make Android Volley perform HTTPS request, using a certificate self-signed by an Unknown CA? 【发布时间】:2017-01-26 00:16:43 【问题描述】:在提出问题之前,我发现了一些链接,我一一检查了,但没有一个给我解决方案:
知名 CA HTTPS request using volley 接受所有 SSL 证书 No peer certificate Exception - Volley and android with self signed certificate Node.js (Socket.io) Socket.io + SSL + self-signed CA certificate gives error when connecting 自签名证书“手动”导入: Android SSL HTTP Request using self signed cert and CA到目前为止,我发现的唯一链接是这个,它提供了两种方法:Making a HTTPS request using Android Volley
1º 指示将一些类导入您的应用程序,而实际上,必须导入其他类,并且这些类使用“apache.org”中已弃用的库 2º NUKE 所有 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,您可以使用它来覆盖 HttpsURLConnection 中的默认 SSLSocketFactory。这样,连接将使用您的 CA 进行证书验证。”
现在,我的问题是: 我有一个使用自签名证书的网络服务器,并且我已经根据其证书创建了一个“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,使用我在 Class COnstructor 上配置的 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/Error.Response: com.android.volley.NoConnectionError: javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException:找不到证书路径的信任锚。
在所有这些解释之后,我有以下问题:
为了让 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:找不到证书路径的信任锚。
【问题讨论】:
KeyManagerFactory 如果不需要客户端认证,则不需要。你可以省略它。Trust anchor for certification path not found
表示 SSL 客户端未在信任库中找到服务器证书。您确定 BKS 包含服务器证书吗?您是否导入了根 CA 或最终证书?如果您已导入根 CA,则可能是服务器端配置错误。尝试导入叶子证书
【参考方案1】:
我已经通过以下代码在我的 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);
【讨论】:
什么是 raw.keystore ? 文件夹 raw ,你把密钥库放在里面 如何制作这个密钥库? 你从服务器生成的证书文件中提取它 只分享我刚刚练习的内容。至于如何从服务器生成证书文件,我使用本教程wikihow.com/Export-Certificate-Public-Key-from-Chrome【参考方案2】:我过去也遇到过类似的问题,解决方法是在服务器端安装中间证书颁发机构。
值得注意的是,在大多数桌面浏览器中访问此服务器不会导致完全未知的 CA 或自签名服务器证书会导致的错误。这是因为大多数桌面浏览器会随着时间的推移缓存受信任的中间 CA。一旦浏览器从一个站点访问并了解了中间 CA,下次它就不需要将中间 CA 包含在证书链中。
一些网站有意为用于提供资源的辅助网络服务器执行此操作。例如,他们的主要 HTML 页面可能由具有完整证书链的服务器提供服务,但图像、CSS 或 javascript 等资源的服务器不包括 CA,大概是为了节省带宽。不幸的是,有时这些服务器可能会提供您尝试从您的 Android 应用程序调用的 Web 服务,这不是那么宽容。
配置服务器以在服务器链中包含中间 CA。大多数 CA 提供有关如何为所有常见 Web 服务器执行此操作的文档。如果您需要网站至少通过 Android 4.2 使用默认 Android 浏览器,这是唯一的方法。
你可以按照这里提到的步骤Missing intermediate certificate authority
另一个例子What is an intermediate certificate?
仅供参考trust-anchor-not-found-for-android-ssl-connection
浏览器可能接受根证书颁发机构,但 Android SDK 可能不会这样做,因为浏览器缓存相同。浏览器将缓存中间证书,并在不同站点之间使用它们。因此,如果您缺少中间证书,随机用户将收到信任错误,而其他用户则不会。 Do intermediate certificates get cached in Firefox?
【讨论】:
你是救命稻草,花了两天时间查看我的代码,这是服务器的错。【参考方案3】:您应该接受所有 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 文件夹中。
【讨论】:
这行得通。至于caInput
,也可以使用ByteArrayInputStream(certificate.getBytes())
。其中证书是String certificate
。对于证书,您需要提供 x509 格式的服务器证书(又名服务器公共)。首先在浏览器上尝试您的自签名 openssl 证书。如果它有效,那么您可以使用服务器证书。另外,我使用 Intermediate CA 签名证书。不是自签的。也许自签名的也可以工作。不过我没试过。【参考方案4】:
使用 OkHttp,您可以构建一个接受所有证书的 http 客户端:
public static OkHttpClient.Builder getUnsafeOkHttpClient()
try
// Create a trust manager that does not validate certificate chains
final TrustManager[] trustAllCerts = new TrustManager[]
new X509TrustManager()
@Override
public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException
@Override
public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException
@Override
public java.security.cert.X509Certificate[] getAcceptedIssuers()
return null;
;
// Install the all-trusting trust manager
final SSLContext sslContext = SSLContext.getInstance("SSL");
sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
// Create an ssl socket factory with our all-trusting manager
final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
OkHttpClient.Builder builder = new OkHttpClient.Builder();
builder.sslSocketFactory(sslSocketFactory, (X509TrustManager)trustAllCerts[0]);
builder.hostnameVerifier(new HostnameVerifier()
@Override
public boolean verify(String hostname, SSLSession session)
return true;
);
return builder;
catch (Exception e)
throw new RuntimeException(e);
【讨论】:
当我在 4.3 中尝试这个时,我陷入了困境,可能在builder.sslSocketFactory
部分。我检查过,直到那部分的所有内容都不为空。可能是什么情况?顺便说一句,我正在像这样创建我的客户:OkHttpClient okHttpClient = getUnsafeOkHttpClient().build();
以上是关于如何使用未知 CA 自签名的证书让 Android Volley 执行 HTTPS 请求?的主要内容,如果未能解决你的问题,请参考以下文章