在Android上接受HTTP的证书
我正在尝试使用HttpClient在Android手机上build立Https连接。 麻烦的是,因为证书没有签名,我不断收到“javax.net.ssl.SSLException:不受信任的服务器证书”。
现在我已经看到了一系列解决scheme,您只需接受所有证书,但如果我想问用户呢? 我想得到一个类似于浏览器的对话框,让用户决定是否继续。
最好我想使用浏览器相同的certificatestore。 有任何想法吗?
您需要做的第一件事是设置validation的级别。 这样的水平不是很多:
- ALLOW_ALL_HOSTNAME_VERIFIER
- BROWSER_COMPATIBLE_HOSTNAME_VERIFIER
- STRICT_HOSTNAME_VERIFIER
虽然setHostnameVerifier()方法对于新库apache已经过时,但是对于Android SDK中的版本来说是正常的。 所以我们把ALLOW_ALL_HOSTNAME_VERIFIER
设置在方法工厂SSLSocketFactory.setHostnameVerifier()
。
接下来,您需要将协议的工厂设置为https。 要做到这一点,只需调用SchemeRegistry.register()
方法即可。
然后你需要用SingleClientConnManager
创build一个DefaultHttpClient
。 另外在下面的代码中你可以看到默认情况下也会使用HttpsURLConnection.setDefaultHostnameVerifier()
方法使用我们的标志( 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);
实现来自authentication机构的安全连接需要以下主要步骤,这些authentication机构不被认为是android平台所信任的。
正如许多用户所要求的,我从我的博客文章中反映了最重要的部分:
- 获取所有必需的证书(root和任何中间CA)
- 使用keytool和BouncyCastle提供程序创build密钥库并导入证书
- 加载你的android应用程序的密钥库,并使用它的安全连接(我build议使用Apache HttpClient而不是标准的
java.net.ssl.HttpsURLConnection
(更容易理解,更高性能)
抓住证书
您必须从terminal证书获取所有构build链的证书,直到根CA. 这意味着,任何(如果有)中级CA证书以及根CA证书。 您不需要获取端点证书。
创build密钥库
下载BouncyCastle提供程序并将其存储到已知位置。 同时确保您可以调用keytool命令(通常位于JRE安装的bin文件夹下)。
现在将获取的证书(不要导入terminal证书)导入BouncyCastle格式的密钥库。
我没有testing它,但我认为导入证书的顺序很重要。 这意味着,首先导入最低的中间CA证书,然后导入根CA证书。
使用以下命令将创build一个密码为mysecret的新密钥库(如果尚不存在),并且将导入Intermediate CA证书。 我还定义了BouncyCastle提供程序,它可以在我的文件系统和密钥库格式中find。 对链中的每个证书执行此命令。
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
validation证书是否正确导入到密钥库中:
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
现在你可以将密钥库作为一个原始资源复制到你的Android应用程序的res/raw/
在您的应用程序中使用密钥库
首先,我们必须创build一个自定义的Apache HttpClient,它使用我们的密钥库来进行HTTPS连接:
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); } } }
我们已经创build了自定义的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();
而已 ;)
如果你在服务器上有一个自定义/自签名证书,那么你可以使用下面的类加载它,并在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
被创build。 -
keyStore.setCertificateEntry("ca", cert)
将证书添加到别名“ca”下的密钥存储区中。 您修改代码以添加更多证书(中间CA等)。 - 主要目标是生成一个
SSLSocketFactory
,然后可以被HTTPClient
或HttpsURLConnection
。 - 可以进一步configuration
SSLSocketFactory
,例如跳过主机名validation等。
更多信息请访问: http : //developer.android.com/training/articles/security-ssl.html
最重要的答案不适合我。 经过一番调查,我发现“Android开发人员”所需的信息: https : //developer.android.com/training/articles/security-ssl.html#SelfSigned
创build一个空的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的这个空白实现仅仅是一个例子,在生产环境中使用它将会造成严重的安全威胁!
以下是如何将额外的证书添加到您的KeyStore以避免此问题: 使用HttpClient通过HTTPS信任所有证书
它不会像提问那样提示用户,但会使用户不太可能遇到“不受信任的服务器证书”错误。
我很沮丧尝试使用https将我的Android应用程序连接到我的RESTful服务。 另外,我对所有build议禁用证书检查的答案都有些恼火。 如果你这样做,https的点是什么?
在search了一段时间后,我终于find了这个解决scheme,不需要外部的jar包,只需要Android API。 感谢2014年7月发布的Andrew Smith
/** * 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; } }
它适用于我的样机应用程序。
Googlebuild议使用Android Volley进行HTTP连接。
永远不要修改SSL证书(从不)。
核实SSL证书与安全无关。
下面是我创build的一个基本的LoginApp,使用自签名的SSL证书,不需要核实所有的SSL证书,创build一个自定义的TrustManager,使用Android Volley来执行HTTPS请求: https : //gist.github.com / ivanlmj / f11fb50d35fa1f2b9698bfb06aedcbcd
这也是另一个可能有助于创build自签名SSL证书的要点: https : //gist.github.com/ivanlmj/a6a93dd142fb623d01262303d5bd8074
OBS:您必须将上述脚本生成的.crt文件复制到Android项目的“raw”目录中。
我花了几个小时寻找这个问题的解决scheme,没有“破坏”SSL证书。
最好的问候,让我知道如果你有任何麻烦。
我写了小型库ssl-utils-android来信任Android上的特定证书。
您可以简单地通过从资产目录中提供文件名来加载任何证书。
用法:
OkHttpClient client = new OkHttpClient(); SSLContext sslContext = SslUtils.getSslContextForCertificateFile(context, "BPClass2RootCA-sha2.cer"); client.setSslSocketFactory(sslContext.getSocketFactory());
这些修补程序都不适用于我的开发平台,目标SDK16版本4.1.2,所以我find了一个解决方法。
我的应用程序使用“ http://www.example.com/page.php?data=somedata ”在服务器上存储数据
最近page.php被转移到“ https://www.secure-example.com/page.php ”,我不断收到“javax.net.ssl.SSLException:不受信任的服务器证书”。
我不是只接受一个页面的所有证书,而是从本指南开始,我解决了我自己写的问题,在“ 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; } ?>
这是由于在ndroid 2.x中缺lessSNI(服务器名称识别)支持而导致的问题。 我一直在为这个问题苦苦挣扎了一个星期,直到遇到以下问题,这不仅给出了问题的良好背景,而且提供了一个没有任何安全漏洞的工作和有效的解决scheme。
Android 2.3中没有“同行证书”错误,但不是4
最简单的方法来创buildSSL证书
打开火狐浏览器(我想也可以使用Chrome浏览器,但使用FF更容易)
使用自签名SSL证书访问您的开发站点。
点击证书(站点名称旁边)
点击“更多信息”
点击“查看证书”
点击“详情”
点击“导出…”
select“X.509证书链(PEM)”,select文件夹名称进行保存,点击“保存”
转到命令行,到您下载pem文件的目录并执行“openssl x509 -inform PEM -outform DM -in .pem -out .crt”
将.crt文件复制到Android设备中的/ sdcard文件夹的根目录在Android设备中,select设置>安全>从存储进行安装。
它应该检测证书,并让您将其添加到设备浏览到您的开发网站。
第一次它应该要求你确认安全例外。 就这样。
该证书应该可以在您的Android上安装任何浏览器(浏览器,Chrome,Opera,Dolphin等)
请记住,如果您正在从不同的域(我们都是页面速度婊子)提供静态文件,则还需要为该域添加证书。
也许这会有帮助…它在使用自签名证书的Java客户端上工作(没有证书的检查)。 小心,只用于开发案例,因为这是不安全的!
如何忽略Apache HttpClient 4.0中的SSL证书错误
希望它能在Android上工作,只需添加HttpClient库…祝你好运!