diff options
author | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2022-08-16 16:49:26 +0000 |
---|---|---|
committer | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2022-08-16 16:49:26 +0000 |
commit | 8ba3c51ea0a70dca7b9fb7d31ed8233478991178 (patch) | |
tree | 44d3e033d966683c80be5d9ebf4b1bc30df57c62 | |
parent | a873050c29b5bb8fbffbb34ced618f6e3616f96d (diff) | |
parent | b3245b663316f3e6726ce500787f0c54f5a41837 (diff) | |
download | RemoteProvisioner-aml_tz4_332714010.tar.gz |
Snap for 8953554 from b3245b663316f3e6726ce500787f0c54f5a41837 to mainline-tzdata4-releaseaml_tz4_332714070aml_tz4_332714050aml_tz4_332714010aml_tz4_331910000aml_tz4_331314030aml_tz4_331314020aml_tz4_331314010aml_tz4_331012050aml_tz4_331012040aml_tz4_331012000android13-mainline-tzdata4-releaseaml_tz4_332714010
Change-Id: I4f89184faaa0b0d2ee08c44d8a00effdfd935e20
13 files changed, 644 insertions, 7 deletions
diff --git a/src/com/android/remoteprovisioner/BootReceiver.java b/src/com/android/remoteprovisioner/BootReceiver.java index 4aa3dcd..fd9b019 100644 --- a/src/com/android/remoteprovisioner/BootReceiver.java +++ b/src/com/android/remoteprovisioner/BootReceiver.java @@ -31,6 +31,7 @@ import android.util.Log; import androidx.work.Constraints; import androidx.work.ExistingPeriodicWorkPolicy; import androidx.work.NetworkType; +import androidx.work.OneTimeWorkRequest; import androidx.work.PeriodicWorkRequest; import androidx.work.WorkManager; @@ -79,8 +80,18 @@ public class BootReceiver extends BroadcastReceiver { .enqueueUniquePeriodicWork("ProvisioningJob", ExistingPeriodicWorkPolicy.REPLACE, // Replace on reboot. workRequest); + if (WidevineProvisioner.isWidevineProvisioningNeeded()) { + Log.i(TAG, "WV provisioning needed. Queueing a one-time provisioning job."); + OneTimeWorkRequest wvRequest = + new OneTimeWorkRequest.Builder(WidevineProvisioner.class) + .setConstraints(constraints) + .build(); + WorkManager.getInstance(context).enqueue(wvRequest); + } } + + private int calcNumPotentialKeysToDownload() { try { IRemoteProvisioning binder = diff --git a/src/com/android/remoteprovisioner/CborUtils.java b/src/com/android/remoteprovisioner/CborUtils.java index d3fc3d7..05bd8a4 100644 --- a/src/com/android/remoteprovisioner/CborUtils.java +++ b/src/com/android/remoteprovisioner/CborUtils.java @@ -264,7 +264,8 @@ public class CborUtils { * IRemotelyProvisionedComponent HAL AIDL files. */ public static byte[] buildCertificateRequest(byte[] deviceInfo, byte[] challenge, - byte[] protectedData, byte[] macedKeysToSign) { + byte[] protectedData, byte[] macedKeysToSign, + Map unverifiedDeviceInfo) { // This CBOR library doesn't support adding already serialized CBOR structures into a // CBOR builder. Because of this, we have to first deserialize the provided parameters // back into the library's CBOR object types, and then reserialize them into the @@ -296,17 +297,18 @@ public class CborUtils { return null; } Map verifiedDeviceInfoMap = (Map) dataItems.get(0); - Map unverifiedDeviceInfoMap = new Map(); - unverifiedDeviceInfoMap.put(new UnicodeString("fingerprint"), - new UnicodeString(Build.FINGERPRINT)); + if (unverifiedDeviceInfo.get(new UnicodeString("fingerprint")) == null) { + Log.e(TAG, "UnverifiedDeviceInfo is missing a fingerprint entry"); + return null; + } // Serialize the actual CertificateSigningRequest structure ByteArrayOutputStream baos = new ByteArrayOutputStream(); new CborEncoder(baos).encode(new CborBuilder() .addArray() .addArray() .add(verifiedDeviceInfoMap) - .add(unverifiedDeviceInfoMap) + .add(unverifiedDeviceInfo) .end() .add(challenge) .add(protectedDataArray) @@ -319,4 +321,17 @@ public class CborUtils { return null; } } + + /** + * Produce a CBOR Map object which contains the unverified device information for a certificate + * signing request. + * + * @return the CBOR Map object. + */ + public static Map buildUnverifiedDeviceInfo() { + Map unverifiedDeviceInfo = new Map(); + unverifiedDeviceInfo.put(new UnicodeString("fingerprint"), + new UnicodeString(Build.FINGERPRINT)); + return unverifiedDeviceInfo; + } } diff --git a/src/com/android/remoteprovisioner/Provisioner.java b/src/com/android/remoteprovisioner/Provisioner.java index c259abe..3bd0f90 100644 --- a/src/com/android/remoteprovisioner/Provisioner.java +++ b/src/com/android/remoteprovisioner/Provisioner.java @@ -84,7 +84,8 @@ public class Provisioner { CborUtils.buildCertificateRequest(deviceInfo.deviceInfo, challenge, protectedData.protectedData, - macedKeysToSign); + macedKeysToSign, + CborUtils.buildUnverifiedDeviceInfo()); if (certificateRequest == null) { throw new RemoteProvisioningException(IGenerateRkpKeyService.Status.INTERNAL_ERROR, "Failed to serialize the payload generated by keystore."); diff --git a/src/com/android/remoteprovisioner/WidevineProvisioner.java b/src/com/android/remoteprovisioner/WidevineProvisioner.java new file mode 100644 index 0000000..d338376 --- /dev/null +++ b/src/com/android/remoteprovisioner/WidevineProvisioner.java @@ -0,0 +1,191 @@ +/** + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.remoteprovisioner; + +import android.content.Context; +import android.media.DeniedByServerException; +import android.media.MediaDrm; +import android.media.UnsupportedSchemeException; +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.work.Worker; +import androidx.work.WorkerParameters; + +import java.io.BufferedInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +/** + * Provides the functionality necessary to provision a Widevine instance running Provisioning 4.0. + * This class extends the Worker class so that it can be scheduled as a one time work request + * in the BootReceiver if the device does need to be provisioned. This can technically be handled + * by any application, but is done within this application for convenience purposes. + */ +public class WidevineProvisioner extends Worker { + + private static final int MAX_RETRIES = 3; + private static final int TIMEOUT_MS = 20000; + + private static final String TAG = "RemoteProvisioningWV"; + + private static final byte[] EMPTY_BODY = new byte[0]; + + private static final Map<String, String> REQ_PROPERTIES = new HashMap<String, String>(); + static { + REQ_PROPERTIES.put("Accept", "*/*"); + REQ_PROPERTIES.put("User-Agent", "Widevine CDM v1.0"); + REQ_PROPERTIES.put("Content-Type", "application/json"); + REQ_PROPERTIES.put("Connection", "close"); + } + + public static final UUID WIDEVINE_UUID = new UUID(0xEDEF8BA979D64ACEL, 0xA3C827DCD51D21EDL); + + public WidevineProvisioner(@NonNull Context context, @NonNull WorkerParameters params) { + super(context, params); + } + + private Result retryOrFail() { + if (getRunAttemptCount() < MAX_RETRIES) { + return Result.retry(); + } else { + return Result.failure(); + } + } + + /** + * Overrides the default doWork method to handle checking and provisioning the device's + * widevine certificate. + */ + @Override + public Result doWork() { + Log.i(TAG, "Beginning WV provisioning request. Current attempt: " + getRunAttemptCount()); + return provisionWidevine(); + } + + /** + * Checks the status of the system in order to determine if stage 1 certificate provisioning + * for Provisioning 4.0 needs to be performed. + * + * @return true if the device supports Provisioning 4.0 and the system ID indicates it has not + * yet been provisioned. + */ + public static boolean isWidevineProvisioningNeeded() { + try { + final MediaDrm drm = new MediaDrm(WidevineProvisioner.WIDEVINE_UUID); + + if (!drm.getPropertyString("provisioningModel").equals("BootCertificateChain")) { + // Not a provisioning 4.0 device. + Log.i(TAG, "Not a WV provisioning 4.0 device. No provisioning required."); + return false; + } + int systemId = Integer.parseInt(drm.getPropertyString("systemId")); + if (systemId != Integer.MAX_VALUE) { + Log.i(TAG, "This device has already been provisioned with its WV cert."); + // First stage provisioning probably complete + return false; + } + return true; + } catch (UnsupportedSchemeException e) { + // Suppress the exception. It isn't particularly informative and may confuse anyone + // reading the logs. + Log.i(TAG, "Widevine not supported. No need to provision widevine certificates."); + return false; + } catch (Exception e) { + Log.e(TAG, "Something went wrong. Will not provision widevine certificates.", e); + return false; + } + } + + /** + * Performs the full roundtrip necessary to provision widevine with the first stage cert + * in Provisioning 4.0. + * + * @return A Result indicating whether the attempt succeeded, failed, or should be retried. + */ + public Result provisionWidevine() { + try { + final MediaDrm drm = new MediaDrm(WIDEVINE_UUID); + final MediaDrm.ProvisionRequest request = drm.getProvisionRequest(); + drm.provideProvisionResponse(fetchWidevineCertificate(request)); + } catch (UnsupportedSchemeException e) { + Log.e(TAG, "WV provisioning unsupported. Should not have been able to get here.", e); + return Result.success(); + } catch (DeniedByServerException e) { + Log.e(TAG, "WV server denied the provisioning request.", e); + return Result.failure(); + } catch (IOException e) { + Log.e(TAG, "WV Provisioning failed.", e); + return retryOrFail(); + } catch (Exception e) { + Log.e(TAG, "Safety catch-all in case of an unexpected run time exception:", e); + return retryOrFail(); + } + Log.i(TAG, "Provisioning successful."); + return Result.success(); + } + + private byte[] fetchWidevineCertificate(MediaDrm.ProvisionRequest req) throws IOException { + final byte[] data = req.getData(); + final String signedUrl = String.format( + "%s&signedRequest=%s", + req.getDefaultUrl(), + new String(data)); + return sendNetworkRequest(signedUrl); + } + + private byte[] sendNetworkRequest(String url) throws IOException { + HttpURLConnection con = (HttpURLConnection) new URL(url).openConnection(); + con.setRequestMethod("POST"); + con.setDoOutput(true); + con.setDoInput(true); + con.setConnectTimeout(TIMEOUT_MS); + con.setReadTimeout(TIMEOUT_MS); + con.setChunkedStreamingMode(0); + for (Map.Entry<String, String> prop : REQ_PROPERTIES.entrySet()) { + con.setRequestProperty(prop.getKey(), prop.getValue()); + } + + try (OutputStream os = con.getOutputStream()) { + os.write(EMPTY_BODY); + } + if (con.getResponseCode() != 200) { + Log.e(TAG, "Server request for WV certs failed. Error: " + con.getResponseCode()); + throw new IOException("Failed to request WV certs. Error: " + con.getResponseCode()); + } + + BufferedInputStream inputStream = new BufferedInputStream(con.getInputStream()); + ByteArrayOutputStream respBytes = new ByteArrayOutputStream(); + byte[] buffer = new byte[1024]; + int read = 0; + while ((read = inputStream.read(buffer, 0, buffer.length)) != -1) { + respBytes.write(buffer, 0, read); + } + byte[] respData = respBytes.toByteArray(); + if (respData.length == 0) { + Log.e(TAG, "WV server returned an empty response."); + throw new IOException("WV server returned an empty response."); + } + return respData; + } +} diff --git a/tests/hosttest/AndroidTest.xml b/tests/hosttest/AndroidTest.xml index 2365fd0..f701cff 100644 --- a/tests/hosttest/AndroidTest.xml +++ b/tests/hosttest/AndroidTest.xml @@ -22,6 +22,10 @@ <option name="cleanup-apks" value="true" /> <option name="test-file-name" value="RemoteProvisionerUnitTests.apk" /> </target_preparer> + <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> + <option name="cleanup-apks" value="true" /> + <option name="test-file-name" value="RemoteProvisionerTestApk.apk" /> + </target_preparer> <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" > <option name="jar" value="RemoteProvisionerHostTests.jar" /> </test> diff --git a/tests/hosttest/src/com/android/remoteprovisioner/hosttest/RemoteProvisionerWidevineTests.java b/tests/hosttest/src/com/android/remoteprovisioner/hosttest/RemoteProvisionerWidevineTests.java new file mode 100644 index 0000000..a1adff9 --- /dev/null +++ b/tests/hosttest/src/com/android/remoteprovisioner/hosttest/RemoteProvisionerWidevineTests.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.remoteprovisioner.hosttest; + +import static org.junit.Assert.assertTrue; + +import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; +import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(DeviceJUnit4ClassRunner.class) +public final class RemoteProvisionerWidevineTests extends BaseHostJUnit4Test { + private static final String TEST_PACKAGE_NAME = "com.android.remoteprovisioner.testapk"; + private static final String WV_CERT_LOCATION = "/data/vendor/mediadrm/IDM1013/L1/oemcert.bin"; + + private void deleteWidevineCert() throws Exception { + assertTrue("Test requires ability to get root.", getDevice().enableAdbRoot()); + getDevice().executeShellCommand("rm " + WV_CERT_LOCATION); + } + + private void runTest(String testClassName, String testMethodName) throws Exception { + testClassName = TEST_PACKAGE_NAME + "." + testClassName; + assertTrue(runDeviceTests(TEST_PACKAGE_NAME, testClassName, testMethodName)); + } + + @Test + public void testIfProvisioningNeededIsConsistentWithSystemStatus() throws Exception { + runTest("WidevineTest", "testIfProvisioningNeededIsConsistentWithSystemStatus"); + } + + @Test + public void testWipeAndReprovisionCert() throws Exception { + deleteWidevineCert(); + runTest("WidevineTest", "testProvisionWidevine"); + } +} diff --git a/tests/testapk/Android.bp b/tests/testapk/Android.bp new file mode 100644 index 0000000..6dc5dfd --- /dev/null +++ b/tests/testapk/Android.bp @@ -0,0 +1,32 @@ +// Copyright (C) 2022 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +android_test { + name: "RemoteProvisionerTestApk", + srcs: ["src/**/*.java"], + static_libs: [ + "androidx.test.core", + "androidx.test.rules", + "androidx.work_work-runtime", + "androidx.work_work-testing", + "platform-test-annotations", + "cbor-java", + ], + platform_apis: true, + test_suites: ["device-tests"], + instrumentation_for: "RemoteProvisioner", +} diff --git a/tests/testapk/AndroidManifest.xml b/tests/testapk/AndroidManifest.xml new file mode 100644 index 0000000..1ce5715 --- /dev/null +++ b/tests/testapk/AndroidManifest.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2022 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.remoteprovisioner.testapk"> + + <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" /> + + <application> + <uses-library android:name="android.test.runner" /> + </application> + + <instrumentation + android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.remoteprovisioner" + android:label="RemoteProvisioner app unit tests" /> +</manifest> diff --git a/tests/testapk/src/com/android/remoteprovisioner/testapk/WidevineTest.java b/tests/testapk/src/com/android/remoteprovisioner/testapk/WidevineTest.java new file mode 100644 index 0000000..9932cc9 --- /dev/null +++ b/tests/testapk/src/com/android/remoteprovisioner/testapk/WidevineTest.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.remoteprovisioner.testapk; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import android.media.MediaDrm; +import android.media.UnsupportedSchemeException; +import android.util.Log; + +import androidx.test.core.app.ApplicationProvider; +import androidx.test.runner.AndroidJUnit4; +import androidx.work.ListenableWorker; +import androidx.work.Worker; +import androidx.work.testing.TestWorkerBuilder; + +import com.android.remoteprovisioner.WidevineProvisioner; + +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.concurrent.Executors; + +@RunWith(AndroidJUnit4.class) +public class WidevineTest { + + private static boolean sSupportsWidevine = true; + private static final String TAG = "RemoteProvisionerWidevineTest"; + private static MediaDrm sDrm; + + @BeforeClass + public static void init() throws Exception { + try { + sDrm = new MediaDrm(WidevineProvisioner.WIDEVINE_UUID); + } catch (UnsupportedSchemeException e) { + Log.i(TAG, "Device doesn't support widevine, all tests should pass."); + sSupportsWidevine = false; + } + } + + private boolean isProvisioning4() { + if (!sDrm.getPropertyString("provisioningModel").equals("BootCertificateChain")) { + // Not a provisioning 4.0 device. + return false; + } + return true; + } + + private boolean isProvisioned() { + int systemId = Integer.parseInt(sDrm.getPropertyString("systemId")); + if (systemId != Integer.MAX_VALUE) { + return true; + } + return false; + } + + @Test + public void testIfProvisioningNeededIsConsistentWithSystemStatus() { + if (!sSupportsWidevine) return; + assertEquals(isProvisioning4() && !isProvisioned(), + WidevineProvisioner.isWidevineProvisioningNeeded()); + } + + @Test + public void testProvisionWidevine() { + if (!sSupportsWidevine) return; + if (!isProvisioning4()) { + Log.i(TAG, "Not a provisioning 4.0 device."); + return; + } + WidevineProvisioner prov = TestWorkerBuilder.from( + ApplicationProvider.getApplicationContext(), + WidevineProvisioner.class, + Executors.newSingleThreadExecutor()).build(); + assertFalse(isProvisioned()); + assertEquals(ListenableWorker.Result.success(), prov.doWork()); + assertTrue(isProvisioned()); + } +} diff --git a/tests/unittests/Android.bp b/tests/unittests/Android.bp index 5c81b4b..ae88bc9 100644 --- a/tests/unittests/Android.bp +++ b/tests/unittests/Android.bp @@ -18,6 +18,7 @@ package { android_test { name: "RemoteProvisionerUnitTests", srcs: ["src/**/*.java"], + exclude_srcs: ["src/**/KeyRegisteredTest.java"], static_libs: [ "Nene", "androidx.test.core", @@ -38,6 +39,23 @@ android_test { instrumentation_for: "RemoteProvisioner", } +// Specifically tests to see if the key is registered for the given device under test. +android_test { + name: "RemoteProvisionerRegistrationTest", + srcs: ["src/**/KeyRegisteredTest.java"], + static_libs: [ + "androidx.test.core", + "androidx.test.rules", + "android.security.remoteprovisioning-java", + "platform-test-annotations", + "cbor-java", + ], + platform_apis: true, + test_config: "AndroidRegistrationTest.xml", + test_suites: ["device-tests"], + instrumentation_for: "RemoteProvisioner", +} + java_import { name: "tink-prebuilt", jars: ["tink-android-1.5.0.jar"], diff --git a/tests/unittests/AndroidRegistrationTest.xml b/tests/unittests/AndroidRegistrationTest.xml new file mode 100644 index 0000000..f01139a --- /dev/null +++ b/tests/unittests/AndroidRegistrationTest.xml @@ -0,0 +1,38 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2021 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<configuration description="Runs RemoteProvisioner app unit tests."> + <object type="module_controller" class="com.android.tradefed.testtype.suite.module.MinApiLevelModuleController"> + <option name="min-api-level" value="31" /> + <option name="api-level-prop" value="ro.product.first_api_level" /> + </object> + <option name="test-suite-tag" value="apct" /> + <option name="test-suite-tag" value="apct-instrumentation" /> + + <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"> + <option name="force-root" value="true" /> + </target_preparer> + + <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> + <option name="cleanup-apks" value="true" /> + <option name="test-file-name" value="RemoteProvisionerRegistrationTest.apk" /> + </target_preparer> + + <test class="com.android.tradefed.testtype.AndroidJUnitTest" > + <option name="package" value="com.android.remoteprovisioner.unittest" /> + <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" /> + <option name="test-filter-dir" value="/data/data/com.android.remoteprovisioner" /> + </test> +</configuration> diff --git a/tests/unittests/src/com/android/remoteprovisioner/unittest/CborUtilsTest.java b/tests/unittests/src/com/android/remoteprovisioner/unittest/CborUtilsTest.java index cc561a8..b112dcb 100644 --- a/tests/unittests/src/com/android/remoteprovisioner/unittest/CborUtilsTest.java +++ b/tests/unittests/src/com/android/remoteprovisioner/unittest/CborUtilsTest.java @@ -18,8 +18,10 @@ package com.android.remoteprovisioner.unittest; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; +import android.os.Build; import android.platform.test.annotations.Presubmit; import androidx.test.runner.AndroidJUnit4; @@ -375,7 +377,8 @@ public class CborUtilsTest { CborUtils.buildCertificateRequest(deviceInfo, challenge, protectedDataPayload, - macedKeysToSign); + macedKeysToSign, + CborUtils.buildUnverifiedDeviceInfo()); ByteArrayInputStream bais = new ByteArrayInputStream(certReq); List<DataItem> dataItems = new CborDecoder(bais).decode(); assertEquals(1, dataItems.size()); @@ -397,4 +400,14 @@ public class CborUtilsTest { // MacedKeysToSign assertEquals(MajorType.ARRAY, dataItems.get(3).getMajorType()); } + + @Test + public void testBuildUnverifiedDeviceInfo() throws Exception { + Map devInfo = CborUtils.buildUnverifiedDeviceInfo(); + assertEquals("Unverified device info only has one entry.", 1, devInfo.getKeys().size()); + DataItem fingerprint = devInfo.get(new UnicodeString("fingerprint")); + assertNotNull("Device info doesn't contain fingerprint", fingerprint); + assertEquals(MajorType.UNICODE_STRING, fingerprint.getMajorType()); + assertEquals(Build.FINGERPRINT, fingerprint.toString()); + } } diff --git a/tests/unittests/src/com/android/remoteprovisioner/unittest/KeyRegisteredTest.java b/tests/unittests/src/com/android/remoteprovisioner/unittest/KeyRegisteredTest.java new file mode 100644 index 0000000..109d6a4 --- /dev/null +++ b/tests/unittests/src/com/android/remoteprovisioner/unittest/KeyRegisteredTest.java @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.remoteprovisioner.unittest; + +import static android.hardware.security.keymint.SecurityLevel.TRUSTED_ENVIRONMENT; + +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; + +import android.content.Context; +import android.hardware.security.keymint.DeviceInfo; +import android.hardware.security.keymint.ProtectedData; +import android.os.Build; +import android.os.ServiceManager; +import android.security.IGenerateRkpKeyService.Status; +import android.security.remoteprovisioning.IRemoteProvisioning; +import android.security.remoteprovisioning.ImplInfo; + +import androidx.test.core.app.ApplicationProvider; +import androidx.test.runner.AndroidJUnit4; + +import com.android.remoteprovisioner.CborUtils; +import com.android.remoteprovisioner.GeekResponse; +import com.android.remoteprovisioner.ProvisionerMetrics; +import com.android.remoteprovisioner.RemoteProvisioningException; +import com.android.remoteprovisioner.ServerInterface; +import com.android.remoteprovisioner.SettingsManager; +import com.android.remoteprovisioner.SystemInterface; + +import org.junit.After; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.List; + +import co.nstant.in.cbor.model.Map; +import co.nstant.in.cbor.model.UnicodeString; + +@RunWith(AndroidJUnit4.class) +public class KeyRegisteredTest { + + private static final String SERVICE = "android.security.remoteprovisioning"; + + private static Context sContext; + private static IRemoteProvisioning sBinder; + private static int sCurve; + + @BeforeClass + public static void init() throws Exception { + sContext = ApplicationProvider.getApplicationContext(); + sBinder = + IRemoteProvisioning.Stub.asInterface(ServiceManager.getService(SERVICE)); + assertNotNull(sBinder); + ImplInfo[] info = sBinder.getImplementationInfo(); + for (int i = 0; i < info.length; i++) { + if (info[i].secLevel == TRUSTED_ENVIRONMENT) { + sCurve = info[i].supportedCurve; + break; + } + } + } + + @Before + public void setUp() throws Exception { + sBinder.deleteAllKeys(); + } + + @After + public void tearDown() throws Exception { + sBinder.deleteAllKeys(); + } + + private void requestCerts(int numKeys, int secLevel, byte[] geekChain, byte[] challenge, + IRemoteProvisioning binder, Context context, + ProvisionerMetrics metrics) throws Exception { + DeviceInfo deviceInfo = new DeviceInfo(); + ProtectedData protectedData = new ProtectedData(); + byte[] macedKeysToSign = SystemInterface.generateCsr(SettingsManager.isTestMode(), numKeys, + secLevel, geekChain, challenge, protectedData, deviceInfo, binder, metrics); + String fingerprint = Build.FINGERPRINT; + // The backend should provision test certs if the build isn't a user build. + // Registration status only factors into user builds, so set a debug property that + // will instruct the underlying provisioning code to appear as a user build to the + // backend if it isn't. + if (!Build.TYPE.equals("user")) { + fingerprint = fingerprint.replace("userdebug", "user"); + fingerprint = fingerprint.replace("eng", "user"); + } + Map unverifiedDeviceInfo = new Map(); + unverifiedDeviceInfo.put(new UnicodeString("fingerprint"), + new UnicodeString(fingerprint)); + byte[] certificateRequest = + CborUtils.buildCertificateRequest(deviceInfo.deviceInfo, + challenge, + protectedData.protectedData, + macedKeysToSign, + unverifiedDeviceInfo); + List<byte[]> certChains = ServerInterface.requestSignedCertificates(context, + certificateRequest, challenge, metrics); + } + + @Test + public void testKeyRegisteredTee() throws Exception { + try { + ProvisionerMetrics metrics = ProvisionerMetrics.createScheduledAttemptMetrics(sContext); + int numTestKeys = 1; + sBinder.generateKeyPair(SettingsManager.isTestMode(), TRUSTED_ENVIRONMENT); + GeekResponse geek = ServerInterface.fetchGeek(sContext, metrics); + assertNotNull(geek); + requestCerts(numTestKeys, TRUSTED_ENVIRONMENT, geek.getGeekChain(sCurve), + geek.getChallenge(), sBinder, sContext, metrics); + } catch (RemoteProvisioningException e) { + // Any exception will be a failure here, but specifically call out DEVICE_NOT_REGISTERED + // as a registration failure before throwing whatever other problem may have occurred. + assertNotEquals("Device isn't registered.", + Status.DEVICE_NOT_REGISTERED, e.getErrorCode()); + throw e; + } + } +} |