Merge "Handle ForegroundServiceStartNotAllowedException when starting the VM" into main
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/Application.kt b/android/TerminalApp/java/com/android/virtualization/terminal/Application.kt
index c427337..efe651e 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/Application.kt
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/Application.kt
@@ -18,21 +18,12 @@
import android.app.Application as AndroidApplication
import android.app.NotificationChannel
import android.app.NotificationManager
-import android.content.ComponentName
import android.content.Context
-import android.content.Intent
-import android.content.ServiceConnection
-import android.os.IBinder
-import androidx.lifecycle.DefaultLifecycleObserver
-import androidx.lifecycle.LifecycleOwner
-import androidx.lifecycle.ProcessLifecycleOwner
public class Application : AndroidApplication() {
override fun onCreate() {
super.onCreate()
setupNotificationChannels()
- val lifecycleObserver = ApplicationLifecycleObserver()
- ProcessLifecycleOwner.get().lifecycle.addObserver(lifecycleObserver)
}
private fun setupNotificationChannels() {
@@ -61,45 +52,4 @@
fun getInstance(c: Context): Application = c.getApplicationContext() as Application
}
-
- /**
- * Observes application lifecycle events and interacts with the VmLauncherService to manage
- * virtual machine state based on application lifecycle transitions. This class binds to the
- * VmLauncherService and notifies it of application lifecycle events (onStart, onStop), allowing
- * the service to manage the VM accordingly.
- */
- inner class ApplicationLifecycleObserver() : DefaultLifecycleObserver {
- private var vmLauncherService: VmLauncherService? = null
- private val connection =
- object : ServiceConnection {
- override fun onServiceConnected(className: ComponentName, service: IBinder) {
- val binder = service as VmLauncherService.VmLauncherServiceBinder
- vmLauncherService = binder.getService()
- }
-
- override fun onServiceDisconnected(arg0: ComponentName) {
- vmLauncherService = null
- }
- }
-
- override fun onCreate(owner: LifecycleOwner) {
- super.onCreate(owner)
- bindToVmLauncherService()
- }
-
- override fun onStart(owner: LifecycleOwner) {
- super.onStart(owner)
- vmLauncherService?.processAppLifeCycleEvent(ApplicationLifeCycleEvent.APP_ON_START)
- }
-
- override fun onStop(owner: LifecycleOwner) {
- vmLauncherService?.processAppLifeCycleEvent(ApplicationLifeCycleEvent.APP_ON_STOP)
- super.onStop(owner)
- }
-
- fun bindToVmLauncherService() {
- val intent = Intent(this@Application, VmLauncherService::class.java)
- this@Application.bindService(intent, connection, 0) // No BIND_AUTO_CREATE
- }
- }
}
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/ApplicationLifeCycleEvent.kt b/android/TerminalApp/java/com/android/virtualization/terminal/ApplicationLifeCycleEvent.kt
deleted file mode 100644
index 4e26c3c..0000000
--- a/android/TerminalApp/java/com/android/virtualization/terminal/ApplicationLifeCycleEvent.kt
+++ /dev/null
@@ -1,21 +0,0 @@
-/*
- * Copyright 2025 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.virtualization.terminal
-
-enum class ApplicationLifeCycleEvent {
- APP_ON_START,
- APP_ON_STOP,
-}
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/DebianServiceImpl.kt b/android/TerminalApp/java/com/android/virtualization/terminal/DebianServiceImpl.kt
index 887ae02..e035ad4 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/DebianServiceImpl.kt
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/DebianServiceImpl.kt
@@ -28,6 +28,7 @@
import com.android.virtualization.terminal.proto.ReportVmActivePortsResponse
import com.android.virtualization.terminal.proto.ShutdownQueueOpeningRequest
import com.android.virtualization.terminal.proto.ShutdownRequestItem
+import io.grpc.stub.ServerCallStreamObserver
import io.grpc.stub.StreamObserver
internal class DebianServiceImpl(context: Context) : DebianServiceImplBase() {
@@ -79,8 +80,15 @@
request: ShutdownQueueOpeningRequest?,
responseObserver: StreamObserver<ShutdownRequestItem?>,
) {
+ val serverCallStreamObserver = responseObserver as ServerCallStreamObserver<ShutdownRequestItem?>
+ serverCallStreamObserver.setOnCancelHandler {
+ shutdownRunnable = null
+ }
Log.d(TAG, "openShutdownRequestQueue")
shutdownRunnable = Runnable {
+ if (serverCallStreamObserver.isCancelled()) {
+ return@Runnable
+ }
responseObserver.onNext(ShutdownRequestItem.newBuilder().build())
responseObserver.onCompleted()
shutdownRunnable = null
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/MemBalloonController.kt b/android/TerminalApp/java/com/android/virtualization/terminal/MemBalloonController.kt
new file mode 100644
index 0000000..7647d9b
--- /dev/null
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/MemBalloonController.kt
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2025 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.virtualization.terminal
+
+import android.content.Context
+import android.os.Handler
+import android.os.Looper
+import android.system.virtualmachine.VirtualMachine
+import android.util.Log
+import androidx.lifecycle.DefaultLifecycleObserver
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.ProcessLifecycleOwner
+import com.android.virtualization.terminal.MainActivity.Companion.TAG
+import java.util.concurrent.Executors
+import java.util.concurrent.ScheduledFuture
+import java.util.concurrent.TimeUnit
+
+/**
+ * MemBalloonController is responsible for adjusting the memory ballon size of a VM depending on
+ * whether the app is visible or running in the background
+ */
+class MemBalloonController(val context: Context, val vm: VirtualMachine) {
+ companion object {
+ private const val INITIAL_PERCENT = 10
+ private const val MAX_PERCENT = 50
+ private const val INFLATION_STEP_PERCENT = 5
+ private const val INFLATION_PERIOD_SEC = 60L
+
+ private val mainHandler = Handler(Looper.getMainLooper())
+
+ private fun runOnMainThread(runnable: Runnable) {
+ mainHandler.post(runnable)
+ }
+ }
+
+ private val executor =
+ Executors.newSingleThreadScheduledExecutor(
+ TerminalThreadFactory(context.getApplicationContext())
+ )
+
+ private val observer =
+ object : DefaultLifecycleObserver {
+
+ // If the app is started or resumed, give deflate the balloon to 0 to give maximum
+ // available memory to the virtual machine
+ override fun onResume(owner: LifecycleOwner) {
+ ongoingInflation?.cancel(false)
+ executor.submit({
+ Log.v(TAG, "app resumed. deflating mem balloon to the minimum")
+ vm.setMemoryBalloonByPercent(0)
+ })
+ }
+
+ // If the app goes into background, progressively inflate the balloon from
+ // INITIAL_PERCENT until it reaches MAX_PERCENT
+ override fun onStop(owner: LifecycleOwner) {
+ ongoingInflation?.cancel(false)
+ balloonPercent = INITIAL_PERCENT
+ ongoingInflation =
+ executor.scheduleAtFixedRate(
+ {
+ if (balloonPercent <= MAX_PERCENT) {
+ Log.v(TAG, "inflating mem balloon to ${balloonPercent} %")
+ vm.setMemoryBalloonByPercent(balloonPercent)
+ balloonPercent += INFLATION_STEP_PERCENT
+ } else {
+ Log.v(TAG, "mem balloon is inflated to its max (${MAX_PERCENT} %)")
+ ongoingInflation!!.cancel(false)
+ }
+ },
+ 0 /* initialDelay */,
+ INFLATION_PERIOD_SEC,
+ TimeUnit.SECONDS,
+ )
+ }
+ }
+
+ private var balloonPercent = 0
+ private var ongoingInflation: ScheduledFuture<*>? = null
+
+ fun start() {
+ // addObserver is @MainThread
+ runOnMainThread({ ProcessLifecycleOwner.get().lifecycle.addObserver(observer) })
+ }
+
+ fun stop() {
+ // removeObserver is @MainThread
+ runOnMainThread({
+ ProcessLifecycleOwner.get().lifecycle.removeObserver(observer)
+ executor.shutdown()
+ })
+ }
+}
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/VmLauncherService.kt b/android/TerminalApp/java/com/android/virtualization/terminal/VmLauncherService.kt
index c49b7cd..2d7dde9 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/VmLauncherService.kt
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/VmLauncherService.kt
@@ -39,7 +39,6 @@
import android.system.virtualmachine.VirtualMachineException
import android.util.Log
import android.widget.Toast
-import com.android.internal.annotations.GuardedBy
import com.android.system.virtualmachine.flags.Flags.terminalGuiSupport
import com.android.virtualization.terminal.MainActivity.Companion.TAG
import com.android.virtualization.terminal.Runner.Companion.create
@@ -56,7 +55,6 @@
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
-import java.lang.Math.min
import java.lang.RuntimeException
import java.net.InetSocketAddress
import java.net.SocketAddress
@@ -67,12 +65,6 @@
import java.util.concurrent.TimeUnit
class VmLauncherService : Service() {
- inner class VmLauncherServiceBinder : android.os.Binder() {
- fun getService(): VmLauncherService = this@VmLauncherService
- }
-
- private val binder = VmLauncherServiceBinder()
-
private lateinit var executorService: ExecutorService
// TODO: using lateinit for some fields to avoid null
@@ -81,42 +73,6 @@
private var server: Server? = null
private var debianService: DebianServiceImpl? = null
private var portNotifier: PortNotifier? = null
- private var mLock = Object()
- @GuardedBy("mLock") private var currentMemBalloonPercent = 0
-
- @GuardedBy("mLock") private val inflateMemBalloonHandler = Handler(Looper.getMainLooper())
- private val inflateMemBalloonTask: Runnable =
- object : Runnable {
- override fun run() {
- synchronized(mLock) {
- if (
- currentMemBalloonPercent < INITIAL_MEM_BALLOON_PERCENT ||
- currentMemBalloonPercent > MAX_MEM_BALLOON_PERCENT
- ) {
- Log.e(
- TAG,
- "currentBalloonPercent=$currentMemBalloonPercent is invalid," +
- " should be in range: " +
- "$INITIAL_MEM_BALLOON_PERCENT~$MAX_MEM_BALLOON_PERCENT",
- )
- return
- }
- // Increases the balloon size by MEM_BALLOON_PERCENT_STEP% every time
- if (currentMemBalloonPercent < MAX_MEM_BALLOON_PERCENT) {
- currentMemBalloonPercent =
- min(
- MAX_MEM_BALLOON_PERCENT,
- currentMemBalloonPercent + MEM_BALLOON_PERCENT_STEP,
- )
- virtualMachine?.setMemoryBalloonByPercent(currentMemBalloonPercent)
- inflateMemBalloonHandler.postDelayed(
- this,
- MEM_BALLOON_INFLATE_INTERVAL_MILLIS,
- )
- }
- }
- }
- }
interface VmLauncherServiceCallback {
fun onVmStart()
@@ -129,45 +85,7 @@
}
override fun onBind(intent: Intent?): IBinder? {
- return binder
- }
-
- /**
- * Processes application lifecycle events and adjusts the virtual machine's memory balloon
- * accordingly.
- *
- * @param event The application lifecycle event.
- */
- fun processAppLifeCycleEvent(event: ApplicationLifeCycleEvent) {
- when (event) {
- // When the app starts, reset the memory balloon to 0%.
- // This gives the app maximum available memory.
- ApplicationLifeCycleEvent.APP_ON_START -> {
- synchronized(mLock) {
- inflateMemBalloonHandler.removeCallbacks(inflateMemBalloonTask)
- currentMemBalloonPercent = 0
- virtualMachine?.setMemoryBalloonByPercent(currentMemBalloonPercent)
- }
- }
- ApplicationLifeCycleEvent.APP_ON_STOP -> {
- // When the app stops, inflate the memory balloon to INITIAL_MEM_BALLOON_PERCENT.
- // Inflate the balloon by MEM_BALLOON_PERCENT_STEP every
- // MEM_BALLOON_INFLATE_INTERVAL_MILLIS milliseconds until reaching
- // MAX_MEM_BALLOON_PERCENT of total memory. This allows the system to reclaim
- // memory while the app is in the background.
- synchronized(mLock) {
- currentMemBalloonPercent = INITIAL_MEM_BALLOON_PERCENT
- virtualMachine?.setMemoryBalloonByPercent(currentMemBalloonPercent)
- inflateMemBalloonHandler.postDelayed(
- inflateMemBalloonTask,
- MEM_BALLOON_INFLATE_INTERVAL_MILLIS,
- )
- }
- }
- else -> {
- Log.e(TAG, "unrecognized lifecycle event: $event")
- }
- }
+ return null
}
override fun onCreate() {
@@ -221,7 +139,11 @@
ResultReceiver::class.java,
)
+ val mbc = MemBalloonController(this, virtualMachine!!)
+ mbc.start()
+
runner.exitStatus.thenAcceptAsync { success: Boolean ->
+ mbc.stop()
resultReceiver?.send(if (success) RESULT_STOP else RESULT_ERROR, null)
stopSelf()
}
@@ -493,11 +415,6 @@
}
}()
- private const val INITIAL_MEM_BALLOON_PERCENT = 10
- private const val MAX_MEM_BALLOON_PERCENT = 50
- private const val MEM_BALLOON_INFLATE_INTERVAL_MILLIS = 60000L
- private const val MEM_BALLOON_PERCENT_STEP = 5
-
private fun getMyIntent(context: Context): Intent {
return Intent(context.getApplicationContext(), VmLauncherService::class.java)
}
diff --git a/libs/dice/TEST_MAPPING b/libs/dice/TEST_MAPPING
index a43d7a2..d2d89e4 100644
--- a/libs/dice/TEST_MAPPING
+++ b/libs/dice/TEST_MAPPING
@@ -12,6 +12,9 @@
"name": "libdiced_open_dice.integration_test"
},
{
+ "name": "libdiced_open_dice_multialg.integration_test"
+ },
+ {
"name": "libdiced_open_dice_nostd.integration_test"
},
{
diff --git a/libs/dice/open_dice/Android.bp b/libs/dice/open_dice/Android.bp
index 986f496..739f245 100644
--- a/libs/dice/open_dice/Android.bp
+++ b/libs/dice/open_dice/Android.bp
@@ -63,6 +63,33 @@
],
}
+rust_library {
+ name: "libdiced_open_dice_multialg",
+ defaults: ["libdiced_open_dice_defaults"],
+ host_supported: true,
+ vendor_available: true,
+ rustlibs: [
+ "libcoset",
+ "libopen_dice_android_bindgen_multialg",
+ "libopen_dice_cbor_bindgen_multialg",
+ "libzeroize",
+ ],
+ features: [
+ "std",
+ "multialg",
+ ],
+ shared_libs: [
+ "libcrypto",
+ ],
+ visibility: [
+ "//system/software_defined_vehicle:__subpackages__",
+ ],
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.virt",
+ ],
+}
+
rust_defaults {
name: "libdiced_open_dice_test_defaults",
crate_name: "diced_open_dice_test",
@@ -80,6 +107,18 @@
}
rust_test {
+ name: "libdiced_open_dice_multialg.integration_test",
+ defaults: ["libdiced_open_dice_test_defaults"],
+ rustlibs: [
+ "libdiced_open_dice_multialg",
+ "libcoset",
+ ],
+ features: [
+ "multialg",
+ ],
+}
+
+rust_test {
name: "libdiced_open_dice_nostd.integration_test",
defaults: ["libdiced_open_dice_test_defaults"],
rustlibs: [
@@ -174,6 +213,20 @@
}
rust_bindgen {
+ name: "libopen_dice_cbor_bindgen_multialg",
+ defaults: [
+ "libopen_dice.rust_defaults",
+ "libopen_dice_cbor_bindgen.rust_defaults",
+ ],
+ bindgen_flags: [
+ "--rustified-enum DiceKeyAlgorithm",
+ "--allowlist-type=DiceContext",
+ ],
+ whole_static_libs: ["libopen_dice_cbor_multialg"],
+ shared_libs: ["libcrypto"],
+}
+
+rust_bindgen {
name: "libopen_dice_cbor_bindgen_nostd",
defaults: [
"libopen_dice_cbor_bindgen.rust_defaults",
@@ -231,6 +284,18 @@
}
rust_bindgen {
+ name: "libopen_dice_android_bindgen_multialg",
+ defaults: [
+ "libopen_dice.rust_defaults",
+ "libopen_dice_android_bindgen.rust_defaults",
+ ],
+ rustlibs: [
+ "libopen_dice_cbor_bindgen_multialg",
+ ],
+ whole_static_libs: ["libopen_dice_android_multialg"],
+}
+
+rust_bindgen {
name: "libopen_dice_android_bindgen_nostd",
defaults: [
"libopen_dice_android_bindgen.rust_defaults",
diff --git a/libs/dice/open_dice/src/lib.rs b/libs/dice/open_dice/src/lib.rs
index 33fb65c..9268b03 100644
--- a/libs/dice/open_dice/src/lib.rs
+++ b/libs/dice/open_dice/src/lib.rs
@@ -43,11 +43,19 @@
};
pub use error::{DiceError, Result};
pub use ops::{
- derive_cdi_leaf_priv, generate_certificate, hash, kdf, keypair_from_seed, sign,
- sign_cose_sign1, sign_cose_sign1_with_cdi_leaf_priv, verify,
+ derive_cdi_leaf_priv, generate_certificate, hash, kdf, keypair_from_seed, sign, verify,
+};
+#[cfg(feature = "multialg")]
+pub use ops::{
+ derive_cdi_leaf_priv_multialg, keypair_from_seed_multialg, sign_cose_sign1_multialg,
+ sign_cose_sign1_with_cdi_leaf_priv_multialg, verify_multialg,
};
pub use retry::{
retry_bcc_format_config_descriptor, retry_bcc_main_flow, retry_dice_main_flow,
retry_generate_certificate, retry_sign_cose_sign1, retry_sign_cose_sign1_with_cdi_leaf_priv,
OwnedDiceArtifacts,
};
+#[cfg(feature = "multialg")]
+pub use retry::{
+ retry_sign_cose_sign1_multialg, retry_sign_cose_sign1_with_cdi_leaf_priv_multialg,
+};
diff --git a/libs/dice/open_dice/src/ops.rs b/libs/dice/open_dice/src/ops.rs
index 2014118..b22b113 100644
--- a/libs/dice/open_dice/src/ops.rs
+++ b/libs/dice/open_dice/src/ops.rs
@@ -21,11 +21,17 @@
PRIVATE_KEY_SEED_SIZE, PRIVATE_KEY_SIZE, VM_KEY_ALGORITHM,
};
use crate::error::{check_result, DiceError, Result};
+#[cfg(feature = "multialg")]
+use crate::KeyAlgorithm;
use alloc::{vec, vec::Vec};
+#[cfg(feature = "multialg")]
+use open_dice_cbor_bindgen::DiceContext_;
use open_dice_cbor_bindgen::{
DiceCoseSignAndEncodeSign1, DiceGenerateCertificate, DiceHash, DiceKdf, DiceKeypairFromSeed,
DicePrincipal, DiceSign, DiceVerify,
};
+#[cfg(feature = "multialg")]
+use std::ffi::c_void;
use std::ptr;
/// Hashes the provided input using DICE's hash function `DiceHash`.
@@ -100,6 +106,44 @@
Ok((public_key, private_key))
}
+/// Deterministically generates a public and private key pair from `seed` and `key_algorithm`.
+/// Since this is deterministic, `seed` is as sensitive as a private key and can
+/// be used directly as the private key.
+#[cfg(feature = "multialg")]
+pub fn keypair_from_seed_multialg(
+ seed: &[u8; PRIVATE_KEY_SEED_SIZE],
+ key_algorithm: KeyAlgorithm,
+) -> Result<(Vec<u8>, PrivateKey)> {
+ let mut public_key = vec![0u8; key_algorithm.public_key_size()];
+ let mut private_key = PrivateKey::default();
+ // This function is used with an open-dice config that uses the same algorithms for the
+ // subject and authority. Therefore, the principal is irrelevant in this context as this
+ // function only derives the key pair cryptographically without caring about which
+ // principal it is for. Hence, we arbitrarily set it to `DicePrincipal::kDicePrincipalSubject`.
+ let principal = DicePrincipal::kDicePrincipalSubject;
+ let context = DiceContext_ {
+ authority_algorithm: key_algorithm.into(),
+ subject_algorithm: key_algorithm.into(),
+ };
+ check_result(
+ // SAFETY: The function writes to the `public_key` and `private_key` within the given
+ // bounds, and only reads the `seed`.
+ // The first argument is a pointer to a valid |DiceContext_| object for multi-alg
+ // open-dice.
+ unsafe {
+ DiceKeypairFromSeed(
+ &context as *const DiceContext_ as *mut c_void,
+ principal,
+ seed.as_ptr(),
+ public_key.as_mut_ptr(),
+ private_key.as_mut_ptr(),
+ )
+ },
+ public_key.len(),
+ )?;
+ Ok((public_key, private_key))
+}
+
/// Derives the CDI_Leaf_Priv from the provided `dice_artifacts`.
///
/// The corresponding public key is included in the leaf certificate of the DICE chain
@@ -114,6 +158,17 @@
Ok(private_key)
}
+/// Multialg variant of `derive_cdi_leaf_priv`.
+#[cfg(feature = "multialg")]
+pub fn derive_cdi_leaf_priv_multialg(
+ dice_artifacts: &dyn DiceArtifacts,
+ key_algorithm: KeyAlgorithm,
+) -> Result<PrivateKey> {
+ let cdi_priv_key_seed = derive_cdi_private_key_seed(dice_artifacts.cdi_attest())?;
+ let (_, private_key) = keypair_from_seed_multialg(cdi_priv_key_seed.as_array(), key_algorithm)?;
+ Ok(private_key)
+}
+
/// Signs the `message` with the given `private_key` using `DiceSign`.
pub fn sign(message: &[u8], private_key: &[u8; PRIVATE_KEY_SIZE]) -> Result<Vec<u8>> {
let mut signature = vec![0u8; VM_KEY_ALGORITHM.signature_size()];
@@ -173,6 +228,45 @@
Ok(encoded_signature_actual_size)
}
+/// Multialg variant of `sign_cose_sign1`.
+#[cfg(feature = "multialg")]
+pub fn sign_cose_sign1_multialg(
+ message: &[u8],
+ aad: &[u8],
+ private_key: &[u8; PRIVATE_KEY_SIZE],
+ encoded_signature: &mut [u8],
+ key_algorithm: KeyAlgorithm,
+) -> Result<usize> {
+ let mut encoded_signature_actual_size = 0;
+ let context = DiceContext_ {
+ authority_algorithm: key_algorithm.into(),
+ subject_algorithm: key_algorithm.into(),
+ };
+ check_result(
+ // SAFETY: The function writes to `encoded_signature` and `encoded_signature_actual_size`
+ // within the given bounds. It only reads `message`, `aad`, and `private_key` within their
+ // given bounds.
+ //
+ // The first argument is a pointer to a valid |DiceContext_| object for multi-alg
+ // open-dice.
+ unsafe {
+ DiceCoseSignAndEncodeSign1(
+ &context as *const DiceContext_ as *mut c_void,
+ message.as_ptr(),
+ message.len(),
+ aad.as_ptr(),
+ aad.len(),
+ private_key.as_ptr(),
+ encoded_signature.len(),
+ encoded_signature.as_mut_ptr(),
+ &mut encoded_signature_actual_size,
+ )
+ },
+ encoded_signature_actual_size,
+ )?;
+ Ok(encoded_signature_actual_size)
+}
+
/// Signs the `message` with a private key derived from the given `dice_artifacts`
/// CDI Attest. On success, places a `CoseSign1` encoded object in `encoded_signature`.
/// Uses `DiceCoseSignAndEncodeSign1`.
@@ -188,6 +282,19 @@
sign_cose_sign1(message, aad, private_key.as_array(), encoded_signature)
}
+/// Multialg variant of `sign_cose_sign1_with_cdi_leaf_priv`.
+#[cfg(feature = "multialg")]
+pub fn sign_cose_sign1_with_cdi_leaf_priv_multialg(
+ message: &[u8],
+ aad: &[u8],
+ dice_artifacts: &dyn DiceArtifacts,
+ encoded_signature: &mut [u8],
+ key_algorithm: KeyAlgorithm,
+) -> Result<usize> {
+ let private_key = derive_cdi_leaf_priv_multialg(dice_artifacts, key_algorithm)?;
+ sign_cose_sign1_multialg(message, aad, private_key.as_array(), encoded_signature, key_algorithm)
+}
+
/// Verifies the `signature` of the `message` with the given `public_key` using `DiceVerify`.
pub fn verify(message: &[u8], signature: &[u8], public_key: &[u8]) -> Result<()> {
if signature.len() != VM_KEY_ALGORITHM.signature_size()
@@ -212,6 +319,40 @@
)
}
+/// Multialg variant of `verify`.
+#[cfg(feature = "multialg")]
+pub fn verify_multialg(
+ message: &[u8],
+ signature: &[u8],
+ public_key: &[u8],
+ key_algorithm: KeyAlgorithm,
+) -> Result<()> {
+ if signature.len() != key_algorithm.signature_size()
+ || public_key.len() != key_algorithm.public_key_size()
+ {
+ return Err(DiceError::InvalidInput);
+ }
+ let context = DiceContext_ {
+ authority_algorithm: key_algorithm.into(),
+ subject_algorithm: key_algorithm.into(),
+ };
+ check_result(
+ // SAFETY: only reads the messages, signature and public key as constant values.
+ // The first argument is a pointer to a valid |DiceContext_| object for multi-alg
+ // open-dice.
+ unsafe {
+ DiceVerify(
+ &context as *const DiceContext_ as *mut c_void,
+ message.as_ptr(),
+ message.len(),
+ signature.as_ptr(),
+ public_key.as_ptr(),
+ )
+ },
+ 0,
+ )
+}
+
/// Generates an X.509 certificate from the given `subject_private_key_seed` and
/// `input_values`, and signed by `authority_private_key_seed`.
/// The subject private key seed is supplied here so the implementation can choose
diff --git a/libs/dice/open_dice/src/retry.rs b/libs/dice/open_dice/src/retry.rs
index cf36bc0..d793218 100644
--- a/libs/dice/open_dice/src/retry.rs
+++ b/libs/dice/open_dice/src/retry.rs
@@ -24,6 +24,11 @@
};
use crate::error::{DiceError, Result};
use crate::ops::{generate_certificate, sign_cose_sign1, sign_cose_sign1_with_cdi_leaf_priv};
+#[cfg(feature = "multialg")]
+use crate::{
+ ops::{sign_cose_sign1_multialg, sign_cose_sign1_with_cdi_leaf_priv_multialg},
+ KeyAlgorithm,
+};
use alloc::vec::Vec;
#[cfg(feature = "serde_derive")]
use serde_derive::{Deserialize, Serialize};
@@ -158,6 +163,19 @@
})
}
+/// Multialg variant of `retry_sign_cose_sign1`.
+#[cfg(feature = "multialg")]
+pub fn retry_sign_cose_sign1_multialg(
+ message: &[u8],
+ aad: &[u8],
+ private_key: &[u8; PRIVATE_KEY_SIZE],
+ key_algorithm: KeyAlgorithm,
+) -> Result<Vec<u8>> {
+ retry_with_measured_buffer(|encoded_signature| {
+ sign_cose_sign1_multialg(message, aad, private_key, encoded_signature, key_algorithm)
+ })
+}
+
/// Signs a message with the given the private key derived from the
/// CDI Attest of the given `dice_artifacts` and returns the signature
/// as an encoded CoseSign1 object.
@@ -170,3 +188,22 @@
sign_cose_sign1_with_cdi_leaf_priv(message, aad, dice_artifacts, encoded_signature)
})
}
+
+/// Multialg variant of `retry_sign_cose_sign1_with_cdi_leaf_priv`.
+#[cfg(feature = "multialg")]
+pub fn retry_sign_cose_sign1_with_cdi_leaf_priv_multialg(
+ message: &[u8],
+ aad: &[u8],
+ dice_artifacts: &dyn DiceArtifacts,
+ key_algorithm: KeyAlgorithm,
+) -> Result<Vec<u8>> {
+ retry_with_measured_buffer(|encoded_signature| {
+ sign_cose_sign1_with_cdi_leaf_priv_multialg(
+ message,
+ aad,
+ dice_artifacts,
+ encoded_signature,
+ key_algorithm,
+ )
+ })
+}
diff --git a/libs/dice/open_dice/tests/api_test.rs b/libs/dice/open_dice/tests/api_test.rs
index b0c2ca7..0f8af10 100644
--- a/libs/dice/open_dice/tests/api_test.rs
+++ b/libs/dice/open_dice/tests/api_test.rs
@@ -21,6 +21,11 @@
retry_sign_cose_sign1, retry_sign_cose_sign1_with_cdi_leaf_priv, sign, verify,
DiceArtifacts, PrivateKey, CDI_SIZE, HASH_SIZE, ID_SIZE, PRIVATE_KEY_SEED_SIZE,
};
+ #[cfg(feature = "multialg")]
+ use diced_open_dice::{
+ keypair_from_seed_multialg, retry_sign_cose_sign1_multialg,
+ retry_sign_cose_sign1_with_cdi_leaf_priv_multialg, verify_multialg, KeyAlgorithm,
+ };
use coset::{CborSerializable, CoseSign1};
@@ -93,7 +98,21 @@
0xfc, 0x23, 0xc9, 0x21, 0x5c, 0x48, 0x21, 0x47, 0xee, 0x5b, 0xfa, 0xaf, 0x88, 0x9a, 0x52,
0xf1, 0x61, 0x06, 0x37,
];
-
+ #[cfg(feature = "multialg")]
+ const EXPECTED_EC_P256_PUB_KEY: &[u8] = &[
+ 0xa7, 0x93, 0x70, 0x16, 0xff, 0xe8, 0x3c, 0x23, 0x5f, 0x6b, 0xf9, 0x38, 0x7e, 0x9c, 0xe5,
+ 0x21, 0xb5, 0x8a, 0x9b, 0x68, 0x5a, 0x2f, 0x62, 0xf4, 0x15, 0x94, 0x1c, 0x61, 0xb3, 0xbb,
+ 0xe1, 0x26, 0x61, 0x47, 0x97, 0xbf, 0x3a, 0x1f, 0x6b, 0x87, 0x86, 0x47, 0x5e, 0xc3, 0xa6,
+ 0x8b, 0x95, 0x89, 0x9e, 0x29, 0xd5, 0x55, 0x2a, 0xdd, 0x2a, 0x3f, 0xe5, 0xf0, 0x7a, 0xd6,
+ 0xc4, 0x7b, 0x64, 0xe0,
+ ];
+ #[cfg(feature = "multialg")]
+ const EXPECTED_EC_P256_PRIV_KEY: &[u8] = &[
+ 0x62, 0x32, 0x1b, 0xb, 0x5c, 0xac, 0x8f, 0x20, 0x61, 0xb7, 0xa3, 0xbb, 0x46, 0x2b, 0x4e,
+ 0xb3, 0x3f, 0xa7, 0xf6, 0x9b, 0x2f, 0x5b, 0x80, 0xa8, 0x55, 0x5e, 0x80, 0x26, 0xbb, 0x72,
+ 0xbe, 0xe7, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ ];
const EXPECTED_SIGNATURE: &[u8] = &[
0x44, 0xae, 0xcc, 0xe2, 0xb9, 0x96, 0x18, 0x39, 0x0e, 0x61, 0x0f, 0x53, 0x07, 0xbf, 0xf2,
0x32, 0x3d, 0x44, 0xd4, 0xf2, 0x07, 0x23, 0x30, 0x85, 0x32, 0x18, 0xd2, 0x69, 0xb8, 0x29,
@@ -140,6 +159,41 @@
assert!(verify_result.is_err());
}
+ #[cfg(feature = "multialg")]
+ #[test]
+ fn sign_cose_sign1_verify_multialg() {
+ let (pub_key, priv_key) = get_test_key_pair_ec_p256();
+
+ let signature_res = retry_sign_cose_sign1_multialg(
+ b"MyMessage",
+ b"MyAad",
+ priv_key.as_array(),
+ KeyAlgorithm::EcdsaP256,
+ );
+ assert!(signature_res.is_ok());
+ let signature = signature_res.unwrap();
+ let cose_sign1_res = CoseSign1::from_slice(&signature);
+ assert!(cose_sign1_res.is_ok());
+ let mut cose_sign1 = cose_sign1_res.unwrap();
+
+ let mut verify_result = cose_sign1.verify_signature(b"MyAad", |sign, data| {
+ verify_multialg(data, sign, &pub_key, KeyAlgorithm::EcdsaP256)
+ });
+ assert!(verify_result.is_ok());
+
+ verify_result = cose_sign1.verify_signature(b"BadAad", |sign, data| {
+ verify_multialg(data, sign, &pub_key, KeyAlgorithm::EcdsaP256)
+ });
+ assert!(verify_result.is_err());
+
+ // if we modify the signature, the payload should no longer verify
+ cose_sign1.signature.push(0xAA);
+ verify_result = cose_sign1.verify_signature(b"MyAad", |sign, data| {
+ verify_multialg(data, sign, &pub_key, KeyAlgorithm::EcdsaP256)
+ });
+ assert!(verify_result.is_err());
+ }
+
struct TestArtifactsForSigning {}
impl DiceArtifacts for TestArtifactsForSigning {
@@ -182,6 +236,41 @@
assert!(verify_result.is_err());
}
+ #[cfg(feature = "multialg")]
+ #[test]
+ fn sign_cose_sign1_with_cdi_leaf_priv_verify_multialg() {
+ let dice = TestArtifactsForSigning {};
+
+ let signature_res = retry_sign_cose_sign1_with_cdi_leaf_priv_multialg(
+ b"MyMessage",
+ b"MyAad",
+ &dice,
+ KeyAlgorithm::EcdsaP256,
+ );
+ assert!(signature_res.is_ok());
+ let signature = signature_res.unwrap();
+ let cose_sign1_res = CoseSign1::from_slice(&signature);
+ assert!(cose_sign1_res.is_ok());
+ let mut cose_sign1 = cose_sign1_res.unwrap();
+
+ let mut verify_result = cose_sign1.verify_signature(b"MyAad", |sign, data| {
+ verify_multialg(data, sign, EXPECTED_EC_P256_PUB_KEY, KeyAlgorithm::EcdsaP256)
+ });
+ assert!(verify_result.is_ok());
+
+ verify_result = cose_sign1.verify_signature(b"BadAad", |sign, data| {
+ verify_multialg(data, sign, EXPECTED_EC_P256_PUB_KEY, KeyAlgorithm::EcdsaP256)
+ });
+ assert!(verify_result.is_err());
+
+ // if we modify the signature, the payload should no longer verify
+ cose_sign1.signature.push(0xAA);
+ verify_result = cose_sign1.verify_signature(b"MyAad", |sign, data| {
+ verify_multialg(data, sign, EXPECTED_EC_P256_PUB_KEY, KeyAlgorithm::EcdsaP256)
+ });
+ assert!(verify_result.is_err());
+ }
+
fn get_test_key_pair() -> (Vec<u8>, PrivateKey) {
let seed = hash(b"MySeedString").unwrap();
assert_eq!(seed, EXPECTED_SEED);
@@ -196,4 +285,23 @@
(pub_key, priv_key)
}
+
+ #[cfg(feature = "multialg")]
+ fn get_test_key_pair_ec_p256() -> (Vec<u8>, PrivateKey) {
+ let seed = hash(b"MySeedString").unwrap();
+ assert_eq!(seed, EXPECTED_SEED);
+ let cdi_attest = &seed[..CDI_SIZE];
+ assert_eq!(cdi_attest, EXPECTED_CDI_ATTEST);
+ let cdi_private_key_seed =
+ derive_cdi_private_key_seed(cdi_attest.try_into().unwrap()).unwrap();
+ assert_eq!(cdi_private_key_seed.as_array(), EXPECTED_CDI_PRIVATE_KEY_SEED);
+ let (pub_key, priv_key) =
+ keypair_from_seed_multialg(cdi_private_key_seed.as_array(), KeyAlgorithm::EcdsaP256)
+ .unwrap();
+
+ assert_eq!(&pub_key, EXPECTED_EC_P256_PUB_KEY);
+ assert_eq!(priv_key.as_array(), EXPECTED_EC_P256_PRIV_KEY);
+
+ (pub_key, priv_key)
+ }
}
diff --git a/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java b/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java
index 4294df4..4523572 100644
--- a/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java
+++ b/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java
@@ -625,7 +625,7 @@
stdout.transferTo(out);
stderr.transferTo(out);
String output = out.toString("UTF-8");
- Log.i(tag, "Got output : " + stdout);
+ Log.i(tag, "Got stdout + stderr : " + output);
return output;
} catch (IOException e) {
Log.e(tag, "Error executing: " + command, e);