accepting HTTPS connections with self-signed certificates
我正在尝试使用
我已经看到了一些解决方案,在这些解决方案中,您仅接受所有证书,但是如果我想问用户该怎么办?
我想要一个类似于浏览器的对话框,让用户决定是否继续。 最好是我想使用与浏览器相同的证书存储。 有任何想法吗?
您需要做的第一件事是设置验证级别。
这样的水平不是很多:
- ALLOW_ALL_HOSTNAME_VERIFIER
- BROWSER_COMPATIBLE_HOSTNAME_VERIFIER
- STRICT_HOSTNAME_VERIFIER
尽管setHostnameVerifier()方法对于新库apache已过时,但对于Android SDK中的版本而言却是正常的。
因此,我们采用
接下来,您需要将协议的工厂设置为https。为此,只需调用
然后,您需要使用
同样在下面的代码中,您可以看到默认情况下还将通过方法
下面的代码对我有用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | 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平台信任。
根据许多用户的要求,我已经在我的博客文章中反映了最重要的部分:
抓住证书
您必须获取从端点证书一直到根CA一直构建链的所有证书。这意味着,任何(如果存在)中间CA证书以及根CA证书。您无需获取端点证书。
创建密钥库
下载BouncyCastle Provider并将其存储到已知位置。
还要确保您可以调用keytool命令(通常位于JRE安装的bin文件夹下)。
现在,将获得的证书(不导入端点证书)导入到BouncyCastle格式的密钥库中。
我没有测试过,但是我认为导入证书的顺序很重要。这意味着,首先导入最低的中间CA证书,然后一直导入根CA证书。
使用以下命令,将创建带有密码mysecret的新密钥库(如果尚不存在),并将导入中间CA证书。我还定义了BouncyCastle提供程序,可以在我的文件系统和密钥库格式中找到它。对链中的每个证书执行此命令。
1 | 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 |
验证证书是否正确导入到密钥库中:
1 | 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 |
应该输出整个链:
1 2 | 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 |
现在,您可以在
在您的应用中使用密钥库
首先,我们必须创建一个自定义Apache HttpClient,它将我们的密钥库用于HTTPS连接:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | 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调用时。
1 2 3 4 5 6 | // 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的客户端上使用它:
将证书
使用下面的类来获取
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 | 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; } } |
关键点:
有关更多信息,请访问:http://developer.android.com/training/articles/security-ssl.html
由于不推荐使用
还有,永不NUKE SSL证书(永不!!!)。
核对SSL证书完全违反SSL的目的,SSL的目的在于提高安全性。如果您打算炸毁所有随附的SSL证书,则没有使用SSL的感觉。更好的解决方案是不使用SSL,或者更好的解决方案是在应用程序上创建自定义
这是我使用基本的LoginApp创建的要点,它使用服务器端使用的自签名证书执行HTTPS连接,并接受该应用程序。
这也是另一个要点,它可能会帮助您创建用于在服务器上设置的自签名SSL证书,并在您的App上使用该证书。非常重要:您必须将上述脚本生成的.crt文件复制到Android项目中的"原始"目录中。
尝试使用https将我的Android应用程序连接到RESTful服务时,我感到沮丧。另外,对于建议完全禁用证书检查的所有答案,我也有些恼火。如果这样做,https的意义是什么?
在搜索了一段时间后,我终于找到了不需要外部jar的解决方案,只需使用Android API。感谢Andrew Smith,他于2014年7月发布了它
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 | /** * 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; } } |
对于我的样机应用程序来说效果很好。
最佳答案对我没有用。经过一番调查,我在" Android Developer"上找到了所需的信息:
https://developer.android.com/training/articles/security-ssl.html#SelfSigned
创建X509TrustManager的空实现可以解决问题:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | 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中,以避免出现此问题:通过HTTPS使用HttpClient信任所有证书
它不会像您要求的那样提示用户,但是这将减少用户遇到"不受信任的服务器证书"错误的可能性。
创建SSL证书的最简单方法
打开Firefox(我想Chrome也可以,但使用FF对我来说更容易)
使用自签名SSL证书访问您的开发站点。
单击证书(站点名称旁边)
点击"更多信息"
点击"查看证书"
点击"详细信息"
点击"导出..."
选择" X.509证书蒙山链(PEM)",选择要保存的文件夹和名称,然后单击"保存"
转到命令行,转到下载了pem文件的目录,然后执行" openssl x509 -inform PEM -outform DM -in .pem -out .crt"
将.crt文件复制到Android设备内/ sdcard文件夹的根目录
在您的Android设备中,依次选择设置>安全>从存储安装。
它应该检测证书并将其添加到设备中
浏览到您的开发站点。
第一次应要求您确认安全例外。就这样。
该证书应可与您Android上安装的任何浏览器(浏览器,Chrome,Opera,海豚...)一起使用。
请记住,如果您要从其他域(我们都是页面速度母狗)提供静态文件,则还需要添加该域的证书。
这些修补程序都不适用于我针对SDK 16,Release 4.1.2的开发平台,因此我找到了一种解决方法。
我的应用程序使用" 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的问题。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | <?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-utils-android来信任Android上的特定证书。
您只需提供资产目录中的文件名即可加载任何证书。
用法:
1 2 3 | OkHttpClient client = new OkHttpClient(); SSLContext sslContext = SslUtils.getSslContextForCertificateFile(context,"BPClass2RootCA-sha2.cer"); client.setSslSocketFactory(sslContext.getSocketFactory()); |
这是由于ndroid 2.x中缺少SNI(服务器名称标识)支持而导致的。我在这个问题上奋斗了一个星期,直到遇到以下问题,这不仅为问题提供了良好的背景,而且还提供了一个有效且有效的解决方案,没有任何安全漏洞。
Android 2.3中没有对等证书错误,但4中没有
也许这会有所帮助...它适用于使用自签名证书的Java客户端(不检查证书)。请小心,仅将其用于开发案例,因为这根本不安全!!
如何忽略Apache HttpClient 4.0中的SSL证书错误
希望它可以在Android上运行,只需添加HttpClient库...祝您好运!