Android SSLPeerUnverifiedException问题解决

Posted by vcoolwind on September 13, 2016

Android SSLPeerUnverifiedException问题解决

本文主要讲解下Android环境下 SSLPeerUnverifiedException异常的解决。

问题现象

公司服务添加Ali WAF后,部分低版本Android https访问异常,debug显示如下信息:

09-13 17:37:27.972: E/LoginActivity(30091): No peer certificate
09-13 17:37:28.735: W/System.err(30091): javax.net.ssl.SSLPeerUnverifiedException: No peer certificate
09-13 17:37:28.749: W/System.err(30091): 	at com.android.org.conscrypt.SSLNullSession.getPeerCertificates(SSLNullSession.java:104)
09-13 17:37:28.761: W/System.err(30091): 	at org.apache.http.conn.ssl.AbstractVerifier.verify(AbstractVerifier.java:93)
09-13 17:37:28.771: W/System.err(30091): 	at org.apache.http.conn.ssl.SSLSocketFactory.createSocket(SSLSocketFactory.java:388)
09-13 17:37:28.783: W/System.err(30091): 	at org.apache.http.impl.conn.DefaultClientConnectionOperator.openConnection(DefaultClientConnectionOperator.java:165)
09-13 17:37:28.793: W/System.err(30091): 	at org.apache.http.impl.conn.AbstractPoolEntry.open(AbstractPoolEntry.java:164)
09-13 17:37:28.804: W/System.err(30091): 	at org.apache.http.impl.conn.AbstractPooledConnAdapter.open(AbstractPooledConnAdapter.java:119)
09-13 17:37:28.815: W/System.err(30091): 	at org.apache.http.impl.client.DefaultRequestDirector.execute(DefaultRequestDirector.java:360)
09-13 17:37:28.825: W/System.err(30091): 	at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:581)
09-13 17:37:28.834: W/System.err(30091): 	at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:506)
09-13 17:37:28.844: W/System.err(30091): 	at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:484)

问题分析

此类问题比较明显,应该是低版本的Android对TLS支持有问题,通过注册TLS解决问题。

问题解决

构建自定义的SSLSocketFactory并在发起请求前注册之。 发起调用片段如下:

        HttpPost post = new HttpPost(SERVICE_URL);
        post.addHeader("http.protocol.content-charset", HTTP.UTF_8);
        List<NameValuePair> params = new ArrayList<NameValuePair>();
        String data = getMessage();
        params.add(new BasicNameValuePair("data", data));
        post.setEntity(new UrlEncodedFormEntity(params, HTTP.UTF_8));

        BasicHttpParams httpParams = new BasicHttpParams();
        HttpConnectionParams.setConnectionTimeout(httpParams, requestTimeout);
        HttpConnectionParams.setSoTimeout(httpParams, soTimeout);

        // 标记UA
        if (Constants.UA != null) {
            HttpProtocolParams.setUserAgent(httpParams, Constants.UA);
        }
        HttpClient client = new DefaultHttpClient(httpParams);
        HttpResponse response = null;
        
        if (SERVICE_URL.startsWith("https")) {
            // 设置SSL访问,解决证书问题。
            SSLContext ctx = SSLContext.getInstance("TLS");
            ctx.init(null, new TrustManager[] { new CustomX509TrustManager() }, new SecureRandom());
            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());
            response = sslClient.execute(post);
        } else {
            response = client.execute(post);
        }
        // 处理响应
        if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
            HttpEntity entity = response.getEntity();
            String content = EntityUtils.toString(entity, "utf-8");
            return content;
        } else {
            String error = "网络出问题啦!请稍后重试!";
            throw new Exception("", error);
        }        

自定义类代码如下:

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;

/**
 * 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();
    }
}

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;
    }

}

### 参考 这里还有这里分析的很清楚了,感谢互联网让大家离的很近!

vcoolwind / -  views
Published under(CC) BY-NC-SA 3.0 CN.