sequoia_openpgp/serialize/
cert_armored.rs

1//! Module to serialize and enarmor a Cert and add informative headers.
2use std::io;
3use std::str;
4
5use crate::armor;
6use crate::cert::{Cert, amalgamation::ValidAmalgamation};
7use crate::Result;
8use crate::types::RevocationStatus;
9use crate::seal;
10use crate::serialize::{
11    Marshal, MarshalInto,
12    generic_serialize_into, generic_export_into,
13    TSK,
14};
15use crate::policy::StandardPolicy as P;
16
17
18/// Whether a character is printable.
19pub(crate) fn is_printable(c: &char) -> bool {
20    // c.is_ascii_alphanumeric || c.is_whitespace || c.is_ascii_punctuation
21    // would exclude any utf8 character, so it seems that to obtain all
22    // printable chars, it works just excluding the control chars.
23    !c.is_control() && !c.is_ascii_control()
24}
25
26impl Cert {
27    /// Creates descriptive armor headers.
28    ///
29    /// Returns armor headers that describe this Cert.  The Cert's
30    /// primary fingerprint and valid userids (according to the
31    /// default policy) are included as comments, so that it is easier
32    /// to identify the Cert when looking at the armored data.
33    pub fn armor_headers(&self) -> Vec<String> {
34        let p = &P::default();
35
36        let length_value = armor::LINE_LENGTH - "Comment: ".len();
37        // Create a header per userid.
38        let mut headers: Vec<String> = self.userids().with_policy(p, None)
39            // Ignore revoked userids.
40            .filter(|uidb| {
41                !matches!(uidb.revocation_status(), RevocationStatus::Revoked(_))
42            // Ignore userids with non-printable characters.
43            }).filter_map(|uidb| {
44                let value = str::from_utf8(uidb.userid().value()).ok()?;
45                for c in value.chars().take(length_value) {
46                    if !is_printable(&c){
47                        return None;
48                    }
49                }
50                // Make sure the line length does not exceed armor::LINE_LENGTH
51                Some(value.chars().take(length_value).collect())
52            }).collect();
53
54        // Add the fingerprint to the front.
55        headers.insert(0, self.fingerprint().to_spaced_hex());
56
57        headers
58    }
59
60    /// Wraps this Cert in an armor structure when serialized.
61    ///
62    /// Derives an object from this `Cert` that adds an armor structure
63    /// to the serialized `Cert` when it is serialized.  Additionally,
64    /// the `Cert`'s User IDs are added as comments, so that it is easier
65    /// to identify the Cert when looking at the armored data.
66    ///
67    /// # Examples
68    ///
69    /// ```rust
70    /// use sequoia_openpgp as openpgp;
71    /// use openpgp::cert::prelude::*;
72    /// use openpgp::serialize::SerializeInto;
73    ///
74    /// # fn main() -> openpgp::Result<()> {
75    /// let (cert, _) =
76    ///     CertBuilder::general_purpose(Some("Mr. Pink ☮☮☮"))
77    ///     .generate()?;
78    /// let armored = String::from_utf8(cert.armored().to_vec()?)?;
79    ///
80    /// assert!(armored.starts_with("-----BEGIN PGP PUBLIC KEY BLOCK-----"));
81    /// assert!(armored.contains("Mr. Pink ☮☮☮"));
82    /// # Ok(()) }
83    /// ```
84    pub fn armored(&self)
85        -> impl crate::serialize::Serialize + crate::serialize::SerializeInto + '_
86    {
87        Encoder::new(self)
88    }
89}
90
91impl<'a> TSK<'a> {
92    /// Wraps this TSK in an armor structure when serialized.
93    ///
94    /// Derives an object from this `TSK` that adds an armor structure
95    /// to the serialized `TSK` when it is serialized.  Additionally,
96    /// the `TSK`'s User IDs are added as comments, so that it is easier
97    /// to identify the `TSK` when looking at the armored data.
98    ///
99    /// # Examples
100    ///
101    /// ```rust
102    /// use sequoia_openpgp as openpgp;
103    /// use openpgp::cert::prelude::*;
104    /// use openpgp::serialize::SerializeInto;
105    ///
106    /// # fn main() -> openpgp::Result<()> {
107    /// let (cert, _) =
108    ///     CertBuilder::general_purpose(Some("Mr. Pink ☮☮☮"))
109    ///     .generate()?;
110    /// let armored = String::from_utf8(cert.as_tsk().armored().to_vec()?)?;
111    ///
112    /// assert!(armored.starts_with("-----BEGIN PGP PRIVATE KEY BLOCK-----"));
113    /// assert!(armored.contains("Mr. Pink ☮☮☮"));
114    /// # Ok(()) }
115    /// ```
116    pub fn armored(self)
117        -> impl crate::serialize::Serialize + crate::serialize::SerializeInto + 'a
118    {
119        Encoder::new_tsk(self)
120    }
121}
122
123/// A `Cert` or `TSK` to be armored and serialized.
124enum Encoder<'a> {
125    Cert(&'a Cert),
126    TSK(TSK<'a>),
127}
128
129impl<'a> Encoder<'a> {
130    /// Returns a new Encoder to enarmor and serialize a `Cert`.
131    fn new(cert: &'a Cert) -> Self {
132        Encoder::Cert(cert)
133    }
134
135    /// Returns a new Encoder to enarmor and serialize a `TSK`.
136    fn new_tsk(tsk: TSK<'a>) -> Self {
137        Encoder::TSK(tsk)
138    }
139
140    /// Returns the cert's primary key version.
141    fn primary_key_version(&self) -> u8 {
142        match self {
143            Encoder::Cert(cert) => cert.primary_key().key().version(),
144            Encoder::TSK(tsk) => tsk.cert.primary_key().key().version(),
145        }
146    }
147
148    fn serialize_common(&self, o: &mut dyn io::Write, export: bool)
149                        -> Result<()> {
150        if export {
151            let exportable = match self {
152                Encoder::Cert(cert) => cert.exportable(),
153                Encoder::TSK(tsk) => tsk.cert.exportable(),
154            };
155            if ! exportable {
156                return Ok(());
157            }
158        }
159
160        let (prelude, headers) = match self {
161            Encoder::Cert(cert) =>
162                (armor::Kind::PublicKey, cert.armor_headers()),
163            Encoder::TSK(tsk) => if tsk.emits_secret_key_packets() {
164                (armor::Kind::SecretKey, tsk.cert.armor_headers())
165            } else {
166                (armor::Kind::PublicKey, tsk.cert.armor_headers())
167            },
168        };
169
170        // Convert the Vec<String> into Vec<(&str, &str)>
171        // `iter_into` can not be used here because will take ownership and
172        // what is needed is the reference.
173        let headers: Vec<_> = headers.iter()
174            .map(|value| ("Comment", value.as_str()))
175            .collect();
176
177        let mut w =
178            armor::Writer::with_headers(o, prelude, headers)?;
179
180        if self.primary_key_version() > 4 {
181            w.set_profile(crate::Profile::RFC9580)?;
182        } else {
183            w.set_profile(crate::Profile::RFC4880)?;
184        }
185
186        if export {
187            match self {
188                Encoder::Cert(cert) => cert.export(&mut w)?,
189                Encoder::TSK(ref tsk) => tsk.export(&mut w)?,
190            }
191        } else {
192            match self {
193                Encoder::Cert(cert) => cert.serialize(&mut w)?,
194                Encoder::TSK(ref tsk) => tsk.serialize(&mut w)?,
195            }
196        }
197        w.finalize()?;
198        Ok(())
199    }
200}
201
202impl<'a> crate::serialize::Serialize for Encoder<'a> {}
203impl<'a> seal::Sealed for Encoder<'a> {}
204impl<'a> Marshal for Encoder<'a> {
205    fn serialize(&self, o: &mut dyn io::Write) -> Result<()> {
206        self.serialize_common(o, false)
207    }
208
209    fn export(&self, o: &mut dyn io::Write) -> Result<()> {
210        self.serialize_common(o, true)
211    }
212}
213
214impl<'a> crate::serialize::SerializeInto for Encoder<'a> {}
215
216impl<'a> MarshalInto for Encoder<'a> {
217    fn serialized_len(&self) -> usize {
218        let h = match self {
219            Encoder::Cert(cert) => cert.armor_headers(),
220            Encoder::TSK(ref tsk) => tsk.cert.armor_headers(),
221        };
222        let headers_len =
223            ("Comment: ".len() + 1 /* NL */) * h.len()
224            + h.iter().map(|c| c.len()).sum::<usize>();
225        let body_len = (match self {
226            Self::Cert(cert) => cert.serialized_len(),
227            Self::TSK(ref tsk) => tsk.serialized_len(),
228        } + 2) / 3 * 4; // base64
229
230        let crc_len = if self.primary_key_version() > 4 {
231            0
232        } else {
233            "=FUaG\n".len()
234        };
235
236        let word = match self {
237            Self::Cert(_) => "PUBLIC",
238            Self::TSK(tsk) => if tsk.emits_secret_key_packets() {
239                "PRIVATE"
240            } else {
241                "PUBLIC"
242            },
243        }.len();
244
245        "-----BEGIN PGP ".len() + word + " KEY BLOCK-----\n\n".len()
246            + headers_len
247            + body_len
248            + (body_len + armor::LINE_LENGTH - 1) / armor::LINE_LENGTH // NLs
249            + crc_len
250            + "-----END PGP ".len() + word + " KEY BLOCK-----\n".len()
251    }
252
253    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
254        generic_serialize_into(self, self.serialized_len(), buf)
255    }
256
257    fn export_into(&self, buf: &mut [u8]) -> Result<usize> {
258        generic_export_into(self, self.serialized_len(), buf)
259    }
260}
261
262
263#[cfg(test)]
264mod tests {
265    use crate::armor::{Kind, Reader, ReaderMode};
266    use crate::cert::prelude::*;
267    use crate::parse::Parse;
268
269    use super::*;
270
271    #[test]
272    fn is_printable_succeed() {
273        let chars: Vec<char> = vec![
274            'a', 'z', 'A', 'Z', '1', '9', '0',
275            '|', '!', '#', '$', '%', '^', '&', '*', '-', '+', '/',
276            // The following Unicode characters were taken from:
277            // https://doc.rust-lang.org/std/primitive.char.html
278            'é', 'ß', 'ℝ', '💣', '❤', '東', '京', '𝕊', '💝', 'δ',
279            'Δ', '中', '越', '٣', '7', '৬', '¾', '①', 'K',
280            'و', '藏', '山', 'I', 'ï', 'İ', 'i'
281        ];
282        for c in &chars {
283            assert!(is_printable(c));
284        }
285    }
286
287    #[test]
288    fn is_printable_fail() {
289        let chars: Vec<char> = vec![
290            '\n', 0x1b_u8.into(),
291            // U+009C, STRING TERMINATOR
292            'œ'
293        ];
294        for c in &chars {
295            assert!(!is_printable(c));
296        }
297    }
298
299    #[test]
300    fn serialize_succeed() {
301        let cert = Cert::from_bytes(crate::tests::key("neal.pgp")).unwrap();
302
303        // Enarmor the Cert.
304        let mut buffer = Vec::new();
305        cert.armored()
306            .serialize(&mut buffer)
307            .unwrap();
308
309        // Parse the armor.
310        let mut cursor = io::Cursor::new(&buffer);
311        let mut reader = Reader::from_reader(
312            &mut cursor, ReaderMode::Tolerant(Some(Kind::PublicKey)));
313
314        // Extract the headers.
315        let mut headers: Vec<&str> = reader.headers()
316            .unwrap()
317            .into_iter()
318            .map(|header| {
319                assert_eq!(&header.0[..], "Comment");
320                &header.1[..]})
321            .collect();
322        headers.sort();
323
324        // Ensure the headers are correct
325        let mut expected_headers = [
326            "Neal H. Walfield <neal@walfield.org>",
327            "Neal H. Walfield <neal@gnupg.org>",
328            "Neal H. Walfield <neal@pep-project.org>",
329            "Neal H. Walfield <neal@pep.foundation>",
330            "Neal H. Walfield <neal@sequoia-pgp.org>",
331            "8F17 7771 18A3 3DDA 9BA4  8E62 AACB 3243 6300 52D9"];
332        expected_headers.sort();
333
334        assert_eq!(&expected_headers[..], &headers[..]);
335    }
336
337    #[test]
338    fn serialize_length_succeed() {
339        let length_value = armor::LINE_LENGTH - "Comment: ".len();
340
341        // Create userids one character longer than the size allowed in the
342        // header and expect headers with the correct length.
343        // 1 byte character
344        // Can not use `to_string` here because not such method for
345        //`std::vec::Vec<char>`
346        let userid1: String = vec!['a'; length_value + 1].into_iter()
347            .collect();
348        let userid1_expected: String = vec!['a'; length_value].into_iter()
349            .collect();
350        // 2 bytes character.
351        let userid2: String = vec!['ß'; length_value + 1].into_iter()
352            .collect();
353        let userid2_expected: String = vec!['ß'; length_value].into_iter()
354            .collect();
355        // 3 bytes character.
356        let userid3: String = vec!['€'; length_value + 1].into_iter()
357            .collect();
358        let userid3_expected: String = vec!['€'; length_value].into_iter()
359            .collect();
360        // 4 bytes character.
361        let userid4: String = vec!['𐍈'; length_value + 1].into_iter()
362            .collect();
363        let userid4_expected: String = vec!['𐍈'; length_value].into_iter()
364            .collect();
365        let mut userid5 = vec!['a'; length_value];
366        userid5[length_value-1] = 'ß';
367        let userid5: String = userid5.into_iter().collect();
368
369        // Create a Cert with the userids.
370        let (cert, _) = CertBuilder::general_purpose(Some(&userid1[..]))
371            .add_userid(&userid2[..])
372            .add_userid(&userid3[..])
373            .add_userid(&userid4[..])
374            .add_userid(&userid5[..])
375            .generate()
376            .unwrap();
377
378        // Enarmor the Cert.
379        let mut buffer = Vec::new();
380        cert.armored()
381            .serialize(&mut buffer)
382            .unwrap();
383
384        // Parse the armor.
385        let mut cursor = io::Cursor::new(&buffer);
386        let mut reader = Reader::from_reader(
387            &mut cursor, ReaderMode::Tolerant(Some(Kind::PublicKey)));
388
389        // Extract the headers.
390        let mut headers: Vec<&str> = reader.headers()
391            .unwrap()
392            .into_iter()
393            .map(|header| {
394                assert_eq!(&header.0[..], "Comment");
395                &header.1[..]})
396            .skip(1) // Ignore the first header since it is the fingerprint
397            .collect();
398        // Cert canonicalization does not preserve the order of
399        // userids.
400        headers.sort();
401
402        let mut headers_iter = headers.into_iter();
403        assert_eq!(headers_iter.next().unwrap(), &userid1_expected);
404        assert_eq!(headers_iter.next().unwrap(), &userid5);
405        assert_eq!(headers_iter.next().unwrap(), &userid2_expected);
406        assert_eq!(headers_iter.next().unwrap(), &userid3_expected);
407        assert_eq!(headers_iter.next().unwrap(), &userid4_expected);
408    }
409
410    #[test]
411    fn serialize_into() {
412        let cert = Cert::from_bytes(crate::tests::key("neal.pgp")).unwrap();
413        let mut v = Vec::new();
414        cert.armored().serialize(&mut v).unwrap();
415        let v_ = cert.armored().to_vec().unwrap();
416        assert_eq!(v, v_);
417
418        // Test truncation.
419        let mut v = vec![0; cert.armored().serialized_len() - 1];
420        let r = cert.armored().serialize_into(&mut v[..]);
421        assert_match!(
422            crate::Error::InvalidArgument(_) =
423                r.unwrap_err().downcast().expect("not an openpgp::Error"));
424    }
425}