Coverage Report

Created: 2026-01-16 07:00

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