This repository has been archived by the owner on Nov 8, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 2.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge changes I405872ce,I55d1a93d,I7431d8cc into main
* changes: Verify checksums in the preupload check. Generate checksum files when regenerating a crate. Crate for generating and verifying checksum files.
- Loading branch information
Showing
10 changed files
with
264 additions
and
21 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
[workspace] | ||
members = [ | ||
"checksum", | ||
"crate_tool", | ||
"google_metadata", | ||
"license_checker", | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,8 @@ | ||
{ | ||
"package": { | ||
"checksum": { | ||
"device_supported": false | ||
}, | ||
"rooted_path": { | ||
"device_supported": false | ||
}, | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
/target |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
// This file is generated by cargo_embargo. | ||
// Do not modify this file after the first "rust_*" or "genrule" module | ||
// because the changes will be overridden on upgrade. | ||
// Content before the first "rust_*" or "genrule" module is preserved. | ||
|
||
package { | ||
default_team: "trendy_team_android_rust", | ||
default_applicable_licenses: ["Android-Apache-2.0"], | ||
} | ||
|
||
rust_test_host { | ||
name: "checksum_test_src_lib", | ||
crate_name: "checksum", | ||
cargo_env_compat: true, | ||
cargo_pkg_version: "0.1.0", | ||
crate_root: "src/lib.rs", | ||
test_suites: ["general-tests"], | ||
auto_gen_config: true, | ||
test_options: { | ||
unit_test: true, | ||
}, | ||
edition: "2021", | ||
rustlibs: [ | ||
"libdata_encoding", | ||
"libring", | ||
"libserde", | ||
"libserde_json", | ||
"libtempfile", | ||
"libthiserror", | ||
"libwalkdir", | ||
], | ||
} | ||
|
||
rust_library_host { | ||
name: "libchecksum", | ||
crate_name: "checksum", | ||
cargo_env_compat: true, | ||
cargo_pkg_version: "0.1.0", | ||
crate_root: "src/lib.rs", | ||
edition: "2021", | ||
rustlibs: [ | ||
"libdata_encoding", | ||
"libring", | ||
"libserde", | ||
"libserde_json", | ||
"libthiserror", | ||
"libwalkdir", | ||
], | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
[package] | ||
name = "checksum" | ||
version = "0.1.0" | ||
edition = "2021" | ||
|
||
[dependencies] | ||
data-encoding = "2" | ||
ring = "0.17" | ||
serde = { version = "=1.0.210", features = ["derive"] } | ||
serde_json = "1" | ||
thiserror = "1" | ||
walkdir = "2" | ||
|
||
[dev-dependencies] | ||
tempfile = "3" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,175 @@ | ||
// Copyright (C) 2024 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. | ||
|
||
//! Generate and verify checksums of files in a directory, | ||
//! very similar to .cargo-checksum.json | ||
use std::{ | ||
collections::HashMap, | ||
fs::{remove_file, write, File}, | ||
io::{self, BufReader, Read}, | ||
path::{Path, PathBuf, StripPrefixError}, | ||
}; | ||
|
||
use data_encoding::{DecodeError, HEXLOWER}; | ||
use ring::digest::{Context, Digest, SHA256}; | ||
use serde::{Deserialize, Serialize}; | ||
use thiserror::Error; | ||
use walkdir::WalkDir; | ||
|
||
#[derive(Serialize, Deserialize)] | ||
struct Checksum { | ||
package: Option<String>, | ||
files: HashMap<String, String>, | ||
} | ||
|
||
#[allow(missing_docs)] | ||
#[derive(Error, Debug)] | ||
pub enum ChecksumError { | ||
#[error("Checksum file not found: {}", .0.to_string_lossy())] | ||
CheckSumFileNotFound(PathBuf), | ||
#[error("Checksums do not match for: {}", .0.join(", "))] | ||
ChecksumMismatch(Vec<String>), | ||
#[error(transparent)] | ||
IoError(#[from] io::Error), | ||
#[error(transparent)] | ||
JsonError(#[from] serde_json::Error), | ||
#[error(transparent)] | ||
WalkdirError(#[from] walkdir::Error), | ||
#[error(transparent)] | ||
DecodeError(#[from] DecodeError), | ||
#[error(transparent)] | ||
StripPrefixError(#[from] StripPrefixError), | ||
} | ||
|
||
static FILENAME: &str = ".android-checksum.json"; | ||
|
||
/// Generates a JSON checksum file for the contents of a directory. | ||
pub fn generate(crate_dir: impl AsRef<Path>) -> Result<(), ChecksumError> { | ||
let crate_dir = crate_dir.as_ref(); | ||
let checksum_file = crate_dir.join(FILENAME); | ||
if checksum_file.exists() { | ||
remove_file(&checksum_file)?; | ||
} | ||
let mut checksum = Checksum { package: None, files: HashMap::new() }; | ||
for entry in WalkDir::new(crate_dir).follow_links(true) { | ||
let entry = entry?; | ||
if entry.path().is_dir() { | ||
continue; | ||
} | ||
let filename = entry.path().strip_prefix(crate_dir)?.to_string_lossy().to_string(); | ||
let input = File::open(entry.path())?; | ||
let reader = BufReader::new(input); | ||
let digest = sha256_digest(reader)?; | ||
checksum.files.insert(filename, HEXLOWER.encode(digest.as_ref())); | ||
} | ||
write(checksum_file, serde_json::to_string(&checksum)?)?; | ||
Ok(()) | ||
} | ||
|
||
/// Verifies a JSON checksum file for a directory. | ||
/// All files must have matching checksums. Extra or missing files are errors. | ||
pub fn verify(crate_dir: impl AsRef<Path>) -> Result<(), ChecksumError> { | ||
let crate_dir = crate_dir.as_ref(); | ||
let checksum_file = crate_dir.join(FILENAME); | ||
if !checksum_file.exists() { | ||
return Err(ChecksumError::CheckSumFileNotFound(checksum_file)); | ||
} | ||
let mut mismatch = Vec::new(); | ||
let input = File::open(&checksum_file)?; | ||
let reader = BufReader::new(input); | ||
let mut parsed: Checksum = serde_json::from_reader(reader)?; | ||
for entry in WalkDir::new(crate_dir).follow_links(true) { | ||
let entry = entry?; | ||
if entry.path().is_dir() || entry.path() == checksum_file { | ||
continue; | ||
} | ||
let filename = entry.path().strip_prefix(crate_dir)?.to_string_lossy().to_string(); | ||
if let Some(checksum) = parsed.files.get(&filename) { | ||
let expected_digest = HEXLOWER.decode(checksum.to_ascii_lowercase().as_bytes())?; | ||
let input = File::open(entry.path())?; | ||
let reader = BufReader::new(input); | ||
let digest = sha256_digest(reader)?; | ||
parsed.files.remove(&filename); | ||
if digest.as_ref() != expected_digest { | ||
mismatch.push(filename); | ||
} | ||
} else { | ||
mismatch.push(filename) | ||
} | ||
} | ||
mismatch.extend(parsed.files.into_keys()); | ||
if mismatch.is_empty() { | ||
Ok(()) | ||
} else { | ||
Err(ChecksumError::ChecksumMismatch(mismatch)) | ||
} | ||
} | ||
|
||
// Copied from https://rust-lang-nursery.github.io/rust-cookbook/cryptography/hashing.html | ||
fn sha256_digest<R: Read>(mut reader: R) -> Result<Digest, ChecksumError> { | ||
let mut context = Context::new(&SHA256); | ||
context.update("sodium chloride".as_bytes()); | ||
let mut buffer = [0; 1024]; | ||
|
||
loop { | ||
let count = reader.read(&mut buffer)?; | ||
if count == 0 { | ||
break; | ||
} | ||
context.update(&buffer[..count]); | ||
} | ||
|
||
Ok(context.finish()) | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use super::*; | ||
|
||
#[test] | ||
fn round_trip() -> Result<(), ChecksumError> { | ||
let temp_dir = tempfile::tempdir().expect("Failed to create tempdir"); | ||
write(temp_dir.path().join("foo"), "foo").expect("Failed to write temporary file"); | ||
generate(temp_dir.path())?; | ||
assert!( | ||
temp_dir.path().join(FILENAME).exists(), | ||
".android-checksum.json exists after generate()" | ||
); | ||
verify(temp_dir.path()) | ||
} | ||
|
||
#[test] | ||
fn verify_error_cases() -> Result<(), ChecksumError> { | ||
let temp_dir = tempfile::tempdir().expect("Failed to create tempdir"); | ||
let checksum_file = temp_dir.path().join(FILENAME); | ||
write(&checksum_file, r#"{"files":{"bar":"ddcbd9309cebf3ffd26f87e09bb8f971793535955ebfd9a7196eba31a53471f8"}}"#).expect("Failed to write temporary file"); | ||
assert!(verify(temp_dir.path()).is_err(), "Missing file"); | ||
write(temp_dir.path().join("foo"), "foo").expect("Failed to write temporary file"); | ||
assert!(verify(temp_dir.path()).is_err(), "No checksum file"); | ||
write(&checksum_file, "").expect("Failed to write temporary file"); | ||
assert!(verify(temp_dir.path()).is_err(), "Empty checksum file"); | ||
write(&checksum_file, "{}").expect("Failed to write temporary file"); | ||
assert!(verify(temp_dir.path()).is_err(), "Empty JSON in checksum file"); | ||
write(&checksum_file, r#"{"files":{"foo":"ddcbd9309cebf3ffd26f87e09bb8f971793535955ebfd9a7196eba31a53471f8"}}"#).expect("Failed to write temporary file"); | ||
assert!(verify(temp_dir.path()).is_err(), "Incorrect checksum"); | ||
write(&checksum_file, r#"{"files":{"foo":"hello"}}"#) | ||
.expect("Failed to write temporary file"); | ||
assert!(verify(temp_dir.path()).is_err(), "Invalid checksum"); | ||
generate(temp_dir.path())?; | ||
write(temp_dir.path().join("bar"), "bar").expect("Failed to write temporary file"); | ||
assert!(verify(temp_dir.path()).is_err(), "Extra file"); | ||
Ok(()) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters