深入理解Android之Java Security(第二部分,最后)
代码路径:
- Security.java:libcore/lunl/src/main/java/java/security/
- TrustedCertificateStore.java:libcore /crypto/src/main/java/org/conscrypt/
- CertInstallerMain:package/apps/CertInstaller/src/com/android/certinstaller/
- CredentialHelper:package/apps/CertInstaller/src/com/android/certinstaller/
- CertInstaller:package/apps/CertInstaller/src/com/android/certinstaller/
- CredentialStorage.java:package/apps/Settings/src/com/android/settings/
- OpenSSLRSAPrivateKey.java:libcore/crypto/src/main/java/org/conscrypt/
- OpenSSLEngine.java:libcore/crypto/src/main/java/org/conscrypt/
- OpenSSLKey.java:libcore/crypto/src/main/java/org/conscrypt/
二 Android Key/Trust Store研究
下面我们来看看Android Key/Trust Store方面的内容。这里就会大量涉及代码了.....。
根据前述内容可知,Android Key/Trust Store是系统全局的Key/Trust Store。虽然Android符合JCE/JSSE规范,但是Android平台的实现和一般PC机上的实现有很大不同。
我们先来看KeyStore的架构,如图16所示:
图16 Android KeyStore架构
图16中:
- 一个APP有两种方式和Android Keystore交互。一种是利用JCE的KeyStore接口,并强制使用“AndroidKeyStore“作为Provider的名字。这样,JCE就会创建AndroidKeyStore对象。当然,这个对象也就是个代理,它会创建另外一个KeyStore对象。这个KeyStore就是android.security.KeyStore。虽然名字一样,但是包名却不同,这个是android特有的。
- 另外一条路是使用Android提供的KeyChain API。KeyChain我觉得从“Key和CertificatesChain的意思”来理解KeyChain的命名可能会更加全面点。KeyChain会和一个叫KeyChainService的服务交互。这个KeyChainService又是运行在“keychain“进程里的。keychain进程里也会创建android.security.KeyStore对象。
- 再来看android.security.KeyStore(以后简称AS Store,而JCE里的,我们则简称JSStore)。好吧,binder无处不在。AS(AndroidSecurity) Store其实也是一个代理,它会通过binder和一个native的进程“keystore“交互。而keystore又会和硬件中的SEE(Security Element Enviroment)设备交互(ARM平台几乎就是Trust Zone了)。高通平中,SEE设备被叫做QSEE。keystore进程会加载一个名叫“libQSEEComAPI.so”的文件。
为什么要搞这么复杂?
- KeyChain其实简化了使用。通过前面的例子大家可以看到,JCE其实用起来是很麻烦的,而且还要考虑各种Provider的情况。而且,通过KeyChain API能使用系统级别的KeyStore,而且还有对应的权限管理。比如,不是某个APP都能使用特定alias的Key和Chain的。有一些需要用户确认。
- 而更重要的功能是把硬件key managment功能融合到AS Keystore里来了。这在普通的JCE中是没有的。硬件级别的KM听起来(实际上也是)应该是够安全的了:)
关于SEE和TrustZone,图17给了一个示意图:
图17 TrustZone示意图[16]
简单点看,ARM芯片上其实跑了两个系统,一个是Android系统,另外一个是安全的系统。Android系统借助指定的API才能和安全系统交互。
提示:关于TrustZone,请童鞋们参考[13],[14][15]。
言归正传,我们马上来看AS KeyStore相关的代码。如下是分析路线:
- 导入前面例子中反复提到的test-keychain.p12文件到系统里去,看看这部分的流程。
- 在示例中使用KeyChain API获取这个文件中的Private Key和CA信息。
- 删除系统中的根CA信息。这部分和Trust Store有关(请读者自行研究吧!)
2.1 p12文件导入系统流程
2.1.1 触发PKCS12文件导入
在DemoActivity.java中有一个importPKCS12函数,代码如下所示:
[-->DemoActivity.java::importPKCS12]
void importPKCS12(){
//根据Android的规定,导入证书等文件到系统的时候,直接把文件内容通过intent发过去就行
//所以我们要先打开”test-keychain.p12”文件,也就是KEYCHAIN_FILE
BufferedInputStream bis =new BufferedInputStream(getAssets().open(
KEYCHAIN_FILE));
byte[] keychain = newbyte[bis.available()];
bis.read(keychain);
/*
调用KeyChain API中的createInstallIntent。根据KeyChain.java代码,该Intent
的内容是:
action名:android.credentials.INSTALL
目标class的包名:com.android.certinstaller
目标class为com.android.certinstaller.CertInstallerMain
*/
Intent installIntent = KeyChain.createInstallIntent();
//Android支持两种证书文件格式,一种是PKCS12,一种是X.509证书
installIntent.putExtra(KeyChain.EXTRA_PKCS12, keychain);
//指定alias名,此处的ENTRY_ALIAS值为“MyKey Chain”
installIntent.putExtra(KeyChain.EXTRA_NAME,ENTRY_ALIAS);
启动目标Activity,其实就是CertInstallerMain
startActivityForResult(installIntent, 1);
}
当处理完毕后,DemoActivity的onActivityResult会被调用。那个函数里没有什么特别的处理。我们先略过。
2.1.2 CertInstallerMain的处理
唉,Android里边除了前面讲到的keychain.apk外,还有一个专门用于导入证书的certinstaller.apk。真够麻烦的。来看CertInstallerMain的代码
[-->CertInstallerMain.java::onCreate]
protected void onCreate(Bundle savedInstanceState) {
.......//启动一个线程,然后运行自己的run函数
new Thread(new Runnable(){
public void run() {
......
runOnUiThread(CertInstallerMain.this);
}
}).start();
}
[-->CertInstallerMain.java::run]
public void run() {
Intent intent = getIntent();
String action = (intent ==null) ? null : intent.getAction();
//我们发出的Intent的Action就是INSTALL_ACTION
if (Credentials.INSTALL_ACTION.equals(action)
||Credentials.INSTALL_AS_USER_ACTION.equals(action)) {
Bundle bundle =intent.getExtras();
......
if (bundle == null ||bundle.isEmpty() || (bundle.size() == 1
&&(bundle.containsKey(KeyChain.EXTRA_NAME)
||bundle.containsKey(Credentials.EXTRA_INSTALL_AS_UID)))) {
//安装方式有两种,一种是搜索SDCard/download目录下面的证书文件,凡是后缀名
//为.crt/.cet/.p12/.pfx的文件都能被自动列出来。用户可选择要安装哪个文件
} else {
//由于我们发出的Intent已经包含了证书文件内容,所以此处再启动一个Activity就好
Intent newIntent =new Intent(this, CertInstaller.class);
newIntent.putExtras(intent);
startActivityForResult(newIntent,REQUEST_INSTALL_CODE);
return;
}
} else if (Intent.ACTION_VIEW.equals(action)) {
......//除了安装,列举系统已经安装的证书文件也是通过CertInstall这个apk来完成的
}
finish();
}
2.1.3 CertInstaller的处理
(1) 安装准备
[-->CertInstaller.java::onCreate]
protected void onCreate(Bundle savedStates) {
super.onCreate(savedStates);
//CredentialHelper是一个关键类,很多操作都是在它那完成的
mCredentials = createCredentialHelper(getIntent());
mState = (savedStates ==null) ? STATE_INIT : STATE_RUNNING;
if (mState == STATE_INIT) {
if(!mCredentials.containsAnyRawData()) {
toastErrorAndFinish(R.string.no_cert_to_saved);
finish();
} else if (//判断这次的安装请求是否对应一个PKCS12文件。显然,本例符合这个条件
mCredentials.hasPkcs12KeyStore()) {
//PKCS12文件一般由密码保护,所以需要弹出一个密码输入框
showDialog(PKCS12_PASSWORD_DIALOG);
} else {
......//其他操作
}
......
}
整个CertInstaller.apk中,核心工作是交给CredentialHelper来完成的。createCredentialHelper就是构造了一个CredentialHelper对象,我们直接来看构造函数:
[-->CredentialHelper.java::CredentialHelper]
CredentialHelper(Intent intent) {
Bundle bundle =intent.getExtras();
......
//取出Alias名,本例是“My Key Chain”,保存到成员mName中
String name =bundle.getString(KeyChain.EXTRA_NAME);
bundle.remove(KeyChain.EXTRA_NAME);
if (name != null) mName= name;
//我们没有设置uid,所以mUid为-1
mUid =bundle.getInt(Credentials.EXTRA_INSTALL_AS_UID, -1);
bundle.remove(Credentials.EXTRA_INSTALL_AS_UID);
//取出bundle中其他的key-value值。除了名字,其他的key就是EXTRA_PKCS12了
for (String key :bundle.keySet()) {
byte[] bytes =bundle.getByteArray(key);
mBundle.put(key, bytes);
}
//如果bundle中包含证书内容,则解析该证书。本例没有包含证书信息
parseCert(getData(KeyChain.EXTRA_CERTIFICATE));
}
安装时候,会判断此次安装是否为PKCS12文件,如果是的话,需要弹出密码输入框来解开这个文件。判断的代码是:
[-->CredentialHelper.java::hasPkcs12KeyStore]
//判断bundle是否包含EXTRA_PKCS12参数,本例是包含的
boolean hasPkcs12KeyStore() {
returnmBundle.containsKey(KeyChain.EXTRA_PKCS12);
}
(2) 密码输入对话框处理
来看密码输入框代码,核心在
[-->CertInstaller.java:: createPkcs12PasswordDialog]
private Dialog createPkcs12PasswordDialog() {
View view =View.inflate(this, R.layout.password_dialog, null);
mView.setView(view);
......
String title =mCredentials.getName();
......
Dialog d = newAlertDialog.Builder(this).setView(view).setTitle(title)
.setPositiveButton(android.R.string.ok,
new DialogInterface.OnClickListener(){
public voidonClick(DialogInterface dialog, int id) {
Stringpassword = mView.getText(R.id.credential_password);
//构造一个Action,把密码传进去。Pkcs12Extraction其实就是调用
//CertInstaller的extractPkcs12InBackground函数
mNextAction = new Pkcs12ExtractAction(password);
mNextAction.run(CertInstaller.this);
}})....create();
...
return d;
}
图18所示为这个密码框:
图18 密码框
注意这个对话框的Title,“Extracfrom”后面跟的是Alias名。按了OK键后,代码执行Pkcs12ExtractAction的run,其内部调用就是CertInstaller的extractPkcs12InBackground。
[-->CertInstaller.java::extractPkcs12InBackground]
voidextractPkcs12InBackground(final String password) {
// show progress bar andextract certs in a background thread
showDialog(PROGRESS_BAR_DIALOG);
newAsyncTask<Void,Void,Boolean>() {
@Override protectedBoolean doInBackground(Void... unused) {
//解码PKCS12文件
return mCredentials.extractPkcs12(password);
}
@Override protected voidonPostExecute(Boolean success) {
MyAction action = new OnExtractionDoneAction(success);
if ......
else action.run(CertInstaller.this);//执行CertInstaller的onExtractionDone
}
}.execute();
}
解码PKCS12文件的核心代码在CredentialHelper的extractPkcs12函数中。这个函数其实和我们前面示例提到的testKeyStore几乎一样。
[-->CredentialHelper.java:: extractPkcs12Internal]
private boolean extractPkcs12Internal(String password) throwsException {
//下面这段代码和testKeyStore示例代码几乎一样
java.security.KeyStore keystore =
java.security.KeyStore.getInstance("PKCS12");
PasswordProtection passwordProtection =
new PasswordProtection(password.toCharArray());
keystore.load(newByteArrayInputStream(getData(KeyChain.EXTRA_PKCS12)),
passwordProtection.getPassword());
Enumeration<String>aliases = keystore.aliases();
......
while(aliases.hasMoreElements()) {
String alias =aliases.nextElement();
KeyStore.Entry entry =keystore.getEntry(alias, passwordProtection);
if (entry instanceof PrivateKeyEntry) {
//test-keychain.p12包含的就是一个PrivateKeyEntry
return installFrom((PrivateKeyEntry)entry);
}
}
return true;
}
extractPkcs12Internal看来只处理PrivateKeyEntry,核心在installFrom函数中,如下:
[-->CredentialHelper.java::installFrom]
private synchronized boolean installFrom(PrivateKeyEntry entry) {
//私钥信息
mUserKey = entry.getPrivateKey();
//证书信息
mUserCert = (X509Certificate) entry.getCertificate();
//获取证书链,然后找到其中的根证书,也就是CA证书
Certificate[] certs = entry.getCertificateChain();
Log.d(TAG, "# certsextracted = " + certs.length);
mCaCerts = newArrayList<X509Certificate>(certs.length);
for (Certificate c : certs){
X509Certificate cert =(X509Certificate) c;
//在本例中,证书链就包含一个证书,所以mUserCert和certs[0]是同一个。
//下面这个isCa函数前面没介绍过。它将解析X509Certificate信息中的
//”Basic Constraints”是否设置“Certificate Authority”为“Yes”,如果是的话
//就说明它是CA证书
if (isCa(cert)) mCaCerts.add(cert);
}
return true;
}
总之,installFrom执行完后,我们得到一个PrivateKey,一个证书,和一个CA证书。当然,这两个证书是同一个东西。
注意,JCE似乎没有提供合适的API来判断一个Certificate是不是CA证书。但是有一些实却实现了这个函数。感兴趣的童鞋可参考isCa的处理。
提取完pkcs12文件后,在onExtractoinDone里,系统又会弹出一个框,告诉你刚才那个文件里都包含什么东西。如图19所示:
图19 证书改名对话框
这个对话框其实是让我们修改别名。当然,图里还能让你设置这个私钥等是干什么用的:
- 一个是用于VPN,另外一个是用于APP。
- 图中最后展示了这个证书包含的东西,有一个key,一个证书和一个CA证书。
(3) 别名修改对话框
[-->CertInstaller.java::createNameCredentialDialog]
private Dialog createNameCredentialDialog() {
ViewGroup view =(ViewGroup) View.inflate(this,
R.layout.name_credential_dialog,null);
mView.setView(view);
......
mView.setText(R.id.credential_info,
mCredentials.getDescription(this).toString());
final EditText nameInput =(EditText)
view.findViewById(R.id.credential_name);
if ......
else {
final Spinner usageSpinner= (Spinner)
view.findViewById(R.id.credential_usage);
usageSpinner.setOnItemSelectedListener(new OnItemSelectedListener() {
@Override
public voidonItemSelected(AdapterView<?> parent, View view, int position,
long id) {
switch ((int) id) {
//图19中的VPN和APP,对应就是设置使用范畴,默认是VPN+APP,也就是系统层面都可以用
case USAGE_TYPE_SYSTEM:
mCredentials.setInstallAsUid(KeyStore.UID_SELF);//UID_SELF为-1
break;
case USAGE_TYPE_WIFI:
mCredentials.setInstallAsUid(Process.WIFI_UID);
break;
......
} }});}
nameInput.setText(getDefaultName());//获取默认alias名
nameInput.selectAll();
Dialog d = newAlertDialog.Builder(this)
.setView(view).setTitle(R.string.name_credential_dialog_title)
.setPositiveButton(android.R.string.ok, new
DialogInterface.OnClickListener(){
public voidonClick(DialogInterface dialog, int id) {
String name =mView.getText(R.id.credential_name);
if ......
else {//用户在这里有机会修改这个alias名。注意,一旦从文件中提取出证书并安装
//到系统中的话,以前那个pkcs12文件的password和alias名都不在需要了!
mCredentials.setName(name);
try {
startActivityForResult(//安装这些个key啊,证书
mCredentials.createSystemInstallIntent(),
REQUEST_SYSTEM_INSTALL_CODE);
} ...
}
}
})....create();
return d;
}
最终导入到系统KeyStore的地方其实还不是在CertInstaller里完成的,而是由Settings!怎么启动Settings呢?关键在createSystemInstallIntent中。
[-->CredentialHelper.java::createSystemInstallIntent]
Intent createSystemInstallIntent() {
Intent intent = newIntent("com.android.credentials.INSTALL");
intent.setClassName("com.android.settings",
"com.android.settings.CredentialStorage");
//mUid=-1
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));//将所有CA Certs转换为PEM格式保存
}
return intent;//返回这个Intent,用于启动Settings中的对应安装步骤!
} ...
}
我们要装三个东西到系统里,一个PrivateKey,一个证书,一个CA证书。在createSystemInstallIntent函数中,会单独为这三个东西设置一个特殊的名字:
- PrivateKey:用“Credentials.USER_PRIVATE_KEY+ mName”组合,本例中对应的名字是”USRPKEY_My KeyChain”
- Certifcate:用“Credentials.USER_CERTIFICATE+ mName”组合,本例中对应的名字是” USRCERT_My KeyChain”
- CACert:用“Credentials.CA_CERTIFICATE+mName”组合,本例中对应的名字是” CACERT_My KeyChain”。
如代码所示,最终的处理将交给Settings来完成。此时,我们已经测试和最开始的pkcs12文件没有关系了,比如打开pkcs12文件的密码,默认的alias(用户可以修改,本例中我们没有改!)。
2.1.4 Settings处理安装
Settings中相关处理代码位于CredentialStorage中,主要处理函数是handleUnlockOrInstall:
[-->CredentialStorage.java::handleUnlockOrInstall]
private void handleUnlockOrInstall() {
......
/*
mKeyStore就是传说中的AS KeyStore了。它有几个状态,最开始是UNINITIALIZED,
只有我们为它设置了密码,它才会变成LOCKED状态。这个密码和用户的锁屏界面解锁有关系
系统会把解锁时使用的密码传递给AS Store以初始化(或者修改新密码)。
LOCKED:AS Store被锁了,需要弹出解锁框来解锁
UNLOCKED:已经解锁,可直接安装
*/
switch (mKeyStore.state()) {
case UNINITIALIZED: {//未初始化,将提醒用户设置密码。
ensureKeyGuard();
return;
}
case LOCKED: {//锁了,请输入解锁密码
new UnlockDialog();
return;
}
case UNLOCKED: {//已经解锁
if(!checkKeyGuardQuality()) {
newConfigureKeyGuardDialog();
return;
}
installIfAvailable();
finish();
return;
}
}
}
installIfAvailable将进行安装。有对UNINTIALIZED流程感兴趣的童鞋不妨自己研究下相关代码。Anyway,用户设置的密码最终会调用AS KeyStore的password函数。回头我们还要来看这其中的处理。
先来看installIfAvailable。
[-->CredentialStorage.java:: installIfAvailable]
private void installIfAvailable() {
if (mInstallBundle != null&& !mInstallBundle.isEmpty()) {
Bundle bundle =mInstallBundle;
mInstallBundle = null;
final int uid = bundle.getInt(Credentials.EXTRA_INSTALL_AS_UID, -1);
//私钥导入到AS KeyStore
if(bundle.containsKey(Credentials.EXTRA_USER_PRIVATE_KEY_NAME)) {
String key =bundle.getString(Credentials.EXTRA_USER_PRIVATE_KEY_NAME);
byte[] value = bundle.getByteArray(
Credentials.EXTRA_USER_PRIVATE_KEY_DATA);
int flags =KeyStore.FLAG_ENCRYPTED;//加密标志
......
mKeyStore.importKey(key, value, uid, flags);
}
int flags = (uid ==Process.WIFI_UID) ? KeyStore.FLAG_NONE :
KeyStore.FLAG_ENCRYPTED;
//导入证书
if(bundle.containsKey(Credentials.EXTRA_USER_CERTIFICATE_NAME)){
String certName =
bundle.getString(Credentials.EXTRA_USER_CERTIFICATE_NAME);
byte[] certData =bundle.getByteArray(
Credentials.EXTRA_USER_CERTIFICATE_DATA);
//证书导入,调用AS KeyStore put函数
mKeyStore.put(certName, certData, uid, flags);
}
//导入CA证书
if(bundle.containsKey(Credentials.EXTRA_CA_CERTIFICATES_NAME)){
String caListName = bundle.getString(
Credentials.EXTRA_CA_CERTIFICATES_NAME);
byte[] caListData = bundle.getByteArray(
Credentials.EXTRA_CA_CERTIFICATES_DATA);
mKeyStore.put(caListName, caListData, uid, flags);
}
setResult(RESULT_OK);
}}
由上述代码可知,Settings最终会调用ASStore的几个函数把东西导进去:
- 对于Private Key来说,importKey将被调用,传入的参数包括Key名,key数据,uid和flags。Key名是前面别名修改对话时提到的“USRPKEY_My Key Chain”。
- 对于Cert和CA Cert,调用的都是put函数。参数和importKey类似。
到此,Java层的流程几乎全部介绍完毕。大家肯定还有一个疑问,ASKeyStore怎么不讲讲呢?去看看代码就知道了,AS KeyStore就是通过binder和“android.security.keystore”服务打交道。
根据图16可知,这个keystore服务应该在native的keystore进程里。直接来看它吧!
2.1.5 Native keystore daemon
keystore是一个native的daemon进程,其代码位于system/security/keystore下。这个进程由init启动。在init.rc中有如图20所示的配置:
图20 keystore启动配置
keystore的代码几乎都在keystore.cpp中,先来看它的main函数:
[-->keystore.cpp::main]
int main(int argc, char* argv[]) {
......
//修改本进程的工作目录。由图20可知,keystore设置的工作目录将变成/data/misc/keystore
chdir(argv[1]);
Entropy entropy; //Entropy设备:熵,和随机数生成有关,能增加随机数的随机性
entropy.open();
//和硬件的keymaster设备有关。如果没有硬件实现,AOSP给了一个软件实现
keymaster_device_t* dev;
keymaster_device_initialize(&dev);
//native层中的KeyStore,关键类
KeyStore keyStore(&entropy,dev);
//为系统添加一个“android.security.keystore”的服务
keyStore.initialize();
android::sp<android::IServiceManager> sm =
android::defaultServiceManager();
android::sp<android::KeyStoreProxy> proxy =
newandroid::KeyStoreProxy(&keyStore);
android::status_t ret =sm->addService(
android::String16("android.security.keystore"),proxy);
android::IPCThreadState::self()->joinThreadPool();
keymaster_device_release(dev);
return 1;
}
Native层也有一个KeyStore类,先来看它的构造函数:
(1) NativeKeyStore初始化
[-->keystore.cpp::KeyStore构造]
KeyStore(Entropy* entropy, keymaster_device_t* device)
: mEntropy(entropy)
, mDevice(device)
{
//mMetaData:一个结构体,内部只有一个int变量用来保存版本号
memset(&mMetaData, '\0', sizeof(mMetaData));
}
再来看init函数:
[-->keystore.cpp::initialize]
ResponseCode initialize() {
//读取工作目录下的.metadata文件,其中存储的是版本号
readMetaData();
if (upgradeKeystore())
writeMetaData();//版本号写到.metadata文件里
return ::NO_ERROR;
}
upgradeKeyStore用于升级Android平台KeyStore的管理结构。这个主要是为多用户支持的。来看图21:
图21 KeyStore目录管理
图21中,keystore目录下有个:
- .metadata文件用来控制版本。
- user_0文件夹:这是为了支持多用户而设置的。初始用户为user_0,后续添加新用户则叫user_xxx之类的。
- user_0目录中有个.masterkey文件和导入的PrivateKey,Cert和CA Cert文件。注意,以1000_USRPKEY_My+PKey+PChain为例,它就是我们前面说的“USRPKEY_My KeyChain”非常像,只不过前面多了一个uid(这里的uid是1000,和settings的uid是一个,也就是系统system_server的uid),然后把空格换成了“+P”。
也就是说,当我们导入东西到AS KeyStore的时候,它会在/data/misc/keystore对应用户(USER_X)目录下生成类似1000_XXX_Alias的文件。这个文件的内容并不直接保存的证书,PrivateKey等关键的二进制数据,而是一个经过加密的二进制数组。这个二进制数组将做为类似于Tag一样的东西,把它和实际的关键数据对应起来。而这些关键数据则是保存到硬件的KeyMasterDevice里的。我们接下来会看到这些文件的生成步骤。
稍微看一下upgradeKeyStore函数:
[-->keystore.cpp::upgradeKeyStore]
bool upgradeKeystore() {
bool upgraded = false;
if (mMetaData.version ==0) {
//每一个用户对应为代码中的一个UserState对象
UserState* userState = getUserState(0);
....
userState->initialize();//来看看它!
......
mMetaData.version = 1;
upgraded = true;
}
return upgraded;
}
[-->keystore.cpp:UserState:initialize]
bool initialize() {
//创建user_n目录,我们是第一个用户,所以n=0
mkdir(mUserDir, S_IRUSR | S_IWUSR | S_IXUSR);
//看看该目录下有没有.masterkey文件,如果没有,表明还没有设置
if (access(mMasterKeyFile, R_OK) == 0)
setState(STATE_LOCKED); //设置KeyStore状态,这也是前面Settings中得到的状态
else setState(STATE_UNINITIALIZED);
return true;
}
(2) password处理
有上述代码可知,当没有.masterkey文件的时候,Native KeyStore为未初始化状态。根据Settings里的处理,我们只有设置了锁屏界面时,Settings会调用password函数来设置密码。对应到Native KeyStore就是它password函数。
[-->keystore.cpp::password]
int32_t password(const String16& password) {
uid_t callingUid =IPCThreadState::self()->getCallingUid();
const String8password8(password);
switch(mKeyStore->getState(callingUid)) {
case::STATE_UNINITIALIZED: {
return mKeyStore->initializeUser(password8, callingUid);
}
case ::STATE_NO_ERROR: {
return mKeyStore->writeMasterKey(password8, callingUid);
}
case ::STATE_LOCKED: {
return mKeyStore->readMasterKey(password8, callingUid);
}
}
return ::SYSTEM_ERROR;
}
initializeUser其实就是把我们设置的密码和两个从Entropy得到的随机数(这些随机数有个奇怪的称呼,叫盐值,salt。salt也需要保存到.masterkey文件里的)搞到一起,生成一个签名,然后再用密码对这个签名进行加密,最终存储到文件里。
上述流程并不是很准确,偶也不想讨论了。说一下解密的问题:解密并不是从.masterkey里边去提取password,而是让用户输入密码,然后按类似的方法得到一个签名,最后和文件里的签名去比较是否匹配!
(3) importKey处理
来看看importKey,当Settings导入PrivateKey的时候,会调用它。importkey在nativeKeyStore中对应的是import函数,代码如下:
[-->keystore.cpp::import]
int32_t import(const String16& name, const uint8_t* data,size_t length,
int targetUid, int32_t flags) {
uid_t callingUid =IPCThreadState::self()->getCallingUid();
//权限检查,keystore对权限检查比较严格,只有system/vpn/wifi/rootuid的进程才
//可以操作它
if(!has_permission(callingUid, P_INSERT)) return ::PERMISSION_DENIED;
.....
State state =mKeyStore->getState(callingUid);
if ((flags &KEYSTORE_FLAG_ENCRYPTED) && !isKeystoreUnlocked(state)) {
return state;
}
//name是传进来的,对应为“USRPKEY_My Key Chain”
String8 name8(name);
//getkeyNameForUidWithDir将把uid和空格替换上去,并加上父目录的路径
//最终”filename=user_0/1000_USRPKEY_My+PKey+PChain”
String8filename(mKeyStore->getKeyNameForUidWithDir(name8,targetUid));
return mKeyStore->importKey(data, length,filename.string(),
callingUid,flags);
}
来看importKey函数:
[-->keystore.cpp::importkey]
ResponseCode importKey(const uint8_t* key, size_t keyLen, constchar* filename,
uid_t uid, int32_t flags) {
uint8_t* data;
size_t dataLength;
int rc;
bool isFallback = false;
//将key信息传递到keymaster device里去,注意data这个参数,它是一个返回参数,
//是keymaster device返回的。具体是什么,由硬件或驱动决定
rc = mDevice->import_keypair(mDevice, key, keyLen,&data, &dataLength);
//如果硬件没这个功能,那么就软件实现,用得是openssl_import_keypair,其内部好像就是
//对key加了把密,然后把加密后的key数据存储到data里了
if (rc) {
if(mDevice->common.module->module_api_version <
KEYMASTER_MODULE_API_VERSION_0_2){
rc = openssl_import_keypair(mDevice, key,keyLen, &data, &dataLength);
isFallback = true;
}
//把import_keypair返回的data信息放到一个Blob中
Blob keyBlob(data,dataLength, NULL, 0, TYPE_KEY_PAIR);
free(data);
keyBlob.setEncrypted(flags& KEYSTORE_FLAG_ENCRYPTED);
keyBlob.setFallback(isFallback);
//再把这个blob信息写到对应的文件里...
return put(filename, &keyBlob, uid);
}
put函数就是把data什么信息都往对应的文件里写...让人解脱的是,cert和CA cert调用的都是put函数,所以我们这也不用再单独介绍put了....
2.1.6 小结
从这一大节的流程来看,导入证书文件其实是一件很麻烦的事情,涉及到好几个进程,比如CertInstaller,Settings,native的KeyStore等。
无论如何,我们还是把信息都写到文件里了。这里要特别指出:
- Android平台中,证书等敏感信息都存储到KeyMaster Device里了,也就是前面提到的SEE中。
- nativekeystore会在对应目录下放几个文件,这几个文件存储的是二进制内容。而这些二进制内容并不是敏感信息,而是由敏感信息通过一些转换(比如加密)得到的东西。这也是所谓的KEK(Key Encryption Key,也就是给密钥加密的密钥)吧?
2.2 通过KeyChain获取PrivateKey
信息都导入到系统里去了,那是不是用JCE标准接口就能用呢?不是,至少我测试了不是。为什么?先想想下面这些个问题:
- 谁都可以往系统里导证书信息,并设置alias。但是,其他程序是否都有权限使用它们?
显然不是。Android系统里,要使用某个alias的证书,系统会弹出一个提示框以提醒用户,这个提示框如图22所示:
图22 证书选择对话框
图22中:
- 首先会列出一些证书(当然,我们这里只导入了一个证书文件,所以只有“MyKey Chain”)。
- 也可以选择从sdcard中安装.pfx或.p12文件。这需要点击图中的“install“按钮。
- 剩下的就是选择是否允许当前app使用所选别名的证书信息了。
所以,一个app要使用系统中的某个证书(这里是指privatekey和非CA的证书)信息时,必须要先调用KeyChain的choosePrivateKeyAlias函数。我们的故事就从这里开始:
2.2.1 choosePrivateKeyAlias介绍
先来看怎么用它,如DemoActivity中的onActivityResult所示:
[-->DemoActivity.java::onActivityResult]
protected void onActivityResult(int requestCode, int resultCode,Intent data) {
KeyChain.choosePrivateKeyAlias(this,
//第二个参数是一个回调对象,当用户选择了哪一个alias的时候,会通过这个回调对象传回来
new KeyChainAliasCallback() {
@Override
public voidalias(String alias) {
......
}
});
}
},
new String[] {},//设置要使用的Key Type,比如RSA或DSA,本例不设置这个
null, //设置证书的issuer,即指定目标证书的发行者,一般也不设置。除非事先约定好
"localhost", //好像和SSLServer有关,用于告诉用户想用在什么ip地址或端口号上
-1,
ENTRY_ALIAS);//ENTRY_ALIAS的值是“My Key Chain“
super.onActivityResult(requestCode,resultCode, data);
}
直接来看KeyChain的实现代码:
[-->KeyChain::choosePrivateKeyAlias]
public static voidchoosePrivateKeyAlias(Activity activity,
KeyChainAliasCallback response,
String[] keyTypes, Principal[] issuers,
String host, intport,String alias) {
......
//Action的值为“com.android.keychain.CHOOSER“
Intent intent = newIntent(ACTION_CHOOSER);
intent.putExtra(EXTRA_RESPONSE, new AliasResponse(response));
intent.putExtra(EXTRA_HOST, host);
intent.putExtra(EXTRA_PORT, port);
intent.putExtra(EXTRA_ALIAS, alias);
intent.putExtra(EXTRA_SENDER, PendingIntent.getActivity(activity, 0,
new Intent(), 0));
activity.startActivity(intent);
}
“com.android.keychain.CHOOSER“这个Intent将好多年来一直默默无名但是又重要无比的keychain这个apk来响应。
(1) KeyChainActivity
直接来看图22中那个框是咋处理的吧。
[-->KeyChainActivity.java:: showCertChooserDialog]
private void showCertChooserDialog() {
new AliasLoader().execute();
}
[-->KeyChainActivity.java::AliasLoader]
private class AliasLoader extendsAsyncTask<Void, Void, CertificateAdapter> {
@Override protectedCertificateAdapter doInBackground(Void...params) {
//借助Binder和Native KeyStore交互,获取alias列表
String[] aliasArray = mKeyStore.saw(Credentials.USER_PRIVATE_KEY);
List<String> aliasList = ((aliasArray== null)
?Collections.<String>emptyList(): Arrays.asList(aliasArray));
Collections.sort(aliasList);
return new CertificateAdapter(aliasList);
}
@Override protected voidonPostExecute(CertificateAdapter adapter) {
displayCertChooserDialog(adapter);//显示图22所示的对话框!
}
}
[-->KeyChainActivity.java::displayCertChooserDialog]
private voiddisplayCertChooserDialog(final CertificateAdapter adapter) {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
......
if......
else {
.....
builder.setPositiveButton(R.string.allow_button,
new DialogInterface.OnClickListener(){
@Override public void onClick(DialogInterface dialog, int id) {
int listViewPosition = lv.getCheckedItemPosition();
int adapterPosition = listViewPosition-1;
String alias = ((adapterPosition >= 0)
?adapter.getItem(adapterPosition) : null);
finish(alias);
}
});
}
...
final Dialog dialog =builder.create();
......
}
关键函数是这个finish,注意它是带参数的,非常容易和Activity的finish()混淆!
[-->KeyChainActivity.java::finish]
private void finish(String alias) {
......
IKeyChainAliasCallback keyChainAliasResponse
= IKeyChainAliasCallback.Stub.asInterface(
getIntent().getIBinderExtra(KeyChain.EXTRA_RESPONSE));
if (keyChainAliasResponse != null) {
new ResponseSender(keyChainAliasResponse,alias).execute();
return;
}
finish();
}
[-->KeyChainActivity.java:ResponseSender]
private class ResponseSender extends AsyncTask<Void,Void, Void> {
private IKeyChainAliasCallback mKeyChainAliasResponse;
private String mAlias;
.......
@Override protected VoiddoInBackground(Void... unused) {
try {
if (mAlias != null) {
KeyChain.KeyChainConnection connection =
KeyChain.bind(KeyChainActivity.this);
try {
//这里的Service不是Native的KeyStore,而是keychain里的KeyChainService
connection.getService().setGrant(mSenderUid,mAlias, true);
} ......
}
mKeyChainAliasResponse.alias(mAlias);//调用我们的回调函数
(2) KeyChainService
KeyChainService也在keychain.apk中,这玩意开机就会启动,因为keychain有一个BroadcastReceiver会接收BOOT_COMPLETE消息,这个BR然后启动KeyChainService。
KeyChainService功能超级简单,就是管理了一个名叫grants的数据库。这个数据库为每一个alias和调用choosePrivateKeyAlias的进程的uid维护了一个关系,也就是所谓的权限管理。即只有在这个数据库里某个alias有对应的uid时,那个uid所在进程才能访问这个alias所代表的证书信息。
图23所示为该数据库的示例:
图23 grants.db示例
(3) getPrivateKey
设置好权限后,下一步就是从对应alias中获取PrivateKey或者是证书信息了。我们这里仅以PrivateKey为例。相应的函数是KeyChain的getPrivateKey。
[-->KeyChain.java::getPrivateKey]
public static PrivateKeygetPrivateKey(Context context, String alias)
throws KeyChainException,InterruptedException {
......
KeyChainConnectionkeyChainConnection = bind(context);
try{
final IKeyChainServicekeyChainService =
keyChainConnection.getService();
//从KeyChainService那获得一个id,然后把这个id传到OpenSSL相关API里
final String keyId = keyChainService.requestPrivateKey(alias);
.....
final OpenSSLEngine engine =OpenSSLEngine.getInstance("keystore");
return engine.getPrivateKeyById(keyId);
} ......
}
由前面代码可知,PrivateKey信息之前是导入到硬件里去的,留下来的在/data/misc/keystore/user_0文件夹下是一些保存了和PrivateKey有关系的数据(想从这些数据里还原PrivateKey显然是不可能的,它有点像MD5码,和硬件里的PrivateKey有着一一对应的关系)。那么,getPrivateKey函数会把硬件里的信息弄出来吗?
显然,如果弄出来的话,安全性就没有了。所以,在Android平台中,PrivateKey永远都是保存在硬件里的,外面拿到的都是一个标志,也就是上面代码里的KeyId。那JCE怎么用这个keyid呢?
- 前面讲了。JCE只是一个框架,具体的工作是由不同引擎来完成的。在Android上,Key相关引擎由OpenSSLEngine构造并经过google修改,其中的很多函数都会和native 的keystore交互。比如加密解决也是把数据传递到硬件来完成的,因为要严格恪守PrivateKey不外传的原则!
JCE一些接口以及Android上的OpenSSLEngine等引擎比较复杂。这里也不会一一涉及,读者有个大概了解就可以了。
先来看KeyChainService的requestPrivateKey函数:
[-->KeyChainService.java::requestPrivateKey]
public String requestPrivateKey(Stringalias) {
//检查数据库里是不是给调用进程设置了权限!
checkArgs(alias);
final String keystoreAlias =Credentials.USER_PRIVATE_KEY + alias;
final int uid =Binder.getCallingUid();
//Native KeyStore也要维护一个类似的uid-alias权限。这里要设置这个权限
if (!mKeyStore.grant(keystoreAlias, uid)) {
return null;
}
//构造keyid很简单,就是返回”1000_USRPKEY_My Key Chain”
final StringBuilder sb = new StringBuilder();
sb.append(Process.SYSTEM_UID);
sb.append('_');
sb.append(keystoreAlias);
return sb.toString();
}
简直太简单了,keyid原来就是用system的uid加上对应的alias。但是,还得为它设置权限,否则你有keyid,Native keystore也无法让你访问。
好了。到此时,我们已经成功拿到了一个PrivateKey。这个privateKey是一个接口,其具体实现应该是OpenSSLRSAPrivateKey(假设我们这个PKey是RSA类似的)。
接着,我们如果继续把玩这个PKey,比如想调用它的getEncoded获取二进制表达式,很可悲得的是它会null。是的,返回null呢!!这个....从情理上似乎说得过去,因为我们不能让私有信息外流。但从法理上我们又不爽,因为我们前面的实例中,一直是可以取出二进制信息的,到底是什么东西导致信息导入到系统后,反而取不了二进制信息了呢?
这部分内容比较繁琐,我这里简单和各位一起把代码撸一遍好了!
先从OpenSSLEngine的getInstance看起。
2.2.2 为什么PrivateKey.getEncoded返回null?
由于KeyChain getPrivateKey创建的是OpenSSLEngine,所以先来看它:
[-->OpenSSLEngine.java::getInstance]
public static OpenSSLEngine getInstance(String engine)
throws IllegalArgumentException {
final long engineCtx;
synchronized(mLoadingLock) {
//engine的值是“keystore“,这里的意思好像是从Engine库里去找对应的实现
engineCtx = NativeCrypto.ENGINE_by_id(engine);
......
NativeCrypto.ENGINE_add(engineCtx);
}
//返回一个OpenSSLEngine对象
return new OpenSSLEngine(engineCtx);
}
打破脑壳你也想不到谁注册了”keystore”到NativeCrypto来。答案在keystore-engine里,这是一个动态库,代码在system/security/keystore-engine中。来看一小段:
[-->eng_keystore.cpp:: keystore_engine_setup]
static int keystore_engine_setup(ENGINE*e) {
ALOGV("keystore_engine_setup");
//这个kKeystoreEngineId是一个字符串,值就是”keystore”
//设置一些函数指针,注意红色的地方
if (!ENGINE_set_id(e, kKeystoreEngineId)
|| !ENGINE_set_name(e, kKeystoreEngineDesc)
||!ENGINE_set_load_privkey_function(e, keystore_loadkey)
||!ENGINE_set_load_pubkey_function(e, keystore_loadkey)
|| !ENGINE_set_flags(e, 0)
|| !ENGINE_set_cmd_defns(e,keystore_cmd_defns)) {
ALOGE("Could not set up keystore engine");
return 0;
}
pthread_once(&key_handle_control, init_key_handle);
//注册DSA,RSA和ECDSA算法所使用的函数,这也是使用系统证书信息的限制,它只支持这三种
//算法
if (!dsa_register(e)) {....
} else if (!ecdsa_register(e)) {....
} else if (!rsa_register(e)) {....
}
return 1;
}
然后KeyChain getPrivateKey会调用OpenSSLEngine的getPrivateKeyById:
[-->OpenSSLEngine.java::getPrivateKeyById]
public PrivateKeygetPrivateKeyById(String id) throws InvalidKeyException {
......
//传递进来的id是“1000_USRPKEY_My Key Chain“
//根据上面的代码,这里的ENGINE_load_private_key应该会调用keystore_engine的
//keystore_loadkey函数
final long keyRef = NativeCrypto.ENGINE_load_private_key(ctx, id);
OpenSSLKey pkey = new OpenSSLKey(keyRef,this, id);
try {
return pkey.getPrivateKey();
} ......
}
注意这两段代码红色的地方。Java层对应的ENGINE_load_private_key最终会走到keystore-engine的keystore_loadkey函数里。代码如下所示:
[-->eng_keystore.cpp::keystore_loadkey]
static EVP_PKEY* keystore_loadkey(ENGINE* e, const char* key_id,
UI_METHOD* ui_method,void*callback_data) {
//说得不错吧,它果然要和native keystore通过binder交互
sp<IServiceManager> sm = defaultServiceManager();
sp<IBinder> binder =sm->getService(String16("android.security.keystore"));
sp<IKeystoreService> service =interface_cast<IKeystoreService>(binder);
uint8_t *pubkey = NULL;
size_t pubkeyLen;
int32_tret = service->get_pubkey(String16(key_id),&pubkey, &pubkeyLen);
......
const unsigned char* tmp = reinterpret_cast<const unsignedchar*>(pubkey);
Unique_EVP_PKEY pkey(d2i_PUBKEY(NULL, &tmp, pubkeyLen));
switch (EVP_PKEY_type(pkey->type)) {
......
case EVP_PKEY_RSA:{//这里的rsa_pkey_setup还会和native keystore交互,此处略过!
rsa_pkey_setup(e, pkey.get(),key_id);
break;
}
.....
return pkey.release();
}
你看,果然我们的DemoActivity应用会和native keystore进程交互。而且很明显,我们调用的是它的get_pubkey函数,即使我们这样想用privatekey信息。真是步步为营啊,绝不泄露PrivateKey信息。最后,我们的PrivateKey将从OpenSSLKey的getPrivateKey得到:
[-->OpenSSLKey.java::getPrivateKey]
public PrivateKey getPrivateKey() throwsNoSuchAlgorithmException {
switch (NativeCrypto.EVP_PKEY_type(ctx)) {
case NativeCrypto.EVP_PKEY_RSA:
return new OpenSSLRSAPrivateKey(this);
case NativeCrypto.EVP_PKEY_DSA:
return newOpenSSLDSAPrivateKey(this);
case NativeCrypto.EVP_PKEY_EC:
return newOpenSSLECPrivateKey(this);
default:
throw newNoSuchAlgorithmException("unknown PKEY type");
}
}
好了,当我们对OpenSSLRSAPrivateKey调用getEncoded的时候,会发生什么问题呢?
[-->OpenSSLRSAPrivateKey.java::getEncoded]
public final byte[] getEncoded() {
if (key.isEngineBased()) {//我们是有Engine支持的,所以返回空!
return null;
}
return NativeCrypto.i2d_PKCS8_PRIV_KEY_INFO(key.getPkeyContext());
}
三 需要继续走的路
到此,本文基本就结束了。在这篇文章里:
- 我们首先对JCE的基础知识进行了一些介绍。这些知识,是理解Android平台中Java Security的重要一部分,属于最基本的知识,需要大家理解。
- 然后我们用代码对Android平台上特有的KeyChain,KeyStore,CertInstaller介绍了一番。这个...需要各位结合代码撸几把,掌握大概意思就好。
那么,剩下还有什么呢?
- 有没有谁把SSLServerFactory和我们导入系统的KeyStore联合起来?我内心觉得这事情应该是能办成的,但是我确实没找到对应的接口函数。
- 另外,实际上,每一个应用程序也可以借助JCE,并指明使用“AndroidKeyStore”。在这种情况下,它只能自己先把信息导进去,然后也只能和它同一个uid的进程才能用这些信息。(其实我们刚才是把信息通过uid=1000的settings导入到系统的,这个是系统全局的证书信息。应用程序可以往native keystore中导入自己uid的信息。)
- 另外,TrustStore的问题,大家也跟着代码看看吧
...
对上述这些感兴趣的童鞋,只能请你们自己看代码玩耍了....
参考文献
Java Security
[1] Java Security, 2nd Edition:http://shop.oreilly.com/product/9780596001575.do 中文名为《Java安全第二版》,此书是关于Java Security最好的参考书。
http://docs.oracle.com/javase/6/docs/technotes/guides/security/crypto/CryptoSpec.html
关于证书和证书文件格式
[2] http://www.360doc.com/content/13/0417/10/11791971_278827661.shtml
[3] http://www.blogjava.net/lihao336/archive/2011/08/18/356763.html
[4] http://en.wikipedia.org/wiki/PKCS
[5] http://en.wikipedia.org/wiki/X.509
X.509和PKCS介绍
[6] http://bbs.csdn.net/topics/190044123
关于X.509和PCKS规范之间的关系的讨论
Key管理
[7] 《Java加密与解密的艺术》,作者梁栋,国人关于JavaSecurity的一本好书。
[8] http://developer.android.com/training/articles/keystore.html
[9] http://en.wikipedia.org/wiki/Public-key_cryptography
JSSE
[10] http://en.wikipedia.org/wiki/Transport_Layer_Security
TLS和SSL的历史。
[11] https://developer.android.com/training/articles/security-ssl.html
Android开发文档中关于SSL方面的知识。
Cipher资料
[12] http://www.javamex.com/tutorials/cryptography/index.shtml
SEE和TrustZone
[13] http://research.microsoft.com/en-us/um/people/alecw/asplos-2014.pdf
[14] http://www.ti.com.cn/cn/lit/wp/spry228/spry228.pdf
TrustZone资料
[15] http://cache.freescale.com/files/32bit/doc/white_paper/QORIQSECBOOTWP.pdf
安全系统的资料
Android Security Internals:
[16] Android Security Internals
美国亚马逊上有电子版,可在浏览器上看。很好的一本系统讲述Android安全方面的书籍。