【问题标题】:Apache HttpClient on Android producing CertPathValidatorException (IssuerName != SubjectName)Android 上的 Apache HttpClient 产生 CertPathValidatorException (IssuerName != SubjectName)
【发布时间】:2011-05-06 03:10:23
【问题描述】:

我正在开发一个用于访问某些战网 (https://eu.battle.net) 帐户数据(用于魔兽世界)的 Android 应用程序,并且我正在使用 org.apache.http.client.HttpClient 来执行此操作。

这是我正在使用的代码:

 public static final String USER_AGENT = "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2.8) Gecko/20100722 Firefox/3.6.8 (.NET CLR 3.5.30729)";

  public static class MyHttpClient extends DefaultHttpClient {

    final Context context;

    public MyHttpClient(Context context) {
      super();
      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.battlenetkeystore);
        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);
      }
    }
  }

  private static void maybeCreateHttpClient(Context context) {
    if (mHttpClient == null) {
      mHttpClient = new MyHttpClient(context);

      final HttpParams params = mHttpClient.getParams();
      HttpConnectionParams.setConnectionTimeout(params, REGISTRATION_TIMEOUT);
      HttpConnectionParams.setSoTimeout(params, REGISTRATION_TIMEOUT);
      ConnManagerParams.setTimeout(params, REGISTRATION_TIMEOUT);
      Log.d(TAG, LEAVE + "maybeCreateHttpClient()");
    }
  }

public static boolean authenticate(String username, String password, Handler handler,
      final Context context) {

    final HttpResponse resp;

    final ArrayList<NameValuePair> params = new ArrayList<NameValuePair>();
    params.add(new BasicNameValuePair(PARAM_USERNAME, username));
    params.add(new BasicNameValuePair(PARAM_PASSWORD, password));

    HttpEntity entity = null;
    try {
      entity = new UrlEncodedFormEntity(params);
    } catch (final UnsupportedEncodingException e) {
      // this should never happen.
      throw new AssertionError(e);
    }

    final HttpPost post = new HttpPost(THE_URL);
    post.addHeader(entity.getContentType());
    post.addHeader("User-Agent", USER_AGENT);
    post.setEntity(entity);

    maybeCreateHttpClient(context);

    if (mHttpClient == null) {
      return false;
    }

    try {
      resp = mHttpClient.execute(post);
    } catch (final IOException e) {
      Log.e(TAG, "IOException while authenticating", e);
      return false;
    } finally {
    }
}

通过 OpenSSL 检索密钥库,如下所示:

openssl s_client -connect eu.battle.net:443 -showcerts

我将命令生成的证书 (http://vipsaran.webs.com/openssl_output.txt) 与我从 Firefox 导出的证书 (http://vipsaran.webs.com/Firefox_output.zip) 进行了比较,它们是相同的。

根据this blog 的建议,我设置了上述代码并将(根和中间)证书导入到用于 HttpClient 的密钥库 (battlenetkeystore.bks)。

这是我用于将证书导入密钥库的命令:

keytool -importcert -v -file ~/lib/ThawteSSLCA.crt -alias thawtesslca -keystore ~/lib/battlenetkeystore.bks -provider org.bouncycastle.jce.provider.BouncyCastleProvider -providerpath ~/lib/bcprov-jdk16-145.jar -storetype BKS -storepass mysecret -keypass mysecret -keyalg "RSA" -sigalg "SHA1withRSA"
keytool -importcert -v -file ~/lib/thawtePrimaryRootCA.crt -alias thawteprimaryrootca -keystore ~/lib/battlenetkeystore.bks -provider org.bouncycastle.jce.provider.BouncyCastleProvider -providerpath ~/lib/bcprov-jdk16-145.jar -storetype BKS -storepass mysecret -keypass mysecret -keyalg "RSA" -sigalg "SHA1withRSA"

顺便说一句。我也试过keytool -import 没有-keyalg "RSA" -sigalg "SHA1withRSA",但没有任何变化。

问题是我收到了这个错误:

javax.net.ssl.SSLException: Not trusted server certificate
    at org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:371)
    at org.apache.http.conn.ssl.AbstractVerifier.verify(AbstractVerifier.java:92)
    at org.apache.http.conn.ssl.SSLSocketFactory.createSocket(SSLSocketFactory.java:381)
    at org.apache.http.impl.conn.DefaultClientConnectionOperator.openConnection(DefaultClientConnectionOperator.java:164)
    at org.apache.http.impl.conn.AbstractPoolEntry.open(AbstractPoolEntry.java:164)
    at org.apache.http.impl.conn.AbstractPooledConnAdapter.open(AbstractPooledConnAdapter.java:119)
    at org.apache.http.impl.client.DefaultRequestDirector.execute(DefaultRequestDirector.java:348)
    at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:555)
    at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:487)
    at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:465)
    at org.homedns.saran.android.wowcalendarsync.network.NetworkUtilities.authenticateWithPass(NetworkUtilities.java:346)
    at org.homedns.saran.android.wowcalendarsync.network.NetworkUtilities$1.run(NetworkUtilities.java:166)
    at org.homedns.saran.android.wowcalendarsync.network.NetworkUtilities$5.run(NetworkUtilities.java:278)
Caused by: java.security.cert.CertificateException: java.security.cert.CertPathValidatorException: IssuerName(CN=thawte Primary Root CA, OU="(c) 2006 thawte, Inc. - For authorized use only", OU=Certification Services Division, O="thawte, Inc.", C=US) does not match SubjectName(CN=Thawte SSL CA, O="Thawte, Inc.", C=US) of signing certificate
    at org.apache.harmony.xnet.provider.jsse.TrustManagerImpl.checkServerTrusted(TrustManagerImpl.java:168)
    at org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:366)
    ... 12 more
Caused by: java.security.cert.CertPathValidatorException: IssuerName(CN=thawte Primary Root CA, OU="(c) 2006 thawte, Inc. - For authorized use only", OU=Certification Services Division, O="thawte, Inc.", C=US) does not match SubjectName(CN=Thawte SSL CA, O="Thawte, Inc.", C=US) of signing certificate
    at org.bouncycastle.jce.provider.PKIXCertPathValidatorSpi.engineValidate(PKIXCertPathValidatorSpi.java:373)
    at java.security.cert.CertPathValidator.validate(CertPathValidator.java:202)
    at org.apache.harmony.xnet.provider.jsse.TrustManagerImpl.checkServerTrusted(TrustManagerImpl.java:164)
    ... 13 more

我想不出办法来解决它。 尝试以不同的顺序将证书导入密钥库。但没有任何效果。

请帮助(请关注基于 Android 的 Apache HttpClient 的解决方案仅限)。

【问题讨论】:

  • +1 绝对是一个有趣的问题,但我有点想知道它的合法性。

标签: android ssl apache-commons-httpclient world-of-warcraft


【解决方案1】:

我希望您现在已经有了自己的解决方案,但如果没有:

结合来自

的见解

我设法通过以下类实现了与https://eu.battle.net/login/en/login.xml 的安全连接。请注意,由于根 CA 受 android 信任,因此无需构建密钥库 - 问题只是证书以错误的顺序返回。

(免责声明:虽然没有花时间清理代码。)

EasyX509TrustManager:

package com.trustit.trustme;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Date;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;

public class EasyX509TrustManager implements X509TrustManager 
{  
    private X509TrustManager standardTrustManager = null;  

    /** 
     * Constructor for EasyX509TrustManager. 
     */  
    public EasyX509TrustManager(KeyStore keystore) throws NoSuchAlgorithmException, KeyStoreException 
    {  
      super();  
      TrustManagerFactory factory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());  
      factory.init(keystore);  
      TrustManager[] trustmanagers = factory.getTrustManagers();  
      if (trustmanagers.length == 0) 
      {  
        throw new NoSuchAlgorithmException("no trust manager found");  
      }  
      this.standardTrustManager = (X509TrustManager) trustmanagers[0];  
    }  

    /** 
     * @see javax.net.ssl.X509TrustManager#checkClientTrusted(X509Certificate[],String authType) 
     */  
    public void checkClientTrusted(X509Certificate[] certificates, String authType) throws CertificateException 
    {  
      standardTrustManager.checkClientTrusted(certificates, authType);  
    }  

    /** 
     * @see javax.net.ssl.X509TrustManager#checkServerTrusted(X509Certificate[],String authType) 
     */  
    public void checkServerTrusted(X509Certificate[] certificates, String authType) throws CertificateException 
    {  
    // Clean up the certificates chain and build a new one.
        // Theoretically, we shouldn't have to do this, but various web servers
        // in practice are mis-configured to have out-of-order certificates or
        // expired self-issued root certificate.
        int chainLength = certificates.length;
        if (certificates.length > 1) 
        {
          // 1. we clean the received certificates chain.
          // We start from the end-entity certificate, tracing down by matching
          // the "issuer" field and "subject" field until we can't continue.
          // This helps when the certificates are out of order or
          // some certificates are not related to the site.
          int currIndex;
          for (currIndex = 0; currIndex < certificates.length; ++currIndex) 
          {
            boolean foundNext = false;
            for (int nextIndex = currIndex + 1;
                           nextIndex < certificates.length;
                           ++nextIndex) 
            {
              if (certificates[currIndex].getIssuerDN().equals(
                            certificates[nextIndex].getSubjectDN())) 
              {
                foundNext = true;
                // Exchange certificates so that 0 through currIndex + 1 are in proper order
                if (nextIndex != currIndex + 1) 
                {
                  X509Certificate tempCertificate = certificates[nextIndex];
                  certificates[nextIndex] = certificates[currIndex + 1];
                  certificates[currIndex + 1] = tempCertificate;
                }
                break;
            }
            }
            if (!foundNext) break;
      }

          // 2. we exam if the last traced certificate is self issued and it is expired.
          // If so, we drop it and pass the rest to checkServerTrusted(), hoping we might
          // have a similar but unexpired trusted root.
          chainLength = currIndex + 1;
          X509Certificate lastCertificate = certificates[chainLength - 1];
          Date now = new Date();
          if (lastCertificate.getSubjectDN().equals(lastCertificate.getIssuerDN())
                  && now.after(lastCertificate.getNotAfter())) 
          {
            --chainLength;
          }
      } 

    standardTrustManager.checkServerTrusted(certificates, authType);    
    }  

    /** 
     * @see javax.net.ssl.X509TrustManager#getAcceptedIssuers() 
     */  
    public X509Certificate[] getAcceptedIssuers() 
    {  
      return this.standardTrustManager.getAcceptedIssuers();  
    }    
}  

EasySSLSocketFactory

package com.trustit.trustme;

import java.io.IOException;  
import java.net.InetAddress;  
import java.net.InetSocketAddress;  
import java.net.Socket;  
import java.net.UnknownHostException;  

import javax.net.ssl.SSLContext;  
import javax.net.ssl.SSLSocket;  
import javax.net.ssl.TrustManager;  

import org.apache.http.conn.ConnectTimeoutException;  
import org.apache.http.conn.scheme.LayeredSocketFactory;  
import org.apache.http.conn.scheme.SocketFactory;  
import org.apache.http.params.HttpConnectionParams;  
import org.apache.http.params.HttpParams;  

public class EasySSLSocketFactory implements SocketFactory, LayeredSocketFactory 
{  
    private SSLContext sslcontext = null;  

    private static SSLContext createEasySSLContext() throws IOException 
    {  
      try
      {  
        SSLContext context = SSLContext.getInstance("TLS");  
        context.init(null, new TrustManager[] { new EasyX509TrustManager(null) }, null);  
        return context;  
      }
      catch (Exception e) 
      {  
        throw new IOException(e.getMessage());  
      }  
    }  

    private SSLContext getSSLContext() throws IOException 
    {  
      if (this.sslcontext == null) 
      {  
        this.sslcontext = createEasySSLContext();  
      }  
      return this.sslcontext;  
    }  

    /** 
     * @see org.apache.http.conn.scheme.SocketFactory#connectSocket(java.net.Socket, java.lang.String, int, 
     *      java.net.InetAddress, int, org.apache.http.params.HttpParams) 
     */  
    public Socket connectSocket(Socket sock,
                                    String host,
                                    int port, 
                                    InetAddress localAddress,
                                    int localPort,
                                    HttpParams params) 

                throws IOException, UnknownHostException, ConnectTimeoutException 
    {  
      int connTimeout = HttpConnectionParams.getConnectionTimeout(params);  
      int soTimeout = HttpConnectionParams.getSoTimeout(params);  
      InetSocketAddress remoteAddress = new InetSocketAddress(host, port);  
      SSLSocket sslsock = (SSLSocket) ((sock != null) ? sock : createSocket());  

      if ((localAddress != null) || (localPort > 0)) 
      {  
        // we need to bind explicitly  
        if (localPort < 0) 
        {  
          localPort = 0; // indicates "any"  
        }  
        InetSocketAddress isa = new InetSocketAddress(localAddress, localPort);  
        sslsock.bind(isa);  
      }  

      sslsock.connect(remoteAddress, connTimeout);  
      sslsock.setSoTimeout(soTimeout);  
      return sslsock;    
    }  

    /** 
     * @see org.apache.http.conn.scheme.SocketFactory#createSocket() 
     */  
    public Socket createSocket() throws IOException {  
        return getSSLContext().getSocketFactory().createSocket();  
    }  

    /** 
     * @see org.apache.http.conn.scheme.SocketFactory#isSecure(java.net.Socket) 
     */  
    public boolean isSecure(Socket socket) throws IllegalArgumentException {  
        return true;  
    }  

    /** 
     * @see org.apache.http.conn.scheme.LayeredSocketFactory#createSocket(java.net.Socket, java.lang.String, int, 
     *      boolean) 
     */  
    public Socket createSocket(Socket socket,
                                   String host, 
                                   int port,
                                   boolean autoClose) throws IOException,  
                                                             UnknownHostException 
    {  
      return getSSLContext().getSocketFactory().createSocket(socket, host, port, autoClose);  
    }  

    // -------------------------------------------------------------------  
    // javadoc in org.apache.http.conn.scheme.SocketFactory says :  
    // Both Object.equals() and Object.hashCode() must be overridden  
    // for the correct operation of some connection managers  
    // -------------------------------------------------------------------  

    public boolean equals(Object obj) {  
        return ((obj != null) && obj.getClass().equals(EasySSLSocketFactory.class));  
    }  

    public int hashCode() {  
        return EasySSLSocketFactory.class.hashCode();  
    }  
}

我的HttpClient

package com.trustit.trustme;

import org.apache.http.conn.ClientConnectionManager;
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.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.SingleClientConnManager;
import org.apache.http.params.HttpParams;

import android.content.Context;

public class MyHttpClient extends DefaultHttpClient 
{    
  final Context context;

  public MyHttpClient(HttpParams hparms, Context context)
  {
    super(hparms);
    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", new EasySSLSocketFactory(), 443));

    //http://blog.synyx.de/2010/06/android-and-self-signed-ssl-certificates/
    return new SingleClientConnManager(getParams(), registry);
  }
}

TrustMe(活动)

package com.trustit.trustme;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;

import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;

public class TrustMe extends Activity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        TextView tv = (TextView)findViewById(R.id.tv1);


        HttpParams httpParameters = new BasicHttpParams();
        // Set the timeout in milliseconds until a connection is established.
        int timeoutConnection = 10000;
        HttpConnectionParams.setConnectionTimeout(httpParameters, timeoutConnection);
        // Set the default socket timeout (SO_TIMEOUT) 
        // in milliseconds which is the timeout for waiting for data.
        int timeoutSocket = 10000;
        HttpConnectionParams.setSoTimeout(httpParameters, timeoutSocket);

        // Instantiate the custom HttpClient
        HttpClient client = new MyHttpClient(httpParameters,
                                             getApplicationContext());
      HttpGet request = new HttpGet("https://eu.battle.net/login/en/login.xml"); 

        BufferedReader in = null;
        try 
        {
        HttpResponse response = client.execute(request);
        in = new BufferedReader(new InputStreamReader(response.getEntity().getContent()));

        StringBuffer sb = new StringBuffer("");
        String line = "";
        String NL = System.getProperty("line.separator");
        while ((line = in.readLine()) != null)
        {
          sb.append(line + NL);
        }
        in.close();
        String page = sb.toString();
        //System.out.println(page);

        tv.setText(page);
            }
        catch (ClientProtocolException e) 
            {
                e.printStackTrace();
            }
        catch (IOException e) 
        {
                e.printStackTrace();
            }
        finally
        {
        if (in != null) 
        {
          try
          {
            in.close();
          }
          catch (IOException e) 
          {
            e.printStackTrace();
          }
        }                       
        }
    }      
}

【讨论】:

  • 你做到了! :) 我一直缺少的是证书链的排序。感谢您的解决方案——简单而有效!
  • 假设我想在自己的 webview 中做 https auth,我可以按照这个方法吗?
  • 我收到一个错误:“找不到证书路径的信任锚。”
  • 我收到此异常“javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certificate path not found.”
  • 真棒答案,真的很感谢它有助于减少我的工作量。
【解决方案2】:

查看“openssl s_client -connect eu.battle.net:443”,我看到以下证书链:

Certificate chain
 0 s:/C=US/ST=California/L=Irvine/O=Blizzard Entertainment, Inc./CN=*.battle.net
   i:/C=US/O=Thawte, Inc./CN=Thawte SSL CA
 1 s:/C=US/O=thawte, Inc./OU=Certification Services Division/OU=(c) 2006 thawte, Inc. - For authorized use only/CN=thawte Primary Root CA
   i:/C=ZA/ST=Western Cape/L=Cape Town/O=Thawte Consulting cc/OU=Certification Services Division/CN=Thawte Premium Server CA/emailAddress=premium-server@thawte.com
 2 s:/C=US/O=Thawte, Inc./CN=Thawte SSL CA
   i:/C=US/O=thawte, Inc./OU=Certification Services Division/OU=(c) 2006 thawte, Inc. - For authorized use only/CN=thawte Primary Root CA

请注意,这是不正常的。链中证书“n”的颁发者应与证书“n+1”的主题匹配。最后一个证书的颁发者应该是自签名的(subject==issuer)并且在技术上不包括在内。

正确的链应该是这样排列的:

Certificate chain
 0 s:/C=US/ST=California/L=Irvine/O=Blizzard Entertainment, Inc./CN=*.battle.net
   i:/C=US/O=Thawte, Inc./CN=Thawte SSL CA
 1 s:/C=US/O=Thawte, Inc./CN=Thawte SSL CA
   i:/C=US/O=thawte, Inc./OU=Certification Services Division/OU=(c) 2006 thawte, Inc. - For authorized use only/CN=thawte Primary Root CA
 2 s:/C=US/O=thawte, Inc./OU=Certification Services Division/OU=(c) 2006 thawte, Inc. - For authorized use only/CN=thawte Primary Root CA
   i:/C=ZA/ST=Western Cape/L=Cape Town/O=Thawte Consulting cc/OU=Certification Services Division/CN=Thawte Premium Server CA/emailAddress=premium-server@thawte.com

Android 浏览器通过让其android.net.http.CertificateChainValidator 代码在传递证书链进行验证之前重新排序证书链来处理乱序链。

 136         // Clean up the certificates chain and build a new one.
 137         // Theoretically, we shouldn't have to do this, but various web servers
 138         // in practice are mis-configured to have out-of-order certificates or
 139         // expired self-issued root certificate.
 140         int chainLength = serverCertificates.length;
 141         if (serverCertificates.length > 1) {
 142           // 1. we clean the received certificates chain.
 143           // We start from the end-entity certificate, tracing down by matching
 144           // the "issuer" field and "subject" field until we can't continue.
 145           // This helps when the certificates are out of order or
 146           // some certificates are not related to the site.
 147           int currIndex;
 148           for (currIndex = 0; currIndex < serverCertificates.length; ++currIndex) {
 149             boolean foundNext = false;
 150             for (int nextIndex = currIndex + 1;
 151                  nextIndex < serverCertificates.length;
 152                  ++nextIndex) {
 153               if (serverCertificates[currIndex].getIssuerDN().equals(
 154                   serverCertificates[nextIndex].getSubjectDN())) {
 155                 foundNext = true;
 156                 // Exchange certificates so that 0 through currIndex + 1 are in proper order
 157                 if (nextIndex != currIndex + 1) {
 158                   X509Certificate tempCertificate = serverCertificates[nextIndex];
 159                   serverCertificates[nextIndex] = serverCertificates[currIndex + 1];
 160                   serverCertificates[currIndex + 1] = tempCertificate;
 161                 }
 162                 break;
 163               }
 164             }
 165             if (!foundNext) break;
 166           }
 167 
 168           // 2. we exam if the last traced certificate is self issued and it is expired.
 169           // If so, we drop it and pass the rest to checkServerTrusted(), hoping we might
 170           // have a similar but unexpired trusted root.
 171           chainLength = currIndex + 1;
 172           X509Certificate lastCertificate = serverCertificates[chainLength - 1];
 173           Date now = new Date();
 174           if (lastCertificate.getSubjectDN().equals(lastCertificate.getIssuerDN())
 175               && now.after(lastCertificate.getNotAfter())) {
 176             --chainLength;
 177           }
 178         }

要在您自己的应用程序中处理此问题,您希望从 SSLContext 创建您自己的 javax.net.ssl.SSLSocketFactory,该 SSLContext 使用 X509TrustManager 在调用默认 TrustManagerFactory 提供的 TrustManager 之前重新排序链。

我最近没有查看 Apache HTTP 客户端代码来了解如何将您的自定义 javax.net.ssl.SSLSocketFactory 提供给他们的 SSLSocketFactory 包装器,但它应该是可能的(或者只是不使用 Apache HTTP 客户端和只需使用 new URL("https://..").openConnection() 即可在 HttpsURLConnection 上指定自定义 javax.net.ssl.SSLSocketFactory。

最后,请注意,您只需要将自签名的根 CA 导入您的密钥库(并且仅当它尚未在系统存储中,但我刚刚检查并且该 CA 不存在于 froyo 中时)。在这种情况下,您想要的 CA 具有主题:

/C=US/O=thawte, Inc./OU=Certification Services Division/OU=(c) 2006 thawte, Inc. - For authorized use only/CN=thawte Primary Root CA

【讨论】:

  • 感谢您的详细分析和提示。覆盖我不会进入的顺序(因为我认为我必须覆盖许多类才能到达那里)。我自己的 SSLSocketFactory 的方式(甚至在 HttpClient 文档中描述:bit.ly/9HefD6)在 Android 中不起作用(因为它有自己的 SSLSocketFactory 实现,它只接受 KeyStore 作为构造函数的参数)。
  • 不知道是不是问得太多,但考虑到你已经深入到证书链,你可以尝试创建你的链(来自eu.battle.net)并在这个测试活动中使用它: vipsaran.webs.com/TestTrusted.java
  • 关于 HttpsURLConnection 路由。好吧,连接工作正常(因为我使用了自己的 HostnameVerifier),但我被请求转发卡住了,想让它与 apache 客户端一起工作,因为该路由似乎不那么手动(而且它以前工作过)。
  • 对这个问题感到非常沮丧,以至于我真的开始考虑为解决方案提供金钱奖励>
  • 我参加这个聚会迟到了,但是 bdc,你今天救了我的命。谢谢!
【解决方案3】:

我想你的问题现在已经解决了,但我也遇到了同样的问题,而且我也花了一些时间寻找正确的解决方案。也许它可以帮助某人。

我也使用了来自 Antoine's Blog 的代码,但我更改了用于 SSLSocketFactory 的构造函数。

所以我用

SSLSocketFactory sf = new SSLSocketFactory(certStore, "some_password", trustStore);

为此我创建了两个 KeyStores

KeyStore trustStore = KeyStore.getInstance("BKS");
KeyStore certStore = KeyStore.getInstance("BKS");
InputStream in = context.getResources().openRawResource(R.raw.signature_certstore);
try {
    certStore.load(in, "some_password".toCharArray());
} finally {
    in.close();
}

in = context.getResources().openRawResource(R.raw.signature_truststore);
try {
    trustStore.load(in, "some_password".toCharArray());
} finally {
    in.close();
}

我使用Portecle 创建了 BKS 商店。在 signature_truststore.bks 我导入了根证书,在 signature_certstore.bks 你必须导入一个或多个中间证书。

其余代码与博客中的完全相同。

【讨论】:

  • 感谢您的努力,但此过程也会产生“签名未验证”。对我来说是个例外。如果您感到好奇,可以尝试连接到this url(只需使用我附加的证书或通过openssl s_client -connect eu.battle.net:443 -showcerts 检索它们)。
  • 也许你能帮我解决我的 ssl 问题 stackoverflow.com/questions/47554129/… 在此先感谢
【解决方案4】:

我没有修复路径的解决方案。但我有一个忽略证书的解决方案。我使用这种方法来忽略开发中的自签名证书。看看有没有帮助。

 protected final static ClientConnectionManager clientConnectionManager;
    protected final static HttpParams params;
    // ......
    SchemeRegistry schemeRegistry = new SchemeRegistry();
    schemeRegistry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
    schemeRegistry.register(new Scheme("https", new EasySSLSocketFactory(), 443));
    params = new BasicHttpParams();
    params.setParameter(ConnManagerPNames.MAX_TOTAL_CONNECTIONS, 1);
    params.setParameter(ConnManagerPNames.MAX_CONNECTIONS_PER_ROUTE, new ConnPerRouteBean(1));
    params.setParameter(HttpProtocolParams.USE_EXPECT_CONTINUE, false);
    HttpProtocolParams.setUserAgent(params, "android-client-v1.0");
    HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
    HttpProtocolParams.setContentCharset(params, "utf8");
    clientConnectionManager = new ThreadSafeClientConnManager(params, schemeRegistry);
    // and later do this
    HttpClient client = new DefaultHttpClient(clientConnectionManager, params);
    HttpGet request = new HttpGet(uri);
    HttpResponse response = client.execute(request);

【讨论】:

  • 好的...但是,我担心缺少一个重要部分——EasySSLSocketFactory 类的源代码 :) 你能附上它吗?
  • 糟糕。其实我之前google了一下,所以忘记了。 code.google.com/p/transdroid/source/browse/trunk/src/org/xmlrpc/…
  • 谢谢。还有 EasyX509TrustManager 类? :) 我想,来自同一个包裹(来自那个主干)的那个应该可以完成这项工作;)
  • 不起作用,我很害怕... java.io.IOException:SSL 握手失败:系统调用期间的 I/O 错误,org.apache.harmony.xnet.provider 的管道损坏org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:316) 中 org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl$SSLInputStream 中的 .jsse.OpenSSLSocketImpl.nativeconnect(Native Method)。 (OpenSSLSocketImpl.java:520) at org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl.getInputStream(OpenSSLSocketImpl.java:461)
  • 那么我的想法很新鲜 :(
【解决方案5】:

这可能会有所帮助:http://blog.antoine.li/index.php/2010/10/android-trusting-ssl-certificates/,您是否拥有来自 CA 的受信任证书(例如 versign 或 Geotrust)?或者您正在使用自签名证书...我遇到了类似的问题,今天解决了...

【讨论】:

  • 感谢您的回复。是的,我知道那个博客(我也在我的帖子中提到过:“按照这个博客上的建议,我已经设置了......”)。不幸的是,作者也没有成功连接:(我在那个博客上的 cmets 名为“Saran”。只需尝试使用他或任何其他代码使用 Android 的 Apache HttpClient 打开此页面:eu.battle.net/login/en/…
【解决方案6】:

顺便说一句,我是上面提到的博客的作者;)我试着在这里回答你的问题。

我查看了您从 firefox 和 openssl 输出的证书,发现了一些有趣的东西。

在您的 openssl 输出中查看根 ca 证书(索引 1)。 发行人名称是:Thawte Premium Server CA 主题名称是:thawte Primary Root CA 主题和发行者名称不同。因此,此证书不被视为根 CA,因为它是由另一个实例颁发的。 因此,bouncycastle 提供者将此证书视为根 CA,但由于问题和主题不同而抱怨。

我不知道您是如何获得“错误的”根 CA 证书的。当我在 Firefox 中查看 Root CA 证书时,主题和颁发者应该是相同的。

尝试获取正确的根 CA,然后重试。

希望这会有所帮助。 问候和好运;)

【讨论】:

  • 试过了。没有成功:(无论如何谢谢:)
【解决方案7】:

我终于解决了同样的“IssuerName 与 SubjectName 不匹配”异常。我已经多次关注 Antoine 的同一个博客以及这里所描述的内容,下面是如何让它最终发挥作用:

1)我们的网站使用GeoTrust的两个证书:中间CA由GeoTrust SSL CA颁发给我们,根CA由GeoTrust Global CA颁发给GeoTrust SSL CA;

2) 如果只使用根 CA 或者 1) 中的根 CA 和中间 CA,我会得到不匹配异常,因为 Android 只支持a limited number of trusted root CA,而 GeoTrust Global CA 不在列表中;

3)在www.geotrust.com的支持页面,有一个页面叫GeoTrust Cross Root CA,直接下载,保存成crossroot.pem这样的名字,使用这个命令生成keystore:

C:\Program Files\Java\jdk1.6.0_24\bin>keytool -importcert -v -trustcacerts -file c:\ssl\crossroot.pem -alias newroot -keystore c:\ssl\crossroot.bks -provider org.bouncycastle.jce.provider.BouncyCastleProvider -providerpath "c:\downloads\bcprov-jdk16-145.jar" -storetype BKS -storepass mysecret

Antonie 博客的第 2 步有一个下载 BouncyCastleProvider 的链接;

4) 将密钥库文件添加到 Android 项目中,它可以工作 - 这很有意义,因为现在 Android 找到了一个受信任的根 Equifax 安全证书颁发机构(参见上面的列表1),其 SubjectName GeoTrust Global CA 与我们网站的根匹配发行人名称。

5) 博客第三步的代码运行良好,为了更完整,我复制了下面的测试代码:

    HttpResponse response = client.execute(get);
    HttpEntity entity = response.getEntity();

    BufferedReader in = new BufferedReader(new InputStreamReader(entity.getContent()));

    String line;
    while ((line = in.readLine()) != null) 
    System.out.println(line);
    in.close();

这个问题的难点在于,如果您的根 CA 的颁发者不在 Android 的受信任列表中,则您必须从为您颁发证书的公司处获取它 - 要求他们为您提供一个跨根 CA将根颁发者作为 Android 的受信任根 CA 之一。

【讨论】:

  • 感谢您的建议,但如果我只导入跨根 CA,则会收到“签名未验证”异常。按照您的指示,当我将它与来自 Thawte 的站点 link 的 Cross Root CA 进行比较时,我发现我已经拥有了 CrossRootCA——它是我原始附加/链接中的 thawtePrimaryRootCA.crt 文件。不过还是谢谢。
  • 也许你能帮我解决我的 ssl 问题 stackoverflow.com/questions/47554129/… 提前谢谢
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-07-23
  • 2023-03-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-05-22
相关资源
最近更新 更多