安卓上使用Apache HttpClient时出现CertPathValidatorException(IssuerName != SubjectName)错误

33
我正在为访问一些战网(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

我已比较了command生成的证书(http://vipsaran.webs.com/openssl_output.txt)和我从Firefox导出的证书(http://vipsaran.webs.com/Firefox_output.zip),它们是相同的。

按照这篇博客的建议,我设置了上面的代码,并将(根和中间)证书导入到一个用于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"

顺便说一下,我也尝试过不加 -keyalg "RSA" -sigalg "SHA1withRSA" 参数的 keytool -import 命令,但是结果没有变化。

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

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 这确实是一个有趣的问题,但我有点担心它的合法性。 - Terrance
7个回答

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

通过结合以下内容的见解:

我成功地使用以下类实现了与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();
          }
        }                       
        }
    }      
}

1
你做到了! :) 我一直缺少的是证书链的排序。感谢您提供的解决方案 - 简单而有效! - Saran
假设我想在自己的Web视图中执行https身份验证,我可以遵循这种方法吗? - Alok Kulkarni
7
我遇到了一个错误:"无法找到证书路径的信任锚点"。 - Felipe
我遇到了这个异常:“javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.” - Manish
太棒了,非常感谢!它真的有助于减轻我的工作负担。 - MohanRaj S
也许你可以帮我解决我的SSL问题https://stackoverflow.com/questions/47554129/https-volley-invalid-header-issue 预先感谢您。 - Sirop4ik

17

当我查看"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个证书的主题匹配。最后一个证书的颁发者应该是自签名的(主题==颁发者),并且在技术上不包含在内。
正确的链条应该按照以下顺序排序:
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         }

为了在您自己的应用程序中处理此问题,您需要创建自己的javax.net.ssl.SSLSocketFactory,该工厂是由初始化了X509TrustManager的SSLContext创建的,该X509TrustManager在调用默认的TrustManagerFactory提供的TrustManager之前重新排序证书链。
我最近没有查看过Apache HTTP Client代码,以了解如何将您的自定义javax.net.ssl.SSLSocketFactory提供给它们的SSLSocketFactory包装器,但这应该是可能的(或者只需不使用Apache HTTP Client并使用new URL(“https://..”).openConnection(),允许您在HttpsURLConnection上指定自定义javax.net.ssl.SSLSocketFactory。
最后,请注意,您只需要将自签名根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文档中所述:http://bit.ly/9HefD6)在Android中无法工作(因为它有自己的SSLSocketFactory实现,只接受KeyStore作为构造函数的参数)。 - Saran
不知道这是不是要求过高,但考虑到您已经深入了证书链,可以尝试创建自己的链(来自https://eu.battle.net),并在此测试活动中使用:http://vipsaran.webs.com/TestTrusted.java。 - Saran
关于HttpsURLConnection路由的问题。嗯,连接已经成功(因为我使用了自己的HostnameVerifier),但是在请求转发方面遇到了困难,我希望能够通过apache客户端来使其工作,因为这条路线似乎更少手动操作(而且之前它也起作用)。 - Saran
这个问题让我非常沮丧,以至于我真的开始考虑为解决方案提供货币奖励>< 哎呀! - Saran
我来晚了,但是 bdc,你今天救了我的命。谢谢! - Sid
也许你可以帮我解决我的SSL问题 https://stackoverflow.com/questions/47554129/https-volley-invalid-header-issue 非常感谢。 - Sirop4ik

4

我猜你的问题现在已经解决了,但是我也遇到了同样的问题,并且花费了一些时间才找到正确的解决方案。也许它可以帮助别人。

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

所以我使用

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

因此,我创建了两个密钥库。
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 Stores。在signature_truststore.bks中,我导入了根证书,在signature_certstore.bks中,您需要导入一个或多个中间证书。

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


感谢您的努力,但对我来说,这个过程也会产生“签名未经验证”的异常。 如果您感到好奇,可以尝试连接到此网址(https://eu.battle.net/login/en/login.xml)(只需使用我附加的证书或通过 openssl s_client -connect eu.battle.net:443 -showcerts 检索它们)。 - Saran
也许你可以帮我解决我的SSL问题 https://stackoverflow.com/questions/47554129/https-volley-invalid-header-issue 非常感谢。 - Sirop4ik

0

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

 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类的源代码 :) 你能附上它吗? - Saran
谢谢。EasyX509TrustManager类怎么样? :) 我猜,来自同一软件包(那个主干)的那个应该能胜任工作 ;) - Saran
不起作用,恐怕是...java.io.IOException: SSL握手失败:系统调用期间的I/O错误,管道中断 at org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl.nativeconnect(Native Method) at org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:316) at org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl$SSLInputStream.<init>(OpenSSLSocketImpl.java:520) at org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl.getInputStream(OpenSSLSocketImpl.java:461) - Saran
我现在一点主意也没有了 :( - Amir Raminfar
也许你可以帮我解决我的SSL问题 https://stackoverflow.com/questions/47554129/https-volley-invalid-header-issue 先行致谢 - Sirop4ik

0

我终于解决了我的“IssuerName与SubjectName不匹配”的异常。我已经多次按照Antoine的博客和这里所描述的方法进行尝试,现在终于成功了:

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

2)如果只使用1)中的根CA或同时使用根CA和中间CA,则会出现不匹配的异常,因为Android仅支持有限数量的受信任根CA,而GeoTrust Global CA不在列表中;

3)在www.geotrust.com的支持页面上,有一个名为GeoTrust Cross Root CA的页面,只需下载它,将其保存为crossroot.pem之类的名称,并使用此命令生成密钥库:

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的博客第二步有一个链接可以下载BouncyCastleProvider;

4) 将密钥库文件添加到Android项目中,它就能正常工作了——这是有道理的,因为现在Android找到了一个受信任的根Equifax Secure Certificate Authority(请参见上面的列表1),其SubjectName GeoTrust Global CA与我们网站的根IssuerName匹配。

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

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

这个问题的难点在于,如果您的根证书颁发机构不在Android的受信任列表中,您将不得不从为您签发证书的公司那里获取它——要求他们提供一个交叉根证书,其中根证书颁发机构是Android的受信任根证书之一。

谢谢您的建议,但是如果我只导入Cross Root CA,我会得到“签名未经验证”的异常。 跟随您的指引,我发现我已经有了CrossRootCA - 它是我的原始附件/链接中的thawtePrimaryRootCA.crt文件 - 当我将其与Thawte网站上的Cross Root CA进行比较时链接。 还是非常感谢您的建议。 - Saran
也许你可以帮我解决我的SSL问题 https://stackoverflow.com/questions/47554129/https-volley-invalid-header-issue 非常感谢。 - Sirop4ik

0

谢谢回复。是的,我知道那个博客(我在我的帖子中也提到了:“通过遵循这个博客上的建议,我已经设置好了...”)。不幸的是,作者也没有成功地连接 :( 我在那个博客上的评论是用“Saran”这个名字发表的。只需尝试使用他或任何其他代码来打开此页面,使用Android的Apache HttpClient:https://eu.battle.net/login/en/login.xml?ref=http%3A%2F%2Feu.wowarmory.com%2Fvault%2Fcharacter-select.xml&app=armory&locale=en_US - Saran

0
顺便说一下,我是上面提到的博客的作者;)我在这里尝试回答你的问题。
我查看了你从Firefox和OpenSSL中获取的证书输出,并发现了一些有趣的东西。
请查看您的OpenSSL输出中的根CA证书(索引1)。发行者名称为:Thawte Premium Server CA,主题名称为:thawte Primary Root CA。主题和发行者名称不同。因此,此证书不被视为根CA,因为它是由另一个实例颁发的。因此,BouncyCastle提供程序将此证书视为根CA,但它会抱怨因为发行者和主题不同。
我不知道您如何获得“错误”的根CA证书。当我查看Firefox中的根CA证书时,主题和发行者是相同的,正如应该的那样。
尝试获取正确的根CA并再次尝试。
希望这可以帮助你。问候和祝好运;)

尝试了,但没有成功 :( 无论如何还是谢谢 :) - Saran

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接