Skip to content

Commit 9131e85

Browse files
authored
der: add PemWriter and 1-pass EncodePem (#618)
Adds a `PemWriter` type which impls the `Writer` trait and can perform on-the-fly PEM encoding.
1 parent f0a3d96 commit 9131e85

4 files changed

Lines changed: 84 additions & 7 deletions

File tree

der/src/encode.rs

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,18 @@
33
use crate::{Encoder, Header, Length, Result, Tagged, Writer};
44

55
#[cfg(feature = "alloc")]
6-
use {crate::ErrorKind, alloc::vec::Vec, core::iter};
6+
use {alloc::vec::Vec, core::iter};
77

88
#[cfg(feature = "pem")]
99
use {
10+
crate::PemWriter,
1011
alloc::string::String,
1112
pem_rfc7468::{self as pem, LineEnding, PemLabel},
12-
zeroize::Zeroizing,
1313
};
1414

15+
#[cfg(any(feature = "alloc", feature = "pem"))]
16+
use crate::ErrorKind;
17+
1518
#[cfg(doc)]
1619
use crate::Tag;
1720

@@ -95,10 +98,30 @@ pub trait EncodePem: Encode + PemLabel {
9598
#[cfg(feature = "pem")]
9699
#[cfg_attr(docsrs, doc(cfg(feature = "pem")))]
97100
impl<T: Encode + PemLabel> EncodePem for T {
101+
#[allow(clippy::integer_arithmetic)]
98102
fn to_pem(&self, line_ending: LineEnding) -> Result<String> {
99-
// TODO(tarcieri): support for encoding directly from PEM (instead of two-pass)
100-
let der = Zeroizing::new(self.to_vec()?);
101-
Ok(pem::encode_string(Self::PEM_LABEL, line_ending, &der)?)
103+
// TODO(tarcieri): checked arithmetic, maybe extract this into `base64ct::base64_len`?
104+
let der_len = usize::try_from(self.encoded_len()?)?;
105+
let mut base64_len = (((der_len * 4) / 3) + 3) & !3;
106+
107+
// Add the length of the line endings which will be inserted when
108+
// encoded Base64 is line wrapped
109+
// TODO(tarcieri): factor this logic into `pem-rfc7468`
110+
base64_len += base64_len
111+
.saturating_sub(1)
112+
.checked_div(64)
113+
.and_then(|len| len.checked_add(line_ending.len()))
114+
.ok_or(ErrorKind::Overflow)?;
115+
116+
let pem_len = pem::encapsulated_len(Self::PEM_LABEL, line_ending, base64_len)?;
117+
118+
let mut buf = vec![0u8; pem_len];
119+
let mut writer = PemWriter::new(Self::PEM_LABEL, line_ending, &mut buf)?;
120+
self.encode(&mut writer)?;
121+
122+
let actual_len = writer.finish()?;
123+
buf.truncate(actual_len);
124+
Ok(String::from_utf8(buf)?)
102125
}
103126
}
104127

der/src/error.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,13 @@ impl From<Utf8Error> for Error {
118118
}
119119
}
120120

121+
#[cfg(feature = "alloc")]
122+
impl From<alloc::string::FromUtf8Error> for Error {
123+
fn from(err: alloc::string::FromUtf8Error) -> Error {
124+
ErrorKind::Utf8(err.utf8_error()).into()
125+
}
126+
}
127+
121128
#[cfg(feature = "oid")]
122129
impl From<const_oid::Error> for Error {
123130
fn from(_: const_oid::Error) -> Error {
@@ -150,6 +157,7 @@ impl From<time::error::ComponentRange> for Error {
150157
ErrorKind::DateTime.into()
151158
}
152159
}
160+
153161
/// Error type.
154162
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
155163
#[non_exhaustive]

der/src/lib.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -338,7 +338,8 @@
338338
//! [`Utf8String`]: asn1::Utf8String
339339
340340
#[cfg(feature = "alloc")]
341-
#[cfg_attr(test, macro_use)]
341+
#[allow(unused_imports)]
342+
#[macro_use]
342343
extern crate alloc;
343344
#[cfg(feature = "std")]
344345
extern crate std;
@@ -396,7 +397,7 @@ pub use der_derive::{Choice, Enumerated, Newtype, Sequence, ValueOrd};
396397
#[cfg(feature = "pem")]
397398
#[cfg_attr(docsrs, doc(cfg(feature = "pem")))]
398399
pub use {
399-
crate::{decode::DecodePem, encode::EncodePem},
400+
crate::{decode::DecodePem, encode::EncodePem, writer::PemWriter},
400401
pem_rfc7468 as pem,
401402
};
402403

der/src/writer.rs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
33
use crate::Result;
44

5+
#[cfg(feature = "pem")]
6+
use crate::pem;
7+
58
#[cfg(feature = "std")]
69
use std::io;
710

@@ -16,6 +19,48 @@ pub trait Writer {
1619
}
1720
}
1821

22+
/// `Writer` type which outputs PEM-encoded data.
23+
#[cfg(feature = "pem")]
24+
#[cfg_attr(docsrs, doc(cfg(feature = "pem")))]
25+
pub struct PemWriter<'o>(pem::Encoder<'static, 'o>);
26+
27+
#[cfg(feature = "pem")]
28+
#[cfg_attr(docsrs, doc(cfg(feature = "pem")))]
29+
impl<'o> PemWriter<'o> {
30+
/// Create a new PEM writer which outputs into the provided buffer.
31+
///
32+
/// Uses the default 64-character line wrapping.
33+
pub fn new(
34+
type_label: &'static str,
35+
line_ending: pem::LineEnding,
36+
out: &'o mut [u8],
37+
) -> Result<Self> {
38+
Ok(Self(pem::Encoder::new(type_label, line_ending, out)?))
39+
}
40+
41+
/// Get the PEM label which will be used in the encapsulation boundaries
42+
/// for this document.
43+
pub fn type_label(&self) -> &'static str {
44+
self.0.type_label()
45+
}
46+
47+
/// Finish encoding PEM, writing the post-encapsulation boundary.
48+
///
49+
/// On success, returns the total number of bytes written to the output buffer.
50+
pub fn finish(self) -> Result<usize> {
51+
Ok(self.0.finish()?)
52+
}
53+
}
54+
55+
#[cfg(feature = "pem")]
56+
#[cfg_attr(docsrs, doc(cfg(feature = "pem")))]
57+
impl Writer for PemWriter<'_> {
58+
fn write(&mut self, slice: &[u8]) -> Result<()> {
59+
self.0.encode(slice)?;
60+
Ok(())
61+
}
62+
}
63+
1964
#[cfg(feature = "std")]
2065
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
2166
impl<W: io::Write> Writer for W {

0 commit comments

Comments
 (0)