use codec_pem::{
decode_pem as decode_primitive_pem, encode_pem as encode_primitive_pem, PemDecodePolicy,
PemEncodeOptions, PemError, PemLabel,
};
use zeroize::Zeroizing;
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
#[non_exhaustive]
pub enum PemOperationError {
#[error("empty pem input")]
EmptyInput,
#[error("empty der body")]
EmptyDer,
#[error("pem input too large")]
InputTooLarge,
#[error("pem der too large")]
DerTooLarge,
#[error("invalid pem boundary")]
InvalidBoundary,
#[error("missing pem begin boundary")]
MissingBegin,
#[error("missing pem end boundary")]
MissingEnd,
#[error("pem label mismatch")]
LabelMismatch,
#[error("unsupported pem label")]
UnsupportedLabel,
#[error("invalid pem body")]
InvalidBody,
#[error("invalid pem base64")]
InvalidBase64,
#[error("invalid pem policy")]
InvalidPolicy,
#[error("pem operation invariant violation")]
OperationInvariant,
}
pub struct DecodedPem {
label: PemLabel,
der: Zeroizing<Vec<u8>>,
}
pub struct EncodedPem {
pem: Zeroizing<String>,
}
impl EncodedPem {
pub(crate) fn into_bytes(mut self) -> Vec<u8> {
core::mem::take(&mut *self.pem).into_bytes()
}
}
impl DecodedPem {
pub const fn label(&self) -> PemLabel {
self.label
}
#[cfg(test)]
pub fn der(&self) -> &[u8] {
self.der.as_slice()
}
pub(crate) fn into_der(mut self) -> Vec<u8> {
core::mem::take(&mut *self.der)
}
}
pub fn decode_pem(
input: &str,
policy: PemDecodePolicy<'_>,
) -> Result<DecodedPem, PemOperationError> {
let decoded = decode_primitive_pem(input, policy).map_err(pem_operation_error)?;
Ok(DecodedPem {
label: decoded.label,
der: decoded.der,
})
}
pub fn encode_pem(
label: PemLabel,
der: &[u8],
options: PemEncodeOptions,
) -> Result<EncodedPem, PemOperationError> {
let pem = encode_primitive_pem(label, der, options).map_err(pem_operation_error)?;
Ok(EncodedPem { pem })
}
fn pem_operation_error(error: PemError) -> PemOperationError {
match error {
PemError::EmptyInput => PemOperationError::EmptyInput,
PemError::EmptyDer => PemOperationError::EmptyDer,
PemError::InputTooLarge => PemOperationError::InputTooLarge,
PemError::DerTooLarge => PemOperationError::DerTooLarge,
PemError::MissingBegin => PemOperationError::MissingBegin,
PemError::MissingEnd => PemOperationError::MissingEnd,
PemError::InvalidBoundary => PemOperationError::InvalidBoundary,
PemError::LabelMismatch => PemOperationError::LabelMismatch,
PemError::UnsupportedLabel => PemOperationError::UnsupportedLabel,
PemError::InvalidBase64 => PemOperationError::InvalidBase64,
PemError::InvalidBody => PemOperationError::InvalidBody,
PemError::InvalidOptions => PemOperationError::InvalidPolicy,
_ => PemOperationError::OperationInvariant,
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::unwrap_used)]
use codec_pem::{encode_pem, PemDecodePolicy, PemEncodeOptions, PemLabel};
use super::{decode_pem, decode_primitive_pem, PemOperationError};
#[test]
fn decode_pem_preserves_primitive_decode_semantics() {
let der = b"not real der";
let pem = encode_pem(PemLabel::PublicKey, der, PemEncodeOptions::default()).unwrap();
let policy = PemDecodePolicy {
allowed_labels: &[PemLabel::PublicKey],
..PemDecodePolicy::default()
};
let primitive = decode_primitive_pem(&pem, policy).unwrap();
let decoded = decode_pem(&pem, policy).unwrap();
assert_eq!(decoded.label(), primitive.label);
assert_eq!(decoded.der(), primitive.der.as_slice());
}
#[test]
fn decode_pem_maps_label_mismatch_without_input_context() {
let pem = "-----BEGIN PUBLIC KEY-----\nAA==\n-----END PRIVATE KEY-----\n";
let error = match decode_pem(pem, PemDecodePolicy::default()) {
Ok(_) => PemOperationError::OperationInvariant,
Err(error) => error,
};
assert_eq!(error, PemOperationError::LabelMismatch);
}
#[test]
fn decode_pem_maps_unsupported_label_without_input_context() {
let der = b"not real der";
let pem = encode_pem(PemLabel::PublicKey, der, PemEncodeOptions::default()).unwrap();
let policy = PemDecodePolicy {
allowed_labels: &[PemLabel::PrivateKey],
..PemDecodePolicy::default()
};
let error = match decode_pem(&pem, policy) {
Ok(_) => PemOperationError::OperationInvariant,
Err(error) => error,
};
assert_eq!(error, PemOperationError::UnsupportedLabel);
}
}