163

我正在尝试使用HttpClientlib 建立 HTTPS 连接,但问题是,由于证书未由 Android 受信任证书集中列出的公认证书颁发机构 (CA) 签署,如VerisignGlobalSIgn等,我不断得到javax.net.ssl.SSLException: Not trusted server certificate

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

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

4

13 回答 13

174

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

  • ALLOW_ALL_HOSTNAME_VERIFIER
  • BROWSER_COMPATIBLE_HOSTNAME_VERIFIER
  • STRICT_HOSTNAME_VERIFIER

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

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

然后你需要创建一个DefaultHttpClientwith SingleClientConnManager。同样在下面的代码中,您可以看到默认情况下也会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);
于 2010-10-11T08:02:28.080 回答
130

需要以下主要步骤来实现来自不被 android 平台信任的证书颁发机构的安全连接。

根据许多用户的要求,我在此处反映了我的博客文章中最重要的部分:

  1. 获取所有必需的证书(根证书和任何中间 CA)
  2. 使用 keytool 和BouncyCastle提供程序创建密钥库并导入证书
  3. 在您的 android 应用程序中加载密钥库并将其用于安全连接(我建议使用Apache HttpClient而不是标准java.net.ssl.HttpsURLConnection(更容易理解,性能更高)

获取证书

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

创建密钥库

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

现在将获得的证书(不要导入端点证书)导入到 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 连接:

import org.apache.http.*

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

而已 ;)

于 2010-10-22T15:16:37.283 回答
19

如果您在设备上没有服务器上的自定义/自签名证书,则可以使用以下类加载它并在 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;
    }

}

关键点:

  1. Certificate对象是从.crt文件中生成的。
  2. 创建一个默认值KeyStore
  3. keyStore.setCertificateEntry("ca", cert)正在将证书添加到别名“ca”下的密钥库中。您修改代码以添加更多证书(中间 CA 等)。
  4. 主要目标是生成一个SSLSocketFactory可以被HTTPClientor使用的HttpsURLConnection
  5. SSLSocketFactory可以进一步配置,例如跳过主机名验证等。

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

于 2014-05-16T14:23:10.603 回答
13

I was frustrated trying to connect my Android App to my RESTful service using https. Also I was a bit annoyed about all the answers that suggested to disable certificate checking altogether. If you do so, whats the point of https?

After googled about the topic for a while, I finally found this solution where external jars are not needed, just Android APIs. Thanks to Andrew Smith, who posted it on July, 2014

 /**
 * 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;
    }
}

It worked nice for my mockup App.

于 2016-01-28T11:14:38.433 回答
8

Google 建议将Android Volley 用于 HTTP/HTTPS 连接,因为它HttpClient已被弃用。所以,你知道正确的选择:)。

而且,永远不要使用 NUKE SSL 证书(永远不要!!!)。

核对 SSL 证书,完全违背了 SSL 促进安全的目的。如果您打算轰炸所有附带的 SSL 证书,那么使用 SSL 是没有意义的。TrustManager更好的解决方案是在您的 App + 上创建自定义,使用 Android Volley 进行 HTTP/HTTPS 连接。

这是我创建的一个要点,带有一个基本的 LoginApp,执行 HTTPS 连接,使用服务器端的自签名证书,在应用程序上接受。

这里还有另一个Gist可能会有所帮助,用于创建自签名 SSL 证书以在您的服务器上进行设置以及在您的应用程序上使用该证书。非常重要:您必须将上述脚本生成的 .crt 文件复制到 Android 项目的“原始”目录中。

于 2016-10-18T15:42:43.140 回答
6

最佳答案对我不起作用。经过一番调查,我找到了“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();

请注意,TustManager 的这个空实现只是一个示例,在生产环境中使用它会导致严重的安全威胁!

于 2014-08-15T17:02:15.493 回答
4

以下是向 KeyStore 添加其他证书以避免此问题的方法:Trusting all Certificates using HttpClient over HTTPS

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

于 2011-06-16T21:32:51.520 回答
4

创建 SSL 证书的最简单方法

打开 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...)

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

于 2017-07-27T07:31:56.387 回答
2

2020 年 1 月 19 日自签名证书问题修复:

要播放视频、图像、为任何自签名证书调用 web 服务或连接到任何不安全的 url,只需在执行任何操作之前调用此方法,它将解决您关于证书问题的问题:

科特林代码

  private fun disableSSLCertificateChecking() {
        val hostnameVerifier = object: HostnameVerifier {
            override fun verify(s:String, sslSession: SSLSession):Boolean {
                return true
            }
        }
        val trustAllCerts = arrayOf<TrustManager>(object: X509TrustManager {
            override fun getAcceptedIssuers(): Array<X509Certificate> {
                TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
            }

            //val acceptedIssuers:Array<X509Certificate> = null
            @Throws(CertificateException::class)
            override fun checkClientTrusted(arg0:Array<X509Certificate>, arg1:String) {// Not implemented
            }
            @Throws(CertificateException::class)
            override fun checkServerTrusted(arg0:Array<X509Certificate>, arg1:String) {// Not implemented
            }
        })
        try
        {
            val sc = SSLContext.getInstance("TLS")
            sc.init(null, trustAllCerts, java.security.SecureRandom())
            HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory())
            HttpsURLConnection.setDefaultHostnameVerifier(hostnameVerifier)
        }
        catch (e: KeyManagementException) {
            e.printStackTrace()
        }
        catch (e: NoSuchAlgorithmException) {
            e.printStackTrace()
        }
    }
于 2020-01-19T12:29:13.517 回答
2

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

您可以通过提供资产目录中的文件名来简单地加载任何证书。

用法:

OkHttpClient client = new OkHttpClient();
SSLContext sslContext = SslUtils.getSslContextForCertificateFile(context, "BPClass2RootCA-sha2.cer");
client.setSslSocketFactory(sslContext.getSocketFactory());
于 2016-03-31T10:22:01.140 回答
2

这些修复都不适用于面向 SDK 16 版本 4.1.2 的开发平台,因此我找到了解决方法。

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

最近 page.php 被移动到“ https://www.secure-example.com/page.php ”,我不断收到“javax.net.ssl.SSLException:不可信的服务器证书”。

不是只接受一个页面的所有证书,而是从本指南开始,我解决了我编写自己的 page.php 发布在“ http://www.example.com/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;
}

?>
于 2017-02-18T00:11:40.833 回答
0

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

如何忽略 Apache HttpClient 4.0 中的 SSL 证书错误

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

于 2011-07-05T11:47:55.317 回答
0

这是由于 A、android 2.x 中缺少 SNI(服务器名称识别)支持而导致的问题。我在这个问题上苦苦挣扎了一周,直到遇到以下问题,它不仅提供了问题的良好背景,而且提供了一个没有任何安全漏洞的有效解决方案。

Android 2.3 中的“无对等证书”错误,但 4 中没有

于 2012-04-01T10:20:57.160 回答