Authenticate file w/ fs-verity digest in hex

An input directory is configured by a build manifest file. With this
change, authfs starts to use the fs-verity digest in the manifest to
authenticate the corresponding file.

Although we're not using it, this change also makes it possible to
specify a known fs-verity digest to a remote file from the command line
(and deprecate the fake support of signature verification by the given
certificate).

Since we no longer derive fs-verity digest from the first chunk of a
Merkle tree, some relevant functions are deleted.

Bug: 206869687
Test: atest AuthFsHostTest ComposHostTestCases
Test: atest authfs_device_test_src_lib
Change-Id: Ibb5c246fb0d29aeafde187555f8d72c0282a65c7
diff --git a/authfs/src/fsverity/common.rs b/authfs/src/fsverity/common.rs
index 83e8ae4..eba379d 100644
--- a/authfs/src/fsverity/common.rs
+++ b/authfs/src/fsverity/common.rs
@@ -24,8 +24,8 @@
 
 #[derive(Error, Debug)]
 pub enum FsverityError {
-    #[error("Cannot verify a signature")]
-    BadSignature,
+    #[error("Invalid digest")]
+    InvalidDigest,
     #[error("Insufficient data, only got {0}")]
     InsufficientData(usize),
     #[error("Cannot verify a block")]
diff --git a/authfs/src/fsverity/metadata/Android.bp b/authfs/src/fsverity/metadata/Android.bp
index b155224..af3729f 100644
--- a/authfs/src/fsverity/metadata/Android.bp
+++ b/authfs/src/fsverity/metadata/Android.bp
@@ -18,6 +18,7 @@
     ],
     rustlibs: [
         "libauthfs_fsverity_metadata_bindgen",
+        "libring",
     ],
     edition: "2018",
     apex_available: ["com.android.virt"],
diff --git a/authfs/src/fsverity/metadata/metadata.hpp b/authfs/src/fsverity/metadata/metadata.hpp
index 7bbd3da..f05740e 100644
--- a/authfs/src/fsverity/metadata/metadata.hpp
+++ b/authfs/src/fsverity/metadata/metadata.hpp
@@ -47,6 +47,9 @@
 
 const uint64_t CHUNK_SIZE = 4096;
 
+// Give the macro value a name to export.
+const uint8_t FSVERITY_HASH_ALG_SHA256 = FS_VERITY_HASH_ALG_SHA256;
+
 enum class FSVERITY_SIGNATURE_TYPE : __le32 {
     NONE = 0,
     PKCS7 = 1,
diff --git a/authfs/src/fsverity/metadata/metadata.rs b/authfs/src/fsverity/metadata/metadata.rs
index 073e044..8bc0617 100644
--- a/authfs/src/fsverity/metadata/metadata.rs
+++ b/authfs/src/fsverity/metadata/metadata.rs
@@ -16,18 +16,31 @@
 
 //! Rust bindgen interface for FSVerity Metadata file (.fsv_meta)
 use authfs_fsverity_metadata_bindgen::{
-    fsverity_metadata_header, FSVERITY_SIGNATURE_TYPE_NONE, FSVERITY_SIGNATURE_TYPE_PKCS7,
-    FSVERITY_SIGNATURE_TYPE_RAW,
+    fsverity_descriptor, fsverity_metadata_header, FSVERITY_HASH_ALG_SHA256,
+    FSVERITY_SIGNATURE_TYPE_NONE, FSVERITY_SIGNATURE_TYPE_PKCS7, FSVERITY_SIGNATURE_TYPE_RAW,
 };
 
+use ring::digest::{Context, SHA256};
 use std::cmp::min;
-use std::os::unix::fs::MetadataExt;
+use std::ffi::OsString;
+use std::fs::File;
+use std::io::{self, Read, Seek};
+use std::mem::{size_of, zeroed};
+use std::os::unix::fs::{FileExt, MetadataExt};
+use std::path::{Path, PathBuf};
+use std::slice::from_raw_parts_mut;
+
+/// Offset of `descriptor` in `struct fsverity_metadatata_header`.
+const DESCRIPTOR_OFFSET: usize = 4;
 
 /// Structure for parsed metadata.
 pub struct FSVerityMetadata {
     /// Header for the metadata.
     pub header: fsverity_metadata_header,
 
+    /// fs-verity digest of the file, with hash algorithm defined in the fs-verity descriptor.
+    pub digest: Vec<u8>,
+
     /// Optional signature for the metadata.
     pub signature: Option<Vec<u8>>,
 
@@ -54,14 +67,6 @@
     }
 }
 
-use std::ffi::OsString;
-use std::fs::File;
-use std::io::{self, Read, Seek};
-use std::mem::{size_of, zeroed};
-use std::os::unix::fs::FileExt;
-use std::path::{Path, PathBuf};
-use std::slice::from_raw_parts_mut;
-
 /// Common block and page size in Linux.
 pub const CHUNK_SIZE: u64 = authfs_fsverity_metadata_bindgen::CHUNK_SIZE;
 
@@ -75,23 +80,42 @@
 
 /// Parse metadata from given file, and returns a structure for the metadata.
 pub fn parse_fsverity_metadata(mut metadata_file: File) -> io::Result<Box<FSVerityMetadata>> {
-    let header_size = size_of::<fsverity_metadata_header>();
+    let (header, digest) = {
+        // SAFETY: The header doesn't include any pointers.
+        let mut header: fsverity_metadata_header = unsafe { zeroed() };
 
-    // SAFETY: the header doesn't include any pointers
-    let header: fsverity_metadata_header = unsafe {
-        let mut header: fsverity_metadata_header = zeroed();
-        let buffer = from_raw_parts_mut(
-            &mut header as *mut fsverity_metadata_header as *mut u8,
-            header_size,
-        );
-        metadata_file.read_exact(buffer)?;
+        // SAFETY: fsverity_metadata_header is packed, so reading/write from/to the back_buffer
+        // won't overflow.
+        let back_buffer = unsafe {
+            from_raw_parts_mut(
+                &mut header as *mut fsverity_metadata_header as *mut u8,
+                size_of::<fsverity_metadata_header>(),
+            )
+        };
+        metadata_file.read_exact(back_buffer)?;
+
+        // Digest needs to be calculated with the raw value (without changing the endianness).
+        let digest = match header.descriptor.hash_algorithm {
+            FSVERITY_HASH_ALG_SHA256 => {
+                let mut context = Context::new(&SHA256);
+                context.update(
+                    &back_buffer
+                        [DESCRIPTOR_OFFSET..DESCRIPTOR_OFFSET + size_of::<fsverity_descriptor>()],
+                );
+                Ok(context.finish().as_ref().to_owned())
+            }
+            alg => Err(io::Error::new(
+                io::ErrorKind::Other,
+                format!("Unsupported hash algorithm {}, continue (likely failing soon)", alg),
+            )),
+        }?;
 
         // TODO(inseob): This doesn't seem ideal. Maybe we can consider nom?
         header.version = u32::from_le(header.version);
         header.descriptor.data_size = u64::from_le(header.descriptor.data_size);
         header.signature_type = u32::from_le(header.signature_type);
         header.signature_size = u32::from_le(header.signature_size);
-        header
+        (header, digest)
     };
 
     if header.version != 1 {
@@ -113,5 +137,5 @@
     let merkle_tree_offset =
         (metadata_file.stream_position()? + CHUNK_SIZE - 1) / CHUNK_SIZE * CHUNK_SIZE;
 
-    Ok(Box::new(FSVerityMetadata { header, signature, metadata_file, merkle_tree_offset }))
+    Ok(Box::new(FSVerityMetadata { header, digest, signature, metadata_file, merkle_tree_offset }))
 }
diff --git a/authfs/src/fsverity/sys.rs b/authfs/src/fsverity/sys.rs
index b3222db..51e10a5 100644
--- a/authfs/src/fsverity/sys.rs
+++ b/authfs/src/fsverity/sys.rs
@@ -14,9 +14,6 @@
  * limitations under the License.
  */
 
-/// Magic used in fs-verity digest
-pub const FS_VERITY_MAGIC: &[u8; 8] = b"FSVerity";
-
 /// fs-verity version that we are using
 pub const FS_VERITY_VERSION: u8 = 1;
 
diff --git a/authfs/src/fsverity/verifier.rs b/authfs/src/fsverity/verifier.rs
index 17a0a2a..61b8e13 100644
--- a/authfs/src/fsverity/verifier.rs
+++ b/authfs/src/fsverity/verifier.rs
@@ -18,17 +18,12 @@
 use std::io;
 
 use super::common::{build_fsverity_digest, merkle_tree_height, FsverityError};
-use super::sys::{FS_VERITY_HASH_ALG_SHA256, FS_VERITY_MAGIC};
-use crate::auth::Authenticator;
 use crate::common::{divide_roundup, CHUNK_SIZE};
 use crate::crypto::{CryptoError, Sha256Hasher};
 use crate::file::{ChunkBuffer, ReadByChunk};
 
 const ZEROS: [u8; CHUNK_SIZE as usize] = [0u8; CHUNK_SIZE as usize];
 
-// The size of `struct fsverity_formatted_digest` in Linux with SHA-256.
-const SIZE_OF_FSVERITY_FORMATTED_DIGEST_SHA256: usize = 12 + Sha256Hasher::HASH_SIZE;
-
 type HashBuffer = [u8; Sha256Hasher::HASH_SIZE];
 
 fn hash_with_padding(chunk: &[u8], pad_to: usize) -> Result<HashBuffer, CryptoError> {
@@ -116,21 +111,6 @@
     }))
 }
 
-fn build_fsverity_formatted_digest(
-    root_hash: &HashBuffer,
-    file_size: u64,
-) -> Result<[u8; SIZE_OF_FSVERITY_FORMATTED_DIGEST_SHA256], CryptoError> {
-    let digest = build_fsverity_digest(root_hash, file_size)?;
-    // Little-endian byte representation of fsverity_formatted_digest from linux/fsverity.h
-    // Not FFI-ed as it seems easier to deal with the raw bytes manually.
-    let mut formatted_digest = [0u8; SIZE_OF_FSVERITY_FORMATTED_DIGEST_SHA256];
-    formatted_digest[0..8].copy_from_slice(FS_VERITY_MAGIC);
-    formatted_digest[8..10].copy_from_slice(&(FS_VERITY_HASH_ALG_SHA256 as u16).to_le_bytes());
-    formatted_digest[10..12].copy_from_slice(&(Sha256Hasher::HASH_SIZE as u16).to_le_bytes());
-    formatted_digest[12..].copy_from_slice(&digest);
-    Ok(formatted_digest)
-}
-
 pub struct VerifiedFileReader<F: ReadByChunk, M: ReadByChunk> {
     chunked_file: F,
     file_size: u64,
@@ -139,11 +119,10 @@
 }
 
 impl<F: ReadByChunk, M: ReadByChunk> VerifiedFileReader<F, M> {
-    pub fn new<A: Authenticator>(
-        authenticator: &A,
+    pub fn new(
         chunked_file: F,
         file_size: u64,
-        sig: Option<&[u8]>,
+        expected_digest: &[u8],
         merkle_tree: M,
     ) -> Result<VerifiedFileReader<F, M>, FsverityError> {
         let mut buf = [0u8; CHUNK_SIZE as usize];
@@ -157,12 +136,11 @@
             }
         }
         let root_hash = Sha256Hasher::new()?.update(&buf[..])?.finalize()?;
-        let formatted_digest = build_fsverity_formatted_digest(&root_hash, file_size)?;
-        let valid = authenticator.verify(sig, &formatted_digest)?;
-        if valid {
+        if expected_digest == build_fsverity_digest(&root_hash, file_size)? {
+            // Once verified, use the root_hash for verification going forward.
             Ok(VerifiedFileReader { chunked_file, file_size, merkle_tree, root_hash })
         } else {
-            Err(FsverityError::BadSignature)
+            Err(FsverityError::InvalidDigest)
         }
     }
 }
@@ -183,7 +161,6 @@
 #[cfg(test)]
 mod tests {
     use super::*;
-    use crate::auth::FakeAuthenticator;
     use crate::file::ReadByChunk;
     use anyhow::Result;
     use authfs_fsverity_metadata::{parse_fsverity_metadata, FSVerityMetadata};
@@ -245,13 +222,11 @@
         let file_reader = LocalFileReader::new(File::open(content_path)?)?;
         let file_size = file_reader.len();
         let metadata = parse_fsverity_metadata(File::open(metadata_path)?)?;
-        let authenticator = FakeAuthenticator::always_succeed();
         Ok((
             VerifiedFileReader::new(
-                &authenticator,
                 file_reader,
                 file_size,
-                metadata.signature.clone().as_deref(),
+                &metadata.digest.clone(),
                 MerkleTreeReader { metadata },
             )?,
             file_size,
@@ -312,21 +287,4 @@
         assert!(file_reader.read_chunk(last_index, &mut buf).is_ok());
         Ok(())
     }
-
-    #[test]
-    fn invalid_signature() -> Result<()> {
-        let authenticator = FakeAuthenticator::always_fail();
-        let file_reader = LocalFileReader::new(File::open("testdata/input.4m")?)?;
-        let file_size = file_reader.len();
-        let metadata = parse_fsverity_metadata(File::open("testdata/input.4m.fsv_meta")?)?;
-        assert!(VerifiedFileReader::new(
-            &authenticator,
-            file_reader,
-            file_size,
-            metadata.signature.clone().as_deref(),
-            MerkleTreeReader { metadata },
-        )
-        .is_err());
-        Ok(())
-    }
 }