Android M源码学习——PMS(PackageManagerService)系统

  • 一个应用完整的安装过程如下图所示

    《Android M源码学习——PMS(PackageManagerService)系统》 PMS系统.png

    入口函数有两处,一处由系统应用PackageInstaller调用installPackageWithVerificationAndEncryption()方法实现有界面的安装过程,一处由System级的APP调用installPackage()实现静默安装,本质其实是一样的:

    @Override
    public void installPackageWithVerificationAndEncryption(Uri packageURI,
            IPackageInstallObserver observer, int flags, String installerPackageName,
            VerificationParams verificationParams, ContainerEncryptionParams encryptionParams) {
        installCommon(packageURI, new LegacyPackageInstallObserver(observer), flags,
                installerPackageName, verificationParams, encryptionParams);
    }

    @Override
    public void installPackage(Uri packageURI, PackageInstallObserver observer,
            int flags, String installerPackageName) {
        final VerificationParams verificationParams = new VerificationParams(null, null,
                null, VerificationParams.NO_UID, null);
        installCommon(packageURI, observer, flags, installerPackageName, verificationParams, null);
    }

我们重点看一下apk的签名校验过程,讲之前先说明一下APK文件解压后的META-INF目录,一般它有如下三个文件:
ANDROIDR.RSA、ANDROIDR.SF、MANIFEST.MF
APK要想安装成功,在校验的过程中要满足如下三个条件:

  • MF文件中的SHA-1值与对应文件的真实SHA-1值要相等(不计META-INF目录)
  • SF文件中的SHA-1值与MF文件本身与文件中的各子项作SHA-1和Base64编码后相等
  • SF文件的签名信息与RSA文件的内容要一致

从函数collectCertificates()开始:

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

        StrictJarFile jarFile = null;
        try {
            /// TODO: [ALPS01655835][L.AOSP.EARLY.DEV] Bring-up build error on frameworks/base @{
            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);
        }
    }

首先是StrictJarFile对象的构造:

    public StrictJarFile(String fileName) throws IOException, SecurityException {
        this.nativeHandle = nativeOpenJarFile(fileName);
        this.raf = new RandomAccessFile(fileName, "r");

        try {
            // Read the MANIFEST and signature files up front and try to
            // parse them. We never want to accept a JAR File with broken signatures
            // or manifests, so it's best to throw as early as possible.
            HashMap<String, byte[]> metaEntries = getMetaEntries();
            this.manifest = new Manifest(metaEntries.get(JarFile.MANIFEST_NAME), true);
            this.verifier = new JarVerifier(fileName, manifest, metaEntries);
            Set<String> files = this.manifest.getEntries().keySet();
            for (String file : files) {
                if (findEntry(file) == null) {
                    throw new SecurityException(fileName + ": File " + file + " in manifest does not exist");
                }
            }

            isSigned = verifier.readCertificates() && verifier.isSignedJar();
        } catch (IOException | SecurityException e) {
            nativeClose(this.nativeHandle);
            IoUtils.closeQuietly(this.raf);
            throw e;
        }

        guard.open("close");
    }

重点是isSigned 进行赋值时使用的两个方法:JarVerifier的readCertificates()和isSignedJar()方法:

    /**
     * If the associated JAR file is signed, check on the validity of all of the
     * known signatures.
     *
     * @return {@code true} if the associated JAR is signed and an internal
     *         check verifies the validity of the signature(s). {@code false} if
     *         the associated JAR file has no entries at all in its {@code
     *         META-INF} directory. This situation is indicative of an invalid
     *         JAR file.
     *         <p>
     *         Will also return {@code true} if the JAR file is <i>not</i>
     *         signed.
     * @throws SecurityException
     *             if the JAR file is signed and it is determined that a
     *             signature block file contains an invalid signature for the
     *             corresponding signature file.
     */
    synchronized boolean readCertificates() {
        if (metaEntries.isEmpty()) {
            return false;
        }

        Iterator<String> it = metaEntries.keySet().iterator();
        while (it.hasNext()) {
            String key = it.next();
            if (key.endsWith(".DSA") || key.endsWith(".RSA") || key.endsWith(".EC")) {
                verifyCertificate(key);
                it.remove();
            }
        }
        return true;
    }
    /**
     * Returns a <code>boolean</code> indication of whether or not the
     * associated jar file is signed.
     *
     * @return {@code true} if the JAR is signed, {@code false}
     *         otherwise.
     */
    boolean isSignedJar() {
        return certificates.size() > 0;
    }

SF文件校验:

  /**
     * @param certFile
     */
    private void verifyCertificate(String certFile) {
        // Found Digital Sig, .SF should already have been read
        String signatureFile = certFile.substring(0, certFile.lastIndexOf('.')) + ".SF";
        byte[] sfBytes = metaEntries.get(signatureFile);
        if (sfBytes == null) {
            return;
        }

        byte[] manifestBytes = metaEntries.get(JarFile.MANIFEST_NAME);
        // Manifest entry is required for any verifications.
        if (manifestBytes == null) {
            return;
        }
        //获取证书信息,验证SF文件的签名信息是否与RSA文件的内部一致
        byte[] sBlockBytes = metaEntries.get(certFile);
        try {
            Certificate[] signerCertChain = JarUtils.verifySignature(
                    new ByteArrayInputStream(sfBytes),
                    new ByteArrayInputStream(sBlockBytes));
            if (signerCertChain != null) {
                certificates.put(signatureFile, signerCertChain);
            }
        } catch (IOException e) {
            return;
        } catch (GeneralSecurityException e) {
            throw failedVerification(jarName, signatureFile);
        }

        // Verify manifest hash in .sf file
        Attributes attributes = new Attributes();
        HashMap<String, Attributes> entries = new HashMap<String, Attributes>();
        try {
            ManifestReader im = new ManifestReader(sfBytes, attributes);
            im.readEntries(entries, null);
        } catch (IOException e) {
            return;
        }

        // Do we actually have any signatures to look at?
        if (attributes.get(Attributes.Name.SIGNATURE_VERSION) == null) {
            return;
        }

        boolean createdBySigntool = false;
        String createdBy = attributes.getValue("Created-By");
        if (createdBy != null) {
            createdBySigntool = createdBy.indexOf("signtool") != -1;
        }

        // Use .SF to verify the mainAttributes of the manifest
        // If there is no -Digest-Manifest-Main-Attributes entry in .SF
        // file, such as those created before java 1.5, then we ignore
        // such verification.
        //MF文件本身的签名及MF文件中各属性块的签名是否与SF文件中的信息匹配
        if (mainAttributesEnd > 0 && !createdBySigntool) {
            String digestAttribute = "-Digest-Manifest-Main-Attributes";
            if (!verify(attributes, digestAttribute, manifestBytes, 0, mainAttributesEnd, false, true)) {
                throw failedVerification(jarName, signatureFile);
            }
        }

        // Use .SF to verify the whole manifest.
        String digestAttribute = createdBySigntool ? "-Digest" : "-Digest-Manifest";
        if (!verify(attributes, digestAttribute, manifestBytes, 0, manifestBytes.length, false, false)) {
            Iterator<Map.Entry<String, Attributes>> it = entries.entrySet().iterator();
            while (it.hasNext()) {
                Map.Entry<String, Attributes> entry = it.next();
                Manifest.Chunk chunk = manifest.getChunk(entry.getKey());
                if (chunk == null) {
                    return;
                }
                if (!verify(entry.getValue(), "-Digest", manifestBytes,
                        chunk.start, chunk.end, createdBySigntool, false)) {
                    throw invalidDigest(signatureFile, entry.getKey(), jarName);
                }
            }
        }
        metaEntries.put(signatureFile, null);
        signatures.put(signatureFile, entries);
    }

使用libcore\luni\src\main\java\org\apache\harmony\security\utils\JarUtils.java的verifySignature()方法获取证书信息:

 public static Certificate[] verifySignature(InputStream signature, InputStream
            signatureBlock) throws IOException, GeneralSecurityException {

        BerInputStream bis = new BerInputStream(signatureBlock);
        ContentInfo info = (ContentInfo)ContentInfo.ASN1.decode(bis);
        SignedData signedData = info.getSignedData();
        if (signedData == null) {
            throw new IOException("No SignedData found");
        }
        Collection<org.apache.harmony.security.x509.Certificate> encCerts
                = signedData.getCertificates();
        if (encCerts.isEmpty()) {
            return null;
        }
        X509Certificate[] certs = new X509Certificate[encCerts.size()];
        CertificateFactory cf = CertificateFactory.getInstance("X.509");
        int i = 0;
        for (org.apache.harmony.security.x509.Certificate encCert : encCerts) {
            final byte[] encoded = encCert.getEncoded();
            final InputStream is = new ByteArrayInputStream(encoded);
            certs[i++] = new VerbatimX509Certificate((X509Certificate) cf.generateCertificate(is),
                    encoded);
        }

        List<SignerInfo> sigInfos = signedData.getSignerInfos();
        SignerInfo sigInfo;
        if (!sigInfos.isEmpty()) {
            sigInfo = sigInfos.get(0);
        } else {
            return null;
        }

        // Issuer
        X500Principal issuer = sigInfo.getIssuer();

        // Certificate serial number
        BigInteger snum = sigInfo.getSerialNumber();

        // Locate the certificate
        int issuerSertIndex = 0;
        for (i = 0; i < certs.length; i++) {
            if (issuer.equals(certs[i].getIssuerDN()) &&
                    snum.equals(certs[i].getSerialNumber())) {
                issuerSertIndex = i;
                break;
            }
        }
        if (i == certs.length) { // No issuer certificate found
            return null;
        }

        if (certs[issuerSertIndex].hasUnsupportedCriticalExtension()) {
            throw new SecurityException("Can not recognize a critical extension");
        }

        // Get Signature instance
        final String daOid = sigInfo.getDigestAlgorithm();
        final String daName = sigInfo.getDigestAlgorithmName();
        final String deaOid = sigInfo.getDigestEncryptionAlgorithm();
        final String deaName = sigInfo.getDigestEncryptionAlgorithmName();

        String alg = null;
        Signature sig = null;

        if (daOid != null && deaOid != null) {
            alg = daOid + "with" + deaOid;
            try {
                sig = Signature.getInstance(alg);
            } catch (NoSuchAlgorithmException e) {
            }

            // Try to convert to names instead of OID.
            if (sig == null && daName != null && deaName != null) {
                alg = daName + "with" + deaName;
                try {
                    sig = Signature.getInstance(alg);
                } catch (NoSuchAlgorithmException e) {
                }
            }
        }

        if (sig == null && deaOid != null) {
            alg = deaOid;
            try {
                sig = Signature.getInstance(alg);
            } catch (NoSuchAlgorithmException e) {
            }

            if (sig == null) {
                alg = deaName;
                try {
                    sig = Signature.getInstance(alg);
                } catch (NoSuchAlgorithmException e) {
                }
            }
        }

        // We couldn't find a valid Signature type.
        if (sig == null) {
            return null;
        }

        sig.initVerify(certs[issuerSertIndex]);

        // If the authenticatedAttributes field of SignerInfo contains more than zero attributes,
        // compute the message digest on the ASN.1 DER encoding of the Attributes value.
        // Otherwise, compute the message digest on the data.
        List<AttributeTypeAndValue> atr = sigInfo.getAuthenticatedAttributes();

        byte[] sfBytes = new byte[signature.available()];
        signature.read(sfBytes);

        if (atr == null) {
            sig.update(sfBytes);
        } else {
            sig.update(sigInfo.getEncodedAuthenticatedAttributes());

            // If the authenticatedAttributes field contains the message-digest attribute,
            // verify that it equals the computed digest of the signature file
            byte[] existingDigest = null;
            for (AttributeTypeAndValue a : atr) {
                if (Arrays.equals(a.getType().getOid(), MESSAGE_DIGEST_OID)) {
                    if (existingDigest != null) {
                        throw new SecurityException("Too many MessageDigest attributes");
                    }
                    Collection<?> entries = a.getValue().getValues(ASN1OctetString.getInstance());
                    if (entries.size() != 1) {
                        throw new SecurityException("Too many values for MessageDigest attribute");
                    }
                    existingDigest = (byte[]) entries.iterator().next();
                }
            }

            // RFC 2315 section 9.1: if authenticatedAttributes is present, it
            // must have a message-digest attribute.
            if (existingDigest == null) {
                throw new SecurityException("Missing MessageDigest in Authenticated Attributes");
            }

            MessageDigest md = null;
            if (daOid != null) {
                md = MessageDigest.getInstance(daOid);
            }
            if (md == null && daName != null) {
                md = MessageDigest.getInstance(daName);
            }
            if (md == null) {
                return null;
            }

            byte[] computedDigest = md.digest(sfBytes);
            if (!Arrays.equals(existingDigest, computedDigest)) {
                throw new SecurityException("Incorrect MD");
            }
        }

        if (!sig.verify(sigInfo.getEncryptedDigest())) {
            throw new SecurityException("Incorrect signature");
        }

        return createChain(certs[issuerSertIndex], certs);
    }

然后看下loadCertificates()方法:

    private static Certificate[][] loadCertificates(StrictJarFile jarFile, ZipEntry entry)
            throws PackageParserException {
        InputStream is = null;
        try {
            // We must read the stream for the JarEntry to retrieve
            // its certificates.
            is = jarFile.getInputStream(entry);
            readFullyIgnoringContents(is);
            return jarFile.getCertificateChains(entry);
        } catch (IOException | RuntimeException e) {
            throw new PackageParserException(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
                    "Failed reading " + entry.getName() + " in " + jarFile, e);
        } finally {
            IoUtils.closeQuietly(is);
        }
    }

进入\libcore\luni\src\main\java\java\util\jar\StrictJarFile.java的getInputStream()方法

public InputStream getInputStream(ZipEntry ze){
    final InputStream is=getZipInputStream(ze);
    if(isSigned){
        JarVerifier.VerifierEntry entry=verifier.initEntry(ze.getName());
        if(entry==null){
            return is;
        }
        return new JarFile.JarFileInputStream(is,ze.getSize(),entry);
    }
    return is;
}

可能使用的算法:

    private static final String[] DIGEST_ALGORITHMS = new String[] {
        "SHA-512",
        "SHA-384",
        "SHA-256",
        "SHA1",
    };

构造一个VerifierEntry对象:

 VerifierEntry initEntry(String name) {
        // If no manifest is present by the time an entry is found,
        // verification cannot occur. If no signature files have
        // been found, do not verify.
        if (manifest == null || signatures.isEmpty()) {
            return null;
        }

        Attributes attributes = manifest.getAttributes(name);
        // entry has no digest
        if (attributes == null) {
            return null;
        }

        ArrayList<Certificate[]> certChains = new ArrayList<Certificate[]>();
        Iterator<Map.Entry<String, HashMap<String, Attributes>>> it = signatures.entrySet().iterator();
        while (it.hasNext()) {
            Map.Entry<String, HashMap<String, Attributes>> entry = it.next();
            HashMap<String, Attributes> hm = entry.getValue();
            if (hm.get(name) != null) {
                // Found an entry for entry name in .SF file
                String signatureFile = entry.getKey();
                Certificate[] certChain = certificates.get(signatureFile);
                if (certChain != null) {
                    certChains.add(certChain);
                }
            }
        }

        // entry is not signed
        if (certChains.isEmpty()) {
            return null;
        }
        Certificate[][] certChainsArray = certChains.toArray(new Certificate[certChains.size()][]);

        for (int i = 0; i < DIGEST_ALGORITHMS.length; i++) {
            final String algorithm = DIGEST_ALGORITHMS[i];
            final String hash = attributes.getValue(algorithm + "-Digest");
            if (hash == null) {
                continue;
            }
            byte[] hashBytes = hash.getBytes(StandardCharsets.ISO_8859_1);

            try {
                return new VerifierEntry(name, MessageDigest.getInstance(algorithm), hashBytes,
                        certChainsArray, verifiedEntries);
            } catch (NoSuchAlgorithmException ignored) {
            }
        }
        return null;
    }

得到一个InputStream对象(JarFileInputStream),回到loadCertificates()方法,进入readFullylgnoringContents()

    public static long readFullyIgnoringContents(InputStream in) throws IOException {
        byte[] buffer = sBuffer.getAndSet(null);
        if (buffer == null) {
            buffer = new byte[4096];
        }

        int n = 0;
        int count = 0;
        while ((n = in.read(buffer, 0, buffer.length)) != -1) {
            count += n;
        }

        sBuffer.set(buffer);
        return count;
    }

主要是一个read方法,我们看下JarFileInputStream的read()方法:

 @Override
        public int read(byte[] buffer, int byteOffset, int byteCount) throws IOException {
            if (done) {
                return -1;
            }
            if (count > 0) {
                int r = super.read(buffer, byteOffset, byteCount);
                if (r != -1) {
                    int size = r;
                    if (count < size) {
                        size = (int) count;
                    }
                    entry.write(buffer, byteOffset, size);
                    count -= size;
                } else {
                    count = 0;
                }
                if (count == 0) {
                    done = true;
                    entry.verify();
                }
                return r;
            } else {
                done = true;
                entry.verify();
                return -1;
            }
        }

这个entry即我们之前构造的VerifierEntry,我们看下它的verify()方法:

        /**
         * Verifies that the digests stored in the manifest match the decrypted
         * digests from the .SF file. This indicates the validity of the
         * signing, not the integrity of the file, as its digest must be
         * calculated and verified when its contents are read.
         *
         * @throws SecurityException
         *             if the digest value stored in the manifest does <i>not</i>
         *             agree with the decrypted digest as recovered from the
         *             <code>.SF</code> file.
         */
        void verify() {
            byte[] d = digest.digest();
            //这里对META-INF/MANIFEST.MF文件作了摘要校验(与指定文件的真实摘要值做对比)
            if (!MessageDigest.isEqual(d, Base64.decode(hash))) {
                throw invalidDigest(JarFile.MANIFEST_NAME, name, name);
            }
            verifiedEntries.put(name, certChains);
        }

回到loadCertificates()方法,进入StrictJarFile的getCertificateChains()方法:

    /**
     * Return all certificate chains for a given {@link ZipEntry} belonging to this jar.
     * This method MUST be called only after fully exhausting the InputStream belonging
     * to this entry.
     *
     * Returns {@code null} if this jar file isn't signed or if this method is
     * called before the stream is processed.
     */
    public Certificate[][] getCertificateChains(ZipEntry ze) {
        if (isSigned) {
            return verifier.getCertificateChains(ze.getName());
        }

        return null;
    }
    原文作者:奥利奥真好吃
    原文地址: https://www.jianshu.com/p/cc7d8c3b104e
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞