1. 程式人生 > >深入理解Android之Java Security第二部分(Final)

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

深入理解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檔案的passwordalias名都不在需要了

       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,

    //第二個引數是一個回撥物件,當用戶選擇了