use std::io::{self, ErrorKind};
#[non_exhaustive]
#[derive(Debug, PartialEq)]
pub enum Item {
X509Certificate(Vec<u8>),
RSAKey(Vec<u8>),
PKCS8Key(Vec<u8>),
ECKey(Vec<u8>),
}
impl Item {
fn from_start_line(start_line: &[u8], der: Vec<u8>) -> Option<Item> {
match start_line {
b"CERTIFICATE" => Some(Item::X509Certificate(der)),
b"RSA PRIVATE KEY" => Some(Item::RSAKey(der)),
b"PRIVATE KEY" => Some(Item::PKCS8Key(der)),
b"EC PRIVATE KEY" => Some(Item::ECKey(der)),
_ => None,
}
}
}
pub fn read_one(rd: &mut dyn io::BufRead) -> Result<Option<Item>, io::Error> {
let mut b64buf = Vec::with_capacity(1024);
let mut section = None::<(Vec<_>, Vec<_>)>;
let mut line = Vec::with_capacity(80);
loop {
line.clear();
let len = rd.read_until(b'\n', &mut line)?;
if len == 0 {
return match section {
Some((_, end_marker)) => Err(io::Error::new(
ErrorKind::InvalidData,
format!(
"section end {:?} missing",
String::from_utf8_lossy(&end_marker)
),
)),
None => Ok(None),
};
}
if line.starts_with(b"-----BEGIN ") {
let (mut trailer, mut pos) = (0, line.len());
for (i, &b) in line.iter().enumerate().rev() {
match b {
b'-' => {
trailer += 1;
pos = i;
}
b'\n' | b'\r' | b' ' => continue,
_ => break,
}
}
if trailer != 5 {
return Err(io::Error::new(
ErrorKind::InvalidData,
format!(
"illegal section start: {:?}",
String::from_utf8_lossy(&line)
),
));
}
let ty = &line[11..pos];
let mut end = Vec::with_capacity(10 + 4 + ty.len());
end.extend_from_slice(b"-----END ");
end.extend_from_slice(ty);
end.extend_from_slice(b"-----");
section = Some((ty.to_owned(), end));
continue;
}
if let Some((section_type, end_marker)) = section.as_ref() {
if line.starts_with(end_marker) {
let der = base64::ENGINE
.decode(&b64buf)
.map_err(|err| io::Error::new(ErrorKind::InvalidData, err))?;
if let Some(item) = Item::from_start_line(§ion_type, der) {
return Ok(Some(item));
} else {
section = None;
b64buf.clear();
}
}
}
if section.is_some() {
let mut trim = 0;
for &b in line.iter().rev() {
match b {
b'\n' | b'\r' | b' ' => trim += 1,
_ => break,
}
}
b64buf.extend(&line[..line.len() - trim]);
}
}
}
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),
}
}
}
mod base64 {
use base64::alphabet::STANDARD;
use base64::engine::general_purpose::{GeneralPurpose, GeneralPurposeConfig};
use base64::engine::DecodePaddingMode;
pub(super) use base64::engine::Engine;
pub(super) const ENGINE: GeneralPurpose = GeneralPurpose::new(
&STANDARD,
GeneralPurposeConfig::new().with_decode_padding_mode(DecodePaddingMode::Indifferent),
);
}
use self::base64::Engine;