Trusting all certificates using HttpClient over HTTPS

后端 未结 21 2239
北恋
北恋 2020-11-21 04:50

Recently posted a question regarding the HttpClient over Https (found here). I\'ve made some headway, but I\'ve run into new issues. As with my last problem, I

相关标签:
21条回答
  • 2020-11-21 05:28

    The code above in https://stackoverflow.com/a/6378872/1553004 is correct, except it MUST also call the hostname verifier:

        @Override
    public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException {
        SSLSocket sslSocket = (SSLSocket)sslContext.getSocketFactory().createSocket(socket, host, port, autoClose);
        getHostnameVerifier().verify(host, sslSocket);
        return sslSocket;
    }
    

    I signed up to stackoverflow expressly to add this fix. Heed my warning!

    0 讨论(0)
  • 2020-11-21 05:30

    You basically have four potential solutions to fix a "Not Trusted" exception on Android using httpclient:

    1. Trust all certificates. Don't do this, unless you really know what you're doing.
    2. Create a custom SSLSocketFactory that trusts only your certificate. This works as long as you know exactly which servers you're going to connect to, but as soon as you need to connect to a new server with a different SSL certificate, you'll need to update your app.
    3. Create a keystore file that contains Android's "master list" of certificates, then add your own. If any of those certs expire down the road, you are responsible for updating them in your app. I can't think of a reason to do this.
    4. Create a custom SSLSocketFactory that uses the built-in certificate KeyStore, but falls back on an alternate KeyStore for anything that fails to verify with the default.

    This answer uses solution #4, which seems to me to be the most robust.

    The solution is to use an SSLSocketFactory that can accept multiple KeyStores, allowing you to supply your own KeyStore with your own certificates. This allows you to load additional top-level certificates such as Thawte that might be missing on some Android devices. It also allows you to load your own self-signed certificates as well. It will use the built-in default device certificates first, and fall back on your additional certificates only as necessary.

    First, you'll want to determine which cert you are missing in your KeyStore. Run the following command:

    openssl s_client -connect www.yourserver.com:443
    

    And you'll see output like the following:

    Certificate chain
     0 s:/O=www.yourserver.com/OU=Go to 
       https://www.thawte.com/repository/index.html/OU=Thawte SSL123 
       certificate/OU=Domain Validated/CN=www.yourserver.com
       i:/C=US/O=Thawte, Inc./OU=Domain Validated SSL/CN=Thawte DV SSL CA
     1 s:/C=US/O=Thawte, Inc./OU=Domain Validated SSL/CN=Thawte DV 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
    

    As you can see, our root certificate is from Thawte. Go to your provider's website and find the corresponding certificate. For us, it was here, and you can see that the one we needed was the one Copyright 2006.

    If you're using a self-signed certificate, you didn't need to do the previous step since you already have your signing certificate.

    Then, create a keystore file containing the missing signing certificate. Crazybob has details how to do this on Android, but the idea is to do the following:

    If you don't have it already, download the bouncy castle provider library from: http://www.bouncycastle.org/latest_releases.html. This will go on your classpath below.

    Run a command to extract the certificate from the server and create a pem file. In this case, mycert.pem.

    echo | openssl s_client -connect ${MY_SERVER}:443 2>&1 | \
     sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > mycert.pem
    

    Then run the following commands to create the keystore.

    export CLASSPATH=/path/to/bouncycastle/bcprov-jdk15on-155.jar
    CERTSTORE=res/raw/mystore.bks
    if [ -a $CERTSTORE ]; then
        rm $CERTSTORE || exit 1
    fi
    keytool \
          -import \
          -v \
          -trustcacerts \
          -alias 0 \
          -file <(openssl x509 -in mycert.pem) \
          -keystore $CERTSTORE \
          -storetype BKS \
          -provider org.bouncycastle.jce.provider.BouncyCastleProvider \
          -providerpath /path/to/bouncycastle/bcprov-jdk15on-155.jar \
          -storepass some-password
    

    You'll notice that the above script places the result in res/raw/mystore.bks. Now you have a file that you'll load into your Android app that provides the missing certificate(s).

    To do this, register your SSLSocketFactory for the SSL scheme:

    final SchemeRegistry schemeRegistry = new SchemeRegistry();
    schemeRegistry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
    schemeRegistry.register(new Scheme("https", createAdditionalCertsSSLSocketFactory(), 443));
    
    // and then however you create your connection manager, I use ThreadSafeClientConnManager
    final HttpParams params = new BasicHttpParams();
    ...
    final ThreadSafeClientConnManager cm = new ThreadSafeClientConnManager(params,schemeRegistry);
    

    To create your SSLSocketFactory:

    protected org.apache.http.conn.ssl.SSLSocketFactory createAdditionalCertsSSLSocketFactory() {
        try {
            final KeyStore ks = KeyStore.getInstance("BKS");
    
            // the bks file we generated above
            final InputStream in = context.getResources().openRawResource( R.raw.mystore);  
            try {
                // don't forget to put the password used above in strings.xml/mystore_password
                ks.load(in, context.getString( R.string.mystore_password ).toCharArray());
            } finally {
                in.close();
            }
    
            return new AdditionalKeyStoresSSLSocketFactory(ks);
    
        } catch( Exception e ) {
            throw new RuntimeException(e);
        }
    }
    

    And finally, the AdditionalKeyStoresSSLSocketFactory code, which accepts your new KeyStore and checks if the built-in KeyStore fails to validate an SSL certificate:

    /**
     * Allows you to trust certificates from additional KeyStores in addition to
     * the default KeyStore
     */
    public class AdditionalKeyStoresSSLSocketFactory extends SSLSocketFactory {
        protected SSLContext sslContext = SSLContext.getInstance("TLS");
    
        public AdditionalKeyStoresSSLSocketFactory(KeyStore keyStore) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
            super(null, null, null, null, null, null);
            sslContext.init(null, new TrustManager[]{new AdditionalKeyStoresTrustManager(keyStore)}, null);
        }
    
        @Override
        public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException {
            return sslContext.getSocketFactory().createSocket(socket, host, port, autoClose);
        }
    
        @Override
        public Socket createSocket() throws IOException {
            return sslContext.getSocketFactory().createSocket();
        }
    
    
    
        /**
         * Based on http://download.oracle.com/javase/1.5.0/docs/guide/security/jsse/JSSERefGuide.html#X509TrustManager
         */
        public static class AdditionalKeyStoresTrustManager implements X509TrustManager {
    
            protected ArrayList<X509TrustManager> x509TrustManagers = new ArrayList<X509TrustManager>();
    
    
            protected AdditionalKeyStoresTrustManager(KeyStore... additionalkeyStores) {
                final ArrayList<TrustManagerFactory> factories = new ArrayList<TrustManagerFactory>();
    
                try {
                    // The default Trustmanager with default keystore
                    final TrustManagerFactory original = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
                    original.init((KeyStore) null);
                    factories.add(original);
    
                    for( KeyStore keyStore : additionalkeyStores ) {
                        final TrustManagerFactory additionalCerts = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
                        additionalCerts.init(keyStore);
                        factories.add(additionalCerts);
                    }
    
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
    
    
    
                /*
                 * Iterate over the returned trustmanagers, and hold on
                 * to any that are X509TrustManagers
                 */
                for (TrustManagerFactory tmf : factories)
                    for( TrustManager tm : tmf.getTrustManagers() )
                        if (tm instanceof X509TrustManager)
                            x509TrustManagers.add( (X509TrustManager)tm );
    
    
                if( x509TrustManagers.size()==0 )
                    throw new RuntimeException("Couldn't find any X509TrustManagers");
    
            }
    
            /*
             * Delegate to the default trust manager.
             */
            public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
                final X509TrustManager defaultX509TrustManager = x509TrustManagers.get(0);
                defaultX509TrustManager.checkClientTrusted(chain, authType);
            }
    
            /*
             * Loop over the trustmanagers until we find one that accepts our server
             */
            public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
                for( X509TrustManager tm : x509TrustManagers ) {
                    try {
                        tm.checkServerTrusted(chain,authType);
                        return;
                    } catch( CertificateException e ) {
                        // ignore
                    }
                }
                throw new CertificateException();
            }
    
            public X509Certificate[] getAcceptedIssuers() {
                final ArrayList<X509Certificate> list = new ArrayList<X509Certificate>();
                for( X509TrustManager tm : x509TrustManagers )
                    list.addAll(Arrays.asList(tm.getAcceptedIssuers()));
                return list.toArray(new X509Certificate[list.size()]);
            }
        }
    
    }
    
    0 讨论(0)
  • 2020-11-21 05:31

    You can disable HttpURLConnection SSL checking for testing purposes this way since API 8:

        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        if (conn instanceof HttpsURLConnection) {
            HttpsURLConnection httpsConn = (HttpsURLConnection) conn;
            httpsConn.setSSLSocketFactory(SSLCertificateSocketFactory.getInsecure(0, null));
            httpsConn.setHostnameVerifier(new AllowAllHostnameVerifier());
        }
    
    0 讨论(0)
  • 2020-11-21 05:31

    Just adding -Dtrust_all_cert=true to VM arguments should do. This argument tells java to ignore certificate checks.

    0 讨论(0)
  • 2020-11-21 05:31

    use this class

    public class WCFs
    {
        //  https://192.168.30.8/myservice.svc?wsdl
    private static final String NAMESPACE = "http://tempuri.org/";
    private static final String URL = "192.168.30.8";
    private static final String SERVICE = "/myservice.svc?wsdl";
    private static String SOAP_ACTION = "http://tempuri.org/iWCFserviceMe/";
    
    
    public static Thread myMethod(Runnable rp)
    {
        String METHOD_NAME = "myMethod";
    
        SoapObject request = new SoapObject(NAMESPACE, METHOD_NAME);
    
        request.addProperty("Message", "Https WCF Running...");
        return _call(rp,METHOD_NAME, request);
    }
    
    protected static HandlerThread _call(final RunProcess rp,final String METHOD_NAME, SoapObject soapReq)
    {
        final SoapSerializationEnvelope envelope = new SoapSerializationEnvelope(SoapEnvelope.VER11);
        int TimeOut = 5*1000;
    
        envelope.dotNet = true;
        envelope.bodyOut = soapReq;
        envelope.setOutputSoapObject(soapReq);
    
        final HttpsTransportSE httpTransport_net = new HttpsTransportSE(URL, 443, SERVICE, TimeOut);
    
        try
        {
            HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() // use this section if crt file is handmake
            {
                @Override
                public boolean verify(String hostname, SSLSession session)
                {
                    return true;
                }
            });
    
            KeyStore k = getFromRaw(R.raw.key, "PKCS12", "password");
            ((HttpsServiceConnectionSE) httpTransport_net.getServiceConnection()).setSSLSocketFactory(getSSLSocketFactory(k, "SSL"));
    
    
        }
        catch(Exception e){}
    
        HandlerThread thread = new HandlerThread("wcfTd"+ Generator.getRandomNumber())
        {
            @Override
            public void run()
            {
                Handler h = new Handler(Looper.getMainLooper());
                Object response = null;
    
                for(int i=0; i<4; i++)
                {
                    response = send(envelope, httpTransport_net , METHOD_NAME, null);
    
                    try
                    {if(Thread.currentThread().isInterrupted()) return;}catch(Exception e){}
    
                    if(response != null)
                        break;
    
                    ThreadHelper.threadSleep(250);
                }
    
                if(response != null)
                {
                    if(rp != null)
                    {
                        rp.setArguments(response.toString());
                        h.post(rp);
                    }
                }
                else
                {
                    if(Thread.currentThread().isInterrupted())
                        return;
    
                    if(rp != null)
                    {
                        rp.setExceptionState(true);
                        h.post(rp);
                    }
                }
    
                ThreadHelper.stopThread(this);
            }
        };
    
        thread.start();
    
        return thread;
    }
    
    
    private static Object send(SoapSerializationEnvelope envelope, HttpTransportSE androidHttpTransport, String METHOD_NAME, List<HeaderProperty> headerList)
    {
        try
        {
            if(headerList != null)
                androidHttpTransport.call(SOAP_ACTION + METHOD_NAME, envelope, headerList);
            else
                androidHttpTransport.call(SOAP_ACTION + METHOD_NAME, envelope);
    
            Object res = envelope.getResponse();
    
            if(res instanceof SoapPrimitive)
                return (SoapPrimitive) envelope.getResponse();
            else if(res instanceof SoapObject)
                return ((SoapObject) envelope.getResponse());
        }
        catch(Exception e)
        {}
    
        return null;
    }
    
    public static KeyStore getFromRaw(@RawRes int id, String algorithm, String filePassword)
    {
        try
        {
            InputStream inputStream = ResourceMaster.openRaw(id);
            KeyStore keystore = KeyStore.getInstance(algorithm);
            keystore.load(inputStream, filePassword.toCharArray());
            inputStream.close();
    
            return keystore;
        }
        catch(Exception e)
        {}
    
        return null;
    }
    
    public static SSLSocketFactory getSSLSocketFactory(KeyStore trustKey, String SSLAlgorithm)
    {
        try
        {
            TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
            tmf.init(trustKey);
    
            SSLContext context = SSLContext.getInstance(SSLAlgorithm);//"SSL" "TLS"
            context.init(null, tmf.getTrustManagers(), null);
    
            return context.getSocketFactory();
        }
        catch(Exception e){}
    
        return null;
    }
    

    }

    0 讨论(0)
  • 2020-11-21 05:32

    I'm adding a response for those that use the httpclient-4.5, and probably works for 4.4 as well.

    import java.security.cert.CertificateException;
    import java.security.cert.X509Certificate;
    
    import org.apache.http.HttpResponse;
    import org.apache.http.client.HttpClient;
    import org.apache.http.client.HttpResponseException;
    import org.apache.http.client.fluent.ContentResponseHandler;
    import org.apache.http.client.methods.HttpPost;
    import org.apache.http.conn.ssl.NoopHostnameVerifier;
    import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
    import org.apache.http.conn.ssl.TrustStrategy;
    import org.apache.http.impl.client.CloseableHttpClient;
    import org.apache.http.impl.client.HttpClients;
    import org.apache.http.ssl.SSLContextBuilder;
    
    
    
    public class HttpClientUtils{
    
    public static HttpClient getHttpClientWithoutSslValidation_UsingHttpClient_4_5_2() {
        try {
            SSLContextBuilder builder = new SSLContextBuilder();
            builder.loadTrustMaterial(null, new TrustStrategy() {
                @Override
                public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException {
                    return true;
                }
            });
            SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(builder.build(), new NoopHostnameVerifier());
            CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(sslsf).build(); 
            return httpclient;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    }
    
    0 讨论(0)
提交回复
热议问题