3

深入理解Android之Java Security第二部分(Final)

 3 years ago
source link: https://blog.csdn.net/Innost/article/details/44199503
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

深入理解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);

        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);

      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地址或端口号上

     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) {

       if (mAlias != null) {

         KeyChain.KeyChainConnection connection =

                       KeyChain.bind(KeyChainActivity.this);

           //这里的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);

      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);

     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

[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安全方面的书籍。

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK