pem-1.1.1/.cargo_vcs_info.json0000644000000001121435753455700116670ustar { "git": { "sha1": "a4179f9ee1bd5e47deb8d35de7f37001cf552c46" } } pem-1.1.1/.gitignore010064400007650000024000000000451355660057000124500ustar 00000000000000target Cargo.lock *.bk .*.swp .idea/ pem-1.1.1/.travis.yml010064400007650000024000000021401366744003400125670ustar 00000000000000sudo: false language: rust rust: - beta - stable - 1.42.0 before_script: - | pip install 'travis-cargo<0.2' --user && export PATH=$HOME/.local/bin:$PATH script: - | travis-cargo build && travis-cargo test && travis-cargo bench && travis-cargo --only stable doc addons: apt: packages: - libcurl4-openssl-dev - libelf-dev - libdw-dev notifications: email: on_success: never env: global: - secure: BvDe6NNo+148Fuy293ptMOkR/X30Gf4ZLFQLxs8I6xlEgzuCi29X7QbOFXRRuFg2HTGhiHBUFZOUoATlr1uuOGfQQB9MI1W+78z/cveB730yZMXXierrWl31PeWGPRVRlBvC2o5sarWmad0giQVdFXl+ZhV3qPI6+Ig5QEkcdpY/yHxZ6jq60hoSN/IXZcSQIF3XJHt9FNofj4w0MyyvU7WBdlIYoAi5YliAJybcAR/yVO5ZJhpw30W01eeLdTy0FRahNAnMAkjMZkvAmSvNkmDMSWN8rie49h/PPAAKSAp9k8Z+FKii/N6eTltfpp3HleD/WaVR2fv9h0Qe7Fpx3vvRferdRCu8o8uYD8e3TURsgbVMPkI3Wzb3eRGAIk3f50DyGsm2Whyv+xMHe3fu4cy/1+lVqYyzjGWBT48+/s5yPZlZDALtS41Hhf5BUVxmKqilmL7zEzerG7xTic3/++8zYwTSfTZ0Q8qZ82FGEpECNfe8EZF33PEGGzq/+LTj7t7dosmSmbtLT2mndD7ZwV/sBD+7t1Q8qA3pwFk68kueQC/MbyHUJqgXuM9aHusfImo+VEV1yn8qs1stVKEeF0XJtVJsE8XQs8TBosEo7sviz/xYwY2bn/6u41uj/M5RbFOMBfN60goUhBz3jEqwyxW7vfJ4XVn1IclI757PM9U= pem-1.1.1/CHANGELOG.md010064400007650000024000000003421413557245500122750ustar 00000000000000# 1.0.1 - hide the ASCII_ARMOR symbol to work around a linking issue with 32-bit windows builds # 1.0 - `pem::parse_many` now returns a `Result>` instead of a `Vec` that silently discarded invalid sections. pem-1.1.1/Cargo.toml0000644000000023131435753455700076720ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies # # If you believe there's an error in this file please file an # issue against the rust-lang/cargo repository. If you're # editing this file be aware that the upstream Cargo.toml # will likely look very different (and much more reasonable) [package] edition = "2018" name = "pem" version = "1.1.1" authors = ["Jonathan Creekmore "] description = "Parse and encode PEM-encoded data." homepage = "https://github.com/jcreekmore/pem-rs.git" documentation = "https://docs.rs/pem/" readme = "README.md" categories = ["cryptography"] license = "MIT" repository = "https://github.com/jcreekmore/pem-rs.git" [[bench]] name = "pem_benchmark" harness = false [dependencies.base64] version = "0.13.0" [dependencies.serde] version = "1" features = ["serde_derive"] optional = true [dev-dependencies.criterion] version = "0.3.0" [dev-dependencies.serde_json] version = "1" [badges.travis-ci] repository = "jcreekmore/pem-rs" pem-1.1.1/Cargo.toml.orig010064400007650000024000000012551435753455700133660ustar 00000000000000[package] authors = ["Jonathan Creekmore "] description = "Parse and encode PEM-encoded data." documentation = "https://docs.rs/pem/" homepage = "https://github.com/jcreekmore/pem-rs.git" license = "MIT" name = "pem" readme = "README.md" repository = "https://github.com/jcreekmore/pem-rs.git" version = "1.1.1" categories = [ "cryptography" ] edition = "2018" rust-version = "1.34.2" [dependencies] base64 = "0.13.0" serde = { version = "1", optional = true, features = ["serde_derive"] } [dev-dependencies] criterion = "0.3.0" serde_json = "1" [[bench]] name = "pem_benchmark" harness = false [badges] travis-ci = { repository = "jcreekmore/pem-rs" } pem-1.1.1/LICENSE.md010064400007650000024000000020751266434431500120720ustar 00000000000000The MIT License (MIT) Copyright (c) 2016 Jonathan Creekmore Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. pem-1.1.1/README.md010064400007650000024000000021431435727302500117410ustar 00000000000000pem === A Rust library for parsing and encoding PEM-encoded data. [![Build Status](https://travis-ci.org/jcreekmore/pem-rs.svg?branch=master)](https://travis-ci.org/jcreekmore/pem-rs) ### Documentation [Module documentation with examples](https://docs.rs/pem/) ### Usage Add this to your `Cargo.toml`: ```toml [dependencies] pem = "1.0" ``` and this to your crate root: ```rust extern crate pem; ``` Here is a simple example that parse PEM-encoded data and prints the tag: ```rust extern crate pem; use pem::parse; const SAMPLE: &'static str = "-----BEGIN RSA PRIVATE KEY----- MIIBPQIBAAJBAOsfi5AGYhdRs/x6q5H7kScxA0Kzzqe6WI6gf6+tc6IvKQJo5rQc dWWSQ0nRGt2hOPDO+35NKhQEjBQxPh/v7n0CAwEAAQJBAOGaBAyuw0ICyENy5NsO 2gkT00AWTSzM9Zns0HedY31yEabkuFvrMCHjscEF7u3Y6PB7An3IzooBHchsFDei AAECIQD/JahddzR5K3A6rzTidmAf1PBtqi7296EnWv8WvpfAAQIhAOvowIXZI4Un DXjgZ9ekuUjZN+GUQRAVlkEEohGLVy59AiEA90VtqDdQuWWpvJX0cM08V10tLXrT TTGsEtITid1ogAECIQDAaFl90ZgS5cMrL3wCeatVKzVUmuJmB/VAmlLFFGzK0QIh ANJGc7AFk4fyFD/OezhwGHbWmo/S+bfeAiIh2Ss2FxKJ -----END RSA PRIVATE KEY----- "; let pem = parse(SAMPLE)?; println!("PEM tag: {}", pem.tag); ``` pem-1.1.1/benches/pem_benchmark.rs010064400007650000024000000161661412562534000152360ustar 00000000000000#[macro_use] extern crate criterion; extern crate pem; use criterion::Criterion; const SAMPLE: &'static str = "-----BEGIN RSA PRIVATE KEY-----\r MIIBPQIBAAJBAOsfi5AGYhdRs/x6q5H7kScxA0Kzzqe6WI6gf6+tc6IvKQJo5rQc\r dWWSQ0nRGt2hOPDO+35NKhQEjBQxPh/v7n0CAwEAAQJBAOGaBAyuw0ICyENy5NsO\r 2gkT00AWTSzM9Zns0HedY31yEabkuFvrMCHjscEF7u3Y6PB7An3IzooBHchsFDei\r AAECIQD/JahddzR5K3A6rzTidmAf1PBtqi7296EnWv8WvpfAAQIhAOvowIXZI4Un\r DXjgZ9ekuUjZN+GUQRAVlkEEohGLVy59AiEA90VtqDdQuWWpvJX0cM08V10tLXrT\r TTGsEtITid1ogAECIQDAaFl90ZgS5cMrL3wCeatVKzVUmuJmB/VAmlLFFGzK0QIh\r ANJGc7AFk4fyFD/OezhwGHbWmo/S+bfeAiIh2Ss2FxKJ\r -----END RSA PRIVATE KEY-----\r \r -----BEGIN RSA PUBLIC KEY-----\r MIIBOgIBAAJBAMIeCnn9G/7g2Z6J+qHOE2XCLLuPoh5NHTO2Fm+PbzBvafBo0oYo\r QVVy7frzxmOqx6iIZBxTyfAQqBPO3Br59BMCAwEAAQJAX+PjHPuxdqiwF6blTkS0\r RFI1MrnzRbCmOkM6tgVO0cd6r5Z4bDGLusH9yjI9iI84gPRjK0AzymXFmBGuREHI\r sQIhAPKf4pp+Prvutgq2ayygleZChBr1DC4XnnufBNtaswyvAiEAzNGVKgNvzuhk\r ijoUXIDruJQEGFGvZTsi1D2RehXiT90CIQC4HOQUYKCydB7oWi1SHDokFW2yFyo6\r /+lf3fgNjPI6OQIgUPmTFXciXxT1msh3gFLf3qt2Kv8wbr9Ad9SXjULVpGkCIB+g\r RzHX0lkJl9Stshd/7Gbt65/QYq+v+xvAeT0CoyIg\r -----END RSA PUBLIC KEY-----\r "; fn pem_parse() { pem::parse(SAMPLE).unwrap(); } fn pem_parse_many() { pem::parse_many(SAMPLE).unwrap(); } fn pem_encode(pem: &pem::Pem) { pem::encode(pem); } fn pem_encode_many(pems: &std::vec::Vec) { pem::encode_many(pems); } fn criterion_benchmark(c: &mut Criterion) { // Parse c.bench_function("pem::parse", |b| b.iter(|| pem_parse())); c.bench_function("pem::parse_many", |b| b.iter(|| pem_parse_many())); // Encode let pem = pem::Pem { tag: String::from("RSA PRIVATE KEY"), contents: vec![ 48, 130, 1, 61, 2, 1, 0, 2, 65, 0, 235, 31, 139, 144, 6, 98, 23, 81, 179, 252, 122, 171, 145, 251, 145, 39, 49, 3, 66, 179, 206, 167, 186, 88, 142, 160, 127, 175, 173, 115, 162, 47, 41, 2, 104, 230, 180, 28, 117, 101, 146, 67, 73, 209, 26, 221, 161, 56, 240, 206, 251, 126, 77, 42, 20, 4, 140, 20, 49, 62, 31, 239, 238, 125, 2, 3, 1, 0, 1, 2, 65, 0, 225, 154, 4, 12, 174, 195, 66, 2, 200, 67, 114, 228, 219, 14, 218, 9, 19, 211, 64, 22, 77, 44, 204, 245, 153, 236, 208, 119, 157, 99, 125, 114, 17, 166, 228, 184, 91, 235, 48, 33, 227, 177, 193, 5, 238, 237, 216, 232, 240, 123, 2, 125, 200, 206, 138, 1, 29, 200, 108, 20, 55, 162, 0, 1, 2, 33, 0, 255, 37, 168, 93, 119, 52, 121, 43, 112, 58, 175, 52, 226, 118, 96, 31, 212, 240, 109, 170, 46, 246, 247, 161, 39, 90, 255, 22, 190, 151, 192, 1, 2, 33, 0, 235, 232, 192, 133, 217, 35, 133, 39, 13, 120, 224, 103, 215, 164, 185, 72, 217, 55, 225, 148, 65, 16, 21, 150, 65, 4, 162, 17, 139, 87, 46, 125, 2, 33, 0, 247, 69, 109, 168, 55, 80, 185, 101, 169, 188, 149, 244, 112, 205, 60, 87, 93, 45, 45, 122, 211, 77, 49, 172, 18, 210, 19, 137, 221, 104, 128, 1, 2, 33, 0, 192, 104, 89, 125, 209, 152, 18, 229, 195, 43, 47, 124, 2, 121, 171, 85, 43, 53, 84, 154, 226, 102, 7, 245, 64, 154, 82, 197, 20, 108, 202, 209, 2, 33, 0, 210, 70, 115, 176, 5, 147, 135, 242, 20, 63, 206, 123, 56, 112, 24, 118, 214, 154, 143, 210, 249, 183, 222, 2, 34, 33, 217, 43, 54, 23, 18, 137, ], }; c.bench_function("pem::encode", move |b| b.iter(|| pem_encode(&pem))); let pems = vec![ pem::Pem { tag: String::from("RSA PRIVATE KEY"), contents: vec![ 48, 130, 1, 61, 2, 1, 0, 2, 65, 0, 235, 31, 139, 144, 6, 98, 23, 81, 179, 252, 122, 171, 145, 251, 145, 39, 49, 3, 66, 179, 206, 167, 186, 88, 142, 160, 127, 175, 173, 115, 162, 47, 41, 2, 104, 230, 180, 28, 117, 101, 146, 67, 73, 209, 26, 221, 161, 56, 240, 206, 251, 126, 77, 42, 20, 4, 140, 20, 49, 62, 31, 239, 238, 125, 2, 3, 1, 0, 1, 2, 65, 0, 225, 154, 4, 12, 174, 195, 66, 2, 200, 67, 114, 228, 219, 14, 218, 9, 19, 211, 64, 22, 77, 44, 204, 245, 153, 236, 208, 119, 157, 99, 125, 114, 17, 166, 228, 184, 91, 235, 48, 33, 227, 177, 193, 5, 238, 237, 216, 232, 240, 123, 2, 125, 200, 206, 138, 1, 29, 200, 108, 20, 55, 162, 0, 1, 2, 33, 0, 255, 37, 168, 93, 119, 52, 121, 43, 112, 58, 175, 52, 226, 118, 96, 31, 212, 240, 109, 170, 46, 246, 247, 161, 39, 90, 255, 22, 190, 151, 192, 1, 2, 33, 0, 235, 232, 192, 133, 217, 35, 133, 39, 13, 120, 224, 103, 215, 164, 185, 72, 217, 55, 225, 148, 65, 16, 21, 150, 65, 4, 162, 17, 139, 87, 46, 125, 2, 33, 0, 247, 69, 109, 168, 55, 80, 185, 101, 169, 188, 149, 244, 112, 205, 60, 87, 93, 45, 45, 122, 211, 77, 49, 172, 18, 210, 19, 137, 221, 104, 128, 1, 2, 33, 0, 192, 104, 89, 125, 209, 152, 18, 229, 195, 43, 47, 124, 2, 121, 171, 85, 43, 53, 84, 154, 226, 102, 7, 245, 64, 154, 82, 197, 20, 108, 202, 209, 2, 33, 0, 210, 70, 115, 176, 5, 147, 135, 242, 20, 63, 206, 123, 56, 112, 24, 118, 214, 154, 143, 210, 249, 183, 222, 2, 34, 33, 217, 43, 54, 23, 18, 137, ], }, pem::Pem { tag: String::from("RSA PUBLIC KEY"), contents: vec![ 48, 130, 1, 58, 2, 1, 0, 2, 65, 0, 194, 30, 10, 121, 253, 27, 254, 224, 217, 158, 137, 250, 161, 206, 19, 101, 194, 44, 187, 143, 162, 30, 77, 29, 51, 182, 22, 111, 143, 111, 48, 111, 105, 240, 104, 210, 134, 40, 65, 85, 114, 237, 250, 243, 198, 99, 170, 199, 168, 136, 100, 28, 83, 201, 240, 16, 168, 19, 206, 220, 26, 249, 244, 19, 2, 3, 1, 0, 1, 2, 64, 95, 227, 227, 28, 251, 177, 118, 168, 176, 23, 166, 229, 78, 68, 180, 68, 82, 53, 50, 185, 243, 69, 176, 166, 58, 67, 58, 182, 5, 78, 209, 199, 122, 175, 150, 120, 108, 49, 139, 186, 193, 253, 202, 50, 61, 136, 143, 56, 128, 244, 99, 43, 64, 51, 202, 101, 197, 152, 17, 174, 68, 65, 200, 177, 2, 33, 0, 242, 159, 226, 154, 126, 62, 187, 238, 182, 10, 182, 107, 44, 160, 149, 230, 66, 132, 26, 245, 12, 46, 23, 158, 123, 159, 4, 219, 90, 179, 12, 175, 2, 33, 0, 204, 209, 149, 42, 3, 111, 206, 232, 100, 138, 58, 20, 92, 128, 235, 184, 148, 4, 24, 81, 175, 101, 59, 34, 212, 61, 145, 122, 21, 226, 79, 221, 2, 33, 0, 184, 28, 228, 20, 96, 160, 178, 116, 30, 232, 90, 45, 82, 28, 58, 36, 21, 109, 178, 23, 42, 58, 255, 233, 95, 221, 248, 13, 140, 242, 58, 57, 2, 32, 80, 249, 147, 21, 119, 34, 95, 20, 245, 154, 200, 119, 128, 82, 223, 222, 171, 118, 42, 255, 48, 110, 191, 64, 119, 212, 151, 141, 66, 213, 164, 105, 2, 32, 31, 160, 71, 49, 215, 210, 89, 9, 151, 212, 173, 178, 23, 127, 236, 102, 237, 235, 159, 208, 98, 175, 175, 251, 27, 192, 121, 61, 2, 163, 34, 32, ], }, ]; c.bench_function("pem::encode_many", move |b| { b.iter(|| pem_encode_many(&pems)) }); } criterion_group!(benches, criterion_benchmark); criterion_main!(benches); pem-1.1.1/rustfmt.toml010064400007650000024000000000561266434300000130520ustar 00000000000000format_strings = false reorder_imports = true pem-1.1.1/src/errors.rs010064400007650000024000000032421355660057000131330ustar 00000000000000// Copyright 2017 Jonathan Creekmore // // Licensed under the MIT license . This file may not be // copied, modified, or distributed except according to those terms. use std::error::Error; use std::fmt; /// The `pem` error type. #[derive(Debug, Eq, PartialEq)] #[allow(missing_docs)] pub enum PemError { MismatchedTags(String, String), MalformedFraming, MissingBeginTag, MissingEndTag, MissingData, InvalidData(::base64::DecodeError), NotUtf8(::std::str::Utf8Error), } impl fmt::Display for PemError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { PemError::MismatchedTags(b, e) => { write!(f, "mismatching BEGIN (\"{}\") and END (\"{}\") tags", b, e) } PemError::MalformedFraming => write!(f, "malformedframing"), PemError::MissingBeginTag => write!(f, "missing BEGIN tag"), PemError::MissingEndTag => write!(f, "missing END tag"), PemError::MissingData => write!(f, "missing data"), PemError::InvalidData(e) => write!(f, "invalid data: {}", e), PemError::NotUtf8(e) => write!(f, "invalid utf-8 value: {}", e), } } } impl Error for PemError { fn source(&self) -> Option<&(dyn Error + 'static)> { match self { // Errors originating from other libraries. PemError::InvalidData(e) => Some(e), PemError::NotUtf8(e) => Some(e), // Errors directly originating from `pem-rs`. _ => None, } } } /// The `pem` result type. pub type Result = ::std::result::Result; pem-1.1.1/src/lib.rs010064400007650000024000000661421435753425300124010ustar 00000000000000// Copyright 2016-2017 Jonathan Creekmore // // Licensed under the MIT license . This file may not be // copied, modified, or distributed except according to those terms. //! This crate provides a parser and encoder for PEM-encoded binary data. //! PEM-encoded binary data is essentially a beginning and matching end //! tag that encloses base64-encoded binary data (see: //! https://en.wikipedia.org/wiki/Privacy-enhanced_Electronic_Mail). //! //! This crate's documentation provides a few simple examples along with //! documentation on the public methods for the crate. //! //! # Usage //! //! This crate is [on crates.io](https://crates.io/crates/pem) and can be used //! by adding `pem` to your dependencies in your project's `Cargo.toml`. //! //! ```toml //! [dependencies] //! pem = "1" //! ``` //! //! and this to your crate root: //! //! ```rust //! extern crate pem; //! ``` //! //! Using the `serde` feature will implement the serde traits for //! the `Pem` struct. //! //! # Example: parse a single chunk of PEM-encoded text //! //! Generally, PEM-encoded files contain a single chunk of PEM-encoded //! text. Commonly, this is in some sort of a key file or an x.509 //! certificate. //! //! ```rust //! //! use pem::parse; //! //! const SAMPLE: &'static str = "-----BEGIN RSA PRIVATE KEY----- //! MIIBPQIBAAJBAOsfi5AGYhdRs/x6q5H7kScxA0Kzzqe6WI6gf6+tc6IvKQJo5rQc //! dWWSQ0nRGt2hOPDO+35NKhQEjBQxPh/v7n0CAwEAAQJBAOGaBAyuw0ICyENy5NsO //! 2gkT00AWTSzM9Zns0HedY31yEabkuFvrMCHjscEF7u3Y6PB7An3IzooBHchsFDei //! AAECIQD/JahddzR5K3A6rzTidmAf1PBtqi7296EnWv8WvpfAAQIhAOvowIXZI4Un //! DXjgZ9ekuUjZN+GUQRAVlkEEohGLVy59AiEA90VtqDdQuWWpvJX0cM08V10tLXrT //! TTGsEtITid1ogAECIQDAaFl90ZgS5cMrL3wCeatVKzVUmuJmB/VAmlLFFGzK0QIh //! ANJGc7AFk4fyFD/OezhwGHbWmo/S+bfeAiIh2Ss2FxKJ //! -----END RSA PRIVATE KEY----- //! "; //! //! let pem = parse(SAMPLE).unwrap(); //! assert_eq!(pem.tag, "RSA PRIVATE KEY"); //! ``` //! //! # Example: parse a set of PEM-encoded test //! //! Sometimes, PEM-encoded files contain multiple chunks of PEM-encoded //! text. You might see this if you have an x.509 certificate file that //! also includes intermediate certificates. //! //! ```rust //! //! use pem::parse_many; //! //! const SAMPLE: &'static str = "-----BEGIN INTERMEDIATE CERT----- //! MIIBPQIBAAJBAOsfi5AGYhdRs/x6q5H7kScxA0Kzzqe6WI6gf6+tc6IvKQJo5rQc //! dWWSQ0nRGt2hOPDO+35NKhQEjBQxPh/v7n0CAwEAAQJBAOGaBAyuw0ICyENy5NsO //! 2gkT00AWTSzM9Zns0HedY31yEabkuFvrMCHjscEF7u3Y6PB7An3IzooBHchsFDei //! AAECIQD/JahddzR5K3A6rzTidmAf1PBtqi7296EnWv8WvpfAAQIhAOvowIXZI4Un //! DXjgZ9ekuUjZN+GUQRAVlkEEohGLVy59AiEA90VtqDdQuWWpvJX0cM08V10tLXrT //! TTGsEtITid1ogAECIQDAaFl90ZgS5cMrL3wCeatVKzVUmuJmB/VAmlLFFGzK0QIh //! ANJGc7AFk4fyFD/OezhwGHbWmo/S+bfeAiIh2Ss2FxKJ //! -----END INTERMEDIATE CERT----- //! //! -----BEGIN CERTIFICATE----- //! MIIBPQIBAAJBAOsfi5AGYhdRs/x6q5H7kScxA0Kzzqe6WI6gf6+tc6IvKQJo5rQc //! dWWSQ0nRGt2hOPDO+35NKhQEjBQxPh/v7n0CAwEAAQJBAOGaBAyuw0ICyENy5NsO //! 2gkT00AWTSzM9Zns0HedY31yEabkuFvrMCHjscEF7u3Y6PB7An3IzooBHchsFDei //! AAECIQD/JahddzR5K3A6rzTidmAf1PBtqi7296EnWv8WvpfAAQIhAOvowIXZI4Un //! DXjgZ9ekuUjZN+GUQRAVlkEEohGLVy59AiEA90VtqDdQuWWpvJX0cM08V10tLXrT //! TTGsEtITid1ogAECIQDAaFl90ZgS5cMrL3wCeatVKzVUmuJmB/VAmlLFFGzK0QIh //! ANJGc7AFk4fyFD/OezhwGHbWmo/S+bfeAiIh2Ss2FxKJ //! -----END CERTIFICATE----- //! "; //! //! let pems = parse_many(SAMPLE).unwrap(); //! assert_eq!(pems.len(), 2); //! assert_eq!(pems[0].tag, "INTERMEDIATE CERT"); //! assert_eq!(pems[1].tag, "CERTIFICATE"); //! ``` #![recursion_limit = "1024"] #![deny( missing_docs, missing_debug_implementations, missing_copy_implementations, trivial_casts, trivial_numeric_casts, unsafe_code, unstable_features, unused_import_braces, unused_qualifications )] mod errors; mod parser; use parser::{parse_captures, parse_captures_iter, Captures}; pub use crate::errors::{PemError, Result}; use std::str; /// The line length for PEM encoding const LINE_WRAP: usize = 64; /// Enum describing line endings #[derive(Debug, Clone, Copy)] pub enum LineEnding { /// Windows-like (`\r\n`) CRLF, /// Unix-like (`\n`) LF, } /// Configuration for Pem encoding #[derive(Debug, Clone, Copy)] pub struct EncodeConfig { /// Line ending to use during encoding pub line_ending: LineEnding, } /// A representation of Pem-encoded data #[derive(PartialEq, Debug, Clone)] pub struct Pem { /// The tag extracted from the Pem-encoded data pub tag: String, /// The binary contents of the Pem-encoded data pub contents: Vec, } fn decode_data(raw_data: &str) -> Result> { // We need to get rid of newlines for base64::decode // As base64 requires an AsRef<[u8]>, this must involve a copy let data: String = raw_data.lines().map(str::trim_end).collect(); // And decode it from Base64 into a vector of u8 let contents = base64::decode_config(&data, base64::STANDARD).map_err(PemError::InvalidData)?; Ok(contents) } impl Pem { fn new_from_captures(caps: Captures) -> Result { fn as_utf8<'a>(bytes: &'a [u8]) -> Result<&'a str> { Ok(str::from_utf8(bytes).map_err(PemError::NotUtf8)?) } // Verify that the begin section exists let tag = as_utf8(caps.begin)?; if tag.is_empty() { return Err(PemError::MissingBeginTag); } // as well as the end section let tag_end = as_utf8(caps.end)?; if tag_end.is_empty() { return Err(PemError::MissingEndTag); } // The beginning and the end sections must match if tag != tag_end { return Err(PemError::MismatchedTags(tag.into(), tag_end.into())); } // If they did, then we can grab the data section let raw_data = as_utf8(caps.data)?; let contents = decode_data(raw_data)?; Ok(Pem { tag: tag.to_owned(), contents, }) } } /// Parses a single PEM-encoded data from a data-type that can be dereferenced as a [u8]. /// /// # Example: parse PEM-encoded data from a Vec /// ```rust /// /// use pem::parse; /// /// const SAMPLE: &'static str = "-----BEGIN RSA PRIVATE KEY----- /// MIIBPQIBAAJBAOsfi5AGYhdRs/x6q5H7kScxA0Kzzqe6WI6gf6+tc6IvKQJo5rQc /// dWWSQ0nRGt2hOPDO+35NKhQEjBQxPh/v7n0CAwEAAQJBAOGaBAyuw0ICyENy5NsO /// 2gkT00AWTSzM9Zns0HedY31yEabkuFvrMCHjscEF7u3Y6PB7An3IzooBHchsFDei /// AAECIQD/JahddzR5K3A6rzTidmAf1PBtqi7296EnWv8WvpfAAQIhAOvowIXZI4Un /// DXjgZ9ekuUjZN+GUQRAVlkEEohGLVy59AiEA90VtqDdQuWWpvJX0cM08V10tLXrT /// TTGsEtITid1ogAECIQDAaFl90ZgS5cMrL3wCeatVKzVUmuJmB/VAmlLFFGzK0QIh /// ANJGc7AFk4fyFD/OezhwGHbWmo/S+bfeAiIh2Ss2FxKJ /// -----END RSA PRIVATE KEY----- /// "; /// let SAMPLE_BYTES: Vec = SAMPLE.into(); /// /// let pem = parse(SAMPLE_BYTES).unwrap(); /// assert_eq!(pem.tag, "RSA PRIVATE KEY"); /// ``` /// /// # Example: parse PEM-encoded data from a String /// ```rust /// /// use pem::parse; /// /// const SAMPLE: &'static str = "-----BEGIN RSA PRIVATE KEY----- /// MIIBPQIBAAJBAOsfi5AGYhdRs/x6q5H7kScxA0Kzzqe6WI6gf6+tc6IvKQJo5rQc /// dWWSQ0nRGt2hOPDO+35NKhQEjBQxPh/v7n0CAwEAAQJBAOGaBAyuw0ICyENy5NsO /// 2gkT00AWTSzM9Zns0HedY31yEabkuFvrMCHjscEF7u3Y6PB7An3IzooBHchsFDei /// AAECIQD/JahddzR5K3A6rzTidmAf1PBtqi7296EnWv8WvpfAAQIhAOvowIXZI4Un /// DXjgZ9ekuUjZN+GUQRAVlkEEohGLVy59AiEA90VtqDdQuWWpvJX0cM08V10tLXrT /// TTGsEtITid1ogAECIQDAaFl90ZgS5cMrL3wCeatVKzVUmuJmB/VAmlLFFGzK0QIh /// ANJGc7AFk4fyFD/OezhwGHbWmo/S+bfeAiIh2Ss2FxKJ /// -----END RSA PRIVATE KEY----- /// "; /// let SAMPLE_STRING: String = SAMPLE.into(); /// /// let pem = parse(SAMPLE_STRING).unwrap(); /// assert_eq!(pem.tag, "RSA PRIVATE KEY"); /// ``` pub fn parse>(input: B) -> Result { parse_captures(&input.as_ref()) .ok_or_else(|| PemError::MalformedFraming) .and_then(Pem::new_from_captures) } /// Parses a set of PEM-encoded data from a data-type that can be dereferenced as a [u8]. /// /// # Example: parse a set of PEM-encoded data from a Vec /// /// ```rust /// /// use pem::parse_many; /// /// const SAMPLE: &'static str = "-----BEGIN INTERMEDIATE CERT----- /// MIIBPQIBAAJBAOsfi5AGYhdRs/x6q5H7kScxA0Kzzqe6WI6gf6+tc6IvKQJo5rQc /// dWWSQ0nRGt2hOPDO+35NKhQEjBQxPh/v7n0CAwEAAQJBAOGaBAyuw0ICyENy5NsO /// 2gkT00AWTSzM9Zns0HedY31yEabkuFvrMCHjscEF7u3Y6PB7An3IzooBHchsFDei /// AAECIQD/JahddzR5K3A6rzTidmAf1PBtqi7296EnWv8WvpfAAQIhAOvowIXZI4Un /// DXjgZ9ekuUjZN+GUQRAVlkEEohGLVy59AiEA90VtqDdQuWWpvJX0cM08V10tLXrT /// TTGsEtITid1ogAECIQDAaFl90ZgS5cMrL3wCeatVKzVUmuJmB/VAmlLFFGzK0QIh /// ANJGc7AFk4fyFD/OezhwGHbWmo/S+bfeAiIh2Ss2FxKJ /// -----END INTERMEDIATE CERT----- /// /// -----BEGIN CERTIFICATE----- /// MIIBPQIBAAJBAOsfi5AGYhdRs/x6q5H7kScxA0Kzzqe6WI6gf6+tc6IvKQJo5rQc /// dWWSQ0nRGt2hOPDO+35NKhQEjBQxPh/v7n0CAwEAAQJBAOGaBAyuw0ICyENy5NsO /// 2gkT00AWTSzM9Zns0HedY31yEabkuFvrMCHjscEF7u3Y6PB7An3IzooBHchsFDei /// AAECIQD/JahddzR5K3A6rzTidmAf1PBtqi7296EnWv8WvpfAAQIhAOvowIXZI4Un /// DXjgZ9ekuUjZN+GUQRAVlkEEohGLVy59AiEA90VtqDdQuWWpvJX0cM08V10tLXrT /// TTGsEtITid1ogAECIQDAaFl90ZgS5cMrL3wCeatVKzVUmuJmB/VAmlLFFGzK0QIh /// ANJGc7AFk4fyFD/OezhwGHbWmo/S+bfeAiIh2Ss2FxKJ /// -----END CERTIFICATE----- /// "; /// let SAMPLE_BYTES: Vec = SAMPLE.into(); /// /// let pems = parse_many(SAMPLE_BYTES).unwrap(); /// assert_eq!(pems.len(), 2); /// assert_eq!(pems[0].tag, "INTERMEDIATE CERT"); /// assert_eq!(pems[1].tag, "CERTIFICATE"); /// ``` /// /// # Example: parse a set of PEM-encoded data from a String /// /// ```rust /// /// use pem::parse_many; /// /// const SAMPLE: &'static str = "-----BEGIN INTERMEDIATE CERT----- /// MIIBPQIBAAJBAOsfi5AGYhdRs/x6q5H7kScxA0Kzzqe6WI6gf6+tc6IvKQJo5rQc /// dWWSQ0nRGt2hOPDO+35NKhQEjBQxPh/v7n0CAwEAAQJBAOGaBAyuw0ICyENy5NsO /// 2gkT00AWTSzM9Zns0HedY31yEabkuFvrMCHjscEF7u3Y6PB7An3IzooBHchsFDei /// AAECIQD/JahddzR5K3A6rzTidmAf1PBtqi7296EnWv8WvpfAAQIhAOvowIXZI4Un /// DXjgZ9ekuUjZN+GUQRAVlkEEohGLVy59AiEA90VtqDdQuWWpvJX0cM08V10tLXrT /// TTGsEtITid1ogAECIQDAaFl90ZgS5cMrL3wCeatVKzVUmuJmB/VAmlLFFGzK0QIh /// ANJGc7AFk4fyFD/OezhwGHbWmo/S+bfeAiIh2Ss2FxKJ /// -----END INTERMEDIATE CERT----- /// /// -----BEGIN CERTIFICATE----- /// MIIBPQIBAAJBAOsfi5AGYhdRs/x6q5H7kScxA0Kzzqe6WI6gf6+tc6IvKQJo5rQc /// dWWSQ0nRGt2hOPDO+35NKhQEjBQxPh/v7n0CAwEAAQJBAOGaBAyuw0ICyENy5NsO /// 2gkT00AWTSzM9Zns0HedY31yEabkuFvrMCHjscEF7u3Y6PB7An3IzooBHchsFDei /// AAECIQD/JahddzR5K3A6rzTidmAf1PBtqi7296EnWv8WvpfAAQIhAOvowIXZI4Un /// DXjgZ9ekuUjZN+GUQRAVlkEEohGLVy59AiEA90VtqDdQuWWpvJX0cM08V10tLXrT /// TTGsEtITid1ogAECIQDAaFl90ZgS5cMrL3wCeatVKzVUmuJmB/VAmlLFFGzK0QIh /// ANJGc7AFk4fyFD/OezhwGHbWmo/S+bfeAiIh2Ss2FxKJ /// -----END CERTIFICATE----- /// "; /// let SAMPLE_STRING: Vec = SAMPLE.into(); /// /// let pems = parse_many(SAMPLE_STRING).unwrap(); /// assert_eq!(pems.len(), 2); /// assert_eq!(pems[0].tag, "INTERMEDIATE CERT"); /// assert_eq!(pems[1].tag, "CERTIFICATE"); /// ``` pub fn parse_many>(input: B) -> Result> { // Each time our regex matches a PEM section, we need to decode it. parse_captures_iter(&input.as_ref()) .map(|caps| Pem::new_from_captures(caps)) .collect() } /// Encode a PEM struct into a PEM-encoded data string /// /// # Example /// ```rust /// use pem::{Pem, encode}; /// /// let pem = Pem { /// tag: String::from("FOO"), /// contents: vec![1, 2, 3, 4], /// }; /// encode(&pem); /// ``` pub fn encode(pem: &Pem) -> String { encode_config( pem, EncodeConfig { line_ending: LineEnding::CRLF, }, ) } /// Encode a PEM struct into a PEM-encoded data string with additional /// configuration options /// /// # Example /// ```rust /// use pem::{Pem, encode_config, EncodeConfig, LineEnding}; /// /// let pem = Pem { /// tag: String::from("FOO"), /// contents: vec![1, 2, 3, 4], /// }; /// encode_config(&pem, EncodeConfig { line_ending: LineEnding::LF }); /// ``` pub fn encode_config(pem: &Pem, config: EncodeConfig) -> String { let line_ending = match config.line_ending { LineEnding::CRLF => "\r\n", LineEnding::LF => "\n", }; let mut output = String::new(); let contents = if pem.contents.is_empty() { String::from("") } else { base64::encode_config( &pem.contents, base64::Config::new(base64::CharacterSet::Standard, true), ) }; output.push_str(&format!("-----BEGIN {}-----{}", pem.tag, line_ending)); for c in contents.as_bytes().chunks(LINE_WRAP) { output.push_str(&format!("{}{}", str::from_utf8(c).unwrap(), line_ending)); } output.push_str(&format!("-----END {}-----{}", pem.tag, line_ending)); output } /// Encode multiple PEM structs into a PEM-encoded data string /// /// # Example /// ```rust /// use pem::{Pem, encode_many}; /// /// let data = vec![ /// Pem { /// tag: String::from("FOO"), /// contents: vec![1, 2, 3, 4], /// }, /// Pem { /// tag: String::from("BAR"), /// contents: vec![5, 6, 7, 8], /// }, /// ]; /// encode_many(&data); /// ``` pub fn encode_many(pems: &[Pem]) -> String { pems.iter() .map(encode) .collect::>() .join("\r\n") } /// Encode multiple PEM structs into a PEM-encoded data string with additional /// configuration options /// /// Same config will be used for each PEM struct. /// /// # Example /// ```rust /// use pem::{Pem, encode_many_config, EncodeConfig, LineEnding}; /// /// let data = vec![ /// Pem { /// tag: String::from("FOO"), /// contents: vec![1, 2, 3, 4], /// }, /// Pem { /// tag: String::from("BAR"), /// contents: vec![5, 6, 7, 8], /// }, /// ]; /// encode_many_config(&data, EncodeConfig { line_ending: LineEnding::LF }); /// ``` pub fn encode_many_config(pems: &[Pem], config: EncodeConfig) -> String { let line_ending = match config.line_ending { LineEnding::CRLF => "\r\n", LineEnding::LF => "\n", }; pems.iter() .map(|value| encode_config(value, config)) .collect::>() .join(line_ending) } #[cfg(feature = "serde")] mod serde_impl { use super::{encode, parse, Pem}; use serde::{ de::{Error, Visitor}, Deserialize, Serialize, }; use std::fmt; impl Serialize for Pem { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { serializer.serialize_str(&encode(self)) } } struct PemVisitor; impl<'de> Visitor<'de> for PemVisitor { type Value = Pem; fn expecting(&self, _formatter: &mut fmt::Formatter) -> fmt::Result { Ok(()) } fn visit_str(self, v: &str) -> Result where E: Error, { parse(v).map_err(Error::custom) } } impl<'de> Deserialize<'de> for Pem { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { deserializer.deserialize_str(PemVisitor) } } } #[cfg(test)] mod test { use super::*; use std::error::Error; const SAMPLE_CRLF: &'static str = "-----BEGIN RSA PRIVATE KEY-----\r MIIBPQIBAAJBAOsfi5AGYhdRs/x6q5H7kScxA0Kzzqe6WI6gf6+tc6IvKQJo5rQc\r dWWSQ0nRGt2hOPDO+35NKhQEjBQxPh/v7n0CAwEAAQJBAOGaBAyuw0ICyENy5NsO\r 2gkT00AWTSzM9Zns0HedY31yEabkuFvrMCHjscEF7u3Y6PB7An3IzooBHchsFDei\r AAECIQD/JahddzR5K3A6rzTidmAf1PBtqi7296EnWv8WvpfAAQIhAOvowIXZI4Un\r DXjgZ9ekuUjZN+GUQRAVlkEEohGLVy59AiEA90VtqDdQuWWpvJX0cM08V10tLXrT\r TTGsEtITid1ogAECIQDAaFl90ZgS5cMrL3wCeatVKzVUmuJmB/VAmlLFFGzK0QIh\r ANJGc7AFk4fyFD/OezhwGHbWmo/S+bfeAiIh2Ss2FxKJ\r -----END RSA PRIVATE KEY-----\r \r -----BEGIN RSA PUBLIC KEY-----\r MIIBOgIBAAJBAMIeCnn9G/7g2Z6J+qHOE2XCLLuPoh5NHTO2Fm+PbzBvafBo0oYo\r QVVy7frzxmOqx6iIZBxTyfAQqBPO3Br59BMCAwEAAQJAX+PjHPuxdqiwF6blTkS0\r RFI1MrnzRbCmOkM6tgVO0cd6r5Z4bDGLusH9yjI9iI84gPRjK0AzymXFmBGuREHI\r sQIhAPKf4pp+Prvutgq2ayygleZChBr1DC4XnnufBNtaswyvAiEAzNGVKgNvzuhk\r ijoUXIDruJQEGFGvZTsi1D2RehXiT90CIQC4HOQUYKCydB7oWi1SHDokFW2yFyo6\r /+lf3fgNjPI6OQIgUPmTFXciXxT1msh3gFLf3qt2Kv8wbr9Ad9SXjULVpGkCIB+g\r RzHX0lkJl9Stshd/7Gbt65/QYq+v+xvAeT0CoyIg\r -----END RSA PUBLIC KEY-----\r "; const SAMPLE_LF: &'static str = "-----BEGIN RSA PRIVATE KEY----- MIIBPQIBAAJBAOsfi5AGYhdRs/x6q5H7kScxA0Kzzqe6WI6gf6+tc6IvKQJo5rQc dWWSQ0nRGt2hOPDO+35NKhQEjBQxPh/v7n0CAwEAAQJBAOGaBAyuw0ICyENy5NsO 2gkT00AWTSzM9Zns0HedY31yEabkuFvrMCHjscEF7u3Y6PB7An3IzooBHchsFDei AAECIQD/JahddzR5K3A6rzTidmAf1PBtqi7296EnWv8WvpfAAQIhAOvowIXZI4Un DXjgZ9ekuUjZN+GUQRAVlkEEohGLVy59AiEA90VtqDdQuWWpvJX0cM08V10tLXrT TTGsEtITid1ogAECIQDAaFl90ZgS5cMrL3wCeatVKzVUmuJmB/VAmlLFFGzK0QIh ANJGc7AFk4fyFD/OezhwGHbWmo/S+bfeAiIh2Ss2FxKJ -----END RSA PRIVATE KEY----- -----BEGIN RSA PUBLIC KEY----- MIIBOgIBAAJBAMIeCnn9G/7g2Z6J+qHOE2XCLLuPoh5NHTO2Fm+PbzBvafBo0oYo QVVy7frzxmOqx6iIZBxTyfAQqBPO3Br59BMCAwEAAQJAX+PjHPuxdqiwF6blTkS0 RFI1MrnzRbCmOkM6tgVO0cd6r5Z4bDGLusH9yjI9iI84gPRjK0AzymXFmBGuREHI sQIhAPKf4pp+Prvutgq2ayygleZChBr1DC4XnnufBNtaswyvAiEAzNGVKgNvzuhk ijoUXIDruJQEGFGvZTsi1D2RehXiT90CIQC4HOQUYKCydB7oWi1SHDokFW2yFyo6 /+lf3fgNjPI6OQIgUPmTFXciXxT1msh3gFLf3qt2Kv8wbr9Ad9SXjULVpGkCIB+g RzHX0lkJl9Stshd/7Gbt65/QYq+v+xvAeT0CoyIg -----END RSA PUBLIC KEY----- "; #[test] fn test_parse_works() { let pem = parse(SAMPLE_CRLF).unwrap(); assert_eq!(pem.tag, "RSA PRIVATE KEY"); } #[test] fn test_parse_invalid_framing() { let input = "--BEGIN data----- -----END data-----"; assert_eq!(parse(&input), Err(PemError::MalformedFraming)); } #[test] fn test_parse_invalid_begin() { let input = "-----BEGIN ----- MIIBOgIBAAJBAMIeCnn9G/7g2Z6J+qHOE2XCLLuPoh5NHTO2Fm+PbzBvafBo0oYo QVVy7frzxmOqx6iIZBxTyfAQqBPO3Br59BMCAwEAAQJAX+PjHPuxdqiwF6blTkS0 RFI1MrnzRbCmOkM6tgVO0cd6r5Z4bDGLusH9yjI9iI84gPRjK0AzymXFmBGuREHI sQIhAPKf4pp+Prvutgq2ayygleZChBr1DC4XnnufBNtaswyvAiEAzNGVKgNvzuhk ijoUXIDruJQEGFGvZTsi1D2RehXiT90CIQC4HOQUYKCydB7oWi1SHDokFW2yFyo6 /+lf3fgNjPI6OQIgUPmTFXciXxT1msh3gFLf3qt2Kv8wbr9Ad9SXjULVpGkCIB+g RzHX0lkJl9Stshd/7Gbt65/QYq+v+xvAeT0CoyIg -----END RSA PUBLIC KEY-----"; assert_eq!(parse(&input), Err(PemError::MissingBeginTag)); } #[test] fn test_parse_invalid_end() { let input = "-----BEGIN DATA----- MIIBOgIBAAJBAMIeCnn9G/7g2Z6J+qHOE2XCLLuPoh5NHTO2Fm+PbzBvafBo0oYo QVVy7frzxmOqx6iIZBxTyfAQqBPO3Br59BMCAwEAAQJAX+PjHPuxdqiwF6blTkS0 RFI1MrnzRbCmOkM6tgVO0cd6r5Z4bDGLusH9yjI9iI84gPRjK0AzymXFmBGuREHI sQIhAPKf4pp+Prvutgq2ayygleZChBr1DC4XnnufBNtaswyvAiEAzNGVKgNvzuhk ijoUXIDruJQEGFGvZTsi1D2RehXiT90CIQC4HOQUYKCydB7oWi1SHDokFW2yFyo6 /+lf3fgNjPI6OQIgUPmTFXciXxT1msh3gFLf3qt2Kv8wbr9Ad9SXjULVpGkCIB+g RzHX0lkJl9Stshd/7Gbt65/QYq+v+xvAeT0CoyIg -----END -----"; assert_eq!(parse(&input), Err(PemError::MissingEndTag)); } #[test] fn test_parse_invalid_data() { let input = "-----BEGIN DATA----- MIIBOgIBAAJBAMIeCnn9G/7g2Z6J+qHOE2XCLLuPoh5NHTO2Fm+PbzBvafBo0oY? QVVy7frzxmOqx6iIZBxTyfAQqBPO3Br59BMCAwEAAQJAX+PjHPuxdqiwF6blTkS0 RFI1MrnzRbCmOkM6tgVO0cd6r5Z4bDGLusH9yjI9iI84gPRjK0AzymXFmBGuREHI sQIhAPKf4pp+Prvutgq2ayygleZChBr1DC4XnnufBNtaswyvAiEAzNGVKgNvzuhk ijoUXIDruJQEGFGvZTsi1D2RehXiT90CIQC4HOQUYKCydB7oWi1SHDokFW2yFyo6 /+lf3fgNjPI6OQIgUPmTFXciXxT1msh3gFLf3qt2Kv8wbr9Ad9SXjULVpGkCIB+g RzHX0lkJl9Stshd/7Gbt65/QYq+v+xvAeT0CoyIg -----END DATA-----"; match parse(&input) { Err(e @ PemError::InvalidData(_)) => { assert_eq!( &format!("{}", e.source().unwrap()), "Invalid byte 63, offset 63." ); } _ => assert!(false), } } #[test] fn test_parse_empty_data() { let input = "-----BEGIN DATA----- -----END DATA-----"; let pem = parse(&input).unwrap(); assert_eq!(pem.contents.len(), 0); } #[test] fn test_parse_many_works() { let pems = parse_many(SAMPLE_CRLF).unwrap(); assert_eq!(pems.len(), 2); assert_eq!(pems[0].tag, "RSA PRIVATE KEY"); assert_eq!(pems[1].tag, "RSA PUBLIC KEY"); } #[test] fn test_parse_many_errors_on_invalid_section() { let input = SAMPLE_LF.to_owned() + "-----BEGIN -----\n-----END -----"; assert_eq!(parse_many(&input), Err(PemError::MissingBeginTag)); } #[test] fn test_encode_empty_contents() { let pem = Pem { tag: String::from("FOO"), contents: vec![], }; let encoded = encode(&pem); assert!(encoded != ""); let pem_out = parse(&encoded).unwrap(); assert_eq!(&pem, &pem_out); } #[test] fn test_encode_contents() { let pem = Pem { tag: String::from("FOO"), contents: vec![1, 2, 3, 4], }; let encoded = encode(&pem); assert!(encoded != ""); let pem_out = parse(&encoded).unwrap(); assert_eq!(&pem, &pem_out); } #[test] fn test_encode_many() { let pems = parse_many(SAMPLE_CRLF).unwrap(); let encoded = encode_many(&pems); assert_eq!(SAMPLE_CRLF, encoded); } #[test] fn test_encode_config_contents() { let pem = Pem { tag: String::from("FOO"), contents: vec![1, 2, 3, 4], }; let config = EncodeConfig { line_ending: LineEnding::LF, }; let encoded = encode_config(&pem, config); assert!(encoded != ""); let pem_out = parse(&encoded).unwrap(); assert_eq!(&pem, &pem_out); } #[test] fn test_encode_many_config() { let pems = parse_many(SAMPLE_LF).unwrap(); let config = EncodeConfig { line_ending: LineEnding::LF, }; let encoded = encode_many_config(&pems, config); assert_eq!(SAMPLE_LF, encoded); } #[cfg(feature = "serde")] #[test] fn test_serde() { let pem = Pem { tag: String::from("Mock tag"), contents: "Mock contents".as_bytes().to_vec(), }; let value = serde_json::to_string_pretty(&pem).unwrap(); let result = serde_json::from_str(&value).unwrap(); assert_eq!(pem, result); } const HEADER_CRLF: &'static str = "-----BEGIN CERTIFICATE-----\r MIIBPQIBAAJBAOsfi5AGYhdRs/x6q5H7kScxA0Kzzqe6WI6gf6+tc6IvKQJo5rQc\r dWWSQ0nRGt2hOPDO+35NKhQEjBQxPh/v7n0CAwEAAQJBAOGaBAyuw0ICyENy5NsO\r 2gkT00AWTSzM9Zns0HedY31yEabkuFvrMCHjscEF7u3Y6PB7An3IzooBHchsFDei\r AAECIQD/JahddzR5K3A6rzTidmAf1PBtqi7296EnWv8WvpfAAQIhAOvowIXZI4Un\r DXjgZ9ekuUjZN+GUQRAVlkEEohGLVy59AiEA90VtqDdQuWWpvJX0cM08V10tLXrT\r TTGsEtITid1ogAECIQDAaFl90ZgS5cMrL3wCeatVKzVUmuJmB/VAmlLFFGzK0QIh\r ANJGc7AFk4fyFD/OezhwGHbWmo/S+bfeAiIh2Ss2FxKJ\r -----END CERTIFICATE-----\r -----BEGIN RSA PRIVATE KEY-----\r Proc-Type: 4,ENCRYPTED\r DEK-Info: AES-256-CBC,975C518B7D2CCD1164A3354D1F89C5A6\r \r MIIBOgIBAAJBAMIeCnn9G/7g2Z6J+qHOE2XCLLuPoh5NHTO2Fm+PbzBvafBo0oYo\r QVVy7frzxmOqx6iIZBxTyfAQqBPO3Br59BMCAwEAAQJAX+PjHPuxdqiwF6blTkS0\r RFI1MrnzRbCmOkM6tgVO0cd6r5Z4bDGLusH9yjI9iI84gPRjK0AzymXFmBGuREHI\r sQIhAPKf4pp+Prvutgq2ayygleZChBr1DC4XnnufBNtaswyvAiEAzNGVKgNvzuhk\r ijoUXIDruJQEGFGvZTsi1D2RehXiT90CIQC4HOQUYKCydB7oWi1SHDokFW2yFyo6\r /+lf3fgNjPI6OQIgUPmTFXciXxT1msh3gFLf3qt2Kv8wbr9Ad9SXjULVpGkCIB+g\r RzHX0lkJl9Stshd/7Gbt65/QYq+v+xvAeT0CoyIg\r -----END RSA PRIVATE KEY-----\r "; const HEADER_CRLF_DATA: [&'static str; 2] = [ "MIIBPQIBAAJBAOsfi5AGYhdRs/x6q5H7kScxA0Kzzqe6WI6gf6+tc6IvKQJo5rQc\r dWWSQ0nRGt2hOPDO+35NKhQEjBQxPh/v7n0CAwEAAQJBAOGaBAyuw0ICyENy5NsO\r 2gkT00AWTSzM9Zns0HedY31yEabkuFvrMCHjscEF7u3Y6PB7An3IzooBHchsFDei\r AAECIQD/JahddzR5K3A6rzTidmAf1PBtqi7296EnWv8WvpfAAQIhAOvowIXZI4Un\r DXjgZ9ekuUjZN+GUQRAVlkEEohGLVy59AiEA90VtqDdQuWWpvJX0cM08V10tLXrT\r TTGsEtITid1ogAECIQDAaFl90ZgS5cMrL3wCeatVKzVUmuJmB/VAmlLFFGzK0QIh\r ANJGc7AFk4fyFD/OezhwGHbWmo/S+bfeAiIh2Ss2FxKJ\r", "MIIBOgIBAAJBAMIeCnn9G/7g2Z6J+qHOE2XCLLuPoh5NHTO2Fm+PbzBvafBo0oYo\r QVVy7frzxmOqx6iIZBxTyfAQqBPO3Br59BMCAwEAAQJAX+PjHPuxdqiwF6blTkS0\r RFI1MrnzRbCmOkM6tgVO0cd6r5Z4bDGLusH9yjI9iI84gPRjK0AzymXFmBGuREHI\r sQIhAPKf4pp+Prvutgq2ayygleZChBr1DC4XnnufBNtaswyvAiEAzNGVKgNvzuhk\r ijoUXIDruJQEGFGvZTsi1D2RehXiT90CIQC4HOQUYKCydB7oWi1SHDokFW2yFyo6\r /+lf3fgNjPI6OQIgUPmTFXciXxT1msh3gFLf3qt2Kv8wbr9Ad9SXjULVpGkCIB+g\r RzHX0lkJl9Stshd/7Gbt65/QYq+v+xvAeT0CoyIg\r", ]; const HEADER_LF: &'static str = "-----BEGIN CERTIFICATE----- MIIBPQIBAAJBAOsfi5AGYhdRs/x6q5H7kScxA0Kzzqe6WI6gf6+tc6IvKQJo5rQc dWWSQ0nRGt2hOPDO+35NKhQEjBQxPh/v7n0CAwEAAQJBAOGaBAyuw0ICyENy5NsO 2gkT00AWTSzM9Zns0HedY31yEabkuFvrMCHjscEF7u3Y6PB7An3IzooBHchsFDei AAECIQD/JahddzR5K3A6rzTidmAf1PBtqi7296EnWv8WvpfAAQIhAOvowIXZI4Un DXjgZ9ekuUjZN+GUQRAVlkEEohGLVy59AiEA90VtqDdQuWWpvJX0cM08V10tLXrT TTGsEtITid1ogAECIQDAaFl90ZgS5cMrL3wCeatVKzVUmuJmB/VAmlLFFGzK0QIh ANJGc7AFk4fyFD/OezhwGHbWmo/S+bfeAiIh2Ss2FxKJ -----END CERTIFICATE----- -----BEGIN RSA PRIVATE KEY----- Proc-Type: 4,ENCRYPTED DEK-Info: AES-256-CBC,975C518B7D2CCD1164A3354D1F89C5A6 MIIBOgIBAAJBAMIeCnn9G/7g2Z6J+qHOE2XCLLuPoh5NHTO2Fm+PbzBvafBo0oYo QVVy7frzxmOqx6iIZBxTyfAQqBPO3Br59BMCAwEAAQJAX+PjHPuxdqiwF6blTkS0 RFI1MrnzRbCmOkM6tgVO0cd6r5Z4bDGLusH9yjI9iI84gPRjK0AzymXFmBGuREHI sQIhAPKf4pp+Prvutgq2ayygleZChBr1DC4XnnufBNtaswyvAiEAzNGVKgNvzuhk ijoUXIDruJQEGFGvZTsi1D2RehXiT90CIQC4HOQUYKCydB7oWi1SHDokFW2yFyo6 /+lf3fgNjPI6OQIgUPmTFXciXxT1msh3gFLf3qt2Kv8wbr9Ad9SXjULVpGkCIB+g RzHX0lkJl9Stshd/7Gbt65/QYq+v+xvAeT0CoyIg -----END RSA PRIVATE KEY----- "; const HEADER_LF_DATA: [&'static str; 2] = [ "MIIBPQIBAAJBAOsfi5AGYhdRs/x6q5H7kScxA0Kzzqe6WI6gf6+tc6IvKQJo5rQc dWWSQ0nRGt2hOPDO+35NKhQEjBQxPh/v7n0CAwEAAQJBAOGaBAyuw0ICyENy5NsO 2gkT00AWTSzM9Zns0HedY31yEabkuFvrMCHjscEF7u3Y6PB7An3IzooBHchsFDei AAECIQD/JahddzR5K3A6rzTidmAf1PBtqi7296EnWv8WvpfAAQIhAOvowIXZI4Un DXjgZ9ekuUjZN+GUQRAVlkEEohGLVy59AiEA90VtqDdQuWWpvJX0cM08V10tLXrT TTGsEtITid1ogAECIQDAaFl90ZgS5cMrL3wCeatVKzVUmuJmB/VAmlLFFGzK0QIh ANJGc7AFk4fyFD/OezhwGHbWmo/S+bfeAiIh2Ss2FxKJ", "MIIBOgIBAAJBAMIeCnn9G/7g2Z6J+qHOE2XCLLuPoh5NHTO2Fm+PbzBvafBo0oYo QVVy7frzxmOqx6iIZBxTyfAQqBPO3Br59BMCAwEAAQJAX+PjHPuxdqiwF6blTkS0 RFI1MrnzRbCmOkM6tgVO0cd6r5Z4bDGLusH9yjI9iI84gPRjK0AzymXFmBGuREHI sQIhAPKf4pp+Prvutgq2ayygleZChBr1DC4XnnufBNtaswyvAiEAzNGVKgNvzuhk ijoUXIDruJQEGFGvZTsi1D2RehXiT90CIQC4HOQUYKCydB7oWi1SHDokFW2yFyo6 /+lf3fgNjPI6OQIgUPmTFXciXxT1msh3gFLf3qt2Kv8wbr9Ad9SXjULVpGkCIB+g RzHX0lkJl9Stshd/7Gbt65/QYq+v+xvAeT0CoyIg", ]; fn cmp_data(left: &[u8], right: &[u8]) -> bool { if left.len() != right.len() { false } else { left.iter() .zip(right.iter()) .all(|(left, right)| left == right) } } #[test] fn test_parse_many_with_headers_crlf() { let pems = parse_many(HEADER_CRLF).unwrap(); assert_eq!(pems.len(), 2); assert_eq!(pems[0].tag, "CERTIFICATE"); assert!(cmp_data( &pems[0].contents, &decode_data(HEADER_CRLF_DATA[0]).unwrap() )); assert_eq!(pems[1].tag, "RSA PRIVATE KEY"); assert!(cmp_data( &pems[1].contents, &decode_data(HEADER_CRLF_DATA[1]).unwrap() )); } #[test] fn test_parse_many_with_headers_lf() { let pems = parse_many(HEADER_LF).unwrap(); assert_eq!(pems.len(), 2); assert_eq!(pems[0].tag, "CERTIFICATE"); assert!(cmp_data( &pems[0].contents, &decode_data(HEADER_LF_DATA[0]).unwrap() )); assert_eq!(pems[1].tag, "RSA PRIVATE KEY"); assert!(cmp_data( &pems[1].contents, &decode_data(HEADER_LF_DATA[1]).unwrap() )); } } pem-1.1.1/src/parser.rs010064400007650000024000000070101435753425300131140ustar 00000000000000pub struct Captures<'a> { pub begin: &'a [u8], pub data: &'a [u8], pub end: &'a [u8], } pub fn parse_captures<'a>(input: &'a [u8]) -> Option> { parser_inner(input).map(|(_, cap)| cap) } pub fn parse_captures_iter<'a>(input: &'a [u8]) -> CaptureMatches<'a> { CaptureMatches { input } } pub struct CaptureMatches<'a> { input: &'a [u8], } impl<'a> Iterator for CaptureMatches<'a> { type Item = Captures<'a>; fn next(&mut self) -> Option { if self.input.is_empty() { return None; } match parser_inner(self.input) { Some((remaining, captures)) => { self.input = remaining; Some(captures) } None => { self.input = &[]; None } } } } fn parse_begin<'a>(input: &'a [u8]) -> Option<(&'a [u8], &'a [u8])> { let (input, _) = read_until(input, b"-----BEGIN ")?; let (input, begin) = read_until(input, b"-----")?; let input = skip_whitespace(input); Some((input, begin)) } fn parse_payload<'a>(input: &'a [u8]) -> Option<(&'a [u8], &'a [u8])> { read_until(input, b"-----END ") } fn extract_headers_and_data<'a>(input: &'a [u8]) -> (&'a [u8], &'a [u8]) { if let Some((rest, headers)) = read_until(input, b"\n\n") { (headers, rest) } else if let Some((rest, headers)) = read_until(input, b"\r\n\r\n") { (headers, rest) } else { (&[], input) } } fn parse_end<'a>(input: &'a [u8]) -> Option<(&'a [u8], &'a [u8])> { let (remaining, end) = read_until(input, b"-----")?; let remaining = skip_whitespace(remaining); Some((remaining, end)) } fn parser_inner<'a>(input: &'a [u8]) -> Option<(&'a [u8], Captures<'a>)> { // Should be equivalent to the regex // "(?s)-----BEGIN (?P.*?)-----[ \t\n\r]*(?P.*?)-----END (?P.*?)-----[ \t\n\r]*" // (?s) # Enable dotall (. matches all characters incl \n) // -----BEGIN (?P.*?)-----[ \t\n\r]* # Parse begin // (?P.*?) # Parse data // -----END (?P.*?)-----[ \t\n\r]* # Parse end let (input, begin) = parse_begin(input)?; let (input, payload) = parse_payload(input)?; let (_headers, data) = extract_headers_and_data(payload); let (remaining, end) = parse_end(input)?; let captures = Captures { begin, data, end }; Some((remaining, captures)) } // Equivalent to the regex [ \t\n\r]* fn skip_whitespace(mut input: &[u8]) -> &[u8] { while let Some(b) = input.first() { match b { b' ' | b'\t' | b'\n' | b'\r' => { input = &input[1..]; } _ => break, } } input } // Equivalent to (.*?) followed by a string // Returns the remaining input (after the secondary matched string) and the matched data fn read_until<'a, 'b>(input: &'a [u8], marker: &'b [u8]) -> Option<(&'a [u8], &'a [u8])> { // If there is no end condition, short circuit if marker.is_empty() { return Some((&[], input)); } let mut index = 0; let mut found = 0; while input.len() - index >= marker.len() - found { if input[index] == marker[found] { found += 1; } else { found = 0; } index += 1; if found == marker.len() { let remaining = &input[index..]; let matched = &input[..index - found]; return Some((remaining, matched)); } } None }