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 
VerifyPackageTask( DownloadPackageTask downloadPackageTask, Context context, ProvisioningParams params, Callback callback)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
VerifyPackageTask( Utils utils, DownloadPackageTask downloadPackageTask, Context context, ProvisioningParams params, Callback callback)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
run(int userId)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
getStatusMsgId()124     public int getStatusMsgId() {
125         return R.string.progress_install;
126     }
127 
computeHashesOfAllSignatures(Signature[] signatures)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 
doesASignatureHashMatch(PackageInfo packageInfo, byte[] signatureChecksum)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      */
doesPackageHashMatch(String downloadLocation, byte[] packageChecksum, boolean supportsSha1)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