接受带有自签名证书的 HTTPS 连接

我尝试使用 HttpClient库建立 HTTPS 连接,但问题是,由于证书没有由认可的证书颁发机构(CA)签名,比如在 Android 可信证书集中列出的 Verisign全球信号等,所以我一直得到 javax.net.ssl.SSLException: Not trusted server certificate

我已经看到了一些解决方案,其中您可以简单地接受所有证书,但是如果我想要询问用户该怎么办呢?

我想得到一个类似于浏览器的对话框,让用户决定是否继续。我最好使用与浏览器相同的证书商店。有什么想法吗?

336497 次浏览

您需要做的第一件事是设置验证级别。 这个水平并不高:

  • ALLOW _ ALL _ HOSTNAME _ VERIFIER
  • 浏览器 _ COMPATIBLE _ HOSTNAME _ VERIFIER
  • STRICT _ HOSTNAME _ VERIFIER

虽然 setHostnameVerifier ()方法对于新的库 apache 已经过时,但是对于 Android SDK 中的版本是正常的。 所以我们取 ALLOW_ALL_HOSTNAME_VERIFIER并将它设置在方法工厂 SSLSocketFactory.setHostnameVerifier()中。

接下来,您需要将协议的工厂设置为 https。

然后您需要使用 SingleClientConnManager创建一个 DefaultHttpClient。 另外在下面的代码中你可以看到,默认情况下也将使用我们的标志(ALLOW_ALL_HOSTNAME_VERIFIER)的方法 HttpsURLConnection.setDefaultHostnameVerifier()

下面的代码适合我:

HostnameVerifier hostnameVerifier = org.apache.http.conn.ssl.SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER;


DefaultHttpClient client = new DefaultHttpClient();


SchemeRegistry registry = new SchemeRegistry();
SSLSocketFactory socketFactory = SSLSocketFactory.getSocketFactory();
socketFactory.setHostnameVerifier((X509HostnameVerifier) hostnameVerifier);
registry.register(new Scheme("https", socketFactory, 443));
SingleClientConnManager mgr = new SingleClientConnManager(client.getParams(), registry);
DefaultHttpClient httpClient = new DefaultHttpClient(mgr, client.getParams());


// Set verifier
HttpsURLConnection.setDefaultHostnameVerifier(hostnameVerifier);


// Example send http request
final String url = "https://encrypted.google.com/";
HttpPost httpPost = new HttpPost(url);
HttpResponse response = httpClient.execute(httpPost);

要实现来自认证中心的安全连接,需要执行以下主要步骤,这些认证中心被认为不受 android 平台的信任。

按照许多用户的要求,我在这里镜像了 博客文章中最重要的部分:

  1. 获取所有必需的证书(root 和任何中间 CA)
  2. 使用 keytool 和 弹跳城堡提供程序创建密钥存储库,并导入证书
  3. 加载 android 应用程序中的密钥存储库,并将其用于安全连接(我建议使用 Apache HttpClient而不是标准的 java.net.ssl.HttpsURLConnection(更容易理解,性能更好))

拿上证书

您必须获得从端点证书到根 CA 的整个过程中构建链的所有证书。这意味着,任何(如果存在)中间 CA 证书和根 CA 证书。您不需要获得端点证书。

创建密钥存储库

下载 弹跳城堡供应商并将其存储到一个已知位置。 还要确保可以调用 keytool 命令(通常位于 JRE 安装的 bin 文件夹下)。

现在将获得的 certs (不要导入端点 cert)导入到 BouncyCastle 格式的密钥存储库中。

我没有测试它,但是我认为导入证书的顺序很重要。这意味着,首先导入最底层的中间 CA 证书,然后一直导入到根 CA 证书。

使用以下命令将创建一个密码为 我的秘密的新密钥存储库(如果还没有出现) ,并导入中级 CA 证书。我还定义了 BouncyCastle 提供程序,它可以在我的文件系统和密钥存储格式中找到。对链中的每个证书执行此命令。

keytool -importcert -v -trustcacerts -file "path_to_cert/interm_ca.cer" -alias IntermediateCA -keystore "res/raw/mykeystore.bks" -provider org.bouncycastle.jce.provider.BouncyCastleProvider -providerpath "path_to_bouncycastle/bcprov-jdk16-145.jar" -storetype BKS -storepass mysecret

验证证书是否正确导入密钥存储库:

keytool -list -keystore "res/raw/mykeystore.bks" -provider org.bouncycastle.jce.provider.BouncyCastleProvider -providerpath "path_to_bouncycastle/bcprov-jdk16-145.jar" -storetype BKS -storepass mysecret

应该输出整个链条:

RootCA, 22.10.2010, trustedCertEntry, Thumbprint (MD5): 24:77:D9:A8:91:D1:3B:FA:88:2D:C2:FF:F8:CD:33:93
IntermediateCA, 22.10.2010, trustedCertEntry, Thumbprint (MD5): 98:0F:C3:F8:39:F7:D8:05:07:02:0D:E3:14:5B:29:43

现在,您可以将密钥存储库作为原始资源复制到 res/raw/下的 Android 应用程序中

使用应用程序中的密钥存储库

首先,我们必须创建一个定制的 Apache HttpClient,它使用我们的密钥存储库进行 HTTPS 连接:

import org.apache.http.*


public class MyHttpClient extends DefaultHttpClient {


final Context context;


public MyHttpClient(Context context) {
this.context = context;
}


@Override
protected ClientConnectionManager createClientConnectionManager() {
SchemeRegistry registry = new SchemeRegistry();
registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
// Register for port 443 our SSLSocketFactory with our keystore
// to the ConnectionManager
registry.register(new Scheme("https", newSslSocketFactory(), 443));
return new SingleClientConnManager(getParams(), registry);
}


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 = context.getResources().openRawResource(R.raw.mykeystore);
try {
// Initialize the keystore with the provided trusted certificates
// Also provide the password of the keystore
trusted.load(in, "mysecret".toCharArray());
} finally {
in.close();
}
// Pass the keystore to the SSLSocketFactory. The factory is responsible
// for the verification of the server certificate.
SSLSocketFactory sf = new SSLSocketFactory(trusted);
// Hostname verification from certificate
// http://hc.apache.org/httpcomponents-client-ga/tutorial/html/connmgmt.html#d4e506
sf.setHostnameVerifier(SSLSocketFactory.STRICT_HOSTNAME_VERIFIER);
return sf;
} catch (Exception e) {
throw new AssertionError(e);
}
}
}

我们已经创建了自定义 HttpClient,现在可以使用它进行安全连接。例如,当我们对 REST 资源进行 GET 调用时:

// Instantiate the custom HttpClient
DefaultHttpClient client = new MyHttpClient(getApplicationContext());
HttpGet get = new HttpGet("https://www.mydomain.ch/rest/contacts/23");
// Execute the GET call and obtain the response
HttpResponse getResponse = client.execute(get);
HttpEntity responseEntity = getResponse.getEntity();

就是这样;)

下面介绍如何向 KeyStore 添加额外的证书以避免这个问题: 通过 HTTPS 信任使用 HttpClient 的所有证书

它不会像您要求的那样提示用户,但是它会降低用户遇到“不受信任的服务器证书”错误的可能性。

也许这会有帮助... ... 它可以在使用自签名证书的 Java 客户端上工作(没有对证书的检查)。请注意,并且只在开发案例中使用它,因为它根本就不安全! !

如何忽略 Apache HttpClient 4.0中的 SSL 证书错误

希望它能在 Android 上运行,只需添加 HttpClient 库... 祝你好运! !

这是由于缺乏 SNI (服务器名称识别)支持导致的问题,在 A,ndroid 2.x。我在这个问题上纠结了一个星期,直到我遇到了下面这个问题,它不仅提供了一个很好的问题背景,而且提供了一个工作和有效的解决方案,没有任何安全漏洞。

在 Android 2.3中没有同行证书错误,但在4中没有

如果你的服务器上有一个自定义/自签名的证书,而设备上没有,你可以使用下面的类来加载它,并在 Android 的客户端使用它:

将证书 *.crt文件放在 /res/raw中,以便它可以从 R.raw.*获得

使用下面的类获取 HTTPClientHttpsURLConnection,它们将使用该证书拥有一个套接字工厂:

package com.example.customssl;


import android.content.Context;
import org.apache.http.client.HttpClient;
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.AllowAllHostnameVerifier;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpParams;


import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManagerFactory;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;


public class CustomCAHttpsProvider {


/**
* Creates a {@link org.apache.http.client.HttpClient} which is configured to work with a custom authority
* certificate.
*
* @param context       Application Context
* @param certRawResId  R.raw.id of certificate file (*.crt). Should be stored in /res/raw.
* @param allowAllHosts If true then client will not check server against host names of certificate.
* @return Http Client.
* @throws Exception If there is an error initializing the client.
*/
public static HttpClient getHttpClient(Context context, int certRawResId, boolean allowAllHosts) throws Exception {




// build key store with ca certificate
KeyStore keyStore = buildKeyStore(context, certRawResId);


// init ssl socket factory with key store
SSLSocketFactory sslSocketFactory = new SSLSocketFactory(keyStore);


// skip hostname security check if specified
if (allowAllHosts) {
sslSocketFactory.setHostnameVerifier(new AllowAllHostnameVerifier());
}


// basic http params for client
HttpParams params = new BasicHttpParams();


// normal scheme registry with our ssl socket factory for "https"
SchemeRegistry schemeRegistry = new SchemeRegistry();
schemeRegistry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
schemeRegistry.register(new Scheme("https", sslSocketFactory, 443));


// create connection manager
ThreadSafeClientConnManager cm = new ThreadSafeClientConnManager(params, schemeRegistry);


// create http client
return new DefaultHttpClient(cm, params);
}


/**
* Creates a {@link javax.net.ssl.HttpsURLConnection} which is configured to work with a custom authority
* certificate.
*
* @param urlString     remote url string.
* @param context       Application Context
* @param certRawResId  R.raw.id of certificate file (*.crt). Should be stored in /res/raw.
* @param allowAllHosts If true then client will not check server against host names of certificate.
* @return Http url connection.
* @throws Exception If there is an error initializing the connection.
*/
public static HttpsURLConnection getHttpsUrlConnection(String urlString, Context context, int certRawResId,
boolean allowAllHosts) throws Exception {


// build key store with ca certificate
KeyStore keyStore = buildKeyStore(context, certRawResId);


// 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 sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, tmf.getTrustManagers(), null);


// Create a connection from url
URL url = new URL(urlString);
HttpsURLConnection urlConnection = (HttpsURLConnection) url.openConnection();
urlConnection.setSSLSocketFactory(sslContext.getSocketFactory());


// skip hostname security check if specified
if (allowAllHosts) {
urlConnection.setHostnameVerifier(new AllowAllHostnameVerifier());
}


return urlConnection;
}


private static KeyStore buildKeyStore(Context context, int certRawResId) throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException {
// init a default key store
String keyStoreType = KeyStore.getDefaultType();
KeyStore keyStore = KeyStore.getInstance(keyStoreType);
keyStore.load(null, null);


// read and add certificate authority
Certificate cert = readCert(context, certRawResId);
keyStore.setCertificateEntry("ca", cert);


return keyStore;
}


private static Certificate readCert(Context context, int certResourceId) throws CertificateException, IOException {


// read certificate resource
InputStream caInput = context.getResources().openRawResource(certResourceId);


Certificate ca;
try {
// generate a certificate
CertificateFactory cf = CertificateFactory.getInstance("X.509");
ca = cf.generateCertificate(caInput);
} finally {
caInput.close();
}


return ca;
}


}

要点:

  1. Certificate对象是从 .crt文件生成的。
  2. 创建默认的 KeyStore
  3. keyStore.setCertificateEntry("ca", cert)正在向别名为“ ca”的密钥存储库添加证书。您可以修改代码以添加更多的证书(中间 CA 等)。
  4. 主要目标是产生一个 SSLSocketFactory,然后可以使用的 HTTPClientHttpsURLConnection
  5. SSLSocketFactory可以进一步配置,例如跳过主机名验证等。

更多信息请访问: http://developer.android.com/training/articles/security-ssl.html

最好的答案对我不起作用。经过一番调查,我找到了“ Android 开发者”的必要信息: Https://developer.android.com/training/articles/security-ssl.html#selfsigned

创建一个 X509TrustManager 的空实现可以解决这个问题:

private static class MyTrustManager implements X509TrustManager
{


@Override
public void checkClientTrusted(X509Certificate[] chain, String authType)
throws CertificateException
{
}


@Override
public void checkServerTrusted(X509Certificate[] chain, String authType)
throws CertificateException
{
}


@Override
public X509Certificate[] getAcceptedIssuers()
{
return null;
}


}


...


HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
try
{
// Create an SSLContext that uses our TrustManager
SSLContext context = SSLContext.getInstance("TLS");
TrustManager[] tmlist = {new MyTrustManager()};
context.init(null, tmlist, null);
conn.setSSLSocketFactory(context.getSocketFactory());
}
catch (NoSuchAlgorithmException e)
{
throw new IOException(e);
} catch (KeyManagementException e)
{
throw new IOException(e);
}
conn.setRequestMethod("GET");
int rcode = conn.getResponseCode();

请注意,TustManager 的这个空白实现只是一个例子,在高效的环境中使用它会造成严重的安全威胁!

我在尝试用 https 将我的 Android 应用程序连接到 RESTful 服务时感到很沮丧。而且我对所有建议禁用证书检查的答案都感到有点恼火。如果您这样做,https 的意义是什么?

在谷歌上搜索了一段时间后,我终于找到了 这个解决方案,其中不需要外部的 jar,只需要 Android API。感谢安德鲁 · 史密斯,他在2014年7月发表了这篇文章

 /**
* Set up a connection to myservice.domain using HTTPS. An entire function
* is needed to do this because myservice.domain has a self-signed certificate.
*
* The caller of the function would do something like:
* HttpsURLConnection urlConnection = setUpHttpsConnection("https://littlesvr.ca");
* InputStream in = urlConnection.getInputStream();
* And read from that "in" as usual in Java
*
* Based on code from:
* https://developer.android.com/training/articles/security-ssl.html#SelfSigned
*/
public static HttpsURLConnection setUpHttpsConnection(String urlString)
{
try
{
// Load CAs from an InputStream
// (could be from a resource or ByteArrayInputStream or ...)
CertificateFactory cf = CertificateFactory.getInstance("X.509");


// My CRT file that I put in the assets folder
// I got this file by following these steps:
// * Go to https://littlesvr.ca using Firefox
// * Click the padlock/More/Security/View Certificate/Details/Export
// * Saved the file as littlesvr.crt (type X.509 Certificate (PEM))
// The MainActivity.context is declared as:
// public static Context context;
// And initialized in MainActivity.onCreate() as:
// MainActivity.context = getApplicationContext();
InputStream caInput = new BufferedInputStream(MainActivity.context.getAssets().open("littlesvr.crt"));
Certificate ca = cf.generateCertificate(caInput);
System.out.println("ca=" + ((X509Certificate) ca).getSubjectDN());


// 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 URLConnection to use a SocketFactory from our SSLContext
URL url = new URL(urlString);
HttpsURLConnection urlConnection = (HttpsURLConnection)url.openConnection();
urlConnection.setSSLSocketFactory(context.getSocketFactory());


return urlConnection;
}
catch (Exception ex)
{
Log.e(TAG, "Failed to establish SSL connection to server: " + ex.toString());
return null;
}
}

我的样机应用程序运行得很好。

我编写了小型库 Ssl-utils-android来信任 Android 上的特定证书。

您可以通过从资产目录中提供文件名来简单地加载任何证书。

用法:

OkHttpClient client = new OkHttpClient();
SSLContext sslContext = SslUtils.getSslContextForCertificateFile(context, "BPClass2RootCA-sha2.cer");
client.setSslSocketFactory(sslContext.getSocketFactory());

谷歌推荐使用 用于 HTTP/HTTPS 连接的 Android Volley,因为 HttpClient已经过时了。所以,你知道正确的选择:)。

还有 禁止核武器 SSL 证书(禁止! ! !)。

微调 SSL 证书,完全违背了 SSL 的目的,SSL 正在促进 保安。如果您打算炸掉所有的 SSL 证书,那么就没有使用 SSL 的必要了。更好的解决方案是在 App + 上使用 Android Volley 为 HTTP/HTTPS 连接创建自定义 TrustManager

下面是我创建的一个 要点,它有一个基本的 LoginApp,使用服务器端的自签名证书执行 HTTPS 连接,在 App 上被接受。

这里还有另一个 要点可能会有所帮助,它可以创建自签名 SSL 证书,用于在服务器上进行设置,也可以在应用程序上使用该证书。必须复制。由上面的脚本生成的 crt 文件,放到 Android 项目的“原始”目录中。

这些修复都不适用于我针对 SDK 16版本4.1.2的开发平台,所以我找到了一个变通方法。

我的应用程序使用“ http://www.example.com/page.php?data=somedata”在服务器上存储数据

最近 page.php 被移动到了“ https://www.secure-example.com/page.php”,我不断收到“ javax.net.ssl.SSLException: Not trust server certificate”。

与只接受一个页面的所有证书不同,从这本指南开始解决了在“ http://www.example.com/page.php”上编写自己的 page.php 的问题

<?php


caronte ("https://www.secure-example.com/page.php");


function caronte($url) {
// build curl request
$ch = curl_init();
foreach ($_POST as $a => $b) {
$post[htmlentities($a)]=htmlentities($b);
}
curl_setopt($ch, CURLOPT_URL,$url);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS,http_build_query($post));


// receive server response ...
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$server_output = curl_exec ($ch);
curl_close ($ch);


echo $server_output;
}


?>

创建 SSL 证书的最简单方法

打开火狐浏览器(我想 Chrome 也可以做到,但是对我来说用 FF 更容易)

使用自签名的 SSL 证书访问开发站点。

单击证书(站点名称旁边)

点击“更多信息”

点击“查看证书”

点击“详情”

点击“出口...”

选择“带链(PEM)的 X.509证书”,选择要保存的文件夹和名称,然后单击“保存”

转到命令行,到下载 PEM 文件的目录并执行“ openssl x509-information PEM-outform DM-in。Pem-out.”

将. crt 文件复制到 Android 设备中/sdcard 文件夹的根目录 在你的 Android 设备中,设置 > 安全 > 从存储器安装。

它应该检测证书并允许您将其添加到设备中 浏览到您的开发站点。

第一次应该要求您确认安全异常,仅此而已。

这个证书应该可以使用安装在 Android 上的任何浏览器(浏览器,Chrome,Opera,Dolphin...)

请记住,如果您从不同的域服务您的静态文件(我们都是页面速度婊子) ,您还需要添加该域的证书。

2020年1月19日自签证书问题解决方案:

要播放视频、图像、为任何自签名的证书调用 webservice 或连接到任何不安全的 URL,只需在执行任何操作之前调用此方法,它就会修复您的证书问题:

KOTLIN 暗号

  private fun disableSSLCertificateChecking() {
val hostnameVerifier = object: HostnameVerifier {
override fun verify(s:String, sslSession: SSLSession):Boolean {
return true
}
}
val trustAllCerts = arrayOf<TrustManager>(object: X509TrustManager {
override fun getAcceptedIssuers(): Array<X509Certificate> {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}


//val acceptedIssuers:Array<X509Certificate> = null
@Throws(CertificateException::class)
override fun checkClientTrusted(arg0:Array<X509Certificate>, arg1:String) {// Not implemented
}
@Throws(CertificateException::class)
override fun checkServerTrusted(arg0:Array<X509Certificate>, arg1:String) {// Not implemented
}
})
try
{
val sc = SSLContext.getInstance("TLS")
sc.init(null, trustAllCerts, java.security.SecureRandom())
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory())
HttpsURLConnection.setDefaultHostnameVerifier(hostnameVerifier)
}
catch (e: KeyManagementException) {
e.printStackTrace()
}
catch (e: NoSuchAlgorithmException) {
e.printStackTrace()
}
}