如何在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 - 如果出现编码错误。

你可能感兴趣的:(Android)