1 | use std::io::{self, ErrorKind}; |
2 | |
3 | /// The contents of a single recognised block in a PEM file. |
4 | #[non_exhaustive ] |
5 | #[derive (Debug, PartialEq)] |
6 | pub 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 | |
23 | impl 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()) { ... }` |
44 | pub 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. |
129 | fn 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`. |
165 | pub 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 | |
176 | mod 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 | } |
187 | use self::base64::Engine; |
188 | |