Key blob protection using AEAD
Add BlobEncryptor, which can be used to write a secret (like our
private key) to an encrypted & authenticated blob and later retrieve
it.
Added the skeleton of code to make use of this, using the sealing CDI
as the input to the key derivation.
Bug: 214233409
Test: atest compsvc_device_tests
Change-Id: Iea7e82405072a31ae5f7ad64a9e894a970913219
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/compos/Android.bp b/compos/Android.bp
index 401f1c7..8d0ba3b 100644
--- a/compos/Android.bp
+++ b/compos/Android.bp
@@ -2,8 +2,8 @@
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",
@@ -36,7 +36,18 @@
shared_libs: [
"libbinder_rpc_unstable",
],
+}
+
+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/src/blob_encryptor.rs b/compos/src/blob_encryptor.rs
new file mode 100644
index 0000000..ed4484e
--- /dev/null
+++ b/compos/src/blob_encryptor.rs
@@ -0,0 +1,132 @@
+/*
+ * 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},
+};
+
+pub struct BlobEncryptor {
+ random: SystemRandom,
+}
+
+// Non-secret input to the AEAD key derivation
+const KDF_INFO: &[u8] = b"CompOS blob sealing key";
+
+impl BlobEncryptor {
+ pub fn new() -> Self {
+ Self { random: SystemRandom::new() }
+ }
+
+ pub fn derive_aead_key(&self, 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(&self, 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];
+ self.random.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(&self, 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 encryptor = BlobEncryptor::new();
+ let input_keying_material = b"Key is derived from this";
+ let original_bytes = b"This is the secret data";
+
+ let key = encryptor.derive_aead_key(input_keying_material)?;
+ let blob = encryptor.encrypt_bytes(key, original_bytes)?;
+
+ let key = encryptor.derive_aead_key(input_keying_material)?;
+ let decoded_bytes = encryptor.decrypt_bytes(key, &blob)?;
+
+ assert_eq!(decoded_bytes, original_bytes);
+ Ok(())
+ }
+
+ #[test]
+ fn test_modified_data_detected() -> Result<()> {
+ let encryptor = BlobEncryptor::new();
+ let input_keying_material = b"Key is derived from this";
+ let original_bytes = b"This is the secret data";
+
+ let key = encryptor.derive_aead_key(input_keying_material)?;
+ let mut blob = encryptor.encrypt_bytes(key, original_bytes)?;
+
+ // Flip a bit.
+ blob[0] ^= 1;
+
+ let key = encryptor.derive_aead_key(input_keying_material)?;
+ let decoded_bytes = encryptor.decrypt_bytes(key, &blob);
+
+ assert!(decoded_bytes.is_err());
+ Ok(())
+ }
+}
diff --git a/compos/src/compsvc.rs b/compos/src/compsvc.rs
index b4af9b5..60e77a7 100644
--- a/compos/src/compsvc.rs
+++ b/compos/src/compsvc.rs
@@ -46,6 +46,7 @@
let service = CompOsService {
odrefresh_path: PathBuf::from(ODREFRESH_PATH),
key_service: CompOsKeyService::new()?,
+ dice: Dice::new()?,
key_blob: RwLock::new(Vec::new()),
};
Ok(BnCompOsService::new_binder(service, BinderFeatures::default()))
@@ -54,6 +55,7 @@
struct CompOsService {
odrefresh_path: PathBuf,
key_service: CompOsKeyService,
+ dice: Dice,
key_blob: RwLock<Vec<u8>>,
}
@@ -68,8 +70,7 @@
}
fn get_boot_certificate_chain(&self) -> Result<Vec<u8>> {
- let dice = Dice::new()?;
- dice.get_boot_certificate_chain()
+ self.dice.get_boot_certificate_chain()
}
}
diff --git a/compos/src/compsvc_main.rs b/compos/src/compsvc_main.rs
index b4e3128..23a6ed0 100644
--- a/compos/src/compsvc_main.rs
+++ b/compos/src/compsvc_main.rs
@@ -17,11 +17,13 @@
//! A tool to start a standalone compsvc server that serves over RPC binder.
mod artifact_signer;
+mod blob_encryptor;
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..cdfc387 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};
@@ -39,4 +39,10 @@
.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..62c7a40
--- /dev/null
+++ b/compos/src/signing_key.rs
@@ -0,0 +1,78 @@
+/*
+ * 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_encryptor::BlobEncryptor;
+use crate::dice::Dice;
+use anyhow::{bail, Result};
+use compos_aidl_interface::aidl::com::android::compos::CompOsKeyData::CompOsKeyData;
+use ring::rand::SystemRandom;
+
+pub struct SigningKey {
+ random: SystemRandom,
+ dice: Dice,
+ blob_encryptor: BlobEncryptor,
+}
+
+impl SigningKey {
+ pub fn new() -> Result<Self> {
+ Ok(Self {
+ random: SystemRandom::new(),
+ dice: Dice::new()?,
+ blob_encryptor: BlobEncryptor::new(),
+ })
+ }
+
+ pub fn generate(&self) -> Result<CompOsKeyData> {
+ // TODO: generate key pair; get aead key; generate random nonce; encrypt private key;
+ // generate self-signed cert
+ bail!("Not implemented")
+ }
+
+ pub fn verify(&self, key_blob: &[u8], public_key: &[u8]) -> Result<()> {
+ bail!("Not implemented")
+ }
+
+ pub fn new_signer(&self, key_blob: &[u8]) -> Signer {
+ Signer { key_blob: key_blob.to_owned() }
+ }
+
+ fn encrypt_private_key(&self, private_key: &[u8]) -> Result<Vec<u8>> {
+ let cdi = self.dice.get_sealing_cdi()?;
+ let aead_key = self.blob_encryptor.derive_aead_key(&cdi)?;
+ self.blob_encryptor.encrypt_bytes(aead_key, private_key)
+ }
+
+ fn decrypt_private_key(&self, blob: &[u8]) -> Result<Vec<u8>> {
+ let cdi = self.dice.get_sealing_cdi()?;
+ let aead_key = self.blob_encryptor.derive_aead_key(&cdi)?;
+ self.blob_encryptor.decrypt_bytes(aead_key, blob)
+ }
+}
+
+pub struct Signer {
+ key_blob: Vec<u8>,
+}
+
+impl Signer {
+ pub fn sign(self, data: &[u8]) -> Result<Vec<u8>> {
+ bail!("Not implemented")
+ }
+}