blob: 95a943f724941ed6f5224e64cee61247dd068354 [file] [log] [blame]
//! Decoding functions for PEM-encoded data
//!
//! A PEM object is a container, which can store (amongst other formats) a public X.509
//! Certificate, or a CRL, etc. It contains only printable characters.
//! PEM-encoded binary data is essentially a beginning and matching end tag that encloses
//! base64-encoded binary data (see:
//! <https://en.wikipedia.org/wiki/Privacy-enhanced_Electronic_Mail>).
//!
//! # Examples
//!
//! To parse a certificate in PEM format, first create the `Pem` object, then decode
//! contents:
//!
//! ```rust,no_run
//! use x509_parser::pem::Pem;
//! use x509_parser::x509::X509Version;
//!
//! static IGCA_PEM: &str = "../assets/IGC_A.pem";
//!
//! # fn main() {
//! let data = std::fs::read(IGCA_PEM).expect("Could not read file");
//! for pem in Pem::iter_from_buffer(&data) {
//! let pem = pem.expect("Reading next PEM block failed");
//! let x509 = pem.parse_x509().expect("X.509: decoding DER failed");
//! assert_eq!(x509.tbs_certificate.version, X509Version::V3);
//! }
//! # }
//! ```
//!
//! This is the most direct method to parse PEM data.
//!
//! Another method to parse the certificate is to use `parse_x509_pem`:
//!
//! ```rust,no_run
//! use x509_parser::pem::parse_x509_pem;
//! use x509_parser::parse_x509_certificate;
//!
//! static IGCA_PEM: &[u8] = include_bytes!("../assets/IGC_A.pem");
//!
//! # fn main() {
//! let res = parse_x509_pem(IGCA_PEM);
//! match res {
//! Ok((rem, pem)) => {
//! assert!(rem.is_empty());
//! //
//! assert_eq!(pem.label, String::from("CERTIFICATE"));
//! //
//! let res_x509 = parse_x509_certificate(&pem.contents);
//! assert!(res_x509.is_ok());
//! },
//! _ => panic!("PEM parsing failed: {:?}", res),
//! }
//! # }
//! ```
//!
//! Note that all methods require to store the `Pem` object in a variable, mainly because decoding
//! the PEM object requires allocation of buffers, and that the lifetime of X.509 certificates will
//! be bound to these buffers.
use crate::certificate::X509Certificate;
use crate::error::{PEMError, X509Error};
use crate::parse_x509_certificate;
use nom::{Err, IResult};
use std::io::{BufRead, Cursor, Seek, SeekFrom};
/// Representation of PEM data
#[derive(PartialEq, Debug)]
pub struct Pem {
/// The PEM label
pub label: String,
/// The PEM decoded data
pub contents: Vec<u8>,
}
#[deprecated(since = "0.8.3", note = "please use `parse_x509_pem` instead")]
pub fn pem_to_der(i: &[u8]) -> IResult<&[u8], Pem, PEMError> {
parse_x509_pem(i)
}
/// Read a PEM-encoded structure, and decode the base64 data
///
/// Return a structure describing the PEM object: the enclosing tag, and the data.
/// Allocates a new buffer for the decoded data.
///
/// Note that only the *first* PEM block is decoded. To iterate all blocks from PEM data,
/// use [`Pem::iter_from_buffer`].
///
/// For X.509 (`CERTIFICATE` tag), the data is a certificate, encoded in DER. To parse the
/// certificate content, use `Pem::parse_x509` or `parse_x509_certificate`.
pub fn parse_x509_pem(i: &[u8]) -> IResult<&'_ [u8], Pem, PEMError> {
let reader = Cursor::new(i);
let res = Pem::read(reader);
match res {
Ok((pem, bytes_read)) => Ok((&i[bytes_read..], pem)),
Err(e) => Err(Err::Error(e)),
}
}
impl Pem {
/// Read the next PEM-encoded structure, and decode the base64 data
///
/// Returns the certificate (encoded in DER) and the number of bytes read.
/// Allocates a new buffer for the decoded data.
///
/// Note that a PEM file can contain multiple PEM blocks. This function returns the
/// *first* decoded object, starting from the current reader position.
/// To get all objects, call this function repeatedly until `PEMError::MissingHeader`
/// is returned.
///
/// # Examples
/// ```
/// let file = std::fs::File::open("assets/certificate.pem").unwrap();
/// let subject = x509_parser::pem::Pem::read(std::io::BufReader::new(file))
/// .unwrap().0
/// .parse_x509().unwrap()
/// .tbs_certificate.subject.to_string();
/// assert_eq!(subject, "CN=lists.for-our.info");
/// ```
pub fn read(mut r: impl BufRead + Seek) -> Result<(Pem, usize), PEMError> {
let mut line = String::new();
let label = loop {
let num_bytes = r.read_line(&mut line)?;
if num_bytes == 0 {
// EOF
return Err(PEMError::MissingHeader);
}
if !line.starts_with("-----BEGIN ") {
line.clear();
continue;
}
let mut iter = line.split_whitespace();
let label = iter.nth(1).ok_or(PEMError::InvalidHeader)?;
break label;
};
let label = label.split('-').next().ok_or(PEMError::InvalidHeader)?;
let mut s = String::new();
loop {
let mut l = String::new();
let num_bytes = r.read_line(&mut l)?;
if num_bytes == 0 {
return Err(PEMError::IncompletePEM);
}
if l.starts_with("-----END ") {
// finished reading
break;
}
s.push_str(l.trim_end());
}
let contents = base64::decode(&s).or(Err(PEMError::Base64DecodeError))?;
let pem = Pem {
label: label.to_string(),
contents,
};
Ok((pem, r.seek(SeekFrom::Current(0))? as usize))
}
/// Decode the PEM contents into a X.509 object
pub fn parse_x509(&self) -> Result<X509Certificate, ::nom::Err<X509Error>> {
parse_x509_certificate(&self.contents).map(|(_, x509)| x509)
}
/// Returns an iterator over the PEM-encapsulated parts of a buffer
///
/// Only the sections enclosed in blocks starting with `-----BEGIN xxx-----`
/// and ending with `-----END xxx-----` will be considered.
/// Lines before, between or after such blocks will be ignored.
///
/// The iterator is fallible: `next()` returns a `Result<Pem, PEMError>` object.
/// An error indicates a block is present but invalid.
///
/// If the buffer does not contain any block, iterator will be empty.
pub fn iter_from_buffer(i: &[u8]) -> PemIterator<Cursor<&[u8]>> {
let reader = Cursor::new(i);
PemIterator { reader }
}
/// Returns an iterator over the PEM-encapsulated parts of a reader
///
/// Only the sections enclosed in blocks starting with `-----BEGIN xxx-----`
/// and ending with `-----END xxx-----` will be considered.
/// Lines before, between or after such blocks will be ignored.
///
/// The iterator is fallible: `next()` returns a `Result<Pem, PEMError>` object.
/// An error indicates a block is present but invalid.
///
/// If the reader does not contain any block, iterator will be empty.
pub fn iter_from_reader<R: BufRead + Seek>(reader: R) -> PemIterator<R> {
PemIterator { reader }
}
}
/// Iterator over PEM-encapsulated blocks
///
/// Only the sections enclosed in blocks starting with `-----BEGIN xxx-----`
/// and ending with `-----END xxx-----` will be considered.
/// Lines before, between or after such blocks will be ignored.
///
/// The iterator is fallible: `next()` returns a `Result<Pem, PEMError>` object.
/// An error indicates a block is present but invalid.
///
/// If the buffer does not contain any block, iterator will be empty.
#[allow(missing_debug_implementations)]
pub struct PemIterator<Reader: BufRead + Seek> {
reader: Reader,
}
impl<R: BufRead + Seek> Iterator for PemIterator<R> {
type Item = Result<Pem, PEMError>;
fn next(&mut self) -> Option<Self::Item> {
if let Ok(&[]) = self.reader.fill_buf() {
return None;
}
let reader = self.reader.by_ref();
let r = Pem::read(reader).map(|(pem, _)| pem);
if let Err(PEMError::MissingHeader) = r {
None
} else {
Some(r)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn read_pem_from_file() {
let file = std::io::BufReader::new(std::fs::File::open("assets/certificate.pem").unwrap());
let subject = Pem::read(file)
.unwrap()
.0
.parse_x509()
.unwrap()
.tbs_certificate
.subject
.to_string();
assert_eq!(subject, "CN=lists.for-our.info");
}
}