Android的證書驗證過程

說明:本文分析源碼基於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 目錄
ca-certificates文件均爲 PEM 格式的 X509 證書格式,包含明文base64編碼公鑰,證書信息,哈希等

0x02 證書管理類

Android系統提供系列類用於根證書管理,位於 /frameworks/base/core/java/android/security/net/config 目錄

CertificateSource
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() 用於判斷證書是否被移除,SystemCertificateSourceUserCertificateSource 分別定義了系統和用戶根證書庫的路徑,並實現抽象方法

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 類看起。
首先給出網絡相關類的繼承關係圖:
HttpURLConnection
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);
}

這裏的 streamAllocationStreamAllocation​ 類型對象,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, boolean connectionRetryEnabled) throws RouteException {
    ......
    while (protocol == null) {
      try {
        rawSocket = proxy.type() == Proxy.Type.DIRECT || proxy.type() == Proxy.Type.HTTP
            ? address.getSocketFactory().createSocket()
            : new Socket(proxy);
        connectSocket(connectTimeout, readTimeout, writeTimeout, connectionSpecSelector);
      ......
    }
  }
  
  private void connectSocket(int connectTimeout, int readTimeout, int writeTimeout,
      ConnectionSpecSelector connectionSpecSelector) throws IOException {
    ......
    if (route.getAddress().getSslSocketFactory() != null) {
      connectTls(readTimeout, writeTimeout, connectionSpecSelector);
    } else {
      protocol = Protocol.HTTP_1_1;
      socket = rawSocket;
    }
    ......
  }

重點看 connectTls() 這個方法,首先是創建 socket 連接,然後配置安全的選項如加密算法、TLS版本等,然後開始握手和證書驗證,可以分爲這三個步驟:

  1. startHandshake(),握手,驗證證書合法性,判斷是否由合法的CA簽發,需要維護可靠的根證書庫;
  2. getHostnameVerifier().verify(),驗證域名匹配性,判斷服務端證書是否爲特定域名簽發,驗證網站身份,這裏如果出錯就會拋出 SSLPeerUnverifiedException 的異常;
  3. getCertificatePinner().check(),驗證證書綁定,判斷特定域名的證書與特定的簽發者綁定,解決證書籤發機構過多的問題
private void connectTls(int readTimeout, int writeTimeout,
                        ConnectionSpecSelector connectionSpecSelector) throws IOException {
  ......
    Address address = route.getAddress();
  SSLSocketFactory sslSocketFactory = address.getSslSocketFactory();
  try {
    // Create the wrapper over the connected socket.
    sslSocket = (SSLSocket) sslSocketFactory.createSocket(
      rawSocket, address.getUriHost(), address.getUriPort(), true /* autoClose */);

    // Configure the socket's ciphers, TLS versions, and extensions.
    ConnectionSpec connectionSpec = connectionSpecSelector.configureSecureSocket(sslSocket);
    if (connectionSpec.supportsTlsExtensions()) {
      Platform.get().configureTlsExtensions(
        sslSocket, address.getUriHost(), address.getProtocols());
    }

    // 1. 握手驗證證書
    // Force handshake. This can throw!
    sslSocket.startHandshake();
    Handshake unverifiedHandshake = Handshake.get(sslSocket.getSession());

    // 2. 驗證主機名
    // Verify that the socket's certificates are acceptable for the target host.
    if (!address.getHostnameVerifier().verify(address.getUriHost(), sslSocket.getSession())) {
      X509Certificate cert = (X509Certificate) unverifiedHandshake.peerCertificates().get(0);
      throw new SSLPeerUnverifiedException("Hostname " + address.getUriHost() + " not verified:"
                                           + "\n    certificate: " + CertificatePinner.pin(cert)
                                           + "\n    DN: " + cert.getSubjectDN().getName()
                                           + "\n    subjectAltNames: " + OkHostnameVerifier.allSubjectAltNames(cert));
    }

    // 3. 驗證證書綁定
    // Check that the certificate pinner is satisfied by the certificates presented.
    if (address.getCertificatePinner() != CertificatePinner.DEFAULT) {
      TrustRootIndex trustRootIndex = trustRootIndex(address.getSslSocketFactory());
      List<Certificate> certificates = new CertificateChainCleaner(trustRootIndex)
        .clean(unverifiedHandshake.peerCertificates());
      address.getCertificatePinner().check(address.getUriHost(), certificates);
    }
    ......
  }
}

完成了這三步的驗證,安全的連接就建立完成了。

3.2 安全連接的相關類

從上面的分析可以看到,進行證書驗證都依賴一個 address 的對象實例,通過這個對象分別獲取相關驗證類,這些對象都是構造 Address 類對象時傳入的參數。

// 獲取 SSLSocketFactory
address.getSslSocketFactory()
// 獲取 HostnameVerifier
address.getHostnameVerifier()
// 獲取 CertificatePinner
address.getCertificatePinner()

address 則是在構造 HttpEngine 對象時創建

this.streamAllocation = streamAllocation != null
        ? streamAllocation
        : new StreamAllocation(client.getConnectionPool(), createAddress(client, request));

構造過程則依賴之前的 delegate.client 對象

private static Address createAddress(OkHttpClient client, Request request) {
    SSLSocketFactory sslSocketFactory = null;
    HostnameVerifier hostnameVerifier = null;
    CertificatePinner certificatePinner = null;
    if (request.isHttps()) {
      sslSocketFactory = client.getSslSocketFactory();
      hostnameVerifier = client.getHostnameVerifier();
      certificatePinner = client.getCertificatePinner();
    }

    return new Address(request.httpUrl().host(), request.httpUrl().port(), client.getDns(),
        client.getSocketFactory(), sslSocketFactory, hostnameVerifier, certificatePinner,
        client.getAuthenticator(), client.getProxy(), client.getProtocols(),
        client.getConnectionSpecs(), client.getProxySelector());
  }

所以這裏分別獲取的類如果不做重新定義,都是最開始 HttpsURLConnection​ 中設置的默認類型,HostnameVerifiercom.android.okhttp.internal.tls.OkHostnameVerifierSSLSocketFactorySSLSocketFactory.getDefault() 獲取的

public static synchronized SocketFactory getDefault() {
  SSLSocketFactory previousDefaultSocketFactory = defaultSocketFactory;
  defaultSocketFactory = null;

  String clsName = getSecurityProperty("ssl.SocketFactory.provider");

  if (clsName != null) {
    if (previousDefaultSocketFactory != null
        && clsName.equals(previousDefaultSocketFactory.getClass().getName())) {
      defaultSocketFactory = previousDefaultSocketFactory;
      return defaultSocketFactory;
    }
    log("setting up default SSLSocketFactory");
    try {
      Class<?> cls = null;
      try {
        cls = Class.forName(clsName);
      } catch (ClassNotFoundException e) {
        ......
      }
      log("class " + clsName + " is loaded");
      SSLSocketFactory fac = (SSLSocketFactory)cls.newInstance();
      log("instantiated an instance of class " + clsName);
      defaultSocketFactory = fac;
      return fac;
    } catch (Exception e) {
      log("SSLSocketFactory instantiation failed: " + e.toString());
      // Android-changed: Fallback to the default SSLContext on exception.
    }
  }

  try {
    // Android-changed: Allow for {@code null} SSLContext.getDefault.
    SSLContext context = SSLContext.getDefault();
    if (context != null) {
      defaultSocketFactory = context.getSocketFactory();
    } else {
      defaultSocketFactory = new DefaultSSLSocketFactory(new IllegalStateException("No factory found."));
    }
    return defaultSocketFactory;
  } catch (NoSuchAlgorithmException e) {
    return new DefaultSSLSocketFactory(e);
  }
}

這部分代碼的邏輯是:

  1. 如果之前有 SSLSocketFactory 對象則直接返回,如果沒有,根據 ssl.SocketFactory.provider 系統變量獲取類名加載並初始化,這裏指向的類名爲 com.android.org.conscrypt.OpenSSLSocketFactoryImpl
  2. 如果類名找不到先獲取默認的 SSLContext,然後通過 getSocketFactory() 獲取
public final SSLSocketFactory getSocketFactory() {
		return contextSpi.engineGetSocketFactory();
}

SSLContextSpi​ 是一個抽象類,實例是 SSLContext 初始化的時候基於傳入的 provider 生成,這裏的 provider 又是系統遍歷所有註冊的 security provider list 得到,暫時不管

  1. 如果 SSLContext 也爲空,就調用 DefaultSSLSocketFactory() 構造,這個類沒做實現,也不用管

因此主要看 OpenSSLSocketFactoryImpl​createSocket() 方法

@Override
public Socket createSocket(Socket socket, String hostname, int port, boolean autoClose) throws IOException {
		Preconditions.checkNotNull(socket, "socket");
		if (!socket.isConnected()) {
      	throw new SocketException("Socket is not connected.");
    }
  
  	if (hasFileDescriptor(socket) && !useEngineSocket) {
      	return new ConscryptFileDescriptorSocket(socket, hostname, port, autoClose, 	(SSLParametersImpl) sslParameters.clone());
    } else {
      	return new ConscryptEngineSocket(socket, hostname, port, autoClose, (SSLParametersImpl) sslParameters.clone());
    }
}

具體返回 ConscryptFileDescriptorSocke 或者 ConscryptEngineSocket​,由 socket 是否具有文件描述符以及一個默認配置決定

對於 CertificatePinner,系統不做默認定義

至此,我們就分別針對 3.1 裏面的三步獲取了完成對應工作的類

3.3 握手的過程

建立安全連接的握手過程在Java標準庫 SSLSocket 中定義,源碼位於 /libcore/ojluni/src/main/java/javax/net/ssl/ 目錄下,這個類在 socket 通信的基礎上封裝了 SSL 的功能,提供了包括 startHandshake() 在內的抽象接口,具體的實現則是由 sslSocketFactory 對象調用 createSocket() 創建的類完成

對於 ConscryptFileDescriptorSocket,握手前會進行一系列 ssl 和會話的初始化,然後調用 ssl 的 doHandshake()

@Override
public void startHandshake() throws IOException {
  ......
    try {
      ......
      ssl.initialize(getHostname(), channelIdPrivateKey);
      ......
      ssl.doHandshake(Platform.getFileDescriptor(socket), getSoTimeout());
    } catch (CertificateException e) {
      ......
    }
  ......
  }
}

對於 ConscryptEngineSocket,握手的實現爲,調用 enginebeginHandshake(),然後根據不同的狀態進行相應的處理,這裏的 engine 是一個 ConscryptEngine 類對象

@Override
public void startHandshake() throws IOException {
  ......
  try {
    synchronized (handshakeLock) {
      synchronized (stateLock) {
        // Initialize the handshake if we haven't already.
        if (state == STATE_NEW) {
          state = STATE_HANDSHAKE_STARTED;
          engine.beginHandshake();
          in = new SSLInputStream();
          out = new SSLOutputStream();
          ......
        }
      }

      boolean finished = false;
      while (!finished) {
        switch (engine.getHandshakeStatus()) {
          ......
          }
        }
      }
    }
  ......
}

ConscryptEnginebeginHandshake() 會繼續調用 beginHandshakeInternal(),這個函數裏面同樣會先進行 ssl 和會話的初始化,然後調用 handshake()

private void beginHandshakeInternal() throws SSLException {
    ......
    try {
        ssl.initialize(getHostname(), channelIdPrivateKey);
      	......
        handshake();
        ......
    }
  	......
}

handshake() 裏面也最終會調用到 ssl 的 doHandshake()

private SSLEngineResult.HandshakeStatus handshake() throws SSLException {
  	try {
      	try {
          	......
            int ssl_error_code = ssl.doHandshake();
          	......
        }
      	// The handshake has completed successfully...
      	// Update the session from the current state of the SSL object.
      	sslSession.onSessionEstablished(getPeerHost(), getPeerPort());
      
      	finishHandshake();
      	return FINISHED;
    } catch (Exception e) {
      	throw toSSLHandshakeException(e);
    }
}

這樣前面提到的兩個類最終都調用到了 ssl 的 doHandshake()

看下這裏的ssl,其實是一個 SslWrapper 類對象,前面不管是通過哪種方式調用 doHandshake(),最終都會走到 NativeCrypto 類。NativeCrypto 相當於一個 OpenSSL 底層實現對 Java 層的接口類,兩個 do_handshake 方法都是在 native 層實現的,最終都會調用到底層的 SSL_do_handshake(ssl) 方法進行具體的握手操作。

void doHandshake(FileDescriptor fd, int timeoutMillis)
        throws CertificateException, SocketTimeoutException, SSLException {
    NativeCrypto.SSL_do_handshake(ssl, fd, handshakeCallbacks, timeoutMillis);
}

int doHandshake() throws IOException {
    return NativeCrypto.ENGINE_SSL_do_handshake(ssl, handshakeCallbacks);
}
3.4 證書驗證的過程(上)

對於握手過程中的證書驗證部分,兩個方法都會傳入一個 SSLHandshakeCallbacks 類對象,這是一個回調接口類,在 NativeCrypto 中,定義了一系列與native層 OpenSSL 握手相關的回調方法,其中 verifyCertificateChain() 方法就是與服務端證書驗證相關的回調

interface SSLHandshakeCallbacks {
  // Verify that we trust the certificate chain is trusted.
  void verifyCertificateChain(long[] certificateChainRefs, String authMethod)
    throws CertificateException;

  // Called on an SSL client when the server requests (or requires a certificate).
  void clientCertificateRequested(byte[] keyTypes, byte[][] asn1DerEncodedX500Principals)
    throws CertificateEncodingException, SSLException;

  // Gets the key to be used in client mode for this connection in Pre-Shared Key (PSK) key exchange.
  int clientPSKKeyRequested(String identityHint, byte[] identity, byte[] key);

  // Gets the key to be used in server mode for this connection in Pre-Shared Key (PSK) key exchange.
  int serverPSKKeyRequested(String identityHint, String identity, byte[] key);

  // Called when SSL state changes. This could be handshake completion.
  void onSSLStateChange(int type, int val);

  // Called when a new session has been established and may be added to the session cache.
  void onNewSessionEstablished(long sslSessionNativePtr);

  // Called for servers where TLS < 1.3 (TLS 1.3 uses session tickets rather than application session caches).
  long serverSessionRequested(byte[] id);
}

找到傳入的 SSLHandshakeCallbacks 類型參數,是 SslWrapper 實例化的時候傳入並賦值

static SslWrapper newInstance(SSLParametersImpl parameters,
                              SSLHandshakeCallbacks handshakeCallbacks, AliasChooser chooser,
                              PSKCallbacks pskCallbacks) throws SSLException {
  long ctx = parameters.getSessionContext().sslCtxNativePointer;
  long ssl = NativeCrypto.SSL_new(ctx);
  return new SslWrapper(ssl, parameters, handshakeCallbacks, chooser, pskCallbacks);
}

再回過頭看 ConscryptFileDescriptorSocketConscryptEngine 都是通過 this.ssl = newSsl(sslParameters, this) 來初始化 SslWrapper 類對象,具體實現也幾乎一樣,而傳入的 SSLHandshakeCallbacks 對象都是各自類對象,那麼對應的回調實現也分別在兩個類裏面

private static SslWrapper newSsl(SSLParametersImpl sslParameters,
                                 ConscryptFileDescriptorSocket engine) {
  try {
    return SslWrapper.newInstance(sslParameters, engine, engine, engine);
  } catch (SSLException e) {
    throw new RuntimeException(e);
  }
}
private static SslWrapper newSsl(SSLParametersImpl sslParameters, ConscryptEngine engine) {
  try {
    return SslWrapper.newInstance(sslParameters, engine, engine, engine);
  } catch (SSLException e) {
    throw new RuntimeException(e);
  }
}

看兩個類的回調,實現邏輯幾乎一樣,主要是這幾步:

  1. 獲取 X509TrustManager 對象
  2. 構造 OpenSSLX509Certificate 證書鏈
  3. 更新會話中的證書信息
  4. 執行服務器/客戶端證書驗證
@Override
public void verifyCertificateChain(long[] certRefs, String authMethod)
  throws CertificateException {
  try {
    X509TrustManager x509tm = sslParameters.getX509TrustManager();
    ......
    OpenSSLX509Certificate[] peerCertChain = OpenSSLX509Certificate.createCertChain(certRefs);
    
    sslSession.onPeerCertificatesReceived(getHostnameOrIP(), getPort(), peerCertChain);
    
    if (getUseClientMode()) {
      Platform.checkServerTrusted(x509tm, peerCertChain, authMethod, this);
    } else {
      String authType = peerCertChain[0].getPublicKey().getAlgorithm();
      Platform.checkClientTrusted(x509tm, peerCertChain, authType, this);
    }
  }
  ......
}

SSLParametersImpl 這個類定義了一系列握手階段的參數,並完成證書驗證相關類的初始化,包括這裏的 X509TrustManagersslParameters 對象是從最開始 OpenSSLSocketFactoryImpl 構造時定義好然後一層層傳下來

OpenSSLSocketFactoryImpl() {
        SSLParametersImpl sslParametersLocal = null;
        IOException instantiationExceptionLocal = null;
        try {
            sslParametersLocal = SSLParametersImpl.getDefault();
        } catch (KeyManagementException e) {
            instantiationExceptionLocal = new IOException("Delayed instantiation exception:", e);
        }
        this.sslParameters = sslParametersLocal;
        this.instantiationException = instantiationExceptionLocal;
    }

看 SSL 參數實現類的初始化過程,主要是完成這麼幾個類的初始化:X509KeyManagerPSKKeyManagerX509TrustManagersecureRandom,爲了看證書驗證過程重點關注 X509TrustManager 這個類

static SSLParametersImpl getDefault() throws KeyManagementException {
    SSLParametersImpl result = defaultParameters;
    if (result == null) {
        // single-check idiom
        defaultParameters = result = new SSLParametersImpl(null,
                                                           null,
                                                           null,
                                                           new ClientSessionContext(),
                                                           new ServerSessionContext(),
                                                           null);
    }
    return (SSLParametersImpl) result.clone();
}

SSLParametersImpl(KeyManager[] kms, TrustManager[] tms,
                  SecureRandom sr, ClientSessionContext clientSessionContext,
                  ServerSessionContext serverSessionContext, String[] protocols)
    throws KeyManagementException {
    this.serverSessionContext = serverSessionContext;
    this.clientSessionContext = clientSessionContext;

    // initialize key managers
    if (kms == null) {
      x509KeyManager = getDefaultX509KeyManager();
      // There's no default PSK key manager
      pskKeyManager = null;
    } else {
      x509KeyManager = findFirstX509KeyManager(kms);
      pskKeyManager = findFirstPSKKeyManager(kms);
    }

    // initialize x509TrustManager
    if (tms == null) {
      x509TrustManager = getDefaultX509TrustManager();
    } else {
      x509TrustManager = findFirstX509TrustManager(tms);
    }

    // initialize secure random
    secureRandom = sr;

    // initialize the list of cipher suites and protocols enabled by default
    enabledProtocols = NativeCrypto.checkEnabledProtocols(
      protocols == null ? NativeCrypto.DEFAULT_PROTOCOLS : protocols).clone();
    boolean x509CipherSuitesNeeded = (x509KeyManager != null) || (x509TrustManager != null);
    boolean pskCipherSuitesNeeded = pskKeyManager != null;
    enabledCipherSuites = getDefaultCipherSuites(
      x509CipherSuitesNeeded, pskCipherSuitesNeeded);
}

X509TrustManager 類實例是通過 getDefaultX509TrustManager() 函數來創建的。在這個函數裏會首先創建一個默認的 TrustManagerFactory 實例,init() 初始化後調用 getTrustManagers() 獲取所有的 TrustManager 數組,然後遍歷數組中的元素返回第一個 X509TrustManager 類型對象。

static X509TrustManager getDefaultX509TrustManager() throws KeyManagementException {
  	X509TrustManager result = defaultX509TrustManager;
  	if (result == null) {
    		// single-check idiom
		    defaultX509TrustManager = result = createDefaultX509TrustManager();
  	}
	  return result;
}

private static X509TrustManager createDefaultX509TrustManager()
        throws KeyManagementException {
    try {
        String algorithm = TrustManagerFactory.getDefaultAlgorithm();
        TrustManagerFactory tmf = TrustManagerFactory.getInstance(algorithm);
        tmf.init((KeyStore) null);
        TrustManager[] tms = tmf.getTrustManagers();
        X509TrustManager trustManager = findFirstX509TrustManager(tms);
        ......
        return trustManager;
    } 
  	......
}

private static X509TrustManager findFirstX509TrustManager(TrustManager[] tms) {
  	for (TrustManager tm : tms) {
      	if (tm instanceof X509TrustManager) {
          	return (X509TrustManager) tm;
        }
    }
  	return null;
}

注意這裏的初始化和獲取 trustmanager 的過程實際上是 TrustManagerFactory 的實現類通過代理的方式完成,完成相關工作的都是 TrustManagerFactorySpi

public final void init(ManagerFactoryParameters spec) throws
  InvalidAlgorithmParameterException {
  factorySpi.engineInit(spec);
}

public final TrustManager[] getTrustManagers() {
  return factorySpi.engineGetTrustManagers();
}

TrustManagerFactorySpi 是一個抽象類,真正的實現類是 TrustManagerFactoryImpl,初始化過程實例化了 AndroidCAStore 類型對象並完成加載,在獲取 trustmanager 階段則構造了一個新的 TrustManagerImpl 對象,這就是我們最開始要獲取的 X509TrustManager 類型對象

@Override
public void engineInit(KeyStore ks) throws KeyStoreException {
  	......
    keyStore = KeyStore.getInstance("AndroidCAStore");
    try {
      keyStore.load(null, null);
    }
  	......
}

@Override
public TrustManager[] engineGetTrustManagers() {
  if (keyStore == null) {
    throw new IllegalStateException(
      "TrustManagerFactory is not initialized");
  }
  return new TrustManager[] { new TrustManagerImpl(keyStore) };
}

接着看證書鏈的生成,實際上是根據傳入的 long 類型數據構造 OpenSSLX509Certificate 類型的證書鏈

static OpenSSLX509Certificate[] createCertChain(long[] certificateRefs) {
  if (certificateRefs == null) {
    return null;
  }
  OpenSSLX509Certificate[] certificates = new OpenSSLX509Certificate[certificateRefs.length];
  for (int i = 0; i < certificateRefs.length; i++) {
    certificates[i] = new OpenSSLX509Certificate(certificateRefs[i]);
  }
  return certificates;
}

最後是證書驗證,Platform 這個類針對平臺有多個實現,但是這部分的驗證邏輯是類似的,看 /external/conscrypt/android/src/main/java/org/conscrypt/ 目錄下的 Platform 類,做一個函數調用判斷後通過執行 X509TrustManager 對象的 checkServerTrusted(chain, authType) 來驗證證書

public static void checkServerTrusted(X509TrustManager tm, X509Certificate[] chain,
                                      String authType, AbstractConscryptSocket socket) throws CertificateException {
  if (!checkTrusted("checkServerTrusted", tm, chain, authType, Socket.class, socket)
      && !checkTrusted("checkServerTrusted", tm, chain, authType, String.class,
                       socket.getHandshakeSession().getPeerHost())) {
    tm.checkServerTrusted(chain, authType);
  }
}

public static void checkServerTrusted(X509TrustManager tm, X509Certificate[] chain,
                                      String authType, ConscryptEngine engine) throws CertificateException {
  if (!checkTrusted("checkServerTrusted", tm, chain, authType, SSLEngine.class, engine)
      && !checkTrusted("checkServerTrusted", tm, chain, authType, String.class,
                       engine.getHandshakeSession().getPeerHost())) {
    tm.checkServerTrusted(chain, authType);
  }
}

這就是爲什麼我們自己實現證書驗證過程時,總是自己實現一個 X509TrustManager 並重寫裏面的 checkServerTrusted(...) 方法來完成驗證

3.5 證書驗證的過程(下)

繼續看 X509TrustManager 的真正實現類 TrustManagerImpl,網上大多數文章的分析也都從這裏開始。checkServerTrusted(...) 方法會依次調用幾個 checkTrusted(...),期間做一些參數判斷和構造,看最後一次調用的邏輯,這裏會維護三個證書鏈式結構,一個已經使用過的哈希集合 used,一個不受信的證書鏈 untrustedChain 和一個受信的證書鏈 trustedChain。首先,通過 findTrustAnchorBySubjectAndPublicKey(leaf) 判斷傳入的證書鏈是否受信,並標記爲 used,受信則加入 trustedChain,否則加入 untrustedChain,然後調用 checkTrustedRecursive(...) 進行遞歸遍歷檢查

public void checkServerTrusted(X509Certificate[] chain, String authType)
  throws CertificateException {
  checkTrusted(chain, authType, null, null, false /* client auth */);
}

private List<X509Certificate> checkTrusted(X509Certificate[] certs, String authType, SSLSession session, SSLParameters parameters, boolean clientAuth) throws CertificateException {
  ......
  return checkTrusted(certs, ocspData, tlsSctData, authType, hostname, clientAuth);
}

private List<X509Certificate> checkTrusted(X509Certificate[] certs, byte[] ocspData, byte[] tlsSctData, String authType, String host, boolean clientAuth) throws CertificateException {
  ......
  Set<X509Certificate> used = new HashSet<X509Certificate>();
  ArrayList<X509Certificate> untrustedChain = new ArrayList<X509Certificate>();
  ArrayList<TrustAnchor> trustedChain = new ArrayList<TrustAnchor>();
  X509Certificate leaf = certs[0];
  TrustAnchor leafAsAnchor = findTrustAnchorBySubjectAndPublicKey(leaf);
  if (leafAsAnchor != null) {
    trustedChain.add(leafAsAnchor);
    used.add(leafAsAnchor.getTrustedCert());
  } else {
    untrustedChain.add(leaf);
  }
  used.add(leaf);
  return checkTrustedRecursive(certs, ocspData, tlsSctData, host, clientAuth,
                               untrustedChain, trustedChain, used);
}

TrustAnchor 這個類可以理解爲一個信任機構,也即包含一個受信任的證書的所有相關信息

public class TrustAnchor {
    private final PublicKey pubKey;
    private final String caName;
    private final X500Principal caPrincipal;
    private final X509Certificate trustedCert;
    private byte[] ncBytes;
    private NameConstraintsExtension nc;
    ......
}

下面這個函數就是根據傳入的證書去系統證書列表中找有沒有對應的 CA,先去 trustedCertificateIndex 裏面找,找到直接返回,找不到再去 trustedCertificateStore 裏面找

private TrustAnchor findTrustAnchorBySubjectAndPublicKey(X509Certificate cert) {
  TrustAnchor trustAnchor = trustedCertificateIndex.findBySubjectAndPublicKey(cert);
  if (trustAnchor != null) {
    return trustAnchor;
  }
  if (trustedCertificateStore == null) {
    return null;
  }
  X509Certificate systemCert = trustedCertificateStore.getTrustAnchor(cert);
  if (systemCert != null) {
    return new TrustAnchor(systemCert, null);
  }
  return null;
}

trustedCertificateIndex 是一個 TrustedCertificateIndex 類對象,在 TrustManagerImpl 類構造時進行的初始化,TrustedCertificateStore 類型變量 trustedCertificateStore 也是在這裏初始化

public TrustManagerImpl(KeyStore keyStore, CertPinManager manager, TrustedCertificateStore
                        certStore, CertBlacklist blacklist, CTLogStore ctLogStore,
                        CTVerifier ctVerifier, CTPolicy ctPolicy) {
  ......
  try {
    ......
    if ("AndroidCAStore".equals(keyStore.getType())) {
      rootKeyStoreLocal = keyStore;
      trustedCertificateStoreLocal =
        (certStore != null) ? certStore : new TrustedCertificateStore();
      acceptedIssuersLocal = null;
      trustedCertificateIndexLocal = new TrustedCertificateIndex();
    } else {
      rootKeyStoreLocal = null;
      trustedCertificateStoreLocal = certStore;
      acceptedIssuersLocal = acceptedIssuers(keyStore);
      trustedCertificateIndexLocal
        = new TrustedCertificateIndex(trustAnchors(acceptedIssuersLocal));
    }

  }
  ......
  this.trustedCertificateStore = trustedCertificateStoreLocal;
  this.trustedCertificateIndex = trustedCertificateIndexLocal;
  ......
}

分別看下這兩個類的證書校驗過程,TrustedCertificateIndex 內部維護了一個 X500PrincipalList<TrustAnchor> 的哈希表 subjectToTrustAnchorsfindBySubjectAndPublicKey(cert) 方法首先獲取證書的主題結構(包含很多證書信息的字段),放到 X500Principal 結構中,然後在 subjectToTrustAnchors 中查找有無對應的 TrustAnchor 數組,若有,繼續調用 findBySubjectAndPublicKey(cert, anchors),獲取證書的公鑰做進一步比對,注意如果是X.509形式的公鑰需要編碼後對比

public TrustAnchor findBySubjectAndPublicKey(X509Certificate cert) {
  X500Principal subject = cert.getSubjectX500Principal();
  synchronized (subjectToTrustAnchors) {
    List<TrustAnchor> anchors = subjectToTrustAnchors.get(subject);
    if (anchors == null) {
      return null;
    }
    return findBySubjectAndPublicKey(cert, anchors);
  }
}

private static TrustAnchor findBySubjectAndPublicKey(X509Certificate cert,
                                                     Collection<TrustAnchor> anchors) {
  PublicKey certPublicKey = cert.getPublicKey();
  for (TrustAnchor anchor : anchors) {
    PublicKey caPublicKey;
    try {
      X509Certificate caCert = anchor.getTrustedCert();
      if (caCert != null) {
        caPublicKey = caCert.getPublicKey();
      } else {
        caPublicKey = anchor.getCAPublicKey();
      }
      if (caPublicKey.equals(certPublicKey)) {
        return anchor;
      } else {
        if ("X.509".equals(caPublicKey.getFormat())&&"X.509".equals(certPublicKey.getFormat())) {
          byte[] caPublicKeyEncoded = caPublicKey.getEncoded();
          byte[] certPublicKeyEncoded = certPublicKey.getEncoded();
          if (certPublicKeyEncoded != null && caPublicKeyEncoded != null
              && Arrays.equals(caPublicKeyEncoded, certPublicKeyEncoded)) {
            return anchor;
          }
        }
      }
    } catch (Exception e) {
      // can happen with unsupported public key types
    }
  }
  return null;
}

而對於 TrustedCertificateStore,則是與系統的證書庫相關,看這個類的構造函數,定義了三個系統路徑:系統證書路徑、添加的證書路徑和移除的證書路徑

private static class PreloadHolder {
  private static File defaultCaCertsSystemDir;
  private static File defaultCaCertsAddedDir;
  private static File defaultCaCertsDeletedDir;

  static {
    String ANDROID_ROOT = System.getenv("ANDROID_ROOT");
    String ANDROID_DATA = System.getenv("ANDROID_DATA");
    defaultCaCertsSystemDir = new File(ANDROID_ROOT + "/etc/security/cacerts");
    setDefaultUserDirectory(new File(ANDROID_DATA + "/misc/keychain"));
  }
}

public static void setDefaultUserDirectory(File root) {
  PreloadHolder.defaultCaCertsAddedDir = new File(root, "cacerts-added");
  PreloadHolder.defaultCaCertsDeletedDir = new File(root, "cacerts-removed");
}

public TrustedCertificateStore() {
  this(PreloadHolder.defaultCaCertsSystemDir, PreloadHolder.defaultCaCertsAddedDir,
       PreloadHolder.defaultCaCertsDeletedDir);
}

public TrustedCertificateStore(File systemDir, File addedDir, File deletedDir) {
	// /system/etc/security/cacerts
  this.systemDir = systemDir;
  // /data/misc/keychain/cacerts-added
  this.addedDir = addedDir;
  // /data/misc/keychain/cacerts-removed
  this.deletedDir = deletedDir;
}

查找證書的過程是調用 getTrustAnchor(cert),這裏會首先註冊一個證書選擇器的類 CertSelector,回調match方法會返回證書的匹配結果,然後分別去用戶添加的證書路徑 addedDir 和系統證書路徑 systemDir 查找證書(注意匹配系統證書還要求證書未被移除)

public X509Certificate getTrustAnchor(final X509Certificate c) {
  CertSelector selector = new CertSelector() {
    @Override
    public boolean match(X509Certificate ca) {
      return ca.getPublicKey().equals(c.getPublicKey());
    }
  };
  X509Certificate user = findCert(addedDir,
                                  c.getSubjectX500Principal(),
                                  selector,
                                  X509Certificate.class);
  if (user != null) {
    return user;
  }
  X509Certificate system = findCert(systemDir,
                                    c.getSubjectX500Principal(),
                                    selector,
                                    X509Certificate.class);
  if (system != null && !isDeletedSystemCertificate(system)) {
    return system;
  }
  return null;
}

繼續看 findCert(...) 方法,這是一個泛型函數所以處理起來比較複雜,但是內部邏輯很簡單,就是計算證書的哈希值構造一個形如 <hash>.<N> 的證書文件,判斷路徑下這個文件是否存在來對比,後面的都是處理各種返回

private <T> T findCert(File dir, X500Principal subject, CertSelector selector, Class<T>
                       desiredReturnType) {
  Set<X509Certificate> certs = null;
  String hash = hash(subject);
  for (int index = 0; true; index++) {
    File file = file(dir, hash, index);
    if (!file.isFile()) {
      ......
    }
    ......
    X509Certificate cert = readCertificate(file);
    if (selector.match(cert)) {
      ......
    }
  }
}

上面的這些步驟就是檢查了 certs[0] 是否爲可信證書,並添加到相應的列表

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章