1 /*
2  * Copyright (C) 2019 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.integrity;
18 
19 import static android.content.Intent.ACTION_PACKAGE_NEEDS_INTEGRITY_VERIFICATION;
20 import static android.content.Intent.EXTRA_LONG_VERSION_CODE;
21 import static android.content.Intent.EXTRA_ORIGINATING_UID;
22 import static android.content.Intent.EXTRA_PACKAGE_NAME;
23 import static android.content.integrity.AppIntegrityManager.EXTRA_STATUS;
24 import static android.content.integrity.AppIntegrityManager.STATUS_FAILURE;
25 import static android.content.integrity.AppIntegrityManager.STATUS_SUCCESS;
26 import static android.content.integrity.InstallerAllowedByManifestFormula.INSTALLER_CERTIFICATE_NOT_EVALUATED;
27 import static android.content.integrity.IntegrityUtils.getHexDigest;
28 import static android.content.pm.PackageManager.EXTRA_VERIFICATION_ID;
29 
30 import android.annotation.BinderThread;
31 import android.annotation.NonNull;
32 import android.annotation.Nullable;
33 import android.content.BroadcastReceiver;
34 import android.content.Context;
35 import android.content.Intent;
36 import android.content.IntentFilter;
37 import android.content.IntentSender;
38 import android.content.integrity.AppInstallMetadata;
39 import android.content.integrity.IAppIntegrityManager;
40 import android.content.integrity.Rule;
41 import android.content.pm.PackageInfo;
42 import android.content.pm.PackageManager;
43 import android.content.pm.PackageManagerInternal;
44 import android.content.pm.ParceledListSlice;
45 import android.content.pm.Signature;
46 import android.content.pm.SigningDetails;
47 import android.content.pm.parsing.result.ParseResult;
48 import android.content.pm.parsing.result.ParseTypeImpl;
49 import android.net.Uri;
50 import android.os.Binder;
51 import android.os.Bundle;
52 import android.os.Handler;
53 import android.os.HandlerThread;
54 import android.provider.Settings;
55 import android.util.Pair;
56 import android.util.Slog;
57 import android.util.apk.SourceStampVerificationResult;
58 import android.util.apk.SourceStampVerifier;
59 
60 import com.android.internal.R;
61 import com.android.internal.annotations.VisibleForTesting;
62 import com.android.internal.pm.parsing.PackageParser2;
63 import com.android.internal.pm.pkg.parsing.ParsingPackageUtils;
64 import com.android.internal.util.ArrayUtils;
65 import com.android.internal.util.FrameworkStatsLog;
66 import com.android.server.LocalServices;
67 import com.android.server.integrity.engine.RuleEvaluationEngine;
68 import com.android.server.integrity.model.IntegrityCheckResult;
69 import com.android.server.integrity.model.RuleMetadata;
70 import com.android.server.pm.PackageManagerServiceUtils;
71 import com.android.server.pm.parsing.PackageParserUtils;
72 
73 import java.io.ByteArrayInputStream;
74 import java.io.File;
75 import java.io.IOException;
76 import java.io.InputStream;
77 import java.nio.charset.StandardCharsets;
78 import java.nio.file.Files;
79 import java.nio.file.Path;
80 import java.security.MessageDigest;
81 import java.security.NoSuchAlgorithmException;
82 import java.security.cert.CertificateEncodingException;
83 import java.security.cert.CertificateException;
84 import java.security.cert.CertificateFactory;
85 import java.security.cert.X509Certificate;
86 import java.util.ArrayList;
87 import java.util.Arrays;
88 import java.util.Collections;
89 import java.util.HashMap;
90 import java.util.HashSet;
91 import java.util.List;
92 import java.util.Map;
93 import java.util.Set;
94 import java.util.function.Supplier;
95 import java.util.stream.Collectors;
96 import java.util.stream.Stream;
97 
98 /** Implementation of {@link AppIntegrityManagerService}. */
99 public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub {
100     /**
101      * This string will be used as the "installer" for formula evaluation when the app's installer
102      * cannot be determined.
103      *
104      * <p>This may happen for various reasons. e.g., the installing app's package name may not match
105      * its UID.
106      */
107     private static final String UNKNOWN_INSTALLER = "";
108     /**
109      * This string will be used as the "installer" for formula evaluation when the app is being
110      * installed via ADB.
111      */
112     public static final String ADB_INSTALLER = "adb";
113 
114     private static final String TAG = "AppIntegrityManagerServiceImpl";
115 
116     private static final String PACKAGE_MIME_TYPE = "application/vnd.android.package-archive";
117     private static final String BASE_APK_FILE = "base.apk";
118     private static final String ALLOWED_INSTALLERS_METADATA_NAME = "allowed-installers";
119     private static final String ALLOWED_INSTALLER_DELIMITER = ",";
120     private static final String INSTALLER_PACKAGE_CERT_DELIMITER = "\\|";
121 
122     public static final boolean DEBUG_INTEGRITY_COMPONENT = false;
123 
124     private static final Set<String> PACKAGE_INSTALLER =
125             new HashSet<>(
126                     Arrays.asList(
127                             "com.google.android.packageinstaller", "com.android.packageinstaller"));
128 
129     // Access to files inside mRulesDir is protected by mRulesLock;
130     private final Context mContext;
131     private final Handler mHandler;
132     private final PackageManagerInternal mPackageManagerInternal;
133     private final Supplier<PackageParser2> mParserSupplier;
134     private final RuleEvaluationEngine mEvaluationEngine;
135     private final IntegrityFileManager mIntegrityFileManager;
136 
137     /** Create an instance of {@link AppIntegrityManagerServiceImpl}. */
create(Context context)138     public static AppIntegrityManagerServiceImpl create(Context context) {
139         HandlerThread handlerThread = new HandlerThread("AppIntegrityManagerServiceHandler");
140         handlerThread.start();
141 
142         return new AppIntegrityManagerServiceImpl(
143                 context,
144                 LocalServices.getService(PackageManagerInternal.class),
145                 PackageParserUtils::forParsingFileWithDefaults,
146                 RuleEvaluationEngine.getRuleEvaluationEngine(),
147                 IntegrityFileManager.getInstance(),
148                 handlerThread.getThreadHandler());
149     }
150 
151     @VisibleForTesting
AppIntegrityManagerServiceImpl( Context context, PackageManagerInternal packageManagerInternal, Supplier<PackageParser2> parserSupplier, RuleEvaluationEngine evaluationEngine, IntegrityFileManager integrityFileManager, Handler handler)152     AppIntegrityManagerServiceImpl(
153             Context context,
154             PackageManagerInternal packageManagerInternal,
155             Supplier<PackageParser2> parserSupplier,
156             RuleEvaluationEngine evaluationEngine,
157             IntegrityFileManager integrityFileManager,
158             Handler handler) {
159         mContext = context;
160         mPackageManagerInternal = packageManagerInternal;
161         mParserSupplier = parserSupplier;
162         mEvaluationEngine = evaluationEngine;
163         mIntegrityFileManager = integrityFileManager;
164         mHandler = handler;
165 
166         IntentFilter integrityVerificationFilter = new IntentFilter();
167         integrityVerificationFilter.addAction(ACTION_PACKAGE_NEEDS_INTEGRITY_VERIFICATION);
168         try {
169             integrityVerificationFilter.addDataType(PACKAGE_MIME_TYPE);
170         } catch (IntentFilter.MalformedMimeTypeException e) {
171             throw new RuntimeException("Mime type malformed: should never happen.", e);
172         }
173 
174         mContext.registerReceiver(
175                 new BroadcastReceiver() {
176                     @Override
177                     public void onReceive(Context context, Intent intent) {
178                         if (!ACTION_PACKAGE_NEEDS_INTEGRITY_VERIFICATION.equals(
179                                 intent.getAction())) {
180                             return;
181                         }
182                         mHandler.post(() -> handleIntegrityVerification(intent));
183                     }
184                 },
185                 integrityVerificationFilter,
186                 /* broadcastPermission= */ null,
187                 mHandler);
188     }
189 
190     @Override
191     @BinderThread
updateRuleSet( String version, ParceledListSlice<Rule> rules, IntentSender statusReceiver)192     public void updateRuleSet(
193             String version, ParceledListSlice<Rule> rules, IntentSender statusReceiver) {
194         String ruleProvider = getCallerPackageNameOrThrow(Binder.getCallingUid());
195         if (DEBUG_INTEGRITY_COMPONENT) {
196             Slog.i(TAG, String.format("Calling rule provider name is: %s.", ruleProvider));
197         }
198 
199         mHandler.post(
200                 () -> {
201                     boolean success = true;
202                     try {
203                         mIntegrityFileManager.writeRules(version, ruleProvider, rules.getList());
204                     } catch (Exception e) {
205                         Slog.e(TAG, "Error writing rules.", e);
206                         success = false;
207                     }
208 
209                     if (DEBUG_INTEGRITY_COMPONENT) {
210                         Slog.i(
211                                 TAG,
212                                 String.format(
213                                         "Successfully pushed rule set to version '%s' from '%s'",
214                                         version, ruleProvider));
215                     }
216 
217                     FrameworkStatsLog.write(
218                             FrameworkStatsLog.INTEGRITY_RULES_PUSHED,
219                             success,
220                             ruleProvider,
221                             version);
222 
223                     Intent intent = new Intent();
224                     intent.putExtra(EXTRA_STATUS, success ? STATUS_SUCCESS : STATUS_FAILURE);
225                     try {
226                         statusReceiver.sendIntent(
227                                 mContext,
228                                 /* code= */ 0,
229                                 intent,
230                                 /* onFinished= */ null,
231                                 /* handler= */ null);
232                     } catch (Exception e) {
233                         Slog.e(TAG, "Error sending status feedback.", e);
234                     }
235                 });
236     }
237 
238     @Override
239     @BinderThread
getCurrentRuleSetVersion()240     public String getCurrentRuleSetVersion() {
241         getCallerPackageNameOrThrow(Binder.getCallingUid());
242 
243         RuleMetadata ruleMetadata = mIntegrityFileManager.readMetadata();
244         return (ruleMetadata != null && ruleMetadata.getVersion() != null)
245                 ? ruleMetadata.getVersion()
246                 : "";
247     }
248 
249     @Override
250     @BinderThread
getCurrentRuleSetProvider()251     public String getCurrentRuleSetProvider() {
252         getCallerPackageNameOrThrow(Binder.getCallingUid());
253 
254         RuleMetadata ruleMetadata = mIntegrityFileManager.readMetadata();
255         return (ruleMetadata != null && ruleMetadata.getRuleProvider() != null)
256                 ? ruleMetadata.getRuleProvider()
257                 : "";
258     }
259 
260     @Override
getCurrentRules()261     public ParceledListSlice<Rule> getCurrentRules() {
262         List<Rule> rules = Collections.emptyList();
263         try {
264             rules = mIntegrityFileManager.readRules(/* appInstallMetadata= */ null);
265         } catch (Exception e) {
266             Slog.e(TAG, "Error getting current rules", e);
267         }
268         return new ParceledListSlice<>(rules);
269     }
270 
271     @Override
getWhitelistedRuleProviders()272     public List<String> getWhitelistedRuleProviders() {
273         return getAllowedRuleProviderSystemApps();
274     }
275 
handleIntegrityVerification(Intent intent)276     private void handleIntegrityVerification(Intent intent) {
277         int verificationId = intent.getIntExtra(EXTRA_VERIFICATION_ID, -1);
278 
279         try {
280             if (DEBUG_INTEGRITY_COMPONENT) {
281                 Slog.d(TAG, "Received integrity verification intent " + intent.toString());
282                 Slog.d(TAG, "Extras " + intent.getExtras());
283             }
284 
285             String installerPackageName = getInstallerPackageName(intent);
286 
287             // Skip integrity verification if the verifier is doing the install.
288             if (!integrityCheckIncludesRuleProvider() && isRuleProvider(installerPackageName)) {
289                 if (DEBUG_INTEGRITY_COMPONENT) {
290                     Slog.i(TAG, "Verifier doing the install. Skipping integrity check.");
291                 }
292                 mPackageManagerInternal.setIntegrityVerificationResult(
293                         verificationId, PackageManagerInternal.INTEGRITY_VERIFICATION_ALLOW);
294                 return;
295             }
296 
297             String packageName = intent.getStringExtra(EXTRA_PACKAGE_NAME);
298 
299             Pair<SigningDetails, Bundle> packageSigningAndMetadata =
300                     getPackageSigningAndMetadata(intent.getData());
301             if (packageSigningAndMetadata == null) {
302                 Slog.w(TAG, "Cannot parse package " + packageName);
303                 // We can't parse the package.
304                 mPackageManagerInternal.setIntegrityVerificationResult(
305                         verificationId, PackageManagerInternal.INTEGRITY_VERIFICATION_ALLOW);
306                 return;
307             }
308 
309             var signingDetails = packageSigningAndMetadata.first;
310             List<String> appCertificates = getCertificateFingerprint(packageName, signingDetails);
311             List<String> appCertificateLineage = getCertificateLineage(packageName, signingDetails);
312             List<String> installerCertificates =
313                     getInstallerCertificateFingerprint(installerPackageName);
314 
315             AppInstallMetadata.Builder builder = new AppInstallMetadata.Builder();
316 
317             builder.setPackageName(getPackageNameNormalized(packageName));
318             builder.setAppCertificates(appCertificates);
319             builder.setAppCertificateLineage(appCertificateLineage);
320             builder.setVersionCode(intent.getLongExtra(EXTRA_LONG_VERSION_CODE, -1));
321             builder.setInstallerName(getPackageNameNormalized(installerPackageName));
322             builder.setInstallerCertificates(installerCertificates);
323             builder.setIsPreInstalled(isSystemApp(packageName));
324 
325             Map<String, String> allowedInstallers =
326                     getAllowedInstallers(packageSigningAndMetadata.second);
327             builder.setAllowedInstallersAndCert(allowedInstallers);
328             extractSourceStamp(intent.getData(), builder);
329 
330             AppInstallMetadata appInstallMetadata = builder.build();
331 
332             if (DEBUG_INTEGRITY_COMPONENT) {
333                 Slog.i(
334                         TAG,
335                         "To be verified: "
336                                 + appInstallMetadata
337                                 + " installers "
338                                 + allowedInstallers);
339             }
340             IntegrityCheckResult result = mEvaluationEngine.evaluate(appInstallMetadata);
341             if (!result.getMatchedRules().isEmpty() || DEBUG_INTEGRITY_COMPONENT) {
342                 Slog.i(
343                         TAG,
344                         String.format(
345                                 "Integrity check of %s result: %s due to %s",
346                                 packageName, result.getEffect(), result.getMatchedRules()));
347             }
348 
349             FrameworkStatsLog.write(
350                     FrameworkStatsLog.INTEGRITY_CHECK_RESULT_REPORTED,
351                     packageName,
352                     appCertificates.toString(),
353                     appInstallMetadata.getVersionCode(),
354                     installerPackageName,
355                     result.getLoggingResponse(),
356                     result.isCausedByAppCertRule(),
357                     result.isCausedByInstallerRule());
358             mPackageManagerInternal.setIntegrityVerificationResult(
359                     verificationId,
360                     result.getEffect() == IntegrityCheckResult.Effect.ALLOW
361                             ? PackageManagerInternal.INTEGRITY_VERIFICATION_ALLOW
362                             : PackageManagerInternal.INTEGRITY_VERIFICATION_REJECT);
363         } catch (IllegalArgumentException e) {
364             // This exception indicates something is wrong with the input passed by package manager.
365             // e.g., someone trying to trick the system. We block installs in this case.
366             Slog.e(TAG, "Invalid input to integrity verification", e);
367             mPackageManagerInternal.setIntegrityVerificationResult(
368                     verificationId, PackageManagerInternal.INTEGRITY_VERIFICATION_REJECT);
369         } catch (Exception e) {
370             // Other exceptions indicate an error within the integrity component implementation and
371             // we allow them.
372             Slog.e(TAG, "Error handling integrity verification", e);
373             mPackageManagerInternal.setIntegrityVerificationResult(
374                     verificationId, PackageManagerInternal.INTEGRITY_VERIFICATION_ALLOW);
375         }
376     }
377 
378     /**
379      * Verify the UID and return the installer package name.
380      *
381      * @return the package name of the installer, or null if it cannot be determined or it is
382      * installed via adb.
383      */
384     @Nullable
getInstallerPackageName(Intent intent)385     private String getInstallerPackageName(Intent intent) {
386         String installer =
387                 intent.getStringExtra(PackageManager.EXTRA_VERIFICATION_INSTALLER_PACKAGE);
388         if (PackageManagerServiceUtils.isInstalledByAdb(installer)) {
389             return ADB_INSTALLER;
390         }
391         int installerUid = intent.getIntExtra(PackageManager.EXTRA_VERIFICATION_INSTALLER_UID, -1);
392         if (installerUid < 0) {
393             Slog.e(
394                     TAG,
395                     "Installer cannot be determined: installer: "
396                             + installer
397                             + " installer UID: "
398                             + installerUid);
399             return UNKNOWN_INSTALLER;
400         }
401 
402         // Verify that the installer UID actually contains the package. Note that comparing UIDs
403         // is not safe since context's uid can change in different settings; e.g. Android Auto.
404         if (!getPackageListForUid(installerUid).contains(installer)) {
405             return UNKNOWN_INSTALLER;
406         }
407 
408         // At this time we can trust "installer".
409 
410         // A common way for apps to install packages is to send an intent to PackageInstaller. In
411         // that case, the installer will always show up as PackageInstaller which is not what we
412         // want.
413         if (PACKAGE_INSTALLER.contains(installer)) {
414             int originatingUid = intent.getIntExtra(EXTRA_ORIGINATING_UID, -1);
415             if (originatingUid < 0) {
416                 Slog.e(TAG, "Installer is package installer but originating UID not found.");
417                 return UNKNOWN_INSTALLER;
418             }
419             List<String> installerPackages = getPackageListForUid(originatingUid);
420             if (installerPackages.isEmpty()) {
421                 Slog.e(TAG, "No package found associated with originating UID " + originatingUid);
422                 return UNKNOWN_INSTALLER;
423             }
424             // In the case of multiple package sharing a UID, we just return the first one.
425             return installerPackages.get(0);
426         }
427 
428         return installer;
429     }
430 
431     /** We will use the SHA256 digest of a package name if it is more than 32 bytes long. */
getPackageNameNormalized(String packageName)432     private String getPackageNameNormalized(String packageName) {
433         if (packageName.length() <= 32) {
434             return packageName;
435         }
436 
437         try {
438             MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
439             byte[] hashBytes = messageDigest.digest(packageName.getBytes(StandardCharsets.UTF_8));
440             return getHexDigest(hashBytes);
441         } catch (NoSuchAlgorithmException e) {
442             throw new RuntimeException("SHA-256 algorithm not found", e);
443         }
444     }
445 
getInstallerCertificateFingerprint(String installer)446     private List<String> getInstallerCertificateFingerprint(String installer) {
447         if (installer.equals(ADB_INSTALLER) || installer.equals(UNKNOWN_INSTALLER)) {
448             return Collections.emptyList();
449         }
450         var installerPkg = mPackageManagerInternal.getPackage(installer);
451         if (installerPkg == null) {
452             Slog.w(TAG, "Installer package " + installer + " not found.");
453             return Collections.emptyList();
454         }
455         return getCertificateFingerprint(installerPkg.getPackageName(),
456                 installerPkg.getSigningDetails());
457     }
458 
getCertificateFingerprint(@onNull String packageName, @NonNull SigningDetails signingDetails)459     private List<String> getCertificateFingerprint(@NonNull String packageName,
460             @NonNull SigningDetails signingDetails) {
461         ArrayList<String> certificateFingerprints = new ArrayList();
462         for (Signature signature : getSignatures(packageName, signingDetails)) {
463             certificateFingerprints.add(getFingerprint(signature));
464         }
465         return certificateFingerprints;
466     }
467 
getCertificateLineage(@onNull String packageName, @NonNull SigningDetails signingDetails)468     private List<String> getCertificateLineage(@NonNull String packageName,
469             @NonNull SigningDetails signingDetails) {
470         ArrayList<String> certificateLineage = new ArrayList();
471         for (Signature signature : getSignatureLineage(packageName, signingDetails)) {
472             certificateLineage.add(getFingerprint(signature));
473         }
474         return certificateLineage;
475     }
476 
477     /** Get the allowed installers and their associated certificate hashes from <meta-data> tag. */
getAllowedInstallers(@ullable Bundle metaData)478     private Map<String, String> getAllowedInstallers(@Nullable Bundle metaData) {
479         Map<String, String> packageCertMap = new HashMap<>();
480         if (metaData != null) {
481             String allowedInstallers = metaData.getString(ALLOWED_INSTALLERS_METADATA_NAME);
482             if (allowedInstallers != null) {
483                 // parse the metadata for certs.
484                 String[] installerCertPairs = allowedInstallers.split(ALLOWED_INSTALLER_DELIMITER);
485                 for (String packageCertPair : installerCertPairs) {
486                     String[] packageAndCert =
487                             packageCertPair.split(INSTALLER_PACKAGE_CERT_DELIMITER);
488                     if (packageAndCert.length == 2) {
489                         String packageName = getPackageNameNormalized(packageAndCert[0]);
490                         String cert = packageAndCert[1];
491                         packageCertMap.put(packageName, cert);
492                     } else if (packageAndCert.length == 1) {
493                         packageCertMap.put(
494                                 getPackageNameNormalized(packageAndCert[0]),
495                                 INSTALLER_CERTIFICATE_NOT_EVALUATED);
496                     }
497                 }
498             }
499         }
500 
501         return packageCertMap;
502     }
503 
504     /** Extract the source stamp embedded in the APK, if present. */
extractSourceStamp(Uri dataUri, AppInstallMetadata.Builder appInstallMetadata)505     private void extractSourceStamp(Uri dataUri, AppInstallMetadata.Builder appInstallMetadata) {
506         File installationPath = getInstallationPath(dataUri);
507         if (installationPath == null) {
508             throw new IllegalArgumentException("Installation path is null, package not found");
509         }
510 
511         SourceStampVerificationResult sourceStampVerificationResult;
512         if (installationPath.isDirectory()) {
513             try (Stream<Path> filesList = Files.list(installationPath.toPath())) {
514                 List<String> apkFiles =
515                         filesList
516                                 .map(path -> path.toAbsolutePath().toString())
517                                 .filter(str -> str.endsWith(".apk"))
518                                 .collect(Collectors.toList());
519                 sourceStampVerificationResult = SourceStampVerifier.verify(apkFiles);
520             } catch (IOException e) {
521                 throw new IllegalArgumentException("Could not read APK directory");
522             }
523         } else {
524             sourceStampVerificationResult =
525                     SourceStampVerifier.verify(installationPath.getAbsolutePath());
526         }
527 
528         appInstallMetadata.setIsStampPresent(sourceStampVerificationResult.isPresent());
529         appInstallMetadata.setIsStampVerified(sourceStampVerificationResult.isVerified());
530         // A verified stamp is set to be trusted.
531         appInstallMetadata.setIsStampTrusted(sourceStampVerificationResult.isVerified());
532         if (sourceStampVerificationResult.isVerified()) {
533             X509Certificate sourceStampCertificate =
534                     (X509Certificate) sourceStampVerificationResult.getCertificate();
535             // Sets source stamp certificate digest.
536             try {
537                 MessageDigest digest = MessageDigest.getInstance("SHA-256");
538                 byte[] certificateDigest = digest.digest(sourceStampCertificate.getEncoded());
539                 appInstallMetadata.setStampCertificateHash(getHexDigest(certificateDigest));
540             } catch (NoSuchAlgorithmException | CertificateEncodingException e) {
541                 throw new IllegalArgumentException(
542                         "Error computing source stamp certificate digest", e);
543             }
544         }
545     }
546 
getSignatures(@onNull String packageName, @NonNull SigningDetails signingDetails)547     private static Signature[] getSignatures(@NonNull String packageName,
548             @NonNull SigningDetails signingDetails) {
549         Signature[] signatures = signingDetails.getSignatures();
550         if (signatures == null || signatures.length < 1) {
551             throw new IllegalArgumentException("Package signature not found in " + packageName);
552         }
553 
554         // We are only interested in evaluating the active signatures.
555         return signatures;
556     }
557 
getSignatureLineage(@onNull String packageName, @NonNull SigningDetails signingDetails)558     private static Signature[] getSignatureLineage(@NonNull String packageName,
559             @NonNull SigningDetails signingDetails) {
560         // Obtain the active signatures of the package.
561         Signature[] signatureLineage = getSignatures(packageName, signingDetails);
562 
563         var pastSignatures = signingDetails.getPastSigningCertificates();
564         // Obtain the past signatures of the package.
565         if (signatureLineage.length == 1 && !ArrayUtils.isEmpty(pastSignatures)) {
566             // Merge the signatures and return.
567             Signature[] allSignatures =
568                     new Signature[signatureLineage.length + pastSignatures.length];
569             int i;
570             for (i = 0; i < signatureLineage.length; i++) {
571                 allSignatures[i] = signatureLineage[i];
572             }
573             for (int j = 0; j < pastSignatures.length; j++) {
574                 allSignatures[i] = pastSignatures[j];
575                 i++;
576             }
577             signatureLineage = allSignatures;
578         }
579 
580         return signatureLineage;
581     }
582 
getFingerprint(Signature cert)583     private static String getFingerprint(Signature cert) {
584         InputStream input = new ByteArrayInputStream(cert.toByteArray());
585 
586         CertificateFactory factory;
587         try {
588             factory = CertificateFactory.getInstance("X509");
589         } catch (CertificateException e) {
590             throw new RuntimeException("Error getting CertificateFactory", e);
591         }
592         X509Certificate certificate = null;
593         try {
594             if (factory != null) {
595                 certificate = (X509Certificate) factory.generateCertificate(input);
596             }
597         } catch (CertificateException e) {
598             throw new RuntimeException("Error getting X509Certificate", e);
599         }
600 
601         if (certificate == null) {
602             throw new RuntimeException("X509 Certificate not found");
603         }
604 
605         try {
606             MessageDigest digest = MessageDigest.getInstance("SHA-256");
607             byte[] publicKey = digest.digest(certificate.getEncoded());
608             return getHexDigest(publicKey);
609         } catch (NoSuchAlgorithmException | CertificateEncodingException e) {
610             throw new IllegalArgumentException("Error error computing fingerprint", e);
611         }
612     }
613 
614     @Nullable
getPackageSigningAndMetadata(Uri dataUri)615     private Pair<SigningDetails, Bundle> getPackageSigningAndMetadata(Uri dataUri) {
616         File installationPath = getInstallationPath(dataUri);
617         if (installationPath == null) {
618             throw new IllegalArgumentException("Installation path is null, package not found");
619         }
620 
621         try (PackageParser2 parser = mParserSupplier.get()) {
622             var pkg = parser.parsePackage(installationPath, 0, false);
623             // APK signatures is already verified elsewhere in PackageManager. We do not need to
624             // verify it again since it could cause a timeout for large APKs.
625             final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing();
626             final ParseResult<SigningDetails> result = ParsingPackageUtils.getSigningDetails(
627                     input, pkg, /* skipVerify= */ true);
628             if (result.isError()) {
629                 Slog.w(TAG, result.getErrorMessage(), result.getException());
630                 return null;
631             }
632             return Pair.create(result.getResult(), pkg.getMetaData());
633         } catch (Exception e) {
634             Slog.w(TAG, "Exception reading " + dataUri, e);
635             return null;
636         }
637     }
638 
getMultiApkInfo(File multiApkDirectory)639     private PackageInfo getMultiApkInfo(File multiApkDirectory) {
640         // The base apk will normally be called base.apk
641         File baseFile = new File(multiApkDirectory, BASE_APK_FILE);
642         PackageInfo basePackageInfo =
643                 mContext.getPackageManager()
644                         .getPackageArchiveInfo(
645                                 baseFile.getAbsolutePath(),
646                                 PackageManager.GET_SIGNING_CERTIFICATES
647                                         | PackageManager.GET_META_DATA);
648 
649         if (basePackageInfo == null) {
650             for (File apkFile : multiApkDirectory.listFiles()) {
651                 if (apkFile.isDirectory()) {
652                     continue;
653                 }
654 
655                 // If we didn't find a base.apk, then try to parse each apk until we find the one
656                 // that succeeds.
657                 try {
658                     basePackageInfo =
659                             mContext.getPackageManager()
660                                     .getPackageArchiveInfo(
661                                             apkFile.getAbsolutePath(),
662                                             PackageManager.GET_SIGNING_CERTIFICATES
663                                                     | PackageManager.GET_META_DATA);
664                 } catch (Exception e) {
665                     // Some of the splits may not contain a valid android manifest. It is an
666                     // expected exception. We still log it nonetheless but we should keep looking.
667                     Slog.w(TAG, "Exception reading " + apkFile, e);
668                 }
669                 if (basePackageInfo != null) {
670                     Slog.i(TAG, "Found package info from " + apkFile);
671                     break;
672                 }
673             }
674         }
675 
676         if (basePackageInfo == null) {
677             throw new IllegalArgumentException(
678                     "Base package info cannot be found from installation directory");
679         }
680 
681         return basePackageInfo;
682     }
683 
getInstallationPath(Uri dataUri)684     private File getInstallationPath(Uri dataUri) {
685         if (dataUri == null) {
686             throw new IllegalArgumentException("Null data uri");
687         }
688 
689         String scheme = dataUri.getScheme();
690         if (!"file".equalsIgnoreCase(scheme)) {
691             throw new IllegalArgumentException("Unsupported scheme for " + dataUri);
692         }
693 
694         File installationPath = new File(dataUri.getPath());
695         if (!installationPath.exists()) {
696             throw new IllegalArgumentException("Cannot find file for " + dataUri);
697         }
698         if (!installationPath.canRead()) {
699             throw new IllegalArgumentException("Cannot read file for " + dataUri);
700         }
701         return installationPath;
702     }
703 
getCallerPackageNameOrThrow(int callingUid)704     private String getCallerPackageNameOrThrow(int callingUid) {
705         String callerPackageName = getCallingRulePusherPackageName(callingUid);
706         if (callerPackageName == null) {
707             throw new SecurityException(
708                     "Only system packages specified in config_integrityRuleProviderPackages are "
709                             + "allowed to call this method.");
710         }
711         return callerPackageName;
712     }
713 
getCallingRulePusherPackageName(int callingUid)714     private String getCallingRulePusherPackageName(int callingUid) {
715         // Obtain the system apps that are allowlisted in config_integrityRuleProviderPackages.
716         List<String> allowedRuleProviders = getAllowedRuleProviderSystemApps();
717         if (DEBUG_INTEGRITY_COMPONENT) {
718             Slog.i(
719                     TAG,
720                     String.format(
721                             "Rule provider system app list contains: %s", allowedRuleProviders));
722         }
723 
724         // Identify the package names in the caller list.
725         List<String> callingPackageNames = getPackageListForUid(callingUid);
726 
727         // Find the intersection between the allowed and calling packages. Ideally, we will have
728         // at most one package name here. But if we have more, it is fine.
729         List<String> allowedCallingPackages = new ArrayList<>();
730         for (String packageName : callingPackageNames) {
731             if (allowedRuleProviders.contains(packageName)) {
732                 allowedCallingPackages.add(packageName);
733             }
734         }
735 
736         return allowedCallingPackages.isEmpty() ? null : allowedCallingPackages.get(0);
737     }
738 
isRuleProvider(String installerPackageName)739     private boolean isRuleProvider(String installerPackageName) {
740         for (String ruleProvider : getAllowedRuleProviderSystemApps()) {
741             if (ruleProvider.matches(installerPackageName)) {
742                 return true;
743             }
744         }
745         return false;
746     }
747 
getAllowedRuleProviderSystemApps()748     private List<String> getAllowedRuleProviderSystemApps() {
749         List<String> integrityRuleProviders =
750                 Arrays.asList(
751                         mContext.getResources()
752                                 .getStringArray(R.array.config_integrityRuleProviderPackages));
753 
754         // Filter out the rule provider packages that are not system apps.
755         List<String> systemAppRuleProviders = new ArrayList<>();
756         for (String ruleProvider : integrityRuleProviders) {
757             if (isSystemApp(ruleProvider)) {
758                 systemAppRuleProviders.add(ruleProvider);
759             }
760         }
761         return systemAppRuleProviders;
762     }
763 
isSystemApp(String packageName)764     private boolean isSystemApp(String packageName) {
765         try {
766             PackageInfo existingPackageInfo =
767                     mContext.getPackageManager().getPackageInfo(packageName, /* flags= */ 0);
768             return existingPackageInfo.applicationInfo != null
769                     && existingPackageInfo.applicationInfo.isSystemApp();
770         } catch (PackageManager.NameNotFoundException e) {
771             return false;
772         }
773     }
774 
integrityCheckIncludesRuleProvider()775     private boolean integrityCheckIncludesRuleProvider() {
776         return Settings.Global.getInt(
777                 mContext.getContentResolver(),
778                 Settings.Global.INTEGRITY_CHECK_INCLUDES_RULE_PROVIDER,
779                 0)
780                 == 1;
781     }
782 
getPackageListForUid(int uid)783     private List<String> getPackageListForUid(int uid) {
784         try {
785             return Arrays.asList(mContext.getPackageManager().getPackagesForUid(uid));
786         } catch (NullPointerException e) {
787             Slog.w(TAG, String.format("No packages were found for uid: %d", uid));
788             return List.of();
789         }
790     }
791 }
792