Home | History | Annotate | Download | only in task
      1 /*
      2  * Copyright 2016, 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.managedprovisioning.task;
     18 
     19 import static com.android.internal.util.Preconditions.checkNotNull;
     20 
     21 import android.content.Context;
     22 import android.content.pm.PackageInfo;
     23 import android.content.pm.PackageManager;
     24 import android.content.pm.Signature;
     25 import android.text.TextUtils;
     26 
     27 import com.android.internal.annotations.VisibleForTesting;
     28 import com.android.managedprovisioning.common.ProvisionLogger;
     29 import com.android.managedprovisioning.R;
     30 import com.android.managedprovisioning.common.StoreUtils;
     31 import com.android.managedprovisioning.common.Utils;
     32 import com.android.managedprovisioning.model.PackageDownloadInfo;
     33 import com.android.managedprovisioning.model.ProvisioningParams;
     34 
     35 import java.util.Arrays;
     36 import java.util.LinkedList;
     37 import java.util.List;
     38 
     39 /**
     40  * Verifies the management app apk downloaded previously in {@link DownloadPackageTask}.
     41  *
     42  * <p>The first check verifies that a {@link android.app.admin.DeviceAdminReceiver} is present in
     43  * the apk and that it corresponds to the one provided via
     44  * {@link ProvisioningParams#deviceAdminComponentName}.</p>
     45  *
     46  * <p>The second check verifies that the package or signature checksum matches the ones given via
     47  * {@link PackageDownloadInfo#packageChecksum} or {@link PackageDownloadInfo#signatureChecksum}
     48  * respectively. The package checksum takes priority in case both are present.</p>
     49  */
     50 public class VerifyPackageTask extends AbstractProvisioningTask {
     51     public static final int ERROR_HASH_MISMATCH = 0;
     52     public static final int ERROR_DEVICE_ADMIN_MISSING = 1;
     53 
     54     private final Utils mUtils;
     55     private final DownloadPackageTask mDownloadPackageTask;
     56     private final PackageManager mPackageManager;
     57     private final PackageDownloadInfo mDownloadInfo;
     58 
     59     public VerifyPackageTask(
     60             DownloadPackageTask downloadPackageTask,
     61             Context context,
     62             ProvisioningParams params,
     63             Callback callback) {
     64         this(new Utils(), downloadPackageTask, context, params, callback);
     65     }
     66 
     67     @VisibleForTesting
     68     VerifyPackageTask(
     69             Utils utils,
     70             DownloadPackageTask downloadPackageTask,
     71             Context context,
     72             ProvisioningParams params,
     73             Callback callback) {
     74         super(context, params, callback);
     75 
     76         mUtils = checkNotNull(utils);
     77         mDownloadPackageTask = checkNotNull(downloadPackageTask);
     78         mPackageManager = mContext.getPackageManager();
     79         mDownloadInfo = checkNotNull(params.deviceAdminDownloadInfo);
     80     }
     81 
     82     @Override
     83     public void run(int userId) {
     84         final String downloadLocation = mDownloadPackageTask.getDownloadedPackageLocation();
     85         if (TextUtils.isEmpty(downloadLocation)) {
     86             ProvisionLogger.logw("VerifyPackageTask invoked, but download location is null");
     87             success();
     88             return;
     89         }
     90 
     91         PackageInfo packageInfo = mPackageManager.getPackageArchiveInfo(downloadLocation,
     92                 PackageManager.GET_SIGNATURES | PackageManager.GET_RECEIVERS);
     93         String packageName = mProvisioningParams.inferDeviceAdminPackageName();
     94         // Device admin package name can't be null
     95         if (packageInfo == null || packageName == null) {
     96             ProvisionLogger.loge("Device admin package info or name is null");
     97             error(ERROR_DEVICE_ADMIN_MISSING);
     98             return;
     99         }
    100 
    101         if (mUtils.findDeviceAdminInPackageInfo(packageName,
    102                 mProvisioningParams.deviceAdminComponentName, packageInfo) == null) {
    103             error(ERROR_DEVICE_ADMIN_MISSING);
    104             return;
    105         }
    106 
    107         if (mDownloadInfo.packageChecksum.length > 0) {
    108             if (!doesPackageHashMatch(downloadLocation, mDownloadInfo.packageChecksum,
    109                     mDownloadInfo.packageChecksumSupportsSha1)) {
    110                 error(ERROR_HASH_MISMATCH);
    111                 return;
    112             }
    113         } else {
    114             if (!doesASignatureHashMatch(packageInfo, mDownloadInfo.signatureChecksum)) {
    115                 error(ERROR_HASH_MISMATCH);
    116                 return;
    117             }
    118         }
    119 
    120         success();
    121     }
    122 
    123     @Override
    124     public int getStatusMsgId() {
    125         return R.string.progress_install;
    126     }
    127 
    128     private List<byte[]> computeHashesOfAllSignatures(Signature[] signatures) {
    129         if (signatures == null) {
    130             return null;
    131         }
    132 
    133         List<byte[]> hashes = new LinkedList<>();
    134         for (Signature signature : signatures) {
    135             byte[] hash = mUtils.computeHashOfByteArray(signature.toByteArray());
    136             hashes.add(hash);
    137         }
    138         return hashes;
    139     }
    140 
    141     private boolean doesASignatureHashMatch(PackageInfo packageInfo, byte[] signatureChecksum) {
    142         // Check whether a signature hash of downloaded apk matches the hash given in constructor.
    143         ProvisionLogger.logd("Checking " + Utils.SHA256_TYPE
    144                 + "-hashes of all signatures of downloaded package.");
    145         List<byte[]> sigHashes = computeHashesOfAllSignatures(packageInfo.signatures);
    146         if (sigHashes == null || sigHashes.isEmpty()) {
    147             ProvisionLogger.loge("Downloaded package does not have any signatures.");
    148             return false;
    149         }
    150 
    151         for (byte[] sigHash : sigHashes) {
    152             if (Arrays.equals(sigHash, signatureChecksum)) {
    153                 return true;
    154             }
    155         }
    156 
    157         ProvisionLogger.loge("Provided hash does not match any signature hash.");
    158         ProvisionLogger.loge("Hash provided by programmer: "
    159                 + StoreUtils.byteArrayToString(signatureChecksum));
    160         ProvisionLogger.loge("Hashes computed from package signatures: ");
    161         for (byte[] sigHash : sigHashes) {
    162             if (sigHash != null) {
    163                 ProvisionLogger.loge(StoreUtils.byteArrayToString(sigHash));
    164             }
    165         }
    166 
    167         return false;
    168     }
    169 
    170     /**
    171      * Check whether package hash of downloaded file matches the hash given in PackageDownloadInfo.
    172      * By default, SHA-256 is used to verify the file hash.
    173      * If mPackageDownloadInfo.packageChecksumSupportsSha1 == true, SHA-1 hash is also supported for
    174      * backwards compatibility.
    175      */
    176     private boolean doesPackageHashMatch(String downloadLocation, byte[] packageChecksum,
    177             boolean supportsSha1) {
    178         byte[] packageSha256Hash, packageSha1Hash = null;
    179 
    180         ProvisionLogger.logd("Checking file hash of entire apk file.");
    181         packageSha256Hash = mUtils.computeHashOfFile(downloadLocation, Utils.SHA256_TYPE);
    182         if (Arrays.equals(packageChecksum, packageSha256Hash)) {
    183             return true;
    184         }
    185 
    186         // Fall back to SHA-1
    187         if (supportsSha1) {
    188             packageSha1Hash = mUtils.computeHashOfFile(downloadLocation, Utils.SHA1_TYPE);
    189             if (Arrays.equals(packageChecksum, packageSha1Hash)) {
    190                 return true;
    191             }
    192         }
    193 
    194         ProvisionLogger.loge("Provided hash does not match file hash.");
    195         ProvisionLogger.loge("Hash provided by programmer: "
    196                 + StoreUtils.byteArrayToString(packageChecksum));
    197         if (packageSha256Hash != null) {
    198             ProvisionLogger.loge("SHA-256 Hash computed from file: "
    199                     + StoreUtils.byteArrayToString(packageSha256Hash));
    200         }
    201         if (packageSha1Hash != null) {
    202             ProvisionLogger.loge("SHA-1 Hash computed from file: "
    203                     + StoreUtils.byteArrayToString(packageSha1Hash));
    204         }
    205         return false;
    206     }
    207 }
    208