说明:本文分析源码基于Android_8.1
(还在看代码和修改文章的阶段,有点乱,慢慢来~)
文章目录
0x01 系统证书
看源码可以发现,/system/ca-certificates/files 目录下存放了系统证书
系统证书命名规则为 <hash>.<N>
,其中 hash 通过命令生成,N 为从 0 开始的一个整数标记
openssl x509 -subject_hash_old -in filename
对应在Android 系统中,证书存放路径为 /system/etc/security/cacerts 目录
文件均为 PEM 格式的 X509 证书格式,包含明文base64编码公钥,证书信息,哈希等
0x02 证书管理类
Android系统提供系列类用于根证书管理,位于 /frameworks/base/core/java/android/security/net/config
目录
CertificateSource
是接口类,定义了对根证书可执行的获取和查询操作
public interface CertificateSource {
Set<X509Certificate> getCertificates();
X509Certificate findBySubjectAndPublicKey(X509Certificate cert);
X509Certificate findByIssuerAndSignature(X509Certificate cert);
Set<X509Certificate> findAllByIssuerAndSignature(X509Certificate cert);
void handleTrustStorageUpdate();
}
他有三个实现类,分别是:
KeyStoreCertificateSource
从 mKeyStore 中获取证书
Set<X509Certificate> certificates = new ArraySet<>(mKeyStore.size());
for (Enumeration<String> en = mKeyStore.aliases(); en.hasMoreElements();) {
String alias = en.nextElement();
X509Certificate cert = (X509Certificate) mKeyStore.getCertificate(alias);
if (cert != null) {
certificates.add(cert);
localIndex.index(cert);
}
}
ResourceCertificateSource
基于 mResourceId 从资源目录读取文件并构造证书
Set<X509Certificate> certificates = new ArraySet<X509Certificate>();
Collection<? extends Certificate> certs;
InputStream in = null;
try {
CertificateFactory factory = CertificateFactory.getInstance("X.509");
in = mContext.getResources().openRawResource(mResourceId);
certs = factory.generateCertificates(in);
} catch (CertificateException e) {
throw new RuntimeException("Failed to load trust anchors from id " + mResourceId, e);
} finally {
IoUtils.closeQuietly(in);
}
TrustedCertificateIndex indexLocal = new TrustedCertificateIndex();
for (Certificate cert : certs) {
certificates.add((X509Certificate) cert);
indexLocal.index((X509Certificate) cert);
}
DirectoryCertificateSource
则遍历指定的目录 mDir 读取证书
Set<X509Certificate> certs = new ArraySet<X509Certificate>();
if (mDir.isDirectory()) {
for (String caFile : mDir.list()) {
if (isCertMarkedAsRemoved(caFile)) {
continue;
}
X509Certificate cert = readCertificate(caFile);
if (cert != null) {
certs.add(cert);
}
}
}
另外这是一个抽象类,还提供了一个抽象方法 isCertMarkedAsRemoved()
用于判断证书是否被移除,SystemCertificateSource
和 UserCertificateSource
分别定义了系统和用户根证书库的路径,并实现抽象方法
SystemCertificateSource()
构造函数定义了系统证书查询路径,同时还指定了另一个文件地址存放在变量 mUserRemovedCaDir 中
private SystemCertificateSource() {
// /system/etc/security/cacerts
super(new File(System.getenv("ANDROID_ROOT") + "/etc/security/cacerts"));
// /data/misc/user/0/cacerts-removed
File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
mUserRemovedCaDir = new File(configDir, "cacerts-removed");
}
判断证书是否移除就是直接判断证书文件是否存在于指定的目录
protected boolean isCertMarkedAsRemoved(String caFile) {
return new File(mUserRemovedCaDir, caFile).exists();
}
同理,对于用户证书指定查询路径,而对于是否移除的判断总是返回 false
private UserCertificateSource() {
// /data/misc/user/0/cacerts-added
super(new File(Environment.getUserConfigDirectory(UserHandle.myUserId()), "cacerts-added"));
}
protected boolean isCertMarkedAsRemoved(String caFile) {
return false;
}
0x03 证书验证流程
对于证书验证流程,分为以下几个步骤来逐步分析,首先是根据之前分析的网络框架看系统如何建立安全的连接,接着我们重点看其中与安全相关的类,最后分析握手和证书验证过程具体是如何进行的。
3.1 建立安全的连接
之前分析 Android 的网络框架时了解到,Android 8.1 使用的网络框架是 HttpURLConnection
,那么先从 HttpsURLConnection
类看起。
首先给出网络相关类的继承关系图:
HttpsURLConnection
类继承自 HttpURLConnection
,在基本的网络通信功能之外,新增了两个内部成员类 HostnameVerifier
和 SSLSocketFactory
并给出默认的实现实例,以支持 https 的相关特性。
// HostnameVerifier
defaultHostnameVerifier = (HostnameVerifier)
Class.forName("com.android.okhttp.internal.tls.OkHostnameVerifier")
.getField("INSTANCE").get(null);
// SSLSocketFactory (具体实现后面会提到)
defaultSSLSocketFactory =
(SSLSocketFactory)SSLSocketFactory.getDefault();
在进行网络连接前,系统会首先根据不同的协议返回不同的客户端网络连接代理类实例。如果是 https,则返回 HttpsURLConnectionImpl
对象,这个类继承自 DlegatingHttpsURLConnection
,而 DlegatingHttpsURLConnection
又正好是 HttpsURLConnection
的派生抽象类,对连接的相关方法做了一个代理转发,重点还定义了握手的抽象类对象 Handshake
,就是在这个方法中,完成了 Https 协议中的握手操作
protected abstract Handshake handshake();
下面看 HttpsURLConnectionImpl
这个类,handshake()
的实现为
// Handshake
return delegate.httpEngine.hasResponse()
? delegate.httpEngine.getResponse().handshake()
: delegate.handshake;
注意,HttpsURLConnectionImpl
类聚合了 HttpURLConnectionImpl
,因此上面方法中的 delegate 也还是 HttpURLConnectionImpl
类型对象,只不过这个对象包含了 SSL 相关属性,例如
@Override public void setHostnameVerifier(HostnameVerifier hostnameVerifier) {
delegate.client.setHostnameVerifier(hostnameVerifier);
}
@Override public void setSSLSocketFactory(SSLSocketFactory sslSocketFactory) {
delegate.client.setSslSocketFactory(sslSocketFactory);
}
对象创建完成之后,就开始建立连接。HttpURLConnectionImpl
实现了 getInputStream()
方法,实际上是调用 getResponse()
返回类型为响应消息的 HttpEngine
对象。而在 getResponse()
方法中,首先调用 initHttpEngine()
初始化 HttpEngine
对象,接着循环调用 execute(true)
直到建立连接
private boolean execute(boolean readResponse) throws IOException {
boolean releaseConnection = true;
......
try {
httpEngine.sendRequest();
Connection connection = httpEngine.getConnection();
if (connection != null) {
route = connection.getRoute();
handshake = connection.getHandshake();
} else {
route = null;
handshake = null;
}
....
}
HttpEngine
的 sendRequest()
会真正调用 connect()
进行连接,这里会传入若干超时的参数,都是之前通过 OkHttpClient
设置的。
private HttpStream connect() throws RouteException, RequestException, IOException {
boolean doExtensiveHealthChecks = !networkRequest.method().equals("GET");
return streamAllocation.newStream(client.getConnectTimeout(),
client.getReadTimeout(), client.getWriteTimeout(),
client.getRetryOnConnectionFailure(),
doExtensiveHealthChecks);
}
这里的 streamAllocation
是 StreamAllocation
类型对象,newStream()
方法中去建立真正的连接,返回 HttpStream
类型对象。
public HttpStream newStream(int connectTimeout, int readTimeout, int writeTimeout,
boolean connectionRetryEnabled, boolean doExtensiveHealthChecks)
throws RouteException, IOException {
try {
RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
writeTimeout, connectionRetryEnabled, doExtensiveHealthChecks);
......
}
......
}
这里是循环判断连接是否 healthy
private RealConnection findHealthyConnection(int connectTimeout, int readTimeout,
int writeTimeout, boolean connectionRetryEnabled,
boolean doExtensiveHealthChecks)
throws IOException, RouteException {
while (true) {
RealConnection candidate = findConnection(connectTimeout, readTimeout, writeTimeout,
connectionRetryEnabled);
// If this is a brand new connection, we can skip the extensive health checks.
synchronized (connectionPool) {
if (candidate.streamCount == 0) {
return candidate;
}
}
// Otherwise do a potentially-slow check to confirm that the pooled connection is still good.
if (candidate.isHealthy(doExtensiveHealthChecks)) {
return candidate;
}
connectionFailed();
}
}
findConnection()
才是真正获取连接的逻辑,如果成员变量 connection
不为空直接返回,否则从 connectionPool
里面获取对应 address 的连接,如果还为空,那么就新建一个连接并放到 connectionPool
里面,最后终于走到连接的封装类 RealConnection
去调用 connect()
private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,
boolean connectionRetryEnabled) throws IOException, RouteException {
synchronized (connectionPool) {
......
RealConnection allocatedConnection = this.connection;
if (allocatedConnection != null && !allocatedConnection.noNewStreams) {
return allocatedConnection;
}
// Attempt to get a connection from the pool.
RealConnection pooledConnection = Internal.instance.get(connectionPool, address, this);
if (pooledConnection != null) {
this.connection = pooledConnection;
return pooledConnection;
}
// Attempt to create a connection.
if (routeSelector == null) {
routeSelector = new RouteSelector(address, routeDatabase());
}
}
Route route = routeSelector.next();
RealConnection newConnection = new RealConnection(route);
acquire(newConnection);
synchronized (connectionPool) {
Internal.instance.put(connectionPool, newConnection);
this.connection = newConnection;
if (canceled) throw new IOException("Canceled");
}
newConnection.connect(connectTimeout, readTimeout, writeTimeout, address.getConnectionSpecs(),
connectionRetryEnabled);
routeDatabase().connected(newConnection.getRoute());
return newConnection;
}
connect()
会调用 connectSocket()
,然后判断如果设置了 SSLSocketFactory
,就会继续调用 connectTls()
建立安全的连接。
public void connect(int connectTimeout, int readTimeout, int writeTimeout,
List<ConnectionSpec> connectionSpecs