sequoia_openpgp/serialize/
cert_armored.rs1use 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
18pub(crate) fn is_printable(c: &char) -> bool {
20 !c.is_control() && !c.is_ascii_control()
24}
25
26impl Cert {
27 pub fn armor_headers(&self) -> Vec<String> {
34 let p = &P::default();
35
36 let length_value = armor::LINE_LENGTH - "Comment: ".len();
37 let mut headers: Vec<String> = self.userids().with_policy(p, None)
39 .filter(|uidb| {
41 !matches!(uidb.revocation_status(), RevocationStatus::Revoked(_))
42 }).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 Some(value.chars().take(length_value).collect())
52 }).collect();
53
54 headers.insert(0, self.fingerprint().to_spaced_hex());
56
57 headers
58 }
59
60 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 pub fn armored(self)
117 -> impl crate::serialize::Serialize + crate::serialize::SerializeInto + 'a
118 {
119 Encoder::new_tsk(self)
120 }
121}
122
123enum Encoder<'a> {
125 Cert(&'a Cert),
126 TSK(TSK<'a>),
127}
128
129impl<'a> Encoder<'a> {
130 fn new(cert: &'a Cert) -> Self {
132 Encoder::Cert(cert)
133 }
134
135 fn new_tsk(tsk: TSK<'a>) -> Self {
137 Encoder::TSK(tsk)
138 }
139
140 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 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 ) * 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; 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 + 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 'é', 'ß', 'ℝ', '💣', '❤', '東', '京', '𝕊', '💝', 'δ',
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 ''
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 let mut buffer = Vec::new();
305 cert.armored()
306 .serialize(&mut buffer)
307 .unwrap();
308
309 let mut cursor = io::Cursor::new(&buffer);
311 let mut reader = Reader::from_reader(
312 &mut cursor, ReaderMode::Tolerant(Some(Kind::PublicKey)));
313
314 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 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 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 let userid2: String = vec!['ß'; length_value + 1].into_iter()
352 .collect();
353 let userid2_expected: String = vec!['ß'; length_value].into_iter()
354 .collect();
355 let userid3: String = vec!['€'; length_value + 1].into_iter()
357 .collect();
358 let userid3_expected: String = vec!['€'; length_value].into_iter()
359 .collect();
360 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 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 let mut buffer = Vec::new();
380 cert.armored()
381 .serialize(&mut buffer)
382 .unwrap();
383
384 let mut cursor = io::Cursor::new(&buffer);
386 let mut reader = Reader::from_reader(
387 &mut cursor, ReaderMode::Tolerant(Some(Kind::PublicKey)));
388
389 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) .collect();
398 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 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}