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