HTTP、HTTPS、SSL、证书的关系

参考文档:
https://developer.android.com/training/articles/security-ssl.html?hl=zh-cn
https://github.com/square/okhttp/wiki/HTTPS

SSL(Secure Sockets Layer)以及TSL(Transport Layer Security)是在传输层对网络连接进行加密,保证数据不被窃听与截取。
通用规格为40bit,美国推出128bit高安全标准。这个是作为RC4流加密算法。

SSL协议包括两层

  • SSL记录协议:数据封装、压缩、加密
  • SSL握手协议:通讯双方进行身份认证,协商加密算法、交换加密秘钥

HTTPS = HTTP + SSL
HTTPS使用的是443端口,而HTTP使用的是80端口。
HTTPS与SSL使用x.509数字认证。

HTTP、HTTPS、SSL、证书的关系_第1张图片

注:
CA:Certificate Authority(证书颁发机构)
Certificate:证书,包括public key。
CA会下发给Server一个证书,该证书是可被信任的,Server发送数据会Client的时候,Client通过校验该证书来检验Server是否合法有效。Client如何去校验证书呢?在Client端会存储被信任的证书集合,如果该证书属于该集合,则有效,否则无效,抛出异常。

在Android4.2中,有超过100个证书。
每一个证书颁发给Server,host唯一识别,例如**.google.com。

如果我们要处理证书相关的东西,URLConnection已经不能满足我们现有的需求,需要使用URLConnection的子类HttpURLConnection。它提供的操作更多,可以方便你处理Request的一些参数要求等。

一些证书相关的问题需要考虑

getInputStream()可能会抛出

javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
        at org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:374)
        at libcore.net.http.HttpConnection.setupSecureSocket(HttpConnection.java:209)
        at libcore.net.http.HttpsURLConnectionImpl$HttpsEngine.makeSslConnection(HttpsURLConnectionImpl.java:478)
        at libcore.net.http.HttpsURLConnectionImpl$HttpsEngine.connect(HttpsURLConnectionImpl.java:433)
        at libcore.net.http.HttpEngine.sendSocketRequest(HttpEngine.java:290)
        at libcore.net.http.HttpEngine.sendRequest(HttpEngine.java:240)
        at libcore.net.http.HttpURLConnectionImpl.getResponse(HttpURLConnectionImpl.java:282)
        at libcore.net.http.HttpURLConnectionImpl.getInputStream(HttpURLConnectionImpl.java:177)
        at libcore.net.http.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:271)

导致上述原因有以下几种可能

  • The CA that issued the server certificate was unknown,简而言之就是CA是不合法的。可以设想以下如果连颁发证书的机构都是不合法的,那么它颁发的证书也是不合法的。
    很多时候,CA不是public的,有些可能是私人机构,例如政府、公司、教育机构等,他们有自己的私人用途。这个时候需要教HttpURLConnection去信任这样的私人证书。主要是通过TrustManager来完成这一过程,下面是官方的一段话。
    Fortunately, you can teach HttpsURLConnection to trust a specific set of CAs. The procedure can be a little convoluted, so below is an example that takes a specific CA from an InputStream, uses it to create a KeyStore, which is then used to create and initialize a TrustManager. A TrustManager is what the system uses to validate certificates from the server and—by creating one from a KeyStore with one or more CAs—those will be the only CAs trusted by that TrustManager.
    Given the new TrustManager, the example initializes a new SSLContext which provides an SSLSocketFactory you can use to override the default SSLSocketFactory from HttpsURLConnection. This way the connection will use your CAs for certificate validation.
    Here is the example in full using an organizational CA from the University of Washington:
// Load CAs from an InputStream
// (could be from a resource or ByteArrayInputStream or ...)
CertificateFactory cf = CertificateFactory.getInstance("X.509");
// From https://www.washington.edu/itconnect/security/ca/load-der.crt
InputStream caInput = new BufferedInputStream(new FileInputStream("load-der.crt"));
Certificate ca;
try {
    ca = cf.generateCertificate(caInput);
    System.out.println("ca=" + ((X509Certificate) ca).getSubjectDN());
} finally {
    caInput.close();
}

// Create a KeyStore containing our trusted CAs
String keyStoreType = KeyStore.getDefaultType();
KeyStore keyStore = KeyStore.getInstance(keyStoreType);
keyStore.load(null, null);
keyStore.setCertificateEntry("ca", ca);

// Create a TrustManager that trusts the CAs in our KeyStore
String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
tmf.init(keyStore);

// Create an SSLContext that uses our TrustManager
SSLContext context = SSLContext.getInstance("TLS");
context.init(null, tmf.getTrustManagers(), null);

// Tell the URLConnection to use a SocketFactory from our SSLContext
URL url = new URL("https://certs.cac.washington.edu/CAtest/");
HttpsURLConnection urlConnection =
    (HttpsURLConnection)url.openConnection();
urlConnection.setSSLSocketFactory(context.getSocketFactory());
InputStream in = urlConnection.getInputStream();
copyInputStreamToOutputStream(in, System.out);
  • Self-signed server certificate,就是服务器把自己当做CA,来自己颁发了一个证书,那当然,这个证书也是无效的,因为CA就是无效的。这个问题的解决和上个问题一样,这里不再赘述。
  • Missing intermediate certificate authority,缺少中间证书机构。这里涉及到一个证书链的问题,根证书下面有子证书,根证书机构可以给子证书机构颁发证书,子证书机构可以给孙子证书机构颁发证书,这些都算是可被信任的证书机构。像Android这种,只相信根证书机构和根证书,所以在server下发证书的时候,不会只下发自己的证书,而是下发一个证书链。那些子证书就叫做intermediate certificate authority,拿到证书链,通过这些子证书就可以拿到根证书,只要验证根证书是否被信任即可。所以如果下发的证书缺少中间证书的话,就会失败。

下面是验证主机名(Hostname)的问题
验证SSL connection有两步:

  • 验证证书是否合法(这是我们前面所讲到的东西)
  • 验证服务器是否有正确的证书(即我们想要的那个证书)

你可能感兴趣的:(android)