x509_parser/
pem.rs

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
60use crate::certificate::X509Certificate;
61use crate::error::{PEMError, X509Error};
62use crate::parse_x509_certificate;
63use nom::{Err, IResult};
64use std::io::{BufRead, Cursor, ErrorKind, Seek};
65
66/// Representation of PEM data
67#[derive(Clone, PartialEq, Eq, Debug)]
68pub 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")]
76pub 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`.
90pub 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
99impl 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    /// ```
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 = match r.read_line(&mut line) {
123                Ok(line) => line,
124                Err(e) if e.kind() == ErrorKind::InvalidData => {
125                    // some tools put invalid UTF-8 data in PEM comment section. Ignore line
126                    continue;
127                }
128                Err(e) => {
129                    return Err(e.into());
130                }
131            };
132            if num_bytes == 0 {
133                // EOF
134                return Err(PEMError::MissingHeader);
135            }
136            if !line.starts_with("-----BEGIN ") {
137                line.clear();
138                continue;
139            }
140            let v: Vec<&str> = line.split("-----").collect();
141            if v.len() < 3 || !v[0].is_empty() {
142                return Err(PEMError::InvalidHeader);
143            }
144            let label = v[1].strip_prefix("BEGIN ").ok_or(PEMError::InvalidHeader)?;
145            break label;
146        };
147        let label = label.split('-').next().ok_or(PEMError::InvalidHeader)?;
148        let mut s = String::new();
149        loop {
150            let mut l = String::new();
151            let num_bytes = r.read_line(&mut l)?;
152            if num_bytes == 0 {
153                return Err(PEMError::IncompletePEM);
154            }
155            if l.starts_with("-----END ") {
156                // finished reading
157                break;
158            }
159            s.push_str(l.trim_end());
160        }
161
162        let contents = data_encoding::BASE64
163            .decode(s.as_bytes())
164            .or(Err(PEMError::Base64DecodeError))?;
165        let pem = Pem {
166            label: label.to_string(),
167            contents,
168        };
169        Ok((pem, r.stream_position()? as usize))
170    }
171
172    /// Decode the PEM contents into a X.509 object
173    pub fn parse_x509(&self) -> Result<X509Certificate<'_>, Err<X509Error>> {
174        parse_x509_certificate(&self.contents).map(|(_, x509)| x509)
175    }
176
177    /// Returns an iterator over the PEM-encapsulated parts of a buffer
178    ///
179    /// Only the sections enclosed in blocks starting with `-----BEGIN xxx-----`
180    /// and ending with `-----END xxx-----` will be considered.
181    /// Lines before, between or after such blocks will be ignored.
182    ///
183    /// The iterator is fallible: `next()` returns a `Result<Pem, PEMError>` object.
184    /// An error indicates a block is present but invalid.
185    ///
186    /// If the buffer does not contain any block, iterator will be empty.
187    pub fn iter_from_buffer(i: &[u8]) -> PemIterator<Cursor<&[u8]>> {
188        let reader = Cursor::new(i);
189        PemIterator { reader }
190    }
191
192    /// Returns an iterator over the PEM-encapsulated parts of a reader
193    ///
194    /// Only the sections enclosed in blocks starting with `-----BEGIN xxx-----`
195    /// and ending with `-----END xxx-----` will be considered.
196    /// Lines before, between or after such blocks will be ignored.
197    ///
198    /// The iterator is fallible: `next()` returns a `Result<Pem, PEMError>` object.
199    /// An error indicates a block is present but invalid.
200    ///
201    /// If the reader does not contain any block, iterator will be empty.
202    pub fn iter_from_reader<R: BufRead + Seek>(reader: R) -> PemIterator<R> {
203        PemIterator { reader }
204    }
205}
206
207/// Iterator over PEM-encapsulated blocks
208///
209/// Only the sections enclosed in blocks starting with `-----BEGIN xxx-----`
210/// and ending with `-----END xxx-----` will be considered.
211/// Lines before, between or after such blocks will be ignored.
212///
213/// The iterator is fallible: `next()` returns a `Result<Pem, PEMError>` object.
214/// An error indicates a block is present but invalid.
215///
216/// If the buffer does not contain any block, iterator will be empty.
217#[allow(missing_debug_implementations)]
218pub struct PemIterator<Reader: BufRead + Seek> {
219    reader: Reader,
220}
221
222impl<R: BufRead + Seek> Iterator for PemIterator<R> {
223    type Item = Result<Pem, PEMError>;
224
225    fn next(&mut self) -> Option<Self::Item> {
226        if let Ok(&[]) = self.reader.fill_buf() {
227            return None;
228        }
229        let reader = self.reader.by_ref();
230        let r = Pem::read(reader).map(|(pem, _)| pem);
231        if let Err(PEMError::MissingHeader) = r {
232            None
233        } else {
234            Some(r)
235        }
236    }
237}
238
239#[cfg(test)]
240mod tests {
241    use super::*;
242
243    #[test]
244    fn read_pem_from_file() {
245        let file = std::io::BufReader::new(std::fs::File::open("assets/certificate.pem").unwrap());
246        let subject = Pem::read(file)
247            .unwrap()
248            .0
249            .parse_x509()
250            .unwrap()
251            .tbs_certificate
252            .subject
253            .to_string();
254        assert_eq!(subject, "CN=lists.for-our.info");
255    }
256
257    #[test]
258    fn pem_multi_word_label() {
259        const PEM_BYTES: &[u8] =
260            b"-----BEGIN MULTI WORD LABEL-----\n-----END MULTI WORD LABEL-----";
261        let (_, pem) = parse_x509_pem(PEM_BYTES).expect("should parse pem");
262        assert_eq!(pem.label, "MULTI WORD LABEL");
263    }
264}