Android上的自签名SSL验收

问题描述:

如何在Android上使用Java验证自签名证书?Android上的自签名SSL验收

代码示例将是完美的。

我在因特网上到处寻找,虽然有些人声称已找到解决方案,但它要么不起作用,要么没有示例代码来备份它。

我有exchangeit这个功能,它通过WebDav连接到Microsoft交换。下面是一些代码来创建一个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是here,而EasyX509TrustManager是here

该代码为交换它是开源的,并托管在googlecode here,如果您有任何问题。我不再积极地工作,但代码应该工作。

请注意,由于Android 2.2的过程发生了一些变化,因此请检查this以使上述代码正常工作。

+0

我不能得到这个工作。我不断收到`IOException:SSL握手失败:系统调用期间发生I/O错误,2.2中断管道。你碰巧知道解决这个问题的方法吗? – Felix 2010-11-01 14:37:35

+11

我不喜欢信任任何自签名证书。有没有办法添加证书颁发机构以防止中间人攻击? – 2011-01-26 14:49:25

+0

这就像魔术一样.... – Krishna 2012-02-03 04:38:55

正如EJP正确评论的,“读者应该注意到,这种技术根本上是不安全的,除非至少有一个对端被认证,否则SSL是不安全的,请参阅RFC 2246。

话虽如此,这里的另一种方式,没有任何多余的类:如果修改了更大的createSocket方法重载如下

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(); 
    } 
} 

布赖恩Yarger的答案在Android 2.2的工程,以及。我花了一段时间才得到自签名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); 

除非我遗漏了一些东西,否则本页的其他答案是危险的,在功能上等同于根本不使用SSL。如果您信任自签名证书而未进行进一步检查以确保证书是您期望的证书,则任何人都可以创建自签名证书并假装成您的服务器。那时,你没有真正的安全感。

只有这样做的合法方式(无需编写完整的SSL堆栈)是在证书验证过程中添加一个额外的可信锚点以便信任。两者都涉及将可信的锚定证书硬编码到您的应用程序中,并将其添加到操作系统提供的任何可信锚点(否则,如果您获得真正的证书,您将无法连接到您的站点)。

我所知道的两种方法可以做到这一点:

  1. 创建自定义的信任存储作为http://www.ibm.com/developerworks/java/library/j-customssl/#8

  2. 描述创建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://*.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; 
    } 
    

请注意,此代码完全未经测试,甚至可能无法编译,但至少应引导您朝正确的方向发展。

@Chris - 由于我无法添加评论(还),因此将其作为回答发布。我想知道你的方法是否应该在使用webView时工作。我无法在Android 2.3上实现这一点 - 相反,我只是得到一个白色屏幕。

经过一番搜索之后,我发现这个simple fix for handling SSL errors in a webView对我来说就像是一个魅力。

在处理程序中,我检查是否处于特殊的开发模式并调用handler.proceed(),否则我调用handler.cancel()。这使我可以根据本地网站上的自签证书进行开发。

昨天我遇到了这个问题,同时将我们公司的RESTful API迁移到HTTPS,但使用自签名SSL证书。

我看到处处都有,但是我发现所有“正确的”标记答案都包含禁用证书验证,明显覆盖了SSL的所有含义。

我终于来到了一个解决方案:

  1. 创建本地密钥库

    为了使您的应用程序,以验证您的自签名的证书,您需要提供与证书的自定义密钥库Android可以信任您的端点的方式。

这种定制密钥库格式为“BKS”从BouncyCastle,所以你需要在1.46版本BouncyCastleProvider的,您可以下载here

您还需要您的自签名证书,我将假定它的名称为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库文件的路径。

STOREPASS是您新创建的密钥库密码。

  1. 包含密钥库在您的应用程序
  2. 从复制到PATH_TO_KEYSTOREres/raw/certs.bkscerts.bks新创建的密钥库只是文件名,你可以使用任何名义你愿意)

    创建res/values/strings.xml

    <!-- language: lang-xml --> 
    
        <resources> 
        ... 
         <string name="store_pass">*STOREPASS*</string> 
        ... 
        </resources> 
    
    的关键10
    1. 创建这个类继承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; 
           } 
          } 
      } 
      
    2. 现在只需使用**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()); 
      } 
      
    开始=>
开始=>