Skip to content

Commit

Permalink
pem: recast APIs in terms of Item constructors
Browse files Browse the repository at this point in the history
  • Loading branch information
ctz committed Sep 6, 2024
1 parent 9b29b39 commit a76ad42
Show file tree
Hide file tree
Showing 2 changed files with 144 additions and 139 deletions.
4 changes: 2 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -812,7 +812,7 @@ impl UnixTime {
pub trait DecodePem: Sized {
/// Decode this type from PEM contained in a byte slice.
fn from_pem_slice(pem: &[u8]) -> Result<Self, pem::Error> {
Self::from_pem_items(&mut pem::read_all_from_slice(pem))
Self::from_pem_items(&mut pem::Item::iter_from_slice(pem))
}

/// Decode this type from PEM contained in a `str`.
Expand All @@ -825,7 +825,7 @@ pub trait DecodePem: Sized {
fn from_pem_reader(rd: &mut impl std::io::Read) -> Result<Self, pem::Error> {
let mut items = Vec::new();
// iterate over items to slough off io errors
for item in pem::read_all(&mut std::io::BufReader::new(rd)) {
for item in pem::Item::iter_from_buf(&mut std::io::BufReader::new(rd)) {
match item {
Ok(item) => items.push(Ok(item)),
Err(err) => return Err(err),
Expand Down
279 changes: 142 additions & 137 deletions src/pem.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,164 +55,175 @@ pub enum Item {
Csr(CertificateSigningRequestDer<'static>),
}

/// Iterate over all PEM sections by reading `pem_slice`
pub(crate) fn read_all_from_slice(
pem_slice: &[u8],
) -> impl Iterator<Item = Result<Item, Error>> + '_ {
struct SliceIter<'a> {
current: &'a [u8],
}

impl Iterator for SliceIter<'_> {
type Item = Result<Item, Error>;
impl Item {
/// Extract and decode the next supported PEM section from `input`
///
/// - `Ok(None)` is returned if there is no PEM section to read from `input`
/// - Syntax errors and decoding errors produce a `Err(...)`
/// - Otherwise each decoded section is returned with a `Ok(Some((Item::..., remainder)))` where
/// `remainder` is the part of the `input` that follows the returned section
pub fn from_slice(mut input: &[u8]) -> Result<Option<(Self, &[u8])>, Error> {
let mut b64buf = Vec::with_capacity(1024);
let mut section = None::<(Vec<_>, Vec<_>)>;

loop {
let next_line = if let Some(index) = input.iter().position(|byte| *byte == b'\n') {
let (line, newline_plus_remainder) = input.split_at(index);
input = &newline_plus_remainder[1..];
Some(line)
} else {
None
};

fn next(&mut self) -> Option<Self::Item> {
match read_one_from_slice(self.current) {
Ok(Some((item, rest))) => {
self.current = rest;
Some(Ok(item))
}
Ok(None) => None,
Err(err) => Some(Err(err)),
match Self::read(next_line, &mut section, &mut b64buf)? {
ControlFlow::Continue(()) => continue,
ControlFlow::Break(item) => return Ok(item.map(|item| (item, input))),
}
}
}

SliceIter { current: pem_slice }
}

/// Extract and decode the next PEM section from `input`
///
/// - `Ok(None)` is returned if there is no PEM section to read from `input`
/// - Syntax errors and decoding errors produce a `Err(...)`
/// - Otherwise each decoded section is returned with a `Ok(Some((Item::..., remainder)))` where
/// `remainder` is the part of the `input` that follows the returned section
pub fn read_one_from_slice(mut input: &[u8]) -> Result<Option<(Item, &[u8])>, Error> {
let mut b64buf = Vec::with_capacity(1024);
let mut section = None::<(Vec<_>, Vec<_>)>;

loop {
let next_line = if let Some(index) = input.iter().position(|byte| *byte == b'\n') {
let (line, newline_plus_remainder) = input.split_at(index);
input = &newline_plus_remainder[1..];
Some(line)
} else {
None
};
/// Extract and decode the next supported 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(|| Item::from_buf(rd).transpose()) { ... }` --
/// [`Item::iter_from_buf`] is this.
#[cfg(feature = "std")]
pub fn from_buf(rd: &mut dyn io::BufRead) -> Result<Option<Self>, 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 = read_until_newline(rd, &mut line).map_err(Error::Io)?;

let next_line = if len == 0 {
None
} else {
Some(line.as_slice())
};

match read_one_impl(next_line, &mut section, &mut b64buf)? {
ControlFlow::Continue(()) => continue,
ControlFlow::Break(item) => return Ok(item.map(|item| (item, input))),
match Self::read(next_line, &mut section, &mut b64buf) {
Ok(ControlFlow::Break(opt)) => return Ok(opt),
Ok(ControlFlow::Continue(())) => continue,
Err(e) => return Err(e),
}
}
}
}

/// 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()) { ... }`
#[cfg(feature = "std")]
pub fn read_one(rd: &mut dyn io::BufRead) -> Result<Option<Item>, 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 = read_until_newline(rd, &mut line).map_err(Error::Io)?;
/// Extract and return all PEM sections by reading `rd`.
#[cfg(feature = "std")]
pub fn iter_from_buf(
rd: &mut dyn io::BufRead,
) -> impl Iterator<Item = Result<Self, Error>> + '_ {
iter::from_fn(move || Self::from_buf(rd).transpose())
}

let next_line = if len == 0 {
None
} else {
Some(line.as_slice())
};
/// Iterate over all PEM sections by reading `pem_slice`
pub(crate) fn iter_from_slice(
pem_slice: &[u8],
) -> impl Iterator<Item = Result<Self, Error>> + '_ {
struct SliceIter<'a> {
current: &'a [u8],
}

match read_one_impl(next_line, &mut section, &mut b64buf) {
Ok(ControlFlow::Break(opt)) => return Ok(opt),
Ok(ControlFlow::Continue(())) => continue,
Err(e) => return Err(e),
impl Iterator for SliceIter<'_> {
type Item = Result<Item, Error>;

fn next(&mut self) -> Option<Self::Item> {
match Item::from_slice(self.current) {
Ok(Some((item, rest))) => {
self.current = rest;
Some(Ok(item))
}
Ok(None) => None,
Err(err) => Some(Err(err)),
}
}
}

SliceIter { current: pem_slice }
}
}

fn read_one_impl(
next_line: Option<&[u8]>,
section: &mut Option<(Vec<u8>, Vec<u8>)>,
b64buf: &mut Vec<u8>,
) -> Result<ControlFlow<Option<Item>, ()>, Error> {
let line = if let Some(line) = next_line {
line
} else {
// EOF
return match section.take() {
Some((_, end_marker)) => Err(Error::MissingSectionEnd { end_marker }),
None => Ok(ControlFlow::Break(None)),
fn read(
next_line: Option<&[u8]>,
section: &mut Option<(Vec<u8>, Vec<u8>)>,
b64buf: &mut Vec<u8>,
) -> Result<ControlFlow<Option<Self>, ()>, Error> {
let line = if let Some(line) = next_line {
line
} else {
// EOF
return match section.take() {
Some((_, end_marker)) => Err(Error::MissingSectionEnd { end_marker }),
None => Ok(ControlFlow::Break(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;

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,
}
b'\n' | b'\r' | b' ' => continue,
_ => break,
}
}

if trailer != 5 {
return Err(Error::IllegalSectionStart {
line: line.to_vec(),
});
}
if trailer != 5 {
return Err(Error::IllegalSectionStart {
line: line.to_vec(),
});
}

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));
return Ok(ControlFlow::Continue(()));
}
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));
return Ok(ControlFlow::Continue(()));
}

if let Some((section_type, end_marker)) = section.as_ref() {
if line.starts_with(end_marker) {
let section_type = match SectionType::try_from(&section_type[..]) {
Ok(typ) => typ,
// unhandled section: have caller try again
Err(()) => {
*section = None;
b64buf.clear();
return Ok(ControlFlow::Continue(()));
if let Some((section_type, end_marker)) = section.as_ref() {
if line.starts_with(end_marker) {
let section_type = match SectionType::try_from(&section_type[..]) {
Ok(typ) => typ,
// unhandled section: have caller try again
Err(()) => {
*section = None;
b64buf.clear();
return Ok(ControlFlow::Continue(()));
}
};

let mut der = vec![0u8; base64::decoded_length(b64buf.len())];
let der_len = match section_type.secret() {
true => base64::decode_secret(b64buf, &mut der),
false => base64::decode_public(b64buf, &mut der),
}
};
.map_err(|err| Error::Base64Decode(format!("{err:?}")))?
.len();

let mut der = vec![0u8; base64::decoded_length(b64buf.len())];
let der_len = match section_type.secret() {
true => base64::decode_secret(b64buf, &mut der),
false => base64::decode_public(b64buf, &mut der),
}
.map_err(|err| Error::Base64Decode(format!("{err:?}")))?
.len();
der.truncate(der_len);

der.truncate(der_len);
return Ok(ControlFlow::Break(Some(section_type.item(der))));
}
}

return Ok(ControlFlow::Break(Some(section_type.item(der))));
if section.is_some() {
b64buf.extend(line);
}
}

if section.is_some() {
b64buf.extend(line);
Ok(ControlFlow::Continue(()))
}

Ok(ControlFlow::Continue(()))
}

enum SectionType {
Expand Down Expand Up @@ -324,9 +335,3 @@ fn read_until_newline<R: io::BufRead + ?Sized>(r: &mut R, buf: &mut Vec<u8>) ->
}
}
}

/// Extract and return all PEM sections by reading `rd`.
#[cfg(feature = "std")]
pub fn read_all(rd: &mut dyn io::BufRead) -> impl Iterator<Item = Result<Item, Error>> + '_ {
iter::from_fn(move || read_one(rd).transpose())
}

0 comments on commit a76ad42

Please sign in to comment.