add encryption feature

This commit is contained in:
spuds 2023-08-16 22:41:58 +01:00
parent 079693af22
commit aae65c12cf
No known key found for this signature in database
GPG key ID: 0B6CA6068E827C8F
5 changed files with 147 additions and 23 deletions

View file

@ -7,13 +7,14 @@ version.workspace = true
edition.workspace = true
[features]
default = ["compression"]
default = ["compression", "encryption"]
compression = ["dep:flate2", "dep:zstd"]
oodle = ["dep:libloading", "dep:ureq", "dep:once_cell", "dep:hex-literal", "dep:hex"]
encryption = ["dep:aes"]
[dependencies]
byteorder = "1.4"
aes = "0.8"
aes = { version = "0.8", optional = true }
flate2 = { version = "1.0", optional = true }
zstd = { version = "0.12", optional = true }
thiserror = "1.0"

View file

@ -311,25 +311,31 @@ impl Entry {
reader: &mut R,
version: Version,
compression: &[Compression],
key: Option<&aes::Aes256>,
#[cfg(feature = "encryption")] key: Option<&aes::Aes256>,
buf: &mut W,
) -> Result<(), super::Error> {
reader.seek(io::SeekFrom::Start(self.offset))?;
Entry::read(reader, version)?;
let data_offset = reader.stream_position()?;
#[allow(unused_mut)]
let mut data = reader.read_len(match self.is_encrypted() {
true => align(self.compressed),
false => self.compressed,
} as usize)?;
if self.is_encrypted() {
let Some(key) = key else {
return Err(super::Error::Encrypted);
};
use aes::cipher::BlockDecrypt;
for block in data.chunks_mut(16) {
key.decrypt_block(aes::Block::from_mut_slice(block))
#[cfg(not(feature = "encryption"))]
return Err(super::Error::Encryption);
#[cfg(feature = "encryption")]
{
let Some(key) = key else {
return Err(super::Error::Encrypted);
};
use aes::cipher::BlockDecrypt;
for block in data.chunks_mut(16) {
key.decrypt_block(aes::Block::from_mut_slice(block))
}
data.truncate(self.compressed as usize);
}
data.truncate(self.compressed as usize);
}
let ranges = match &self.blocks {

View file

@ -13,6 +13,9 @@ pub enum Error {
#[error("enable the compression feature to read compressed paks")]
Compression,
#[error("enable the encryption feature to read encrypted paks")]
Encryption,
#[error("enable the oodle feature to read oodle paks")]
Oodle,

View file

@ -1,5 +1,6 @@
use super::ext::{ReadExt, WriteExt};
use super::{Version, VersionMajor};
#[cfg(feature = "encryption")]
use aes::Aes256;
use byteorder::{ReadBytesExt, WriteBytesExt, LE};
use std::collections::BTreeMap;
@ -8,6 +9,7 @@ use std::io::{self, Read, Seek, Write};
#[derive(Debug)]
pub struct PakReader {
pak: Pak,
#[cfg(feature = "encryption")]
key: Option<aes::Aes256>,
}
@ -15,6 +17,7 @@ pub struct PakReader {
pub struct PakWriter<W: Write + Seek> {
pak: Pak,
writer: W,
#[cfg(feature = "encryption")]
key: Option<aes::Aes256>,
}
@ -70,6 +73,7 @@ impl Index {
}
}
#[cfg(feature = "encryption")]
fn decrypt(key: Option<&aes::Aes256>, bytes: &mut [u8]) -> Result<(), super::Error> {
if let Some(key) = key {
use aes::cipher::BlockDecrypt;
@ -83,28 +87,79 @@ fn decrypt(key: Option<&aes::Aes256>, bytes: &mut [u8]) -> Result<(), super::Err
}
impl PakReader {
pub fn new_any<R: Read + Seek>(
#[cfg(not(feature = "encryption"))]
pub fn new_any<R: Read + Seek>(reader: &mut R) -> Result<Self, super::Error> {
Self::new_any_inner(reader)
}
#[cfg(feature = "encryption")]
pub fn new_any_with_key<R: Read + Seek>(
reader: &mut R,
key: Option<aes::Aes256>,
) -> Result<Self, super::Error> {
Self::new_any_inner(reader, key)
}
fn new_any_inner<R: Read + Seek>(
reader: &mut R,
#[cfg(feature = "encryption")] key: Option<aes::Aes256>,
) -> Result<Self, super::Error> {
use std::fmt::Write;
let mut log = "\n".to_owned();
for ver in Version::iter() {
match Pak::read(&mut *reader, ver, key.as_ref()) {
Ok(pak) => return Ok(Self { pak, key }),
match Pak::read(
&mut *reader,
ver,
#[cfg(feature = "encryption")]
key.as_ref(),
) {
Ok(pak) => {
return Ok(Self {
pak,
#[cfg(feature = "encryption")]
key,
})
}
Err(err) => writeln!(log, "trying version {} failed: {}", ver, err)?,
}
}
Err(super::Error::UnsupportedOrEncrypted(log))
}
#[cfg(not(feature = "encryption"))]
pub fn new<R: Read + Seek>(
reader: &mut R,
version: super::Version,
) -> Result<Self, super::Error> {
Self::new_inner(reader, version)
}
#[cfg(feature = "encryption")]
pub fn new_with_key<R: Read + Seek>(
reader: &mut R,
version: super::Version,
key: Option<aes::Aes256>,
) -> Result<Self, super::Error> {
Pak::read(reader, version, key.as_ref()).map(|pak| Self { pak, key })
Self::new_inner(reader, version, key)
}
fn new_inner<R: Read + Seek>(
reader: &mut R,
version: super::Version,
#[cfg(feature = "encryption")] key: Option<aes::Aes256>,
) -> Result<Self, super::Error> {
Pak::read(
reader,
version,
#[cfg(feature = "encryption")]
key.as_ref(),
)
.map(|pak| Self {
pak,
#[cfg(feature = "encryption")]
key,
})
}
pub fn version(&self) -> super::Version {
@ -140,6 +195,7 @@ impl PakReader {
reader,
self.pak.version,
&self.pak.compression,
#[cfg(feature = "encryption")]
self.key.as_ref(),
writer,
),
@ -158,6 +214,7 @@ impl PakReader {
writer.seek(io::SeekFrom::Start(self.pak.index_offset.unwrap()))?;
Ok(PakWriter {
pak: self.pak,
#[cfg(feature = "encryption")]
key: self.key,
writer,
})
@ -165,7 +222,21 @@ impl PakReader {
}
impl<W: Write + Seek> PakWriter<W> {
#[cfg(not(feature = "encryption"))]
pub fn new(
writer: W,
version: Version,
mount_point: String,
path_hash_seed: Option<u64>,
) -> Self {
PakWriter {
pak: Pak::new(version, mount_point, path_hash_seed),
writer,
}
}
#[cfg(feature = "encryption")]
pub fn new_with_key(
writer: W,
key: Option<aes::Aes256>,
version: Version,
@ -179,6 +250,21 @@ impl<W: Write + Seek> PakWriter<W> {
}
}
fn new_inner(
writer: W,
#[cfg(feature = "encryption")] key: Option<aes::Aes256>,
version: Version,
mount_point: String,
path_hash_seed: Option<u64>,
) -> Self {
PakWriter {
pak: Pak::new(version, mount_point, path_hash_seed),
writer,
#[cfg(feature = "encryption")]
key,
}
}
pub fn into_writer(self) -> W {
self.writer
}
@ -219,7 +305,11 @@ impl<W: Write + Seek> PakWriter<W> {
}
pub fn write_index(mut self) -> Result<W, super::Error> {
self.pak.write(&mut self.writer, self.key)?;
self.pak.write(
&mut self.writer,
#[cfg(feature = "encryption")]
self.key,
)?;
Ok(self.writer)
}
}
@ -228,17 +318,21 @@ impl Pak {
fn read<R: Read + Seek>(
reader: &mut R,
version: super::Version,
key: Option<&aes::Aes256>,
#[cfg(feature = "encryption")] key: Option<&aes::Aes256>,
) -> Result<Self, super::Error> {
// read footer to get index, encryption & compression info
reader.seek(io::SeekFrom::End(-version.size()))?;
let footer = super::footer::Footer::read(reader, version)?;
// read index to get all the entry info
reader.seek(io::SeekFrom::Start(footer.index_offset))?;
#[allow(unused_mut)]
let mut index = reader.read_len(footer.index_size as usize)?;
// decrypt index if needed
if footer.encrypted {
#[cfg(not(feature = "encryption"))]
return Err(super::Error::Encryption);
#[cfg(feature = "encryption")]
decrypt(key, &mut index)?;
}
@ -260,6 +354,9 @@ impl Pak {
// TODO verify hash
if footer.encrypted {
#[cfg(not(feature = "encryption"))]
return Err(super::Error::Encryption);
#[cfg(feature = "encryption")]
decrypt(key, &mut path_hash_index_buf)?;
}
@ -283,11 +380,15 @@ impl Pak {
let _full_directory_index_hash = index.read_len(20)?;
reader.seek(io::SeekFrom::Start(full_directory_index_offset))?;
#[allow(unused_mut)]
let mut full_directory_index =
reader.read_len(full_directory_index_size as usize)?;
// TODO verify hash
if footer.encrypted {
#[cfg(not(feature = "encryption"))]
return Err(super::Error::Encryption);
#[cfg(feature = "encryption")]
decrypt(key, &mut full_directory_index)?;
}
let mut fdi = io::Cursor::new(full_directory_index);
@ -367,7 +468,7 @@ impl Pak {
fn write<W: Write + Seek>(
&self,
writer: &mut W,
_key: Option<aes::Aes256>,
#[cfg(feature = "encryption")] _key: Option<aes::Aes256>,
) -> Result<(), super::Error> {
let index_offset = writer.stream_position()?;
@ -594,6 +695,7 @@ fn pad_zeros_to_alignment(v: &mut Vec<u8>, alignment: usize) {
assert!(v.len() % alignment == 0);
}
#[cfg(feature = "encryption")]
fn encrypt(key: Aes256, bytes: &mut [u8]) {
use aes::cipher::BlockEncrypt;
for chunk in bytes.chunks_mut(16) {

View file

@ -175,7 +175,10 @@ fn main() -> Result<(), repak::Error> {
}
fn info(aes_key: Option<aes::Aes256>, action: ActionInfo) -> Result<(), repak::Error> {
let pak = repak::PakReader::new_any(&mut BufReader::new(File::open(action.input)?), aes_key)?;
let pak = repak::PakReader::new_any_with_key(
&mut BufReader::new(File::open(action.input)?),
aes_key,
)?;
println!("mount point: {}", pak.mount_point());
println!("version: {}", pak.version());
println!("version major: {}", pak.version().version_major());
@ -186,7 +189,10 @@ fn info(aes_key: Option<aes::Aes256>, action: ActionInfo) -> Result<(), repak::E
}
fn list(aes_key: Option<aes::Aes256>, action: ActionList) -> Result<(), repak::Error> {
let pak = repak::PakReader::new_any(&mut BufReader::new(File::open(action.input)?), aes_key)?;
let pak = repak::PakReader::new_any_with_key(
&mut BufReader::new(File::open(action.input)?),
aes_key,
)?;
let mount_point = PathBuf::from(pak.mount_point());
let prefix = Path::new(&action.strip_prefix);
@ -215,7 +221,10 @@ fn list(aes_key: Option<aes::Aes256>, action: ActionList) -> Result<(), repak::E
}
fn hash_list(aes_key: Option<aes::Aes256>, action: ActionHashList) -> Result<(), repak::Error> {
let pak = repak::PakReader::new_any(&mut BufReader::new(File::open(&action.input)?), aes_key)?;
let pak = repak::PakReader::new_any_with_key(
&mut BufReader::new(File::open(&action.input)?),
aes_key,
)?;
let mount_point = PathBuf::from(pak.mount_point());
let prefix = Path::new(&action.strip_prefix);
@ -270,7 +279,10 @@ fn hash_list(aes_key: Option<aes::Aes256>, action: ActionHashList) -> Result<(),
const STYLE: &str = "[{elapsed_precise}] [{wide_bar}] {pos}/{len} ({eta})";
fn unpack(aes_key: Option<aes::Aes256>, action: ActionUnpack) -> Result<(), repak::Error> {
let pak = repak::PakReader::new_any(&mut BufReader::new(File::open(&action.input)?), aes_key)?;
let pak = repak::PakReader::new_any_with_key(
&mut BufReader::new(File::open(&action.input)?),
aes_key,
)?;
let output = action
.output
.map(PathBuf::from)
@ -388,7 +400,7 @@ fn pack(args: ActionPack) -> Result<(), repak::Error> {
collect_files(&mut paths, input_path)?;
paths.sort();
let mut pak = repak::PakWriter::new(
let mut pak = repak::PakWriter::new_with_key(
BufWriter::new(File::create(&output)?),
None,
args.version,
@ -423,7 +435,7 @@ fn pack(args: ActionPack) -> Result<(), repak::Error> {
fn get(aes_key: Option<aes::Aes256>, args: ActionGet) -> Result<(), repak::Error> {
let mut reader = BufReader::new(File::open(&args.input)?);
let pak = repak::PakReader::new_any(&mut reader, aes_key)?;
let pak = repak::PakReader::new_any_with_key(&mut reader, aes_key)?;
let mount_point = PathBuf::from(pak.mount_point());
let prefix = Path::new(&args.strip_prefix);