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