Android上的自签名SSL接受
-
07-07-2019 - |
题
如何在Android上接受Java中的自签名证书?
代码示例将是完美的。
我在互联网上随处可见,虽然有些人声称找到了解决方案,但它要么不起作用,要么没有示例代码来支持它。
解决方案
我在exchangeIt中有这个功能,它通过WebDav连接到Microsoft Exchange。下面是一些创建HttpClient的代码,它将通过SSL连接到自签名证书:
SchemeRegistry schemeRegistry = new SchemeRegistry();
// http scheme
schemeRegistry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
// https scheme
schemeRegistry.register(new Scheme("https", new EasySSLSocketFactory(), 443));
HttpParams params = new BasicHttpParams();
params.setParameter(ConnManagerPNames.MAX_TOTAL_CONNECTIONS, 30);
params.setParameter(ConnManagerPNames.MAX_CONNECTIONS_PER_ROUTE, new ConnPerRouteBean(30));
params.setParameter(HttpProtocolParams.USE_EXPECT_CONTINUE, false);
HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
ClientConnectionManager cm = new ThreadSafeClientConnManager(params, schemeRegistry);
EasySSLSocketFactory是这里,EasyX509TrustManager是这里。
exchangeIt的代码是开源的,并且托管在googlecode 这里,如果你有任何问题。我不再积极地研究它,但代码应该可以工作。
请注意,由于Android 2.2的过程发生了一些变化,因此请检查这个使代码在上面工作。
其他提示
正如EJP正确评论的那样,"读者应该注意到这种技术根本不安全。除非至少对一个对等方进行身份验证,否则SSL不安全。请参阅RFC 2246.“
话虽如此,这是另一种方式,没有任何额外的课程:
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.X509TrustManager;
private void trustEveryone() {
try {
HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier(){
public boolean verify(String hostname, SSLSession session) {
return true;
}});
SSLContext context = SSLContext.getInstance("TLS");
context.init(null, new X509TrustManager[]{new X509TrustManager(){
public void checkClientTrusted(X509Certificate[] chain,
String authType) throws CertificateException {}
public void checkServerTrusted(X509Certificate[] chain,
String authType) throws CertificateException {}
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}}}, new SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(
context.getSocketFactory());
} catch (Exception e) { // should never happen
e.printStackTrace();
}
}
我昨天遇到了这个问题,同时将我们公司的RESTful API迁移到HTTPS,但使用自签名SSL证书。
我到处寻找,但所有的“正确”都是我发现的标记答案包括禁用证书验证,明显凌驾于所有SSL的意义上。
我终于找到了解决方案:
-
创建本地密钥库
要使您的应用能够验证自签名证书,您需要以Android可以信任您的终端的方式提供包含证书的自定义密钥库。
醇>
- 在您的应用中包含KeyStore 醇>
-
创建一个继承
的此类DefaultHttpClient
import android.content.Context; import android.util.Log; 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.SSLSocketFactory; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.params.HttpParams; import java.io.IOException; import java.io.InputStream; import java.security.*; public class MyHttpClient extends DefaultHttpClient { private static Context appContext = null; private static HttpParams params = null; private static SchemeRegistry schmReg = null; private static Scheme httpsScheme = null; private static Scheme httpScheme = null; private static String TAG = "MyHttpClient"; public MyHttpClient(Context myContext) { appContext = myContext; if (httpScheme == null || httpsScheme == null) { httpScheme = new Scheme("http", PlainSocketFactory.getSocketFactory(), 80); httpsScheme = new Scheme("https", mySSLSocketFactory(), 443); } getConnectionManager().getSchemeRegistry().register(httpScheme); getConnectionManager().getSchemeRegistry().register(httpsScheme); } private SSLSocketFactory mySSLSocketFactory() { SSLSocketFactory ret = null; try { final KeyStore ks = KeyStore.getInstance("BKS"); final InputStream inputStream = appContext.getResources().openRawResource(R.raw.certs); ks.load(inputStream, appContext.getString(R.string.store_pass).toCharArray()); inputStream.close(); ret = new SSLSocketFactory(ks); } catch (UnrecoverableKeyException ex) { Log.d(TAG, ex.getMessage()); } catch (KeyStoreException ex) { Log.d(TAG, ex.getMessage()); } catch (KeyManagementException ex) { Log.d(TAG, ex.getMessage()); } catch (NoSuchAlgorithmException ex) { Log.d(TAG, ex.getMessage()); } catch (IOException ex) { Log.d(TAG, ex.getMessage()); } catch (Exception ex) { Log.d(TAG, ex.getMessage()); } finally { return ret; } } }
醇>
这种自定义密钥库的格式是“BKS”。来自 BouncyCastle ,因此您需要可以下载的1.46版本的BouncyCastleProvider 这里。
您还需要自签名证书,我会假设它名为 self_cert.pem
。
现在,创建密钥库的命令是:
<!-- language: lang-sh -->
$ keytool -import -v -trustcacerts -alias 0 \
-file *PATH_TO_SELF_CERT.PEM* \
-keystore *PATH_TO_KEYSTORE* \
-storetype BKS \
-provider org.bouncycastle.jce.provider.BouncyCastleProvider \
-providerpath *PATH_TO_bcprov-jdk15on-146.jar* \
-storepass *STOREPASS*
PATH_TO_KEYSTORE
指向将创建密钥库的文件。它 必须不存在 。
PATH_TO_bcprov-jdk15on-146.jar.JAR
是下载的.jar libary的路径。
STOREPASS
是您新创建的密钥库密码。
将新创建的密钥库从 PATH_TO_KEYSTORE
复制到 res / raw / certs.bks
( certs.bks 只是文件名;你可以使用你想要的任何名称)
使用
在 res / values / strings.xml
中创建一个键
<!-- language: lang-xml -->
<resources>
...
<string name="store_pass">*STOREPASS*</string>
...
</resources>
现在只需使用 ** MyHttpClient **
的实例,就像使用 ** DefaultHttpClient **
进行HTTPS查询一样,它将正确使用和验证您的自签名SSL证书。
HttpResponse httpResponse;
HttpPost httpQuery = new HttpPost("https://yourserver.com");
... set up your query ...
MyHttpClient myClient = new MyHttpClient(myContext);
try{
httpResponse = myClient.(peticionHttp);
// Check for 200 OK code
if (httpResponse.getStatusLine().getStatusCode() == HttpURLConnection.HTTP_OK) {
... do whatever you want with your response ...
}
}catch (Exception ex){
Log.d("httpError", ex.getMessage());
}
除非我遗漏了某些内容,否则此页面上的其他答案都是危险的,并且在功能上等同于根本不使用SSL。如果您信任自签名证书而未进行进一步检查以确保证书是您期望的证书,那么任何人都可以创建自签名证书并伪装成您的服务器。那时,你没有真正的安全感。
执行此操作的唯一合法方式(无需编写完整的SSL堆栈)是在证书验证过程中添加要信任的其他可信锚。两者都涉及将可信锚定证书硬编码到您的应用程序中并将其添加到操作系统提供的任何可信锚点(否则,如果您获得真实证书,则无法连接到您的站点)。
我知道有两种方法可以做到这一点:
-
按照 http所述创建自定义信任存储区://www.ibm.com/developerworks/java/library/j-customssl/#8
-
创建X509TrustManager的自定义实例并覆盖getAcceptedIssuers方法以返回包含证书的数组:
public X509Certificate[] getAcceptedIssuers() { X509Certificate[] trustedAnchors = super.getAcceptedIssuers(); /* Create a new array with room for an additional trusted certificate. */ X509Certificate[] myTrustedAnchors = new X509Certificate[trustedAnchors.length + 1]; System.arraycopy(trustedAnchors, 0, myTrustedAnchors, 0, trustedAnchors.length); /* Load your certificate. Thanks to http://stackoverflow.com/questions/11857417/x509trustmanager-override-without-allowing-all-certs for this bit. */ InputStream inStream = new FileInputStream("fileName-of-cert"); CertificateFactory cf = CertificateFactory.getInstance("X.509"); X509Certificate cert = (X509Certificate)cf.generateCertificate(inStream); inStream.close(); /* Add your anchor cert as the last item in the array. */ myTrustedAnchors[trustedAnchors.length] = cert; return myTrustedAnchors; }
醇>
请注意,此代码完全未经测试,甚至可能无法编译,但至少应该引导您朝着正确的方向发展。
Brian Yarger的答案适用于Android 2.2,如果您修改更大的createSocket方法重载,如下所示。我需要一段时间才能使自签名SSL工作。
public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException, UnknownHostException {
return getSSLContext().getSocketFactory().createSocket(socket, host, port, autoClose);
}
在Android上, HttpProtocolParams
接受 ProtocolVersion
而不是 HttpVersion
。
ProtocolVersion pv = new ProtocolVersion("HTTP", 1, 1);
HttpProtocolParams.setVersion(params, pv);
@Chris - 发布此作为答案,因为我无法添加评论(还)。我想知道你的方法是否应该在使用webView时工作。我无法在Android 2.3上实现这一点 - 相反,我只是得到一个白色屏幕。
经过一番搜索,我发现了这个用于处理webView中的SSL错误的简单修复,对我来说就像一个魅力。
在处理程序中,我检查我是否处于特殊开发模式并调用handler.proceed(),否则我调用handler.cancel()。这允许我根据本地网站上的自签名证书进行开发。