use std::{
io::{BufRead, BufReader},
path::Path,
};
use super::{
reader::{
CompressedDataReader, LiteralDataReader, SignatureBodyReader, SignatureOnePassReader,
},
DebugBufRead, MessageReader,
};
use crate::pgp::{
armor::{BlockType, DearmorOptions},
composed::{message::Message, shared::is_binary, Edata, Esk},
errors::{bail, format_err, unimplemented_err, Result},
packet::{ProtectedDataConfig, SymEncryptedProtectedDataConfig},
parsing_reader::BufReadParsing,
types::{PkeskVersion, SkeskVersion, Tag},
};
pub(super) fn next(
mut packets: crate::pgp::packet::PacketParser<MessageReader<'_>>,
is_nested: bool,
) -> Result<Option<Message<'_>>> {
loop {
let Some(packet) = packets.next_owned() else {
return Ok(None);
};
let mut packet = packet?;
let tag = packet.packet_header().tag();
match tag {
Tag::SymKeyEncryptedSessionKey
| Tag::PublicKeyEncryptedSessionKey
| Tag::SymEncryptedData
| Tag::SymEncryptedProtectedData
| Tag::GnupgAeadData => {
let mut esks = Vec::new();
if tag == Tag::SymKeyEncryptedSessionKey || tag == Tag::PublicKeyEncryptedSessionKey
{
let esk = Esk::try_from_reader(&mut packet)?;
esks.push(esk);
} else {
let edata = Edata::try_from_reader(packet)?;
return Ok(Some(Message::Encrypted {
esk: esks, edata,
is_nested,
}));
}
packets = crate::pgp::packet::PacketParser::new(packet.into_inner());
loop {
let Some(packet) = packets.next_owned() else {
bail!("missing encrypted data packet");
};
let mut packet = packet?;
let tag = packet.packet_header().tag();
match tag {
Tag::SymKeyEncryptedSessionKey | Tag::PublicKeyEncryptedSessionKey => {
let esk = Esk::try_from_reader(&mut packet)?;
esks.push(esk);
packets = crate::pgp::packet::PacketParser::new(packet.into_inner());
}
Tag::SymEncryptedData
| Tag::SymEncryptedProtectedData
| Tag::GnupgAeadData => {
let edata = Edata::try_from_reader(packet)?;
let esk = match edata {
Edata::SymEncryptedData { .. } => {
esk_filter(esks, PkeskVersion::V3, &[SkeskVersion::V4])
}
Edata::SymEncryptedProtectedData { ref reader } => {
match reader.config() {
ProtectedDataConfig::Seipd(
SymEncryptedProtectedDataConfig::V1,
) => {
esk_filter(esks, PkeskVersion::V3, &[SkeskVersion::V4])
}
ProtectedDataConfig::Seipd(
SymEncryptedProtectedDataConfig::V2 { .. },
) => {
esk_filter(esks, PkeskVersion::V6, &[SkeskVersion::V6])
}
ProtectedDataConfig::GnupgAead { .. } => {
bail!("GnupgAead config not allowed in SymEncryptedProtectedData")
}
}
}
Edata::GnupgAeadData { ref reader, .. } => match reader.config() {
ProtectedDataConfig::Seipd(_) => {
bail!("Seipd config not allowed in GnupgAeadData");
}
ProtectedDataConfig::GnupgAead { .. } => esk_filter(
esks,
PkeskVersion::V3,
&[SkeskVersion::V4, SkeskVersion::V5],
),
},
};
return Ok(Some(Message::Encrypted {
esk,
edata,
is_nested,
}));
}
Tag::Padding => {
packet.drain()?;
packets = crate::pgp::packet::PacketParser::new(packet.into_inner());
}
Tag::Marker => {
packet.drain()?;
packets = crate::pgp::packet::PacketParser::new(packet.into_inner());
}
_ => {
bail!("unexpected tag in an encrypted message: {:?}", tag);
}
}
}
}
Tag::Signature => {
let signature =
crate::pgp::packet::Signature::try_from_reader(packet.packet_header(), &mut packet)?;
packets = crate::pgp::packet::PacketParser::new(packet.into_inner());
let Some(inner_message) = next(packets, true)? else {
bail!("missing next packet");
};
let reader = SignatureBodyReader::new(signature, Box::new(inner_message))?;
let message = Message::Signed { reader, is_nested };
return Ok(Some(message));
}
Tag::OnePassSignature => {
let one_pass_signature = crate::pgp::packet::OnePassSignature::try_from_reader(
packet.packet_header(),
&mut packet,
)?;
packets = crate::pgp::packet::PacketParser::new(packet.into_inner());
let Some(inner_message) = next(packets, true)? else {
bail!("missing next packet");
};
let reader =
SignatureOnePassReader::new(one_pass_signature, Box::new(inner_message))?;
let message = Message::SignedOnePass { reader, is_nested };
return Ok(Some(message));
}
Tag::CompressedData => {
let reader = CompressedDataReader::new(packet, false)?;
let message = Message::Compressed { reader, is_nested };
return Ok(Some(message));
}
Tag::LiteralData => {
let reader = LiteralDataReader::new(packet)?;
let message = Message::Literal { reader, is_nested };
return Ok(Some(message));
}
Tag::Padding => {
packet.drain()?;
packets = crate::pgp::packet::PacketParser::new(packet.into_inner());
}
Tag::Marker => {
packet.drain()?;
packets = crate::pgp::packet::PacketParser::new(packet.into_inner());
}
Tag::UnassignedNonCritical(_) | Tag::Experimental(_) => {
packet.drain()?;
packets = crate::pgp::packet::PacketParser::new(packet.into_inner());
}
_ => {
bail!("unexpected packet type: {:?}", tag);
}
}
}
}
fn esk_filter(
esk: Vec<Esk>,
pkesk_allowed: PkeskVersion,
skesk_allowed: &[SkeskVersion],
) -> Vec<Esk> {
esk.into_iter()
.filter(|esk| match esk {
Esk::PublicKeyEncryptedSessionKey(pkesk) => pkesk.version() == pkesk_allowed,
Esk::SymKeyEncryptedSessionKey(skesk) => skesk_allowed.contains(&skesk.version()),
})
.collect()
}
impl<'a> Message<'a> {
fn from_packets(packets: crate::pgp::packet::PacketParser<MessageReader<'a>>) -> Result<Self> {
match next(packets, false)? {
Some(message) => Ok(message),
None => {
bail!("no valid OpenPGP message found");
}
}
}
pub fn from_bytes<R: BufRead + std::fmt::Debug + 'a + Send>(source: R) -> Result<Self> {
let source = MessageReader::Reader(Box::new(source) as Box<dyn DebugBufRead>);
let parser = crate::pgp::packet::PacketParser::new(source);
Self::from_packets(parser)
}
pub(super) fn from_edata(edata: Edata<'a>, is_nested: bool) -> Result<Self> {
let source = MessageReader::Edata(Box::new(edata));
Message::internal_from_bytes(source, is_nested)
}
pub(super) fn from_compressed(
reader: CompressedDataReader<MessageReader<'a>>,
is_nested: bool,
) -> Result<Self> {
let source = MessageReader::Compressed(Box::new(reader));
Message::internal_from_bytes(source, is_nested)
}
fn internal_from_bytes(source: MessageReader<'a>, is_nested: bool) -> Result<Self> {
let packets = crate::pgp::packet::PacketParser::new(source);
match next(packets, is_nested)? {
Some(message) => Ok(message),
None => {
bail!("no valid OpenPGP message found");
}
}
}
pub fn from_armor_file<P: AsRef<Path>>(path: P) -> Result<(Self, crate::pgp::armor::Headers)> {
let file = std::fs::File::open(path)?;
Self::from_armor(BufReader::new(file))
}
pub fn from_string(data: &'a str) -> Result<(Self, crate::pgp::armor::Headers)> {
Self::from_armor(data.as_bytes())
}
pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self> {
let file = std::fs::File::open(path)?;
Self::from_bytes(BufReader::new(file))
}
pub fn from_armor<R: BufRead + std::fmt::Debug + 'a + Send>(
input: R,
) -> Result<(Self, crate::pgp::armor::Headers)> {
Self::from_armor_with_options(input, DearmorOptions::default())
}
pub fn from_armor_with_options<R: BufRead + std::fmt::Debug + 'a + Send>(
input: R,
opt: DearmorOptions,
) -> Result<(Self, crate::pgp::armor::Headers)> {
let mut dearmor = crate::pgp::armor::Dearmor::with_options(input, opt);
dearmor.read_header()?;
let typ = dearmor
.typ
.ok_or_else(|| format_err!("dearmor failed to retrieve armor type"))?;
match typ {
BlockType::File | BlockType::Message | BlockType::MultiPartMessage(_, _) => {
let headers = dearmor.headers.clone();
if !Self::matches_block_type(typ) {
bail!("unexpected block type: {}", typ);
}
Ok((Self::from_bytes(BufReader::new(dearmor))?, headers))
}
BlockType::PublicKey
| BlockType::PrivateKey
| BlockType::Signature
| BlockType::CleartextMessage
| BlockType::PublicKeyPKCS1(_)
| BlockType::PublicKeyPKCS8
| BlockType::PublicKeyOpenssh
| BlockType::PrivateKeyPKCS1(_)
| BlockType::PrivateKeyPKCS8
| BlockType::PrivateKeyOpenssh => {
unimplemented_err!("key format {:?}", typ);
}
}
}
pub fn from_reader<R: BufRead + std::fmt::Debug + 'a + Send>(
mut source: R,
) -> Result<(Self, Option<crate::pgp::armor::Headers>)> {
if is_binary(&mut source)? {
let msg = Self::from_bytes(source)?;
Ok((msg, None))
} else {
let (msg, headers) = Self::from_armor(source)?;
Ok((msg, Some(headers)))
}
}
fn matches_block_type(typ: BlockType) -> bool {
matches!(
typ,
BlockType::Message | BlockType::MultiPartMessage(_, _) | BlockType::File
)
}
}