Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support PEM decoding for EchConfigListBytes #54

Merged
merged 3 commits into from
Sep 27, 2024
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
54 changes: 51 additions & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,8 @@
//! from a slice, or from an `std::io` stream.
//!
//! There is also a lower-level API that allows a given PEM file to be fully consumed
//! in one pass, even if it contains different data types: see [`pem::from_slice()`]
//! and the implementation of the [`pem::PemObject`] trait on the `(pem::SectionKind, Vec<u8>)`
//! tuple.
//! in one pass, even if it contains different data types: see the implementation of
//! the [`pem::PemObject`] trait on the `(pem::SectionKind, Vec<u8>)` tuple.
//!
//! ## Creating new certificates and keys
//!
Expand Down Expand Up @@ -762,6 +761,55 @@ impl EchConfigListBytes<'_> {
}
}

#[cfg(feature = "alloc")]
impl EchConfigListBytes<'static> {
/// Convert an iterator over PEM items into an `EchConfigListBytes` and private key.
///
/// This handles the "ECHConfig file" format specified in
/// <https://www.ietf.org/archive/id/draft-farrell-tls-pemesni-05.html#name-echconfig-file>
///
/// Use it like:
///
/// ```rust
/// # #[cfg(all(feature = "alloc", feature = "std"))] {
/// # use rustls_pki_types::{EchConfigListBytes, pem::PemObject};
/// let (config, key) = EchConfigListBytes::config_and_key_from_iter(
/// PemObject::pem_file_iter("tests/data/ech.pem").unwrap()
/// ).unwrap();
/// # }
/// ```
pub fn config_and_key_from_iter(
iter: impl Iterator<Item = Result<(SectionKind, Vec<u8>), pem::Error>>,
) -> Result<(Self, PrivatePkcs8KeyDer<'static>), pem::Error> {
let mut key = None;
let mut config = None;

for item in iter {
let (kind, data) = item?;
match kind {
SectionKind::PrivateKey => {
key = PrivatePkcs8KeyDer::from_pem(kind, data);
}
SectionKind::EchConfigList => {
config = Self::from_pem(kind, data);
}
_ => continue,
};

if let (Some(_key), Some(_config)) = (&key, &config) {
return Ok((config.take().unwrap(), key.take().unwrap()));
}
cpu marked this conversation as resolved.
Show resolved Hide resolved
}

Err(pem::Error::NoItemsFound)
}
}

#[cfg(feature = "alloc")]
impl PemObjectFilter for EchConfigListBytes<'static> {
const KIND: SectionKind = SectionKind::EchConfigList;
}

impl fmt::Debug for EchConfigListBytes<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
hex(f, self.as_ref())
Expand Down
11 changes: 10 additions & 1 deletion src/pem.rs
Original file line number Diff line number Diff line change
Expand Up @@ -357,13 +357,21 @@ pub enum SectionKind {
///
/// Appears as "CERTIFICATE REQUEST" in PEM files.
Csr,

/// An EchConfigList structure, as specified in
/// <https://www.ietf.org/archive/id/draft-farrell-tls-pemesni-05.html>.
///
/// Appears as "ECHCONFIG" in PEM files.
EchConfigList,
}

impl SectionKind {
fn secret(&self) -> bool {
match self {
Self::RsaPrivateKey | Self::PrivateKey | Self::EcPrivateKey => true,
Self::Certificate | Self::PublicKey | Self::Crl | Self::Csr => false,
Self::Certificate | Self::PublicKey | Self::Crl | Self::Csr | Self::EchConfigList => {
false
}
}
}
}
Expand All @@ -380,6 +388,7 @@ impl TryFrom<&[u8]> for SectionKind {
b"EC PRIVATE KEY" => Self::EcPrivateKey,
b"X509 CRL" => Self::Crl,
b"CERTIFICATE REQUEST" => Self::Csr,
b"ECHCONFIG" => Self::EchConfigList,
_ => return Err(()),
})
}
Expand Down
7 changes: 7 additions & 0 deletions tests/data/ech.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
-----BEGIN PRIVATE KEY-----
MC4CAQAwBQYDK2VuBCIEICjd4yGRdsoP9gU7YT7My8DHx1Tjme8GYDXrOMCi8v1V
-----END PRIVATE KEY-----
-----BEGIN ECHCONFIG-----
AD7+DQA65wAgACA8wVN2BtscOl3vQheUzHeIkVmKIiydUhDCliA4iyQRCwAEAAEA
AQALZXhhbXBsZS5jb20AAA==
-----END ECHCONFIG-----
4 changes: 4 additions & 0 deletions tests/data/zen.pem
Original file line number Diff line number Diff line change
Expand Up @@ -162,4 +162,8 @@ gdiZyLcf1VDCCUGaskEi2CsggCQQJNyGi+8BSQ8MPKm/m0KrSchGQ157eWCCjopz
f5GQe2UGOg5T7g8+S4GdECMwkMlTGUwlAM6LuOG/NZqP528PCAYQv0eOYdSwALQT
GwTyU4AZ9y1uBFuaFxABew9GbDEtNY/XHTF8308edUwGBk6jfD+UuTeEwRZGs9E=
-----END CERTIFICATE REQUEST-----
-----BEGIN ECHCONFIG-----
AD7+DQA65wAgACA8wVN2BtscOl3vQheUzHeIkVmKIiydUhDCliA4iyQRCwAEAAEA
AQALZXhhbXBsZS5jb20AAA==
-----END ECHCONFIG-----
... that's all folks!
36 changes: 33 additions & 3 deletions tests/pem.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ use std::io::Cursor;

use rustls_pki_types::pem::PemObject;
use rustls_pki_types::{
pem, CertificateDer, CertificateRevocationListDer, CertificateSigningRequestDer, PrivateKeyDer,
PrivatePkcs1KeyDer, PrivatePkcs8KeyDer, PrivateSec1KeyDer, SubjectPublicKeyInfoDer,
pem, CertificateDer, CertificateRevocationListDer, CertificateSigningRequestDer,
EchConfigListBytes, PrivateKeyDer, PrivatePkcs1KeyDer, PrivatePkcs8KeyDer, PrivateSec1KeyDer,
SubjectPublicKeyInfoDer,
};

#[test]
Expand Down Expand Up @@ -180,6 +181,34 @@ fn crls() {
);
}

#[test]
fn ech_config() {
let data = include_bytes!("data/zen.pem");

EchConfigListBytes::from_pem_slice(data).unwrap();
EchConfigListBytes::from_pem_reader(&mut Cursor::new(&data[..])).unwrap();
EchConfigListBytes::from_pem_file("tests/data/zen.pem").unwrap();

assert!(matches!(
EchConfigListBytes::from_pem_file("tests/data/certificate.chain.pem").unwrap_err(),
pem::Error::NoItemsFound
));

let (config, key) = EchConfigListBytes::config_and_key_from_iter(
PemObject::pem_file_iter("tests/data/ech.pem").unwrap(),
)
.unwrap();
println!("{config:?} {key:?}");

assert!(matches!(
EchConfigListBytes::config_and_key_from_iter(
PemObject::pem_file_iter("tests/data/certificate.chain.pem").unwrap(),
)
.unwrap_err(),
pem::Error::NoItemsFound,
));
}

#[test]
fn certificates_with_binary() {
let data = include_bytes!("data/gunk.pem");
Expand Down Expand Up @@ -212,7 +241,7 @@ fn parse_in_order() {
let items = <(pem::SectionKind, Vec<u8>) as PemObject>::pem_slice_iter(data)
.collect::<Result<Vec<_>, _>>()
.unwrap();
assert_eq!(items.len(), 11);
assert_eq!(items.len(), 12);
assert!(matches!(items[0], (pem::SectionKind::Certificate, _)));
assert!(matches!(items[1], (pem::SectionKind::Certificate, _)));
assert!(matches!(items[2], (pem::SectionKind::Certificate, _)));
Expand All @@ -224,6 +253,7 @@ fn parse_in_order() {
assert!(matches!(items[8], (pem::SectionKind::PrivateKey, _)));
assert!(matches!(items[9], (pem::SectionKind::Crl, _)));
assert!(matches!(items[10], (pem::SectionKind::Csr, _)));
assert!(matches!(items[11], (pem::SectionKind::EchConfigList, _)));
}

#[test]
Expand Down