summaryrefslogtreecommitdiffstats
path: root/vendor/pem-rfc7468/src/encoder.rs
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--vendor/pem-rfc7468/src/encoder.rs299
1 files changed, 299 insertions, 0 deletions
diff --git a/vendor/pem-rfc7468/src/encoder.rs b/vendor/pem-rfc7468/src/encoder.rs
new file mode 100644
index 0000000..c48df1f
--- /dev/null
+++ b/vendor/pem-rfc7468/src/encoder.rs
@@ -0,0 +1,299 @@
+//! PEM encoder.
+
+use crate::{
+ grammar, Base64Encoder, Error, LineEnding, Result, BASE64_WRAP_WIDTH,
+ ENCAPSULATION_BOUNDARY_DELIMITER, POST_ENCAPSULATION_BOUNDARY, PRE_ENCAPSULATION_BOUNDARY,
+};
+use base64ct::{Base64, Encoding};
+use core::str;
+
+#[cfg(feature = "alloc")]
+use alloc::string::String;
+
+#[cfg(feature = "std")]
+use std::io;
+
+/// Compute the length of a PEM encoded document which encapsulates a
+/// Base64-encoded body including line endings every 64 characters.
+///
+/// The `input_len` parameter specifies the length of the raw input
+/// bytes prior to Base64 encoding.
+///
+/// Note that the current implementation of this function computes an upper
+/// bound of the length and the actual encoded document may be slightly shorter
+/// (typically 1-byte). Downstream consumers of this function should check the
+/// actual encoded length and potentially truncate buffers allocated using this
+/// function to estimate the encapsulated size.
+///
+/// Use [`encoded_len`] (when possible) to obtain a precise length.
+///
+/// ## Returns
+/// - `Ok(len)` on success
+/// - `Err(Error::Length)` on length overflow
+pub fn encapsulated_len(label: &str, line_ending: LineEnding, input_len: usize) -> Result<usize> {
+ encapsulated_len_wrapped(label, BASE64_WRAP_WIDTH, line_ending, input_len)
+}
+
+/// Compute the length of a PEM encoded document with the Base64 body
+/// line wrapped at the specified `width`.
+///
+/// This is the same as [`encapsulated_len`], which defaults to a width of 64.
+///
+/// Note that per [RFC7468 § 2] encoding PEM with any other wrap width besides
+/// 64 is technically non-compliant:
+///
+/// > Generators MUST wrap the base64-encoded lines so that each line
+/// > consists of exactly 64 characters except for the final line, which
+/// > will encode the remainder of the data (within the 64-character line
+/// > boundary)
+///
+/// [RFC7468 § 2]: https://datatracker.ietf.org/doc/html/rfc7468#section-2
+pub fn encapsulated_len_wrapped(
+ label: &str,
+ line_width: usize,
+ line_ending: LineEnding,
+ input_len: usize,
+) -> Result<usize> {
+ if line_width < 4 {
+ return Err(Error::Length);
+ }
+
+ let base64_len = input_len
+ .checked_mul(4)
+ .and_then(|n| n.checked_div(3))
+ .and_then(|n| n.checked_add(3))
+ .ok_or(Error::Length)?
+ & !3;
+
+ let base64_len_wrapped = base64_len_wrapped(base64_len, line_width, line_ending)?;
+ encapsulated_len_inner(label, line_ending, base64_len_wrapped)
+}
+
+/// Get the length of a PEM encoded document with the given bytes and label.
+///
+/// This function computes a precise length of the PEM encoding of the given
+/// `input` data.
+///
+/// ## Returns
+/// - `Ok(len)` on success
+/// - `Err(Error::Length)` on length overflow
+pub fn encoded_len(label: &str, line_ending: LineEnding, input: &[u8]) -> Result<usize> {
+ let base64_len = Base64::encoded_len(input);
+ let base64_len_wrapped = base64_len_wrapped(base64_len, BASE64_WRAP_WIDTH, line_ending)?;
+ encapsulated_len_inner(label, line_ending, base64_len_wrapped)
+}
+
+/// Encode a PEM document according to RFC 7468's "Strict" grammar.
+pub fn encode<'o>(
+ type_label: &str,
+ line_ending: LineEnding,
+ input: &[u8],
+ buf: &'o mut [u8],
+) -> Result<&'o str> {
+ let mut encoder = Encoder::new(type_label, line_ending, buf)?;
+ encoder.encode(input)?;
+ let encoded_len = encoder.finish()?;
+ let output = &buf[..encoded_len];
+
+ // Sanity check
+ debug_assert!(str::from_utf8(output).is_ok());
+
+ // Ensure `output` contains characters from the lower 7-bit ASCII set
+ if output.iter().fold(0u8, |acc, &byte| acc | (byte & 0x80)) == 0 {
+ // Use unchecked conversion to avoid applying UTF-8 checks to potentially
+ // secret PEM documents (and therefore introducing a potential timing
+ // sidechannel)
+ //
+ // SAFETY: contents of this buffer are controlled entirely by the encoder,
+ // which ensures the contents are always a valid (ASCII) subset of UTF-8.
+ // It's also additionally sanity checked by two assertions above to ensure
+ // the validity (with the always-on runtime check implemented in a
+ // constant time-ish manner.
+ #[allow(unsafe_code)]
+ Ok(unsafe { str::from_utf8_unchecked(output) })
+ } else {
+ Err(Error::CharacterEncoding)
+ }
+}
+
+/// Encode a PEM document according to RFC 7468's "Strict" grammar, returning
+/// the result as a [`String`].
+#[cfg(feature = "alloc")]
+pub fn encode_string(label: &str, line_ending: LineEnding, input: &[u8]) -> Result<String> {
+ let expected_len = encoded_len(label, line_ending, input)?;
+ let mut buf = vec![0u8; expected_len];
+ let actual_len = encode(label, line_ending, input, &mut buf)?.len();
+ debug_assert_eq!(expected_len, actual_len);
+ String::from_utf8(buf).map_err(|_| Error::CharacterEncoding)
+}
+
+/// Compute the encapsulated length of Base64 data of the given length.
+fn encapsulated_len_inner(
+ label: &str,
+ line_ending: LineEnding,
+ base64_len: usize,
+) -> Result<usize> {
+ [
+ PRE_ENCAPSULATION_BOUNDARY.len(),
+ label.as_bytes().len(),
+ ENCAPSULATION_BOUNDARY_DELIMITER.len(),
+ line_ending.len(),
+ base64_len,
+ line_ending.len(),
+ POST_ENCAPSULATION_BOUNDARY.len(),
+ label.as_bytes().len(),
+ ENCAPSULATION_BOUNDARY_DELIMITER.len(),
+ line_ending.len(),
+ ]
+ .into_iter()
+ .try_fold(0usize, |acc, len| acc.checked_add(len))
+ .ok_or(Error::Length)
+}
+
+/// Compute Base64 length line-wrapped at the specified width with the given
+/// line ending.
+fn base64_len_wrapped(
+ base64_len: usize,
+ line_width: usize,
+ line_ending: LineEnding,
+) -> Result<usize> {
+ base64_len
+ .saturating_sub(1)
+ .checked_div(line_width)
+ .and_then(|lines| lines.checked_mul(line_ending.len()))
+ .and_then(|len| len.checked_add(base64_len))
+ .ok_or(Error::Length)
+}
+
+/// Buffered PEM encoder.
+///
+/// Stateful buffered encoder type which encodes an input PEM document according
+/// to RFC 7468's "Strict" grammar.
+pub struct Encoder<'l, 'o> {
+ /// PEM type label.
+ type_label: &'l str,
+
+ /// Line ending used to wrap Base64.
+ line_ending: LineEnding,
+
+ /// Buffered Base64 encoder.
+ base64: Base64Encoder<'o>,
+}
+
+impl<'l, 'o> Encoder<'l, 'o> {
+ /// Create a new PEM [`Encoder`] with the default options which
+ /// writes output into the provided buffer.
+ ///
+ /// Uses the default 64-character line wrapping.
+ pub fn new(type_label: &'l str, line_ending: LineEnding, out: &'o mut [u8]) -> Result<Self> {
+ Self::new_wrapped(type_label, BASE64_WRAP_WIDTH, line_ending, out)
+ }
+
+ /// Create a new PEM [`Encoder`] which wraps at the given line width.
+ ///
+ /// Note that per [RFC7468 § 2] encoding PEM with any other wrap width besides
+ /// 64 is technically non-compliant:
+ ///
+ /// > Generators MUST wrap the base64-encoded lines so that each line
+ /// > consists of exactly 64 characters except for the final line, which
+ /// > will encode the remainder of the data (within the 64-character line
+ /// > boundary)
+ ///
+ /// This method is provided with the intended purpose of implementing the
+ /// OpenSSH private key format, which uses a non-standard wrap width of 70.
+ ///
+ /// [RFC7468 § 2]: https://datatracker.ietf.org/doc/html/rfc7468#section-2
+ pub fn new_wrapped(
+ type_label: &'l str,
+ line_width: usize,
+ line_ending: LineEnding,
+ mut out: &'o mut [u8],
+ ) -> Result<Self> {
+ grammar::validate_label(type_label.as_bytes())?;
+
+ for boundary_part in [
+ PRE_ENCAPSULATION_BOUNDARY,
+ type_label.as_bytes(),
+ ENCAPSULATION_BOUNDARY_DELIMITER,
+ line_ending.as_bytes(),
+ ] {
+ if out.len() < boundary_part.len() {
+ return Err(Error::Length);
+ }
+
+ let (part, rest) = out.split_at_mut(boundary_part.len());
+ out = rest;
+
+ part.copy_from_slice(boundary_part);
+ }
+
+ let base64 = Base64Encoder::new_wrapped(out, line_width, line_ending)?;
+
+ Ok(Self {
+ type_label,
+ line_ending,
+ base64,
+ })
+ }
+
+ /// Get the PEM type label used for this document.
+ pub fn type_label(&self) -> &'l str {
+ self.type_label
+ }
+
+ /// Encode the provided input data.
+ ///
+ /// This method can be called as many times as needed with any sized input
+ /// to write data encoded data into the output buffer, so long as there is
+ /// sufficient space in the buffer to handle the resulting Base64 encoded
+ /// data.
+ pub fn encode(&mut self, input: &[u8]) -> Result<()> {
+ self.base64.encode(input)?;
+ Ok(())
+ }
+
+ /// Borrow the inner [`Base64Encoder`].
+ pub fn base64_encoder(&mut self) -> &mut Base64Encoder<'o> {
+ &mut self.base64
+ }
+
+ /// Finish encoding PEM, writing the post-encapsulation boundary.
+ ///
+ /// On success, returns the total number of bytes written to the output
+ /// buffer.
+ pub fn finish(self) -> Result<usize> {
+ let (base64, mut out) = self.base64.finish_with_remaining()?;
+
+ for boundary_part in [
+ self.line_ending.as_bytes(),
+ POST_ENCAPSULATION_BOUNDARY,
+ self.type_label.as_bytes(),
+ ENCAPSULATION_BOUNDARY_DELIMITER,
+ self.line_ending.as_bytes(),
+ ] {
+ if out.len() < boundary_part.len() {
+ return Err(Error::Length);
+ }
+
+ let (part, rest) = out.split_at_mut(boundary_part.len());
+ out = rest;
+
+ part.copy_from_slice(boundary_part);
+ }
+
+ encapsulated_len_inner(self.type_label, self.line_ending, base64.len())
+ }
+}
+
+#[cfg(feature = "std")]
+impl<'l, 'o> io::Write for Encoder<'l, 'o> {
+ fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
+ self.encode(buf)?;
+ Ok(buf.len())
+ }
+
+ fn flush(&mut self) -> io::Result<()> {
+ // TODO(tarcieri): return an error if there's still data remaining in the buffer?
+ Ok(())
+ }
+}