use crate::error::Result;
use crate::lookup::{
lookup_aead_algorithm, lookup_hash_algorithm, lookup_s2k_type, lookup_symmetric_algorithm,
};
use crate::stream::ByteStream;
use super::Field;
#[derive(Debug, Clone)]
pub struct S2kSpecifier {
pub s2k_type: u8,
pub hash_algorithm: Option<u8>,
pub salt: Option<Vec<u8>>,
pub count: Option<u8>,
pub argon2_params: Option<Argon2Params>,
}
#[derive(Debug, Clone)]
pub struct Argon2Params {
pub salt: Vec<u8>,
pub parallelism: u8,
pub memory: u8,
pub iterations: u8,
}
#[derive(Debug, Clone)]
pub struct SkeskPacket {
pub version: u8,
pub cipher_algorithm: u8,
pub aead_algorithm: Option<u8>,
pub s2k: S2kSpecifier,
pub iv: Option<Vec<u8>>,
pub encrypted_session_key: Option<Vec<u8>>,
pub auth_tag: Option<Vec<u8>>,
}
pub fn parse_skesk(
stream: &mut ByteStream,
fields: &mut Vec<Field>,
body_offset: usize,
) -> Result<SkeskPacket> {
let version_start = body_offset + stream.pos();
let version = stream.octet()?;
fields.push(Field::field(
"Version",
version.to_string(),
(version_start, version_start + 1),
));
match version {
4 => parse_skesk_v4(stream, fields, body_offset, version),
5 | 6 => parse_skesk_v6(stream, fields, body_offset, version),
_ => parse_skesk_v4(stream, fields, body_offset, version), }
}
fn parse_skesk_v4(
stream: &mut ByteStream,
fields: &mut Vec<Field>,
body_offset: usize,
version: u8,
) -> Result<SkeskPacket> {
let cipher_start = body_offset + stream.pos();
let cipher_algorithm = stream.octet()?;
let cipher_lookup = lookup_symmetric_algorithm(cipher_algorithm);
fields.push(Field::field(
"Cipher Algorithm",
cipher_lookup.display(),
(cipher_start, cipher_start + 1),
));
let s2k = parse_s2k_specifier(stream, fields, body_offset)?;
let remaining = stream.remaining();
let encrypted_session_key = if remaining > 0 {
let esk_start = body_offset + stream.pos();
let esk = stream.rest();
let esk_end = body_offset + stream.pos();
fields.push(Field::field(
"Encrypted Session Key",
format!("{} bytes", esk.len()),
(esk_start, esk_end),
));
Some(esk)
} else {
fields.push(Field::field(
"Session Key",
"Derived directly from S2K",
(body_offset + stream.pos(), body_offset + stream.pos()),
));
None
};
Ok(SkeskPacket {
version,
cipher_algorithm,
aead_algorithm: None,
s2k,
iv: None,
encrypted_session_key,
auth_tag: None,
})
}
fn parse_skesk_v6(
stream: &mut ByteStream,
fields: &mut Vec<Field>,
body_offset: usize,
version: u8,
) -> Result<SkeskPacket> {
let count_start = body_offset + stream.pos();
let count = stream.octet()?;
fields.push(Field::field(
"S2K + Cipher Info Length",
count.to_string(),
(count_start, count_start + 1),
));
let cipher_start = body_offset + stream.pos();
let cipher_algorithm = stream.octet()?;
let cipher_lookup = lookup_symmetric_algorithm(cipher_algorithm);
fields.push(Field::field(
"Cipher Algorithm",
cipher_lookup.display(),
(cipher_start, cipher_start + 1),
));
let aead_start = body_offset + stream.pos();
let aead_algorithm = stream.octet()?;
let aead_lookup = lookup_aead_algorithm(aead_algorithm);
fields.push(Field::field(
"AEAD Algorithm",
aead_lookup.display(),
(aead_start, aead_start + 1),
));
let s2k_len_start = body_offset + stream.pos();
let s2k_len = stream.octet()? as usize;
fields.push(Field::field(
"S2K Length",
s2k_len.to_string(),
(s2k_len_start, s2k_len_start + 1),
));
let s2k = parse_s2k_specifier(stream, fields, body_offset)?;
let iv_len = match aead_algorithm {
1 => 16, 2 => 15, 3 => 12, _ => 16, };
let iv_start = body_offset + stream.pos();
let iv = stream.bytes(iv_len)?;
fields.push(Field::field(
"IV/Nonce",
format!("{} bytes", iv.len()),
(iv_start, iv_start + iv_len),
));
let remaining = stream.remaining();
let tag_len = 16;
let esk_len = remaining.saturating_sub(tag_len);
let encrypted_session_key = if esk_len > 0 {
let esk_start = body_offset + stream.pos();
let esk = stream.bytes(esk_len)?;
fields.push(Field::field(
"Encrypted Session Key",
format!("{} bytes", esk.len()),
(esk_start, esk_start + esk_len),
));
Some(esk)
} else {
None
};
let auth_tag = if stream.remaining() >= tag_len {
let tag_start = body_offset + stream.pos();
let tag = stream.bytes(tag_len)?;
fields.push(Field::field(
"Authentication Tag",
format!("{} bytes", tag.len()),
(tag_start, tag_start + tag_len),
));
Some(tag)
} else {
None
};
Ok(SkeskPacket {
version,
cipher_algorithm,
aead_algorithm: Some(aead_algorithm),
s2k,
iv: Some(iv),
encrypted_session_key,
auth_tag,
})
}
fn parse_s2k_specifier(
stream: &mut ByteStream,
fields: &mut Vec<Field>,
body_offset: usize,
) -> Result<S2kSpecifier> {
let type_start = body_offset + stream.pos();
let s2k_type = stream.octet()?;
fields.push(Field::field(
"S2K Type",
lookup_s2k_type(s2k_type),
(type_start, type_start + 1),
));
match s2k_type {
0 => {
let hash_start = body_offset + stream.pos();
let hash_algorithm = stream.octet()?;
let hash_lookup = lookup_hash_algorithm(hash_algorithm);
fields.push(Field::field(
"S2K Hash",
hash_lookup.display(),
(hash_start, hash_start + 1),
));
Ok(S2kSpecifier {
s2k_type,
hash_algorithm: Some(hash_algorithm),
salt: None,
count: None,
argon2_params: None,
})
}
1 => {
let hash_start = body_offset + stream.pos();
let hash_algorithm = stream.octet()?;
let hash_lookup = lookup_hash_algorithm(hash_algorithm);
fields.push(Field::field(
"S2K Hash",
hash_lookup.display(),
(hash_start, hash_start + 1),
));
let salt_start = body_offset + stream.pos();
let salt = stream.bytes(8)?;
let salt_hex: String = salt.iter().map(|b| format!("{:02X}", b)).collect();
fields.push(Field::field(
"S2K Salt",
salt_hex,
(salt_start, salt_start + 8),
));
Ok(S2kSpecifier {
s2k_type,
hash_algorithm: Some(hash_algorithm),
salt: Some(salt),
count: None,
argon2_params: None,
})
}
3 => {
let hash_start = body_offset + stream.pos();
let hash_algorithm = stream.octet()?;
let hash_lookup = lookup_hash_algorithm(hash_algorithm);
fields.push(Field::field(
"S2K Hash",
hash_lookup.display(),
(hash_start, hash_start + 1),
));
let salt_start = body_offset + stream.pos();
let salt = stream.bytes(8)?;
let salt_hex: String = salt.iter().map(|b| format!("{:02X}", b)).collect();
fields.push(Field::field(
"S2K Salt",
salt_hex,
(salt_start, salt_start + 8),
));
let count_start = body_offset + stream.pos();
let count = stream.octet()?;
let iterations = (16u32 + (count as u32 & 15)) << ((count >> 4) + 6);
fields.push(Field::field(
"S2K Iterations",
format!("{} (encoded: {})", iterations, count),
(count_start, count_start + 1),
));
Ok(S2kSpecifier {
s2k_type,
hash_algorithm: Some(hash_algorithm),
salt: Some(salt),
count: Some(count),
argon2_params: None,
})
}
4 => {
let salt_start = body_offset + stream.pos();
let salt = stream.bytes(16)?;
let salt_hex: String = salt.iter().map(|b| format!("{:02X}", b)).collect();
fields.push(Field::field(
"Argon2 Salt",
salt_hex,
(salt_start, salt_start + 16),
));
let t_start = body_offset + stream.pos();
let parallelism = stream.octet()?;
fields.push(Field::field(
"Argon2 Parallelism (t)",
parallelism.to_string(),
(t_start, t_start + 1),
));
let m_start = body_offset + stream.pos();
let memory = stream.octet()?;
let memory_kb = 1u64 << memory;
fields.push(Field::field(
"Argon2 Memory (m)",
format!("{} KiB (2^{})", memory_kb, memory),
(m_start, m_start + 1),
));
let p_start = body_offset + stream.pos();
let iterations = stream.octet()?;
fields.push(Field::field(
"Argon2 Iterations (p)",
iterations.to_string(),
(p_start, p_start + 1),
));
Ok(S2kSpecifier {
s2k_type,
hash_algorithm: None,
salt: None,
count: None,
argon2_params: Some(Argon2Params {
salt,
parallelism,
memory,
iterations,
}),
})
}
_ => {
Ok(S2kSpecifier {
s2k_type,
hash_algorithm: None,
salt: None,
count: None,
argon2_params: None,
})
}
}
}