Skip to content

Commit 7300320

Browse files
authored
pem-rfc7468: detect PEM headers in decode_vec (#19)
Missed in PR #13 - add test coverage for new cases - refactor out decoding of encapsulated text - compromise here on estimating buffer size - slightly more overestimation, due to counting whitespace - faster due to no two-pass - required to ensure that we attempt a base64 decode of the first line before chopping the second line
1 parent e918dea commit 7300320

File tree

3 files changed

+48
-23
lines changed

3 files changed

+48
-23
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
/target/
1+
target/
22
**/*.rs.bk

pem-rfc7468/src/decoder.rs

Lines changed: 27 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -20,32 +20,42 @@ use crate::{
2020
use base64ct::{Base64, Encoding};
2121
use core::{convert::TryFrom, str};
2222

23-
/// Decode a PEM document according to RFC 7468's "Strict" grammar.
24-
///
25-
/// On success, writes the decoded document into the provided buffer, returning
26-
/// the decoded label and the portion of the provided buffer containing the
27-
/// decoded message.
28-
pub fn decode<'i, 'o>(pem: &'i [u8], buf: &'o mut [u8]) -> Result<(&'i str, &'o [u8])> {
29-
let encapsulation = Encapsulation::try_from(pem)?;
30-
let label = encapsulation.label();
31-
let mut out_len = 0;
32-
23+
fn decode_encapsulated_text<'i, 'o>(
24+
encapsulation: &Encapsulation<'i>,
25+
buf: &'o mut [u8],
26+
out_len: &mut usize,
27+
) -> Result<()> {
3328
for line in encapsulation.encapsulated_text() {
3429
let line = line?;
35-
match Base64::decode(line, &mut buf[out_len..]) {
30+
31+
match Base64::decode(line, &mut buf[*out_len..]) {
3632
Err(error) => {
3733
// in the case that we are decoding the first line
3834
// and we error, then attribute the error to an unsupported header
3935
// if a colon char is present in the line
40-
if out_len == 0 && line.iter().any(|&b| b == grammar::CHAR_COLON) {
36+
if *out_len == 0 && line.iter().any(|&b| b == grammar::CHAR_COLON) {
4137
return Err(Error::HeaderDetected);
4238
} else {
4339
return Err(error.into());
4440
}
4541
}
46-
Ok(out) => out_len += out.len(),
42+
Ok(out) => *out_len += out.len(),
4743
}
4844
}
45+
Ok(())
46+
}
47+
48+
/// Decode a PEM document according to RFC 7468's "Strict" grammar.
49+
///
50+
/// On success, writes the decoded document into the provided buffer, returning
51+
/// the decoded label and the portion of the provided buffer containing the
52+
/// decoded message.
53+
pub fn decode<'i, 'o>(pem: &'i [u8], buf: &'o mut [u8]) -> Result<(&'i str, &'o [u8])> {
54+
let encapsulation = Encapsulation::try_from(pem)?;
55+
let label = encapsulation.label();
56+
let mut out_len = 0;
57+
58+
decode_encapsulated_text(&encapsulation, buf, &mut out_len)?;
4959

5060
Ok((label, &buf[..out_len]))
5161
}
@@ -57,17 +67,12 @@ pub fn decode<'i, 'o>(pem: &'i [u8], buf: &'o mut [u8]) -> Result<(&'i str, &'o
5767
pub fn decode_vec(pem: &[u8]) -> Result<(&str, Vec<u8>)> {
5868
let encapsulation = Encapsulation::try_from(pem)?;
5969
let label = encapsulation.label();
60-
let mut max_len = 0;
61-
62-
for line in encapsulation.encapsulated_text() {
63-
max_len += line?.len() * 3 / 4;
64-
}
65-
70+
// count all chars (gives over-estimation, due to whitespace)
71+
let max_len = encapsulation.encapsulated_text.len() * 3 / 4;
6672
let mut result = vec![0u8; max_len];
6773
let mut actual_len = 0;
68-
for line in encapsulation.encapsulated_text() {
69-
actual_len += Base64::decode(line?, &mut result[actual_len..])?.len();
70-
}
74+
75+
decode_encapsulated_text(&encapsulation, &mut result, &mut actual_len)?;
7176

7277
// Actual encoded length can be slightly shorter than estimated
7378
// TODO(tarcieri): more reliable length estimation

pem-rfc7468/tests/decode.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,16 @@ fn pkcs1_enc_example() {
1919
}
2020
}
2121

22+
#[test]
23+
#[cfg(feature = "alloc")]
24+
fn pkcs1_enc_example_with_vec() {
25+
let pem = include_bytes!("examples/ssh_rsa_pem_password.pem");
26+
match pem_rfc7468::decode_vec(pem) {
27+
Err(pem_rfc7468::Error::HeaderDetected) => (),
28+
_ => panic!("Expected HeaderDetected error"),
29+
}
30+
}
31+
2232
#[test]
2333
fn header_of_length_64() {
2434
let pem = include_bytes!("examples/chosen_header.pem");
@@ -29,6 +39,16 @@ fn header_of_length_64() {
2939
}
3040
}
3141

42+
#[test]
43+
#[cfg(feature = "alloc")]
44+
fn header_of_length_64_with_vec() {
45+
let pem = include_bytes!("examples/chosen_header.pem");
46+
match pem_rfc7468::decode_vec(pem) {
47+
Err(pem_rfc7468::Error::HeaderDetected) => (),
48+
res => panic!("Expected HeaderDetected error; Found {:?}", res),
49+
}
50+
}
51+
3252
#[test]
3353
fn pkcs8_example() {
3454
let pem = include_bytes!("examples/pkcs8.pem");

0 commit comments

Comments
 (0)