Android上的自签名SSL接受

时间:2009-08-01 18:12:34

标签: android ssl ssl-certificate self-signed

如何在Android上接受Java中的自签名证书?

代码示例将是完美的。

我在互联网上随处可见,虽然有些人声称找到了解决方案,但它要么不起作用,要么没有示例代码来支持它。

8 个答案:

答案 0 :(得分:37)

我在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为here,EasyX509TrustManager为here

如果您有任何问题,exchangeIt的代码是开源的,并托管在googlecode here上。我不再积极地研究它,但代码应该可以工作。

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

答案 1 :(得分:37)

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

答案 2 :(得分:35)

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

我到处寻找,但我发现的所有“正确”标记答案都包括禁用证书验证,明显凌驾于所有SSL的意义上。

我终于找到了解决方案:

  1. 创建本地密钥库

    要使您的应用能够验证自签名证书,您需要以Android可以信任您的终端的方式提供包含证书的自定义密钥库。

  2. 此类自定义密钥库的格式是BouncyCastle的“BKS”,因此您需要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 libary的路径。

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

    1. 在您的应用中包含KeyStore
    2. 将新创建的密钥库从PATH_TO_KEYSTORE复制到res/raw/certs.bks certs.bks 只是文件名;您可以使用您想要的任何名称)

      使用

      res/values/strings.xml中创建密钥
      <!-- language: lang-xml -->
      
          <resources>
          ...
              <string name="store_pass">*STOREPASS*</string>
          ...
          </resources>
      
      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());
        }
        

答案 3 :(得分:15)

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

执行此操作的唯一合法方式(无需编写完整的SSL堆栈)是在证书验证过程中添加要信任的其他可信锚。两者都涉及将可信锚定证书硬编码到您的应用程序中并将其添加到操作系统提供的任何可信锚点(否则,如果您获得真实证书,则无法连接到您的站点)。

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

  1. http://www.ibm.com/developerworks/java/library/j-customssl/#8

  2. 所述创建自定义信任存储区
  3. 创建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;
    }
    
  4. 请注意,此代码完全未经测试,甚至可能无法编译,但至少应该引导您朝着正确的方向发展。

答案 4 :(得分:4)

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

答案 5 :(得分:0)

在Android上,HttpProtocolParams接受ProtocolVersion而不是HttpVersion

ProtocolVersion pv = new ProtocolVersion("HTTP", 1, 1);
HttpProtocolParams.setVersion(params, pv);

答案 6 :(得分:0)

@Chris - 发布此作为答案,因为我还无法添加评论。我想知道你的方法是否应该在使用webView时起作用。我无法在Android 2.3上实现这一目标 - 相反,我只是得到一个白屏。

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

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

答案 7 :(得分:0)

此用例有很多替代方案。如果您不想在代码库中包含任何自定义代码,例如自定义 TrustManager,我建议您尝试使用 GitHub - SSLContext Kickstart 和以下代码片段:

<dependency>
    <groupId>io.github.hakky54</groupId>
    <artifactId>sslcontext-kickstart</artifactId>
    <version>6.7.0</version>
</dependency>

SSL 配置

SSLFactory sslFactory = SSLFactory.builder()
    .withUnsafeTrustMaterial()
    .build();

SSLContext sslContext = sslFactory.getSslContext();
SSLSocketFactory sslSocketFactory = sslFactory.getSslSocketFactory();

HttpClient 配置

HttpParams params = new BasicHttpParams();
HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
HttpProtocolParams.setContentCharset(params, HTTP.UTF_8);

SchemeRegistry registry = new SchemeRegistry();
registry.register(new Scheme("https", sslSocketFactory, 443));

ClientConnectionManager ccm = new ThreadSafeClientConnManager(params, registry);

HttpClient httpClient = new DefaultHttpClient(ccm, params);

HttpsUrlConnection

HttpsURLConnection.setDefaultSSLSocketFactory(sslSocketFactory);