安装证书相关流程
@[证书|KeyStore]
通过上层一系列跳转到该函数
0x01 : 函数入口
startInstallActivity(String mimeType, Uri uri)
参数: uri 为证书路径,mimeType 根据 uri 获取文件 mimeType 说明: 将文件读入到流(可以直接读取网络文件),传给下面函数
startInstallActivity(String mimeType, byte[] value)
参数: mimeType 为证书类型,value 为证书字节流
说明: 判断证书类型并构造 intent 传给 CertInstaller
证书类型:
- application/x-pkcs12
- application/x-x509-ca-cert
- application/x-x509-user-cert
- application/x-x509-server-cert
- application/x-pem-file
- application/pkix-cert
以上证书类型区别主要在于:
- pkcs12 含有私钥,同时可以有公钥,有口令保护
- x509 公钥证书,只有公钥
Intent intent = new Intent(this, CertInstaller.class);
if ("application/x-pkcs12".equals(mimeType)) {
intent.putExtra(KeyChain.EXTRA_PKCS12, value);
startActivityForResult(intent, REQUEST_INSTALL);
} else if ("application/x-x509-ca-cert".equals(mimeType)
|| "application/x-x509-user-cert".equals(mimeType)
|| "application/x-x509-server-cert".equals(mimeType)
|| "application/x-pem-file".equals(mimeType)
|| "application/pkix-cert".equals(mimeType)) {
intent.putExtra(KeyChain.EXTRA_CERTIFICATE, value);
startActivityForResult(intent, REQUEST_INSTALL);
} else {
Log.e(TAG, "Failed to read certificate");
Toast.makeText(this, R.string.invalid_cert, Toast.LENGTH_SHORT).show();
finish();
}
CertInstaller
-
CertInstaller 接收到 REQUEST_INSTALL 后开始进行各种初始化,通过 createCredentialHelper 对象对传入的证书字节流开始解析,我们查看了CredentialHelper 对象结构,可以看到安装证书时需要用到的基础数据结构有:
struct { HashMap<String, byte[]> mBundle; // 证书其他条目保持到改哈希表 String mName; // 证书名称 int mUid; // 安装UID(默认值 -1) PrivateKey mUserKey; // 用户私匙 X509Certificate mUserCert; // CA 用户证书 List<X509Certificate> mCaCerts; // CA 证书 byte[] mWapiAsCert; // WAPI 证书 byte[] mWapiUserCert;// WAPI 用户证书 } CredentialHelper;
-
通过 parseCert 函数进一步解析证书内容
parseCert(getData(KeyChain.EXTRA_CERTIFICATE));
说明: 通过 CertificateFactory 类来解析证书内容
-
通过 CredentialHelper 预加载证书数据后,做一些常规验证,比如:判断证书内容是否全部处理完、判断证书是否含有 pkcs12 密钥(另外流程)、判断状态,其中状态为当前用户 KeyStore 的状态:
public enum State { UNLOCKED, LOCKED, UNINITIALIZED };
如果不是UNLOCKED状态则弹出锁屏解锁界面。
-
解锁通过后开始执行 installOthers(),做两件事情:
- 查找私匙并设置它 // mCredentials.setPrivateKey(privatekey);
- 弹出命名的对话框 // nameCredential();
0x02 : 开始安装
-
点击命名对话框的确定后,开始带上前面解析的数据并构建 intent 发给 com.android.settings.CredentialStorage
Intent createSystemInstallIntent() { Intent intent = new Intent("com.android.credentials.INSTALL"); // To prevent the private key from being sniffed, we explicitly spell // out the intent receiver class. intent.setClassName("com.android.settings", "com.android.settings.CredentialStorage"); intent.putExtra(Credentials.EXTRA_INSTALL_AS_UID, mUid); try { if (mUserKey != null) { intent.putExtra(Credentials.EXTRA_USER_PRIVATE_KEY_NAME, Credentials.USER_PRIVATE_KEY + mName); intent.putExtra(Credentials.EXTRA_USER_PRIVATE_KEY_DATA, mUserKey.getEncoded()); } if (mUserCert != null) { intent.putExtra(Credentials.EXTRA_USER_CERTIFICATE_NAME, Credentials.USER_CERTIFICATE + mName); intent.putExtra(Credentials.EXTRA_USER_CERTIFICATE_DATA, Credentials.convertToPem(mUserCert)); } if (!mCaCerts.isEmpty()) { intent.putExtra(Credentials.EXTRA_CA_CERTIFICATES_NAME, Credentials.CA_CERTIFICATE + mName); X509Certificate[] caCerts = mCaCerts.toArray(new X509Certificate[mCaCerts.size()]); intent.putExtra(Credentials.EXTRA_CA_CERTIFICATES_DATA, Credentials.convertToPem(caCerts)); } //Broadcom, WAPI if (mWapiAsCert != null) { /* intent.putExtra(Credentials.WAPI_AS_CERTIFICATE + mName, mWapiAsCert); */ intent.putExtra(Credentials.EXTRA_WAPI_AS_CERTIFICATES_NAME, Credentials.WAPI_AS_CERTIFICATE + mName); intent.putExtra(Credentials.EXTRA_WAPI_AS_CERTIFICATES_DATA, mWapiAsCert); } if (mWapiUserCert != null) { /* intent.putExtra(Credentials.WAPI_USER_CERTIFICATE + mName, mWapiUserCert); */ intent.putExtra(Credentials.EXTRA_WAPI_USER_CERTIFICATES_NAME, Credentials.WAPI_USER_CERTIFICATE + mName); intent.putExtra(Credentials.EXTRA_WAPI_USER_CERTIFICATES_DATA, mWapiUserCert); } //Broadcom, WAPI return intent; } catch (IOException e) { throw new AssertionError(e); } catch (CertificateEncodingException e) { throw new AssertionError(e); } }
-
CredentialStorage 开始处理数据
- handleUnlockOrInstall 再次检查状态,mKeyStore.state() 不是 UNLOCKED 再弹出解锁界面
- installIfAvailable 准备安装了:
- 取出前面的 uid
- 构造 flags 值 int flags = (uid == Process.WIFI_UID) ? KeyStore.FLAG_NONE : KeyStore.FLAG_ENCRYPTED;
- 取出证书内容 byte[] caListData = bundle.getByteArray(Credentials.EXTRA_WAPI_AS_CERTIFICATES_DATA);
- 通过 KeyStore 服务来安装 mKeyStore.put(caListName, caListData, uid, flags)
小结:到此为止上面代码为上层处理证书内容和准备数据的全部流程。
0x03 : Binder 机制与 KeyStore.cpp 通信
-
通过 IKeystoreService 与 KeyStore.cpp 通信
int32_t insert(const String16& name, const uint8_t* item, size_t itemLength, int targetUid, int32_t flags);
该函数为 KeyStore.cpp 里的 insert 函数, mKeyStore.put 调用的就是该函数:
- name 为证书名称(固定值)
- item 为证书 EXTRA_WAPI_AS_CERTIFICATES_DATA 段内容
- itemLength 其实是 uid
准备好参数后,检查是否有 P_INSERT 权限并 KeyStore 获取状态, 准备写入数据,KeyStore 里的元数据是 Blob,把上面的参数构造到 KeyBlob 对象里,然后调用 writeBlob 方法,写入数据
最后,writeBlob 方法通过AES加密算法把东西加密后写到文件里去了,以上就是Android在存储私有数据的流程,读取则反之。
0x04 : 那么问题来了
- 为什么安装证书的时候要设置锁屏密码呢?哪是因为 AES 在加密的时候会用到一个 Key 这个 Key 一般就是我们的锁屏密码,那么回到一开始的问题上:“安装证书的时候能否绕过锁屏密码呢?” 目前答案是否定的,如果直接修改代码绕过去后,对手机的信息安全会有造成较大风险。哪这个问题怎么解决呢?要么等 android 加入指纹解锁验证等相关机制,要么我们自己开发一套和 android 并存的密保机制。比如,安装证书不存入 KeyStore 存入我们自己的加密文件,在使用证书的时候从我们自己的加密文件里取证书,该机制和现有的指纹存储类似。风险可控。
PS:支付宝钱包已经加入指纹支付相关的功能了,我们是不是也要加把劲了。