1 //! Cipher Type Byte (CTB).
2 //!
3 //! The CTB encodes the packet's type and some length information.  It
4 //! has two variants: the so-called old format and the so-called new
5 //! format.  See [Section 4.2 of RFC 4880] for more details.
6 //!
7 //!   [Section 4.2 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-4.2
8 
9 use std::convert::TryFrom;
10 
11 use crate::{
12     packet::Tag,
13     Error,
14     Result
15 };
16 use crate::packet::header::BodyLength;
17 
18 /// Data common to all CTB formats.
19 ///
20 /// OpenPGP defines two packet formats: an old format and a new
21 /// format.  They both include the packet's so-called tag.
22 ///
23 /// See [Section 4.2 of RFC 4880] for more details.
24 ///
25 ///   [Section 4.2 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-4.2
26 #[derive(Clone, Debug)]
27 struct CTBCommon {
28     /// RFC4880 Packet tag
29     tag: Tag,
30 }
31 
32 /// A CTB using the new format encoding.
33 ///
34 /// See [Section 4.2 of RFC 4880] for more details.
35 ///
36 ///   [Section 4.2 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-4.2
37 #[derive(Clone, Debug)]
38 pub struct CTBNew {
39     /// Packet CTB fields
40     common: CTBCommon,
41 }
42 
43 impl CTBNew {
44     /// Constructs a new-style CTB.
new(tag: Tag) -> Self45     pub fn new(tag: Tag) -> Self {
46         CTBNew {
47             common: CTBCommon {
48                 tag,
49             },
50         }
51     }
52 
53     /// Returns the packet's tag.
tag(&self) -> Tag54     pub fn tag(&self) -> Tag {
55         self.common.tag
56     }
57 }
58 
59 /// The length encoded for an old style CTB.
60 ///
61 /// The `PacketLengthType` is only part of the [old CTB], and is
62 /// partially used to determine the packet's size.
63 ///
64 /// See [Section 4.2.1 of RFC 4880] for more details.
65 ///
66 ///   [old CTB]: struct.CTBOld.html
67 ///   [Section 4.2.1 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-4.2.1
68 #[derive(Debug)]
69 #[derive(Clone, Copy, PartialEq)]
70 pub enum PacketLengthType {
71     /// A one-octet Body Length header encodes a length of 0 to 191 octets.
72     ///
73     /// The header is 2 octets long.  It contains the one byte CTB
74     /// followed by the one octet length.
75     OneOctet,
76     /// A two-octet Body Length header encodes a length of 192 to 8383 octets.
77     ///
78     /// The header is 3 octets long.  It contains the one byte CTB
79     /// followed by the two octet length.
80     TwoOctets,
81     /// A four-octet Body Length.
82     ///
83     /// The header is 5 octets long.  It contains the one byte CTB
84     /// followed by the four octet length.
85     FourOctets,
86     /// The packet is of indeterminate length.
87     ///
88     /// Neither the packet header nor the packet itself contain any
89     /// information about the length.  The end of the packet is clear
90     /// from the context, e.g., EOF.
91     Indeterminate,
92 }
93 
94 impl TryFrom<u8> for PacketLengthType {
95     type Error = anyhow::Error;
96 
try_from(u: u8) -> Result<Self>97     fn try_from(u: u8) -> Result<Self> {
98         match u {
99             0 => Ok(PacketLengthType::OneOctet),
100             1 => Ok(PacketLengthType::TwoOctets),
101             2 => Ok(PacketLengthType::FourOctets),
102             3 => Ok(PacketLengthType::Indeterminate),
103             _ => Err(Error::InvalidArgument(
104                 format!("Invalid packet length: {}", u)).into()),
105         }
106     }
107 }
108 
109 impl From<PacketLengthType> for u8 {
from(l: PacketLengthType) -> Self110     fn from(l: PacketLengthType) -> Self {
111         match l {
112             PacketLengthType::OneOctet => 0,
113             PacketLengthType::TwoOctets => 1,
114             PacketLengthType::FourOctets => 2,
115             PacketLengthType::Indeterminate => 3,
116         }
117     }
118 }
119 
120 /// A CTB using the old format encoding.
121 ///
122 /// See [Section 4.2 of RFC 4880] for more details.
123 ///
124 ///   [Section 4.2 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-4.2
125 #[derive(Clone, Debug)]
126 pub struct CTBOld {
127     /// Common CTB fields.
128     common: CTBCommon,
129     /// Type of length specifier.
130     length_type: PacketLengthType,
131 }
132 
133 impl CTBOld {
134     /// Constructs an old-style CTB.
135     ///
136     /// # Errors
137     ///
138     /// Returns [`Error::InvalidArgument`] if the tag or the body
139     /// length cannot be expressed using an old-style CTB.
140     ///
141     /// [`Error::InvalidArgument`]: ../../enum.Error.html#variant.InvalidArgument
new(tag: Tag, length: BodyLength) -> Result<Self>142     pub fn new(tag: Tag, length: BodyLength) -> Result<Self> {
143         let n: u8 = tag.into();
144 
145         // Only tags 0-15 are supported.
146         if n > 15 {
147             return Err(Error::InvalidArgument(
148                 format!("Only tags 0-15 are supported, got: {:?} ({})",
149                         tag, n)).into());
150         }
151 
152         let length_type = match length {
153             // Assume an optimal encoding.
154             BodyLength::Full(l) => {
155                 match l {
156                     // One octet length.
157                     0 ..= 0xFF => PacketLengthType::OneOctet,
158                     // Two octet length.
159                     0x1_00 ..= 0xFF_FF => PacketLengthType::TwoOctets,
160                     // Four octet length,
161                     _ => PacketLengthType::FourOctets,
162                 }
163             },
164             BodyLength::Partial(_) =>
165                 return Err(Error::InvalidArgument(
166                     "Partial body lengths are not support for old format packets".
167                         into()).into()),
168             BodyLength::Indeterminate =>
169                 PacketLengthType::Indeterminate,
170         };
171 
172         Ok(CTBOld {
173             common: CTBCommon {
174                 tag,
175             },
176             length_type,
177         })
178     }
179 
180     /// Returns the packet's tag.
tag(&self) -> Tag181     pub fn tag(&self) -> Tag {
182         self.common.tag
183     }
184 
185     /// Returns the packet's length type.
length_type(&self) -> PacketLengthType186     pub fn length_type(&self) -> PacketLengthType {
187         self.length_type
188     }
189 }
190 
191 /// The CTB variants.
192 ///
193 /// There are two CTB variants: the [old CTB format] and the [new CTB
194 /// format].
195 ///
196 ///   [old CTB format]: struct.CTBOld.html
197 ///   [new CTB format]: struct.CTBNew.html
198 ///
199 /// Note: CTB stands for Cipher Type Byte.
200 #[derive(Clone, Debug)]
201 pub enum CTB {
202     /// New (current) packet header format.
203     New(CTBNew),
204     /// Old PGP 2.6 header format.
205     Old(CTBOld),
206 }
207 
208 impl CTB {
209     /// Constructs a new-style CTB.
new(tag: Tag) -> Self210     pub fn new(tag: Tag) -> Self {
211         CTB::New(CTBNew::new(tag))
212     }
213 
214     /// Returns the packet's tag.
tag(&self) -> Tag215     pub fn tag(&self) -> Tag {
216         match self {
217             CTB::New(c) => c.tag(),
218             CTB::Old(c) => c.tag(),
219         }
220     }
221 }
222 
223 impl TryFrom<u8> for CTB {
224     type Error = anyhow::Error;
225 
226     /// Parses a CTB as described in [Section 4.2 of RFC 4880].  This
227     /// function parses both new and old format CTBs.
228     ///
229     ///   [Section 4.2 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-4.2
try_from(ptag: u8) -> Result<CTB>230     fn try_from(ptag: u8) -> Result<CTB> {
231         // The top bit of the ptag must be set.
232         if ptag & 0b1000_0000 == 0 {
233             return Err(
234                 Error::MalformedPacket(
235                     format!("Malformed CTB: MSB of ptag ({:#010b}) not set{}.",
236                             ptag,
237                             if ptag == '-' as u8 {
238                                 " (ptag is a dash, perhaps this is an \
239                                  ASCII-armor encoded message)"
240                             } else {
241                                 ""
242                             })).into());
243         }
244 
245         let new_format = ptag & 0b0100_0000 != 0;
246         let ctb = if new_format {
247             let tag = ptag & 0b0011_1111;
248             CTB::New(CTBNew {
249                 common: CTBCommon {
250                     tag: tag.into()
251                 }})
252         } else {
253             let tag = (ptag & 0b0011_1100) >> 2;
254             let length_type = ptag & 0b0000_0011;
255 
256             CTB::Old(CTBOld {
257                 common: CTBCommon {
258                     tag: tag.into(),
259                 },
260                 length_type: PacketLengthType::try_from(length_type)?,
261             })
262         };
263 
264         Ok(ctb)
265     }
266 }
267 
268 #[test]
ctb()269 fn ctb() {
270     // 0x99 = public key packet
271     if let CTB::Old(ctb) = CTB::try_from(0x99).unwrap() {
272         assert_eq!(ctb.tag(), Tag::PublicKey);
273         assert_eq!(ctb.length_type, PacketLengthType::TwoOctets);
274     } else {
275         panic!("Expected an old format packet.");
276     }
277 
278     // 0xa3 = old compressed packet
279     if let CTB::Old(ctb) = CTB::try_from(0xa3).unwrap() {
280         assert_eq!(ctb.tag(), Tag::CompressedData);
281         assert_eq!(ctb.length_type, PacketLengthType::Indeterminate);
282     } else {
283         panic!("Expected an old format packet.");
284     }
285 
286     // 0xcb: new literal
287     if let CTB::New(ctb) = CTB::try_from(0xcb).unwrap() {
288         assert_eq!(ctb.tag(), Tag::Literal);
289     } else {
290         panic!("Expected a new format packet.");
291     }
292 }
293