1 /*
2  * Copyright (C) 2022 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 
18 package com.android.managedprovisioning.task;
19 
20 import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE;
21 
22 import static com.android.managedprovisioning.task.VerifyAdminPackageTask.ERROR_DEVICE_ADMIN_MISSING;
23 import static com.android.managedprovisioning.task.VerifyAdminPackageTask.ERROR_HASH_MISMATCH;
24 
25 import static org.mockito.Matchers.any;
26 import static org.mockito.Mockito.mock;
27 import static org.mockito.Mockito.verify;
28 import static org.mockito.Mockito.verifyNoMoreInteractions;
29 import static org.mockito.Mockito.when;
30 
31 import android.content.ComponentName;
32 import android.content.Context;
33 import android.content.pm.PackageInfo;
34 import android.content.pm.PackageManager;
35 import android.content.pm.Signature;
36 
37 import androidx.test.filters.SmallTest;
38 import androidx.test.runner.AndroidJUnit4;
39 
40 import com.android.managedprovisioning.analytics.ProvisioningAnalyticsTracker;
41 import com.android.managedprovisioning.common.Utils;
42 import com.android.managedprovisioning.model.PackageDownloadInfo;
43 import com.android.managedprovisioning.model.ProvisioningParams;
44 
45 import org.junit.Before;
46 import org.junit.Test;
47 import org.junit.runner.RunWith;
48 import org.mockito.Mock;
49 import org.mockito.MockitoAnnotations;
50 
51 import java.io.File;
52 
53 /**
54  * Unit tests for {@link VerifyAdminPackageTask}.
55  */
56 @RunWith(AndroidJUnit4.class)
57 @SmallTest
58 public class VerifyAdminPackageTaskTest {
59 
60     private static final String TEST_PACKAGE_NAME = "sample.package.name";
61     private static final String TEST_ADMIN_NAME = TEST_PACKAGE_NAME + ".DeviceAdmin";
62     private static final String TEST_PACKAGE_LOCATION = "http://www.some.uri.com";
63     private static final String TEST_LOCAL_FILENAME = "/local/filename";
64     private static final File TEST_LOCAL_FILE = new File(TEST_LOCAL_FILENAME);
65     private static final int TEST_USER_ID = 123;
66     private static final byte[] TEST_BAD_HASH = new byte[] { 'b', 'a', 'd' };
67     private static final byte[] TEST_PACKAGE_CHECKSUM_HASH = new byte[] { '1', '2', '3', '4', '5' };
68     private static final byte[] TEST_SIGNATURE_HASH = new byte[] {'a', 'b', 'c', 'd'};
69     private static final byte[] EMPTY_BYTE_ARRAY = new byte[] {};
70     private static final Signature[] TEST_SIGNATURES = new Signature[] { new Signature("1986") };
71 
72     @Mock private Context mContext;
73     @Mock private DownloadPackageTask mDownloadPackageTask;
74     @Mock private AbstractProvisioningTask.Callback mCallback;
75     @Mock private PackageManager mPackageManager;
76     @Mock private Utils mUtils;
77     @Mock private PackageInfo mPackageInfo;
78 
79     private ChecksumUtils mChecksumUtils;
80 
81     private AbstractProvisioningTask mTask;
82 
83     @Before
setUp()84     public void setUp() throws Exception {
85         // This is necessary for mockito to work
86         MockitoAnnotations.initMocks(this);
87 
88         when(mContext.getPackageManager()).thenReturn(mPackageManager);
89 
90         mPackageInfo.packageName = TEST_PACKAGE_NAME;
91         mPackageInfo.signatures = TEST_SIGNATURES;
92 
93         when(mPackageManager.getPackageArchiveInfo(TEST_LOCAL_FILENAME,
94                 PackageManager.GET_SIGNATURES | PackageManager.GET_RECEIVERS))
95                 .thenReturn(mPackageInfo);
96 
97         when(mDownloadPackageTask.getPackageLocation()).thenReturn(TEST_LOCAL_FILE);
98 
99         when(mUtils.findDeviceAdminInPackageInfo(TEST_PACKAGE_NAME, null, mPackageInfo))
100                 .thenReturn(new ComponentName(TEST_PACKAGE_NAME, TEST_ADMIN_NAME));
101 
102         mChecksumUtils = new ChecksumUtils(mUtils);
103     }
104 
105     @Test
testDownloadLocationNull()106     public void testDownloadLocationNull() {
107         // GIVEN that the download package location is null
108         when(mDownloadPackageTask.getPackageLocation()).thenReturn(null);
109 
110         // WHEN running the VerifyPackageTask
111         runWithDownloadInfo(TEST_PACKAGE_CHECKSUM_HASH, EMPTY_BYTE_ARRAY);
112 
113         // THEN success should be called
114         verify(mCallback).onSuccess(mTask);
115         verifyNoMoreInteractions(mCallback);
116     }
117 
118     @Test
testMissingDeviceAdminComponent()119     public void testMissingDeviceAdminComponent() {
120         // GIVEN that the device admin component cannot be found
121         when(mUtils.findDeviceAdminInPackageInfo(TEST_PACKAGE_NAME, null, mPackageInfo))
122                 .thenReturn(null);
123 
124         // WHEN running the VerifyPackageTask
125         runWithDownloadInfo(TEST_PACKAGE_CHECKSUM_HASH, EMPTY_BYTE_ARRAY);
126 
127         // THEN an error should be reported
128         verify(mCallback).onError(mTask, ERROR_DEVICE_ADMIN_MISSING, /* errorMessage= */ null);
129         verifyNoMoreInteractions(mCallback);
130     }
131 
132     @Test
testPackageChecksumSha256_success()133     public void testPackageChecksumSha256_success() throws Exception {
134         // GIVEN the hash of the downloaded file matches the parameter value
135         when(mUtils.computeHashOfFile(TEST_LOCAL_FILENAME, Utils.SHA256_TYPE))
136                 .thenReturn(TEST_PACKAGE_CHECKSUM_HASH);
137 
138         // WHEN running the VerifyPackageTask
139         runWithDownloadInfo(TEST_PACKAGE_CHECKSUM_HASH, EMPTY_BYTE_ARRAY);
140 
141         // THEN success should be called
142         verify(mCallback).onSuccess(mTask);
143         verifyNoMoreInteractions(mCallback);
144     }
145 
146     @Test
testSignatureHash_success()147     public void testSignatureHash_success() throws Exception {
148         // GIVEN the hash of the signature matches the parameter value
149         when(mUtils.computeHashOfByteArray(TEST_SIGNATURES[0].toByteArray()))
150                 .thenReturn(TEST_SIGNATURE_HASH);
151 
152         // WHEN running the VerifyPackageTask
153         runWithDownloadInfo(EMPTY_BYTE_ARRAY, TEST_SIGNATURE_HASH);
154 
155         // THEN success should be called
156         verify(mCallback).onSuccess(mTask);
157         verifyNoMoreInteractions(mCallback);
158     }
159 
160     @Test
testSignatureHash_failure()161     public void testSignatureHash_failure() throws Exception {
162         // GIVEN the hash of the signature does not match the parameter value
163         when(mUtils.computeHashOfByteArray(TEST_SIGNATURES[0].toByteArray()))
164                 .thenReturn(TEST_BAD_HASH);
165 
166         // WHEN running the VerifyPackageTask
167         runWithDownloadInfo(EMPTY_BYTE_ARRAY, TEST_SIGNATURE_HASH);
168 
169         // THEN hash mismatch error should be called
170         verify(mCallback).onError(mTask, ERROR_HASH_MISMATCH, /* errorMessage= */ null);
171         verifyNoMoreInteractions(mCallback);
172     }
173 
174     @Test
testSignatureHash_noSignature()175     public void testSignatureHash_noSignature() throws Exception {
176         // GIVEN the package has no signature
177         mPackageInfo.signatures = null;
178 
179         // WHEN running the VerifyPackageTask
180         runWithDownloadInfo(EMPTY_BYTE_ARRAY, TEST_SIGNATURE_HASH);
181 
182         // THEN hash mismatch error should be called
183         verify(mCallback).onError(mTask, ERROR_HASH_MISMATCH, /* errorMessage= */ null);
184         verifyNoMoreInteractions(mCallback);
185     }
186 
187     @Test
testSignatureHash_digestFailure()188     public void testSignatureHash_digestFailure() throws Exception {
189         // GIVEN the package has no signature
190         when(mUtils.computeHashOfByteArray(any(byte[].class))).thenReturn(null);
191 
192         // WHEN running the VerifyPackageTask
193         runWithDownloadInfo(EMPTY_BYTE_ARRAY, TEST_SIGNATURE_HASH);
194 
195         // THEN hash mismatch error should be called
196         verify(mCallback).onError(mTask, ERROR_HASH_MISMATCH, /* errorMessage= */ null);
197         verifyNoMoreInteractions(mCallback);
198     }
199 
runWithDownloadInfo(byte[] packageChecksum, byte[] signatureChecksum)200     private void runWithDownloadInfo(byte[] packageChecksum, byte[] signatureChecksum) {
201         PackageDownloadInfo downloadInfo = new PackageDownloadInfo.Builder()
202                 .setLocation(TEST_PACKAGE_LOCATION)
203                 .setPackageChecksum(packageChecksum)
204                 .setSignatureChecksum(signatureChecksum)
205                 .build();
206         ProvisioningParams params = new ProvisioningParams.Builder()
207                 .setProvisioningAction(ACTION_PROVISION_MANAGED_DEVICE)
208                 .setDeviceAdminPackageName(TEST_PACKAGE_NAME)
209                 .setDeviceAdminDownloadInfo(downloadInfo)
210                 .build();
211         mTask = new VerifyAdminPackageTask(mUtils, mDownloadPackageTask, mContext, params,
212                 downloadInfo, mCallback, mock(ProvisioningAnalyticsTracker.class),
213                 mChecksumUtils);
214         mTask.run(TEST_USER_ID);
215     }
216 }
217