安卓SSL HttpGet出现(无法验证对等证书)错误或(连接被对等方关闭)错误

28

我正在尝试使用简单的HttpGet读取网页。在iOS上,我已经可以通过http实现,但在Android上无法通过https实现。

该url是一个内部网络IP和自定义端口,所以我可以像这样使用http读取:http://ipaddress:port/MyPage.html

HttpClient httpclient = new DefaultHttpClient(httpParameters);
                    HttpResponse response;
        String responseString = null;
        try {
            // Try connection
            HttpGet get = new HttpGet(params[0].path);
            get.addHeader("Authorization",
                    "Basic "
                            + Base64.encodeBytes(new String(params[0].username + ":" + params[0].password)
                                    .getBytes()));
        response = httpclient.execute(get);
        StatusLine statusLine = response.getStatusLine();
        if (statusLine.getStatusCode() == HttpStatus.SC_OK) {
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            response.getEntity().writeTo(out);
            out.close();
            responseString = out.toString();
        } else {
            // Closes the connection.
            response.getEntity().getContent().close();
            throw new IOException(statusLine.getReasonPhrase());
        }
    } catch (ClientProtocolException e) {
        Log.e(TAG, "ClientProtocolException");
        this.e = e;
    } catch (IOException e) {
        Log.e(TAG, "IOException");
        this.e = e;
    }
    return responseString;

当我尝试使用https时,会出现No peer certificate错误。因此,我已经尝试使用以下代码:HttpClient httpclient = new DefaultHttpClient(httpParameters);

private HttpClient createHttpClient() {
        try {
            KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
            trustStore.load(null, null);

            SSLSocketFactory sf = new MySSLSocketFactory(trustStore);
            sf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);

            HttpParams params = new BasicHttpParams();
            HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
            HttpProtocolParams.setContentCharset(params, HTTP.DEFAULT_CONTENT_CHARSET);
            HttpProtocolParams.setUseExpectContinue(params, true);

            SchemeRegistry schReg = new SchemeRegistry();
            schReg.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
            schReg.register(new Scheme("https", sf, 8080));
            ClientConnectionManager conMgr = new ThreadSafeClientConnManager(params, schReg);

            return new DefaultHttpClient(conMgr, params);

        } catch (Exception e) {
            return new DefaultHttpClient();
        }
    }

但是这给了我一个"连接被对等方关闭"的错误。

我做错了什么?我可以安全地忽略证书,因为它是一个带自签名证书的内部网络,但是我无法控制vert和我的应用程序用户可能有不同的证书,所以我真的需要自动接受或绕过它。

谢谢

编辑 ------------------------------

尝试下面My-Name-Is的答案后:

我创建了一个CustomX509TrustManager类,然后使用它创建一个自定义HttpClient,如下所示:

private HttpClient sslClient(HttpClient client) {
        try {
            CustomX509TrustManager tm = new CustomX509TrustManager();
            SSLContext ctx = SSLContext.getInstance("TLS");
            ctx.init(null, new TrustManager[] { tm }, null);
            SSLSocketFactory ssf = new MySSLSocketFactory(ctx);
            ssf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
            ClientConnectionManager ccm = client.getConnectionManager();
            SchemeRegistry sr = ccm.getSchemeRegistry();
            sr.register(new Scheme("https", ssf, 8080));
            return new DefaultHttpClient(ccm, client.getParams());
        } catch (Exception ex) {
            return null;
        }
    }

最后,像这样使用HttpClient:

private class httpGETTask extends AsyncTask<GetParams, Void, String> {
private Exception e = null;

@Override
protected String doInBackground(GetParams... params) {
    // Set connection parameters
    HttpParams httpParameters = new BasicHttpParams();
    int timeoutConnection = 15000;
    HttpConnectionParams.setConnectionTimeout(httpParameters, timeoutConnection);
    int timeoutSocket = 15000;
    HttpConnectionParams.setSoTimeout(httpParameters, timeoutSocket);

    Log.v(TAG, params[0].path);
    HttpClient httpclient = new DefaultHttpClient(httpParameters);
    httpclient = sslClient(httpclient);

    HttpResponse response;
    String responseString = null;
    try {
        // Try connection
        HttpGet get = new HttpGet(params[0].path);
        get.addHeader("Authorization",
                "Basic "
                        + Base64.encodeBytes(new String(params[0].username + ":" + params[0].password)
                                .getBytes()));

        response = httpclient.execute(get);
        StatusLine statusLine = response.getStatusLine();
        if (statusLine.getStatusCode() == HttpStatus.SC_OK) {
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            response.getEntity().writeTo(out);
            out.close();
            responseString = out.toString();
        } else {
            // Closes the connection.
            response.getEntity().getContent().close();
            throw new IOException(statusLine.getReasonPhrase());
        }
    } catch (ClientProtocolException e) {
        Log.e(TAG, "ClientProtocolException");
        this.e = e;
    } catch (IOException e) {
        Log.e(TAG, "IOException");
        this.e = e;
    }
    return responseString;

记录的路径格式为https://ipaddress:8080/Page.html,但我遇到了一个连接被对等方关闭的错误:

05-24 08:20:32.500:E/ConnectionHelper(1129):IOException 05-24 08:20:32.550:E/ConnectionHelper(1129):加载内容时出现异常 05-24 08:20:32.550:E/ConnectionHelper(1129): javax.net.ssl.SSLException:连接被对等方关闭 05-24 08:20:32.550:E/ConnectionHelper(1129): 在 org.apache.harmony.xnet.provider.jsse.NativeCrypto.SSL_do_handshake(Native Method)05-24 08:20:32.550:E/ConnectionHelper(1129): 在 org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:410) 05-24 08:20:32.550:E/ConnectionHelper(1129): 在 org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl$SSLInputStream.(OpenSSLSocketImpl.java:643) 05-24 08:20:32.550:E/ConnectionHelper(1129): 在 org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl.getInputStream(OpenSSLSocketImpl.java:614) 05-24 08:20:32.550:E/ConnectionHelper(1129): 在 org.apache.http.impl.io.SocketInputBuffer.(SocketInputBuffer.java:70) 05-24 08:20:32.550:E/ConnectionHelper(1129): 在 org.apache.http.impl.SocketHttpClientConnection.createSessionInputBuffer(SocketHttpClientConnection.java:83) 05-24 08:20:32.550:E/ConnectionHelper(1129): 在 org.apache.http.impl.conn.DefaultClientConnection.createSessionInputBuffer(DefaultClientConnection.java:170) 05-24 08:20:32.550:E/ConnectionHelper(1129): 在 org.apache.http.impl.SocketHttpClientConnection.bind(SocketHttpClientConnection.java:106) 05-24 08:20:32.550:E/ConnectionHelper(1129): 在 org.apache.http.impl.conn.DefaultClientConnection.openCompleted(DefaultClientConnection.java:129) 05-24 08:20:32.550:E/ConnectionHelper(1129): 在 org.apache.http.impl.conn.DefaultClientConnectionOperator.openConnection(DefaultClientConnectionOperator.java:172) 05-24 08:20:32.550:E/ConnectionHelper(1129): 在 org.apache.http.impl.conn.AbstractPoolEntry.open(AbstractPoolEntry.java:164) 05-24 08:20:32.550:E/ConnectionHelper(1129): 在 org.apache.http.impl.conn.AbstractPooledConnAdapter.open(AbstractPooledConnAdapter.java:119) 05-24 08:20:32.550:E/ConnectionHelper(1129): 在 org.apache.http.impl.client.DefaultRequestDirector.execute(DefaultRequestDirector.java:360) 05-24 08:20:32.550:E/ConnectionHelper(1129): 在 org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:555) 05-24 08:20:32.550:E/ConnectionHelper(1129): 在 org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:487) 05-24 08:20:32.550:E/ConnectionHelper(1129): 在 org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:465) 05-24 08:20:32.550:E/ConnectionHelper(1129): 在 com.d_apps.my_app.connection_helpers.ConnectionHelper$httpGETTask.doInBackground(ConnectionHelper.java:114)

7个回答

56
以下源代码应该可以解决你的问题。
import android.app.Activity;
import android.widget.EditText;
import android.os.Bundle;
import org.apache.http.HttpResponse;
import org.apache.http.Header
import java.io.InputStream;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import android.util.Log;
import android.view.Menu;
public class MainActivity extends Activity {

    private EditText text;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        text = (EditText) findViewById(R.id.editText1);
        connect();
    }

    private void connect(){
        try {
            DataLoader dl = new DataLoader();
            String url = "https://IpAddress";
            HttpResponse response = dl.secureLoadData(url); 

            StringBuilder sb = new StringBuilder();
            sb.append("HEADERS:\n\n");

            Header[] headers = response.getAllHeaders();
            for (int i = 0; i < headers.length; i++) {
                Header h = headers[i];
                sb.append(h.getName()).append(":\t").append(h.getValue()).append("\n");
            }

            InputStream is = response.getEntity().getContent();
            StringBuilder out = new StringBuilder();
            BufferedReader br = new BufferedReader(new InputStreamReader(is));
            for (String line = br.readLine(); line != null; line = br.readLine())
                out.append(line);
            br.close();

            sb.append("\n\nCONTENT:\n\n").append(out.toString()); 

            Log.i("response", sb.toString());
            text.setText(sb.toString());

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.activity_main, menu);
        return true;
    }

}


import android.app.Application;
import android.content.Context;
import java.io.InputStream;
public class MeaApplication extends Application {

    private static Context context;

    @Override
    public void onCreate() {
        super.onCreate();
        MeaApplication.context = getApplicationContext();
    }

    public static Context getAppContext() {
        return MeaApplication.context;
    }

    public static InputStream loadCertAsInputStream() {
        return MeaApplication.context.getResources().openRawResource(
                R.raw.meacert);
    }

}


import org.apache.http.conn.ssl.SSLSocketFactory;
import javax.net.ssl.SSLContext;
import java.security.KeyStore;
import java.security.NoSuchAlgorithmException;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.UnrecoverableKeyException;
import javax.net.ssl.TrustManager;
import java.net.Socket;
import java.io.IOException;
import java.net.UnknownHostException;
/**
 * Taken from: http://janis.peisenieks.lv/en/76/english-making-an-ssl-connection-via-android/
 *
 */
public class CustomSSLSocketFactory extends SSLSocketFactory {
    SSLContext sslContext = SSLContext.getInstance("TLS");

    public CustomSSLSocketFactory(KeyStore truststore)
            throws NoSuchAlgorithmException, KeyManagementException,
            KeyStoreException, UnrecoverableKeyException {
        super(truststore);

        TrustManager tm = new CustomX509TrustManager();

        sslContext.init(null, new TrustManager[] { tm }, null);
    }

    public CustomSSLSocketFactory(SSLContext context)
            throws KeyManagementException, NoSuchAlgorithmException,
            KeyStoreException, UnrecoverableKeyException {
        super(null);
        sslContext = context;
    }

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

    @Override
    public Socket createSocket() throws IOException {
        return sslContext.getSocketFactory().createSocket();
    }
}


import javax.net.ssl.X509TrustManager;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.io.IOException;
import java.io.InputStream;
import java.security.cert.CertificateFactory;
public class CustomX509TrustManager implements X509TrustManager {

    @Override
    public void checkClientTrusted(X509Certificate[] chain, String authType)
            throws CertificateException {
    }

    @Override
    public void checkServerTrusted(java.security.cert.X509Certificate[] certs,
            String authType) throws CertificateException {

        // Here you can verify the servers certificate. (e.g. against one which is stored on mobile device)

        // InputStream inStream = null;
        // try {
        // inStream = MeaApplication.loadCertAsInputStream();
        // CertificateFactory cf = CertificateFactory.getInstance("X.509");
        // X509Certificate ca = (X509Certificate)
        // cf.generateCertificate(inStream);
        // inStream.close();
        //
        // for (X509Certificate cert : certs) {
        // // Verifing by public key
        // cert.verify(ca.getPublicKey());
        // }
        // } catch (Exception e) {
        // throw new IllegalArgumentException("Untrusted Certificate!");
        // } finally {
        // try {
        // inStream.close();
        // } catch (IOException e) {
        // e.printStackTrace();
        // }
        // }
    }

    public X509Certificate[] getAcceptedIssuers() {
        return null;
    }

}


import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.security.KeyManagementException;
import java.net.URISyntaxException;
import java.security.KeyStoreException;
import java.security.UnrecoverableKeyException;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import java.security.SecureRandom;
import org.apache.http.client.HttpClient;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.client.methods.HttpGet;
public class DataLoader {

    public HttpResponse secureLoadData(String url)
            throws ClientProtocolException, IOException,
            NoSuchAlgorithmException, KeyManagementException,
            URISyntaxException, KeyStoreException, UnrecoverableKeyException {
        SSLContext ctx = SSLContext.getInstance("TLS");
        ctx.init(null, new TrustManager[] { new CustomX509TrustManager() },
                new SecureRandom());

        HttpClient client = new DefaultHttpClient();

        SSLSocketFactory ssf = new CustomSSLSocketFactory(ctx);
        ssf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
        ClientConnectionManager ccm = client.getConnectionManager();
        SchemeRegistry sr = ccm.getSchemeRegistry();
        sr.register(new Scheme("https", ssf, 443));
        DefaultHttpClient sslClient = new DefaultHttpClient(ccm,
                client.getParams());

        HttpGet get = new HttpGet(new URI(url));
        HttpResponse response = sslClient.execute(get);

        return response;
    }

}

@benoffi7 当然。目前我手头没有源代码。不过,我会在今晚添加导入项。 - My-Name-Is
经过一天多的尝试,最终这段代码对我起作用了。我所做的唯一更改是稍微修改了一下以便与我的POST一起使用,因为我没有使用GET进行SSL。我另外的区别是我没有一个扩展Application的类,并且我不会在本地加载任何证书。但仍然可以正常工作。我想知道,如果我不在本地加载我的证书,我是否会面临任何漏洞,或者我还好吗?或者我是否基本上在不知情的情况下绕过身份验证?我不是SSL方面的专家,但它似乎可以顺利运行而没有任何错误。 - zeeshan
@zeeshan “我没有在本地加载任何证书[...] 我是否会暴露出任何漏洞?” 不,您不会引入任何漏洞。这只是允许您在移动应用程序中额外验证接收到的证书。cert.verify(ca.getPublicKey()); 我添加了这段源代码,只是为了展示如何实现“checkServerTrusted”方法的一种可能性。对于大多数应用程序来说,没有必要进行此检查。 - My-Name-Is
2
明白了。感谢您的澄清。如果我的应用程序(LifeLog24)变得出名,我会考虑添加这个额外的检查 :) - zeeshan
1
@John 这个解决方案不使用备用的KeyStore,但是你可以调整你的SSLSocketFactory来支持这个功能。上面的解决方案使用了一个自定义的X509TrustManager,它根据证书的公钥决定应用程序是否被允许连接到特定的服务器。控制这种行为的checkServerTrusted的自定义实现已经被注释掉了,因为它可能对其他环境没有用处。 - My-Name-Is
显示剩余9条评论

13

如果您使用的是“不受信任”(开发人员)证书,则以下是解决方法。我们需要信任所有证书,以下是实现方式。对于受信任的证书,无需添加以下功能,我们只需要将http更改为https即可正常工作。

以下是不受信任证书的解决方案。

在HttpClient中,您应该从org.apache.http.conn.ssl.SSLSocketFactory创建一个自定义类,而不是使用org.apache.http.conn.ssl.SSLSocketFactory本身。

示例如下...

import java.io.IOException;
import java.net.Socket;
import java.net.UnknownHostException;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;

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

import org.apache.http.conn.ssl.SSLSocketFactory;
public class MySSLSocketFactory extends SSLSocketFactory {
    SSLContext sslContext = SSLContext.getInstance("TLS");

    public MySSLSocketFactory(KeyStore truststore) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
        super(truststore);

        TrustManager tm = new X509TrustManager() {
            public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
            }

            public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
            }

            public X509Certificate[] getAcceptedIssuers() {
                return null;
            }
        };

        sslContext.init(null, new TrustManager[] { tm }, null);
    }

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

    @Override
    public Socket createSocket() throws IOException {
        return sslContext.getSocketFactory().createSocket();
    }
}

并在创建HttpClient实例时使用此类。

public HttpClient getNewHttpClient() {
    try {
        KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
        trustStore.load(null, null);

        SSLSocketFactory sf = new MySSLSocketFactory(trustStore);
        sf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);

        HttpParams params = new BasicHttpParams();
        HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
        HttpProtocolParams.setContentCharset(params, HTTP.UTF_8);

        SchemeRegistry registry = new SchemeRegistry();
        registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
        registry.register(new Scheme("https", sf, 443));

        ClientConnectionManager ccm = new ThreadSafeClientConnManager(params, registry);

        return new DefaultHttpClient(ccm, params);
    } catch (Exception e) {
        return new DefaultHttpClient();
    }
}

@IgorGanapolsky,你解决了这个异常吗?我也遇到了同样的问题。 - techroid

7

请检查您的设备日期是否正确。可能是您将日期改成了其他年份。当孩子使用我的手机时,我经常会遇到这个问题。


+1 当你遇到错误 Νο peer certificate 时,这似乎是最常见的情况。 - AggelosK

3
如果您可以访问具有受信任证书的服务器,则更好的解决方案是正确配置SSL,因为与iOS和桌面浏览器相比,Android在SSL验证方面更加受限。此解决方案不需要对Android应用程序进行任何更改,因此更加清洁。以下是Apache的示例SSL配置(将其添加到虚拟主机定义中,例如/ etc / apache2 / sites-enabled)。
SSLEngine on
SSLCertificateFile    YOUR_CERT_PATH
SSLCACertificateFile  CA_ROOT_CERT_PATH
SSLCertificateKeyFile KEY_PATH

我曾经遇到同样的错误,当我添加了CA根证书后,错误就消失了,安卓也不再抱怨。请提供这些文件的正确路径,重启Apache并重新测试。
CA根证书文件可以包含根和中间证书。
您可以使用此网站进行SSL配置测试,并确保在结果的“认证路径”下服务器发送了所有必需的证书。 这里是该网站

1

允许Android的安全提供程序在启动您的应用程序时进行更新。

5.0+版本之前的默认提供程序不会禁用SSLv3。只要您可以访问Google Play服务,就相对容易地从您的应用程序中修补Android的安全提供程序。

  private void updateAndroidSecurityProvider(Activity callingActivity) {
    try {
        ProviderInstaller.installIfNeeded(this);
    } catch (GooglePlayServicesRepairableException e) {
      // Thrown when Google Play Services is not installed, up-to-date, or  enabled
        // Show dialog to allow users to install, update, or otherwise    enable Google Play services.
       GooglePlayServicesUtil.getErrorDialog(e.getConnectionStatusCode(), callingActivity, 0);
    } catch (GooglePlayServicesNotAvailableException e) {
        Log.e("SecurityException", "Google Play Services not available.");
    }
}

来源: 使用ProviderInstaller Provider修补安全提供程序


“installIfNeeded(this)”是什么意思?这里的“this”指的是什么? - IgorGanapolsky
@nani 做得好。感谢您与社区分享这个。我很可能无法及时找出这个问题。 - tronious

0

当搜索此异常时,你只能得到实现“允许所有证书”的建议。

SSLPeerUnverifiedException的javadoc说明如下:

当对等方无法进行身份验证时(例如:没有证书,使用的特定密码套件不支持身份验证,或在SSL握手期间未建立对等方身份验证),将抛出此异常。

因此,错误可能是连接非常不稳定,请求花费了荒谬的时间。在我们的移动应用程序中,有时会遇到大量的连接/套接字超时和这些SSLPeerUnverifiedException的组合。一些请求可以通过,但需要60秒以上 - 在这些情况下,网络连接简直太差劲了。

在这种情况下,仅仅强行实现“允许所有证书”是不可取的-最好实施适当的重试策略。


套接字超时与SSLPeerUnverifiedException无关。你要么得到一个,要么得到另一个。 - user207421
我并不是说你可以同时获得两者。我不会进行验证,但我的经验是,在处理非常不稳定的连接时,可能会在SSL握手期间的对等身份验证时遇到SSLPeerUnverifiedException。在这种情况下,当然应该出现SSLHandshakeException,但我不能100%确定你是否会看到一个"错误的"SSLPeerUnverifiedException。 你是@user207421吗? - icyerasor

-2

我最初使用的是HttpsURLConnection,但它并不一直有效,所以我决定改用HttpClient。现在它可以正常工作了。


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