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