authfs: create a chunked reader with fs-verity verification

The new chunked reader uses a Merkle tree to verify each chunk read of
the corresponding backing file. The reader also accepts an
autheneticator for signature verification, though it is currently a fake
implementation due to the lack of PKCS#7 signature support in BoringSSL
(b/170494765).

Test: atest authfs_host_test_src_lib
Bug: 171310075

Change-Id: Ibf4151ab2a93f7515ad8c9c0462df6c21c10d767
diff --git a/authfs/src/auth.rs b/authfs/src/auth.rs
new file mode 100644
index 0000000..71ad858
--- /dev/null
+++ b/authfs/src/auth.rs
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+use std::io;
+
+// TODO(b/170494765): Implement an authenticator to verify a PKCS#7 signature. We only need to
+// verify the signature, not the full certificate chain.
+
+pub trait Authenticator {
+    fn verify(&self, signature: &[u8], signed_data: &[u8]) -> io::Result<bool>;
+}
+
+pub struct FakeAuthenticator {
+    should_allow: bool,
+}
+
+#[allow(dead_code)]
+impl FakeAuthenticator {
+    pub fn always_succeed() -> Self {
+        FakeAuthenticator { should_allow: true }
+    }
+
+    pub fn always_fail() -> Self {
+        FakeAuthenticator { should_allow: false }
+    }
+}
+
+impl Authenticator for FakeAuthenticator {
+    fn verify(&self, _signature_pem: &[u8], _signed_data: &[u8]) -> io::Result<bool> {
+        Ok(self.should_allow)
+    }
+}
diff --git a/authfs/src/fsverity.rs b/authfs/src/fsverity.rs
index f32ccab..c9070ba 100644
--- a/authfs/src/fsverity.rs
+++ b/authfs/src/fsverity.rs
@@ -14,16 +14,25 @@
  * limitations under the License.
  */
 
+use libc::EIO;
 use std::io;
 use thiserror::Error;
 
+use crate::auth::Authenticator;
 use crate::crypto::{CryptoError, Sha256Hasher};
 use crate::reader::ReadOnlyDataByChunk;
 
 const ZEROS: [u8; 4096] = [0u8; 4096];
 
+// The size of `struct fsverity_formatted_digest` in Linux with SHA-256.
+const SIZE_OF_FSVERITY_FORMATTED_DIGEST_SHA256: usize = 12 + Sha256Hasher::HASH_SIZE;
+
 #[derive(Error, Debug)]
 pub enum FsverityError {
+    #[error("Cannot verify a signature")]
+    BadSignature,
+    #[error("Insufficient data, only got {0}")]
+    InsufficientData(usize),
     #[error("Cannot verify a block")]
     CannotVerify,
     #[error("I/O error")]
@@ -43,7 +52,6 @@
     Sha256Hasher::new()?.update(&chunk)?.update(&ZEROS[..padding_size])?.finalize()
 }
 
-#[allow(dead_code)]
 fn verity_check<T: ReadOnlyDataByChunk>(
     chunk: &[u8],
     chunk_index: u64,
@@ -123,9 +131,90 @@
     }))
 }
 
+fn build_fsverity_formatted_digest(
+    root_hash: &HashBuffer,
+    file_size: u64,
+) -> Result<[u8; SIZE_OF_FSVERITY_FORMATTED_DIGEST_SHA256], CryptoError> {
+    let desc_hash = Sha256Hasher::new()?
+        .update(&1u8.to_le_bytes())? // version
+        .update(&1u8.to_le_bytes())? // hash_algorithm
+        .update(&12u8.to_le_bytes())? // log_blocksize
+        .update(&0u8.to_le_bytes())? // salt_size
+        .update(&0u32.to_le_bytes())? // sig_size
+        .update(&file_size.to_le_bytes())? // data_size
+        .update(root_hash)? // root_hash, first 32 bytes
+        .update(&[0u8; 32])? // root_hash, last 32 bytes
+        .update(&[0u8; 32])? // salt
+        .update(&[0u8; 32])? // reserved
+        .update(&[0u8; 32])? // reserved
+        .update(&[0u8; 32])? // reserved
+        .update(&[0u8; 32])? // reserved
+        .update(&[0u8; 16])? // reserved
+        .finalize()?;
+
+    let mut fsverity_digest = [0u8; SIZE_OF_FSVERITY_FORMATTED_DIGEST_SHA256];
+    fsverity_digest[0..8].copy_from_slice(b"FSVerity");
+    fsverity_digest[8..10].copy_from_slice(&1u16.to_le_bytes());
+    fsverity_digest[10..12].copy_from_slice(&32u16.to_le_bytes());
+    fsverity_digest[12..].copy_from_slice(&desc_hash);
+    Ok(fsverity_digest)
+}
+
+pub struct FsverityChunkedFileReader<F: ReadOnlyDataByChunk, M: ReadOnlyDataByChunk> {
+    chunked_file: F,
+    file_size: u64,
+    merkle_tree: M,
+    root_hash: HashBuffer,
+}
+
+impl<F: ReadOnlyDataByChunk, M: ReadOnlyDataByChunk> FsverityChunkedFileReader<F, M> {
+    #[allow(dead_code)]
+    pub fn new<A: Authenticator>(
+        authenticator: &A,
+        chunked_file: F,
+        file_size: u64,
+        sig: Vec<u8>,
+        merkle_tree: M,
+    ) -> Result<FsverityChunkedFileReader<F, M>, FsverityError> {
+        // TODO(victorhsieh): Use generic constant directly once supported. No need to assert
+        // afterward.
+        let mut buf = [0u8; 4096];
+        assert_eq!(buf.len() as u64, M::CHUNK_SIZE);
+        let size = merkle_tree.read_chunk(0, &mut buf)?;
+        if buf.len() != size {
+            return Err(FsverityError::InsufficientData(size));
+        }
+        let root_hash = Sha256Hasher::new()?.update(&buf[..])?.finalize()?;
+        let fsverity_digest = build_fsverity_formatted_digest(&root_hash, file_size)?;
+        let valid = authenticator.verify(&sig, &fsverity_digest)?;
+        if valid {
+            Ok(FsverityChunkedFileReader { chunked_file, file_size, merkle_tree, root_hash })
+        } else {
+            Err(FsverityError::BadSignature)
+        }
+    }
+}
+
+impl<F: ReadOnlyDataByChunk, M: ReadOnlyDataByChunk> ReadOnlyDataByChunk
+    for FsverityChunkedFileReader<F, M>
+{
+    fn read_chunk(&self, chunk_index: u64, buf: &mut [u8]) -> io::Result<usize> {
+        debug_assert!(buf.len() as u64 >= Self::CHUNK_SIZE);
+        let size = self.chunked_file.read_chunk(chunk_index, buf)?;
+        let root_hash = verity_check(&buf[..size], chunk_index, self.file_size, &self.merkle_tree)
+            .map_err(|_| io::Error::from_raw_os_error(EIO))?;
+        if root_hash != self.root_hash {
+            Err(io::Error::from_raw_os_error(EIO))
+        } else {
+            Ok(size)
+        }
+    }
+}
+
 #[cfg(test)]
 mod tests {
     use super::*;
+    use crate::auth::FakeAuthenticator;
     use crate::reader::ReadOnlyDataByChunk;
     use anyhow::Result;
 
@@ -137,12 +226,19 @@
     fn fsverity_verify_full_read_4k() -> Result<()> {
         let file = &include_bytes!("../testdata/input.4k")[..];
         let merkle_tree = &include_bytes!("../testdata/input.4k.merkle_dump")[..];
-
-        let mut buf = [0u8; 4096];
+        let sig = include_bytes!("../testdata/input.4k.fsv_sig").to_vec();
+        let authenticator = FakeAuthenticator::always_succeed();
+        let verified_file = FsverityChunkedFileReader::new(
+            &authenticator,
+            file,
+            file.len() as u64,
+            sig,
+            merkle_tree,
+        )?;
 
         for i in 0..total_chunk_number(file.len() as u64) {
-            let size = file.read_chunk(i, &mut buf[..])?;
-            assert!(verity_check(&buf[..size], i, file.len() as u64, &merkle_tree).is_ok());
+            let mut buf = [0u8; 4096];
+            assert!(verified_file.read_chunk(i, &mut buf[..]).is_ok());
         }
         Ok(())
     }
@@ -151,11 +247,19 @@
     fn fsverity_verify_full_read_4k1() -> Result<()> {
         let file = &include_bytes!("../testdata/input.4k1")[..];
         let merkle_tree = &include_bytes!("../testdata/input.4k1.merkle_dump")[..];
+        let sig = include_bytes!("../testdata/input.4k1.fsv_sig").to_vec();
+        let authenticator = FakeAuthenticator::always_succeed();
+        let verified_file = FsverityChunkedFileReader::new(
+            &authenticator,
+            file,
+            file.len() as u64,
+            sig,
+            merkle_tree,
+        )?;
 
-        let mut buf = [0u8; 4096];
         for i in 0..total_chunk_number(file.len() as u64) {
-            let size = file.read_chunk(i, &mut buf[..])?;
-            assert!(verity_check(&buf[..size], i, file.len() as u64, &merkle_tree).is_ok());
+            let mut buf = [0u8; 4096];
+            assert!(verified_file.read_chunk(i, &mut buf[..]).is_ok());
         }
         Ok(())
     }
@@ -164,11 +268,19 @@
     fn fsverity_verify_full_read_4m() -> Result<()> {
         let file = &include_bytes!("../testdata/input.4m")[..];
         let merkle_tree = &include_bytes!("../testdata/input.4m.merkle_dump")[..];
+        let sig = include_bytes!("../testdata/input.4m.fsv_sig").to_vec();
+        let authenticator = FakeAuthenticator::always_succeed();
+        let verified_file = FsverityChunkedFileReader::new(
+            &authenticator,
+            file,
+            file.len() as u64,
+            sig,
+            merkle_tree,
+        )?;
 
-        let mut buf = [0u8; 4096];
         for i in 0..total_chunk_number(file.len() as u64) {
-            let size = file.read_chunk(i, &mut buf[..])?;
-            assert!(verity_check(&buf[..size], i, file.len() as u64, &merkle_tree).is_ok());
+            let mut buf = [0u8; 4096];
+            assert!(verified_file.read_chunk(i, &mut buf[..]).is_ok());
         }
         Ok(())
     }
@@ -178,6 +290,15 @@
         let file = &include_bytes!("../testdata/input.4m")[..];
         // First leaf node is corrupted.
         let merkle_tree = &include_bytes!("../testdata/input.4m.merkle_dump.bad")[..];
+        let sig = include_bytes!("../testdata/input.4m.fsv_sig").to_vec();
+        let authenticator = FakeAuthenticator::always_succeed();
+        let verified_file = FsverityChunkedFileReader::new(
+            &authenticator,
+            file,
+            file.len() as u64,
+            sig,
+            merkle_tree,
+        )?;
 
         // A lowest broken node (a 4K chunk that contains 128 sha256 hashes) will fail the read
         // failure of the underlying chunks, but not before or after.
@@ -185,11 +306,26 @@
         let num_hashes = 4096 / 32;
         let last_index = num_hashes;
         for i in 0..last_index {
-            let size = file.read_chunk(i, &mut buf[..])?;
-            assert!(verity_check(&buf[..size], i, file.len() as u64, &merkle_tree).is_err());
+            assert!(verified_file.read_chunk(i, &mut buf[..]).is_err());
         }
-        let size = file.read_chunk(last_index, &mut buf[..])?;
-        assert!(verity_check(&buf[..size], last_index, file.len() as u64, &merkle_tree).is_ok());
+        assert!(verified_file.read_chunk(last_index, &mut buf[..]).is_ok());
+        Ok(())
+    }
+
+    #[test]
+    fn invalid_signature() -> Result<()> {
+        let authenticator = FakeAuthenticator::always_fail();
+        let file = &include_bytes!("../testdata/input.4m")[..];
+        let merkle_tree = &include_bytes!("../testdata/input.4m.merkle_dump")[..];
+        let sig = include_bytes!("../testdata/input.4m.fsv_sig").to_vec();
+        assert!(FsverityChunkedFileReader::new(
+            &authenticator,
+            file,
+            file.len() as u64,
+            sig,
+            merkle_tree
+        )
+        .is_err());
         Ok(())
     }
 }
diff --git a/authfs/src/lib.rs b/authfs/src/lib.rs
index 26de157..05070d6 100644
--- a/authfs/src/lib.rs
+++ b/authfs/src/lib.rs
@@ -22,6 +22,7 @@
 //!
 //! The implementation is not finished.
 
+mod auth;
 mod crypto;
 mod fsverity;
 mod reader;
diff --git a/authfs/testdata/README.md b/authfs/testdata/README.md
new file mode 100644
index 0000000..113fe62
--- /dev/null
+++ b/authfs/testdata/README.md
@@ -0,0 +1,9 @@
+fs-verity signing
+=================
+With a key pair, fs-verity signature can be generated by simply running
+`fsverity` command line tool from
+[fsverity-util](https://git.kernel.org/pub/scm/linux/kernel/git/ebiggers/fsverity-utils.git).
+
+```
+fsverity sign test.data test.data.fsv_sig --key=key.pem --cert=cert.pem
+```
diff --git a/authfs/testdata/cert.der b/authfs/testdata/cert.der
new file mode 100644
index 0000000..e7e2273
--- /dev/null
+++ b/authfs/testdata/cert.der
Binary files differ
diff --git a/authfs/testdata/cert.pem b/authfs/testdata/cert.pem
new file mode 100644
index 0000000..0a380f3
--- /dev/null
+++ b/authfs/testdata/cert.pem
@@ -0,0 +1,32 @@
+-----BEGIN CERTIFICATE-----
+MIIFmzCCA4OgAwIBAgIUbgQdchyPL/bN/LLgs8CPJU+fO10wDQYJKoZIhvcNAQEL
+BQAwXTELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcM
+DU1vdW50YWluIFZpZXcxDzANBgNVBAoMBkdvb2dsZTEQMA4GA1UECwwHQW5kcm9p
+ZDAeFw0yMDExMDIyMTM1MTRaFw0zMDEwMzEyMTM1MTRaMF0xCzAJBgNVBAYTAlVT
+MRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1Nb3VudGFpbiBWaWV3MQ8w
+DQYDVQQKDAZHb29nbGUxEDAOBgNVBAsMB0FuZHJvaWQwggIiMA0GCSqGSIb3DQEB
+AQUAA4ICDwAwggIKAoICAQDAHeyQArSPm8IE+Fx65zFVG4Sjicxdv8Pb5Be5rO+P
+JZ/QtQfNqBTVBKVP//Gp8tY79IlgiJYq69ZbofqPDwy/Q+9HfxdjLxp5uotYi9LP
+uYQTEmt3Lufu3oFHqyvhNVzm1sFEa9cYsQA7xtAZ5a4yQWKI2IBIR9kX6AvJQnoU
+KUg8c8QcXO6fjhO+zEcZo6rqYz7qMnpj28nKGPpuQlARqXFIXvSwVTz3D0GHri3x
+0Xzam5XG6h+KBdRQ8Zq3OEk8XaI/P4I8d/NgjWtDEjgNmLSkPFAshv9V4aZ1JCN7
+Oibp7vSCbr2ExeL859mxxLbk0WR3BfDmV6n3g7Rix93hO3NogBADp8P1blc23Iyh
+FcoiG3ub+668uQB8KurMmEDmNzOAAjsE2N6VNauDM+N7k7JXlAHk9No/D5uW3lfn
+ndNfAM/Wo8u3nPpFj92UilSoxgONKvSmta+zYraODylKDqZeuyU26u/UCSbf63Qf
+BirIq7p/ZAcADPODmINfjJaEMAHAVLxyMA83rX+d/ohsmDlfP7XB4cnKY1XJSGxm
+831F3VUr/1dBfZ21oavLLqE4B3EIB5hbp4KfLYsuMgMqPefWCONH9zVmuLTJJwSQ
+pe8LYKcYpjy3espes+63/c5pjfF8427z1anLxyz8lVVFnJaavsUPiQEpLQNBYARc
+QwIDAQABo1MwUTAdBgNVHQ4EFgQUv8uHLwpLoWE8AiLrqrfN3jJj61gwHwYDVR0j
+BBgwFoAUv8uHLwpLoWE8AiLrqrfN3jJj61gwDwYDVR0TAQH/BAUwAwEB/zANBgkq
+hkiG9w0BAQsFAAOCAgEAXqvjFadmSEGp9NhGGpgLMl6CqvJtyhe2FDfsvhAwfKdD
+qGcxuXN/HWctUYj/r+jASFfXHzse+O6lWo6MmSUP4mu/dEDyW8vTB8NHp9QgCVuC
+pCTWrfkEXwuq7ARHeFkaW1iSMwHosAilKibTNCHWqfmlmMeOF9e/JeAZbP6X8/5N
+u1y8vZEdMOA/JGEMjI4H27P+txCA8e24PjwGYbopH9URb6shRAR4W6r/Rp9+jhwR
+o/RUJfxavGXX69iR7MiwT756eSeihyE9+TQLYrGaPktDnZ18x/QjXFWOrAR1H9pe
+Pngma3L+/YyevVwbpLzZ724qpPFd1I5daZDgMebzZU6atbYBEQQCuiwvwEQ3ilUK
+pNjZyqN8rL0T20L5wr6PFDA3NOYlGWePYws3kB8DUOE9MFhtbzibDfSlrbRRaMP1
+n9N+HTtvMIRcGh02WxPsV2Z/AU6eSrX2XH55mh2SI9M9jmH0IkQieyAeeT9/qI6h
+eD3XrHdKr/uLL8C0sngueQjmbCoS1jAAEXJrPZgAouC6vUeKx0wc4lEX4rbDEO0m
+qXZwJ7W/0kHRHlUztbpdiYSo+WaOpk9XqUs/C/F8gNSazdawS1/weH3gnvBC6eXT
+gqHoGWlxMlRl5Qd5LdmZ1L1ASyjH5Yk8QhLCFoNjLYkEfAbDyc4hi5y5sccMDrA=
+-----END CERTIFICATE-----
diff --git a/authfs/testdata/input.4k.fsv_sig b/authfs/testdata/input.4k.fsv_sig
new file mode 100644
index 0000000..247a297
--- /dev/null
+++ b/authfs/testdata/input.4k.fsv_sig
Binary files differ
diff --git a/authfs/testdata/input.4k1.fsv_sig b/authfs/testdata/input.4k1.fsv_sig
new file mode 100644
index 0000000..02f0056
--- /dev/null
+++ b/authfs/testdata/input.4k1.fsv_sig
Binary files differ
diff --git a/authfs/testdata/input.4m.fsv_sig b/authfs/testdata/input.4m.fsv_sig
new file mode 100644
index 0000000..12adca3
--- /dev/null
+++ b/authfs/testdata/input.4m.fsv_sig
Binary files differ
diff --git a/authfs/testdata/key.pem b/authfs/testdata/key.pem
new file mode 100644
index 0000000..c8520f8
--- /dev/null
+++ b/authfs/testdata/key.pem
@@ -0,0 +1,52 @@
+-----BEGIN PRIVATE KEY-----
+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
+wjfm06+rqBcJkr1MUXfmT0i3YEKL9uA=
+-----END PRIVATE KEY-----