Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion der/src/asn1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ pub use self::{

#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
pub use self::set_of::SetOfVec;
pub use self::{bit_string::BitStringOwned, set_of::SetOfVec};

#[cfg(feature = "oid")]
#[cfg_attr(docsrs, doc(cfg(feature = "oid")))]
Expand Down
92 changes: 92 additions & 0 deletions der/src/asn1/bit_string.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ use crate::{
};
use core::{cmp::Ordering, iter::FusedIterator};

#[cfg(feature = "alloc")]
use alloc::vec::Vec;

/// ASN.1 `BIT STRING` type.
///
/// This type contains a sequence of any number of bits, modeled internally as
Expand Down Expand Up @@ -297,6 +300,95 @@ where
}
}

/// Owned form of ASN.1 `BIT STRING` type.
///
/// This type provides the same functionality as [`BitString`] but owns the
/// backing data.
#[cfg(feature = "alloc")]
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
pub struct BitStringOwned {
/// Number of unused bits in the final octet.
unused_bits: u8,

/// Length of this `BIT STRING` in bits.
bit_length: usize,

/// Bitstring represented as a slice of bytes.
inner: Vec<u8>,
}

#[cfg(feature = "alloc")]
impl BitStringOwned {
/// Get the number of unused bits in the octet serialization of this
/// `BIT STRING`.
pub fn unused_bits(&self) -> u8 {
self.unused_bits
}

/// Borrow the raw bytes of this `BIT STRING`.
pub fn raw_bytes(&self) -> &[u8] {
self.inner.as_slice()
}
}

#[cfg(feature = "alloc")]
impl<'a> DecodeValue<'a> for BitStringOwned {
fn decode_value<R: Reader<'a>>(reader: &mut R, header: Header) -> Result<Self> {
let unused_bits = reader.read_byte()?;
let inner_len: usize = (header.length - Length::ONE)?.try_into()?;

if (unused_bits > BitString::MAX_UNUSED_BITS) || (unused_bits != 0 && inner_len == 0) {
return Err(Self::TAG.value_error());
}

let bit_length = inner_len
.checked_mul(8)
.and_then(|n| n.checked_sub(usize::from(unused_bits)))
.ok_or(ErrorKind::Overflow)?;

let mut inner = vec![0u8; inner_len];
let actual_len = reader.read_into(&mut inner)?.len();

if inner_len == actual_len {
Ok(Self {
unused_bits,
bit_length,
inner,
})
} else {
Err(Self::TAG.length_error())
}
}
}

#[cfg(feature = "alloc")]
impl EncodeValue for BitStringOwned {
fn value_len(&self) -> Result<Length> {
Length::ONE + Length::try_from(self.inner.len())?
}

fn encode_value(&self, writer: &mut dyn Writer) -> Result<()> {
writer.write_byte(self.unused_bits)?;
writer.write(&self.inner)
}
}

#[cfg(feature = "alloc")]
impl FixedTag for BitStringOwned {
const TAG: Tag = Tag::BitString;
}

#[cfg(feature = "alloc")]
impl ValueOrd for BitStringOwned {
fn value_cmp(&self, other: &Self) -> Result<Ordering> {
match self.unused_bits.cmp(&other.unused_bits) {
Ordering::Equal => self.inner.der_cmp(&other.inner),
ordering => Ok(ordering),
}
}
}

#[cfg(test)]
mod tests {
use super::{BitString, Result, Tag};
Expand Down
15 changes: 10 additions & 5 deletions der/src/asn1/oid.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
//! ASN.1 `OBJECT IDENTIFIER`

use crate::{
asn1::Any, ord::OrdIsValueOrd, ByteSlice, DecodeValue, EncodeValue, Error, FixedTag, Header,
Length, Reader, Result, Tag, Tagged, Writer,
asn1::Any, ord::OrdIsValueOrd, DecodeValue, EncodeValue, Error, FixedTag, Header, Length,
Reader, Result, Tag, Tagged, Writer,
};
use const_oid::ObjectIdentifier;

impl<'a> DecodeValue<'a> for ObjectIdentifier {
fn decode_value<R: Reader<'a>>(reader: &mut R, header: Header) -> Result<Self> {
Ok(Self::from_bytes(
ByteSlice::decode_value(reader, header)?.as_ref(),
)?)
let mut buf = [0u8; ObjectIdentifier::MAX_SIZE];
let slice = buf
.get_mut(..header.length.try_into()?)
.ok_or_else(|| Self::TAG.length_error())?;

let actual_len = reader.read_into(slice)?.len();
debug_assert_eq!(actual_len, header.length.try_into()?);
Ok(Self::from_bytes(slice)?)
}
}

Expand Down
15 changes: 4 additions & 11 deletions der/src/decode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,7 @@
use crate::{Decoder, FixedTag, Header, Reader, Result};

#[cfg(feature = "pem")]
use {
crate::pem::{self, PemLabel},
zeroize::Zeroize,
};
use crate::{pem::PemLabel, PemReader};

#[cfg(doc)]
use crate::{Length, Tag};
Expand Down Expand Up @@ -65,13 +62,9 @@ pub trait DecodePem: DecodeOwned + PemLabel {
#[cfg_attr(docsrs, doc(cfg(feature = "pem")))]
impl<T: DecodeOwned + PemLabel> DecodePem for T {
fn from_pem(pem: impl AsRef<[u8]>) -> Result<Self> {
// TODO(tarcieri): support for decoding directly from PEM (instead of two-pass)
let (label, mut der_bytes) = pem::decode_vec(pem.as_ref())?;
Self::validate_pem_label(label)?;

let result = T::from_der(&der_bytes);
der_bytes.zeroize();
result
let mut reader = PemReader::new(pem.as_ref())?;
Self::validate_pem_label(reader.type_label())?;
T::decode(&mut reader)
}
}

Expand Down
2 changes: 1 addition & 1 deletion der/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -396,7 +396,7 @@ pub use const_oid as oid;
#[cfg(feature = "pem")]
#[cfg_attr(docsrs, doc(cfg(feature = "pem")))]
pub use {
crate::{decode::DecodePem, encode::EncodePem, writer::pem::PemWriter},
crate::{decode::DecodePem, encode::EncodePem, reader::pem::PemReader, writer::pem::PemWriter},
pem_rfc7468 as pem,
};

Expand Down
3 changes: 3 additions & 0 deletions der/src/reader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

mod nested;

#[cfg(feature = "pem")]
pub(crate) mod pem;

pub(crate) use nested::NestedReader;

use crate::{
Expand Down
83 changes: 83 additions & 0 deletions der/src/reader/pem.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
//! Streaming PEM reader.

use super::Reader;
use crate::{ErrorKind, Header, Length, Result};
use pem_rfc7468::Decoder;

/// `Reader` type which decodes PEM on-the-fly.
#[cfg(feature = "pem")]
#[cfg_attr(docsrs, doc(cfg(feature = "pem")))]
#[derive(Clone)]
pub struct PemReader<'i> {
/// Inner PEM decoder.
decoder: Decoder<'i>,

/// Input length (in bytes after Base64 decoding).
input_len: Length,

/// Position in the input buffer (in bytes after Base64 decoding).
position: Length,
}

#[cfg(feature = "pem")]
#[cfg_attr(docsrs, doc(cfg(feature = "pem")))]
impl<'i> PemReader<'i> {
/// Create a new PEM reader which decodes data on-the-fly.
///
/// Uses the default 64-character line wrapping.
pub fn new(pem: &'i [u8]) -> Result<Self> {
let decoder = Decoder::new(pem)?;
let input_len = Length::try_from(decoder.remaining_len())?;

Ok(Self {
decoder,
input_len,
position: Length::ZERO,
})
}

/// Get the PEM label which will be used in the encapsulation boundaries
/// for this document.
pub fn type_label(&self) -> &'i str {
self.decoder.type_label()
}
}

#[cfg(feature = "pem")]
#[cfg_attr(docsrs, doc(cfg(feature = "pem")))]
impl<'i> Reader<'i> for PemReader<'i> {
fn input_len(&self) -> Length {
self.input_len
}

fn peek_byte(&self) -> Option<u8> {
// TODO(tarcieri): lookahead buffer
None
}

fn peek_header(&self) -> Result<Header> {
// TODO(tarcieri): lookahead buffer
Err(ErrorKind::Reader.into())
}

fn position(&self) -> Length {
self.position
}

fn read_slice(&mut self, _len: Length) -> Result<&'i [u8]> {
// Can't borrow from PEM because it requires decoding
Err(ErrorKind::Reader.into())
}

fn read_into<'o>(&mut self, buf: &'o mut [u8]) -> Result<&'o [u8]> {
let bytes = self.decoder.decode(buf)?;
self.position = (self.position + bytes.len())?;

debug_assert_eq!(
self.position,
(self.input_len - Length::try_from(self.decoder.remaining_len())?)?
);

Ok(bytes)
}
}
Binary file added der/tests/examples/spki.der
Binary file not shown.
3 changes: 3 additions & 0 deletions der/tests/examples/spki.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
-----BEGIN PUBLIC KEY-----
MCowBQYDK2VwAyEATSkWfz8ZEqb3rfopOgUaFcBexnuPFyZ7HFVQ3OhTvQ0=
-----END PUBLIC KEY-----
67 changes: 67 additions & 0 deletions der/tests/pem.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
//! PEM decoding and encoding tests.

#![cfg(all(feature = "derive", feature = "oid", feature = "pem"))]

use der::{
asn1::{BitStringOwned, ObjectIdentifier},
pem::{LineEnding, PemLabel},
Decode, DecodePem, EncodePem, Sequence,
};

/// Example SPKI document encoded as DER.
const SPKI_DER: &[u8] = include_bytes!("examples/spki.der");

/// Example SPKI document encoded as PEM.
const SPKI_PEM: &str = include_str!("examples/spki.pem");

/// X.509 `AlgorithmIdentifier`
#[derive(Copy, Clone, Debug, Eq, PartialEq, Sequence)]
pub struct AlgorithmIdentifier {
pub algorithm: ObjectIdentifier,
// pub parameters: ... (not used in spki.pem)
}

/// X.509 `SubjectPublicKeyInfo` (SPKI) in borrowed form
#[derive(Copy, Clone, Debug, Eq, PartialEq, Sequence)]
pub struct SpkiBorrowed<'a> {
pub algorithm: AlgorithmIdentifier,
#[asn1(type = "BIT STRING")]
pub subject_public_key: &'a [u8],
}

impl PemLabel for SpkiBorrowed<'_> {
const PEM_LABEL: &'static str = "PUBLIC KEY";
}

/// X.509 `SubjectPublicKeyInfo` (SPKI) in owned form
#[derive(Clone, Debug, Eq, PartialEq, Sequence)]
pub struct SpkiOwned {
pub algorithm: AlgorithmIdentifier,
pub subject_public_key: BitStringOwned,
}

impl PemLabel for SpkiOwned {
const PEM_LABEL: &'static str = "PUBLIC KEY";
}

#[test]
fn from_pem() {
// Decode PEM to owned form.
let pem_spki = SpkiOwned::from_pem(SPKI_PEM).unwrap();

// Decode DER to borrowed form.
let der_spki = SpkiBorrowed::from_der(SPKI_DER).unwrap();

assert_eq!(pem_spki.algorithm, der_spki.algorithm);
assert_eq!(
pem_spki.subject_public_key.raw_bytes(),
der_spki.subject_public_key
);
}

#[test]
fn to_pem() {
let spki = SpkiBorrowed::from_der(SPKI_DER).unwrap();
let pem = spki.to_pem(LineEnding::LF).unwrap();
assert_eq!(&pem, SPKI_PEM);
}