首页 文章

接受与自签名证书的HTTPS连接

提问于
浏览
145

我正在尝试使用 HttpClient lib Build HTTPS连接,但问题是,因为证书未由识别的证书颁发机构(CA)签名,如VerisignGlobalSIgn等,列在Android受信任证书集上,我一直得到 javax.net.ssl.SSLException: Not trusted server certificate .

我已经看到了你只接受所有证书的解决方案,但如果我想询问用户该怎么办?

我想得到一个类似于浏览器的对话框,让用户决定是否继续 . 我最好使用与浏览器相同的证书库 . 有任何想法吗?

12 回答

  • 167

    您需要做的第一件事是设置验证级别 . 这样的水平并不是那么多:

    • ALLOW_ALL_HOSTNAME_VERIFIER

    • BROWSER_COMPATIBLE_HOSTNAME_VERIFIER

    • STRICT_HOSTNAME_VERIFIER

    虽然方法setHostnameVerifier()对于新的库apache已经过时,但对于Android SDK中的版本是正常的 . 所以我们将 ALLOW_ALL_HOSTNAME_VERIFIER 设置在方法工厂 SSLSocketFactory.setHostnameVerifier() 中 .

    接下来,您需要将协议的工厂设置为https . 为此,只需调用 SchemeRegistry.register() 方法即可 .

    然后你需要用 SingleClientConnManager 创建一个 DefaultHttpClient . 同样在下面的代码中你可以看到默认情况下也会使用我们的标志( ALLOW_ALL_HOSTNAME_VERIFIER )方法 HttpsURLConnection.setDefaultHostnameVerifier()

    下面的代码适用于我:

    HostnameVerifier hostnameVerifier = org.apache.http.conn.ssl.SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER;
    
    DefaultHttpClient client = new DefaultHttpClient();
    
    SchemeRegistry registry = new SchemeRegistry();
    SSLSocketFactory socketFactory = SSLSocketFactory.getSocketFactory();
    socketFactory.setHostnameVerifier((X509HostnameVerifier) hostnameVerifier);
    registry.register(new Scheme("https", socketFactory, 443));
    SingleClientConnManager mgr = new SingleClientConnManager(client.getParams(), registry);
    DefaultHttpClient httpClient = new DefaultHttpClient(mgr, client.getParams());
    
    // Set verifier     
    HttpsURLConnection.setDefaultHostnameVerifier(hostnameVerifier);
    
    // Example send http request
    final String url = "https://encrypted.google.com/";
    HttpPost httpPost = new HttpPost(url);
    HttpResponse response = httpClient.execute(httpPost);
    
  • 121

    要从证书颁发机构获得安全连接,需要以下主要步骤,这些安全连接不被android平台视为可信任 .

    根据许多用户的要求,我在这里反映了我最重要的部分:blog article

    • 获取所有必需的证书(root和任何中间CA)

    • 使用keytool和BouncyCastle提供程序创建密钥库并导入证书

    • 在您的Android应用程序中加载密钥库并将其用于安全连接(我建议使用Apache HttpClient而不是标准 java.net.ssl.HttpsURLConnection (更容易理解,性能更高)

    获取证书

    您必须从 endpoints 证书一直到根CA获取构建链的所有证书 . 这意味着,任何(如果存在)中间CA证书以及根CA证书 . 您无需获取 endpoints 证书 .

    创建密钥库

    下载BouncyCastle Provider并将其存储到已知位置 . 还要确保可以调用keytool命令(通常位于JRE安装的bin文件夹下) .

    现在将获取的证书(不要导入 endpoints 证书)导入到BouncyCastle格式的密钥库中 .

    我没有测试它,但我认为导入证书的顺序很重要 . 这意味着,首先导入最低级的中级CA证书,然后一直导入根CA证书 .

    使用以下命令,将创建一个密码为mysecret的新密钥库(如果尚未存在),并将导入中间CA证书 . 我还定义了BouncyCastle提供程序,它可以在我的文件系统和密钥库格式中找到 . 对链中的每个证书执行此命令 .

    keytool -importcert -v -trustcacerts -file "path_to_cert/interm_ca.cer" -alias IntermediateCA -keystore "res/raw/myKeystore.bks" -provider org.bouncycastle.jce.provider.BouncyCastleProvider -providerpath "path_to_bouncycastle/bcprov-jdk16-145.jar" -storetype BKS -storepass mysecret
    

    验证证书是否已正确导入密钥库:

    keytool -list -keystore "res/raw/myKeystore.bks" -provider org.bouncycastle.jce.provider.BouncyCastleProvider -providerpath "path_to_bouncycastle/bcprov-jdk16-145.jar" -storetype BKS -storepass mysecret
    

    应该输出整个链:

    RootCA, 22.10.2010, trustedCertEntry, Thumbprint (MD5): 24:77:D9:A8:91:D1:3B:FA:88:2D:C2:FF:F8:CD:33:93
    IntermediateCA, 22.10.2010, trustedCertEntry, Thumbprint (MD5): 98:0F:C3:F8:39:F7:D8:05:07:02:0D:E3:14:5B:29:43
    

    现在您可以将密钥库复制为Android应用程序中的原始资源 res/raw/

    在您的应用中使用密钥库

    首先,我们必须创建一个自定义Apache HttpClient,它使用我们的密钥库进行HTTPS连接:

    public class MyHttpClient extends DefaultHttpClient {
    
      final Context context;
    
      public MyHttpClient(Context context) {
          this.context = context;
      }
    
      @Override
      protected ClientConnectionManager createClientConnectionManager() {
          SchemeRegistry registry = new SchemeRegistry();
          registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
          // Register for port 443 our SSLSocketFactory with our keystore
          // to the ConnectionManager
          registry.register(new Scheme("https", newSslSocketFactory(), 443));
          return new SingleClientConnManager(getParams(), registry);
      }
    
      private SSLSocketFactory newSslSocketFactory() {
          try {
              // Get an instance of the Bouncy Castle KeyStore format
              KeyStore trusted = KeyStore.getInstance("BKS");
              // Get the raw resource, which contains the keystore with
              // your trusted certificates (root and any intermediate certs)
              InputStream in = context.getResources().openRawResource(R.raw.mykeystore);
              try {
                  // Initialize the keystore with the provided trusted certificates
                  // Also provide the password of the keystore
                  trusted.load(in, "mysecret".toCharArray());
              } finally {
                  in.close();
              }
              // Pass the keystore to the SSLSocketFactory. The factory is responsible
              // for the verification of the server certificate.
              SSLSocketFactory sf = new SSLSocketFactory(trusted);
              // Hostname verification from certificate
              // http://hc.apache.org/httpcomponents-client-ga/tutorial/html/connmgmt.html#d4e506
              sf.setHostnameVerifier(SSLSocketFactory.STRICT_HOSTNAME_VERIFIER);
              return sf;
          } catch (Exception e) {
              throw new AssertionError(e);
          }
      }
    }
    

    我们已经创建了自定义HttpClient,现在我们可以将它用于安全连接 . 例如,当我们对REST资源进行GET调用时 .

    // Instantiate the custom HttpClient
    DefaultHttpClient client = new MyHttpClient(getApplicationContext());
    HttpGet get = new HttpGet("https://www.mydomain.ch/rest/contacts/23");
    // Execute the GET call and obtain the response
    HttpResponse getResponse = client.execute(get);
    HttpEntity responseEntity = getResponse.getEntity();
    

    而已 ;)

  • 4

    如果您在设备上没有自定义/自签名证书,则可以使用以下类加载它并在Android的客户端使用它:

    将证书 *.crt 文件放在 /res/raw 中,以便从 R.raw.* 获取

    使用以下类获取 HTTPClientHttpsURLConnection ,它将具有使用该证书的套接字工厂:

    package com.example.customssl;
    
    import android.content.Context;
    import org.apache.http.client.HttpClient;
    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.AllowAllHostnameVerifier;
    import org.apache.http.conn.ssl.SSLSocketFactory;
    import org.apache.http.impl.client.DefaultHttpClient;
    import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
    import org.apache.http.params.BasicHttpParams;
    import org.apache.http.params.HttpParams;
    
    import javax.net.ssl.HttpsURLConnection;
    import javax.net.ssl.SSLContext;
    import javax.net.ssl.TrustManagerFactory;
    import java.io.IOException;
    import java.io.InputStream;
    import java.net.URL;
    import java.security.KeyStore;
    import java.security.KeyStoreException;
    import java.security.NoSuchAlgorithmException;
    import java.security.cert.Certificate;
    import java.security.cert.CertificateException;
    import java.security.cert.CertificateFactory;
    
    public class CustomCAHttpsProvider {
    
        /**
         * Creates a {@link org.apache.http.client.HttpClient} which is configured to work with a custom authority
         * certificate.
         *
         * @param context       Application Context
         * @param certRawResId  R.raw.id of certificate file (*.crt). Should be stored in /res/raw.
         * @param allowAllHosts If true then client will not check server against host names of certificate.
         * @return Http Client.
         * @throws Exception If there is an error initializing the client.
         */
        public static HttpClient getHttpClient(Context context, int certRawResId, boolean allowAllHosts) throws Exception {
    
    
            // build key store with ca certificate
            KeyStore keyStore = buildKeyStore(context, certRawResId);
    
            // init ssl socket factory with key store
            SSLSocketFactory sslSocketFactory = new SSLSocketFactory(keyStore);
    
            // skip hostname security check if specified
            if (allowAllHosts) {
                sslSocketFactory.setHostnameVerifier(new AllowAllHostnameVerifier());
            }
    
            // basic http params for client
            HttpParams params = new BasicHttpParams();
    
            // normal scheme registry with our ssl socket factory for "https"
            SchemeRegistry schemeRegistry = new SchemeRegistry();
            schemeRegistry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
            schemeRegistry.register(new Scheme("https", sslSocketFactory, 443));
    
            // create connection manager
            ThreadSafeClientConnManager cm = new ThreadSafeClientConnManager(params, schemeRegistry);
    
            // create http client
            return new DefaultHttpClient(cm, params);
        }
    
        /**
         * Creates a {@link javax.net.ssl.HttpsURLConnection} which is configured to work with a custom authority
         * certificate.
         *
         * @param urlString     remote url string.
         * @param context       Application Context
         * @param certRawResId  R.raw.id of certificate file (*.crt). Should be stored in /res/raw.
         * @param allowAllHosts If true then client will not check server against host names of certificate.
         * @return Http url connection.
         * @throws Exception If there is an error initializing the connection.
         */
        public static HttpsURLConnection getHttpsUrlConnection(String urlString, Context context, int certRawResId,
                                                               boolean allowAllHosts) throws Exception {
    
            // build key store with ca certificate
            KeyStore keyStore = buildKeyStore(context, certRawResId);
    
            // Create a TrustManager that trusts the CAs in our KeyStore
            String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
            TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
            tmf.init(keyStore);
    
            // Create an SSLContext that uses our TrustManager
            SSLContext sslContext = SSLContext.getInstance("TLS");
            sslContext.init(null, tmf.getTrustManagers(), null);
    
            // Create a connection from url
            URL url = new URL(urlString);
            HttpsURLConnection urlConnection = (HttpsURLConnection) url.openConnection();
            urlConnection.setSSLSocketFactory(sslContext.getSocketFactory());
    
            // skip hostname security check if specified
            if (allowAllHosts) {
                urlConnection.setHostnameVerifier(new AllowAllHostnameVerifier());
            }
    
            return urlConnection;
        }
    
        private static KeyStore buildKeyStore(Context context, int certRawResId) throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException {
            // init a default key store
            String keyStoreType = KeyStore.getDefaultType();
            KeyStore keyStore = KeyStore.getInstance(keyStoreType);
            keyStore.load(null, null);
    
            // read and add certificate authority
            Certificate cert = readCert(context, certRawResId);
            keyStore.setCertificateEntry("ca", cert);
    
            return keyStore;
        }
    
        private static Certificate readCert(Context context, int certResourceId) throws CertificateException, IOException {
    
            // read certificate resource
            InputStream caInput = context.getResources().openRawResource(certResourceId);
    
            Certificate ca;
            try {
                // generate a certificate
                CertificateFactory cf = CertificateFactory.getInstance("X.509");
                ca = cf.generateCertificate(caInput);
            } finally {
                caInput.close();
            }
    
            return ca;
        }
    
    }
    

    关键点:

    • Certificate 个对象是从 .crt 文件生成的 .

    • 创建了默认 KeyStore .

    • keyStore.setCertificateEntry("ca", cert) 正在将别名"ca"下的密钥库添加到密钥库中 . 您修改代码以添加更多证书(中间CA等) .

    • 主要目标是生成 SSLSocketFactory ,然后由 HTTPClientHttpsURLConnection 使用 .

    • SSLSocketFactory 可以进一步配置,例如跳过主机名验证等 .

    更多信息at:http://developer.android.com/training/articles/security-ssl.html

  • 1

    最佳答案对我不起作用 . 经过一番调查后,我在"Android Developer"找到了所需信息:https://developer.android.com/training/articles/security-ssl.html#SelfSigned

    创建一个X509TrustManager的空实现就可以了:

    private static class MyTrustManager implements X509TrustManager
    {
    
        @Override
        public void checkClientTrusted(X509Certificate[] chain, String authType)
             throws CertificateException
        {
        }
    
        @Override
        public void checkServerTrusted(X509Certificate[] chain, String authType)
            throws CertificateException
        {
        }
    
        @Override
        public X509Certificate[] getAcceptedIssuers()
        {
            return null;
        }
    
    }
    
    ...
    
    HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
    try
    {
        // Create an SSLContext that uses our TrustManager
        SSLContext context = SSLContext.getInstance("TLS");
        TrustManager[] tmlist = {new MyTrustManager()};
        context.init(null, tmlist, null);
        conn.setSSLSocketFactory(context.getSocketFactory());
    }
    catch (NoSuchAlgorithmException e)
    {
        throw new IOException(e);
    } catch (KeyManagementException e)
    {
        throw new IOException(e);
    }
    conn.setRequestMethod("GET");
    int rcode = conn.getResponseCode();
    

    Please be aware that this empty implementation of TustManager is just an example and using it in a productive environment would cause a severe security threat!

  • 0

    我试图使用https将我的Android应用程序连接到我的RESTful服务时感到很沮丧 . 此外,我对所有建议完全禁用证书检查的答案感到有些恼火 . 如果你这样做,那么https的重点是什么?

    在搜索了一段时间的主题后,我终于找到了this解决方案,其中不需要外部jar,只需Android API . 感谢2014年7月发布的Andrew Smith

    /**
     * Set up a connection to myservice.domain using HTTPS. An entire function
     * is needed to do this because myservice.domain has a self-signed certificate.
     * 
     * The caller of the function would do something like:
     * HttpsURLConnection urlConnection = setUpHttpsConnection("https://littlesvr.ca");
     * InputStream in = urlConnection.getInputStream();
     * And read from that "in" as usual in Java
     * 
     * Based on code from:
     * https://developer.android.com/training/articles/security-ssl.html#SelfSigned
     */
    public static HttpsURLConnection setUpHttpsConnection(String urlString)
    {
        try
        {
            // Load CAs from an InputStream
            // (could be from a resource or ByteArrayInputStream or ...)
            CertificateFactory cf = CertificateFactory.getInstance("X.509");
    
            // My CRT file that I put in the assets folder
            // I got this file by following these steps:
            // * Go to https://littlesvr.ca using Firefox
            // * Click the padlock/More/Security/View Certificate/Details/Export
            // * Saved the file as littlesvr.crt (type X.509 Certificate (PEM))
            // The MainActivity.context is declared as:
            // public static Context context;
            // And initialized in MainActivity.onCreate() as:
            // MainActivity.context = getApplicationContext();
            InputStream caInput = new BufferedInputStream(MainActivity.context.getAssets().open("littlesvr.crt"));
            Certificate ca = cf.generateCertificate(caInput);
            System.out.println("ca=" + ((X509Certificate) ca).getSubjectDN());
    
            // Create a KeyStore containing our trusted CAs
            String keyStoreType = KeyStore.getDefaultType();
            KeyStore keyStore = KeyStore.getInstance(keyStoreType);
            keyStore.load(null, null);
            keyStore.setCertificateEntry("ca", ca);
    
            // Create a TrustManager that trusts the CAs in our KeyStore
            String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
            TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
            tmf.init(keyStore);
    
            // Create an SSLContext that uses our TrustManager
            SSLContext context = SSLContext.getInstance("TLS");
            context.init(null, tmf.getTrustManagers(), null);
    
            // Tell the URLConnection to use a SocketFactory from our SSLContext
            URL url = new URL(urlString);
            HttpsURLConnection urlConnection = (HttpsURLConnection)url.openConnection();
            urlConnection.setSSLSocketFactory(context.getSocketFactory());
    
            return urlConnection;
        }
        catch (Exception ex)
        {
            Log.e(TAG, "Failed to establish SSL connection to server: " + ex.toString());
            return null;
        }
    }
    

    它适用于我的模型App .

  • 0

    Google建议使用Android Volley for HTTP/HTTPS connections,因为不推荐使用 HttpClient . 所以,你知道正确的选择:) .

    而且, NEVER NUKE SSL Certificates (NEVER!!!).

    核对SSL证书,完全违背SSL的目的,即推广 security . 有's no sense of using SSL, if you'计划轰炸所有SSL证书 . 更好的解决方案是,不使用SSL或更好的解决方案,将使用Android Volley for HTTP / HTTPS连接在您的应用上创建自定义 TrustManager .

    这是我创建的Gist,使用基本的LoginApp,在服务器端使用自签名证书执行HTTPS连接,在应用程序上接受 .

    这也是另一个Gist,可能有助于创建自签名SSL证书,以便在您的服务器上进行设置并在您的应用程序上使用证书 . Very important: 您必须将上述脚本生成的.crt文件复制到Android项目的"raw"目录中 .

  • 15

    以下是如何向KeyStore添加其他证书以避免此问题:Trusting all certificates using HttpClient over HTTPS

    它不会像您要求的那样提示用户,但会降低用户遇到“不可信服务器证书”错误的可能性 .

  • 6

    Simplest way for create SSL certificate

    打开Firefox(我想这也适用于Chrome,但对我来说,使用FF更容易)

    使用自签名SSL证书访问您的开发站点 .

    单击证书(站点名称旁边)

    点击“更多信息”

    点击“查看证书”

    点击“详细信息”

    点击“导出...”

    选择“X.509 Certificate whith chain(PEM)”,选择要保存的文件夹和名称,然后单击“保存”

    转到命令行,到您下载pem文件的目录并执行“openssl x509 -inform PEM -outform DM -in .pem -out .crt”

    将.crt文件复制到Android设备内/ sdcard文件夹的根目录在Android设备中,“设置”>“安全”>“从存储中安装” .

    它应该检测证书并允许您将其添加到设备浏览到您的开发站点 .

    第一次它应该要求您确认安全例外 . 就这样 .

    该证书适用于Android上安装的任何浏览器(浏览器,Chrome,Opera,Dolphin ......)

    请记住,如果您从不同的域提供静态文件(我们都是页面速度的婊子),您还需要为该域添加证书 .

  • 6

    我写了一个小型库ssl-utils-android来信任Android上的特定证书 .

    您可以通过从assets目录中提供文件名来加载任何证书 .

    用法:

    OkHttpClient client = new OkHttpClient();
    SSLContext sslContext = SslUtils.getSslContextForCertificateFile(context, "BPClass2RootCA-sha2.cer");
    client.setSslSocketFactory(sslContext.getSocketFactory());
    
  • 5

    这些修复程序都不适用于我的开发平台,目标是SDK 16,版本4.1.2,因此我找到了一种解决方法 .

    我的应用程序使用“http://www.example.com/page.php?data=somedata”将数据存储在服务器上

    最近page.php被移至“https://www.secure-example.com/page.php " and I keep getting " javax.net.ssl.SSLException:不可信服务器证书” .

    starting with this guide我没有接受只有一个页面的所有证书,而是在“http://www.example.com/page.php”上发表我自己的page.php解决了我的问题 .

    <?php
    
    caronte ("https://www.secure-example.com/page.php");
    
    function caronte($url) {
        // build curl request
        $ch = curl_init();
        foreach ($_POST as $a => $b) {
            $post[htmlentities($a)]=htmlentities($b);
        }
        curl_setopt($ch, CURLOPT_URL,$url);
        curl_setopt($ch, CURLOPT_POST, 1);
        curl_setopt($ch, CURLOPT_POSTFIELDS,http_build_query($post));
    
        // receive server response ...
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        $server_output = curl_exec ($ch);
        curl_close ($ch);
    
        echo $server_output;
    }
    
    ?>
    
  • 2

    也许这会有帮助......它适用于使用自签名证书的Java客户端(没有检查证书) . 小心并仅将其用于开发案例,因为这根本不安全!

    How to ignore SSL certificate errors in Apache HttpClient 4.0

    希望它能在Android上运行,只需添加HttpClient库...祝你好运!

  • 1

    这是由于缺乏SNI(服务器名称识别)支持在A,ndroid 2.x中导致的问题 . 我一直在努力解决这个问题,直到我遇到以下问题,这不仅提供了问题的良好背景,而且提供了一个没有任何安全漏洞的工作和有效的解决方案 .

    'No peer certificate' error in Android 2.3 but NOT in 4

相关问题