Merge "Add last_update_seconds."
diff --git a/TEST_MAPPING b/TEST_MAPPING
index c4ee878..6338c82 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -14,6 +14,9 @@
},
{
"name": "art_standalone_dexpreopt_tests"
+ },
+ {
+ "name": "compsvc_device_tests"
}
],
"postsubmit": [
diff --git a/binder_common/lazy_service.rs b/binder_common/lazy_service.rs
index a2b85db..9d605b6 100644
--- a/binder_common/lazy_service.rs
+++ b/binder_common/lazy_service.rs
@@ -17,7 +17,7 @@
//! Rust API for lazy (aka dynamic) AIDL services.
//! See https://source.android.com/devices/architecture/aidl/dynamic-aidl.
-use binder::public_api::force_lazy_services_persist;
+use binder::force_lazy_services_persist;
use lazy_static::lazy_static;
use std::sync::Mutex;
diff --git a/binder_common/lib.rs b/binder_common/lib.rs
index fa91f5a..fd81da5 100644
--- a/binder_common/lib.rs
+++ b/binder_common/lib.rs
@@ -20,7 +20,7 @@
pub mod rpc_client;
pub mod rpc_server;
-use binder::public_api::{ExceptionCode, Status};
+use binder::{ExceptionCode, Status};
use std::ffi::CString;
/// Constructs a new Binder error `Status` with the given `ExceptionCode` and message.
diff --git a/binder_common/rpc_client.rs b/binder_common/rpc_client.rs
index 262a689..1aabe84 100644
--- a/binder_common/rpc_client.rs
+++ b/binder_common/rpc_client.rs
@@ -16,14 +16,14 @@
//! Helpers for implementing an RPC Binder client.
-use binder::public_api::{StatusCode, Strong};
use binder::unstable_api::{new_spibinder, AIBinder};
+use binder::{StatusCode, Strong};
/// Connects to a binder RPC server.
pub fn connect_rpc_binder<T: binder::FromIBinder + ?Sized>(
cid: u32,
port: u32,
-) -> binder::Result<Strong<T>> {
+) -> Result<Strong<T>, StatusCode> {
// SAFETY: AIBinder returned by RpcClient has correct reference count, and the ownership can be
// safely taken by new_spibinder.
let ibinder = unsafe {
diff --git a/binder_common/rpc_server.rs b/binder_common/rpc_server.rs
index 36075cf..5c9d2a0 100644
--- a/binder_common/rpc_server.rs
+++ b/binder_common/rpc_server.rs
@@ -16,8 +16,8 @@
//! Helpers for implementing an RPC Binder server.
-use binder::public_api::SpIBinder;
use binder::unstable_api::AsNative;
+use binder::SpIBinder;
use std::os::raw;
/// Run a binder RPC server, serving the supplied binder service implementation on the given vsock
diff --git a/compos/Android.bp b/compos/Android.bp
index 401f1c7..658f8bf 100644
--- a/compos/Android.bp
+++ b/compos/Android.bp
@@ -2,14 +2,12 @@
default_applicable_licenses: ["Android-Apache-2.0"],
}
-rust_binary {
- name: "compsvc",
+rust_defaults {
+ name: "compsvc_defaults",
srcs: ["src/compsvc_main.rs"],
rustlibs: [
"android.hardware.security.dice-V1-rust",
- "android.hardware.security.keymint-V1-rust",
"android.security.dice-rust",
- "android.system.keystore2-V1-rust",
"android.system.virtualmachineservice-rust",
"authfs_aidl_interface-rust",
"compos_aidl_interface-rust",
@@ -20,6 +18,7 @@
"libbinder_rs",
"libclap",
"libcompos_common",
+ "libcompos_native_rust",
"libenv_logger",
"liblibc",
"liblog_rust",
@@ -35,8 +34,20 @@
prefer_rlib: true,
shared_libs: [
"libbinder_rpc_unstable",
+ "libcrypto",
],
+}
+
+rust_binary {
+ name: "compsvc",
+ defaults: ["compsvc_defaults"],
apex_available: [
"com.android.compos",
],
}
+
+rust_test {
+ name: "compsvc_device_tests",
+ defaults: ["compsvc_defaults"],
+ test_suites: ["device-tests"],
+}
diff --git a/compos/aidl/com/android/compos/CompOsKeyData.aidl b/compos/aidl/com/android/compos/CompOsKeyData.aidl
index 381ec0d..dafb009 100644
--- a/compos/aidl/com/android/compos/CompOsKeyData.aidl
+++ b/compos/aidl/com/android/compos/CompOsKeyData.aidl
@@ -19,9 +19,10 @@
/** {@hide} */
parcelable CompOsKeyData {
/**
- * Self-signed certificate (X.509 DER) containing the public key.
+ * The public key, as a DER-encoded RSAPublicKey
+ * (https://datatracker.ietf.org/doc/html/rfc8017#appendix-A.1.1).
*/
- byte[] certificate;
+ byte[] publicKey;
/**
* Opaque encrypted blob containing the private key and related metadata.
diff --git a/compos/aidl/com/android/compos/FdAnnotation.aidl b/compos/aidl/com/android/compos/FdAnnotation.aidl
deleted file mode 100644
index b910391..0000000
--- a/compos/aidl/com/android/compos/FdAnnotation.aidl
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * 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.compos;
-
-/** {@hide} */
-parcelable FdAnnotation {
- /**
- * Input file descriptor numbers to be passed to the program. This is currently assumed to be
- * same as the file descriptor number used in the backend server.
- */
- int[] input_fds;
-
- /**
- * Output file descriptor numbers to be passed to the program. This is currently assumed to be
- * same as the file descriptor number used in the backend server.
- */
- int[] output_fds;
-}
diff --git a/compos/aidl/com/android/compos/ICompOsService.aidl b/compos/aidl/com/android/compos/ICompOsService.aidl
index b2b961a..cead5d0 100644
--- a/compos/aidl/com/android/compos/ICompOsService.aidl
+++ b/compos/aidl/com/android/compos/ICompOsService.aidl
@@ -17,7 +17,6 @@
package com.android.compos;
import com.android.compos.CompOsKeyData;
-import com.android.compos.FdAnnotation;
/** {@hide} */
interface ICompOsService {
@@ -51,21 +50,6 @@
String zygoteArch, String systemServerCompilerFilter);
/**
- * Runs dexopt compilation encoded in the marshaled dexopt arguments.
- *
- * To keep ART indepdendantly updatable, the compilation arguments are not stabilized. As a
- * result, the arguments are marshaled into byte array. Upon received, the service asks ART to
- * return relevant information (since ART is able to unmarshal its own encoding), in order to
- * set up the execution context (mainly file descriptors for compiler input and output) then
- * invokes the compiler.
- *
- * @param marshaledArguments The marshaled dexopt arguments.
- * @param fd_annotation Additional file descriptor information of the execution.
- * @return exit code
- */
- byte compile(in byte[] marshaledArguments, in FdAnnotation fd_annotation);
-
- /**
* Generate a new public/private key pair suitable for signing CompOs output files.
*
* @return a certificate for the public key and the encrypted private key
@@ -82,9 +66,4 @@
* @return whether the inputs are valid and correspond to each other.
*/
boolean verifySigningKey(in byte[] keyBlob, in byte[] publicKey);
-
- /**
- * Returns the DICE BCC for this instance of CompOS, allowing signatures to be verified.
- */
- byte[] getBootCertificateChain();
}
diff --git a/compos/apex/composd.rc b/compos/apex/composd.rc
index bf34335..3e2efb1 100644
--- a/compos/apex/composd.rc
+++ b/compos/apex/composd.rc
@@ -17,6 +17,5 @@
user root
group system
interface aidl android.system.composd
- interface aidl android.system.composd.internal
disabled
oneshot
diff --git a/compos/common/binder.rs b/compos/common/binder.rs
index 6bd3957..ae857e0 100644
--- a/compos/common/binder.rs
+++ b/compos/common/binder.rs
@@ -17,7 +17,7 @@
//! Helper for converting Error types to what Binder expects
use anyhow::Result;
-use binder::public_api::{ExceptionCode, Result as BinderResult};
+use binder::{ExceptionCode, Result as BinderResult};
use binder_common::new_binder_exception;
use log::warn;
use std::fmt::Debug;
diff --git a/compos/common/compos_client.rs b/compos/common/compos_client.rs
index b54a921..f4b3440 100644
--- a/compos/common/compos_client.rs
+++ b/compos/common/compos_client.rs
@@ -46,6 +46,9 @@
use std::sync::{Arc, Condvar, Mutex};
use std::thread;
+// Enough memory to complete odrefresh in the VM.
+const VM_MEMORY_MIB: i32 = 1024;
+
/// This owns an instance of the CompOS VM.
pub struct VmInstance {
#[allow(dead_code)] // Keeps the VM alive even if we don`t touch it
@@ -119,9 +122,9 @@
configPath: config_path.to_owned(),
debugLevel: debug_level,
extraIdsigs: vec![idsig_manifest_apk_fd],
+ memoryMib: VM_MEMORY_MIB,
numCpus: parameters.cpus.map_or(1, NonZeroU32::get) as i32,
cpuAffinity: parameters.cpu_set.clone(),
- ..Default::default()
});
let vm = service
diff --git a/compos/compos_key_cmd/Android.bp b/compos/compos_key_cmd/Android.bp
index 9d5a490..d412f66 100644
--- a/compos/compos_key_cmd/Android.bp
+++ b/compos/compos_key_cmd/Android.bp
@@ -17,7 +17,6 @@
"libbase",
"libbinder_ndk",
"libbinder_rpc_unstable",
- "libcrypto",
"libfsverity",
"libprotobuf-cpp-lite",
],
diff --git a/compos/compos_key_cmd/compos_key_cmd.cpp b/compos/compos_key_cmd/compos_key_cmd.cpp
index 8ed07e6..07ff636 100644
--- a/compos/compos_key_cmd/compos_key_cmd.cpp
+++ b/compos/compos_key_cmd/compos_key_cmd.cpp
@@ -27,10 +27,6 @@
#include <asm/byteorder.h>
#include <libfsverity.h>
#include <linux/fsverity.h>
-#include <openssl/evp.h>
-#include <openssl/mem.h>
-#include <openssl/sha.h>
-#include <openssl/x509.h>
#include <stdio.h>
#include <unistd.h>
@@ -73,6 +69,8 @@
constexpr unsigned int kRpcPort = 6432;
+constexpr int kVmMemoryMib = 1024;
+
constexpr const char* kConfigApkPath =
"/apex/com.android.compos/app/CompOSPayloadApp/CompOSPayloadApp.apk";
@@ -279,7 +277,7 @@
appConfig.configPath = mPreferStaged ? kPreferStagedConfigFilePath : kDefaultConfigFilePath;
appConfig.debugLevel = mDebuggable ? VirtualMachineAppConfig::DebugLevel::FULL
: VirtualMachineAppConfig::DebugLevel::NONE;
- appConfig.memoryMib = 0; // Use default
+ appConfig.memoryMib = kVmMemoryMib;
LOG(INFO) << "Starting VM";
auto status = service->createVm(config, logFd, logFd, &mVm);
@@ -329,37 +327,6 @@
} // namespace
-static Result<std::vector<uint8_t>> extractRsaPublicKey(
- const std::vector<uint8_t>& der_certificate) {
- auto data = der_certificate.data();
- bssl::UniquePtr<X509> x509(d2i_X509(nullptr, &data, der_certificate.size()));
- if (!x509) {
- return Error() << "Failed to parse certificate";
- }
- if (data != der_certificate.data() + der_certificate.size()) {
- return Error() << "Certificate has unexpected trailing data";
- }
-
- bssl::UniquePtr<EVP_PKEY> pkey(X509_get_pubkey(x509.get()));
- if (EVP_PKEY_base_id(pkey.get()) != EVP_PKEY_RSA) {
- return Error() << "Subject key is not RSA";
- }
- RSA* rsa = EVP_PKEY_get0_RSA(pkey.get());
- if (!rsa) {
- return Error() << "Failed to extract RSA key";
- }
-
- uint8_t* out = nullptr;
- int size = i2d_RSAPublicKey(rsa, &out);
- if (size < 0 || !out) {
- return Error() << "Failed to convert to RSAPublicKey";
- }
-
- bssl::UniquePtr<uint8_t> buffer(out);
- std::vector<uint8_t> result(out, out + size);
- return result;
-}
-
static Result<void> generate(TargetVm& vm, const std::string& blob_file,
const std::string& public_key_file) {
auto cid = vm.resolveCid();
@@ -377,15 +344,11 @@
return Error() << "Failed to generate key: " << status.getDescription();
}
- auto public_key = extractRsaPublicKey(key_data.certificate);
- if (!public_key.ok()) {
- return Error() << "Failed to extract public key from cert: " << public_key.error();
- }
if (!writeBytesToFile(key_data.keyBlob, blob_file)) {
return Error() << "Failed to write keyBlob to " << blob_file;
}
- if (!writeBytesToFile(public_key.value(), public_key_file)) {
+ if (!writeBytesToFile(key_data.publicKey, public_key_file)) {
return Error() << "Failed to write public key to " << public_key_file;
}
diff --git a/compos/composd/Android.bp b/compos/composd/Android.bp
index 3190395..3b545e5 100644
--- a/compos/composd/Android.bp
+++ b/compos/composd/Android.bp
@@ -9,7 +9,6 @@
prefer_rlib: true,
rustlibs: [
"android.system.composd-rust",
- "android.system.composd.internal-rust",
"android.system.virtualizationservice-rust",
"compos_aidl_interface-rust",
"libandroid_logger",
diff --git a/compos/composd/aidl/Android.bp b/compos/composd/aidl/Android.bp
index 376313b..56b0b60 100644
--- a/compos/composd/aidl/Android.bp
+++ b/compos/composd/aidl/Android.bp
@@ -19,19 +19,3 @@
},
},
}
-
-aidl_interface {
- name: "android.system.composd.internal",
- srcs: ["android/system/composd/internal/*.aidl"],
- imports: ["compos_aidl_interface"],
- // TODO: Make this stable when the APEX becomes updatable.
- unstable: true,
- backend: {
- rust: {
- enabled: true,
- apex_available: [
- "com.android.compos",
- ],
- },
- },
-}
diff --git a/compos/composd/aidl/android/system/composd/IIsolatedCompilationService.aidl b/compos/composd/aidl/android/system/composd/IIsolatedCompilationService.aidl
index 0b5eec1..8156265 100644
--- a/compos/composd/aidl/android/system/composd/IIsolatedCompilationService.aidl
+++ b/compos/composd/aidl/android/system/composd/IIsolatedCompilationService.aidl
@@ -42,10 +42,4 @@
* a reference to the ICompilationTask until compilation completes or is cancelled.
*/
ICompilationTask startTestCompile(ICompilationTaskCallback callback);
-
- /**
- * For testing.
- * TODO(b/214233409): Remove
- */
- byte[] getBcc();
}
diff --git a/compos/composd/aidl/android/system/composd/internal/ICompilationInternal.aidl b/compos/composd/aidl/android/system/composd/internal/ICompilationInternal.aidl
deleted file mode 100644
index a16a0a3..0000000
--- a/compos/composd/aidl/android/system/composd/internal/ICompilationInternal.aidl
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * 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.system.composd.internal;
-
-import com.android.compos.FdAnnotation;
-
-interface ICompilationInternal {
- /**
- * Run dex2oat in the currently running instance of the CompOS VM. This is a simple proxy
- * to ICompOsService#compile.
- *
- * This method can only be called from libcompos_client. If there is no currently running
- * instance an error is returned.
- */
- byte compile(in byte[] marshaledArguments, in FdAnnotation fd_annotation);
-}
diff --git a/compos/composd/native/Android.bp b/compos/composd/native/Android.bp
index 135f4d4..ccd8651 100644
--- a/compos/composd/native/Android.bp
+++ b/compos/composd/native/Android.bp
@@ -8,40 +8,10 @@
srcs: ["lib.rs"],
rustlibs: [
"libanyhow",
- "libcxx",
"liblibc",
],
- static_libs: [
- "libcomposd_native_cpp",
- ],
shared_libs: [
"libartpalette-system",
- "libcrypto",
],
apex_available: ["com.android.compos"],
}
-
-cc_library_static {
- name: "libcomposd_native_cpp",
- srcs: ["composd_native.cpp"],
- shared_libs: ["libcrypto"],
- generated_headers: ["composd_native_header"],
- generated_sources: ["composd_native_code"],
- apex_available: ["com.android.compos"],
-}
-
-genrule {
- name: "composd_native_code",
- tools: ["cxxbridge"],
- cmd: "$(location cxxbridge) $(in) >> $(out)",
- srcs: ["lib.rs"],
- out: ["composd_native_cxx_generated.cc"],
-}
-
-genrule {
- name: "composd_native_header",
- tools: ["cxxbridge"],
- cmd: "$(location cxxbridge) $(in) --header >> $(out)",
- srcs: ["lib.rs"],
- out: ["lib.rs.h"],
-}
diff --git a/compos/composd/native/composd_native.cpp b/compos/composd/native/composd_native.cpp
deleted file mode 100644
index ebed816..0000000
--- a/compos/composd/native/composd_native.cpp
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * 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.
- */
-
-#include "composd_native.h"
-
-#include <openssl/evp.h>
-#include <openssl/mem.h>
-#include <openssl/sha.h>
-#include <openssl/x509.h>
-
-#include <algorithm>
-#include <iterator>
-
-using rust::Slice;
-using rust::String;
-
-namespace {
-KeyResult make_error(const char* message) {
- return KeyResult{{}, message};
-}
-} // namespace
-
-KeyResult extract_rsa_public_key(rust::Slice<const uint8_t> der_certificate) {
- auto data = der_certificate.data();
- bssl::UniquePtr<X509> x509(d2i_X509(nullptr, &data, der_certificate.size()));
- if (!x509) {
- return make_error("Failed to parse certificate");
- }
- if (data != der_certificate.data() + der_certificate.size()) {
- return make_error("Certificate has unexpected trailing data");
- }
-
- bssl::UniquePtr<EVP_PKEY> pkey(X509_get_pubkey(x509.get()));
- if (EVP_PKEY_base_id(pkey.get()) != EVP_PKEY_RSA) {
- return make_error("Subject key is not RSA");
- }
- RSA* rsa = EVP_PKEY_get0_RSA(pkey.get());
- if (!rsa) {
- return make_error("Failed to extract RSA key");
- }
-
- uint8_t* out = nullptr;
- int size = i2d_RSAPublicKey(rsa, &out);
- if (size < 0 || !out) {
- return make_error("Failed to convert to RSAPublicKey");
- }
- bssl::UniquePtr<uint8_t> buffer(out);
-
- KeyResult result;
- result.key.reserve(size);
- std::copy(out, out + size, std::back_inserter(result.key));
- return result;
-}
diff --git a/compos/composd/native/lib.rs b/compos/composd/native/lib.rs
index cbec7fd..042eb2a 100644
--- a/compos/composd/native/lib.rs
+++ b/compos/composd/native/lib.rs
@@ -15,28 +15,6 @@
//! Native helpers for composd.
pub use art::*;
-pub use crypto::*;
-
-#[cxx::bridge]
-mod crypto {
- /// Contains either a key or a reason why the key could not be extracted.
- struct KeyResult {
- /// The extracted key. If empty, the attempt to extract the key failed.
- key: Vec<u8>,
- /// A description of what went wrong if the attempt failed.
- error: String,
- }
-
- unsafe extern "C++" {
- include!("composd_native.h");
-
- // SAFETY: The C++ implementation manages its own memory, and does not retain or abuse
- // the der_certificate reference. cxx handles the mapping of the return value.
-
- /// Parse the supplied DER X.509 certificate and extract the subject's RsaPublicKey.
- fn extract_rsa_public_key(der_certificate: &[u8]) -> KeyResult;
- }
-}
mod art {
use anyhow::{anyhow, Result};
diff --git a/compos/composd/src/composd_main.rs b/compos/composd/src/composd_main.rs
index 6acf470..235c107 100644
--- a/compos/composd/src/composd_main.rs
+++ b/compos/composd/src/composd_main.rs
@@ -21,7 +21,6 @@
mod fd_server_helper;
mod instance_manager;
mod instance_starter;
-mod internal_service;
mod odrefresh_task;
mod service;
@@ -47,10 +46,6 @@
register_lazy_service("android.system.composd", composd_service.as_binder())
.context("Registering composd service")?;
- let internal_service = internal_service::new_binder();
- register_lazy_service("android.system.composd.internal", internal_service.as_binder())
- .context("Registering internal service")?;
-
info!("Registered services, joining threadpool");
ProcessState::join_thread_pool();
diff --git a/compos/composd/src/instance_starter.rs b/compos/composd/src/instance_starter.rs
index 4fed98a..a886584 100644
--- a/compos/composd/src/instance_starter.rs
+++ b/compos/composd/src/instance_starter.rs
@@ -138,13 +138,7 @@
let key_data = service.generateSigningKey().context("Generating signing key")?;
fs::write(&self.key_blob, &key_data.keyBlob).context("Writing key blob")?;
-
- let key_result = composd_native::extract_rsa_public_key(&key_data.certificate);
- let rsa_public_key = key_result.key;
- if rsa_public_key.is_empty() {
- bail!("Failed to extract public key from certificate: {}", key_result.error);
- }
- fs::write(&self.public_key, &rsa_public_key).context("Writing public key")?;
+ fs::write(&self.public_key, &key_data.publicKey).context("Writing public key")?;
// Unlike when starting an existing instance, we don't need to verify the key, since we
// just generated it and have it in memory.
diff --git a/compos/composd/src/internal_service.rs b/compos/composd/src/internal_service.rs
deleted file mode 100644
index 33935ef..0000000
--- a/compos/composd/src/internal_service.rs
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * 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.
- */
-
-//! Implementation of ICompilationInternal, called from odrefresh during compilation.
-
-use android_system_composd_internal::aidl::android::system::composd::internal::ICompilationInternal::{
- BnCompilationInternal, ICompilationInternal,
-};
-use android_system_composd::binder::{self, BinderFeatures, Interface, Strong};
-use binder_common::new_binder_service_specific_error;
-use compos_aidl_interface::aidl::com::android::compos::FdAnnotation::FdAnnotation;
-
-pub struct CompilationInternalService {}
-
-pub fn new_binder() -> Strong<dyn ICompilationInternal> {
- let service = CompilationInternalService {};
- BnCompilationInternal::new_binder(service, BinderFeatures::default())
-}
-
-impl Interface for CompilationInternalService {}
-
-impl ICompilationInternal for CompilationInternalService {
- fn compile(&self, _marshaled: &[u8], _fd_annotation: &FdAnnotation) -> binder::Result<i8> {
- Err(new_binder_service_specific_error(-1, "Not yet implemented"))
- }
-}
diff --git a/compos/composd/src/service.rs b/compos/composd/src/service.rs
index cb52037..6cdcd85 100644
--- a/compos/composd/src/service.rs
+++ b/compos/composd/src/service.rs
@@ -61,12 +61,6 @@
check_permissions()?;
to_binder_result(self.do_start_test_compile(callback))
}
-
- // TODO(b/214233409): Remove
- fn getBcc(&self) -> binder::Result<Vec<u8>> {
- check_permissions()?;
- to_binder_result(self.do_get_bcc())
- }
}
impl IsolatedCompilationService {
@@ -94,11 +88,6 @@
Ok(BnCompilationTask::new_binder(task, BinderFeatures::default()))
}
-
- fn do_get_bcc(&self) -> Result<Vec<u8>> {
- let comp_os = self.instance_manager.start_test_instance().context("Starting CompOS")?;
- comp_os.get_service().getBootCertificateChain().context("getBcc")
- }
}
fn check_permissions() -> binder::Result<()> {
diff --git a/compos/composd_cmd/composd_cmd.rs b/compos/composd_cmd/composd_cmd.rs
index 9b41104..546c4af 100644
--- a/compos/composd_cmd/composd_cmd.rs
+++ b/compos/composd_cmd/composd_cmd.rs
@@ -29,8 +29,6 @@
};
use anyhow::{bail, Context, Result};
use compos_common::timeouts::timeouts;
-use std::fs::File;
-use std::io::Write;
use std::sync::{Arc, Condvar, Mutex};
use std::time::Duration;
@@ -40,7 +38,7 @@
.index(1)
.takes_value(true)
.required(true)
- .possible_values(&["staged-apex-compile", "test-compile", "dice"]),
+ .possible_values(&["staged-apex-compile", "test-compile"]),
);
let args = app.get_matches();
let command = args.value_of("command").unwrap();
@@ -50,7 +48,6 @@
match command {
"staged-apex-compile" => run_staged_apex_compile()?,
"test-compile" => run_test_compile()?,
- "dice" => write_dice()?,
_ => panic!("Unexpected command {}", command),
}
@@ -115,16 +112,6 @@
run_async_compilation(|service, callback| service.startTestCompile(callback))
}
-fn write_dice() -> Result<()> {
- let service = wait_for_interface::<dyn IIsolatedCompilationService>("android.system.composd")
- .context("Failed to connect to composd service")?;
-
- let bcc = service.getBcc()?;
- let mut file =
- File::create("/data/misc/apexdata/com.android.compos/bcc").context("Creating bcc file")?;
- file.write_all(&bcc).context("Writing bcc")
-}
-
fn run_async_compilation<F>(start_compile_fn: F) -> Result<()>
where
F: FnOnce(
diff --git a/compos/native/Android.bp b/compos/native/Android.bp
new file mode 100644
index 0000000..84e312a
--- /dev/null
+++ b/compos/native/Android.bp
@@ -0,0 +1,46 @@
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+rust_library {
+ name: "libcompos_native_rust",
+ crate_name: "compos_native",
+ srcs: ["lib.rs"],
+ rustlibs: [
+ "libanyhow",
+ "libcxx",
+ "liblibc",
+ ],
+ static_libs: [
+ "libcompos_native_cpp",
+ ],
+ shared_libs: [
+ "libcrypto",
+ ],
+ apex_available: ["com.android.compos"],
+}
+
+cc_library_static {
+ name: "libcompos_native_cpp",
+ srcs: ["compos_native.cpp"],
+ shared_libs: ["libcrypto"],
+ generated_headers: ["compos_native_header"],
+ generated_sources: ["compos_native_code"],
+ apex_available: ["com.android.compos"],
+}
+
+genrule {
+ name: "compos_native_code",
+ tools: ["cxxbridge"],
+ cmd: "$(location cxxbridge) $(in) >> $(out)",
+ srcs: ["lib.rs"],
+ out: ["compos_native_cxx_generated.cc"],
+}
+
+genrule {
+ name: "compos_native_header",
+ tools: ["cxxbridge"],
+ cmd: "$(location cxxbridge) $(in) --header >> $(out)",
+ srcs: ["lib.rs"],
+ out: ["lib.rs.h"],
+}
diff --git a/compos/native/compos_native.cpp b/compos/native/compos_native.cpp
new file mode 100644
index 0000000..f529421
--- /dev/null
+++ b/compos/native/compos_native.cpp
@@ -0,0 +1,112 @@
+/*
+ * 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.
+ */
+
+#include "compos_native.h"
+
+#include <openssl/bn.h>
+#include <openssl/mem.h>
+#include <openssl/nid.h>
+#include <openssl/rsa.h>
+#include <openssl/sha.h>
+
+#include <algorithm>
+#include <iterator>
+#include <vector>
+
+namespace {
+KeyResult make_key_error(const char* message) {
+ return KeyResult{{}, {}, message};
+}
+
+SignResult make_sign_error(const char* message) {
+ return SignResult{{}, message};
+}
+} // namespace
+
+constexpr int KEY_BITS = 2048;
+
+KeyResult generate_key_pair() {
+ bssl::UniquePtr<RSA> key_pair(RSA_new());
+
+ // This function specifies that the public exponent is always 65537, which is good because
+ // that's what odsign is expecting.
+ if (!RSA_generate_key_fips(key_pair.get(), KEY_BITS, /*callback=*/nullptr)) {
+ return make_key_error("Failed to generate key pair");
+ }
+
+ KeyResult result;
+
+ uint8_t* out;
+ int size;
+ bssl::UniquePtr<uint8_t> out_owner;
+
+ // Extract public key as DER.
+ out = nullptr;
+ size = i2d_RSAPublicKey(key_pair.get(), &out);
+ if (size < 0 || !out) {
+ return make_key_error("Failed to get RSAPublicKey");
+ }
+ out_owner.reset(out);
+
+ result.public_key.reserve(size);
+ std::copy(out, out + size, std::back_inserter(result.public_key));
+ out_owner.reset();
+
+ // And ditto for the private key (which actually includes the public bits).
+ out = nullptr;
+ size = i2d_RSAPrivateKey(key_pair.get(), &out);
+ if (size < 0 || !out) {
+ return make_key_error("Failed to get RSAPrivateKey");
+ }
+ out_owner.reset(out);
+
+ result.private_key.reserve(size);
+ std::copy(out, out + size, std::back_inserter(result.private_key));
+ out_owner.reset();
+
+ return result;
+}
+
+SignResult sign(rust::Slice<const uint8_t> private_key, rust::Slice<const uint8_t> data) {
+ uint8_t digest[SHA256_DIGEST_LENGTH];
+ SHA256(data.data(), data.size(), digest);
+
+ const uint8_t* key_in = private_key.data();
+ bssl::UniquePtr<RSA> key(d2i_RSAPrivateKey(nullptr, &key_in, private_key.size()));
+ if (!key) {
+ return make_sign_error("Failed to load RSAPrivateKey");
+ }
+
+ // rust::Vec doesn't support resize, so we need our own buffer.
+ // The signature is always less than the modulus (public key), so
+ // will fit in KEY_BITS.
+
+ uint8_t signature[KEY_BITS / 8];
+ if (sizeof(signature) < RSA_size(key.get())) {
+ return make_sign_error("Signing key is too large");
+ }
+ unsigned signature_len = 0;
+
+ if (!RSA_sign(NID_sha256, digest, sizeof(digest), signature, &signature_len, key.get())) {
+ return make_sign_error("Failed to sign");
+ }
+
+ SignResult result;
+ result.signature.reserve(signature_len);
+ std::copy(signature, signature + signature_len, std::back_inserter(result.signature));
+
+ return result;
+}
diff --git a/compos/composd/native/composd_native.h b/compos/native/compos_native.h
similarity index 83%
rename from compos/composd/native/composd_native.h
rename to compos/native/compos_native.h
index 112ef73..6497c9b 100644
--- a/compos/composd/native/composd_native.h
+++ b/compos/native/compos_native.h
@@ -18,4 +18,6 @@
#include "lib.rs.h"
-KeyResult extract_rsa_public_key(rust::Slice<const uint8_t> der_certificate);
+KeyResult generate_key_pair();
+
+SignResult sign(rust::Slice<const uint8_t> private_key, rust::Slice<const uint8_t> data);
diff --git a/compos/native/lib.rs b/compos/native/lib.rs
new file mode 100644
index 0000000..a5f0cc5
--- /dev/null
+++ b/compos/native/lib.rs
@@ -0,0 +1,59 @@
+// 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.
+
+//! Native helpers for CompOS.
+
+pub use crypto::*;
+
+#[cxx::bridge]
+mod crypto {
+ /// Contains either a key pair or a reason why the key could not be extracted.
+ struct KeyResult {
+ /// The DER-encoded RSAPublicKey
+ /// (https://datatracker.ietf.org/doc/html/rfc8017#appendix-A.1.1).
+ public_key: Vec<u8>,
+ /// The DER-encoded RSAPrivateKey
+ /// (https://datatracker.ietf.org/doc/html/rfc8017#appendix-A.1.2).
+ /// Note that this is unencrypted.
+ private_key: Vec<u8>,
+ /// A description of what went wrong if the attempt failed.
+ error: String,
+ }
+
+ /// Contains either a signature or a reason why signing failed.
+ struct SignResult {
+ /// The RSAES-PKCS1-v1_5 signature
+ /// (https://datatracker.ietf.org/doc/html/rfc3447#section-7.2).
+ signature: Vec<u8>,
+ /// A description of what went wrong if the attempt failed.
+ error: String,
+ }
+
+ unsafe extern "C++" {
+ include!("compos_native.h");
+
+ // SAFETY: The C++ implementation manages its own memory. cxx handles the mapping of the
+ // return value.
+
+ /// Generate a public/private key pair.
+ fn generate_key_pair() -> KeyResult;
+
+ // SAFETY: The C++ implementation manages its own memory, and does not retain or abuse
+ // the references passed to it. cxx handles the mapping of the return value.
+
+ /// Sign data using a SHA256 digest and RSAES-PKCS1-v1_5 using the given
+ /// DER-encoded RSAPrivateKey.
+ fn sign(private_key: &[u8], data: &[u8]) -> SignResult;
+ }
+}
diff --git a/compos/src/artifact_signer.rs b/compos/src/artifact_signer.rs
index a4b47d6..e1b0efa 100644
--- a/compos/src/artifact_signer.rs
+++ b/compos/src/artifact_signer.rs
@@ -19,8 +19,8 @@
#![allow(dead_code)] // Will be used soon
-use crate::compos_key_service::Signer;
use crate::fsverity;
+use crate::signing_key::Signer;
use anyhow::{anyhow, Context, Result};
use odsign_proto::odsign_info::OdsignInfo;
use protobuf::Message;
diff --git a/compos/src/blob_encryption.rs b/compos/src/blob_encryption.rs
new file mode 100644
index 0000000..0db09ba
--- /dev/null
+++ b/compos/src/blob_encryption.rs
@@ -0,0 +1,118 @@
+/*
+ * Copyright 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.
+ */
+
+//! Allows for data to be encrypted and authenticated (AEAD) with a key derived from some secret.
+//! The encrypted blob can be passed to the untrusted host without revealing the encrypted data
+//! but with the key the data can be retrieved as long as the blob has not been tampered with.
+
+use anyhow::{bail, Context, Result};
+use ring::{
+ aead::{Aad, LessSafeKey, Nonce, AES_256_GCM, NONCE_LEN},
+ hkdf::{Salt, HKDF_SHA256},
+ rand::{SecureRandom, SystemRandom},
+};
+
+// Non-secret input to the AEAD key derivation
+const KDF_INFO: &[u8] = b"CompOS blob sealing key";
+
+pub fn derive_aead_key(input_keying_material: &[u8]) -> Result<LessSafeKey> {
+ // Derive key using HKDF - see https://datatracker.ietf.org/doc/html/rfc5869#section-2
+ let salt = [];
+ let prk = Salt::new(HKDF_SHA256, &salt).extract(input_keying_material);
+ let okm = prk.expand(&[KDF_INFO], &AES_256_GCM).context("HKDF failed")?;
+ // LessSafeKey is only less safe in that it has less inherent protection against nonce
+ // reuse; we are safe because we use a new random nonce for each sealing operation.
+ // (See https://github.com/briansmith/ring/issues/899.)
+ Ok(LessSafeKey::new(okm.into()))
+}
+
+pub fn encrypt_bytes(key: LessSafeKey, bytes: &[u8]) -> Result<Vec<u8>> {
+ let mut output = Vec::with_capacity(bytes.len() + NONCE_LEN + key.algorithm().tag_len());
+
+ // Generate a unique nonce, since we may use the same key more than once, and put it at the
+ // start of the output blob.
+ let mut nonce_bytes = [0u8; NONCE_LEN];
+ SystemRandom::new().fill(&mut nonce_bytes).context("Failed to generate random nonce")?;
+ output.extend_from_slice(&nonce_bytes);
+
+ // Copy input to output then encrypt & seal it in place.
+ output.extend_from_slice(bytes);
+ let nonce = Nonce::assume_unique_for_key(nonce_bytes);
+ let tag = key
+ .seal_in_place_separate_tag(nonce, Aad::empty(), &mut output[NONCE_LEN..])
+ .context("Failed to seal blob")?;
+ output.extend_from_slice(tag.as_ref());
+
+ Ok(output)
+}
+
+pub fn decrypt_bytes(key: LessSafeKey, bytes: &[u8]) -> Result<Vec<u8>> {
+ if bytes.len() < NONCE_LEN + key.algorithm().tag_len() {
+ bail!("Encrypted blob is too small");
+ }
+
+ // We expect the nonce at the start followed by the sealed data (encrypted data +
+ // authentication tag).
+ let nonce = Nonce::try_assume_unique_for_key(&bytes[..NONCE_LEN]).unwrap();
+ let mut output = bytes[NONCE_LEN..].to_vec();
+
+ // Verify & decrypt the data in place
+ let unsealed_size =
+ key.open_in_place(nonce, Aad::empty(), &mut output).context("Failed to unseal blob")?.len();
+
+ // Remove the tag after the plaintext
+ output.truncate(unsealed_size);
+
+ Ok(output)
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_round_trip_data() -> Result<()> {
+ let input_keying_material = b"Key is derived from this";
+ let original_bytes = b"This is the secret data";
+
+ let key = derive_aead_key(input_keying_material)?;
+ let blob = encrypt_bytes(key, original_bytes)?;
+
+ let key = derive_aead_key(input_keying_material)?;
+ let decoded_bytes = decrypt_bytes(key, &blob)?;
+
+ assert_eq!(decoded_bytes, original_bytes);
+ Ok(())
+ }
+
+ #[test]
+ fn test_modified_data_detected() -> Result<()> {
+ let input_keying_material = b"Key is derived from this";
+ let original_bytes = b"This is the secret data";
+
+ let key = derive_aead_key(input_keying_material)?;
+ let mut blob = encrypt_bytes(key, original_bytes)?;
+
+ // Flip a bit.
+ blob[0] ^= 1;
+
+ let key = derive_aead_key(input_keying_material)?;
+ let decoded_bytes = decrypt_bytes(key, &blob);
+
+ assert!(decoded_bytes.is_err());
+ Ok(())
+ }
+}
diff --git a/compos/src/compilation.rs b/compos/src/compilation.rs
index 6db31b6..850a0a8 100644
--- a/compos/src/compilation.rs
+++ b/compos/src/compilation.rs
@@ -26,7 +26,7 @@
use std::process::Command;
use crate::artifact_signer::ArtifactSigner;
-use crate::compos_key_service::Signer;
+use crate::signing_key::Signer;
use authfs_aidl_interface::aidl::com::android::virt::fs::{
AuthFsConfig::{
AuthFsConfig, InputDirFdAnnotation::InputDirFdAnnotation,
diff --git a/compos/src/compos_key_service.rs b/compos/src/compos_key_service.rs
deleted file mode 100644
index 086a162..0000000
--- a/compos/src/compos_key_service.rs
+++ /dev/null
@@ -1,148 +0,0 @@
-// 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.
-
-//! Provides a binder service for key generation & verification for CompOs. We assume we have
-//! access to Keystore in the VM, but not persistent storage; instead the host stores the key
-//! on our behalf via this service.
-
-use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
- Algorithm::Algorithm, Digest::Digest, KeyParameter::KeyParameter,
- KeyParameterValue::KeyParameterValue, KeyPurpose::KeyPurpose, PaddingMode::PaddingMode,
- SecurityLevel::SecurityLevel, Tag::Tag,
-};
-use android_system_keystore2::aidl::android::system::keystore2::{
- Domain::Domain, IKeystoreSecurityLevel::IKeystoreSecurityLevel,
- IKeystoreService::IKeystoreService, KeyDescriptor::KeyDescriptor,
-};
-use android_system_keystore2::binder::{wait_for_interface, Strong};
-use anyhow::{anyhow, Context, Result};
-use compos_aidl_interface::aidl::com::android::compos::CompOsKeyData::CompOsKeyData;
-use ring::rand::{SecureRandom, SystemRandom};
-use ring::signature;
-use scopeguard::ScopeGuard;
-
-/// Keystore2 namespace ID, used for access control to keys. In a VM we can use the generic ID
-/// allocated for payloads. See microdroid's keystore2_key_contexts.
-const KEYSTORE_NAMESPACE: i64 = 140;
-
-const KEYSTORE_SERVICE_NAME: &str = "android.system.keystore2.IKeystoreService/default";
-const PURPOSE_SIGN: KeyParameter =
- KeyParameter { tag: Tag::PURPOSE, value: KeyParameterValue::KeyPurpose(KeyPurpose::SIGN) };
-const ALGORITHM: KeyParameter =
- KeyParameter { tag: Tag::ALGORITHM, value: KeyParameterValue::Algorithm(Algorithm::RSA) };
-const PADDING: KeyParameter = KeyParameter {
- tag: Tag::PADDING,
- value: KeyParameterValue::PaddingMode(PaddingMode::RSA_PKCS1_1_5_SIGN),
-};
-const DIGEST: KeyParameter =
- KeyParameter { tag: Tag::DIGEST, value: KeyParameterValue::Digest(Digest::SHA_2_256) };
-const KEY_SIZE: KeyParameter =
- KeyParameter { tag: Tag::KEY_SIZE, value: KeyParameterValue::Integer(2048) };
-const EXPONENT: KeyParameter =
- KeyParameter { tag: Tag::RSA_PUBLIC_EXPONENT, value: KeyParameterValue::LongInteger(65537) };
-const NO_AUTH_REQUIRED: KeyParameter =
- KeyParameter { tag: Tag::NO_AUTH_REQUIRED, value: KeyParameterValue::BoolValue(true) };
-
-const BLOB_KEY_DESCRIPTOR: KeyDescriptor =
- KeyDescriptor { domain: Domain::BLOB, nspace: KEYSTORE_NAMESPACE, alias: None, blob: None };
-
-/// An internal service for CompOS key management.
-#[derive(Clone)]
-pub struct CompOsKeyService {
- random: SystemRandom,
- security_level: Strong<dyn IKeystoreSecurityLevel>,
-}
-
-impl CompOsKeyService {
- pub fn new() -> Result<Self> {
- let keystore_service = wait_for_interface::<dyn IKeystoreService>(KEYSTORE_SERVICE_NAME)
- .context("No Keystore service")?;
-
- Ok(CompOsKeyService {
- random: SystemRandom::new(),
- security_level: keystore_service
- .getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT)
- .context("Getting SecurityLevel failed")?,
- })
- }
-
- pub fn generate(&self) -> Result<CompOsKeyData> {
- let key_descriptor = BLOB_KEY_DESCRIPTOR;
- let key_parameters =
- [PURPOSE_SIGN, ALGORITHM, PADDING, DIGEST, KEY_SIZE, EXPONENT, NO_AUTH_REQUIRED];
- let attestation_key = None;
- let flags = 0;
- let entropy = [];
-
- let key_metadata = self
- .security_level
- .generateKey(&key_descriptor, attestation_key, &key_parameters, flags, &entropy)
- .context("Generating key failed")?;
-
- if let (Some(certificate), Some(blob)) = (key_metadata.certificate, key_metadata.key.blob) {
- Ok(CompOsKeyData { certificate, keyBlob: blob })
- } else {
- Err(anyhow!("Missing cert or blob"))
- }
- }
-
- pub fn verify(&self, key_blob: &[u8], public_key: &[u8]) -> Result<()> {
- let mut data = [0u8; 32];
- self.random.fill(&mut data).context("No random data")?;
-
- let signature = self.new_signer(key_blob).sign(&data)?;
-
- let public_key =
- signature::UnparsedPublicKey::new(&signature::RSA_PKCS1_2048_8192_SHA256, public_key);
- public_key.verify(&data, &signature).context("Signature verification failed")?;
-
- Ok(())
- }
-
- pub fn new_signer(&self, key_blob: &[u8]) -> Signer {
- Signer { key_blob: key_blob.to_vec(), security_level: self.security_level.clone() }
- }
-}
-
-pub struct Signer {
- key_blob: Vec<u8>,
- security_level: Strong<dyn IKeystoreSecurityLevel>,
-}
-
-impl Signer {
- pub fn sign(self, data: &[u8]) -> Result<Vec<u8>> {
- let key_descriptor = KeyDescriptor { blob: Some(self.key_blob), ..BLOB_KEY_DESCRIPTOR };
- let operation_parameters = [PURPOSE_SIGN, ALGORITHM, PADDING, DIGEST];
- let forced = false;
-
- let response = self
- .security_level
- .createOperation(&key_descriptor, &operation_parameters, forced)
- .context("Creating key failed")?;
- let operation = scopeguard::guard(
- response.iOperation.ok_or_else(|| anyhow!("No operation created"))?,
- |op| op.abort().unwrap_or_default(),
- );
-
- if response.operationChallenge.is_some() {
- return Err(anyhow!("Key requires user authorization"));
- }
-
- let signature = operation.finish(Some(data), None).context("Signing failed")?;
- // Operation has finished, we're no longer responsible for aborting it
- ScopeGuard::into_inner(operation);
-
- signature.ok_or_else(|| anyhow!("No signature returned"))
- }
-}
diff --git a/compos/src/compsvc.rs b/compos/src/compsvc.rs
index 356cc7e..422f271 100644
--- a/compos/src/compsvc.rs
+++ b/compos/src/compsvc.rs
@@ -27,12 +27,10 @@
use std::sync::RwLock;
use crate::compilation::{odrefresh, OdrefreshContext};
-use crate::compos_key_service::{CompOsKeyService, Signer};
-use crate::dice::Dice;
+use crate::signing_key::{Signer, SigningKey};
use authfs_aidl_interface::aidl::com::android::virt::fs::IAuthFsService::IAuthFsService;
use compos_aidl_interface::aidl::com::android::compos::{
CompOsKeyData::CompOsKeyData,
- FdAnnotation::FdAnnotation,
ICompOsService::{BnCompOsService, ICompOsService},
};
use compos_aidl_interface::binder::{
@@ -46,7 +44,7 @@
pub fn new_binder() -> Result<Strong<dyn ICompOsService>> {
let service = CompOsService {
odrefresh_path: PathBuf::from(ODREFRESH_PATH),
- key_service: CompOsKeyService::new()?,
+ signing_key: SigningKey::new()?,
key_blob: RwLock::new(Vec::new()),
};
Ok(BnCompOsService::new_binder(service, BinderFeatures::default()))
@@ -54,7 +52,7 @@
struct CompOsService {
odrefresh_path: PathBuf,
- key_service: CompOsKeyService,
+ signing_key: SigningKey,
key_blob: RwLock<Vec<u8>>,
}
@@ -64,14 +62,9 @@
if key.is_empty() {
Err(new_binder_exception(ExceptionCode::ILLEGAL_STATE, "Key is not initialized"))
} else {
- Ok(self.key_service.new_signer(key))
+ to_binder_result(self.signing_key.new_signer(key))
}
}
-
- fn get_boot_certificate_chain(&self) -> Result<Vec<u8>> {
- let dice = Dice::new()?;
- dice.get_boot_certificate_chain()
- }
}
impl Interface for CompOsService {}
@@ -113,26 +106,18 @@
Ok(exit_code as i8)
}
- fn compile(&self, _marshaled: &[u8], _fd_annotation: &FdAnnotation) -> BinderResult<i8> {
- Err(new_binder_exception(ExceptionCode::UNSUPPORTED_OPERATION, "Not yet implemented"))
- }
-
fn generateSigningKey(&self) -> BinderResult<CompOsKeyData> {
- to_binder_result(self.key_service.generate())
+ to_binder_result(self.signing_key.generate())
}
fn verifySigningKey(&self, key_blob: &[u8], public_key: &[u8]) -> BinderResult<bool> {
- Ok(if let Err(e) = self.key_service.verify(key_blob, public_key) {
+ Ok(if let Err(e) = self.signing_key.verify(key_blob, public_key) {
warn!("Signing key verification failed: {:?}", e);
false
} else {
true
})
}
-
- fn getBootCertificateChain(&self) -> BinderResult<Vec<u8>> {
- to_binder_result(self.get_boot_certificate_chain())
- }
}
fn get_authfs_service() -> BinderResult<Strong<dyn IAuthFsService>> {
diff --git a/compos/src/compsvc_main.rs b/compos/src/compsvc_main.rs
index b4e3128..c2923f0 100644
--- a/compos/src/compsvc_main.rs
+++ b/compos/src/compsvc_main.rs
@@ -17,11 +17,12 @@
//! A tool to start a standalone compsvc server that serves over RPC binder.
mod artifact_signer;
+mod blob_encryption;
mod compilation;
-mod compos_key_service;
mod compsvc;
mod dice;
mod fsverity;
+mod signing_key;
use android_system_virtualmachineservice::{
aidl::android::system::virtualmachineservice::IVirtualMachineService::{
diff --git a/compos/src/dice.rs b/compos/src/dice.rs
index 22a7ee2..9f66b5e 100644
--- a/compos/src/dice.rs
+++ b/compos/src/dice.rs
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-//! Handles the use of DICE as the source of our unique signing key via diced / IDiceNode.
+//! Handles the use of DICE (via diced / IDiceNode) for accessing our VM's unique secret.
use android_security_dice::aidl::android::security::dice::IDiceNode::IDiceNode;
use android_security_dice::binder::{wait_for_interface, Strong};
@@ -31,12 +31,9 @@
Ok(Self { node: dice_service })
}
- pub fn get_boot_certificate_chain(&self) -> Result<Vec<u8>> {
- let input_values = []; // Get our BCC, not a child's
- let bcc = self
- .node
- .getAttestationChain(&input_values)
- .context("Getting attestation chain failed")?;
- Ok(bcc.data)
+ pub fn get_sealing_cdi(&self) -> Result<Vec<u8>> {
+ let input_values = [];
+ let bcc_handover = self.node.derive(&input_values).context("Failed to retrieve CDI")?;
+ Ok(bcc_handover.cdiSeal.to_vec())
}
}
diff --git a/compos/src/signing_key.rs b/compos/src/signing_key.rs
new file mode 100644
index 0000000..175a11b
--- /dev/null
+++ b/compos/src/signing_key.rs
@@ -0,0 +1,97 @@
+/*
+ * Copyright 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.
+ */
+
+//! RSA key pair generation, persistence (with the private key encrypted), verification and
+//! signing.
+
+#![allow(dead_code, unused_variables)]
+
+use crate::blob_encryption;
+use crate::dice::Dice;
+use anyhow::{bail, Context, Result};
+use compos_aidl_interface::aidl::com::android::compos::CompOsKeyData::CompOsKeyData;
+use ring::{
+ rand::{SecureRandom, SystemRandom},
+ signature,
+};
+
+pub struct SigningKey {
+ _unused: (), // Prevent construction other than by new()
+}
+
+impl SigningKey {
+ pub fn new() -> Result<Self> {
+ Ok(Self { _unused: () })
+ }
+
+ pub fn generate(&self) -> Result<CompOsKeyData> {
+ let key_result = compos_native::generate_key_pair();
+ if key_result.public_key.is_empty() || key_result.private_key.is_empty() {
+ bail!("Failed to generate key pair: {}", key_result.error);
+ }
+
+ let encrypted = encrypt_private_key(&Dice::new()?, &key_result.private_key)?;
+ Ok(CompOsKeyData { publicKey: key_result.public_key, keyBlob: encrypted })
+ }
+
+ pub fn verify(&self, key_blob: &[u8], public_key: &[u8]) -> Result<()> {
+ // We verify the private key by verifying the AEAD authentication tag in the signer.
+ // To verify the public key matches, we sign a block of random data with the private key,
+ // and then check that the signature matches the purported key.
+ let mut data = [0u8; 32]; // Size is fairly arbitrary.
+ SystemRandom::new().fill(&mut data).context("No random data")?;
+
+ let signature = self.new_signer(key_blob)?.sign(&data)?;
+
+ let public_key =
+ signature::UnparsedPublicKey::new(&signature::RSA_PKCS1_2048_8192_SHA256, public_key);
+ public_key.verify(&data, &signature).context("Signature verification failed")?;
+
+ Ok(())
+ }
+
+ pub fn new_signer(&self, key_blob: &[u8]) -> Result<Signer> {
+ Ok(Signer { key_blob: key_blob.to_owned(), dice: Dice::new()? })
+ }
+}
+
+pub struct Signer {
+ key_blob: Vec<u8>,
+ dice: Dice,
+}
+
+impl Signer {
+ pub fn sign(self, data: &[u8]) -> Result<Vec<u8>> {
+ let private_key = decrypt_private_key(&self.dice, &self.key_blob)?;
+ let sign_result = compos_native::sign(&private_key, data);
+ if sign_result.signature.is_empty() {
+ bail!("Failed to sign: {}", sign_result.error);
+ }
+ Ok(sign_result.signature)
+ }
+}
+
+fn encrypt_private_key(dice: &Dice, private_key: &[u8]) -> Result<Vec<u8>> {
+ let cdi = dice.get_sealing_cdi()?;
+ let aead_key = blob_encryption::derive_aead_key(&cdi)?;
+ blob_encryption::encrypt_bytes(aead_key, private_key)
+}
+
+fn decrypt_private_key(dice: &Dice, blob: &[u8]) -> Result<Vec<u8>> {
+ let cdi = dice.get_sealing_cdi()?;
+ let aead_key = blob_encryption::derive_aead_key(&cdi)?;
+ blob_encryption::decrypt_bytes(aead_key, blob)
+}
diff --git a/compos/tests/java/android/compos/test/ComposTestCase.java b/compos/tests/java/android/compos/test/ComposTestCase.java
index 1e439af..ff3baa2 100644
--- a/compos/tests/java/android/compos/test/ComposTestCase.java
+++ b/compos/tests/java/android/compos/test/ComposTestCase.java
@@ -181,8 +181,8 @@
throws Exception {
// Sort by filename (second column) to make comparison easier. Filter out compos.info and
// compos.info.signature since it's only generated by CompOS.
- // TODO(b/210473615): Remove irrelevant APEXes (i.e. those aren't contributing to the
- // classpaths, thus not in the VM) from cache-info.xml.
+ // TODO(b/211458160): Remove cache-info.xml once we can plumb timestamp and isFactory of
+ // APEXes to the VM.
return runner.run("cd " + path + "; find -type f -exec sha256sum {} \\;"
+ "| grep -v cache-info.xml | grep -v compos.info"
+ "| sort -k2");
diff --git a/microdroid/Android.bp b/microdroid/Android.bp
index 7f0b806..e078108 100644
--- a/microdroid/Android.bp
+++ b/microdroid/Android.bp
@@ -72,11 +72,11 @@
"apexd",
"debuggerd",
- "diced",
+ "diced.microdroid",
"keystore2_microdroid",
"linker",
"linkerconfig",
- "servicemanager",
+ "servicemanager.microdroid",
"tombstoned",
"cgroups.json",
"public.libraries.android.txt",
@@ -178,6 +178,7 @@
partition_name: "vendor",
use_avb: true,
deps: [
+ "android.hardware.security.dice-service.microdroid",
"android.hardware.security.keymint-service.microdroid",
"microdroid_fstab",
"microdroid_precompiled_sepolicy.plat_sepolicy_and_mapping.sha256",
diff --git a/microdroid/dice/Android.bp b/microdroid/dice/Android.bp
new file mode 100644
index 0000000..8026581
--- /dev/null
+++ b/microdroid/dice/Android.bp
@@ -0,0 +1,29 @@
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+rust_binary {
+ name: "android.hardware.security.dice-service.microdroid",
+ srcs: ["service.rs"],
+ relative_install_path: "hw",
+ vendor: true,
+ prefer_rlib: true,
+ rustlibs: [
+ "android.hardware.security.dice-V1-rust",
+ "libandroid_logger",
+ "libanyhow",
+ "libbinder_rs",
+ "libbyteorder",
+ "libdiced_open_dice_cbor",
+ "libdiced_sample_inputs",
+ "libdiced_vendor",
+ "liblibc",
+ "liblog_rust",
+ "libserde",
+ ],
+ init_rc: ["android.hardware.security.dice-service.microdroid.rc"],
+ vintf_fragments: [
+ "android.hardware.security.dice-service.microdroid.xml",
+ ],
+ bootstrap: true,
+}
diff --git a/microdroid/dice/android.hardware.security.dice-service.microdroid.rc b/microdroid/dice/android.hardware.security.dice-service.microdroid.rc
new file mode 100644
index 0000000..162081e
--- /dev/null
+++ b/microdroid/dice/android.hardware.security.dice-service.microdroid.rc
@@ -0,0 +1,3 @@
+service vendor.dice-microdroid /vendor/bin/hw/android.hardware.security.dice-service.microdroid
+ class early_hal
+ user nobody
diff --git a/microdroid/dice/android.hardware.security.dice-service.microdroid.xml b/microdroid/dice/android.hardware.security.dice-service.microdroid.xml
new file mode 100644
index 0000000..cf6c482
--- /dev/null
+++ b/microdroid/dice/android.hardware.security.dice-service.microdroid.xml
@@ -0,0 +1,6 @@
+<manifest version="1.0" type="device">
+ <hal format="aidl">
+ <name>android.hardware.security.dice</name>
+ <fqname>IDiceDevice/default</fqname>
+ </hal>
+</manifest>
diff --git a/microdroid/dice/service.rs b/microdroid/dice/service.rs
new file mode 100644
index 0000000..3401654
--- /dev/null
+++ b/microdroid/dice/service.rs
@@ -0,0 +1,117 @@
+// Copyright 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.
+
+//! Main entry point for the microdroid IDiceDevice HAL implementation.
+
+use anyhow::Result;
+use diced::{
+ dice,
+ hal_node::{DiceArtifacts, DiceDevice, ResidentHal, UpdatableDiceArtifacts},
+};
+use serde::{Deserialize, Serialize};
+use std::panic;
+use std::sync::Arc;
+
+const DICE_HAL_SERVICE_NAME: &str = "android.hardware.security.dice.IDiceDevice/default";
+
+/// Artifacts that are kept in the process address space after the artifacts
+/// from the driver have been consumed.
+#[derive(Clone, Serialize, Deserialize)]
+struct RawArtifacts {
+ cdi_attest: [u8; dice::CDI_SIZE],
+ cdi_seal: [u8; dice::CDI_SIZE],
+ bcc: Vec<u8>,
+}
+
+impl DiceArtifacts for RawArtifacts {
+ fn cdi_attest(&self) -> &[u8; dice::CDI_SIZE] {
+ &self.cdi_attest
+ }
+ fn cdi_seal(&self) -> &[u8; dice::CDI_SIZE] {
+ &self.cdi_seal
+ }
+ fn bcc(&self) -> Vec<u8> {
+ // The BCC only contains public information so it's fine to copy.
+ self.bcc.clone()
+ }
+}
+
+#[derive(Clone, Serialize, Deserialize)]
+enum DriverArtifactManager {
+ Updated(RawArtifacts),
+}
+
+impl DriverArtifactManager {
+ fn new() -> Self {
+ // TODO(214231981): replace with true values passed by bootloader
+ let (cdi_attest, cdi_seal, bcc) = diced_sample_inputs::make_sample_bcc_and_cdis()
+ .expect("Failed to create sample dice artifacts.");
+ Self::Updated(RawArtifacts {
+ cdi_attest: cdi_attest[..].try_into().unwrap(),
+ cdi_seal: cdi_seal[..].try_into().unwrap(),
+ bcc,
+ })
+ }
+}
+
+impl UpdatableDiceArtifacts for DriverArtifactManager {
+ fn with_artifacts<F, T>(&self, f: F) -> Result<T>
+ where
+ F: FnOnce(&dyn DiceArtifacts) -> Result<T>,
+ {
+ match self {
+ Self::Updated(raw_artifacts) => f(raw_artifacts),
+ }
+ }
+ fn update(self, new_artifacts: &impl DiceArtifacts) -> Result<Self> {
+ Ok(Self::Updated(RawArtifacts {
+ cdi_attest: *new_artifacts.cdi_attest(),
+ cdi_seal: *new_artifacts.cdi_seal(),
+ bcc: new_artifacts.bcc(),
+ }))
+ }
+}
+
+fn main() {
+ android_logger::init_once(
+ android_logger::Config::default()
+ .with_tag("android.hardware.security.dice")
+ .with_min_level(log::Level::Debug),
+ );
+ // Redirect panic messages to logcat.
+ panic::set_hook(Box::new(|panic_info| {
+ log::error!("{}", panic_info);
+ }));
+
+ // Saying hi.
+ log::info!("android.hardware.security.dice is starting.");
+
+ let hal_impl = Arc::new(
+ unsafe {
+ // Safety: ResidentHal cannot be used in multi threaded processes.
+ // This service does not start a thread pool. The main thread is the only thread
+ // joining the thread pool, thereby keeping the process single threaded.
+ ResidentHal::new(DriverArtifactManager::new())
+ }
+ .expect("Failed to create ResidentHal implementation."),
+ );
+
+ let hal = DiceDevice::new_as_binder(hal_impl).expect("Failed to construct hal service.");
+
+ binder::add_service(DICE_HAL_SERVICE_NAME, hal.as_binder())
+ .expect("Failed to register IDiceDevice Service");
+
+ log::info!("Joining thread pool now.");
+ binder::ProcessState::join_thread_pool();
+}
diff --git a/microdroid/init.rc b/microdroid/init.rc
index 31c06d3..e76260e 100644
--- a/microdroid/init.rc
+++ b/microdroid/init.rc
@@ -17,25 +17,6 @@
start ueventd
- mkdir /mnt/apk 0755 system system
- mkdir /mnt/extra-apk 0755 root root
- # Microdroid_manager starts apkdmverity/zipfuse/apexd
- start microdroid_manager
-
- # restorecon so microdroid_manager can create subdirectories
- restorecon /mnt/extra-apk
-
- # Wait for apexd to finish activating APEXes before starting more processes.
- wait_for_prop apexd.status activated
- perform_apex_config
-
- # Notify to microdroid_manager that perform_apex_config is done.
- # Microdroid_manager shouldn't execute payload before this, because app
- # payloads are not designed to run with bootstrap bionic
- setprop apex_config.done true
-
- setprop ro.debuggable ${ro.boot.microdroid.debuggable:-0}
-
on init
# Mount binderfs
mkdir /dev/binderfs
@@ -78,18 +59,36 @@
chmod 0664 /dev/cpuset/background/tasks
chmod 0664 /dev/cpuset/system-background/tasks
-on init && property:ro.boot.logd.enabled=1
- # Start logd before any other services run to ensure we capture all of their logs.
- start logd
-
-on init
start servicemanager
+ start vendor.dice-microdroid
+ start diced
+
+ mkdir /mnt/apk 0755 system system
+ mkdir /mnt/extra-apk 0755 root root
+ # Microdroid_manager starts apkdmverity/zipfuse/apexd
+ start microdroid_manager
+
+ # restorecon so microdroid_manager can create subdirectories
+ restorecon /mnt/extra-apk
+
+ # Wait for apexd to finish activating APEXes before starting more processes.
+ wait_for_prop apexd.status activated
+ perform_apex_config
+
+ # Notify to microdroid_manager that perform_apex_config is done.
+ # Microdroid_manager shouldn't execute payload before this, because app
+ # payloads are not designed to run with bootstrap bionic
+ setprop apex_config.done true
+
+ setprop ro.debuggable ${ro.boot.microdroid.debuggable:-0}
+
# TODO(b/185767624): remove hidl after full keymint support
start hwservicemanager
- # TODO(b/214231981): start diced (and servicemanager) earlier than microdroid_manager.
- start diced
+on init && property:ro.boot.logd.enabled=1
+ # Start logd before any other services run to ensure we capture all of their logs.
+ start logd
on init && property:ro.boot.adb.enabled=1
start adbd
diff --git a/microdroid_manager/Android.bp b/microdroid_manager/Android.bp
index 23a61d9..f888b80 100644
--- a/microdroid_manager/Android.bp
+++ b/microdroid_manager/Android.bp
@@ -9,6 +9,7 @@
edition: "2018",
prefer_rlib: true,
rustlibs: [
+ "android.security.dice-rust",
"android.system.virtualizationservice-rust",
"android.system.virtualmachineservice-rust",
"libanyhow",
diff --git a/microdroid_manager/src/instance.rs b/microdroid_manager/src/instance.rs
index cb59e3b..1068792 100644
--- a/microdroid_manager/src/instance.rs
+++ b/microdroid_manager/src/instance.rs
@@ -33,7 +33,9 @@
//! The payload of a partition is encrypted/signed by a key that is unique to the loader and to the
//! VM as well. Failing to decrypt/authenticate a partition by a loader stops the boot process.
+use android_security_dice::aidl::android::security::dice::IDiceNode::IDiceNode;
use anyhow::{anyhow, bail, Context, Result};
+use binder::wait_for_interface;
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
use ring::aead::{Aad, Algorithm, LessSafeKey, Nonce, UnboundKey, AES_256_GCM};
use ring::hkdf::{Salt, HKDF_SHA256};
@@ -131,7 +133,7 @@
// Decrypt and authenticate the data (along with the header). The data is decrypted in
// place. `open_in_place` returns slice to the decrypted part in the buffer.
- let plaintext_len = get_key().open_in_place(nonce, Aad::from(&header), &mut data)?.len();
+ let plaintext_len = get_key()?.open_in_place(nonce, Aad::from(&header), &mut data)?.len();
// Truncate to remove the tag
data.truncate(plaintext_len);
@@ -174,7 +176,7 @@
// Then encrypt and sign the data. The non-encrypted input data is copied to a vector
// because it is encrypted in place, and also the tag is appended.
- get_key().seal_in_place_append_tag(nonce, Aad::from(&header), &mut data)?;
+ get_key()?.seal_in_place_append_tag(nonce, Aad::from(&header), &mut data)?;
// Persist the encrypted payload data
self.file.write_all(&data)?;
@@ -276,17 +278,18 @@
/// Returns the key that is used to encrypt the microdroid manager partition. It is derived from
/// the sealing CDI of the previous stage, which is Android Boot Loader (ABL).
-fn get_key() -> ZeroOnDropKey {
- // Sealing CDI from the previous stage. For now, this is hardcoded.
- // TODO(jiyong): actually read this from the previous stage
- const SEALING_CDI: [u8; 32] = [10; 32];
+fn get_key() -> Result<ZeroOnDropKey> {
+ // Sealing CDI from the previous stage.
+ let diced = wait_for_interface::<dyn IDiceNode>("android.security.dice.IDiceNode")
+ .context("IDiceNode service not found")?;
+ let bcc_handover = diced.derive(&[]).context("Failed to get BccHandover")?;
// Derive a key from the Sealing CDI
// Step 1 is extraction: https://datatracker.ietf.org/doc/html/rfc5869#section-2.2 where a
// pseduo random key (PRK) is extracted from (Input Keying Material - IKM, which is secret) and
// optional salt.
let salt = Salt::new(HKDF_SHA256, &[]); // use 0 as salt
- let prk = salt.extract(&SEALING_CDI); // Sealing CDI as IKM
+ let prk = salt.extract(&bcc_handover.cdiSeal); // Sealing CDI as IKM
// Step 2 is expansion: https://datatracker.ietf.org/doc/html/rfc5869#section-2.3 where the PRK
// (optionally with the `info` which gives contextual information) is expanded into the output
@@ -308,7 +311,7 @@
::std::ptr::write_volatile::<[u8; 32]>(&mut key, [0; 32]);
}
- ret
+ Ok(ret)
}
#[derive(Debug, Serialize, Deserialize, PartialEq)]
diff --git a/statslog_virtualization/Android.bp b/statslog_virtualization/Android.bp
index 846d12b..51a51a3 100644
--- a/statslog_virtualization/Android.bp
+++ b/statslog_virtualization/Android.bp
@@ -14,6 +14,10 @@
// limitations under the License.
// Autogenerate the class (and respective headers) with logging methods and constants
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
genrule {
name: "statslog_virtualization_header.rs",
tools: ["stats-log-api-gen"],
diff --git a/tests/aidl/com/android/microdroid/testservice/ITestService.aidl b/tests/aidl/com/android/microdroid/testservice/ITestService.aidl
index cdcb2bd..208d61f 100644
--- a/tests/aidl/com/android/microdroid/testservice/ITestService.aidl
+++ b/tests/aidl/com/android/microdroid/testservice/ITestService.aidl
@@ -21,4 +21,7 @@
/* add two integers. */
int addInteger(int a, int b);
+
+ /* read a system property. */
+ String readProperty(String prop);
}
diff --git a/tests/hostside/java/android/virt/test/MicrodroidTestCase.java b/tests/hostside/java/android/virt/test/MicrodroidTestCase.java
index 69638d8..6aa7566 100644
--- a/tests/hostside/java/android/virt/test/MicrodroidTestCase.java
+++ b/tests/hostside/java/android/virt/test/MicrodroidTestCase.java
@@ -97,15 +97,6 @@
final String label = "u:object_r:system_file:s0";
assertThat(runOnMicrodroid("ls", "-Z", testLib), is(label + " " + testLib));
- // Check if the command in vm_config.json was executed by examining the side effect of the
- // command
- assertThat(runOnMicrodroid("getprop", "debug.microdroid.app.run"), is("true"));
- assertThat(runOnMicrodroid("getprop", "debug.microdroid.app.sublib.run"), is("true"));
-
- // Check that keystore was found by the payload. Wait until the property is set.
- tryRunOnMicrodroid("watch -e \"getprop debug.microdroid.test.keystore | grep '^$'\"");
- assertThat(runOnMicrodroid("getprop", "debug.microdroid.test.keystore"), is("PASS"));
-
// Check that no denials have happened so far
assertThat(runOnMicrodroid("logcat -d -e 'avc:[[:space:]]{1,2}denied'"), is(""));
diff --git a/tests/testapk/Android.bp b/tests/testapk/Android.bp
index 32c47dd..6cd16c2 100644
--- a/tests/testapk/Android.bp
+++ b/tests/testapk/Android.bp
@@ -9,6 +9,7 @@
static_libs: [
"androidx.test.runner",
"androidx.test.ext.junit",
+ "com.android.microdroid.testservice-java",
],
libs: ["android.system.virtualmachine"],
jni_libs: ["MicrodroidTestNativeLib"],
@@ -29,6 +30,11 @@
"libbinder_rpc_unstable",
"MicrodroidTestNativeLibSub",
],
+ static_libs: [
+ "libfsverity_digests_proto_cc",
+ "liblog",
+ "libprotobuf-cpp-lite-ndk",
+ ],
}
cc_library_shared {
diff --git a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
index b03a915..032ecfd 100644
--- a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
+++ b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
@@ -17,8 +17,10 @@
import static org.hamcrest.core.Is.is;
import static org.hamcrest.core.IsNot.not;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import static org.junit.Assume.assumeNoException;
import static org.junit.Assume.assumeThat;
@@ -26,6 +28,7 @@
import android.content.Context;
import android.os.Build;
+import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.system.virtualmachine.VirtualMachine;
import android.system.virtualmachine.VirtualMachineCallback;
@@ -36,6 +39,8 @@
import androidx.test.core.app.ApplicationProvider;
+import com.android.microdroid.testservice.ITestService;
+
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
@@ -49,6 +54,7 @@
import java.nio.file.Files;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
@RunWith(JUnit4.class)
@@ -132,9 +138,10 @@
private static final int MIN_MEM_X86_64 = 196;
@Test
- public void startAndStop() throws VirtualMachineException, InterruptedException {
+ public void connectToVmService() throws VirtualMachineException, InterruptedException {
VirtualMachineConfig.Builder builder =
- new VirtualMachineConfig.Builder(mInner.mContext, "assets/vm_config.json");
+ new VirtualMachineConfig.Builder(mInner.mContext,
+ "assets/vm_config_extra_apk.json");
if (Build.SUPPORTED_ABIS.length > 0) {
String primaryAbi = Build.SUPPORTED_ABIS[0];
switch(primaryAbi) {
@@ -148,24 +155,55 @@
}
VirtualMachineConfig config = builder.build();
- mInner.mVm = mInner.mVmm.getOrCreate("test_vm", config);
+ mInner.mVm = mInner.mVmm.getOrCreate("test_vm_extra_apk", config);
VmEventListener listener =
new VmEventListener() {
private boolean mPayloadReadyCalled = false;
private boolean mPayloadStartedCalled = false;
- @Override
- public void onPayloadStarted(VirtualMachine vm, ParcelFileDescriptor stream) {
- mPayloadStartedCalled = true;
+ private void testVMService(Future<IBinder> service) {
+ try {
+ IBinder binder = service.get();
+
+ ITestService testService = ITestService.Stub.asInterface(binder);
+ assertEquals(
+ testService.addInteger(123, 456),
+ 123 + 456);
+ assertEquals(
+ testService.readProperty("debug.microdroid.app.run"),
+ "true");
+ assertEquals(
+ testService.readProperty("debug.microdroid.app.sublib.run"),
+ "true");
+ assertEquals(
+ testService.readProperty("debug.microdroid.test.keystore"),
+ "PASS");
+ assertEquals(
+ testService.readProperty("debug.microdroid.test.extra_apk"),
+ "PASS");
+ } catch (Exception e) {
+ fail("Exception while testing service: " + e.toString());
+ }
}
@Override
public void onPayloadReady(VirtualMachine vm) {
mPayloadReadyCalled = true;
+ try {
+ testVMService(vm.connectToVsockServer(ITestService.SERVICE_PORT));
+ } catch (Exception e) {
+ fail("Exception while connecting to service: " + e.toString());
+ }
+
forceStop(vm);
}
@Override
+ public void onPayloadStarted(VirtualMachine vm, ParcelFileDescriptor stream) {
+ mPayloadStartedCalled = true;
+ }
+
+ @Override
public void onDied(VirtualMachine vm, @DeathReason int reason) {
assertTrue(mPayloadReadyCalled);
assertTrue(mPayloadStartedCalled);
diff --git a/tests/testapk/src/native/testbinary.cpp b/tests/testapk/src/native/testbinary.cpp
index f56b261..c748b2a 100644
--- a/tests/testapk/src/native/testbinary.cpp
+++ b/tests/testapk/src/native/testbinary.cpp
@@ -16,11 +16,14 @@
#include <aidl/android/system/keystore2/IKeystoreService.h>
#include <aidl/android/system/virtualmachineservice/IVirtualMachineService.h>
#include <aidl/com/android/microdroid/testservice/BnTestService.h>
+#include <android-base/file.h>
+#include <android-base/properties.h>
#include <android-base/result.h>
#include <android-base/unique_fd.h>
#include <android/binder_auto_utils.h>
#include <android/binder_manager.h>
#include <fcntl.h>
+#include <fsverity_digests.pb.h>
#include <linux/vm_sockets.h>
#include <stdint.h>
#include <stdio.h>
@@ -29,6 +32,7 @@
#include <unistd.h>
#include <binder_rpc_unstable.hpp>
+#include <string>
using aidl::android::hardware::security::keymint::Algorithm;
using aidl::android::hardware::security::keymint::Digest;
@@ -191,8 +195,8 @@
outcome << "PASS";
} else {
outcome << "FAIL: " << result.error();
- // Pollute stdout with the error in case the property is truncated.
- std::cout << "[" << name << "] test failed: " << result.error() << "\n";
+ // Pollute stderr with the error in case the property is truncated.
+ std::cerr << "[" << name << "] test failed: " << result.error() << "\n";
}
__system_property_set(property.c_str(), outcome.str().c_str());
return result;
@@ -204,6 +208,17 @@
*out = a + b;
return ndk::ScopedAStatus::ok();
}
+
+ ndk::ScopedAStatus readProperty(const std::string& prop, std::string* out) override {
+ *out = android::base::GetProperty(prop, "");
+ if (out->empty()) {
+ std::string msg = "cannot find property " + prop;
+ return ndk::ScopedAStatus::fromExceptionCodeWithMessage(EX_SERVICE_SPECIFIC,
+ msg.c_str());
+ }
+
+ return ndk::ScopedAStatus::ok();
+ }
};
auto testService = ndk::SharedRefBase::make<TestService>();
@@ -230,6 +245,21 @@
return {};
}
+Result<void> verify_apk() {
+ const char* path = "/mnt/extra-apk/0/assets/build_manifest.pb";
+
+ std::string str;
+ if (!android::base::ReadFileToString(path, &str)) {
+ return ErrnoError() << "failed to read build_manifest.pb";
+ }
+
+ if (!android::security::fsverity::FSVerityDigests().ParseFromString(str)) {
+ return Error() << "invalid build_manifest.pb";
+ }
+
+ return {};
+}
+
} // Anonymous namespace
extern "C" int android_native_main(int argc, char* argv[]) {
@@ -249,6 +279,9 @@
testlib_sub();
printf("\n");
+ // Extra apks may be missing; this is not a fatal error
+ report_test("extra_apk", verify_apk());
+
__system_property_set("debug.microdroid.app.run", "true");
if (!report_test("keystore", test_keystore()).ok()) return 1;