rustls_pemfile/
pemfile.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
use alloc::format;
use alloc::string::String;
use alloc::vec::Vec;
#[cfg(feature = "std")]
use core::iter;
#[cfg(feature = "std")]
use std::io::{self, ErrorKind};

use pki_types::{
    pem, CertificateDer, CertificateRevocationListDer, CertificateSigningRequestDer,
    PrivatePkcs1KeyDer, PrivatePkcs8KeyDer, PrivateSec1KeyDer, SubjectPublicKeyInfoDer,
};

/// The contents of a single recognised block in a PEM file.
#[non_exhaustive]
#[derive(Debug, PartialEq)]
pub enum Item {
    /// A DER-encoded x509 certificate.
    ///
    /// 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.
    Pkcs1Key(PrivatePkcs1KeyDer<'static>),

    /// A DER-encoded plaintext private key; as specified in PKCS #8/RFC 5958
    ///
    /// Appears as "PRIVATE KEY" in PEM files.
    Pkcs8Key(PrivatePkcs8KeyDer<'static>),

    /// A Sec1-encoded plaintext private key; as specified in RFC 5915
    ///
    /// Appears as "EC PRIVATE KEY" in PEM files.
    Sec1Key(PrivateSec1KeyDer<'static>),

    /// A Certificate Revocation List; as specified in RFC 5280
    ///
    /// Appears as "X509 CRL" in PEM files.
    Crl(CertificateRevocationListDer<'static>),

    /// A Certificate Signing Request; as specified in RFC 2986
    ///
    /// Appears as "CERTIFICATE REQUEST" in PEM files.
    Csr(CertificateSigningRequestDer<'static>),
}

impl Item {
    #[cfg(feature = "std")]
    fn from_buf(rd: &mut dyn io::BufRead) -> Result<Option<Self>, pem::Error> {
        loop {
            match pem::from_buf(rd)? {
                Some((kind, data)) => match Self::from_kind(kind, data) {
                    Some(item) => return Ok(Some(item)),
                    None => continue,
                },

                None => return Ok(None),
            }
        }
    }

    fn from_slice(pem: &[u8]) -> Result<Option<(Self, &[u8])>, pem::Error> {
        let mut iter = <(pem::SectionKind, Vec<u8>) as pem::PemObject>::pem_slice_iter(pem);

        for found in iter.by_ref() {
            match found {
                Ok((kind, data)) => match Self::from_kind(kind, data) {
                    Some(item) => return Ok(Some((item, iter.remainder()))),
                    None => continue,
                },
                Err(err) => return Err(err.into()),
            }
        }

        Ok(None)
    }

    fn from_kind(kind: pem::SectionKind, data: Vec<u8>) -> Option<Self> {
        use pem::SectionKind::*;
        match kind {
            Certificate => Some(Self::X509Certificate(data.into())),
            PublicKey => Some(Self::SubjectPublicKeyInfo(data.into())),
            RsaPrivateKey => Some(Self::Pkcs1Key(data.into())),
            PrivateKey => Some(Self::Pkcs8Key(data.into())),
            EcPrivateKey => Some(Self::Sec1Key(data.into())),
            Crl => Some(Self::Crl(data.into())),
            Csr => Some(Self::Csr(data.into())),
            _ => None,
        }
    }
}

/// Errors that may arise when parsing the contents of a PEM file
///
/// This differs from [`rustls_pki_types::pem::Error`] because it is `PartialEq`;
/// it is retained for compatibility.
#[derive(Debug, PartialEq)]
pub enum Error {
    /// a section is missing its "END marker" line
    MissingSectionEnd {
        /// the expected "END marker" line that was not found
        end_marker: Vec<u8>,
    },

    /// syntax error found in the line that starts a new section
    IllegalSectionStart {
        /// line that contains the syntax error
        line: Vec<u8>,
    },

    /// base64 decode error
    Base64Decode(String),
}

#[cfg(feature = "std")]
impl From<Error> for io::Error {
    fn from(error: Error) -> Self {
        match error {
            Error::MissingSectionEnd { end_marker } => io::Error::new(
                ErrorKind::InvalidData,
                format!(
                    "section end {:?} missing",
                    String::from_utf8_lossy(&end_marker)
                ),
            ),

            Error::IllegalSectionStart { line } => io::Error::new(
                ErrorKind::InvalidData,
                format!(
                    "illegal section start: {:?}",
                    String::from_utf8_lossy(&line)
                ),
            ),

            Error::Base64Decode(err) => io::Error::new(ErrorKind::InvalidData, err),
        }
    }
}

impl From<pem::Error> for Error {
    fn from(error: pem::Error) -> Self {
        match error {
            pem::Error::MissingSectionEnd { end_marker } => Error::MissingSectionEnd { end_marker },
            pem::Error::IllegalSectionStart { line } => Error::IllegalSectionStart { line },
            pem::Error::Base64Decode(str) => Error::Base64Decode(str),

            // this is a necessary bodge to funnel any new errors into our existing type
            // (to which we can add no new variants)
            other => Error::Base64Decode(format!("{other:?}")),
        }
    }
}

/// Extract and decode the next PEM section from `input`
///
/// - `Ok(None)` is returned if there is no PEM section to read from `input`
/// - Syntax errors and decoding errors produce a `Err(...)`
/// - Otherwise each decoded section is returned with a `Ok(Some((Item::..., remainder)))` where
///   `remainder` is the part of the `input` that follows the returned section
pub fn read_one_from_slice(input: &[u8]) -> Result<Option<(Item, &[u8])>, Error> {
    Item::from_slice(input).map_err(Into::into)
}

/// Extract and decode the next PEM section from `rd`.
///
/// - Ok(None) is returned if there is no PEM section read from `rd`.
/// - Underlying IO errors produce a `Err(...)`
/// - Otherwise each decoded section is returned with a `Ok(Some(Item::...))`
///
/// You can use this function to build an iterator, for example:
/// `for item in iter::from_fn(|| read_one(rd).transpose()) { ... }`
#[cfg(feature = "std")]
pub fn read_one(rd: &mut dyn io::BufRead) -> Result<Option<Item>, io::Error> {
    Item::from_buf(rd).map_err(|err| match err {
        pem::Error::Io(io) => io,
        other => Error::from(other).into(),
    })
}

/// Extract and return all PEM sections by reading `rd`.
#[cfg(feature = "std")]
pub fn read_all(rd: &mut dyn io::BufRead) -> impl Iterator<Item = Result<Item, io::Error>> + '_ {
    iter::from_fn(move || read_one(rd).transpose())
}