- Pack signature accepts optional salt, writes 16-byte salt between header and TOC - Set flags bit 4 and adjust toc_offset to 56 when salt present - read_archive_metadata returns salt alongside header and TOC entries - Add read_archive_salt() public helper for pre-unpack salt reading - main.rs uses resolve_key_for_pack/resolve_key_for_unpack for two-phase password flow - Add 5 new integration tests: password roundtrip, wrong password rejection, salt flag presence, no-salt flag for key archives, directory password roundtrip - All 52 tests pass (25 unit + 7 golden + 20 integration)
666 lines
21 KiB
Rust
666 lines
21 KiB
Rust
//! CLI round-trip integration tests.
|
|
//!
|
|
//! Each test runs the actual `encrypted_archive` binary via `assert_cmd`,
|
|
//! packs files into an archive, unpacks them, and verifies byte-identical output.
|
|
//! All tests use `tempdir()` for isolation (auto-cleanup, parallel-safe).
|
|
|
|
use assert_cmd::Command;
|
|
use predicates::prelude::*;
|
|
use std::fs;
|
|
use std::os::unix::fs::PermissionsExt;
|
|
use tempfile::tempdir;
|
|
|
|
/// Hex-encoded 32-byte key for test archives (matches legacy hardcoded key)
|
|
const TEST_KEY_HEX: &str = "7a35c1d94fe82b6a910df358bc74a61e428fd063e5179b2cfa8406cd3e79b550";
|
|
|
|
/// Helper: get a Command for the encrypted_archive binary with --key pre-set.
|
|
fn cmd_with_key() -> Command {
|
|
let mut c = Command::new(assert_cmd::cargo::cargo_bin!("encrypted_archive"));
|
|
c.args(["--key", TEST_KEY_HEX]);
|
|
c
|
|
}
|
|
|
|
/// Helper: get a Command for the encrypted_archive binary without a key.
|
|
fn cmd() -> Command {
|
|
Command::new(assert_cmd::cargo::cargo_bin!("encrypted_archive"))
|
|
}
|
|
|
|
/// Single text file: pack "Hello", unpack, verify byte-identical.
|
|
#[test]
|
|
fn test_roundtrip_single_text_file() {
|
|
let dir = tempdir().unwrap();
|
|
let input_file = dir.path().join("hello.txt");
|
|
let archive = dir.path().join("archive.bin");
|
|
let output_dir = dir.path().join("output");
|
|
|
|
fs::write(&input_file, b"Hello").unwrap();
|
|
|
|
cmd_with_key()
|
|
.args(["pack", input_file.to_str().unwrap(), "-o", archive.to_str().unwrap()])
|
|
.assert()
|
|
.success();
|
|
|
|
cmd_with_key()
|
|
.args(["unpack", archive.to_str().unwrap(), "-o", output_dir.to_str().unwrap()])
|
|
.assert()
|
|
.success();
|
|
|
|
let extracted = fs::read(output_dir.join("hello.txt")).unwrap();
|
|
assert_eq!(extracted, b"Hello");
|
|
}
|
|
|
|
/// Multiple files: pack two files, unpack, verify both byte-identical.
|
|
#[test]
|
|
fn test_roundtrip_multiple_files() {
|
|
let dir = tempdir().unwrap();
|
|
let text_file = dir.path().join("text.txt");
|
|
let binary_file = dir.path().join("binary.dat");
|
|
let archive = dir.path().join("archive.bin");
|
|
let output_dir = dir.path().join("output");
|
|
|
|
fs::write(&text_file, b"Some text content").unwrap();
|
|
fs::write(&binary_file, &[0x42u8; 256]).unwrap();
|
|
|
|
cmd_with_key()
|
|
.args([
|
|
"pack",
|
|
text_file.to_str().unwrap(),
|
|
binary_file.to_str().unwrap(),
|
|
"-o",
|
|
archive.to_str().unwrap(),
|
|
])
|
|
.assert()
|
|
.success();
|
|
|
|
cmd_with_key()
|
|
.args(["unpack", archive.to_str().unwrap(), "-o", output_dir.to_str().unwrap()])
|
|
.assert()
|
|
.success();
|
|
|
|
assert_eq!(
|
|
fs::read(output_dir.join("text.txt")).unwrap(),
|
|
b"Some text content"
|
|
);
|
|
assert_eq!(
|
|
fs::read(output_dir.join("binary.dat")).unwrap(),
|
|
vec![0x42u8; 256]
|
|
);
|
|
}
|
|
|
|
/// Empty file: pack 0-byte file, unpack, verify extracted file is exactly 0 bytes.
|
|
/// This tests PKCS7 full-block padding on 0-byte input (encrypted_size = 16).
|
|
#[test]
|
|
fn test_roundtrip_empty_file() {
|
|
let dir = tempdir().unwrap();
|
|
let input_file = dir.path().join("empty.txt");
|
|
let archive = dir.path().join("archive.bin");
|
|
let output_dir = dir.path().join("output");
|
|
|
|
fs::write(&input_file, b"").unwrap();
|
|
|
|
cmd_with_key()
|
|
.args(["pack", input_file.to_str().unwrap(), "-o", archive.to_str().unwrap()])
|
|
.assert()
|
|
.success();
|
|
|
|
cmd_with_key()
|
|
.args(["unpack", archive.to_str().unwrap(), "-o", output_dir.to_str().unwrap()])
|
|
.assert()
|
|
.success();
|
|
|
|
let extracted = fs::read(output_dir.join("empty.txt")).unwrap();
|
|
assert!(extracted.is_empty(), "Empty file should extract as 0 bytes");
|
|
}
|
|
|
|
/// Cyrillic filename: pack file with UTF-8 non-ASCII name, unpack, verify content.
|
|
/// This tests non-ASCII (UTF-8) filename handling in TOC entries.
|
|
#[test]
|
|
fn test_roundtrip_cyrillic_filename() {
|
|
let dir = tempdir().unwrap();
|
|
let input_file = dir.path().join("файл.txt");
|
|
let archive = dir.path().join("archive.bin");
|
|
let output_dir = dir.path().join("output");
|
|
|
|
let content = "Содержимое".as_bytes();
|
|
fs::write(&input_file, content).unwrap();
|
|
|
|
cmd_with_key()
|
|
.args(["pack", input_file.to_str().unwrap(), "-o", archive.to_str().unwrap()])
|
|
.assert()
|
|
.success();
|
|
|
|
cmd_with_key()
|
|
.args(["unpack", archive.to_str().unwrap(), "-o", output_dir.to_str().unwrap()])
|
|
.assert()
|
|
.success();
|
|
|
|
let extracted = fs::read(output_dir.join("файл.txt")).unwrap();
|
|
assert_eq!(extracted, content);
|
|
}
|
|
|
|
/// Large file (11MB): pack, unpack, verify byte-identical.
|
|
/// Tests large file handling (>10MB) per Phase 3 success criteria.
|
|
#[test]
|
|
fn test_roundtrip_large_file() {
|
|
let dir = tempdir().unwrap();
|
|
let input_file = dir.path().join("large.bin");
|
|
let archive = dir.path().join("archive.bin");
|
|
let output_dir = dir.path().join("output");
|
|
|
|
// Generate 11MB of deterministic pseudo-random data
|
|
let data: Vec<u8> = (0..11_000_000u32)
|
|
.map(|i| i.wrapping_mul(2654435761) as u8)
|
|
.collect();
|
|
fs::write(&input_file, &data).unwrap();
|
|
|
|
// Pack with --no-compress bin (skip compression for binary extension)
|
|
cmd_with_key()
|
|
.args([
|
|
"pack",
|
|
input_file.to_str().unwrap(),
|
|
"-o",
|
|
archive.to_str().unwrap(),
|
|
"--no-compress",
|
|
"bin",
|
|
])
|
|
.assert()
|
|
.success();
|
|
|
|
cmd_with_key()
|
|
.args(["unpack", archive.to_str().unwrap(), "-o", output_dir.to_str().unwrap()])
|
|
.assert()
|
|
.success();
|
|
|
|
let extracted = fs::read(output_dir.join("large.bin")).unwrap();
|
|
assert_eq!(extracted.len(), data.len(), "Extracted file size must match original");
|
|
assert_eq!(extracted, data, "Extracted file content must be byte-identical");
|
|
}
|
|
|
|
/// No-compress flag: pack APK file (auto-detected as no-compress), unpack, verify.
|
|
/// APK extension is in the known compressed extensions list, so compression_flag=0.
|
|
#[test]
|
|
fn test_roundtrip_no_compress_flag() {
|
|
let dir = tempdir().unwrap();
|
|
let input_file = dir.path().join("data.apk");
|
|
let archive = dir.path().join("archive.bin");
|
|
let output_dir = dir.path().join("output");
|
|
|
|
// 100 bytes of pattern data
|
|
let data: Vec<u8> = (0..100u8).collect();
|
|
fs::write(&input_file, &data).unwrap();
|
|
|
|
cmd_with_key()
|
|
.args(["pack", input_file.to_str().unwrap(), "-o", archive.to_str().unwrap()])
|
|
.assert()
|
|
.success();
|
|
|
|
cmd_with_key()
|
|
.args(["unpack", archive.to_str().unwrap(), "-o", output_dir.to_str().unwrap()])
|
|
.assert()
|
|
.success();
|
|
|
|
let extracted = fs::read(output_dir.join("data.apk")).unwrap();
|
|
assert_eq!(extracted, data);
|
|
}
|
|
|
|
/// Directory round-trip: pack a directory tree, unpack, verify files, empty dirs, and permissions.
|
|
#[test]
|
|
fn test_roundtrip_directory() {
|
|
let dir = tempdir().unwrap();
|
|
let testdir = dir.path().join("testdir");
|
|
let subdir = testdir.join("subdir");
|
|
let emptydir = testdir.join("empty");
|
|
let archive = dir.path().join("archive.bin");
|
|
let output_dir = dir.path().join("output");
|
|
|
|
// Create directory structure
|
|
fs::create_dir_all(&subdir).unwrap();
|
|
fs::create_dir_all(&emptydir).unwrap();
|
|
|
|
fs::write(testdir.join("hello.txt"), b"Hello from dir").unwrap();
|
|
fs::write(subdir.join("nested.txt"), b"Nested file").unwrap();
|
|
|
|
// Set specific permissions
|
|
fs::set_permissions(&testdir, fs::Permissions::from_mode(0o755)).unwrap();
|
|
fs::set_permissions(testdir.join("hello.txt"), fs::Permissions::from_mode(0o644)).unwrap();
|
|
fs::set_permissions(&subdir, fs::Permissions::from_mode(0o755)).unwrap();
|
|
fs::set_permissions(subdir.join("nested.txt"), fs::Permissions::from_mode(0o755)).unwrap();
|
|
fs::set_permissions(&emptydir, fs::Permissions::from_mode(0o700)).unwrap();
|
|
|
|
// Pack directory
|
|
cmd_with_key()
|
|
.args(["pack", testdir.to_str().unwrap(), "-o", archive.to_str().unwrap()])
|
|
.assert()
|
|
.success();
|
|
|
|
// Unpack
|
|
cmd_with_key()
|
|
.args(["unpack", archive.to_str().unwrap(), "-o", output_dir.to_str().unwrap()])
|
|
.assert()
|
|
.success();
|
|
|
|
// Verify file contents
|
|
let hello = fs::read(output_dir.join("testdir/hello.txt")).unwrap();
|
|
assert_eq!(hello, b"Hello from dir");
|
|
|
|
let nested = fs::read(output_dir.join("testdir/subdir/nested.txt")).unwrap();
|
|
assert_eq!(nested, b"Nested file");
|
|
|
|
// Verify empty directory exists
|
|
assert!(
|
|
output_dir.join("testdir/empty").is_dir(),
|
|
"Empty directory should be recreated"
|
|
);
|
|
|
|
// Verify permissions
|
|
let nested_mode = fs::metadata(output_dir.join("testdir/subdir/nested.txt"))
|
|
.unwrap()
|
|
.permissions()
|
|
.mode()
|
|
& 0o7777;
|
|
assert_eq!(nested_mode, 0o755, "nested.txt should have mode 0755");
|
|
|
|
let empty_mode = fs::metadata(output_dir.join("testdir/empty"))
|
|
.unwrap()
|
|
.permissions()
|
|
.mode()
|
|
& 0o7777;
|
|
assert_eq!(empty_mode, 0o700, "empty dir should have mode 0700");
|
|
}
|
|
|
|
/// Mixed files and directories: pack both a standalone file and a directory, verify round-trip.
|
|
#[test]
|
|
fn test_roundtrip_mixed_files_and_dirs() {
|
|
let dir = tempdir().unwrap();
|
|
let standalone = dir.path().join("standalone.txt");
|
|
let mydir = dir.path().join("mydir");
|
|
let archive = dir.path().join("archive.bin");
|
|
let output_dir = dir.path().join("output");
|
|
|
|
fs::write(&standalone, b"Standalone").unwrap();
|
|
fs::create_dir_all(&mydir).unwrap();
|
|
fs::write(mydir.join("inner.txt"), b"Inner").unwrap();
|
|
|
|
// Pack both file and directory
|
|
cmd_with_key()
|
|
.args([
|
|
"pack",
|
|
standalone.to_str().unwrap(),
|
|
mydir.to_str().unwrap(),
|
|
"-o",
|
|
archive.to_str().unwrap(),
|
|
])
|
|
.assert()
|
|
.success();
|
|
|
|
// Unpack
|
|
cmd_with_key()
|
|
.args(["unpack", archive.to_str().unwrap(), "-o", output_dir.to_str().unwrap()])
|
|
.assert()
|
|
.success();
|
|
|
|
// Verify both entries
|
|
assert_eq!(
|
|
fs::read(output_dir.join("standalone.txt")).unwrap(),
|
|
b"Standalone"
|
|
);
|
|
assert_eq!(
|
|
fs::read(output_dir.join("mydir/inner.txt")).unwrap(),
|
|
b"Inner"
|
|
);
|
|
}
|
|
|
|
/// Inspect shows directory info: entry type and permissions for directory entries.
|
|
/// Now requires --key to see full TOC listing.
|
|
#[test]
|
|
fn test_inspect_shows_directory_info() {
|
|
let dir = tempdir().unwrap();
|
|
let testdir = dir.path().join("testdir");
|
|
let archive = dir.path().join("archive.bin");
|
|
|
|
fs::create_dir_all(&testdir).unwrap();
|
|
fs::write(testdir.join("file.txt"), b"content").unwrap();
|
|
|
|
cmd_with_key()
|
|
.args(["pack", testdir.to_str().unwrap(), "-o", archive.to_str().unwrap()])
|
|
.assert()
|
|
.success();
|
|
|
|
// Inspect with key: shows full TOC entry listing
|
|
cmd_with_key()
|
|
.args(["inspect", archive.to_str().unwrap()])
|
|
.assert()
|
|
.success()
|
|
.stdout(predicate::str::contains("dir"))
|
|
.stdout(predicate::str::contains("file"))
|
|
.stdout(predicate::str::contains("0755").or(predicate::str::contains("0775")))
|
|
.stdout(predicate::str::contains("Permissions:"));
|
|
}
|
|
|
|
// ========== New tests for key input ==========
|
|
|
|
/// Key file round-trip: create a 32-byte key file, pack with --key-file, unpack with --key-file.
|
|
#[test]
|
|
fn test_key_file_roundtrip() {
|
|
let dir = tempdir().unwrap();
|
|
let input_file = dir.path().join("data.txt");
|
|
let key_file = dir.path().join("test.key");
|
|
let archive = dir.path().join("archive.bin");
|
|
let output_dir = dir.path().join("output");
|
|
|
|
fs::write(&input_file, b"Key file test data").unwrap();
|
|
|
|
// Write a 32-byte key file (raw bytes)
|
|
let key_bytes: [u8; 32] = [
|
|
0x7A, 0x35, 0xC1, 0xD9, 0x4F, 0xE8, 0x2B, 0x6A,
|
|
0x91, 0x0D, 0xF3, 0x58, 0xBC, 0x74, 0xA6, 0x1E,
|
|
0x42, 0x8F, 0xD0, 0x63, 0xE5, 0x17, 0x9B, 0x2C,
|
|
0xFA, 0x84, 0x06, 0xCD, 0x3E, 0x79, 0xB5, 0x50,
|
|
];
|
|
fs::write(&key_file, key_bytes).unwrap();
|
|
|
|
// Pack with --key-file
|
|
cmd()
|
|
.args([
|
|
"--key-file", key_file.to_str().unwrap(),
|
|
"pack", input_file.to_str().unwrap(), "-o", archive.to_str().unwrap(),
|
|
])
|
|
.assert()
|
|
.success();
|
|
|
|
// Unpack with --key-file
|
|
cmd()
|
|
.args([
|
|
"--key-file", key_file.to_str().unwrap(),
|
|
"unpack", archive.to_str().unwrap(), "-o", output_dir.to_str().unwrap(),
|
|
])
|
|
.assert()
|
|
.success();
|
|
|
|
let extracted = fs::read(output_dir.join("data.txt")).unwrap();
|
|
assert_eq!(extracted, b"Key file test data");
|
|
}
|
|
|
|
/// Wrong key: pack with one key, try unpack with different key, expect HMAC failure.
|
|
#[test]
|
|
fn test_rejects_wrong_key() {
|
|
let dir = tempdir().unwrap();
|
|
let input_file = dir.path().join("secret.txt");
|
|
let archive = dir.path().join("archive.bin");
|
|
let output_dir = dir.path().join("output");
|
|
|
|
fs::write(&input_file, b"Secret data").unwrap();
|
|
|
|
// Pack with the test key
|
|
cmd_with_key()
|
|
.args(["pack", input_file.to_str().unwrap(), "-o", archive.to_str().unwrap()])
|
|
.assert()
|
|
.success();
|
|
|
|
// Try to unpack with a different key (all zeros).
|
|
// The wrong key causes TOC decryption to fail (invalid padding) or HMAC verification
|
|
// to fail on individual files, depending on where the decryption error surfaces first.
|
|
let wrong_key = "0000000000000000000000000000000000000000000000000000000000000000";
|
|
cmd()
|
|
.args([
|
|
"--key", wrong_key,
|
|
"unpack", archive.to_str().unwrap(), "-o", output_dir.to_str().unwrap(),
|
|
])
|
|
.assert()
|
|
.failure()
|
|
.stderr(
|
|
predicate::str::contains("HMAC")
|
|
.or(predicate::str::contains("verification"))
|
|
.or(predicate::str::contains("Decryption failed"))
|
|
.or(predicate::str::contains("wrong key"))
|
|
);
|
|
}
|
|
|
|
/// Bad hex: --key with too-short hex string should produce a clear error.
|
|
#[test]
|
|
fn test_rejects_bad_hex() {
|
|
let dir = tempdir().unwrap();
|
|
let input_file = dir.path().join("data.txt");
|
|
let archive = dir.path().join("archive.bin");
|
|
|
|
fs::write(&input_file, b"data").unwrap();
|
|
|
|
cmd()
|
|
.args([
|
|
"--key", "abcd",
|
|
"pack", input_file.to_str().unwrap(), "-o", archive.to_str().unwrap(),
|
|
])
|
|
.assert()
|
|
.failure()
|
|
.stderr(predicate::str::contains("32 bytes").or(predicate::str::contains("hex")));
|
|
}
|
|
|
|
/// Missing key: running pack without any key arg should produce a clear error.
|
|
#[test]
|
|
fn test_rejects_missing_key() {
|
|
let dir = tempdir().unwrap();
|
|
let input_file = dir.path().join("data.txt");
|
|
let archive = dir.path().join("archive.bin");
|
|
|
|
fs::write(&input_file, b"data").unwrap();
|
|
|
|
cmd()
|
|
.args(["pack", input_file.to_str().unwrap(), "-o", archive.to_str().unwrap()])
|
|
.assert()
|
|
.failure()
|
|
.stderr(predicate::str::contains("required for pack"));
|
|
}
|
|
|
|
/// Inspect without key: should succeed and show header metadata but NOT entry listing.
|
|
#[test]
|
|
fn test_inspect_without_key() {
|
|
let dir = tempdir().unwrap();
|
|
let input_file = dir.path().join("data.txt");
|
|
let archive = dir.path().join("archive.bin");
|
|
|
|
fs::write(&input_file, b"Hello inspect").unwrap();
|
|
|
|
// Pack with key
|
|
cmd_with_key()
|
|
.args(["pack", input_file.to_str().unwrap(), "-o", archive.to_str().unwrap()])
|
|
.assert()
|
|
.success();
|
|
|
|
// Inspect without key: should show header metadata, print TOC encrypted message
|
|
cmd()
|
|
.args(["inspect", archive.to_str().unwrap()])
|
|
.assert()
|
|
.success()
|
|
.stdout(predicate::str::contains("Version:"))
|
|
.stdout(predicate::str::contains("Flags:"))
|
|
.stdout(predicate::str::contains("Entries:"))
|
|
.stdout(predicate::str::contains("TOC is encrypted, provide a key to see entry listing"));
|
|
}
|
|
|
|
/// Inspect with key: should succeed and show full TOC entry listing.
|
|
#[test]
|
|
fn test_inspect_with_key() {
|
|
let dir = tempdir().unwrap();
|
|
let input_file = dir.path().join("data.txt");
|
|
let archive = dir.path().join("archive.bin");
|
|
|
|
fs::write(&input_file, b"Hello inspect with key").unwrap();
|
|
|
|
// Pack with key
|
|
cmd_with_key()
|
|
.args(["pack", input_file.to_str().unwrap(), "-o", archive.to_str().unwrap()])
|
|
.assert()
|
|
.success();
|
|
|
|
// Inspect with key: should show full entry listing
|
|
cmd_with_key()
|
|
.args(["inspect", archive.to_str().unwrap()])
|
|
.assert()
|
|
.success()
|
|
.stdout(predicate::str::contains("Version:"))
|
|
.stdout(predicate::str::contains("data.txt"))
|
|
.stdout(predicate::str::contains("Original:"))
|
|
.stdout(predicate::str::contains("SHA-256:"));
|
|
}
|
|
|
|
// ========== Password-based key derivation tests ==========
|
|
|
|
/// Password round-trip: pack with --password, unpack with same --password, verify byte-identical.
|
|
#[test]
|
|
fn test_password_roundtrip() {
|
|
let dir = tempdir().unwrap();
|
|
let input_file = dir.path().join("secret.txt");
|
|
let archive = dir.path().join("archive.aea");
|
|
let output_dir = dir.path().join("output");
|
|
|
|
fs::write(&input_file, b"Password protected data").unwrap();
|
|
|
|
// Pack with --password
|
|
cmd()
|
|
.args([
|
|
"--password", "testpass123",
|
|
"pack", input_file.to_str().unwrap(), "-o", archive.to_str().unwrap(),
|
|
])
|
|
.assert()
|
|
.success();
|
|
|
|
// Unpack with same --password
|
|
cmd()
|
|
.args([
|
|
"--password", "testpass123",
|
|
"unpack", archive.to_str().unwrap(), "-o", output_dir.to_str().unwrap(),
|
|
])
|
|
.assert()
|
|
.success();
|
|
|
|
let extracted = fs::read(output_dir.join("secret.txt")).unwrap();
|
|
assert_eq!(extracted, b"Password protected data");
|
|
}
|
|
|
|
/// Wrong password: pack with correct, unpack with wrong, expect HMAC/decryption failure.
|
|
#[test]
|
|
fn test_password_wrong_rejects() {
|
|
let dir = tempdir().unwrap();
|
|
let input_file = dir.path().join("data.txt");
|
|
let archive = dir.path().join("archive.aea");
|
|
let output_dir = dir.path().join("output");
|
|
|
|
fs::write(&input_file, b"Sensitive data").unwrap();
|
|
|
|
// Pack with correct password
|
|
cmd()
|
|
.args([
|
|
"--password", "correctpassword",
|
|
"pack", input_file.to_str().unwrap(), "-o", archive.to_str().unwrap(),
|
|
])
|
|
.assert()
|
|
.success();
|
|
|
|
// Try unpack with wrong password
|
|
cmd()
|
|
.args([
|
|
"--password", "wrongpassword",
|
|
"unpack", archive.to_str().unwrap(), "-o", output_dir.to_str().unwrap(),
|
|
])
|
|
.assert()
|
|
.failure()
|
|
.stderr(
|
|
predicate::str::contains("HMAC")
|
|
.or(predicate::str::contains("verification"))
|
|
.or(predicate::str::contains("Decryption failed"))
|
|
.or(predicate::str::contains("wrong key"))
|
|
);
|
|
}
|
|
|
|
/// Password archive has salt flag: flags should contain bit 4 (0x10).
|
|
#[test]
|
|
fn test_password_archive_has_salt_flag() {
|
|
let dir = tempdir().unwrap();
|
|
let input_file = dir.path().join("data.txt");
|
|
let archive = dir.path().join("archive.aea");
|
|
|
|
fs::write(&input_file, b"Flagged data").unwrap();
|
|
|
|
// Pack with --password
|
|
cmd()
|
|
.args([
|
|
"--password", "testpass",
|
|
"pack", input_file.to_str().unwrap(), "-o", archive.to_str().unwrap(),
|
|
])
|
|
.assert()
|
|
.success();
|
|
|
|
// Inspect with --password to see flags
|
|
cmd()
|
|
.args([
|
|
"--password", "testpass",
|
|
"inspect", archive.to_str().unwrap(),
|
|
])
|
|
.assert()
|
|
.success()
|
|
.stdout(predicate::str::contains("Flags: 0x1F")); // 0x0F (bits 0-3) + 0x10 (bit 4) = 0x1F
|
|
}
|
|
|
|
/// Key archive has no salt flag: flags should NOT contain bit 4 (0x10).
|
|
#[test]
|
|
fn test_key_archive_no_salt_flag() {
|
|
let dir = tempdir().unwrap();
|
|
let input_file = dir.path().join("data.txt");
|
|
let archive = dir.path().join("archive.aea");
|
|
|
|
fs::write(&input_file, b"No salt data").unwrap();
|
|
|
|
// Pack with --key (no password, no salt)
|
|
cmd_with_key()
|
|
.args(["pack", input_file.to_str().unwrap(), "-o", archive.to_str().unwrap()])
|
|
.assert()
|
|
.success();
|
|
|
|
// Inspect with --key
|
|
cmd_with_key()
|
|
.args(["inspect", archive.to_str().unwrap()])
|
|
.assert()
|
|
.success()
|
|
.stdout(predicate::str::contains("Flags: 0x0F")); // bits 0-3 set, bit 4 clear
|
|
}
|
|
|
|
/// Password archive multiple files: pack a directory with --password, unpack, verify.
|
|
#[test]
|
|
fn test_password_roundtrip_directory() {
|
|
let dir = tempdir().unwrap();
|
|
let testdir = dir.path().join("mydir");
|
|
let archive = dir.path().join("archive.aea");
|
|
let output_dir = dir.path().join("output");
|
|
|
|
fs::create_dir_all(&testdir).unwrap();
|
|
fs::write(testdir.join("file1.txt"), b"File one content").unwrap();
|
|
fs::write(testdir.join("file2.txt"), b"File two content").unwrap();
|
|
|
|
// Pack with --password
|
|
cmd()
|
|
.args([
|
|
"--password", "dirpass",
|
|
"pack", testdir.to_str().unwrap(), "-o", archive.to_str().unwrap(),
|
|
])
|
|
.assert()
|
|
.success();
|
|
|
|
// Unpack with same --password
|
|
cmd()
|
|
.args([
|
|
"--password", "dirpass",
|
|
"unpack", archive.to_str().unwrap(), "-o", output_dir.to_str().unwrap(),
|
|
])
|
|
.assert()
|
|
.success();
|
|
|
|
assert_eq!(
|
|
fs::read(output_dir.join("mydir/file1.txt")).unwrap(),
|
|
b"File one content"
|
|
);
|
|
assert_eq!(
|
|
fs::read(output_dir.join("mydir/file2.txt")).unwrap(),
|
|
b"File two content"
|
|
);
|
|
}
|