可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
How do I accept a self-signed certificate in Java on Android?
A code sample would be perfect.
I\'ve looked everywhere on the Internet and while some people claim to have found the solution, it either does not work or there is no sample code to back it up.
回答1:
I have this functionality in exchangeIt, which connects to Microsoft exchange via WebDav. Here\'s some code to create an HttpClient which will connect to self signed cert\'s via 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);
The EasySSLSocketFactory is here, and the EasyX509TrustManager is here.
The code for exchangeIt is open source, and hosted on googlecode here, if you have any issues. I\'m not actively working on it anymore, but the code should work.
Note that since Android 2.2 the process has changed a bit, so check this to make the code above work.
回答2:
As EJP correctly commented, \"Readers should note that this technique is radically insecure. SSL is not secure unless at least one peer is authenticated. See RFC 2246.\"
Having said that, here\'s another way, without any extra classes:
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();
}
}
回答3:
I faced this issue yesterday, while migrating our company\'s RESTful API to HTTPS, but using self-signed SSL certificates.
I\'ve looking everywhere, but all the \"correct\" marked answers I\'ve found consisted of disabling certificate validation, clearly overriding all the sense of SSL.
I finally came to a solution:
Create Local KeyStore
To enable your app to validate your self-signed certificates, you need to provide a custom keystore with the certificates in a manner that Android can trust your endpoint.
The format for such custom keystores is \"BKS\" from BouncyCastle, so you need the 1.46 version of BouncyCastleProvider that you can download here.
You also need your self-signed certificate, I will assume it\'s named self_cert.pem
.
Now the command for creating your keystore is:
<!-- 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
points to a file where your keystore will be created. It MUST NOT EXIST.
PATH_TO_bcprov-jdk15on-146.jar.JAR
is the path to the downloaded .jar libary.
STOREPASS
is your newly created keystore password.
- Include KeyStore in your Application
Copy your newly created keystore from PATH_TO_KEYSTORE
to res/raw/certs.bks
(certs.bks is just the file name; you can use whatever name you wish)
Create a key in res/values/strings.xml
with
<!-- language: lang-xml -->
<resources>
...
<string name=\"store_pass\">*STOREPASS*</string>
...
</resources>
Create a this class that inherits 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;
}
}
}
Now simply use an instance of **MyHttpClient**
as you would with **DefaultHttpClient**
to make your HTTPS queries, and it will use and validate correctly your self-signed SSL certificates.
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());
}
回答4:
Unless I missed something, the other answers on this page are DANGEROUS, and are functionally equivalent to not using SSL at all. If you trust self-signed certificates without doing further checks to make sure the certificates are the ones that you are expecting, then anyone can create a self-signed certificate and can pretend to be your server. At that point, you have no real security.
The only legitimate way to do this (without writing a full SSL stack) is to add an additional trusted anchor to be trusted during the certificate verification process. Both involve hard-coding the trusted anchor certificate into your app and adding it to whatever trusted anchors that the OS provides (or else you won\'t be able to connect to your site if you get a real certificate).
I\'m aware of two ways to do this:
Create a custom trust store as described at http://www.ibm.com/developerworks/java/library/j-customssl/#8
Create a custom instance of X509TrustManager and override the getAcceptedIssuers method to return an array that contains your certificate:
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;
}
Note that this code is completely untested and may not even compile, but should at least steer you in the right direction.
回答5:
Brian Yarger\'s answer works in Android 2.2 as well if you modify the bigger createSocket method overload as follows. It took me a while to get self-signed SSLs working.
public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException, UnknownHostException {
return getSSLContext().getSocketFactory().createSocket(socket, host, port, autoClose);
}
回答6:
On Android, HttpProtocolParams
accepts ProtocolVersion
rather than HttpVersion
.
ProtocolVersion pv = new ProtocolVersion(\"HTTP\", 1, 1);
HttpProtocolParams.setVersion(params, pv);
回答7:
@Chris - Posting this as an answer since I can\'t add comments (yet). I\'m wondering if your approach is supposed to work when using a webView. I can\'t get it do so on Android 2.3 - instead I just get a white screen.
After some more searching, I came across this simple fix for handling SSL errors in a webView which worked like a charm for me.
In the handler I check to see if I\'m in a special dev mode and call handler.proceed(), otherwise I call handler.cancel(). This allows me to do development against a self-signed cert on a local website.