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
use base64;
use std::io::{self, ErrorKind};

/// The contents of a single recognised block in a PEM file.
#[non_exhaustive]
#[derive(Debug, PartialEq)]
pub enum Item {
    /// A DER-encoded x509 certificate.
    X509Certificate(Vec<u8>),

    /// A DER-encoded plaintext RSA private key; as specified in PKCS#1/RFC3447
    RSAKey(Vec<u8>),

    /// A DER-encoded plaintext private key; as specified in PKCS#8/RFC5958
    PKCS8Key(Vec<u8>),

    /// A Sec1-encoded plaintext private key; as specified in RFC5915
    ECKey(Vec<u8>),
}

impl Item {
    fn from_start_line(start_line: &str, der: Vec<u8>) -> Option<Item> {
        match start_line {
            "CERTIFICATE" => Some(Item::X509Certificate(der)),
            "RSA PRIVATE KEY" => Some(Item::RSAKey(der)),
            "PRIVATE KEY" => Some(Item::PKCS8Key(der)),
            "EC PRIVATE KEY" => Some(Item::ECKey(der)),
            _ => None,
        }
    }
}

/// 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()) { ... }`
pub fn read_one(rd: &mut dyn io::BufRead) -> Result<Option<Item>, io::Error> {
    let mut b64buf = String::with_capacity(1024);
    let mut section_type = None;
    let mut end_marker = None;
    let mut line = String::with_capacity(80);

    loop {
        line.clear();
        let len = rd.read_line(&mut line)?;

        if len == 0 {
            // EOF
            if end_marker.is_some() {
                return Err(io::Error::new(
                    ErrorKind::InvalidData,
                    format!("section end {:?} missing", end_marker.unwrap()),
                ));
            }
            return Ok(None);
        }

        if line.starts_with("-----BEGIN ") {
            let trailer = line[11..]
                .find("-----")
                .ok_or_else(|| {
                    io::Error::new(
                        ErrorKind::InvalidData,
                        format!("illegal section start: {:?}", line),
                    )
                })?;

            let ty = &line[11..11 + trailer];

            section_type = Some(ty.to_string());
            end_marker = Some(format!("-----END {}-----", ty).to_string());
            continue;
        }

        if end_marker.is_some() && line.starts_with(end_marker.as_ref().unwrap()) {
            let der = base64::decode(&b64buf)
                .map_err(|err| io::Error::new(ErrorKind::InvalidData, err))?;

            let item = Item::from_start_line(&section_type.unwrap(), der);

            if let Some(item) = item {
                return Ok(Some(item));
            } else {
                section_type = None;
                end_marker = None;
                b64buf.clear();
            }
        }

        if section_type.is_some() {
            b64buf.push_str(line.trim());
        }
    }
}

/// Extract and return all PEM sections by reading `rd`.
pub fn read_all(rd: &mut dyn io::BufRead) -> Result<Vec<Item>, io::Error> {
    let mut v = Vec::<Item>::new();

    loop {
        match read_one(rd)? {
            None => return Ok(v),
            Some(item) => v.push(item),
        }
    }
}