1use std::io::{self, ErrorKind};
2
3/// The contents of a single recognised block in a PEM file.
4#[non_exhaustive]
5#[derive(Debug, PartialEq)]
6pub enum Item {
7 /// A DER-encoded x509 certificate.
8 X509Certificate(Vec<u8>),
9
10 /// A DER-encoded plaintext RSA private key; as specified in PKCS#1/RFC3447
11 RSAKey(Vec<u8>),
12
13 /// A DER-encoded plaintext private key; as specified in PKCS#8/RFC5958
14 PKCS8Key(Vec<u8>),
15
16 /// A Sec1-encoded plaintext private key; as specified in RFC5915
17 ECKey(Vec<u8>),
18
19 /// A Certificate Revocation List; as specified in RFC5280
20 Crl(Vec<u8>),
21}
22
23impl Item {
24 fn from_start_line(start_line: &[u8], der: Vec<u8>) -> Option<Item> {
25 match start_line {
26 b"CERTIFICATE" => Some(Item::X509Certificate(der)),
27 b"RSA PRIVATE KEY" => Some(Item::RSAKey(der)),
28 b"PRIVATE KEY" => Some(Item::PKCS8Key(der)),
29 b"EC PRIVATE KEY" => Some(Item::ECKey(der)),
30 b"X509 CRL" => Some(Item::Crl(der)),
31 _ => None,
32 }
33 }
34}
35
36/// Extract and decode the next PEM section from `rd`.
37///
38/// - Ok(None) is returned if there is no PEM section read from `rd`.
39/// - Underlying IO errors produce a `Err(...)`
40/// - Otherwise each decoded section is returned with a `Ok(Some(Item::...))`
41///
42/// You can use this function to build an iterator, for example:
43/// `for item in iter::from_fn(|| read_one(rd).transpose()) { ... }`
44pub fn read_one(rd: &mut dyn io::BufRead) -> Result<Option<Item>, io::Error> {
45 let mut b64buf = Vec::with_capacity(1024);
46 let mut section = None::<(Vec<_>, Vec<_>)>;
47 let mut line = Vec::with_capacity(80);
48
49 loop {
50 line.clear();
51 let len = read_until_newline(rd, &mut line)?;
52
53 if len == 0 {
54 // EOF
55 return match section {
56 Some((_, end_marker)) => Err(io::Error::new(
57 ErrorKind::InvalidData,
58 format!(
59 "section end {:?} missing",
60 String::from_utf8_lossy(&end_marker)
61 ),
62 )),
63 None => Ok(None),
64 };
65 }
66
67 if line.starts_with(b"-----BEGIN ") {
68 let (mut trailer, mut pos) = (0, line.len());
69 for (i, &b) in line.iter().enumerate().rev() {
70 match b {
71 b'-' => {
72 trailer += 1;
73 pos = i;
74 }
75 b'\n' | b'\r' | b' ' => continue,
76 _ => break,
77 }
78 }
79
80 if trailer != 5 {
81 return Err(io::Error::new(
82 ErrorKind::InvalidData,
83 format!(
84 "illegal section start: {:?}",
85 String::from_utf8_lossy(&line)
86 ),
87 ));
88 }
89
90 let ty = &line[11..pos];
91 let mut end = Vec::with_capacity(10 + 4 + ty.len());
92 end.extend_from_slice(b"-----END ");
93 end.extend_from_slice(ty);
94 end.extend_from_slice(b"-----");
95 section = Some((ty.to_owned(), end));
96 continue;
97 }
98
99 if let Some((section_type, end_marker)) = section.as_ref() {
100 if line.starts_with(end_marker) {
101 let der = base64::ENGINE
102 .decode(&b64buf)
103 .map_err(|err| io::Error::new(ErrorKind::InvalidData, err))?;
104
105 if let Some(item) = Item::from_start_line(section_type, der) {
106 return Ok(Some(item));
107 } else {
108 section = None;
109 b64buf.clear();
110 }
111 }
112 }
113
114 if section.is_some() {
115 let mut trim = 0;
116 for &b in line.iter().rev() {
117 match b {
118 b'\n' | b'\r' | b' ' => trim += 1,
119 _ => break,
120 }
121 }
122 b64buf.extend(&line[..line.len() - trim]);
123 }
124 }
125}
126
127// Ported from https://github.com/rust-lang/rust/blob/91cfcb021935853caa06698b759c293c09d1e96a/library/std/src/io/mod.rs#L1990 and
128// modified to look for our accepted newlines.
129fn read_until_newline<R: io::BufRead + ?Sized>(
130 r: &mut R,
131 buf: &mut Vec<u8>,
132) -> std::io::Result<usize> {
133 let mut read = 0;
134 loop {
135 let (done, used) = {
136 let available = match r.fill_buf() {
137 Ok(n) => n,
138 Err(ref e) if e.kind() == ErrorKind::Interrupted => continue,
139 Err(e) => return Err(e),
140 };
141 match available
142 .iter()
143 .copied()
144 .position(|b| b == b'\n' || b == b'\r')
145 {
146 Some(i) => {
147 buf.extend_from_slice(&available[..=i]);
148 (true, i + 1)
149 }
150 None => {
151 buf.extend_from_slice(available);
152 (false, available.len())
153 }
154 }
155 };
156 r.consume(used);
157 read += used;
158 if done || used == 0 {
159 return Ok(read);
160 }
161 }
162}
163
164/// Extract and return all PEM sections by reading `rd`.
165pub fn read_all(rd: &mut dyn io::BufRead) -> Result<Vec<Item>, io::Error> {
166 let mut v: Vec = Vec::<Item>::new();
167
168 loop {
169 match read_one(rd)? {
170 None => return Ok(v),
171 Some(item: Item) => v.push(item),
172 }
173 }
174}
175
176mod base64 {
177 use base64::alphabet::STANDARD;
178 use base64::engine::general_purpose::{GeneralPurpose, GeneralPurposeConfig};
179 use base64::engine::DecodePaddingMode;
180 pub(super) use base64::engine::Engine;
181
182 pub(super) const ENGINE: GeneralPurpose = GeneralPurpose::new(
183 &STANDARD,
184 config:GeneralPurposeConfig::new().with_decode_padding_mode(DecodePaddingMode::Indifferent),
185 );
186}
187use self::base64::Engine;
188