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

Mor*_*ten 149 ssl https android ca httpclient

我正在尝试使用HttpClientlib 进行HTTPS连接,但问题在于,由于证书未由Verisign,GlobalSIgn等公认的证书颁发机构(CA)签署,并列在Android受信任证书集上,我一直在javax.net.ssl.SSLException: Not trusted server certificate.

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

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

Nik*_*vin 171

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

  • ALLOW_ALL_HOSTNAME_VERIFIER
  • BROWSER_COMPATIBLE_HOSTNAME_VERIFIER
  • STRICT_HOSTNAME_VERIFIER

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

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

然后,你需要创建一个DefaultHttpClientSingleClientConnManager.同样在下面的代码中,您可以看到默认情况下也会使用我们的flag(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);
Run Code Online (Sandbox Code Playgroud)

  • 你能解释一下这段代码是如何完全禁用证书验证的吗?我不熟悉android的ssl API,但是一眼就看出这对于主动攻击者来说似乎是完全不安全的. (9认同)
  • 不幸的是,我无法使这段代码工作,我仍然得到"不值得信任的服务器证书".是否有任何额外的权限我必须设置才能使其工作? (5认同)
  • 我正在使用`org.apache.http.conn.ssl.SSLSocketFactory`为什么我要使用`javax.net.ssl.HttpsURLConnection` ?? (3认同)
  • 我建议使用ThreadSafeClientConnManager而不是SingleClientConnManager (3认同)

sax*_*xos 121

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

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

  1. 获取所有必需的证书(root和任何中间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
Run Code Online (Sandbox Code Playgroud)

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

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
Run Code Online (Sandbox Code Playgroud)

应该输出整个链:

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
Run Code Online (Sandbox Code Playgroud)

现在,您可以将密钥库复制为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);
      }
  }
}
Run Code Online (Sandbox Code Playgroud)

我们已经创建了自定义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();
Run Code Online (Sandbox Code Playgroud)

而已 ;)

  • 这仅适用于在发送应用程序之前获取证书.并不真正帮助用户接受他们自己的证书.为您的应用程序 (6认同)

S.D*_*.D. 16

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

}
Run Code Online (Sandbox Code Playgroud)

关键点:

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

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


小智 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();
Run Code Online (Sandbox Code Playgroud)

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


小智 6

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

经过一段时间的谷歌搜索后,我终于找到了这个解决方案,不需要外部罐子,只需要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;
    }
}
Run Code Online (Sandbox Code Playgroud)

它适用于我的模型App.


iva*_*ncz 6

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

而且,从来没有NUKE SSL证书(永远不要!!!).

核对SSL证书,完全违背SSL的目的,即提升安全性.如果你计划轰炸所有SSL证书,那就没有使用SSL的意识了.更好的解决方案是,不使用SSL或更好的解决方案,将TrustManager使用Android Volley为HTTP + HTTPS连接在App +上创建自定义.

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

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


归档时间:

查看次数:

278899 次

最近记录:

7 年,8 月 前