diff --git a/src/lib.rs b/src/lib.rs index 6471dd0..197850e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -69,7 +69,7 @@ use pki_types::PrivateKeyDer; #[cfg(feature = "std")] use pki_types::{ CertificateDer, CertificateRevocationListDer, CertificateSigningRequestDer, PrivatePkcs1KeyDer, - PrivatePkcs8KeyDer, PrivateSec1KeyDer, + PrivatePkcs8KeyDer, PrivateSec1KeyDer, SubjectPublicKeyInfoDer, }; #[cfg(feature = "std")] @@ -104,7 +104,10 @@ pub fn private_key(rd: &mut dyn io::BufRead) -> Result return Ok(Some(key.into())), Item::Pkcs8Key(key) => return Ok(Some(key.into())), Item::Sec1Key(key) => return Ok(Some(key.into())), - Item::X509Certificate(_) | Item::Crl(_) | Item::Csr(_) => continue, + Item::X509Certificate(_) + | Item::SubjectPublicKeyInfo(_) + | Item::Crl(_) + | Item::Csr(_) => continue, } } @@ -126,6 +129,7 @@ pub fn csr( | Item::Pkcs8Key(_) | Item::Sec1Key(_) | Item::X509Certificate(_) + | Item::SubjectPublicKeyInfo(_) | Item::Crl(_) => continue, } } @@ -192,3 +196,40 @@ pub fn ec_private_keys( _ => None, }) } + +/// Return the first public key found in `rd`. +/// +/// Yields the first PEM section describing a public key, or an error if a +/// problem occurs while trying to read PEM sections. +#[cfg(feature = "std")] +pub fn public_key( + rd: &mut dyn io::BufRead, +) -> Result>, io::Error> { + for result in iter::from_fn(move || read_one(rd).transpose()) { + match result? { + Item::SubjectPublicKeyInfo(key) => return Ok(Some(key)), + Item::X509Certificate(_) + | Item::Pkcs1Key(_) + | Item::Pkcs8Key(_) + | Item::Sec1Key(_) + | Item::Crl(_) + | Item::Csr(_) => continue, + }; + } + Ok(None) +} + +/// Return an iterator over SPKI-encoded keys from `rd`. +/// +/// Filters out any PEM sections that are not SPKI-encoded public keys and yields errors if a +/// problem occurs while trying to extract a SPKI-encoded public key. +#[cfg(feature = "std")] +pub fn public_keys( + rd: &mut dyn io::BufRead, +) -> impl Iterator, io::Error>> + '_ { + iter::from_fn(move || read_one(rd).transpose()).filter_map(|item| match item { + Ok(Item::SubjectPublicKeyInfo(key)) => Some(Ok(key)), + Err(err) => Some(Err(err)), + _ => None, + }) +} diff --git a/src/pemfile.rs b/src/pemfile.rs index 3fb8c7e..96c3c6b 100644 --- a/src/pemfile.rs +++ b/src/pemfile.rs @@ -10,7 +10,7 @@ use std::io::{self, ErrorKind}; use pki_types::{ CertificateDer, CertificateRevocationListDer, CertificateSigningRequestDer, PrivatePkcs1KeyDer, - PrivatePkcs8KeyDer, PrivateSec1KeyDer, + PrivatePkcs8KeyDer, PrivateSec1KeyDer, SubjectPublicKeyInfoDer, }; /// The contents of a single recognised block in a PEM file. @@ -22,6 +22,11 @@ pub enum Item { /// Appears as "CERTIFICATE" in PEM files. X509Certificate(CertificateDer<'static>), + /// A DER-encoded Subject Public Key Info; as specified in RFC 7468. + /// + /// Appears as "PUBLIC KEY" in PEM files. + SubjectPublicKeyInfo(SubjectPublicKeyInfoDer<'static>), + /// A DER-encoded plaintext RSA private key; as specified in PKCS #1/RFC 3447 /// /// Appears as "RSA PRIVATE KEY" in PEM files. @@ -196,6 +201,7 @@ fn read_one_impl( let item = match section_type.as_slice() { b"CERTIFICATE" => Some(Item::X509Certificate(der.into())), + b"PUBLIC KEY" => Some(Item::SubjectPublicKeyInfo(der.into())), b"RSA PRIVATE KEY" => Some(Item::Pkcs1Key(der.into())), b"PRIVATE KEY" => Some(Item::Pkcs8Key(der.into())), b"EC PRIVATE KEY" => Some(Item::Sec1Key(der.into())), diff --git a/tests/data/spki.pem b/tests/data/spki.pem new file mode 100644 index 0000000..314a0f2 --- /dev/null +++ b/tests/data/spki.pem @@ -0,0 +1,14 @@ +-----BEGIN PUBLIC KEY----- +MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAqIh8FTj9DIgI8DAoCBh+ +6UXOfaWkvNaGZx2GwXl4WDAa/ZSE5/8ofg/6V59bmk9yry57UR4F+blscBvE4g3U +dTvWJOBRD900l21vwpDLKzZguyGOCmKwJu3vCnAQKzBRXW5sDgvO67GeU6kpaic9 +LYPYnYaoxCRTYTZu0wy72rW5G0Fe8Gg/duJmUH7vqGIZupTTVzIBMbFVPBMJqprT +MStDhaUL0JiAz0ZgTeNLRIBZWV9mY4PG3rZtbV0BZGR1ipAq9xfgqJcURCcKl/ZT +UMtzvgk8s5hYkIJX0ZL3qsfdM4BMgIFhHq/GisQKbbu9kWldBrxQylOwa6r0m3Jv +KJX2ViDSORndaCz2sppmVx5HDHnj+Bw381yawphnpumP3BJK4iof//uYKvfdc4RC +y2EXL8PYPsT5DMB0jaBt92ytR5sLhn8Sl9Hk0buN4IjrYPISrdhS45xQXUqxcp9O +9hcU+rSaQyZ45cj+VlWhKq8MDvGvaAONBFSEh01mnUwoJObsAZNVFVtuOkwAli0F +kGouMycQY1BGscpdC516Nya361Hk/ICyby2Y0BJrrVGaSM6poXH9yEjglzAdtSDb +Cvhn/zlAI5ltm4Nv2qTgYBDns5JRGVhBym6RbbZ1C/KfCgn0hOxiw3N7AN4d0K5n +LI6p7U9RnNVbWgbqsuoxBtkCAwEAAQ== +-----END PUBLIC KEY----- diff --git a/tests/data/zen2.pem b/tests/data/zen2.pem index abff47f..222bd87 100644 --- a/tests/data/zen2.pem +++ b/tests/data/zen2.pem @@ -205,4 +205,32 @@ fxfwqBTecp4MBmRbH76PLKjekhL/LTWJnvVTzBLFo9PLIUIyTiZjAkEAuODuDzIP LUuD0YV85m8BXM+OoPbyNmH9UmwPP0iFigbPz2IdYeSWU6Kxs6b5J7KdULwKgVeY k9zbpTrkZyMjXw== -----END PRIVATE KEY----- +-----BEGIN PUBLIC KEY----- +MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAqIh8FTj9DIgI8DAoCBh+ +6UXOfaWkvNaGZx2GwXl4WDAa/ZSE5/8ofg/6V59bmk9yry57UR4F+blscBvE4g3U +dTvWJOBRD900l21vwpDLKzZguyGOCmKwJu3vCnAQKzBRXW5sDgvO67GeU6kpaic9 +LYPYnYaoxCRTYTZu0wy72rW5G0Fe8Gg/duJmUH7vqGIZupTTVzIBMbFVPBMJqprT +MStDhaUL0JiAz0ZgTeNLRIBZWV9mY4PG3rZtbV0BZGR1ipAq9xfgqJcURCcKl/ZT +UMtzvgk8s5hYkIJX0ZL3qsfdM4BMgIFhHq/GisQKbbu9kWldBrxQylOwa6r0m3Jv +KJX2ViDSORndaCz2sppmVx5HDHnj+Bw381yawphnpumP3BJK4iof//uYKvfdc4RC +y2EXL8PYPsT5DMB0jaBt92ytR5sLhn8Sl9Hk0buN4IjrYPISrdhS45xQXUqxcp9O +9hcU+rSaQyZ45cj+VlWhKq8MDvGvaAONBFSEh01mnUwoJObsAZNVFVtuOkwAli0F +kGouMycQY1BGscpdC516Nya361Hk/ICyby2Y0BJrrVGaSM6poXH9yEjglzAdtSDb +Cvhn/zlAI5ltm4Nv2qTgYBDns5JRGVhBym6RbbZ1C/KfCgn0hOxiw3N7AN4d0K5n +LI6p7U9RnNVbWgbqsuoxBtkCAwEAAQ== +-----END PUBLIC KEY----- +-----BEGIN PUBLIC KEY----- +MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAqIh8FTj9DIgI8DAoCBh+ +6UXOfaWkvNaGZx2GwXl4WDAa/ZSE5/8ofg/6V59bmk9yry57UR4F+blscBvE4g3U +dTvWJOBRD900l21vwpDLKzZguyGOCmKwJu3vCnAQKzBRXW5sDgvO67GeU6kpaic9 +LYPYnYaoxCRTYTZu0wy72rW5G0Fe8Gg/duJmUH7vqGIZupTTVzIBMbFVPBMJqprT +MStDhaUL0JiAz0ZgTeNLRIBZWV9mY4PG3rZtbV0BZGR1ipAq9xfgqJcURCcKl/ZT +UMtzvgk8s5hYkIJX0ZL3qsfdM4BMgIFhHq/GisQKbbu9kWldBrxQylOwa6r0m3Jv +KJX2ViDSORndaCz2sppmVx5HDHnj+Bw381yawphnpumP3BJK4iof//uYKvfdc4RC +y2EXL8PYPsT5DMB0jaBt92ytR5sLhn8Sl9Hk0buN4IjrYPISrdhS45xQXUqxcp9O +9hcU+rSaQyZ45cj+VlWhKq8MDvGvaAONBFSEh01mnUwoJObsAZNVFVtuOkwAli0F +kGouMycQY1BGscpdC516Nya361Hk/ICyby2Y0BJrrVGaSM6poXH9yEjglzAdtSDb +Cvhn/zlAI5ltm4Nv2qTgYBDns5JRGVhBym6RbbZ1C/KfCgn0hOxiw3N7AN4d0K5n +LI6p7U9RnNVbWgbqsuoxBtkCAwEAAQ== +-----END PUBLIC KEY----- ... that's all folks! diff --git a/tests/integration.rs b/tests/integration.rs index d9620ff..35ef2b0 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -26,6 +26,50 @@ fn private_key() { assert!(rustls_pemfile::private_key(&mut reader).unwrap().is_none()); } +#[test] +fn public_keys() { + let data = include_bytes!("data/spki.pem"); + let mut reader = BufReader::new(&data[..]); + assert_eq!( + rustls_pemfile::public_keys(&mut reader) + .collect::, _>>() + .unwrap() + .len(), + 1 + ); + + let data = include_bytes!("data/zen2.pem"); + let mut reader = BufReader::new(&data[..]); + assert_eq!( + rustls_pemfile::public_keys(&mut reader) + .collect::, _>>() + .unwrap() + .len(), + 2 + ); + + let data = include_bytes!("data/certificate.chain.pem"); + let mut reader = BufReader::new(&data[..]); + assert_eq!( + rustls_pemfile::public_keys(&mut reader) + .collect::, _>>() + .unwrap() + .len(), + 0 + ); +} + +#[test] +fn public_key() { + let data = include_bytes!("data/spki.pem"); + let mut reader = BufReader::new(&data[..]); + rustls_pemfile::public_key(&mut reader).unwrap().unwrap(); + + let data = include_bytes!("data/certificate.chain.pem"); + let mut reader = BufReader::new(&data[..]); + assert!(rustls_pemfile::public_key(&mut reader).unwrap().is_none()); +} + #[test] fn test_csr() { let data = include_bytes!("data/csr.pem"); @@ -115,7 +159,7 @@ fn smoketest_iterate() { count += 1; } - assert_eq!(count, 16); + assert_eq!(count, 18); } #[test]