1. 程式人生 > >如何在Android中增加自己的應用簽名校驗?

如何在Android中增加自己的應用簽名校驗?

背景:

我最近的遇到一個專案,需要在PMS中增加廠商自己的應用簽名校驗,本文將描述整個新增過程。

Android使用Java的數字證書相關的機制來給apk加蓋數字證書,要理解Android數字證書,需要先了解以下數字證書的概念和java的數字證書機制。

1、基礎概念

數字證書:數字證實是採用數字手段來證實使用者身份的一種方法。數字證書含有兩部分資料:一部分是對應主體(單位或個人)的資訊,另一部分是這個主體所對應的公鑰。即數字證書儲存了主體和它的公鑰的一一對應關係,用於自我認證(向其他的使用者證明自己的身份)。

1)建立證書方法:

Java中的keytool.exe可以用來建立數字證書,所有的數字證書是以一條一條(採用別名區別)的形式存入證書庫的中,證書庫中的一條證書包含該條證書的私鑰,公鑰和對應的數字證書的資訊。證書庫中的一條證書可以匯出數字證書檔案,數字證書檔案只包括主體資訊和對應的公鑰。

每一個證書庫是一個檔案組成,它有訪問密碼,在首次建立時,它會自動生成證書庫,並要求指定訪問證書庫的密碼。

在建立證書的的時候,需要填寫證書的一些資訊和證書對應的私鑰密碼。
例如這條命令:

keytool -genkey -alias testCA -keyalg RSA -keysize 1024 -keystore testCALib -validity 3650

在數字證書庫testCALib中建立了一個別名為testCA,使用RSA演算法加密的,有效期為3650天的數字證書。

證書生成以後,我們可以使用命名將數字證書匯出為一個檔案。

keytool -export -alias
testCA -file testCA.cer -keystore testALib -rfc

2)簽名方法:
數字證書生成以後,我們需要使用生成的數字證書給程式包簽名,這個是使用jarsigner 工具。例如,如果我們有一個android的程式包test.apk.,我們就可以使用剛生成的testCA給改程式包簽名。

jarsigner -keystore testCALib test.apk. testCA.

另外,我們也可以使用像eclipse這些工具來進行簽名。

2、android平臺修改

應用安裝流程,無論是使用命令安裝,還是點選APK檔案進行安裝,流程基本為:
pm install->packageManagerService或packageManager->packageManagerService或者是packageInstaller->packageManagerService

添加簽名校驗,一般是在packageManagerService來執行。

我們新增如下程式碼,用屬性persist.sys.appcheck來控制是否要增加自己的校驗:

private static boolean isAppStoreCheck() {
return SystemProperties.getBoolean("persist.sys.appcheck",false);
}

接下來將testCA.cer放到/system/etc/security/下,確認要有可讀許可權。

// flag indicating whether to check the app store signature before installing.
 private static String sAppStoreCertificatePath = "/system/etc/security/testCA.cer";

在初始化的時候PMS時,就先載入我們新增的簽名

//for app store signature check
private static final Signature sAppStoreSignature = loadAppStoreSignature();

private static Signature loadAppStoreSignature() {
 if(!isAppStoreCheck()) return null;
        try {
            CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
            Certificate appStoreCertificate = certificateFactory.generateCertificate(new FileInputStream(sAppStoreCertificatePath));
            return (new Signature(appStoreCertificate.getEncoded()));
        } catch (Exception e) {
            Slog.i(TAG, "loadAppStoreSignature  failed");
            e.printStackTrace();
        }       
        return null;
 }

在呼叫到安裝的主函式installNewPackageLI時,我們先進行自己的簽名校驗,如果校驗不合格,返回我們自己定義的錯誤碼給packageInstaller

private void installNewPackageLI(PackageParser.Package pkg,
            int parseFlags, int scanMode, UserHandle user,
            String installerPackageName, PackageInstalledInfo res) {
        // Remember this for later, in case we need to rollback this install
        String pkgName = pkg.packageName;

        // Check whether the app was signed by the app store when it is first installed.
        if (!isSystemApp(pkg)&&(isAppStoreCheck()
                && (checkAppStoreSignature(pkg.mSignatures) != PackageManager.APP_STORE_SIGNATURE_SIGNED))) {
            res.returnCode = PackageManager.INSTALL_PARSE_FAILED_INVALID_APP_STORE_CERTIFICATE;
            return;
        }
        ....
 }       

checkAppStoreSignature函式則將應用的簽名一個個和我們自己的簽名進行對比。

//for app store signature check 
    private int checkAppStoreSignature(Signature[] signatures) {
        if (signatures != null && sAppStoreSignature != null) {
            for (Signature s : signatures) {
                if (s != null) {
                    if (s.equals(sAppStoreSignature)) {
                        return PackageManager.APP_STORE_SIGNATURE_SIGNED;
                    }
                }
            }
        }

        return PackageManager.APP_STORE_SIGNATURE_NOT_SIGNED;
    }   

修改後,設定屬性persist.sys.appcheck為true,則增加一層校驗,校驗不過會返回
PackageManager.INSTALL_PARSE_FAILED_INVALID_APP_STORE_CERTIFICATE;
這個是我們自己新增的錯誤碼,在packageInstaller中,得自己去處理並提示使用者。這個就不說了,比較簡單。
校驗通過則會走平時的安裝流程。

3、Certificate相關方法介紹

loadAppStoreSignature函式中要去讀取證書中的簽名,
先建立CertificateFactory ,再從我們的證書中生成一個證書物件並初始化,最後返回此證書的編碼形式,獲得Signature型別的簽名物件。

下面介紹使用到的函式:

getInstance

public static final CertificateFactory getInstance(String type)
throws CertificateException

生成一個實現指定證書型別的 CertificateFactory 物件。如果預設提供程式包提供了所請求的證書型別的實現,則返回一個包含該實現的 CertificateFactory 例項。如果預設包中不存在該證書型別,則搜尋其他包。

引數:
type - 所請求的證書型別的名稱。有關標準證書型別的資訊,請參見《Java Cryptography Architecture API Specification & Reference 》中的附錄 A
返回:
指定型別的 CertificateFactory 物件。
丟擲:
CertificateException - 如果預設提供程式包中或搜尋過的所有其他提供程式包中不存在請求的證書型別。
generateCertificate

public final Certificate generateCertificate(InputStream inStream)
throws CertificateException
生成一個證書物件並使用從輸入流 inStream 中讀取的資料對它進行初始化。
為了利用此 CertificateFactory 所支援的專門的證書格式,可將返回的證書物件的型別強制轉換為相應的證書類。例如,如果此 CertificateFactory 實現 X.509 證書,則可將返回的證書物件的型別強制轉換為 X509Certificate 類。

在用於 X.509 證書的 CertificateFactory 情況中,inStream 中提供的證書必須是 DER 編碼的,並且可以二進位制或可列印的 (Base64) 編碼形式提供。如果以 Base64 編碼的形式提供該證書,則該證書必須由 -----BEGIN CERTIFICATE----- 語句開始,由 -----END CERTIFICATE----- 語句結束。

注意,如果給定的輸入流不支援 mark 和 reset,則此方法將使用整個輸入流。否則,每次呼叫此方法都需要一個證書,並且將輸入流的讀取位置定位在固有的證書結尾標記後的下一個可用位元組處。如果輸入流中的資料不包含固有的證書結尾標記(不同於 EOF),並且在解析該證書後有一個尾隨資料,則丟擲 CertificateException。

引數:
inStream - 帶有證書資料的輸入流。
返回:
已使用輸入流中的資料初始化的證書物件。
丟擲:
CertificateException - 如果發生解析錯誤。
getEncoded

public abstract byte[] getEncoded()
throws CertificateEncodingException
返回此證書的編碼形式。假定每種證書型別只有一種編碼形式;例如 X.509 證書將以 ASN.1 DER 的形式進行編碼。
返回:
此證書的編碼形式
丟擲:
CertificateEncodingException - 如果出現編碼錯誤。