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")
+    }
+}