blob: d36ffe315090cd6443ac76bbca0eadf770381bb7 [file] [log] [blame]
use base64;
use std::io::{self, ErrorKind};
/// The contents of a single recognised block in a PEM file.
#[non_exhaustive]
#[derive(Debug, PartialEq)]
pub enum Item {
/// A DER-encoded x509 certificate.
X509Certificate(Vec<u8>),
/// A DER-encoded plaintext RSA private key; as specified in PKCS#1/RFC3447
RSAKey(Vec<u8>),
/// A DER-encoded plaintext private key; as specified in PKCS#8/RFC5958
PKCS8Key(Vec<u8>),
/// A Sec1-encoded plaintext private key; as specified in RFC5915
ECKey(Vec<u8>),
}
impl Item {
fn from_start_line(start_line: &str, der: Vec<u8>) -> Option<Item> {
match start_line {
"CERTIFICATE" => Some(Item::X509Certificate(der)),
"RSA PRIVATE KEY" => Some(Item::RSAKey(der)),
"PRIVATE KEY" => Some(Item::PKCS8Key(der)),
"EC PRIVATE KEY" => Some(Item::ECKey(der)),
_ => None,
}
}
}
/// Extract and decode the next PEM section from `rd`.
///
/// - Ok(None) is returned if there is no PEM section read from `rd`.
/// - Underlying IO errors produce a `Err(...)`
/// - Otherwise each decoded section is returned with a `Ok(Some(Item::...))`
///
/// You can use this function to build an iterator, for example:
/// `for item in iter::from_fn(|| read_one(rd).transpose()) { ... }`
pub fn read_one(rd: &mut dyn io::BufRead) -> Result<Option<Item>, io::Error> {
let mut b64buf = String::with_capacity(1024);
let mut section_type = None;
let mut end_marker = None;
let mut line = String::with_capacity(80);
loop {
line.clear();
let len = rd.read_line(&mut line)?;
if len == 0 {
// EOF
if end_marker.is_some() {
return Err(io::Error::new(
ErrorKind::InvalidData,
format!("section end {:?} missing", end_marker.unwrap()),
));
}
return Ok(None);
}
if line.starts_with("-----BEGIN ") {
let trailer = line[11..]
.find("-----")
.ok_or_else(|| {
io::Error::new(
ErrorKind::InvalidData,
format!("illegal section start: {:?}", line),
)
})?;
let ty = &line[11..11 + trailer];
section_type = Some(ty.to_string());
end_marker = Some(format!("-----END {}-----", ty).to_string());
continue;
}
if end_marker.is_some() && line.starts_with(end_marker.as_ref().unwrap()) {
let der = base64::decode(&b64buf)
.map_err(|err| io::Error::new(ErrorKind::InvalidData, err))?;
let item = Item::from_start_line(&section_type.unwrap(), der);
if let Some(item) = item {
return Ok(Some(item));
} else {
section_type = None;
end_marker = None;
b64buf.clear();
}
}
if section_type.is_some() {
b64buf.push_str(line.trim());
}
}
}
/// Extract and return all PEM sections by reading `rd`.
pub fn read_all(rd: &mut dyn io::BufRead) -> Result<Vec<Item>, io::Error> {
let mut v = Vec::<Item>::new();
loop {
match read_one(rd)? {
None => return Ok(v),
Some(item) => v.push(item),
}
}
}