• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 //! Decoding functions for PEM-encoded data
2 //!
3 //! A PEM object is a container, which can store (amongst other formats) a public X.509
4 //! Certificate, or a CRL, etc. It contains only printable characters.
5 //! PEM-encoded binary data is essentially a beginning and matching end tag that encloses
6 //! base64-encoded binary data (see:
7 //! <https://en.wikipedia.org/wiki/Privacy-enhanced_Electronic_Mail>).
8 //!
9 //! # Examples
10 //!
11 //! To parse a certificate in PEM format, first create the `Pem` object, then decode
12 //! contents:
13 //!
14 //! ```rust,no_run
15 //! use x509_parser::pem::Pem;
16 //! use x509_parser::x509::X509Version;
17 //!
18 //! static IGCA_PEM: &str = "../assets/IGC_A.pem";
19 //!
20 //! # fn main() {
21 //! let data = std::fs::read(IGCA_PEM).expect("Could not read file");
22 //! for pem in Pem::iter_from_buffer(&data) {
23 //!     let pem = pem.expect("Reading next PEM block failed");
24 //!     let x509 = pem.parse_x509().expect("X.509: decoding DER failed");
25 //!     assert_eq!(x509.tbs_certificate.version, X509Version::V3);
26 //! }
27 //! # }
28 //! ```
29 //!
30 //! This is the most direct method to parse PEM data.
31 //!
32 //! Another method to parse the certificate is to use `parse_x509_pem`:
33 //!
34 //! ```rust,no_run
35 //! use x509_parser::pem::parse_x509_pem;
36 //! use x509_parser::parse_x509_certificate;
37 //!
38 //! static IGCA_PEM: &[u8] = include_bytes!("../assets/IGC_A.pem");
39 //!
40 //! # fn main() {
41 //! let res = parse_x509_pem(IGCA_PEM);
42 //! match res {
43 //!     Ok((rem, pem)) => {
44 //!         assert!(rem.is_empty());
45 //!         //
46 //!         assert_eq!(pem.label, String::from("CERTIFICATE"));
47 //!         //
48 //!         let res_x509 = parse_x509_certificate(&pem.contents);
49 //!         assert!(res_x509.is_ok());
50 //!     },
51 //!     _ => panic!("PEM parsing failed: {:?}", res),
52 //! }
53 //! # }
54 //! ```
55 //!
56 //! Note that all methods require to store the `Pem` object in a variable, mainly because decoding
57 //! the PEM object requires allocation of buffers, and that the lifetime of X.509 certificates will
58 //! be bound to these buffers.
59 
60 use crate::certificate::X509Certificate;
61 use crate::error::{PEMError, X509Error};
62 use crate::parse_x509_certificate;
63 use nom::{Err, IResult};
64 use std::io::{BufRead, Cursor, Seek, SeekFrom};
65 
66 /// Representation of PEM data
67 #[derive(Clone, PartialEq, Debug)]
68 pub struct Pem {
69     /// The PEM label
70     pub label: String,
71     /// The PEM decoded data
72     pub contents: Vec<u8>,
73 }
74 
75 #[deprecated(since = "0.8.3", note = "please use `parse_x509_pem` instead")]
pem_to_der(i: &[u8]) -> IResult<&[u8], Pem, PEMError>76 pub fn pem_to_der(i: &[u8]) -> IResult<&[u8], Pem, PEMError> {
77     parse_x509_pem(i)
78 }
79 
80 /// Read a PEM-encoded structure, and decode the base64 data
81 ///
82 /// Return a structure describing the PEM object: the enclosing tag, and the data.
83 /// Allocates a new buffer for the decoded data.
84 ///
85 /// Note that only the *first* PEM block is decoded. To iterate all blocks from PEM data,
86 /// use [`Pem::iter_from_buffer`].
87 ///
88 /// For X.509 (`CERTIFICATE` tag), the data is a certificate, encoded in DER. To parse the
89 /// certificate content, use `Pem::parse_x509` or `parse_x509_certificate`.
parse_x509_pem(i: &[u8]) -> IResult<&'_ [u8], Pem, PEMError>90 pub fn parse_x509_pem(i: &[u8]) -> IResult<&'_ [u8], Pem, PEMError> {
91     let reader = Cursor::new(i);
92     let res = Pem::read(reader);
93     match res {
94         Ok((pem, bytes_read)) => Ok((&i[bytes_read..], pem)),
95         Err(e) => Err(Err::Error(e)),
96     }
97 }
98 
99 impl Pem {
100     /// Read the next PEM-encoded structure, and decode the base64 data
101     ///
102     /// Returns the certificate (encoded in DER) and the number of bytes read.
103     /// Allocates a new buffer for the decoded data.
104     ///
105     /// Note that a PEM file can contain multiple PEM blocks. This function returns the
106     /// *first* decoded object, starting from the current reader position.
107     /// To get all objects, call this function repeatedly until `PEMError::MissingHeader`
108     /// is returned.
109     ///
110     /// # Examples
111     /// ```
112     /// let file = std::fs::File::open("assets/certificate.pem").unwrap();
113     /// let subject = x509_parser::pem::Pem::read(std::io::BufReader::new(file))
114     ///      .unwrap().0
115     ///     .parse_x509().unwrap()
116     ///     .tbs_certificate.subject.to_string();
117     /// assert_eq!(subject, "CN=lists.for-our.info");
118     /// ```
read(mut r: impl BufRead + Seek) -> Result<(Pem, usize), PEMError>119     pub fn read(mut r: impl BufRead + Seek) -> Result<(Pem, usize), PEMError> {
120         let mut line = String::new();
121         let label = loop {
122             let num_bytes = r.read_line(&mut line)?;
123             if num_bytes == 0 {
124                 // EOF
125                 return Err(PEMError::MissingHeader);
126             }
127             if !line.starts_with("-----BEGIN ") {
128                 line.clear();
129                 continue;
130             }
131             let mut iter = line.split_whitespace();
132             let label = iter.nth(1).ok_or(PEMError::InvalidHeader)?;
133             break label;
134         };
135         let label = label.split('-').next().ok_or(PEMError::InvalidHeader)?;
136         let mut s = String::new();
137         loop {
138             let mut l = String::new();
139             let num_bytes = r.read_line(&mut l)?;
140             if num_bytes == 0 {
141                 return Err(PEMError::IncompletePEM);
142             }
143             if l.starts_with("-----END ") {
144                 // finished reading
145                 break;
146             }
147             s.push_str(l.trim_end());
148         }
149 
150         let contents = base64::decode(&s).or(Err(PEMError::Base64DecodeError))?;
151         let pem = Pem {
152             label: label.to_string(),
153             contents,
154         };
155         Ok((pem, r.seek(SeekFrom::Current(0))? as usize))
156     }
157 
158     /// Decode the PEM contents into a X.509 object
parse_x509(&self) -> Result<X509Certificate, ::nom::Err<X509Error>>159     pub fn parse_x509(&self) -> Result<X509Certificate, ::nom::Err<X509Error>> {
160         parse_x509_certificate(&self.contents).map(|(_, x509)| x509)
161     }
162 
163     /// Returns an iterator over the PEM-encapsulated parts of a buffer
164     ///
165     /// Only the sections enclosed in blocks starting with `-----BEGIN xxx-----`
166     /// and ending with `-----END xxx-----` will be considered.
167     /// Lines before, between or after such blocks will be ignored.
168     ///
169     /// The iterator is fallible: `next()` returns a `Result<Pem, PEMError>` object.
170     /// An error indicates a block is present but invalid.
171     ///
172     /// If the buffer does not contain any block, iterator will be empty.
iter_from_buffer(i: &[u8]) -> PemIterator<Cursor<&[u8]>>173     pub fn iter_from_buffer(i: &[u8]) -> PemIterator<Cursor<&[u8]>> {
174         let reader = Cursor::new(i);
175         PemIterator { reader }
176     }
177 
178     /// Returns an iterator over the PEM-encapsulated parts of a reader
179     ///
180     /// Only the sections enclosed in blocks starting with `-----BEGIN xxx-----`
181     /// and ending with `-----END xxx-----` will be considered.
182     /// Lines before, between or after such blocks will be ignored.
183     ///
184     /// The iterator is fallible: `next()` returns a `Result<Pem, PEMError>` object.
185     /// An error indicates a block is present but invalid.
186     ///
187     /// If the reader does not contain any block, iterator will be empty.
iter_from_reader<R: BufRead + Seek>(reader: R) -> PemIterator<R>188     pub fn iter_from_reader<R: BufRead + Seek>(reader: R) -> PemIterator<R> {
189         PemIterator { reader }
190     }
191 }
192 
193 /// Iterator over PEM-encapsulated blocks
194 ///
195 /// Only the sections enclosed in blocks starting with `-----BEGIN xxx-----`
196 /// and ending with `-----END xxx-----` will be considered.
197 /// Lines before, between or after such blocks will be ignored.
198 ///
199 /// The iterator is fallible: `next()` returns a `Result<Pem, PEMError>` object.
200 /// An error indicates a block is present but invalid.
201 ///
202 /// If the buffer does not contain any block, iterator will be empty.
203 #[allow(missing_debug_implementations)]
204 pub struct PemIterator<Reader: BufRead + Seek> {
205     reader: Reader,
206 }
207 
208 impl<R: BufRead + Seek> Iterator for PemIterator<R> {
209     type Item = Result<Pem, PEMError>;
210 
next(&mut self) -> Option<Self::Item>211     fn next(&mut self) -> Option<Self::Item> {
212         if let Ok(&[]) = self.reader.fill_buf() {
213             return None;
214         }
215         let reader = self.reader.by_ref();
216         let r = Pem::read(reader).map(|(pem, _)| pem);
217         if let Err(PEMError::MissingHeader) = r {
218             None
219         } else {
220             Some(r)
221         }
222     }
223 }
224 
225 #[cfg(test)]
226 mod tests {
227     use super::*;
228 
229     #[test]
read_pem_from_file()230     fn read_pem_from_file() {
231         let file = std::io::BufReader::new(std::fs::File::open("assets/certificate.pem").unwrap());
232         let subject = Pem::read(file)
233             .unwrap()
234             .0
235             .parse_x509()
236             .unwrap()
237             .tbs_certificate
238             .subject
239             .to_string();
240         assert_eq!(subject, "CN=lists.for-our.info");
241     }
242 }
243