Add PackageInfoCompat signature verification APIs
Create methods for retrieving the Signature[] for a package on device,
as well as verifying that a package has a set of certificates.
Checking by app UID is not supported. Callers must use package name.
This is done to ensure the system doesn't arbitrarily choose a package
to check against.
A matchExact parameter is provided to address compatibility for
un-patched devices that are affected by the certificate reference fake
ID vulnerability. For such devices, all certificates of the package
being checked must be verified.
Bug: 159831205
Test: androidx.core.content.pm.PackageInfoCompatSignaturesTest
Test: androidx.core.content.pm.PackageInfoCompatTest
Relnote: "Added PackageInfoCompat#getSignatures for retrieving the
certificate array for a package"
Relnote: "Added PackageInfoCompat#hasSignatures for verifying package
ceritificates"
Change-Id: I8e9a3ece2d45416abbcbaaa0cf2a0485180997d3
diff --git a/testutils/testutils-mockito/build.gradle b/testutils/testutils-mockito/build.gradle
new file mode 100644
index 0000000..ea8a7de
--- /dev/null
+++ b/testutils/testutils-mockito/build.gradle
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2020 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.
+ */
+
+import static androidx.build.dependencies.DependenciesKt.*
+
+plugins {
+ id("AndroidXPlugin")
+ id("com.android.library")
+ id("kotlin-android")
+}
+
+dependencies {
+ api(MOCKITO_CORE, libs.exclude_bytebuddy)
+
+ implementation(KOTLIN_STDLIB)
+}
diff --git a/testutils/testutils-mockito/src/main/AndroidManifest.xml b/testutils/testutils-mockito/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..dbe1e34
--- /dev/null
+++ b/testutils/testutils-mockito/src/main/AndroidManifest.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 2020 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 package="androidx.testutils.mockito"/>
diff --git a/testutils/testutils-mockito/src/main/java/androidx/testutils/mockito/MockitoUtils.kt b/testutils/testutils-mockito/src/main/java/androidx/testutils/mockito/MockitoUtils.kt
new file mode 100644
index 0000000..96d4b0a
--- /dev/null
+++ b/testutils/testutils-mockito/src/main/java/androidx/testutils/mockito/MockitoUtils.kt
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2020 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 androidx.testutils.mockito
+
+import org.mockito.Answers
+import org.mockito.MockSettings
+import org.mockito.Mockito
+import org.mockito.invocation.InvocationOnMock
+import org.mockito.stubbing.Answer
+
+/**
+ * [Answer] variant intended for [MockSettings.defaultAnswer] that logs the unmocked method
+ * that was called, serializing the arguments used, to try and provide a more informative
+ * error message.
+ */
+val ANSWER_THROWS = Answer {
+ when (val name = it.method.name) {
+ // Delegate to the actual toString, since that will probably not be mocked by a test
+ "toString" -> Answers.CALLS_REAL_METHODS.answer(it)
+ else -> {
+ val arguments = it.arguments
+ ?.takeUnless { it.isEmpty() }
+ ?.mapIndexed { index, arg ->
+ try {
+ arg?.toString()
+ } catch (e: Exception) {
+ "toString[$index] threw ${e.message}"
+ }
+ }
+ ?.joinToString()
+ ?: "no arguments"
+
+ throw UnsupportedOperationException(
+ "${it.mock::class.java.simpleName}#$name with $arguments should not be called"
+ )
+ }
+ }
+}
+
+fun <Type : Any?> whenever(mock: Type, block: InvocationOnMock.() -> Type) =
+ Mockito.`when`(mock).thenAnswer { block(it) }!!
+
+/**
+ * Spy an existing object and allow mocking within [block]. Once the method returns, the spied
+ * instance is prepped to throw exceptions whenever an unmocked method is called. This can be
+ * used to enforce that only specifically mocked methods are called, avoiding unexpected
+ * results when the behavior under test adds code to call an unexpected method.
+ */
+inline fun <reified T> spyThrowOnUnmocked(value: T?, block: T.() -> Unit = {}): T {
+ val swappingAnswer = object : Answer<Any?> {
+ var delegate: Answer<*> = Answers.RETURNS_DEFAULTS
+
+ override fun answer(invocation: InvocationOnMock?): Any? {
+ return delegate.answer(invocation)
+ }
+ }
+
+ val settings = Mockito.withSettings()
+ .spiedInstance(value)
+ .defaultAnswer(swappingAnswer)
+
+ return Mockito.mock(T::class.java, settings)
+ .apply(block)
+ .also {
+ // To allow Mockito.when() usage inside block, only swap to throwing afterwards
+ swappingAnswer.delegate = ANSWER_THROWS
+ }
+}
+
+/**
+ * [Mockito.mock] equivalent of [spyThrowOnUnmocked] which doesn't spy an existing instance.
+ */
+inline fun <reified T> mockThrowOnUnmocked(block: T.() -> Unit = {}) =
+ spyThrowOnUnmocked(null, block)
\ No newline at end of file