cmail_rpgp/types/
packet.rs

1use std::io;
2
3use byteorder::{BigEndian, WriteBytesExt};
4use log::debug;
5use num_enum::{FromPrimitive, IntoPrimitive, TryFromPrimitive};
6
7use crate::errors::Result;
8
9/// Represents a Packet. A packet is the record structure used to encode a chunk of data in OpenPGP.
10/// Ref: <https://www.rfc-editor.org/rfc/rfc9580.html#name-packet-syntax>
11#[derive(Debug, PartialEq, Eq, Clone)]
12pub struct Packet {
13    /// Indicator if this is an old or new versioned packet
14    pub version: Version,
15    /// Denotes the type of data this packet holds
16    pub tag: Tag,
17    /// The raw bytes of the packet
18    pub body: Vec<u8>,
19}
20
21/// Represents the packet length.
22#[derive(Debug, PartialEq, Eq, Clone)]
23pub enum PacketLength {
24    Fixed(usize),
25    Indeterminate,
26    Partial(usize),
27}
28
29impl From<usize> for PacketLength {
30    fn from(val: usize) -> PacketLength {
31        PacketLength::Fixed(val)
32    }
33}
34
35/// Packet Type ID, see <https://www.rfc-editor.org/rfc/rfc9580.html#packet-types>
36///
37/// The "Packet Type ID" was called "Packet tag" in RFC 4880 (Section 4.3 "Packet Tags").
38/// Ref <https://www.rfc-editor.org/rfc/rfc9580.html#appendix-B.1-3.7.1>
39///
40/// However, rPGP will continue to use the term "(Packet) Tag" for the time being.
41#[derive(Debug, PartialEq, Eq, Clone, Copy, FromPrimitive, IntoPrimitive)]
42#[repr(u8)]
43pub enum Tag {
44    /// Public-Key Encrypted Session Key Packet
45    PublicKeyEncryptedSessionKey = 1,
46    /// Signature Packet
47    Signature = 2,
48    /// Symmetric-Key Encrypted Session Key Packet
49    SymKeyEncryptedSessionKey = 3,
50    /// One-Pass Signature Packet
51    OnePassSignature = 4,
52    /// Secret-Key Packet
53    SecretKey = 5,
54    /// Public-Key Packet
55    PublicKey = 6,
56    /// Secret-Subkey Packet
57    SecretSubkey = 7,
58    /// Compressed Data Packet
59    CompressedData = 8,
60    /// Symmetrically Encrypted Data Packet
61    SymEncryptedData = 9,
62    /// Marker Packet
63    Marker = 10,
64    /// Literal Data Packet
65    LiteralData = 11,
66    /// Trust Packet
67    Trust = 12,
68    /// User ID Packet
69    UserId = 13,
70    /// Public-Subkey Packet
71    PublicSubkey = 14,
72    /// User Attribute Packet
73    UserAttribute = 17,
74    /// Sym. Encrypted and Integrity Protected Data Packet
75    SymEncryptedProtectedData = 18,
76    /// Modification Detection Code Packet
77    ModDetectionCode = 19,
78    /// Padding Packet
79    Padding = 21,
80
81    #[num_enum(catch_all)]
82    Other(u8),
83}
84
85impl Tag {
86    /// Packet Type ID encoded in OpenPGP format
87    /// (bits 7 and 6 set, bits 5-0 carry the packet type ID)
88    pub fn encode(self) -> u8 {
89        let t: u8 = self.into();
90        0b1100_0000 | t
91    }
92}
93
94/// The version of the packet format.
95///
96/// There are two packet formats
97/// (see <https://www.rfc-editor.org/rfc/rfc9580.html#name-packet-headers>):
98///
99/// 1) the (current) OpenPGP packet format specified by this document and its
100///    predecessors RFC 4880 and RFC 2440 and
101///
102/// 2) the Legacy packet format as used by implementations predating any IETF specification of OpenPGP.
103#[derive(Debug, PartialEq, Eq, Clone, Copy, TryFromPrimitive)]
104#[repr(u8)]
105#[derive(Default)]
106pub enum Version {
107    /// Old Packet Format ("Legacy packet format")
108    Old = 0,
109    /// New Packet Format ("OpenPGP packet format")
110    #[default]
111    New = 1,
112}
113
114impl Version {
115    pub fn write_header(self, writer: &mut impl io::Write, tag: u8, len: usize) -> Result<()> {
116        debug!("write_header {:?} {} {}", self, tag, len);
117
118        match self {
119            Version::Old => {
120                if len < 256 {
121                    // one octet
122                    writer.write_u8(0b1000_0000 | tag << 2)?;
123                    writer.write_u8(len.try_into()?)?;
124                } else if len < 65536 {
125                    // two octets
126                    writer.write_u8(0b1000_0001 | tag << 2)?;
127                    writer.write_u16::<BigEndian>(len as u16)?;
128                } else {
129                    // four octets
130                    writer.write_u8(0b1000_0010 | tag << 2)?;
131                    writer.write_u32::<BigEndian>(len as u32)?;
132                }
133            }
134            Version::New => {
135                writer.write_u8(0b1100_0000 | tag)?;
136                if len < 192 {
137                    writer.write_u8(len.try_into()?)?;
138                } else if len < 8384 {
139                    writer.write_u8((((len - 192) >> 8) + 192) as u8)?;
140                    writer.write_u8(((len - 192) & 0xFF) as u8)?;
141                } else {
142                    writer.write_u8(255)?;
143                    writer.write_u32::<BigEndian>(len as u32)?;
144                }
145            }
146        }
147
148        Ok(())
149    }
150}
151
152// TODO: find a better place for this
153#[derive(Debug, PartialEq, Eq, Clone, Copy, FromPrimitive, IntoPrimitive)]
154#[repr(u8)]
155pub enum KeyVersion {
156    V2 = 2,
157    V3 = 3,
158    V4 = 4,
159    V5 = 5,
160    V6 = 6,
161
162    #[num_enum(catch_all)]
163    Other(u8),
164}
165
166impl KeyVersion {
167    /// Size of OpenPGP fingerprint in bytes
168    /// (returns `None` for unknown versions)
169    pub const fn fingerprint_len(&self) -> Option<usize> {
170        match self {
171            KeyVersion::V2 | KeyVersion::V3 => Some(16), // MD5
172            KeyVersion::V4 => Some(20),                  // SHA1
173            KeyVersion::V5 | KeyVersion::V6 => Some(32), // SHA256
174            KeyVersion::Other(_) => None,
175        }
176    }
177}
178
179impl Default for KeyVersion {
180    fn default() -> Self {
181        Self::V4
182    }
183}
184
185#[derive(Debug, PartialEq, Eq, Clone, Copy, FromPrimitive, IntoPrimitive)]
186#[repr(u8)]
187pub enum PkeskVersion {
188    V3 = 3,
189    V6 = 6,
190
191    #[num_enum(catch_all)]
192    Other(u8),
193}
194
195#[derive(Debug, PartialEq, Eq, Clone, Copy, FromPrimitive, IntoPrimitive)]
196#[repr(u8)]
197pub enum SkeskVersion {
198    V4 = 4,
199    V6 = 6,
200
201    #[num_enum(catch_all)]
202    Other(u8),
203}
204
205#[cfg(test)]
206mod tests {
207    #![allow(clippy::unwrap_used)]
208
209    use super::*;
210
211    #[test]
212    fn test_write_header() {
213        let mut buf = Vec::new();
214        Version::New
215            .write_header(&mut buf, Tag::UserAttribute.into(), 12875)
216            .unwrap();
217
218        assert_eq!(hex::encode(buf), "d1ff0000324b");
219
220        let mut buf = Vec::new();
221        Version::New
222            .write_header(&mut buf, Tag::Signature.into(), 302)
223            .unwrap();
224
225        assert_eq!(hex::encode(buf), "c2c06e");
226
227        let mut buf = Vec::new();
228        Version::New
229            .write_header(&mut buf, Tag::Signature.into(), 303)
230            .unwrap();
231
232        assert_eq!(hex::encode(buf), "c2c06f");
233    }
234}