[Android] Bypass SSL cert check when connecting to localhost through HTTPS

最近需要用HTTPS连接到localhost做一些测试,但是发现一个SSL证书的问题, 用Android的MediaPlayer播放Https内容的时候总是失败在SSL连接握手阶段,应该是localhost证书非法,验证不了,直接导致MediaPlayer拒绝连接。

1
2
javax.net.ssl.SSLProtocolException: SSL handshake terminated: ssl=0xef44d200: Failure in SSL library, usually a protocol error
error:14094416:SSL routines:SSL3_READ_BYTES:sslv3 alert certificate unknown (external/openssl/ssl/s3_pkt.c:1286 0xef40c640:0x00000003)

Level of Security

对于连接的安全级别,可以分为下列3个级别:

  • Level A – no encryption, no verification
  • Level B – encryption, no verification
  • Level C – encryption and verification

Level A 就是相当于http连接,没有任何保护;而Level C就相当于https连接,连接加密且验证连接方。

而对于媒体播放的需求其实更接近于是Level B,只需要传输数据加密,不让别人知道自己看过什么,而对播放源验证不会有那么严格的要求。

How to bypass verification?

https 连接验证可以粗略的分为两个阶段:

  • Step 1: 验证服务器证书是否是由trusted CA颁发的。
  • Step 2: 验证服务器是否隶属于该授信证书。

Java Implement

Java 中将上面的两个验证步骤交由一下两个class实现:

  • Step 1: javax.net.ssl.X509TrustManager
  • Step 2: javax.net.ssl.HostnameVerifier

Bypass in Android

Android 沿用了Java的HttpsURLConnection, 它里面提供两个API: setDefaultSSLSocketFactory, setDefaultHostnameVerifier 可以修改全局的SSL验证机制。

通过修改默认的SSLSocketFactoryHostnameVerifier,之后调用MediaPlayer.setDataSource(URL), 就可以成功连接。

注意到line 15line 39,我们重新定义了这两个class,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
import java.security.cert.X509Certificate;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import javax.net.ssl.SSLSocketFactory;
 
public class BypassSSLCert {
    private SSLSocketFactory mDefaultSSLSocketFactory = HttpsURLConnection.getDefaultSSLSocketFactory();
    private HostnameVerifier mDefaultHostnameVerifier = HttpsURLConnection.getDefaultHostnameVerifier();
 
    /* dummy TrustManager to trust all host, test only */
    private static X509TrustManager getDummyTrustManager() {
        return new X509TrustManager() {
            public java.security.cert.X509Certificate[] getAcceptedIssuers() {
                return null;
            }
            public void checkClientTrusted(X509Certificate[] certs, String authType) {
            }
            public void checkServerTrusted(X509Certificate[] certs, String authType) {
            }
        };
    }
 
    /* by pass SSL connection error to localhost */
    public void bypassLocalhostSSL(boolean enable) throws Exception {
        HostnameVerifier hostnameVerifier;
        SSLSocketFactory sslSocketFactory;
        if (enable) {
            TrustManager[] tma = new TrustManager[] {getDummyTrustManager()};
 
            // Install the all-trusting trust manager
            SSLContext sc = SSLContext.getInstance("TLS");
            sc.init(null, tma, new java.security.SecureRandom());
            sslSocketFactory = sc.getSocketFactory();
 
            hostnameVerifier = new HostnameVerifier() {
                @Override
                public boolean verify(String hostname, SSLSession session) {
                    HostnameVerifier hv = mDefaultHostnameVerifier;
                    if (hostname.equals("localhost")) {
                        return true;
                    } else {
                        return hv.verify(hostname, session);
                    }
                }
            };
        } else {
            sslSocketFactory = mDefaultSSLSocketFactory;
            hostnameVerifier = mDefaultHostnameVerifier;
        }
        HttpsURLConnection.setDefaultSSLSocketFactory(sslSocketFactory);
        HttpsURLConnection.setDefaultHostnameVerifier(hostnameVerifier);
    }
}

但是这个方法有一个局限,只能在自己进程内有效,如果通过startActivity调用另外进程的APP,比如系统default的Player,就不能起作用了。

Reference

  1. https://agiletribe.wordpress.com/2011/10/16/working-around-javas-ssl-limitations/
  2. http://stackoverflow.com/questions/2642777/trusting-all-certificates-using-httpclient-over-https/6378872#6378872
  3. http://stackoverflow.com/questions/2012497/accepting-a-certificate-for-https-on-android
  4. http://developer.android.com/reference/javax/net/ssl/HttpsURLConnection.html
  5. http://developer.android.com/training/articles/security-ssl.html
  6. http://stackoverflow.com/questions/7828985/android-video-streaming-over-local-https-server-ssl-certificate-rejected

留言