Merge "Revert "open_then_run: open directory as path fd""
diff --git a/authfs/src/file/dir.rs b/authfs/src/file/dir.rs
index 7f26fd7..f3cc6f8 100644
--- a/authfs/src/file/dir.rs
+++ b/authfs/src/file/dir.rs
@@ -17,18 +17,20 @@
use log::warn;
use nix::sys::stat::Mode;
use std::collections::{hash_map, HashMap};
+use std::ffi::{CString, OsString};
use std::io;
+use std::os::unix::ffi::OsStringExt;
use std::path::{Path, PathBuf};
use super::attr::Attr;
use super::remote_file::RemoteFileEditor;
use super::{validate_basename, VirtFdService, VirtFdServiceStatus};
use crate::fsverity::VerifiedFileEditor;
-use crate::fusefs::Inode;
+use crate::fusefs::{AuthFsDirEntry, Inode};
const MAX_ENTRIES: u16 = 100; // Arbitrary limit
-struct DirEntry {
+struct InodeInfo {
inode: Inode,
// This information is duplicated since it is also available in `AuthFs::inode_table` via the
@@ -58,7 +60,7 @@
/// Mapping of entry names to the corresponding inode. The actual file/directory is stored in
/// the global pool in fusefs.
- entries: HashMap<PathBuf, DirEntry>,
+ entries: HashMap<PathBuf, InodeInfo>,
}
impl RemoteDirEditor {
@@ -88,7 +90,7 @@
let new_remote_file =
VerifiedFileEditor::new(RemoteFileEditor::new(self.service.clone(), new_fd));
- self.entries.insert(basename.to_path_buf(), DirEntry { inode, is_dir: false });
+ self.entries.insert(basename.to_path_buf(), InodeInfo { inode, is_dir: false });
let new_attr = Attr::new_file_with_mode(self.service.clone(), new_fd, mode);
Ok((new_remote_file, new_attr))
}
@@ -110,7 +112,7 @@
.map_err(into_io_error)?;
let new_remote_dir = RemoteDirEditor::new(self.service.clone(), new_fd);
- self.entries.insert(basename.to_path_buf(), DirEntry { inode, is_dir: true });
+ self.entries.insert(basename.to_path_buf(), InodeInfo { inode, is_dir: true });
let new_attr = Attr::new_dir_with_mode(self.service.clone(), new_fd, mode);
Ok((new_remote_dir, new_attr))
}
@@ -156,6 +158,15 @@
self.entries.contains_key(name)
}
+ pub fn retrieve_entries(&self) -> io::Result<Vec<AuthFsDirEntry>> {
+ self.entries
+ .iter()
+ .map(|(name, InodeInfo { inode, is_dir })| {
+ Ok(AuthFsDirEntry { inode: *inode, name: path_to_cstring(name)?, is_dir: *is_dir })
+ })
+ .collect::<io::Result<Vec<_>>>()
+ }
+
fn force_delete_entry(&mut self, basename: &Path, expect_dir: bool) -> io::Result<Inode> {
// Kernel should only give us a basename.
debug_assert!(validate_basename(basename).is_ok());
@@ -192,7 +203,7 @@
}
/// An in-memory directory representation of a directory structure.
-pub struct InMemoryDir(HashMap<PathBuf, Inode>);
+pub struct InMemoryDir(HashMap<PathBuf, InodeInfo>);
impl InMemoryDir {
/// Creates an empty instance of `InMemoryDir`.
@@ -206,16 +217,26 @@
self.0.len() as u16 // limited to MAX_ENTRIES
}
- /// Adds an entry (name and the inode number) to the directory. Fails if already exists. The
+ /// Adds a directory name and its inode number to the directory. Fails if already exists. The
/// caller is responsible for ensure the inode uniqueness.
- pub fn add_entry(&mut self, basename: &Path, inode: Inode) -> io::Result<()> {
+ pub fn add_dir(&mut self, basename: &Path, inode: Inode) -> io::Result<()> {
+ self.add_entry(basename, InodeInfo { inode, is_dir: true })
+ }
+
+ /// Adds a file name and its inode number to the directory. Fails if already exists. The
+ /// caller is responsible for ensure the inode uniqueness.
+ pub fn add_file(&mut self, basename: &Path, inode: Inode) -> io::Result<()> {
+ self.add_entry(basename, InodeInfo { inode, is_dir: false })
+ }
+
+ fn add_entry(&mut self, basename: &Path, dir_entry: InodeInfo) -> io::Result<()> {
validate_basename(basename)?;
if self.0.len() >= MAX_ENTRIES.into() {
return Err(io::Error::from_raw_os_error(libc::EMLINK));
}
if let hash_map::Entry::Vacant(entry) = self.0.entry(basename.to_path_buf()) {
- entry.insert(inode);
+ entry.insert(dir_entry);
Ok(())
} else {
Err(io::Error::from_raw_os_error(libc::EEXIST))
@@ -224,8 +245,22 @@
/// Looks up an entry inode by name. `None` if not found.
pub fn lookup_inode(&self, basename: &Path) -> Option<Inode> {
- self.0.get(basename).copied()
+ self.0.get(basename).map(|entry| entry.inode)
}
+
+ pub fn retrieve_entries(&self) -> io::Result<Vec<AuthFsDirEntry>> {
+ self.0
+ .iter()
+ .map(|(name, InodeInfo { inode, is_dir })| {
+ Ok(AuthFsDirEntry { inode: *inode, name: path_to_cstring(name)?, is_dir: *is_dir })
+ })
+ .collect::<io::Result<Vec<_>>>()
+ }
+}
+
+fn path_to_cstring(path: &Path) -> io::Result<CString> {
+ let bytes = OsString::from(path).into_vec();
+ CString::new(bytes).map_err(|_| io::Error::from_raw_os_error(libc::EILSEQ))
}
fn into_io_error(e: VirtFdServiceStatus) -> io::Error {
diff --git a/authfs/src/fusefs.rs b/authfs/src/fusefs.rs
index 85a8371..cbd24a9 100644
--- a/authfs/src/fusefs.rs
+++ b/authfs/src/fusefs.rs
@@ -17,24 +17,24 @@
mod mount;
use anyhow::{anyhow, bail, Result};
+use fuse::filesystem::{
+ Context, DirEntry, DirectoryIterator, Entry, FileSystem, FsOptions, GetxattrReply,
+ SetattrValid, ZeroCopyReader, ZeroCopyWriter,
+};
+use fuse::sys::OpenOptions as FuseOpenOptions;
use log::{debug, error, warn};
use std::collections::{btree_map, BTreeMap};
-use std::convert::TryFrom;
-use std::ffi::{CStr, OsStr};
+use std::convert::{TryFrom, TryInto};
+use std::ffi::{CStr, CString, OsStr};
use std::io;
use std::mem::{zeroed, MaybeUninit};
use std::option::Option;
use std::os::unix::ffi::OsStrExt;
use std::path::{Component, Path, PathBuf};
use std::sync::atomic::{AtomicU64, Ordering};
-use std::sync::Mutex;
+use std::sync::{Arc, Mutex};
use std::time::Duration;
-use fuse::filesystem::{
- Context, DirEntry, DirectoryIterator, Entry, FileSystem, FsOptions, GetxattrReply,
- SetattrValid, ZeroCopyReader, ZeroCopyWriter,
-};
-
use crate::common::{divide_roundup, ChunkedSizeIter, CHUNK_SIZE};
use crate::file::{
validate_basename, Attr, InMemoryDir, RandomWrite, ReadByChunk, RemoteDirEditor,
@@ -119,6 +119,69 @@
}
}
+/// Data type that a directory implementation should be able to present its entry to `AuthFs`.
+#[derive(Clone)]
+pub struct AuthFsDirEntry {
+ pub inode: Inode,
+ pub name: CString,
+ pub is_dir: bool,
+}
+
+/// A snapshot of a directory entries for supporting `readdir` operation.
+///
+/// The `readdir` implementation is required by FUSE to not return any entries that have been
+/// returned previously (while it's fine to not return new entries). Snapshot is the easiest way to
+/// be compliant. See `fuse::filesystem::readdir` for more details.
+///
+/// A `DirEntriesSnapshot` is created on `opendir`, and is associated with the returned
+/// `Handle`/FD. The snapshot is deleted when the handle is released in `releasedir`.
+type DirEntriesSnapshot = Vec<AuthFsDirEntry>;
+
+/// An iterator for reading from `DirEntriesSnapshot`.
+pub struct DirEntriesSnapshotIterator {
+ /// A reference to the `DirEntriesSnapshot` in `AuthFs`.
+ snapshot: Arc<DirEntriesSnapshot>,
+
+ /// A value determined by `Self` to identify the last entry. 0 is a reserved value by FUSE to
+ /// mean reading from the beginning.
+ prev_offset: usize,
+}
+
+impl<'a> DirectoryIterator for DirEntriesSnapshotIterator {
+ fn next(&mut self) -> Option<DirEntry> {
+ // This iterator should not be the only reference to the snapshot. The snapshot should
+ // still be hold in `dir_handle_table`, i.e. when the FD is not yet closed.
+ //
+ // This code is unreachable when `readdir` is called with a closed FD. Only when the FD is
+ // not yet closed, `DirEntriesSnapshotIterator` can be created (but still short-lived
+ // during `readdir`).
+ debug_assert!(Arc::strong_count(&self.snapshot) >= 2);
+
+ // Since 0 is reserved, let's use 1-based index for the offset. This allows us to
+ // resume from the previous read in the snapshot easily.
+ let current_offset = if self.prev_offset == 0 {
+ 1 // first element in the vector
+ } else {
+ self.prev_offset + 1 // next element in the vector
+ };
+ if current_offset > self.snapshot.len() {
+ None
+ } else {
+ let AuthFsDirEntry { inode, name, is_dir } = &self.snapshot[current_offset - 1];
+ let entry = DirEntry {
+ offset: current_offset as u64,
+ ino: *inode,
+ name,
+ type_: if *is_dir { libc::DT_DIR.into() } else { libc::DT_REG.into() },
+ };
+ self.prev_offset = current_offset;
+ Some(entry)
+ }
+ }
+}
+
+type DirHandleTable = BTreeMap<Handle, Arc<DirEntriesSnapshot>>;
+
// AuthFS needs to be `Sync` to be accepted by fuse::worker::start_message_loop as a `FileSystem`.
pub struct AuthFs {
/// Table for `Inode` to `InodeState` lookup. This needs to be `Sync` to be used in
@@ -128,6 +191,18 @@
/// The next available inode number.
next_inode: AtomicU64,
+ /// Table for `Handle` to `Arc<DirEntriesSnapshot>` lookup. On `opendir`, a new directory handle
+ /// is created and the snapshot of the current directory is created. This is not super
+ /// efficient, but is the simplest way to be compliant to the FUSE contract (see
+ /// `fuse::filesystem::readdir`).
+ ///
+ /// Currently, no code locks `dir_handle_table` and `inode_table` at the same time to avoid
+ /// deadlock.
+ dir_handle_table: Mutex<DirHandleTable>,
+
+ /// The next available handle number.
+ next_handle: AtomicU64,
+
/// A reader to access the remote filesystem stats, which is supposed to be of "the" output
/// directory. We assume all output are stored in the same partition.
remote_fs_stats_reader: RemoteFsStatsReader,
@@ -147,6 +222,8 @@
AuthFs {
inode_table: Mutex::new(inode_table),
next_inode: AtomicU64::new(ROOT_INODE + 1),
+ dir_handle_table: Mutex::new(BTreeMap::new()),
+ next_handle: AtomicU64::new(1),
remote_fs_stats_reader,
}
}
@@ -199,7 +276,7 @@
AuthFsEntry::ReadonlyDirectory { dir: InMemoryDir::new() };
// Actually update the tables.
- dir.add_entry(name.as_ref(), new_inode)?;
+ dir.add_dir(name.as_ref(), new_inode)?;
if inode_table
.insert(new_inode, InodeState::new(new_dir_entry))
.is_some()
@@ -223,7 +300,7 @@
let new_inode = self.next_inode.fetch_add(1, Ordering::Relaxed);
// Actually update the tables.
- dir.add_entry(basename.as_ref(), new_inode)?;
+ dir.add_file(basename.as_ref(), new_inode)?;
if inode_table.insert(new_inode, InodeState::new(entry)).is_some() {
bail!("Unexpected to find a duplicated inode");
}
@@ -283,6 +360,20 @@
unreachable!("Unexpected duplication of inode {}", new_inode);
}
}
+
+ fn open_dir_store_snapshot(
+ &self,
+ dir_entries: Vec<AuthFsDirEntry>,
+ ) -> io::Result<(Option<Handle>, FuseOpenOptions)> {
+ let handle = self.next_handle.fetch_add(1, Ordering::Relaxed);
+ let mut dir_handle_table = self.dir_handle_table.lock().unwrap();
+ if let btree_map::Entry::Vacant(value) = dir_handle_table.entry(handle) {
+ value.insert(Arc::new(dir_entries));
+ Ok((Some(handle), FuseOpenOptions::empty()))
+ } else {
+ unreachable!("Unexpected to see new handle {} to existing in the table", handle);
+ }
+ }
}
fn check_access_mode(flags: u32, mode: libc::c_int) -> io::Result<()> {
@@ -402,19 +493,10 @@
Ok(total)
}
-// TODO(205715172): Support enumerating directory entries.
-pub struct EmptyDirectoryIterator {}
-
-impl DirectoryIterator for EmptyDirectoryIterator {
- fn next(&mut self) -> Option<DirEntry> {
- None
- }
-}
-
impl FileSystem for AuthFs {
type Inode = Inode;
type Handle = Handle;
- type DirIter = EmptyDirectoryIterator;
+ type DirIter = DirEntriesSnapshotIterator;
fn max_buffer_size(&self) -> u32 {
MAX_WRITE_BYTES
@@ -546,7 +628,7 @@
_ctx: Context,
inode: Self::Inode,
flags: u32,
- ) -> io::Result<(Option<Self::Handle>, fuse::sys::OpenOptions)> {
+ ) -> io::Result<(Option<Self::Handle>, FuseOpenOptions)> {
// Since file handle is not really used in later operations (which use Inode directly),
// return None as the handle.
self.handle_inode(&inode, |config| {
@@ -566,7 +648,7 @@
}
// Always cache the file content. There is currently no need to support direct I/O or
// avoid the cache buffer. Memory mapping is only possible with cache enabled.
- Ok((None, fuse::sys::OpenOptions::KEEP_CACHE))
+ Ok((None, FuseOpenOptions::KEEP_CACHE))
})
}
@@ -578,7 +660,7 @@
mode: u32,
_flags: u32,
umask: u32,
- ) -> io::Result<(Entry, Option<Self::Handle>, fuse::sys::OpenOptions)> {
+ ) -> io::Result<(Entry, Option<Self::Handle>, FuseOpenOptions)> {
// TODO(205172873): handle O_TRUNC and O_EXCL properly.
let new_inode = self.create_new_entry_with_ref_count(
parent,
@@ -606,7 +688,7 @@
},
// See also `open`.
/* handle */ None,
- fuse::sys::OpenOptions::KEEP_CACHE,
+ FuseOpenOptions::KEEP_CACHE,
))
}
@@ -853,6 +935,53 @@
)
}
+ fn opendir(
+ &self,
+ _ctx: Context,
+ inode: Self::Inode,
+ _flags: u32,
+ ) -> io::Result<(Option<Self::Handle>, FuseOpenOptions)> {
+ let entries = self.handle_inode(&inode, |config| match config {
+ AuthFsEntry::VerifiedNewDirectory { dir, .. } => dir.retrieve_entries(),
+ AuthFsEntry::ReadonlyDirectory { dir } => dir.retrieve_entries(),
+ _ => Err(io::Error::from_raw_os_error(libc::ENOTDIR)),
+ })?;
+ self.open_dir_store_snapshot(entries)
+ }
+
+ fn readdir(
+ &self,
+ _ctx: Context,
+ _inode: Self::Inode,
+ handle: Self::Handle,
+ _size: u32,
+ offset: u64,
+ ) -> io::Result<Self::DirIter> {
+ let dir_handle_table = self.dir_handle_table.lock().unwrap();
+ if let Some(entry) = dir_handle_table.get(&handle) {
+ Ok(DirEntriesSnapshotIterator {
+ snapshot: entry.clone(),
+ prev_offset: offset.try_into().unwrap(),
+ })
+ } else {
+ Err(io::Error::from_raw_os_error(libc::EBADF))
+ }
+ }
+
+ fn releasedir(
+ &self,
+ _ctx: Context,
+ inode: Self::Inode,
+ _flags: u32,
+ handle: Self::Handle,
+ ) -> io::Result<()> {
+ let mut dir_handle_table = self.dir_handle_table.lock().unwrap();
+ if dir_handle_table.remove(&handle).is_none() {
+ unreachable!("Unknown directory handle {}, inode {}", handle, inode);
+ }
+ Ok(())
+ }
+
fn statfs(&self, _ctx: Context, _inode: Self::Inode) -> io::Result<libc::statvfs64> {
let remote_stat = self.remote_fs_stats_reader.statfs()?;
diff --git a/authfs/tests/java/src/com/android/fs/AuthFsHostTest.java b/authfs/tests/java/src/com/android/fs/AuthFsHostTest.java
index 494b082..101a349 100644
--- a/authfs/tests/java/src/com/android/fs/AuthFsHostTest.java
+++ b/authfs/tests/java/src/com/android/fs/AuthFsHostTest.java
@@ -519,6 +519,47 @@
}
@Test
+ public void testReadOutputDirectory() throws Exception {
+ // Setup
+ runFdServerOnAndroid("--open-dir 3:" + TEST_OUTPUT_DIR, "--rw-dirs 3");
+ runAuthFsOnMicrodroid("--remote-new-rw-dir 3 --cid " + VMADDR_CID_HOST);
+
+ // Action
+ String authfsOutputDir = MOUNT_DIR + "/3";
+ runOnMicrodroid("mkdir -p " + authfsOutputDir + "/dir/dir2/dir3");
+ runOnMicrodroid("touch " + authfsOutputDir + "/dir/dir2/dir3/file1");
+ runOnMicrodroid("touch " + authfsOutputDir + "/dir/dir2/dir3/file2");
+ runOnMicrodroid("touch " + authfsOutputDir + "/dir/dir2/dir3/file3");
+ runOnMicrodroid("touch " + authfsOutputDir + "/file");
+
+ // Verify
+ String[] actual = runOnMicrodroid("cd " + authfsOutputDir + "; find |sort").split("\n");
+ String[] expected = new String[] {
+ ".",
+ "./dir",
+ "./dir/dir2",
+ "./dir/dir2/dir3",
+ "./dir/dir2/dir3/file1",
+ "./dir/dir2/dir3/file2",
+ "./dir/dir2/dir3/file3",
+ "./file"};
+ assertEquals(expected, actual);
+
+ // Add more entries.
+ runOnMicrodroid("mkdir -p " + authfsOutputDir + "/dir2");
+ runOnMicrodroid("touch " + authfsOutputDir + "/file2");
+ // Check new entries. Also check that the types are correct.
+ actual = runOnMicrodroid(
+ "cd " + authfsOutputDir + "; find -maxdepth 1 -type f |sort").split("\n");
+ expected = new String[] {"./file", "./file2"};
+ assertEquals(expected, actual);
+ actual = runOnMicrodroid(
+ "cd " + authfsOutputDir + "; find -maxdepth 1 -type d |sort").split("\n");
+ expected = new String[] {".", "./dir", "./dir2"};
+ assertEquals(expected, actual);
+ }
+
+ @Test
public void testChmod_File() throws Exception {
// Setup
runFdServerOnAndroid("--open-rw 3:" + TEST_OUTPUT_DIR + "/file", "--rw-fds 3");
diff --git a/compos/Android.bp b/compos/Android.bp
index b9fcfff..783ba22 100644
--- a/compos/Android.bp
+++ b/compos/Android.bp
@@ -50,6 +50,8 @@
"liblog_rust",
"libminijail_rust",
"libnix",
+ "libodsign_proto_rust",
+ "libprotobuf",
"libring",
"libscopeguard",
],
diff --git a/compos/compos_key_cmd/Android.bp b/compos/compos_key_cmd/Android.bp
index 75ea090..9d5a490 100644
--- a/compos/compos_key_cmd/Android.bp
+++ b/compos/compos_key_cmd/Android.bp
@@ -8,7 +8,6 @@
apex_available: ["com.android.compos"],
static_libs: [
- "lib_compos_proto",
"lib_odsign_proto",
],
diff --git a/compos/compos_key_cmd/compos_key_cmd.cpp b/compos/compos_key_cmd/compos_key_cmd.cpp
index 159e9e9..f8b3d16 100644
--- a/compos/compos_key_cmd/compos_key_cmd.cpp
+++ b/compos/compos_key_cmd/compos_key_cmd.cpp
@@ -45,7 +45,6 @@
#include <string_view>
#include <thread>
-#include "compos_signature.pb.h"
#include "odsign_info.pb.h"
using namespace std::literals;
@@ -65,7 +64,6 @@
using android::base::Result;
using android::base::unique_fd;
using android::base::WriteFully;
-using compos::proto::Signature;
using ndk::ScopedAStatus;
using ndk::ScopedFileDescriptor;
using ndk::SharedRefBase;
@@ -441,77 +439,6 @@
return std::vector(&digest->digest[0], &digest->digest[digest->digest_size]);
}
-static Result<void> signFile(ICompOsService* service, const std::string& file) {
- std::filesystem::path signature_path{file};
- signature_path += ".signature";
- unique_fd out_fd(TEMP_FAILURE_RETRY(open(signature_path.c_str(),
- O_CREAT | O_WRONLY | O_TRUNC | O_CLOEXEC,
- S_IRUSR | S_IWUSR | S_IRGRP)));
- if (!out_fd.ok()) {
- return ErrnoError() << "Unable to create signature file";
- }
-
- auto digest = computeDigest(file);
- if (!digest.ok()) {
- return digest.error();
- }
-
- std::vector<uint8_t> buffer(sizeof(fsverity_formatted_digest) + digest->size());
- auto to_be_signed = new (buffer.data()) fsverity_formatted_digest;
- memcpy(to_be_signed->magic, "FSVerity", sizeof(to_be_signed->magic));
- to_be_signed->digest_algorithm = __cpu_to_le16(FS_VERITY_HASH_ALG_SHA256);
- to_be_signed->digest_size = __cpu_to_le16(digest->size());
- memcpy(to_be_signed->digest, digest->data(), digest->size());
-
- std::vector<uint8_t> signature;
- auto status = service->sign(buffer, &signature);
- if (!status.isOk()) {
- return Error() << "Failed to sign: " << status.getDescription();
- }
-
- Signature compos_signature;
- compos_signature.set_digest(digest->data(), digest->size());
- compos_signature.set_signature(signature.data(), signature.size());
- if (!compos_signature.SerializeToFileDescriptor(out_fd.get())) {
- return Error() << "Failed to write signature";
- }
- if (close(out_fd.release()) != 0) {
- return ErrnoError() << "Failed to close signature file";
- }
-
- return {};
-}
-
-static Result<void> sign(TargetVm& vm, const std::string& blob_file,
- const std::vector<std::string>& files) {
- auto cid = vm.resolveCid();
- if (!cid.ok()) {
- return cid.error();
- }
- auto service = getService(*cid);
- if (!service) {
- return Error() << "No service";
- }
-
- auto blob = readBytesFromFile(blob_file);
- if (!blob.ok()) {
- return blob.error();
- }
-
- auto status = service->initializeSigningKey(blob.value());
- if (!status.isOk()) {
- return Error() << "Failed to initialize signing key: " << status.getDescription();
- }
-
- for (auto& file : files) {
- auto result = signFile(service.get(), file);
- if (!result.ok()) {
- return Error() << result.error() << ": " << file;
- }
- }
- return {};
-}
-
static std::string toHex(const std::vector<uint8_t>& digest) {
std::stringstream ss;
for (auto it = digest.begin(); it != digest.end(); ++it) {
@@ -701,15 +628,6 @@
} else {
std::cerr << result.error() << '\n';
}
- } else if (argc >= 4 && argv[1] == "sign"sv) {
- const std::vector<std::string> files{&argv[3], &argv[argc]};
- auto result = sign(vm, argv[2], files);
- if (result.ok()) {
- std::cerr << "All signatures generated.\n";
- return 0;
- } else {
- std::cerr << result.error() << '\n';
- }
} else if (argc >= 5 && argv[1] == "sign-info"sv) {
const std::string blob_file = argv[2];
const std::string info_file = argv[3];
@@ -744,9 +662,6 @@
<< " verify <blob file> <public key file> Verify that the content of the\n"
<< " specified private key blob and public key files are valid.\n "
<< " init-key <blob file> Initialize the service key.\n"
- << " sign <blob file> <files to be signed> Generate signatures for one or\n"
- << " more files using the supplied private key blob. Signature is stored \n"
- << " in <filename>.signature\n"
<< " sign-info <blob file> <info file> <files to be signed> Generate\n"
<< " an info file listing the paths and root digests of each of the files to\n"
<< " be signed, along with a signature of that file.\n"
diff --git a/compos/src/artifact_signer.rs b/compos/src/artifact_signer.rs
new file mode 100644
index 0000000..54b8d7a
--- /dev/null
+++ b/compos/src/artifact_signer.rs
@@ -0,0 +1,94 @@
+/*
+ * 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.
+ */
+
+//! Support for generating and signing an info file listing names and digests of generated
+//! artifacts.
+
+#![allow(dead_code)] // Will be used soon
+
+use crate::compos_key_service::CompOsKeyService;
+use crate::fsverity;
+use anyhow::{anyhow, Context, Result};
+use odsign_proto::odsign_info::OdsignInfo;
+use protobuf::Message;
+use std::fs::File;
+use std::io::Write;
+use std::os::unix::io::AsRawFd;
+use std::path::{Path, PathBuf};
+
+const TARGET_DIRECTORY: &str = "/data/misc/apexdata/com.android.art/dalvik-cache";
+const SIGNATURE_EXTENSION: &str = ".signature";
+
+/// Accumulates and then signs information about generated artifacts.
+pub struct ArtifactSigner<'a> {
+ key_blob: Vec<u8>,
+ key_service: &'a CompOsKeyService,
+ base_directory: PathBuf,
+ file_digests: Vec<(String, String)>, // (File name, digest in hex)
+}
+
+impl<'a> ArtifactSigner<'a> {
+ /// base_directory specifies the directory under which the artifacts are currently located;
+ /// they will eventually be moved under TARGET_DIRECTORY once they are verified and activated.
+ pub fn new(
+ key_blob: Vec<u8>,
+ key_service: &'a CompOsKeyService,
+ base_directory: PathBuf,
+ ) -> Self {
+ ArtifactSigner { key_blob, key_service, base_directory, file_digests: Vec::new() }
+ }
+
+ pub fn add_artifact(&mut self, path: PathBuf) -> Result<()> {
+ // The path we store is where the file will be when it is verified, not where it is now.
+ let suffix = path
+ .strip_prefix(&self.base_directory)
+ .context("Artifacts must be under base directory")?;
+ let target_path = Path::new(TARGET_DIRECTORY).join(suffix);
+ let target_path = target_path.to_str().ok_or_else(|| anyhow!("Invalid path"))?;
+
+ let file = File::open(&path)?;
+ let digest = fsverity::measure(file.as_raw_fd())?;
+ let digest = to_hex_string(&digest);
+
+ self.file_digests.push((target_path.to_owned(), digest));
+ Ok(())
+ }
+
+ /// Consume this ArtifactSigner and write details of all its artifacts to the given path,
+ /// with accompanying sigature file.
+ pub fn write_info_and_signature(self, info_path: &Path) -> Result<()> {
+ let mut info = OdsignInfo::new();
+ info.mut_file_hashes().extend(self.file_digests.into_iter());
+ let bytes = info.write_to_bytes()?;
+
+ let signature = self.key_service.sign(&self.key_blob, &bytes)?;
+
+ let mut file = File::create(info_path)?;
+ file.write_all(&bytes)?;
+
+ let mut signature_name = info_path.file_name().unwrap().to_owned();
+ signature_name.push(SIGNATURE_EXTENSION);
+ let signature_path = info_path.with_file_name(&signature_name);
+ let mut signature_file = File::create(&signature_path)?;
+ signature_file.write_all(&signature)?;
+
+ Ok(())
+ }
+}
+
+fn to_hex_string(buf: &[u8]) -> String {
+ buf.iter().map(|b| format!("{:02x}", b)).collect()
+}
diff --git a/compos/src/compos_key_service.rs b/compos/src/compos_key_service.rs
index 4a1566d..f6caac9 100644
--- a/compos/src/compos_key_service.rs
+++ b/compos/src/compos_key_service.rs
@@ -77,7 +77,7 @@
})
}
- pub fn do_generate(&self) -> Result<CompOsKeyData> {
+ 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];
@@ -97,11 +97,11 @@
}
}
- pub fn do_verify(&self, key_blob: &[u8], public_key: &[u8]) -> Result<()> {
+ 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.do_sign(key_blob, &data)?;
+ let signature = self.sign(key_blob, &data)?;
let public_key =
signature::UnparsedPublicKey::new(&signature::RSA_PKCS1_2048_8192_SHA256, public_key);
@@ -110,7 +110,7 @@
Ok(())
}
- pub fn do_sign(&self, key_blob: &[u8], data: &[u8]) -> Result<Vec<u8>> {
+ pub fn sign(&self, key_blob: &[u8], data: &[u8]) -> Result<Vec<u8>> {
let key_descriptor = KeyDescriptor { blob: Some(key_blob.to_vec()), ..BLOB_KEY_DESCRIPTOR };
let operation_parameters = [PURPOSE_SIGN, ALGORITHM, PADDING, DIGEST];
let forced = false;
diff --git a/compos/src/compsvc.rs b/compos/src/compsvc.rs
index 0a15876..19f2f47 100644
--- a/compos/src/compsvc.rs
+++ b/compos/src/compsvc.rs
@@ -24,7 +24,7 @@
use std::default::Default;
use std::env;
use std::path::PathBuf;
-use std::sync::{Arc, RwLock};
+use std::sync::RwLock;
use crate::compilation::{compile_cmd, odrefresh, CompilerOutput};
use crate::compos_key_service::CompOsKeyService;
@@ -50,7 +50,7 @@
dex2oat_path: PathBuf::from(DEX2OAT_PATH),
odrefresh_path: PathBuf::from(ODREFRESH_PATH),
key_service: CompOsKeyService::new()?,
- key_blob: Arc::new(RwLock::new(Vec::new())),
+ key_blob: RwLock::new(Vec::new()),
};
Ok(BnCompOsService::new_binder(service, BinderFeatures::default()))
}
@@ -59,7 +59,7 @@
dex2oat_path: PathBuf,
odrefresh_path: PathBuf,
key_service: CompOsKeyService,
- key_blob: Arc<RwLock<Vec<u8>>>,
+ key_blob: RwLock<Vec<u8>>,
}
impl CompOsService {
@@ -69,7 +69,7 @@
fsverity_digest: &fsverity::Sha256Digest,
) -> Vec<u8> {
let formatted_digest = fsverity::to_formatted_digest(fsverity_digest);
- self.key_service.do_sign(key_blob, &formatted_digest[..]).unwrap_or_else(|e| {
+ self.key_service.sign(key_blob, &formatted_digest[..]).unwrap_or_else(|e| {
warn!("Failed to sign the fsverity digest, returning empty signature. Error: {}", e);
Vec::new()
})
@@ -189,12 +189,12 @@
fn generateSigningKey(&self) -> BinderResult<CompOsKeyData> {
self.key_service
- .do_generate()
+ .generate()
.map_err(|e| new_binder_exception(ExceptionCode::ILLEGAL_STATE, e.to_string()))
}
fn verifySigningKey(&self, key_blob: &[u8], public_key: &[u8]) -> BinderResult<bool> {
- Ok(if let Err(e) = self.key_service.do_verify(key_blob, public_key) {
+ Ok(if let Err(e) = self.key_service.verify(key_blob, public_key) {
warn!("Signing key verification failed: {}", e.to_string());
false
} else {
@@ -208,7 +208,7 @@
Err(new_binder_exception(ExceptionCode::ILLEGAL_STATE, "Key is not initialized"))
} else {
self.key_service
- .do_sign(key, data)
+ .sign(key, data)
.map_err(|e| new_binder_exception(ExceptionCode::ILLEGAL_STATE, e.to_string()))
}
}
diff --git a/compos/src/compsvc_main.rs b/compos/src/compsvc_main.rs
index fc00039..9347905 100644
--- a/compos/src/compsvc_main.rs
+++ b/compos/src/compsvc_main.rs
@@ -16,11 +16,11 @@
//! A tool to start a standalone compsvc server that serves over RPC binder.
+mod artifact_signer;
mod compilation;
mod compos_key_service;
mod compsvc;
mod fsverity;
-mod signer;
use android_system_virtualmachineservice::{
aidl::android::system::virtualmachineservice::IVirtualMachineService::{
diff --git a/compos/src/signer.rs b/compos/src/signer.rs
deleted file mode 100644
index 9ff1477..0000000
--- a/compos/src/signer.rs
+++ /dev/null
@@ -1,23 +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.
- */
-
-use anyhow::Result;
-
-/// Provides the ability to cryptographically sign messages.
-pub trait Signer: Send + Sync {
- /// Sign the supplied data. The result is a raw signature over the input data.
- fn sign(&self, data: &[u8]) -> Result<Vec<u8>>;
-}
diff --git a/compos/tests/java/android/compos/test/ComposKeyTestCase.java b/compos/tests/java/android/compos/test/ComposKeyTestCase.java
index 140f74b..eacf3fb 100644
--- a/compos/tests/java/android/compos/test/ComposKeyTestCase.java
+++ b/compos/tests/java/android/compos/test/ComposKeyTestCase.java
@@ -141,12 +141,13 @@
android.run(
COMPOS_KEY_CMD_BIN,
"--cid " + mCid,
- "sign",
+ "sign-info",
TEST_ROOT + "test_key3.blob",
+ TEST_ROOT + "test.info",
"/data/local/tmp/something.txt");
// Check existence of the output signature - should succeed
- android.run("test -f /data/local/tmp/something.txt.signature");
+ android.run("test -f " + TEST_ROOT + "test.info.signature");
}
private void startVm() throws Exception {