use std::{fmt, io};
use aes_gcm::aead::rand_core::CryptoRng;
use byteorder::{LittleEndian, WriteBytesExt};
use chrono::{SubsecRound, Utc};
use log::debug;
use nom::bytes::streaming::take;
use nom::combinator::{map, map_parser, rest};
use nom::number::streaming::{be_u8, le_u16};
use rand::Rng;
use crate::errors::{IResult, Result};
use crate::packet::{
PacketTrait, Signature, SignatureConfig, SignatureType, Subpacket, SubpacketData,
};
use crate::ser::Serialize;
use crate::types::{KeyVersion, PublicKeyTrait, SecretKeyTrait, SignedUserAttribute, Tag, Version};
use crate::util::{packet_length, write_packet_length};
#[derive(Clone, PartialEq, Eq, derive_more::Debug)]
pub enum UserAttribute {
Image {
packet_version: Version,
#[debug("{}", hex::encode(header))]
header: Vec<u8>,
#[debug("{}", hex::encode(data))]
data: Vec<u8>,
},
Unknown {
packet_version: Version,
typ: u8,
#[debug("{}", hex::encode(data))]
data: Vec<u8>,
},
}
impl UserAttribute {
pub fn from_slice(packet_version: Version, input: &[u8]) -> Result<Self> {
let (_, pk) = parse(packet_version)(input)?;
Ok(pk)
}
pub fn to_u8(&self) -> u8 {
match *self {
UserAttribute::Image { .. } => 1,
UserAttribute::Unknown { typ, .. } => typ,
}
}
pub fn packet_len(&self) -> usize {
match self {
UserAttribute::Image { ref data, .. } => {
1 + 16 + data.len()
}
UserAttribute::Unknown { ref data, .. } => {
1 + data.len()
}
}
}
pub fn sign<R, F>(
&self,
rng: R,
key: &impl SecretKeyTrait,
key_pw: F,
) -> Result<SignedUserAttribute>
where
R: CryptoRng + Rng,
F: FnOnce() -> String,
{
self.sign_third_party(rng, key, key_pw, key)
}
pub fn sign_third_party<R, F>(
&self,
mut rng: R,
signer: &impl SecretKeyTrait,
signer_pw: F,
signee: &impl PublicKeyTrait,
) -> Result<SignedUserAttribute>
where
R: CryptoRng + Rng,
F: FnOnce() -> String,
{
let hashed_subpackets = vec![Subpacket::regular(SubpacketData::SignatureCreationTime(
Utc::now().trunc_subsecs(0),
))];
let unhashed_subpackets = vec![Subpacket::regular(SubpacketData::Issuer(signer.key_id()))];
let mut config = match signer.version() {
KeyVersion::V4 => SignatureConfig::v4(
SignatureType::CertGeneric,
signer.algorithm(),
signer.hash_alg(),
),
KeyVersion::V6 => SignatureConfig::v6(
&mut rng,
SignatureType::CertGeneric,
signer.algorithm(),
signer.hash_alg(),
)?,
v => unsupported_err!("unsupported key version: {:?}", v),
};
config.hashed_subpackets = hashed_subpackets;
config.unhashed_subpackets = unhashed_subpackets;
let sig =
config.sign_certification_third_party(signer, signer_pw, signee, self.tag(), &self)?;
Ok(SignedUserAttribute::new(self.clone(), vec![sig]))
}
pub fn into_signed(self, sig: Signature) -> SignedUserAttribute {
SignedUserAttribute::new(self, vec![sig])
}
}
impl fmt::Display for UserAttribute {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
UserAttribute::Image { data, .. } => {
write!(f, "User Attribute: Image (len: {})", data.len())
}
UserAttribute::Unknown { typ, data, .. } => {
write!(f, "User Attribute: typ: {} (len: {})", typ, data.len())
}
}
}
}
fn image(packet_version: Version) -> impl Fn(&[u8]) -> IResult<&[u8], UserAttribute> {
move |i: &[u8]| {
let (i, len) = le_u16(i)?;
if len < 2 {
return Err(nom::Err::Error(crate::errors::Error::InvalidInput));
}
let (img, header) = take(len - 2)(i)?;
Ok((
&[][..],
UserAttribute::Image {
packet_version,
header: header.to_vec(),
data: img.to_vec(),
},
))
}
}
fn parse(packet_version: Version) -> impl Fn(&[u8]) -> IResult<&[u8], UserAttribute> {
move |i: &[u8]| {
let (i, len) = packet_length(i)?;
if len < 1 {
return Err(nom::Err::Error(crate::errors::Error::InvalidInput));
}
let (i, typ) = be_u8(i)?;
let (i, attr) = map_parser(take(len - 1), |i| match typ {
1 => image(packet_version)(i),
_ => map(rest, |data: &[u8]| UserAttribute::Unknown {
packet_version,
typ,
data: data.to_vec(),
})(i),
})(i)?;
Ok((i, {
debug!("attr with len {}", len);
attr
}))
}
}
impl Serialize for UserAttribute {
fn to_writer<W: io::Write>(&self, writer: &mut W) -> Result<()> {
debug!("write_packet_len {}", self.packet_len());
write_packet_length(self.packet_len(), writer)?;
match self {
UserAttribute::Image {
ref data,
ref header,
..
} => {
writer.write_u8(0x01)?;
writer.write_u16::<LittleEndian>((header.len() + 2).try_into()?)?;
writer.write_all(header)?;
writer.write_all(data)?;
}
UserAttribute::Unknown { ref data, typ, .. } => {
writer.write_u8(*typ)?;
writer.write_all(data)?;
}
}
Ok(())
}
}
impl PacketTrait for UserAttribute {
fn packet_version(&self) -> Version {
match self {
UserAttribute::Image { packet_version, .. } => *packet_version,
UserAttribute::Unknown { packet_version, .. } => *packet_version,
}
}
fn tag(&self) -> Tag {
Tag::UserAttribute
}
}