Android系统源码分析进阶

定制framework使系统级权限应用同时支持系统签名和第三方的

2018-06-10  本文已影响198人  道墟

一.背景知识

1.1 android权限分级

在android开发中权限主要分为以下四级

关于权限定义的源文件位于:frameworks\base\core\res\AndroidManifest.xml

这边要注意 系统权限应用(signature)和系统应用(system)的区别:
系统应用一般是厂商内置的应用,在目录 system/app 、/system/framework/、/vendor/app/下的都是系统应用,
系统权限应用是 指在 AndroidManifest.xml 申请了 android:sharedUserId="android.uid.system" 的应用,而这个是需要系统签名的。
这部分的知识在PKMS服务中会详细的介绍。

1.2 系统签名类型

android的标准签名key有:

在源码的/build/target/product/security里面看到对应的密钥,其中.pk8代表私钥,.pem公钥,一定是成对出现的。

查看.pem公钥方法
openssl x509 -in cert.pem(文件名) -noout -text

关于.pk8和.pem在下面会继续详细介绍。

1.3 签名相关知识

1.3.1 数据摘要

消息摘要算法(Message Digest Algorithm)是一种能产生特殊输出格式的算法,其原理是根据一定的运算规则对原始数据进行某种形式的信息提取,被提取出的信息就被称作原始数据的消息摘要。简单点说就是提取数据的“指纹”。

著名的摘要算法有RSA公司的MD5算法和SHA-1算法及其大量的变体。

消息摘要的主要特点有:

1)无论输入的消息有多长,计算出来的消息摘要的长度总是固定的。例如应用MD5算法摘要的消息有128个比特位,用SHA-1算法摘要的消息最终有160比特位的输出

2)一般来说(不考虑碰撞的情况下),只要输入的原始数据不同,对其进行摘要以后产生的消息摘要也必不相同,即使原始数据稍有改变,输出的消息摘要便完全不同。但是,相同的输入必会产生相同的输出。

3)具有不可逆性,即只能进行正向的信息摘要,而无法从摘要中恢复出任何的原始消息。

1.3.2 jarsign和signapk工具

这两个都是android系统中使用的签名工具。jarsign是Java本生自带的一个工具,他可以对jar进行签名的。而signapk是后面专门为了Android应用程序apk进行签名的工具,他们两的签名算法没什么区别,主要是签名时使用的文件不一样。

这边补充下:keystore 是Eclipse 打包生成的签名。 而 .jks是Android studio 生成的签名!都是用来打包的,并保证应用的唯一性,它们是可以互相转换的。

因为我们现在常用的是jks文件,可以通过代码获取jks文件的公钥和私钥信息

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;

import javax.security.cert.Certificate;

public class JKSTesting {
    public static PublicKey getPublicKey(String keyStoreFile,
                                         String storeFilePass, String keyAlias) {

        // 读取密钥是所要用到的工具类
        KeyStore ks;

        // 公钥类所对应的类
        PublicKey pubkey = null;
        try {

            // 得到实例对象
            ks = KeyStore.getInstance("JKS");
            FileInputStream fin;
            try {

                // 读取JKS文件
                fin = new FileInputStream(keyStoreFile);
                try {
                    // 读取公钥
                    ks.load(fin, storeFilePass.toCharArray());
                    java.security.cert.Certificate cert = ks
                            .getCertificate(keyAlias);
                    pubkey = cert.getPublicKey();
                } catch (NoSuchAlgorithmException e) {
                    e.printStackTrace();
                } catch (CertificateException e) {
                    e.printStackTrace();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            }
        } catch (KeyStoreException e) {
            e.printStackTrace();
        }
        return pubkey;
    }

    /**
     * 得到私钥
     *
     * @param keyStoreFile
     *            私钥文件
     * @param storeFilePass
     *            私钥文件的密码
     * @param keyAlias
     *            别名
     * @param keyAliasPass
     *            密码
     * @return
     */
    public static PrivateKey getPrivateKey(String keyStoreFile,
                                           String storeFilePass, String keyAlias, String keyAliasPass) {
        KeyStore ks;
        PrivateKey prikey = null;
        try {
            ks = KeyStore.getInstance("JKS");
            FileInputStream fin;
            try {
                fin = new FileInputStream(keyStoreFile);
                try {
                    try {
                        ks.load(fin, storeFilePass.toCharArray());
                        // 先打开文件
                        prikey = (PrivateKey) ks.getKey(keyAlias, keyAliasPass
                                .toCharArray());
                        // 通过别名和密码得到私钥
                    } catch (UnrecoverableKeyException e) {
                        e.printStackTrace();
                    } catch (CertificateException e) {
                        e.printStackTrace();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                } catch (NoSuchAlgorithmException e) {
                    e.printStackTrace();
                }
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            }
        } catch (KeyStoreException e) {
            e.printStackTrace();
        }
        return prikey;
    }

    public static void main(String[] args) {
        PublicKey publicKey;
        PrivateKey privateKey;

        publicKey=getPublicKey("*******","*******", "*******");
        privateKey=getPrivateKey("*******","*******", "*******","*******");

        System.out.println("publicKey");
        System.out.println(publicKey.toString());
        System.out.println("privateKey");
        System.out.println(privateKey.toString());
    }
}

1.3.3 签名后生成的文件

解压签名后的apk文件,在 META-INF 文件夹中可以看到以下格式的文件

Android中是允许使用多个 keystore 对apk进行签名的,这样在 META-INF 就会生成多对(.RSA和.SF)的文件,以 keystore 文件的 alias 的值为名称。如果是用 signapk工具 进行签名的话就会直接固定命名CERT。

1.3.3.1 MANIFEST.MF文件

我们先看下这个文件的内容

Name: lombok/installer/InstallerGUI$4.SCL.lombok
SHA1-Digest: ZSCgYd7ezY8EyCteY5YFVNSzqbg=

Name: res/drawable-hdpi-v4/modify_phone_num_step_first.png
SHA1-Digest: 1A/23eA5r36ww5luAwlaK5n42fc=

Name: res/drawable-hdpi-v4/right_kuohao_focus.png
SHA1-Digest: fPzTTOc+c1ogDOlHKpeJ0JGDt6U=

Name: res/layout/activity_modify_phone.xml
SHA1-Digest: aE+Ctjnxcu/xHp79HhRZ8LvnA/c=

Name: res/drawable-1920x1080/item_h_default.png
SHA1-Digest: KA0HpPJUVaKTpTnF5ouiJmRNyXo=

Name: lombok/delombok/LombokOptionsFactory$1.SCL.lombok
SHA1-Digest: QlP0zlFTJ9scHtfLL3/OZNhA2uw=

Name: AndroidManifest.xml
SHA1-Digest: lkuasUmz9QVhBrAh8JEMmyvL65c=

Name: lombok/installer/InstallerGUI$9.SCL.lombok
SHA1-Digest: ifEHckOynWf1wUlfPH/aoj73utk=

Name: lombok/core/LombokConfiguration.SCL.lombok
SHA1-Digest: XOaLbgugkPhHqkxGiDFxUvxNJSA=

Name: lombok/installer/eclipse/JbdsLocationProvider.SCL.lombok
SHA1-Digest: 3M+Q/7s7VwuoOWk1hpUJe8yBJjM=

Name: res/drawable-hdpi-v4/user_prog_entry_layer.png
SHA1-Digest: 5oJ0Hv4o+c7PkwJ4MOox2/xZmzU=

文件的内容是逐一遍历apk里面的所有条目,如果是目录就跳过,如果是一个文件,就用SHA1(或者SHA256)消息摘要算法提取出该文件的摘要然后进行BASE64编码后,作为“SHA1-Digest”属性的值写入到MANIFEST.MF文件中的一个块中。该块有一个“Name”属性,其值就是该文件在apk包中的路径。

1.3.3.2 SF文件

Signature-Version: 1.0
X-Android-APK-Signed: 2
SHA1-Digest-Manifest: oZ0/oKbtbxza48iz2t1zT9MQ/vw=
Created-By: 1.0 (Android)

Name: res/layout/play_progress_tip_layout.xml
SHA1-Digest: zGKChGzCciVxMBfsf/9QS1jA9f0=

Name: res/anim/boot_one_circle.xml
SHA1-Digest: P4oLIBrBAQqjEn2VUDXvvuYXfeI=

Name: res/drawable-hdpi-v4/myapp_del_no_focus.png
SHA1-Digest: GEyz0DWb4vzq33MLCBnxtTVbWQE=

Name: lombok/eclipse/handlers/HandleLog$LoggingFramework.SCL.lombok
SHA1-Digest: LSHMxv3Atl0Z7MCQaPf9yKvhax4=

Name: lombok/delombok/DelombokApp$1.SCL.lombok
SHA1-Digest: DfUlhS1EZ55lpPpz+vsDIf847No=

SF文件记载的内容:

1.3.3.3 RSA文件

因为RSA文件加密了,所以我们需要用openssl命令才能查看其内容

openssl pkcs7 -inform DER -in CERT.RSA(文件名) -noout -print_certs –text

Certificate:
    Data:
        Version: 3 (0x2)       //版本号
        Serial Number: 610763924 (0x24678494) //证书序列号
    Signature Algorithm: sha256WithRSAEncryption    //算法
        Issuer: CN=icntv                            //发行者名称
        Validity                                    
            Not Before: Nov  5 15:43:01 2012 GMT        //生效日期
            Not After : Oct 12 15:43:01 2112 GMT        //终止日期
        Subject: CN=icntv                               //主体名称
        //算法 参数 秘钥:主体公钥信息,包含开发者的加密算法以及密钥信息
        Subject Public Key Info:                        
            Public Key Algorithm: rsaEncryption
                Public-Key: (2048 bit)
                Modulus:
                    00:80:03:44:30:c7:6a:93:e3:3a:94:8e:b6:a8:70:
                    e0:19:eb:00:de:85:a9:b8:57:b9:e5:2e:0a:db:c0:
                    1b:e4:56:c0:32:7c:f3:54:65:ca:20:02:df:6c:3b:
                    f6:7e:29:71:3d:aa:69:ac:e9:61:17:65:9c:9b:94:
                    74:40:a2:67:f8:65:26:2c:a0:cf:2a:2e:15:7d:04:
                    c2:c2:44:d7:28:8a:c0:a7:65:ac:e5:eb:c6:67:ce:
                    74:82:47:0a:af:3f:7d:a9:d8:de:49:39:9e:17:49:
                    b4:f7:86:39:e1:02:25:6b:6e:94:82:95:3b:f2:62:
                    07:6a:41:33:0b:3a:d2:31:a7:2b:68:c7:73:49:2f:
                    7b:5c:83:51:05:5b:a4:5d:01:cf:3a:b9:98:f4:a1:
                    6b:20:85:f9:3f:aa:e7:5e:e6:35:82:43:1e:d2:ed:
                    90:8c:24:47:22:06:95:ab:8b:fd:12:0f:23:13:74:
                    07:f4:02:e0:94:71:0e:7b:b8:ae:02:54:84:d6:e3:
                    eb:91:a5:41:7b:6e:c5:26:57:9d:b6:ab:58:f0:e4:
                    c4:d0:a1:9e:23:31:02:ff:60:98:a3:4e:2b:d2:58:
                    6c:d1:12:a8:95:cb:19:69:0d:76:bc:50:e1:ed:6a:
                    e3:a9:42:9a:b5:76:55:fb:34:41:b3:00:9a:99:cb:
                    9c:d9
                Exponent: 65537 (0x10001)
        X509v3 extensions:
            X509v3 Subject Key Identifier: 
                6D:26:4A:D1:30:4C:15:D6:EE:9B:63:95:AF:C1:63:7F:8B:83:31:68
    //算法 参数 已加密的hash值:用CA的私钥对证书的所有域以及这些域的Hash值一起加密
    Signature Algorithm: sha256WithRSAEncryption
         08:2d:00:a8:6e:ad:67:87:6b:20:31:b0:d3:0f:52:64:cf:23:
         ab:05:3a:53:8b:30:05:3e:47:0a:5c:19:1a:b0:e9:8d:4b:1d:
         2c:f6:41:33:ba:b3:67:0a:77:46:bb:46:57:ef:f9:66:d8:68:
         5d:41:be:3c:1d:35:f3:a3:58:4a:f4:f0:7f:87:1b:e4:0c:0c:
         84:91:08:23:4c:9f:82:44:da:11:b1:60:b9:db:da:f6:36:58:
         25:f6:ae:dc:4c:24:0d:1f:65:05:ee:9c:9c:c5:64:4a:a0:21:
         4c:53:5a:3c:3e:c6:ad:67:cb:36:5e:6d:58:85:f5:2f:dd:6d:
         c8:cc:7b:09:ba:8c:bd:6f:e8:53:d1:88:8d:35:90:48:ba:db:
         5b:16:22:31:46:da:aa:91:ba:50:57:a5:38:08:af:05:ae:a1:
         ec:3d:4f:fe:06:88:c8:60:9d:b9:16:00:7a:74:c8:a1:85:53:
         dd:b5:0c:b7:09:f3:d4:e5:07:29:12:05:c2:83:84:c3:96:ec:
         ba:ad:12:ad:1d:cf:52:d3:e3:ce:47:6a:89:4a:b1:39:c3:d2:
         b7:e0:d5:77:d9:02:e5:17:89:09:00:b3:cb:38:38:ef:97:90:
         35:27:41:90:cf:b7:98:34:21:69:bd:64:ad:2b:23:85:97:96:
         6b:74:56:9b

1.3.4 为什么android要用这样的方式加密

那么能不能继续伪造数字签名呢?不可能,因为没有数字证书对应的私钥。
所以,如果要重新打包后的应用程序能再Android设备上安装,必须对其进行重签名。

1.3.5 总结

1.3.5.1 概念

1.3.5.2 流程

关于签名相关知识因为本人对安全这块涉及很少,所以基本都是引用自blog(https://blog.csdn.net/jiangwei0910410003/article/details/50402000) 的内容

二.问题分析
前面我们对相关的知识做了梳理,现在回到我们的需求上来。已知需求为:要求系统级权限应用使用系统签名文件和指定的第三方签名文件都可以完成验证。在之前的PKMS服务的介绍中我们知道在应用安装开机扫描的时候是需要做签名验证的,我们现在以应用安装为入口对源码进行跟踪。(关于PKMS服务的源码分析blog会在后续更新出来)

下面的代码基于android6.0,设计的代码文件路径如下:
frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
frameworks/base/core/java/android/content/pm/PackageParser.java
libcore/luni/src/main/java/java/util/jar/StrictJarFile.java

2.1 installPackageLI

这边要先声明 installPackageLI 并不是一个应用安装的入口方法,应用的安装涉及到的服务还有 PackageManagerInstallerService 服务,我们可以简单的把应用的安装分为两个步骤:一是拷贝阶段,二是安装阶段。而这个过程的控制则是由 PackageManagerInstallerService 进行控制的,我们这边要介绍的 installPackageLI 方法算是 第二阶段的入口方法(因为本文主要是介绍签名验证,关于应用安装过程会有跳过,毕竟这个不是本文的重点,后续文章会介绍6.0应用安装的过程。)

installPackageLI 方法比较长,篇幅原因就不全部拷贝出来了,这边列举下该方法做了哪些东西

这边我们的重点在第二点收集应用的签名和第四点中调用的 verifySignaturesLP 方法。首先我们先看看第二点的代码

        final PackageParser.Package pkg;
        try {
            pkg = pp.parsePackage(tmpPackageFile, parseFlags);
        } catch (PackageParserException e) {
            res.setError("Failed parse during installPackageLI", e);
            return;
        }

        // Mark that we have an install time CPU ABI override.
        pkg.cpuAbiOverride = args.abiOverride;

        String pkgName = res.name = pkg.packageName;
        if ((pkg.applicationInfo.flags&ApplicationInfo.FLAG_TEST_ONLY) != 0) {
            if ((installFlags & PackageManager.INSTALL_ALLOW_TEST) == 0) {
                res.setError(INSTALL_FAILED_TEST_ONLY, "installPackageLI");
                return;
            }
        }

    //收集应用签名    
        try {
            pp.collectCertificates(pkg, parseFlags);
            pp.collectManifestDigest(pkg);
        } catch (PackageParserException e) {
            res.setError("Failed collect during installPackageLI", e);
            return;
        }

代码很简单,涉及到一个新的类 ackageParser.Package,我们跟踪进 collectCertificates 方法中查看

2.2 collectCertificates

    public void collectCertificates(Package pkg, int flags) throws PackageParserException {
        pkg.mCertificates = null;
        pkg.mSignatures = null;
        pkg.mSigningKeys = null;

        collectCertificates(pkg, new File(pkg.baseCodePath), flags);

        if (!ArrayUtils.isEmpty(pkg.splitCodePaths)) {
            for (String splitCodePath : pkg.splitCodePaths) {
                collectCertificates(pkg, new File(splitCodePath), flags);
            }
        }
    }

这边我们重点关注重载方法 collectCertificates 这里面才是真正实现部分

    private static void collectCertificates(Package pkg, File apkFile, int flags)
            throws PackageParserException {
        final String apkPath = apkFile.getAbsolutePath();

        StrictJarFile jarFile = null;
        try {
            jarFile = new StrictJarFile(apkPath);

            // Always verify manifest, regardless of source
            final ZipEntry manifestEntry = jarFile.findEntry(ANDROID_MANIFEST_FILENAME);
            if (manifestEntry == null) {
                throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST,
                        "Package " + apkPath + " has no manifest");
            }

            final List<ZipEntry> toVerify = new ArrayList<>();
            toVerify.add(manifestEntry);

            // If we're parsing an untrusted package, verify all contents
            if ((flags & PARSE_IS_SYSTEM) == 0) {
                final Iterator<ZipEntry> i = jarFile.iterator();
                while (i.hasNext()) {
                    final ZipEntry entry = i.next();

                    if (entry.isDirectory()) continue;
                    if (entry.getName().startsWith("META-INF/")) continue;
                    if (entry.getName().equals(ANDROID_MANIFEST_FILENAME)) continue;

                    toVerify.add(entry);
                }
            }

            // Verify that entries are signed consistently with the first entry
            // we encountered. Note that for splits, certificates may have
            // already been populated during an earlier parse of a base APK.
            for (ZipEntry entry : toVerify) {
                final Certificate[][] entryCerts = loadCertificates(jarFile, entry);
                if (ArrayUtils.isEmpty(entryCerts)) {
                    throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
                            "Package " + apkPath + " has no certificates at entry "
                            + entry.getName());
                }
                final Signature[] entrySignatures = convertToSignatures(entryCerts);

                if (pkg.mCertificates == null) {
                    pkg.mCertificates = entryCerts;
                    pkg.mSignatures = entrySignatures;
                    pkg.mSigningKeys = new ArraySet<PublicKey>();
                    for (int i=0; i < entryCerts.length; i++) {
                        pkg.mSigningKeys.add(entryCerts[i][0].getPublicKey());
                    }
                } else {
                    if (!Signature.areExactMatch(pkg.mSignatures, entrySignatures)) {
                        throw new PackageParserException(
                                INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES, "Package " + apkPath
                                        + " has mismatched certificates at entry "
                                        + entry.getName());
                    }
                }
            }
        } catch (GeneralSecurityException e) {
            throw new PackageParserException(INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING,
                    "Failed to collect certificates from " + apkPath, e);
        } catch (IOException | RuntimeException e) {
            throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,
                    "Failed to collect certificates from " + apkPath, e);
        } finally {
            closeQuietly(jarFile);
        }
    }

这边我们重点关注这里

                    pkg.mCertificates = entryCerts;
                    pkg.mSignatures = entrySignatures;
                    pkg.mSigningKeys = new ArraySet<PublicKey>();
                    for (int i=0; i < entryCerts.length; i++) {
                        pkg.mSigningKeys.add(entryCerts[i][0].getPublicKey());
                    }

因为我们的需求是增加一个支持系统级权限的签名,所以关于apk和签名校对来保证apk未被篡改不是我们关注的重点,我们关注的重点应该是证书文件(RSA文件)里面的公钥和我们需要比对的公钥是否是一致的。
所以前面签名校对我们直接跳过,直接看这边获取到的公钥信息保存在上面代码所示的位置。

关于这个签名信息的获取过程建议查看姜老师的这篇blog:https://blog.csdn.net/jiangwei0910410003/article/details/50443505

现在我们获取到了apk的公钥信息,那么在哪里进行比较呢?这时候我们回到 2.1 中 第四点提到的 verifySignaturesLP 方法
我们先来看看这个方法里面都做了什么

2.4 verifySignaturesLP

    private void verifySignaturesLP(PackageSetting pkgSetting, PackageParser.Package pkg)
            throws PackageManagerException {
        if (pkgSetting.signatures.mSignatures != null) {
            // Already existing package. Make sure signatures match
            boolean match = compareSignatures(pkgSetting.signatures.mSignatures, pkg.mSignatures)
                    == PackageManager.SIGNATURE_MATCH;
            if (!match) {
                match = compareSignaturesCompat(pkgSetting.signatures, pkg)
                        == PackageManager.SIGNATURE_MATCH;
            }
            if (!match) {
                match = compareSignaturesRecover(pkgSetting.signatures, pkg)
                        == PackageManager.SIGNATURE_MATCH;
            }
            if (!match) {
                throw new PackageManagerException(INSTALL_FAILED_UPDATE_INCOMPATIBLE, "Package "
                        + pkg.packageName + " signatures do not match the "
                        + "previously installed version; ignoring!");
            }
        }
        
        //这边是重点了 检查shared uid的签名信息
        // Check for shared user signatures
        if (pkgSetting.sharedUser != null && pkgSetting.sharedUser.signatures.mSignatures != null) {
            // Already existing package. Make sure signatures match
            boolean match = compareSignatures(pkgSetting.sharedUser.signatures.mSignatures,
                    pkg.mSignatures) == PackageManager.SIGNATURE_MATCH;
            if (!match) {
                match = compareSignaturesCompat(pkgSetting.sharedUser.signatures, pkg)
                        == PackageManager.SIGNATURE_MATCH;
            }
            if (!match) {
                match = compareSignaturesRecover(pkgSetting.sharedUser.signatures, pkg)
                        == PackageManager.SIGNATURE_MATCH;
            }
            if (!match) {
                throw new PackageManagerException(INSTALL_FAILED_SHARED_USER_INCOMPATIBLE,
                        "Package " + pkg.packageName
                        + " has no signatures that match those in shared user "
                        + pkgSetting.sharedUser.name + "; ignoring!");
            }
        }
    }

如上面代码所示的,在这里会对 shared uid 的apk进行签名认证,那么我们现在思路有了,因为到这里的时候apk的公钥信息已经收集到了,那么只需要在这里多做一个判断:判断公钥信息是否和指定的第三方公钥信息一致就可以了。

verifySignaturesLP 并不止是在 installPackageLI 被调用,在 scanPackageDirtyLI 都也有调用该方法,而 scanPackageDirtyLI 在安装apk和开机扫描apk(例如:installNewPackageLI replaceSystemPackageLI replaceNonSystemPackageLI)的时候都会被调用到,所以我们只需要在这个方法里面添加判断即可全部覆盖到。

三.解决方案

    private boolean verifySignaturesLP(PackageSetting pkgSetting,
            PackageParser.Package pkg) {
        if (pkgSetting.signatures.mSignatures != null) {
            // Already existing package. Make sure signatures match
            if (compareSignatures(pkgSetting.signatures.mSignatures, pkg.mSignatures) !=
                PackageManager.SIGNATURE_MATCH) {
                    Slog.e(TAG, "Package " + pkg.packageName
                            + " signatures do not match the previously installed version; ignoring!");
                    mLastScanError = PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE;
                    return false;
                }
        }
        // Check for shared user signatures
        if (pkgSetting.sharedUser != null && pkgSetting.sharedUser.signatures.mSignatures != null) {
            if( !isNeedSystemSign || (isNeedPackageSignatures() && checkPackageSignatures(pkg))) {
                isNeedSystemSign = true;
                return true;
            }else if (compareSignatures(pkgSetting.sharedUser.signatures.mSignatures,
                    pkg.mSignatures) != PackageManager.SIGNATURE_MATCH) {
                Slog.e(TAG, "Package " + pkg.packageName
                        + " has no signatures that match those in shared user "
                        + pkgSetting.sharedUser.name + "; ignoring!");
                mLastScanError = PackageManager.INSTALL_FAILED_SHARED_USER_INCOMPATIBLE;
                return false;
            }
        }
        return true;
    }
    private boolean checkPackageSignatures(PackageParser.Package pkg){
        if (DEBUG_INSTALL) Slog.i(TAG, "checkPackageSignatures("+pkg.packageName+")");
        if(pkg == null)
            return false;
        FileInputStream is =null;
        try{
            is= new FileInputStream(new File("/etc/CERT.RSA"));
        }catch(java.io.FileNotFoundException e){
            e.printStackTrace();
            return false;
        }
        byte[] readBuffer=new byte[8192];;
        try{
            while (is.read(readBuffer, 0, readBuffer.length) != -1) {
                // not using
            }
            is.close();
        }catch(java.io.IOException e){
            e.printStackTrace();
            return false;
        }

        String publickeytxt = null;

        Signature auths=new Signature(readBuffer);
        //Slog.i(TAG, "print:auths.toCharsString:"+auths.toCharsString());
        try{
            Slog.i(TAG, "auths.PublicKey:"+auths.getPublicKey());
        }catch (Exception e) {
            try{
                StringBuffer sb=new StringBuffer();
                InputStreamReader read = new InputStreamReader(new FileInputStream(new File("/etc/CERT.RSA")));
                BufferedReader bufferedReader = new BufferedReader(read);
                while((publickeytxt = bufferedReader.readLine()) != null){
                    sb.append(publickeytxt);
                }
                publickeytxt = sb.toString();
                Slog.i(TAG, "PublicKey: " + publickeytxt);
                read.close();
                bufferedReader.close();
            }catch(java.io.IOException e1){
            }
        }

        Signature authsSignatures[] = new Signature[1];
        authsSignatures[0]=auths;
        if(pkg.mSignatures[0]==null){
            Slog.i(TAG, "package have not Signatures");
            return false;
        }
        if (DEBUG_INSTALL){
            try{
                Slog.i(TAG, "pkg.mSignatures[0]:"+ pkg.mSignatures[0].getPublicKey());
                Slog.i(TAG, "publickeytxt.length(): " + publickeytxt.length() + "  index :"
                        + (pkg.mSignatures[0].getPublicKey() + "").indexOf(publickeytxt));
            }catch (Exception e){
            }
        }
        //if(PackageManager.SIGNATURE_MATCH == compareSignatures(authsSignatures,pkg.mSignatures)){
        //  return true;
        //}
        try{
            if(authsSignatures[0].getPublicKey().equals(pkg.mSignatures[0].getPublicKey())){
                isNeedSystemSign = false;
                Slog.i(TAG, "verification success1");
                return true;
            }
        }catch(java.security.cert.CertificateException e){
            try{
                if((publickeytxt.length() > 0) && (pkg.mSignatures[0].getPublicKey() + "").indexOf(publickeytxt) > 0){
                    isNeedSystemSign = false;
                    Slog.i(TAG, "verification success2");
                    return true;
                }
            }catch (Exception e1){
            }
        }
        Slog.i(TAG, "verification  failed");
        return false;
    }

四.补充

方案中获取RSA文件公钥的方法在app中是无法直接使用的,因为有些方法是hide的只能在framework层或者反射使用,这样不是很方便,这边根据源码提取一个简单的类可以在app中直接使用。有兴趣的同学也可以用jks文件打包一个apk进行验证:用同一个jks文件打包两个apk然后分别解压获取RAS文件 用下面的方法获取公钥验证是否一致。


import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.lang.ref.SoftReference;
import java.security.PublicKey;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.util.Arrays;

public class SignatureTest {

    private final byte[] mSignature;
    private int mHashCode;
    private boolean mHaveHashCode;
    private SoftReference<String> mStringRef;
    private Certificate[] mCertificateChain;

    /**
     * Create Signature from an existing raw byte array.
     */
    public SignatureTest(byte[] signature) {
        mSignature = signature.clone();
        mCertificateChain = null;
    }

    /**
     * Create signature from a certificate chain. Used for backward
     * compatibility.
     *
     * @throws CertificateEncodingException
     *
     */
    public SignatureTest(Certificate[] certificateChain) throws CertificateEncodingException {
        mSignature = certificateChain[0].getEncoded();
        if (certificateChain.length > 1) {
            mCertificateChain = Arrays.copyOfRange(certificateChain, 1, certificateChain.length);
        }
    }

    private static final int parseHexDigit(int nibble) {
        if ('0' <= nibble && nibble <= '9') {
            return nibble - '0';
        } else if ('a' <= nibble && nibble <= 'f') {
            return nibble - 'a' + 10;
        } else if ('A' <= nibble && nibble <= 'F') {
            return nibble - 'A' + 10;
        } else {
            throw new IllegalArgumentException("Invalid character " + nibble + " in hex string");
        }
    }

    /**
     * Create Signature from a text representation previously returned by
     * {@link #toChars} or {@link #toCharsString()}. Signatures are expected to
     * be a hex-encoded ASCII string.
     *
     * @param text hex-encoded string representing the signature
     * @throws IllegalArgumentException when signature is odd-length
     */
    public SignatureTest(String text) {
        final byte[] input = text.getBytes();
        final int N = input.length;

        if (N % 2 != 0) {
            throw new IllegalArgumentException("text size " + N + " is not even");
        }

        final byte[] sig = new byte[N / 2];
        int sigIndex = 0;

        for (int i = 0; i < N;) {
            final int hi = parseHexDigit(input[i++]);
            final int lo = parseHexDigit(input[i++]);
            sig[sigIndex++] = (byte) ((hi << 4) | lo);
        }

        mSignature = sig;
    }

    /**
     * Encode the Signature as ASCII text.
     */
    public char[] toChars() {
        return toChars(null, null);
    }

    /**
     * Encode the Signature as ASCII text in to an existing array.
     *
     * @param existingArray Existing char array or null.
     * @param outLen Output parameter for the number of characters written in
     * to the array.
     * @return Returns either <var>existingArray</var> if it was large enough
     * to hold the ASCII representation, or a newly created char[] array if
     * needed.
     */
    public char[] toChars(char[] existingArray, int[] outLen) {
        byte[] sig = mSignature;
        final int N = sig.length;
        final int N2 = N*2;
        char[] text = existingArray == null || N2 > existingArray.length
                ? new char[N2] : existingArray;
        for (int j=0; j<N; j++) {
            byte v = sig[j];
            int d = (v>>4)&0xf;
            text[j*2] = (char)(d >= 10 ? ('a' + d - 10) : ('0' + d));
            d = v&0xf;
            text[j*2+1] = (char)(d >= 10 ? ('a' + d - 10) : ('0' + d));
        }
        if (outLen != null) outLen[0] = N;
        return text;
    }

    /**
     * Return the result of {@link #toChars()} as a String.
     */
    public String toCharsString() {
        String str = mStringRef == null ? null : mStringRef.get();
        if (str != null) {
            return str;
        }
        str = new String(toChars());
        mStringRef = new SoftReference<String>(str);
        return str;
    }

    /**
     * @return the contents of this signature as a byte array.
     */
    public byte[] toByteArray() {
        byte[] bytes = new byte[mSignature.length];
        System.arraycopy(mSignature, 0, bytes, 0, mSignature.length);
        return bytes;
    }

    /**
     * Returns the public key for this signature.
     *
     * @throws CertificateException when Signature isn't a valid X.509
     *             certificate; shouldn't happen.
     *
     */
    public PublicKey getPublicKey() throws CertificateException {
        final CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
        final ByteArrayInputStream bais = new ByteArrayInputStream(mSignature);
        final Certificate cert = certFactory.generateCertificate(bais);
        return cert.getPublicKey();
    }




    @Override
    public int hashCode() {
        if (mHaveHashCode) {
            return mHashCode;
        }
        mHashCode = Arrays.hashCode(mSignature);
        mHaveHashCode = true;
        return mHashCode;
    }




    public static PublicKey getPublickey(String path){

        FileInputStream is =null;
        try{
            is= new FileInputStream(new File(path));
        }catch(java.io.FileNotFoundException e){
            e.printStackTrace();
            return null;
        }
        byte[] readBuffer=new byte[8192];;
        try{
            while (is.read(readBuffer, 0, readBuffer.length) != -1) {
                // not using
            }
            is.close();
        }catch(java.io.IOException e){
            e.printStackTrace();
            return null;
        }
        String publickeytxt = null;
        SignatureTest auths=new SignatureTest(readBuffer);
        try {
            return auths.getPublicKey();
        } catch (CertificateException e) {
            e.printStackTrace();
        }

        return null;
    }
    


}

上一篇 下一篇

猜你喜欢

热点阅读