diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index 04763070cded4..089c2691a277c 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -200,6 +200,8 @@ import android.safetycenter.SafetyCenterFrameworkInitializer; import android.scheduling.SchedulingFrameworkInitializer; import android.security.FileIntegrityManager; import android.security.IFileIntegrityService; +import android.security.attestationverification.AttestationVerificationManager; +import android.security.attestationverification.IAttestationVerificationManagerService; import android.service.oemlock.IOemLockService; import android.service.oemlock.OemLockManager; import android.service.persistentdata.IPersistentDataBlockService; @@ -1425,6 +1427,19 @@ public final class SystemServiceRegistry { return new FileIntegrityManager(ctx.getOuterContext(), IFileIntegrityService.Stub.asInterface(b)); }}); + + registerService(Context.ATTESTATION_VERIFICATION_SERVICE, + AttestationVerificationManager.class, + new CachedServiceFetcher() { + @Override + public AttestationVerificationManager createService(ContextImpl ctx) + throws ServiceNotFoundException { + IBinder b = ServiceManager.getServiceOrThrow( + Context.ATTESTATION_VERIFICATION_SERVICE); + return new AttestationVerificationManager(ctx.getOuterContext(), + IAttestationVerificationManagerService.Stub.asInterface(b)); + }}); + //CHECKSTYLE:ON IndentationCheck registerService(Context.APP_INTEGRITY_SERVICE, AppIntegrityManager.class, new CachedServiceFetcher() { diff --git a/core/java/android/security/attestationverification/AttestationVerificationManager.java b/core/java/android/security/attestationverification/AttestationVerificationManager.java index 8ed5b20b085ee..db783ceabcdb4 100644 --- a/core/java/android/security/attestationverification/AttestationVerificationManager.java +++ b/core/java/android/security/attestationverification/AttestationVerificationManager.java @@ -26,12 +26,19 @@ import android.annotation.RequiresPermission; import android.annotation.SystemService; import android.content.Context; import android.os.Bundle; +import android.os.ParcelDuration; +import android.os.RemoteException; +import android.util.Log; + +import com.android.internal.infra.AndroidFuture; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import java.time.Duration; import java.util.concurrent.Executor; +import java.util.concurrent.TimeUnit; import java.util.function.BiConsumer; /** @@ -43,6 +50,12 @@ import java.util.function.BiConsumer; @SystemService(Context.ATTESTATION_VERIFICATION_SERVICE) public class AttestationVerificationManager { + private static final String TAG = "AVF"; + private static final Duration MAX_TOKEN_AGE = Duration.ofHours(1); + + private final Context mContext; + private final IAttestationVerificationManagerService mService; + /** * Verifies that {@code attestation} describes a computing environment that meets the * requirements of {@code profile}, {@code localBindingType}, and {@code requirements}. @@ -96,7 +109,21 @@ public class AttestationVerificationManager { @NonNull byte[] attestation, @NonNull @CallbackExecutor Executor executor, @NonNull BiConsumer<@VerificationResult Integer, VerificationToken> callback) { - executor.execute(() -> callback.accept(RESULT_UNKNOWN, null)); + try { + AndroidFuture resultCallback = new AndroidFuture<>(); + resultCallback.thenAccept(result -> { + Log.d(TAG, "verifyAttestation result: " + result.resultCode + " / " + result.token); + executor.execute(() -> { + callback.accept(result.resultCode, result.token); + }); + }); + + mService.verifyAttestation(profile, localBindingType, requirements, attestation, + resultCallback); + + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } } /** @@ -134,12 +161,38 @@ public class AttestationVerificationManager { @LocalBindingType int localBindingType, @NonNull Bundle requirements, @NonNull VerificationToken token, - @Nullable java.time.Duration maximumAge) { - return RESULT_UNKNOWN; + @Nullable Duration maximumAge) { + Duration usedMaximumAge; + if (maximumAge == null) { + usedMaximumAge = MAX_TOKEN_AGE; + } else { + if (maximumAge.compareTo(MAX_TOKEN_AGE) > 0) { + throw new IllegalArgumentException( + "maximumAge cannot be greater than " + MAX_TOKEN_AGE + "; was " + + maximumAge); + } + usedMaximumAge = maximumAge; + } + + try { + AndroidFuture resultCallback = new AndroidFuture<>(); + resultCallback.orTimeout(5, TimeUnit.SECONDS); + + mService.verifyToken(token, new ParcelDuration(usedMaximumAge), resultCallback); + return resultCallback.get(); // block on result callback + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } catch (Throwable t) { + throw new RuntimeException("Error verifying token.", t); + } } /** @hide */ - public AttestationVerificationManager() { + public AttestationVerificationManager( + @NonNull Context context, + @NonNull IAttestationVerificationManagerService service) { + this.mContext = context; + this.mService = service; } /** @hide */ diff --git a/core/java/android/security/attestationverification/IAttestationVerificationManagerService.aidl b/core/java/android/security/attestationverification/IAttestationVerificationManagerService.aidl new file mode 100644 index 0000000000000..2fb328c3e1fb5 --- /dev/null +++ b/core/java/android/security/attestationverification/IAttestationVerificationManagerService.aidl @@ -0,0 +1,43 @@ +/* + * Copyright 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 android.security.attestationverification; + +import android.os.Bundle; +import android.os.ParcelDuration; +import android.security.attestationverification.AttestationProfile; +import android.security.attestationverification.VerificationToken; +import com.android.internal.infra.AndroidFuture; + + +/** + * Binder interface to communicate with AttestationVerificationManagerService. + * @hide + */ +oneway interface IAttestationVerificationManagerService { + + void verifyAttestation( + in AttestationProfile profile, + in int localBindingType, + in Bundle requirements, + in byte[] attestation, + in AndroidFuture resultCallback); + + void verifyToken( + in VerificationToken token, + in ParcelDuration maximumTokenAge, + in AndroidFuture resultCallback); +} diff --git a/core/java/android/security/attestationverification/IAttestationVerificationService.aidl b/core/java/android/security/attestationverification/IAttestationVerificationService.aidl new file mode 100644 index 0000000000000..082ad3247eadb --- /dev/null +++ b/core/java/android/security/attestationverification/IAttestationVerificationService.aidl @@ -0,0 +1,33 @@ +/* + * 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 android.security.attestationverification; + +import android.os.Bundle; +import com.android.internal.infra.AndroidFuture; + + +/** + * Binder interface for the system server to communicate with app implementations of + * AttestationVerificationService. + * @hide + */ +oneway interface IAttestationVerificationService { + void onVerifyAttestation( + in Bundle requirements, + in byte[] attestation, + in AndroidFuture callback); +} diff --git a/core/java/android/security/attestationverification/IVerificationResult.aidl b/core/java/android/security/attestationverification/IVerificationResult.aidl new file mode 100644 index 0000000000000..f61c45697b31b --- /dev/null +++ b/core/java/android/security/attestationverification/IVerificationResult.aidl @@ -0,0 +1,32 @@ +/* + * 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 android.security.attestationverification; + +import android.security.attestationverification.VerificationToken; + + +/** + * The result of an attestation verification. + * + * {@hide} + */ +parcelable IVerificationResult { + /** The result code corresponding to @VerificationResult. */ + int resultCode; + /** The token for the verification or null. */ + VerificationToken token; +} diff --git a/services/core/java/com/android/server/security/AttestationVerificationManagerService.java b/services/core/java/com/android/server/security/AttestationVerificationManagerService.java new file mode 100644 index 0000000000000..f519ceda1e501 --- /dev/null +++ b/services/core/java/com/android/server/security/AttestationVerificationManagerService.java @@ -0,0 +1,91 @@ +/* + * 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.server.security; + +import static android.security.attestationverification.AttestationVerificationManager.RESULT_UNKNOWN; + +import android.content.Context; +import android.os.Bundle; +import android.os.IBinder; +import android.os.ParcelDuration; +import android.os.RemoteException; +import android.security.attestationverification.AttestationProfile; +import android.security.attestationverification.IAttestationVerificationManagerService; +import android.security.attestationverification.IVerificationResult; +import android.security.attestationverification.VerificationToken; +import android.util.ExceptionUtils; +import android.util.Slog; + +import com.android.internal.infra.AndroidFuture; +import com.android.server.SystemService; + +/** + * A {@link SystemService} which provides functionality related to verifying attestations of + * (usually) remote computing environments. + * + * @hide + */ +public class AttestationVerificationManagerService extends SystemService { + + private static final String TAG = "AVF"; + + public AttestationVerificationManagerService(final Context context) { + super(context); + } + + private final IBinder mService = new IAttestationVerificationManagerService.Stub() { + @Override + public void verifyAttestation( + AttestationProfile profile, + int localBindingType, + Bundle requirements, + byte[] attestation, + AndroidFuture resultCallback) throws RemoteException { + try { + Slog.d(TAG, "verifyAttestation"); + verifyAttestationForAllVerifiers(profile, localBindingType, requirements, + attestation, resultCallback); + } catch (Throwable t) { + Slog.e(TAG, "failed to verify attestation", t); + throw ExceptionUtils.propagate(t, RemoteException.class); + } + } + + @Override + public void verifyToken(VerificationToken token, ParcelDuration parcelDuration, + AndroidFuture resultCallback) throws RemoteException { + // TODO(b/201696614): Implement + resultCallback.complete(RESULT_UNKNOWN); + } + }; + + private void verifyAttestationForAllVerifiers( + AttestationProfile profile, int localBindingType, Bundle requirements, + byte[] attestation, AndroidFuture resultCallback) { + // TODO(b/201696614): Implement + IVerificationResult result = new IVerificationResult(); + result.resultCode = RESULT_UNKNOWN; + result.token = null; + resultCallback.complete(result); + } + + @Override + public void onStart() { + Slog.d(TAG, "Started"); + publishBinderService(Context.ATTESTATION_VERIFICATION_SERVICE, mService); + } +} diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 3ede408f0c1c4..a72cf3a79d376 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -176,6 +176,7 @@ import com.android.server.recoverysystem.RecoverySystemService; import com.android.server.restrictions.RestrictionsManagerService; import com.android.server.role.RoleServicePlatformHelper; import com.android.server.rotationresolver.RotationResolverManagerService; +import com.android.server.security.AttestationVerificationManagerService; import com.android.server.security.FileIntegrityService; import com.android.server.security.KeyAttestationApplicationIdProviderService; import com.android.server.security.KeyChainSystemService; @@ -2339,6 +2340,10 @@ public final class SystemServer implements Dumpable { t.traceEnd(); } + t.traceBegin("StartAttestationVerificationService"); + mSystemServiceManager.startService(AttestationVerificationManagerService.class); + t.traceEnd(); + if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_COMPANION_DEVICE_SETUP)) { t.traceBegin("StartCompanionDeviceManager"); mSystemServiceManager.startService(COMPANION_DEVICE_MANAGER_SERVICE_CLASS); diff --git a/tests/AttestationVerificationTest/Android.bp b/tests/AttestationVerificationTest/Android.bp new file mode 100644 index 0000000000000..a4741eedaac08 --- /dev/null +++ b/tests/AttestationVerificationTest/Android.bp @@ -0,0 +1,44 @@ +// 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 { + default_applicable_licenses: ["frameworks_base_license"], +} + +android_test { + name: "AttestationVerificationTest", + srcs: [ + "src/**/*.java", + "src/**/*.kt", + ], + defaults: ["cts_defaults"], + manifest: "AndroidManifest.xml", + test_config: "AndroidTest.xml", + platform_apis: true, + certificate: "platform", + optimize: { + enabled: false, + }, + test_suites: ["device-tests"], + libs: [ + "android.test.runner", + "android.test.base", + ], + static_libs: [ + "compatibility-device-util-axt", + "androidx.test.rules", + "androidx.test.ext.junit", + "platform-test-annotations", + ], +} diff --git a/tests/AttestationVerificationTest/AndroidManifest.xml b/tests/AttestationVerificationTest/AndroidManifest.xml new file mode 100755 index 0000000000000..c42bde9dca3aa --- /dev/null +++ b/tests/AttestationVerificationTest/AndroidManifest.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + diff --git a/tests/AttestationVerificationTest/AndroidTest.xml b/tests/AttestationVerificationTest/AndroidTest.xml new file mode 100644 index 0000000000000..132576035952a --- /dev/null +++ b/tests/AttestationVerificationTest/AndroidTest.xml @@ -0,0 +1,27 @@ + + + + diff --git a/tests/AttestationVerificationTest/src/android/security/attestationverification/SystemAttestationVerificationTest.kt b/tests/AttestationVerificationTest/src/android/security/attestationverification/SystemAttestationVerificationTest.kt new file mode 100644 index 0000000000000..48bfd6f5d33c0 --- /dev/null +++ b/tests/AttestationVerificationTest/src/android/security/attestationverification/SystemAttestationVerificationTest.kt @@ -0,0 +1,90 @@ +package android.security.attestationverification + +import android.os.Bundle +import android.app.Activity +import androidx.test.ext.junit.rules.ActivityScenarioRule +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import org.junit.Assert.assertThrows +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import com.google.common.truth.Truth.assertThat +import android.security.attestationverification.AttestationVerificationManager.PROFILE_SELF_TRUSTED +import android.security.attestationverification.AttestationVerificationManager.TYPE_PUBLIC_KEY +import android.security.attestationverification.AttestationVerificationManager.RESULT_UNKNOWN +import java.lang.IllegalArgumentException +import java.time.Duration +import java.util.concurrent.CompletableFuture +import java.util.concurrent.TimeUnit + +/** Test for system-defined attestation verifiers. */ +@SmallTest +@RunWith(AndroidJUnit4::class) +class SystemAttestationVerificationTest { + + @get:Rule + val rule = ActivityScenarioRule(TestActivity::class.java) + + private lateinit var activity: Activity + private lateinit var avm: AttestationVerificationManager + + @Before + fun setup() { + rule.getScenario().onActivity { + avm = it.getSystemService(AttestationVerificationManager::class.java) + activity = it + } + } + + @Test + fun verifyAttestation_returnsUnknown() { + val future = CompletableFuture() + val profile = AttestationProfile(PROFILE_SELF_TRUSTED) + avm.verifyAttestation(profile, TYPE_PUBLIC_KEY, Bundle(), ByteArray(0), + activity.mainExecutor) { result, _ -> + future.complete(result) + } + + assertThat(future.getSoon()).isEqualTo(RESULT_UNKNOWN) + } + + @Test + fun verifyToken_returnsUnknown() { + val future = CompletableFuture() + val profile = AttestationProfile(PROFILE_SELF_TRUSTED) + avm.verifyAttestation(profile, TYPE_PUBLIC_KEY, Bundle(), ByteArray(0), + activity.mainExecutor) { _, token -> + val result = avm.verifyToken(profile, TYPE_PUBLIC_KEY, Bundle(), token, null) + future.complete(result) + } + + assertThat(future.getSoon()).isEqualTo(RESULT_UNKNOWN) + } + + @Test + fun verifyToken_tooBigMaxAgeThrows() { + val future = CompletableFuture() + val profile = AttestationProfile(PROFILE_SELF_TRUSTED) + avm.verifyAttestation(profile, TYPE_PUBLIC_KEY, Bundle(), ByteArray(0), + activity.mainExecutor) { _, token -> + future.complete(token) + } + + assertThrows(IllegalArgumentException::class.java) { + avm.verifyToken(profile, TYPE_PUBLIC_KEY, Bundle(), future.getSoon(), + Duration.ofSeconds(3601)) + } + } + + private fun CompletableFuture.getSoon(): T { + return this.get(1, TimeUnit.SECONDS) + } + + class TestActivity : Activity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + } + } +}