From 0227a3ab699b70dcef0ee0a7f0d341cf6e01e488 Mon Sep 17 00:00:00 2001 From: Jorge Aparicio Date: Thu, 9 Nov 2023 13:35:49 +0100 Subject: [PATCH] add `read_one_from_slice` --- src/lib.rs | 2 +- src/pemfile.rs | 26 ++++++++++++++ src/tests.rs | 95 +++++++++++++++++++++++++++++++++----------------- 3 files changed, 90 insertions(+), 33 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 3cf31f9..667edb2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -51,7 +51,7 @@ mod tests; /// --- Main crate APIs: mod pemfile; -pub use pemfile::{read_all, read_one, Error, Item}; +pub use pemfile::{read_all, read_one, read_one_from_slice, Error, Item}; use pki_types::PrivateKeyDer; use pki_types::{ CertificateDer, CertificateRevocationListDer, PrivatePkcs1KeyDer, PrivatePkcs8KeyDer, diff --git a/src/pemfile.rs b/src/pemfile.rs index 63bd1fa..c09bc8c 100644 --- a/src/pemfile.rs +++ b/src/pemfile.rs @@ -60,6 +60,32 @@ pub enum Error { Base64Decode(String), } +/// 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, 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 + }; + + match read_one_impl(next_line, &mut section, &mut b64buf)? { + ControlFlow::Continue(()) => continue, + ControlFlow::Break(item) => return Ok(item.map(|item| (item, input))), + } + } +} + /// Extract and decode the next PEM section from `rd`. /// /// - Ok(None) is returned if there is no PEM section read from `rd`. diff --git a/src/tests.rs b/src/tests.rs index 90826c7..3c32998 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -1,100 +1,131 @@ #[cfg(test)] mod unit { - fn check(data: &[u8]) -> Result, std::io::Error> { - let mut reader = std::io::BufReader::new(data); - crate::read_all(&mut reader).collect() - } + use crate::{Error, Item}; #[test] fn skips_leading_junk() { assert_eq!( - check( + check_both( b"junk\n\ -----BEGIN RSA PRIVATE KEY-----\n\ qw\n\ -----END RSA PRIVATE KEY-----\n" - ) - .unwrap(), - vec![crate::Item::Pkcs1Key(vec![0xab].into())] + ), + vec![Item::Pkcs1Key(vec![0xab].into())] ); } #[test] fn skips_trailing_junk() { assert_eq!( - check( + check_both( b"-----BEGIN RSA PRIVATE KEY-----\n\ qw\n\ -----END RSA PRIVATE KEY-----\n\ junk" - ) - .unwrap(), - vec![crate::Item::Pkcs1Key(vec![0xab].into())] + ), + vec![Item::Pkcs1Key(vec![0xab].into())] ); } #[test] fn skips_non_utf8_junk() { assert_eq!( - check( + check_both( b"\x00\x00\n\ -----BEGIN RSA PRIVATE KEY-----\n\ qw\n\ -----END RSA PRIVATE KEY-----\n \x00\x00" - ) - .unwrap(), - vec![crate::Item::Pkcs1Key(vec![0xab].into())] + ), + vec![Item::Pkcs1Key(vec![0xab].into())] ); } #[test] fn rejects_invalid_base64() { - assert_eq!( - format!( - "{:?}", - check( - b"-----BEGIN RSA PRIVATE KEY-----\n\ + let input = b"-----BEGIN RSA PRIVATE KEY-----\n\ q=w\n\ - -----END RSA PRIVATE KEY-----\n" - ) - ), + -----END RSA PRIVATE KEY-----\n"; + assert_eq!( + format!("{:?}", check_io(input)), "Err(Custom { kind: InvalidData, error: \"InvalidByte(1, 61)\" })" ); + assert!(matches!(check_slice(input), Err(Error::Base64Decode(_)))); } #[test] fn rejects_unclosed_start_section() { + let input = b"-----BEGIN RSA PRIVATE KEY-----\n\ + qw\n"; assert_eq!( format!("{:?}", - check(b"-----BEGIN RSA PRIVATE KEY-----\n\ - qw\n")), + check_io(input)), "Err(Custom { kind: InvalidData, error: \"section end \\\"-----END RSA PRIVATE KEY-----\\\" missing\" })" ); + assert_eq!( + check_slice(input), + Err(Error::MissingSectionEnd { + end_marker: b"-----END RSA PRIVATE KEY-----".to_vec() + }) + ) } #[test] fn rejects_bad_start() { + let input = b"-----BEGIN RSA PRIVATE KEY----\n\ + qw\n\ + -----END RSA PRIVATE KEY-----\n"; assert_eq!( format!("{:?}", - check(b"-----BEGIN RSA PRIVATE KEY----\n\ - qw\n\ - -----END RSA PRIVATE KEY-----\n")), + check_io(input)), "Err(Custom { kind: InvalidData, error: \"illegal section start: \\\"-----BEGIN RSA PRIVATE KEY----\\\\n\\\"\" })" ); + assert_eq!( + check_slice(input), + Err(Error::IllegalSectionStart { + line: b"-----BEGIN RSA PRIVATE KEY----".to_vec() + }) + ) } #[test] fn skips_unrecognised_section() { assert_eq!( - check( + check_both( b"junk\n\ -----BEGIN BREAKFAST CLUB-----\n\ qw\n\ -----END BREAKFAST CLUB-----\n" - ) - .unwrap(), + ), vec![] ); } + + fn check_both(data: &[u8]) -> Vec { + let mut reader = std::io::BufReader::new(data); + let io_outcome = crate::read_all(&mut reader) + .collect::, _>>() + .unwrap(); + let slice_outcome = check_slice(data).unwrap(); + + assert_eq!(io_outcome, slice_outcome); + + io_outcome + } + + fn check_slice(mut data: &[u8]) -> Result, Error> { + let mut items = vec![]; + while let Some((item, rest)) = crate::read_one_from_slice(data)? { + items.push(item); + data = rest; + } + + Ok(items) + } + + fn check_io(data: &[u8]) -> Result, std::io::Error> { + let mut reader = std::io::BufReader::new(data); + crate::read_all(&mut reader).collect() + } }