如何在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可以信任您的端点的方式提供包含证书的自定义密钥库.
这种自定义密钥库的格式是BouncyCastle的 "BKS" ,所以你需要1.46版本的BouncyCastleProvider,你可以在这里下载.
您还需要自签名证书,我会假设它已命名self_cert.pem
.
现在,创建密钥库的命令是:
$ 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库的路径.
STOREPASS
是您新创建的密钥库密码.
在您的应用程序中包含KeyStore
将新创建的密钥库复制PATH_TO_KEYSTORE
到res/raw/certs.bks
(certs.bks只是文件名;您可以使用任何名称)
创建一个关键res/values/strings.xml
与
... *STOREPASS* ...
创建一个继承的这个类 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;
}
}
}
现在只需使用的实例**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; }
请注意,此代码完全未经测试,甚至可能无法编译,但至少应该引导您朝着正确的方向前进.