我正在尝试使用HttpClient
lib建立HTTPS连接,但问题是,由于证书未由Verisign等GlobalSIgn等公认的证书颁发机构(CA)签名,{{ 3}}等等,列在Android受信任证书集上,我不断获得javax.net.ssl.SSLException: Not trusted server certificate
。
我已经看到了您只需接受所有证书的解决方案,但如果我想询问用户该怎么办?
我希望得到一个类似于浏览器的对话框,让用户决定是否继续。我最好使用与浏览器相同的证书库。有什么想法吗?
答案 0 :(得分:169)
您需要做的第一件事是设置验证级别。 这样的水平并不是那么多:
虽然方法setHostnameVerifier()对于新的库apache已经过时,但对于Android SDK中的版本是正常的。
因此我们采用ALLOW_ALL_HOSTNAME_VERIFIER
并在方法工厂SSLSocketFactory.setHostnameVerifier()
中设置它。
接下来,您需要将协议的工厂设置为https。为此,只需调用SchemeRegistry.register()
方法。
然后您需要使用DefaultHttpClient
创建SingleClientConnManager
。
同样在下面的代码中,您可以看到默认情况下也会使用方法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);
答案 1 :(得分:121)
要从证书颁发机构获得安全连接,需要执行以下主要步骤,这些安全连接不被android平台视为可信任。
根据许多用户的要求,我在这里反映了blog article中最重要的部分:
java.net.ssl.HttpsURLConnection
(更容易理解,更高效)您必须从端点证书一直到根CA获取构建链的所有证书。这意味着,任何(如果存在)中间CA证书以及根CA证书。您无需获取端点证书。
下载BouncyCastle Provider并将其存储到已知位置。 还要确保可以调用keytool命令(通常位于JRE安装的bin文件夹下)。
现在将获取的证书(不要导入端点证书)导入到BouncyCastle格式的密钥库中。
我没有测试过,但我认为导入证书的顺序很重要。这意味着,首先导入最低级别的中级CA证书,然后一直导入根CA证书。
使用以下命令将创建一个密码 mysecret 的新密钥库(如果尚未存在),并将导入中间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/
首先,我们必须创建一个使用我们的密钥库进行HTTPS连接的自定义Apache HttpClient:
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();
就是这样;)
答案 2 :(得分:15)
如果您在设备上没有自定义/自签名证书,则可以使用以下类加载它并在Android的客户端使用它:
将证书*.crt
文件放在/res/raw
中,以便R.raw.*
使用以下类获取HTTPClient
或HttpsURLConnection
将使用该证书的套接字工厂:
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;
}
}
关键点:
Certificate
个对象是从.crt
个文件生成的。KeyStore
。keyStore.setCertificateEntry("ca", cert)
正在将别名" ca"下的证书添加到密钥库。您修改代码以添加更多证书(中间CA等)。SSLSocketFactory
,HTTPClient
或HttpsURLConnection
可以使用SSLSocketFactory
。有关详情,请访问:http://developer.android.com/training/articles/security-ssl.html
答案 3 :(得分:6)
最佳答案对我不起作用。经过一番调查后,我发现了“Android Developer”所需的信息: 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的这个空实现只是一个示例,在高效的环境中使用它会导致严重的安全威胁!
答案 4 :(得分:6)
我很沮丧尝试使用https将我的Android应用程序连接到我的RESTful服务。此外,我对所有建议完全禁用证书检查的答案感到有些恼火。如果你这样做,那么重点是什么?
在谷歌搜索了一段时间之后,我终于找到了this解决方案,其中不需要外部jar,只需Android API。感谢Andrew Smith,他于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;
}
}
它适用于我的模型App。
答案 5 :(得分:6)
Google建议使用Android Volley for HTTP/HTTPS connections,因为HttpClient
已弃用。所以,你知道正确的选择:)。
此外,永远不会获得SSL证书(绝对不是!!!)。
核对SSL证书,完全违背SSL的目的,即提升安全性。如果你计划轰炸所有SSL证书,那就没有使用SSL的意识了。一个更好的解决方案是,不使用SSL,或者更好的解决方案,就是在App +上使用Android Volley为HTTP / HTTPS连接创建自定义TrustManager
。
这是我创建的Gist,使用基本的LoginApp执行HTTPS连接,在服务器端使用自签名证书,在应用程序上接受。
这也是另一个Gist,它可能有助于创建自签名SSL证书,以便在您的服务器上进行设置并在您的应用上使用证书。 非常重要:您必须将上述脚本生成的.crt文件复制到Android项目的“raw”目录中。
答案 6 :(得分:4)
以下是如何向KeyStore添加其他证书以避免此问题:Trusting all certificates using HttpClient over HTTPS
它不会像您要求的那样提示用户,但会降低用户遇到“不信任的服务器证书”错误的可能性。
答案 7 :(得分:2)
创建SSL证书的最简单方法
打开Firefox(我想这也适用于Chrome,但使用FF对我来说更容易)
使用自签名SSL证书访问您的开发站点。
单击证书(站点名称旁边)
点击“更多信息”
点击“查看证书”
点击“详细信息”
点击“导出...”
选择“X.509 Certificate whith chain(PEM)”,选择要保存的文件夹和名称,然后单击“保存”
转到命令行,到下载pem文件的目录并执行“openssl x509 -inform PEM -outform DM -in .pem -out .crt”
将.crt文件复制到Android设备内/ sdcard文件夹的根目录 在您的Android设备中,设置>安全>从存储安装。
它应检测证书并允许您将其添加到设备 浏览到您的开发站点。
第一次应该要求您确认安全例外。就这样。
证书应适用于Android上安装的任何浏览器(浏览器,Chrome,Opera,Dolphin ...)
请记住,如果您从其他域提供静态文件(我们都是页面速度的婊子),您还需要为该域添加证书。
答案 8 :(得分:1)
我写了一个小型库ssl-utils-android来信任Android上的特定证书。
您可以通过从assets目录中提供文件名来加载任何证书。
用法:
OkHttpClient client = new OkHttpClient();
SSLContext sslContext = SslUtils.getSslContextForCertificateFile(context, "BPClass2RootCA-sha2.cer");
client.setSslSocketFactory(sslContext.getSocketFactory());
答案 9 :(得分:1)
这些修复程序都不适用于我的开发平台,目标是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:不受信任的服务器证书”。
我没有接受只有一个页面的所有证书,starting with this guide我在“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;
}
?>
答案 10 :(得分:1)
2020年1月19日的自签名证书问题修正:
要播放视频,图像,为任何自签名证书调用网络服务或连接到任何不安全的url,只需在执行任何操作之前调用此方法,即可解决有关证书问题的问题:
科林码
Install-Package MeCab.DotNet -Version 0.0.26
答案 11 :(得分:0)
也许这会有用......它适用于使用自签名证书的Java客户端(没有检查证书)。小心并仅将其用于开发案例,因为这根本不安全!!
How to ignore SSL certificate errors in Apache HttpClient 4.0
希望它能在Android上运行,只需添加HttpClient库...祝你好运!!
答案 12 :(得分:0)
这是由于缺乏SNI(服务器名称识别)inA,ndroid 2.x而导致的问题。我一直在努力解决这个问题,直到我遇到以下问题,这不仅提供了问题的良好背景,而且提供了一个没有任何安全漏洞的有效解决方案。