1use alloc::format;
2use alloc::string::String;
3use alloc::vec::Vec;
4#[cfg(feature = "std")]
5use core::iter;
6#[cfg(feature = "std")]
7use std::io::{self, ErrorKind};
8
9use pki_types::{
10 pem, CertificateDer, CertificateRevocationListDer, CertificateSigningRequestDer,
11 PrivatePkcs1KeyDer, PrivatePkcs8KeyDer, PrivateSec1KeyDer, SubjectPublicKeyInfoDer,
12};
13
14/// The contents of a single recognised block in a PEM file.
15#[non_exhaustive]
16#[derive(Debug, PartialEq)]
17pub enum Item {
18 /// A DER-encoded x509 certificate.
19 ///
20 /// Appears as "CERTIFICATE" in PEM files.
21 X509Certificate(CertificateDer<'static>),
22
23 /// A DER-encoded Subject Public Key Info; as specified in RFC 7468.
24 ///
25 /// Appears as "PUBLIC KEY" in PEM files.
26 SubjectPublicKeyInfo(SubjectPublicKeyInfoDer<'static>),
27
28 /// A DER-encoded plaintext RSA private key; as specified in PKCS #1/RFC 3447
29 ///
30 /// Appears as "RSA PRIVATE KEY" in PEM files.
31 Pkcs1Key(PrivatePkcs1KeyDer<'static>),
32
33 /// A DER-encoded plaintext private key; as specified in PKCS #8/RFC 5958
34 ///
35 /// Appears as "PRIVATE KEY" in PEM files.
36 Pkcs8Key(PrivatePkcs8KeyDer<'static>),
37
38 /// A Sec1-encoded plaintext private key; as specified in RFC 5915
39 ///
40 /// Appears as "EC PRIVATE KEY" in PEM files.
41 Sec1Key(PrivateSec1KeyDer<'static>),
42
43 /// A Certificate Revocation List; as specified in RFC 5280
44 ///
45 /// Appears as "X509 CRL" in PEM files.
46 Crl(CertificateRevocationListDer<'static>),
47
48 /// A Certificate Signing Request; as specified in RFC 2986
49 ///
50 /// Appears as "CERTIFICATE REQUEST" in PEM files.
51 Csr(CertificateSigningRequestDer<'static>),
52}
53
54impl Item {
55 #[cfg(feature = "std")]
56 fn from_buf(rd: &mut dyn io::BufRead) -> Result<Option<Self>, pem::Error> {
57 loop {
58 match pem::from_buf(rd)? {
59 Some((kind, data)) => match Self::from_kind(kind, data) {
60 Some(item) => return Ok(Some(item)),
61 None => continue,
62 },
63
64 None => return Ok(None),
65 }
66 }
67 }
68
69 fn from_slice(pem: &[u8]) -> Result<Option<(Self, &[u8])>, pem::Error> {
70 let mut iter = <(pem::SectionKind, Vec<u8>) as pem::PemObject>::pem_slice_iter(pem);
71
72 for found in iter.by_ref() {
73 match found {
74 Ok((kind, data)) => match Self::from_kind(kind, data) {
75 Some(item) => return Ok(Some((item, iter.remainder()))),
76 None => continue,
77 },
78 Err(err) => return Err(err.into()),
79 }
80 }
81
82 Ok(None)
83 }
84
85 fn from_kind(kind: pem::SectionKind, data: Vec<u8>) -> Option<Self> {
86 use pem::SectionKind::*;
87 match kind {
88 Certificate => Some(Self::X509Certificate(data.into())),
89 PublicKey => Some(Self::SubjectPublicKeyInfo(data.into())),
90 RsaPrivateKey => Some(Self::Pkcs1Key(data.into())),
91 PrivateKey => Some(Self::Pkcs8Key(data.into())),
92 EcPrivateKey => Some(Self::Sec1Key(data.into())),
93 Crl => Some(Self::Crl(data.into())),
94 Csr => Some(Self::Csr(data.into())),
95 _ => None,
96 }
97 }
98}
99
100/// Errors that may arise when parsing the contents of a PEM file
101///
102/// This differs from [`rustls_pki_types::pem::Error`] because it is `PartialEq`;
103/// it is retained for compatibility.
104#[derive(Debug, PartialEq)]
105pub enum Error {
106 /// a section is missing its "END marker" line
107 MissingSectionEnd {
108 /// the expected "END marker" line that was not found
109 end_marker: Vec<u8>,
110 },
111
112 /// syntax error found in the line that starts a new section
113 IllegalSectionStart {
114 /// line that contains the syntax error
115 line: Vec<u8>,
116 },
117
118 /// base64 decode error
119 Base64Decode(String),
120}
121
122#[cfg(feature = "std")]
123impl From<Error> for io::Error {
124 fn from(error: Error) -> Self {
125 match error {
126 Error::MissingSectionEnd { end_marker: Vec } => io::Error::new(
127 kind:ErrorKind::InvalidData,
128 error:format!(
129 "section end {:?} missing",
130 String::from_utf8_lossy(&end_marker)
131 ),
132 ),
133
134 Error::IllegalSectionStart { line: Vec } => io::Error::new(
135 kind:ErrorKind::InvalidData,
136 error:format!(
137 "illegal section start: {:?}",
138 String::from_utf8_lossy(&line)
139 ),
140 ),
141
142 Error::Base64Decode(err: String) => io::Error::new(kind:ErrorKind::InvalidData, error:err),
143 }
144 }
145}
146
147impl From<pem::Error> for Error {
148 fn from(error: pem::Error) -> Self {
149 match error {
150 pem::Error::MissingSectionEnd { end_marker: Vec } => Error::MissingSectionEnd { end_marker },
151 pem::Error::IllegalSectionStart { line: Vec } => Error::IllegalSectionStart { line },
152 pem::Error::Base64Decode(str: String) => Error::Base64Decode(str),
153
154 // this is a necessary bodge to funnel any new errors into our existing type
155 // (to which we can add no new variants)
156 other: Error => Error::Base64Decode(format!("{other:?}")),
157 }
158 }
159}
160
161/// Extract and decode the next PEM section from `input`
162///
163/// - `Ok(None)` is returned if there is no PEM section to read from `input`
164/// - Syntax errors and decoding errors produce a `Err(...)`
165/// - Otherwise each decoded section is returned with a `Ok(Some((Item::..., remainder)))` where
166/// `remainder` is the part of the `input` that follows the returned section
167pub fn read_one_from_slice(input: &[u8]) -> Result<Option<(Item, &[u8])>, Error> {
168 Item::from_slice(input).map_err(op:Into::into)
169}
170
171/// Extract and decode the next PEM section from `rd`.
172///
173/// - Ok(None) is returned if there is no PEM section read from `rd`.
174/// - Underlying IO errors produce a `Err(...)`
175/// - Otherwise each decoded section is returned with a `Ok(Some(Item::...))`
176///
177/// You can use this function to build an iterator, for example:
178/// `for item in iter::from_fn(|| read_one(rd).transpose()) { ... }`
179#[cfg(feature = "std")]
180pub fn read_one(rd: &mut dyn io::BufRead) -> Result<Option<Item>, io::Error> {
181 Item::from_buf(rd).map_err(|err: Error| match err {
182 pem::Error::Io(io: Error) => io,
183 other: Error => Error::from(other).into(),
184 })
185}
186
187/// Extract and return all PEM sections by reading `rd`.
188#[cfg(feature = "std")]
189pub fn read_all(rd: &mut dyn io::BufRead) -> impl Iterator<Item = Result<Item, io::Error>> + '_ {
190 iter::from_fn(move || read_one(rd).transpose())
191}
192