use crate::certificate::X509Certificate;
use crate::error::{PEMError, X509Error};
use crate::parse_x509_certificate;
use nom::{Err, IResult};
use std::io::{BufRead, Cursor, ErrorKind, Seek};
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct Pem {
pub label: String,
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)
}
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 {
pub fn read(mut r: impl BufRead + Seek) -> Result<(Pem, usize), PEMError> {
let mut line = String::new();
let label = loop {
let num_bytes = match r.read_line(&mut line) {
Ok(line) => line,
Err(e) if e.kind() == ErrorKind::InvalidData => {
continue;
}
Err(e) => {
return Err(e.into());
}
};
if num_bytes == 0 {
return Err(PEMError::MissingHeader);
}
if !line.starts_with("-----BEGIN ") {
line.clear();
continue;
}
let v: Vec<&str> = line.split("-----").collect();
if v.len() < 3 || !v[0].is_empty() {
return Err(PEMError::InvalidHeader);
}
let label = v[1].strip_prefix("BEGIN ").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 ") {
break;
}
s.push_str(l.trim_end());
}
let contents = data_encoding::BASE64
.decode(s.as_bytes())
.or(Err(PEMError::Base64DecodeError))?;
let pem = Pem {
label: label.to_string(),
contents,
};
Ok((pem, r.stream_position()? as usize))
}
pub fn parse_x509(&self) -> Result<X509Certificate<'_>, Err<X509Error>> {
parse_x509_certificate(&self.contents).map(|(_, x509)| x509)
}
pub fn iter_from_buffer(i: &[u8]) -> PemIterator<Cursor<&[u8]>> {
let reader = Cursor::new(i);
PemIterator { reader }
}
pub fn iter_from_reader<R: BufRead + Seek>(reader: R) -> PemIterator<R> {
PemIterator { reader }
}
}
#[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");
}
#[test]
fn pem_multi_word_label() {
const PEM_BYTES: &[u8] =
b"-----BEGIN MULTI WORD LABEL-----\n-----END MULTI WORD LABEL-----";
let (_, pem) = parse_x509_pem(PEM_BYTES).expect("should parse pem");
assert_eq!(pem.label, "MULTI WORD LABEL");
}
}