pem-rfc7468-0.7.0/.cargo_vcs_info.json0000644000000001510000000000100127170ustar { "git": { "sha1": "66e782b01d58aeea7dd57213c2e262211e98a9c0" }, "path_in_vcs": "pem-rfc7468" }pem-rfc7468-0.7.0/CHANGELOG.md000064400000000000000000000061161046102023000133270ustar 00000000000000# Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## 0.7.0 (2023-02-26) ### Changed - MSRV 1.60 ([#802]) - Lint improvements ([#824]) [#802]: https://github.com/RustCrypto/formats/pull/802 [#824]: https://github.com/RustCrypto/formats/pull/824 ## 0.6.0 (2022-04-26) ### Added - `encapsulated_len_wrapped` ([#619]) ### Changed - `encapsulated_len` now accepts the length of the raw input bytes prior to Base64 encoding, and computes the length of the full PEM encoded document including newlines when the resulting Base64 is linewrapped ([#619]) [#619]: https://github.com/RustCrypto/formats/pull/619 ## 0.5.1 (2022-03-30) ### Changed - Rename `PemLabel::TYPE_LABEL` => `::PEM_LABEL` ([#568]) [#568]: https://github.com/RustCrypto/formats/pull/568 ## 0.5.0 (2022-03-29) [YANKED] ### Added - Clippy lints for checked arithmetic and panics ([#564]) ### Changed - Use `str::from_utf8_unchecked` in `encode` ([#565]) [#564]: https://github.com/RustCrypto/formats/pull/564 [#565]: https://github.com/RustCrypto/formats/pull/565 ## 0.4.0 (2022-03-12) ### Added - Buffered `Decoder` type ([#406]) - Buffered `Encoder` type ([#463], [#474]) ### Changed - Return `str` from `encode` ([#482]) [#406]: https://github.com/RustCrypto/formats/pull/406 [#463]: https://github.com/RustCrypto/formats/pull/463 [#474]: https://github.com/RustCrypto/formats/pull/474 [#482]: https://github.com/RustCrypto/formats/pull/482 ## 0.3.1 (2021-11-17) ### Changed - Relax `base64ct` version requirement to `^1` ([#239]) [#239]: https://github.com/RustCrypto/formats/pull/239 ## 0.3.0 (2021-11-14) ### Added - `Decoder` struct ([#177]) ### Changed - Rust 2021 edition upgrade; MSRV 1.56 ([#136]) - Bump `base64ct` dependency to v1.2 ([#175]) [#136]: https://github.com/RustCrypto/formats/pull/136 [#175]: https://github.com/RustCrypto/formats/pull/175 [#177]: https://github.com/RustCrypto/formats/pull/177 ## 0.2.4 (2021-11-07) ### Changed - Restrict `base64ct` dependency to `<1.2` to prevent MSRV breakages ## 0.2.3 (2021-10-17) ### Added - `PemLabel` trait ([#117]) [#117]: https://github.com/RustCrypto/formats/pull/117 ## 0.2.2 (2021-09-16) ### Changed - Allow for data before PEM encapsulation boundary ([#40]) [#40]: https://github.com/RustCrypto/formats/pull/40 ## 0.2.1 (2021-09-14) ### Added - `decode_label` ([#22]) - `Error::HeaderDisallowed` ([#13], [#19], [#21]) ### Changed - Moved to `formats` repo ([#2]) [#2]: https://github.com/RustCrypto/formats/pull/2 [#13]: https://github.com/RustCrypto/formats/pull/13 [#19]: https://github.com/RustCrypto/formats/pull/19 [#21]: https://github.com/RustCrypto/formats/pull/21 [#22]: https://github.com/RustCrypto/formats/pull/22 ## 0.2.0 (2021-07-26) ### Added - Support for customizing PEM line endings ## 0.1.1 (2021-07-24) ### Changed - Increase LF precedence in EOL stripping functions ### Fixed - Bug in the size calculation for `decode_vec` ## 0.1.0 (2021-07-23) - Initial release pem-rfc7468-0.7.0/Cargo.toml0000644000000027140000000000100107240ustar # 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 are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" rust-version = "1.60" name = "pem-rfc7468" version = "0.7.0" authors = ["RustCrypto Developers"] description = """ PEM Encoding (RFC 7468) for PKIX, PKCS, and CMS Structures, implementing a strict subset of the original Privacy-Enhanced Mail encoding intended specifically for use with cryptographic keys, certificates, and other messages. Provides a no_std-friendly, constant-time implementation suitable for use with cryptographic private keys. """ readme = "README.md" keywords = [ "crypto", "key", "pem", "pkcs", "rsa", ] categories = [ "cryptography", "data-structures", "encoding", "no-std", "parser-implementations", ] license = "Apache-2.0 OR MIT" repository = "https://github.com/RustCrypto/formats/tree/master/pem-rfc7468" [package.metadata.docs.rs] all-features = true rustdoc-args = [ "--cfg", "docsrs", ] [dependencies.base64ct] version = "1.4" [features] alloc = ["base64ct/alloc"] std = [ "alloc", "base64ct/std", ] pem-rfc7468-0.7.0/Cargo.toml.orig000064400000000000000000000017211046102023000144020ustar 00000000000000[package] name = "pem-rfc7468" version = "0.7.0" description = """ PEM Encoding (RFC 7468) for PKIX, PKCS, and CMS Structures, implementing a strict subset of the original Privacy-Enhanced Mail encoding intended specifically for use with cryptographic keys, certificates, and other messages. Provides a no_std-friendly, constant-time implementation suitable for use with cryptographic private keys. """ authors = ["RustCrypto Developers"] license = "Apache-2.0 OR MIT" repository = "https://github.com/RustCrypto/formats/tree/master/pem-rfc7468" categories = ["cryptography", "data-structures", "encoding", "no-std", "parser-implementations"] keywords = ["crypto", "key", "pem", "pkcs", "rsa"] readme = "README.md" edition = "2021" rust-version = "1.60" [dependencies] base64ct = { version = "1.4", path = "../base64ct" } [features] alloc = ["base64ct/alloc"] std = ["alloc", "base64ct/std"] [package.metadata.docs.rs] all-features = true rustdoc-args = ["--cfg", "docsrs"] pem-rfc7468-0.7.0/LICENSE-APACHE000064400000000000000000000251411046102023000134410ustar 00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. pem-rfc7468-0.7.0/LICENSE-MIT000064400000000000000000000020651046102023000131510ustar 00000000000000Copyright (c) 2021 The RustCrypto Project Developers 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-rfc7468-0.7.0/README.md000064400000000000000000000076521046102023000130030ustar 00000000000000# [RustCrypto]: PEM Encoding ([RFC 7468]) [![crate][crate-image]][crate-link] [![Docs][docs-image]][docs-link] [![Build Status][build-image]][build-link] ![Apache2/MIT licensed][license-image] ![Rust Version][rustc-image] [![Project Chat][chat-image]][chat-link] Pure Rust implementation of PEM Encoding ([RFC 7468]) for PKIX, PKCS, and CMS Structures, a strict subset of the original Privacy-Enhanced Mail encoding intended specifically for use with cryptographic keys, certificates, and other messages. Provides a `no_std`-friendly, constant-time implementation suitable for use with cryptographic private keys. [Documentation][docs-link] ## About Many cryptography-related document formats, such as certificates (PKIX), private and public keys/keypairs (PKCS), and other cryptographic messages (CMS) provide an ASCII encoding which can be traced back to Privacy-Enhanced Mail (PEM) as defined [RFC 1421], which look like the following: ```text -----BEGIN PRIVATE KEY----- MC4CAQAwBQYDK2VwBCIEIBftnHPp22SewYmmEoMcX8VwI4IHwaqd+9LFPj/15eqF -----END PRIVATE KEY----- ``` However, all of these formats actually implement a text-based encoding that is similar but *not* identical to the legacy PEM encoding as described in [RFC 1421]. For this reason, [RFC 7468] was created to describe a stricter form of "PEM encoding" for use in these applications which codifies the previously de facto rules that most implementations operate by, and makes recommendations to promote interoperability. This crate provides a strict interpretation of the [RFC 7468] rules, implementing MUSTs and SHOULDs while avoiding the MAYs, targeting the "ABNF (Strict)" subset of the grammar as described in [RFC 7468 Section 3 Figure 3 (p6)][RFC 7468 p6]. ## Implementation notes - `no_std`-friendly core implementation which requires no heap allocations and avoids copies and temporary buffers. - Optional `alloc`-dependent convenience features and buffered decoder/encoder. - Uses the [`base64ct`] crate to decode/encode Base64 in constant-time. - PEM parser avoids branching on potentially secret data as much as possible. The paper [Util::Lookup: Exploiting key decoding in cryptographic libraries][Util::Lookup] demonstrates how the leakage from non-constant-time PEM parsers can be used to practically extract RSA private keys from SGX enclaves. ## Minimum Supported Rust Version This crate requires **Rust 1.60** at a minimum. We may change the MSRV in the future, but it will be accompanied by a minor version bump. ## License Licensed under either of: - [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) - [MIT license](http://opensource.org/licenses/MIT) at your option. ### Contribution Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. [//]: # (badges) [crate-image]: https://buildstats.info/crate/pem-rfc7468 [crate-link]: https://crates.io/crates/pem-rfc7468 [docs-image]: https://docs.rs/pem-rfc7468/badge.svg [docs-link]: https://docs.rs/pem-rfc7468/ [build-image]: https://github.com/RustCrypto/formats/actions/workflows/pem-rfc7468.yml/badge.svg [build-link]: https://github.com/RustCrypto/formats/actions/workflows/pem-rfc7468.yml [license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg [rustc-image]: https://img.shields.io/badge/rustc-1.60+-blue.svg [chat-image]: https://img.shields.io/badge/zulip-join_chat-blue.svg [chat-link]: https://rustcrypto.zulipchat.com/#narrow/stream/300570-formats [//]: # (links) [RustCrypto]: https://github.com/rustcrypto [RFC 1421]: https://datatracker.ietf.org/doc/html/rfc1421 [RFC 7468]: https://datatracker.ietf.org/doc/html/rfc7468 [RFC 7468 p6]: https://datatracker.ietf.org/doc/html/rfc7468#page-6 [`base64ct`]: https://github.com/RustCrypto/formats/tree/master/base64ct [Util::Lookup]: https://arxiv.org/pdf/2108.04600.pdf pem-rfc7468-0.7.0/src/decoder.rs000064400000000000000000000224451046102023000142630ustar 00000000000000//! Decoder for PEM encapsulated data. //! //! From RFC 7468 Section 2: //! //! > Textual encoding begins with a line comprising "-----BEGIN ", a //! > label, and "-----", and ends with a line comprising "-----END ", a //! > label, and "-----". Between these lines, or "encapsulation //! > boundaries", are base64-encoded data according to Section 4 of //! > [RFC 4648]. //! //! [RFC 4648]: https://datatracker.ietf.org/doc/html/rfc4648 use crate::{ grammar, Base64Decoder, Error, Result, BASE64_WRAP_WIDTH, POST_ENCAPSULATION_BOUNDARY, PRE_ENCAPSULATION_BOUNDARY, }; use core::str; #[cfg(feature = "alloc")] use alloc::vec::Vec; #[cfg(feature = "std")] use std::io; /// Decode a PEM document according to RFC 7468's "Strict" grammar. /// /// On success, writes the decoded document into the provided buffer, returning /// the decoded label and the portion of the provided buffer containing the /// decoded message. pub fn decode<'i, 'o>(pem: &'i [u8], buf: &'o mut [u8]) -> Result<(&'i str, &'o [u8])> { let mut decoder = Decoder::new(pem).map_err(|e| check_for_headers(pem, e))?; let type_label = decoder.type_label(); let buf = buf .get_mut(..decoder.remaining_len()) .ok_or(Error::Length)?; let decoded = decoder.decode(buf).map_err(|e| check_for_headers(pem, e))?; if decoder.base64.is_finished() { Ok((type_label, decoded)) } else { Err(Error::Length) } } /// Decode a PEM document according to RFC 7468's "Strict" grammar, returning /// the result as a [`Vec`] upon success. #[cfg(feature = "alloc")] pub fn decode_vec(pem: &[u8]) -> Result<(&str, Vec)> { let mut decoder = Decoder::new(pem).map_err(|e| check_for_headers(pem, e))?; let type_label = decoder.type_label(); let mut buf = Vec::new(); decoder .decode_to_end(&mut buf) .map_err(|e| check_for_headers(pem, e))?; Ok((type_label, buf)) } /// Decode the encapsulation boundaries of a PEM document according to RFC 7468's "Strict" grammar. /// /// On success, returning the decoded label. pub fn decode_label(pem: &[u8]) -> Result<&str> { Ok(Encapsulation::try_from(pem)?.label()) } /// Buffered PEM decoder. /// /// Stateful buffered decoder type which decodes an input PEM document according /// to RFC 7468's "Strict" grammar. #[derive(Clone)] pub struct Decoder<'i> { /// PEM type label. type_label: &'i str, /// Buffered Base64 decoder. base64: Base64Decoder<'i>, } impl<'i> Decoder<'i> { /// Create a new PEM [`Decoder`] with the default options. /// /// Uses the default 64-character line wrapping. pub fn new(pem: &'i [u8]) -> Result { Self::new_wrapped(pem, BASE64_WRAP_WIDTH) } /// Create a new PEM [`Decoder`] which wraps at the given line width. pub fn new_wrapped(pem: &'i [u8], line_width: usize) -> Result { let encapsulation = Encapsulation::try_from(pem)?; let type_label = encapsulation.label(); let base64 = Base64Decoder::new_wrapped(encapsulation.encapsulated_text, line_width)?; Ok(Self { type_label, base64 }) } /// Get the PEM type label for the input document. pub fn type_label(&self) -> &'i str { self.type_label } /// Decode data into the provided output buffer. /// /// There must be at least as much remaining Base64 input to be decoded /// in order to completely fill `buf`. pub fn decode<'o>(&mut self, buf: &'o mut [u8]) -> Result<&'o [u8]> { Ok(self.base64.decode(buf)?) } /// Decode all of the remaining data in the input buffer into `buf`. #[cfg(feature = "alloc")] pub fn decode_to_end<'o>(&mut self, buf: &'o mut Vec) -> Result<&'o [u8]> { Ok(self.base64.decode_to_end(buf)?) } /// Get the decoded length of the remaining PEM data after Base64 decoding. pub fn remaining_len(&self) -> usize { self.base64.remaining_len() } /// Are we finished decoding the PEM input? pub fn is_finished(&self) -> bool { self.base64.is_finished() } } impl<'i> From> for Base64Decoder<'i> { fn from(decoder: Decoder<'i>) -> Base64Decoder<'i> { decoder.base64 } } #[cfg(feature = "std")] impl<'i> io::Read for Decoder<'i> { fn read(&mut self, buf: &mut [u8]) -> io::Result { self.base64.read(buf) } fn read_to_end(&mut self, buf: &mut Vec) -> io::Result { self.base64.read_to_end(buf) } fn read_exact(&mut self, buf: &mut [u8]) -> io::Result<()> { self.base64.read_exact(buf) } } /// PEM encapsulation parser. /// /// This parser performs an initial pass over the data, locating the /// pre-encapsulation (`---BEGIN [...]---`) and post-encapsulation /// (`---END [...]`) boundaries while attempting to avoid branching /// on the potentially secret Base64-encoded data encapsulated between /// the two boundaries. /// /// It only supports a single encapsulated message at present. Future work /// could potentially include extending it provide an iterator over a series /// of encapsulated messages. #[derive(Copy, Clone, Debug)] struct Encapsulation<'a> { /// Type label extracted from the pre/post-encapsulation boundaries. /// /// From RFC 7468 Section 2: /// /// > The type of data encoded is labeled depending on the type label in /// > the "-----BEGIN " line (pre-encapsulation boundary). For example, /// > the line may be "-----BEGIN CERTIFICATE-----" to indicate that the /// > content is a PKIX certificate (see further below). Generators MUST /// > put the same label on the "-----END " line (post-encapsulation /// > boundary) as the corresponding "-----BEGIN " line. Labels are /// > formally case-sensitive, uppercase, and comprised of zero or more /// > characters; they do not contain consecutive spaces or hyphen-minuses, /// > nor do they contain spaces or hyphen-minuses at either end. Parsers /// > MAY disregard the label in the post-encapsulation boundary instead of /// > signaling an error if there is a label mismatch: some extant /// > implementations require the labels to match; others do not. label: &'a str, /// Encapsulated text portion contained between the boundaries. /// /// This data should be encoded as Base64, however this type performs no /// validation of it so it can be handled in constant-time. encapsulated_text: &'a [u8], } impl<'a> Encapsulation<'a> { /// Parse the type label and encapsulated text from between the /// pre/post-encapsulation boundaries. pub fn parse(data: &'a [u8]) -> Result { // Strip the "preamble": optional text occurring before the pre-encapsulation boundary let data = grammar::strip_preamble(data)?; // Parse pre-encapsulation boundary (including label) let data = data .strip_prefix(PRE_ENCAPSULATION_BOUNDARY) .ok_or(Error::PreEncapsulationBoundary)?; let (label, body) = grammar::split_label(data).ok_or(Error::Label)?; let mut body = match grammar::strip_trailing_eol(body).unwrap_or(body) { [head @ .., b'-', b'-', b'-', b'-', b'-'] => head, _ => return Err(Error::PreEncapsulationBoundary), }; // Ensure body ends with a properly labeled post-encapsulation boundary for &slice in [POST_ENCAPSULATION_BOUNDARY, label.as_bytes()].iter().rev() { // Ensure the input ends with the post encapsulation boundary as // well as a matching label if !body.ends_with(slice) { return Err(Error::PostEncapsulationBoundary); } let len = body.len().checked_sub(slice.len()).ok_or(Error::Length)?; body = body.get(..len).ok_or(Error::PostEncapsulationBoundary)?; } let encapsulated_text = grammar::strip_trailing_eol(body).ok_or(Error::PostEncapsulationBoundary)?; Ok(Self { label, encapsulated_text, }) } /// Get the label parsed from the encapsulation boundaries. pub fn label(self) -> &'a str { self.label } } impl<'a> TryFrom<&'a [u8]> for Encapsulation<'a> { type Error = Error; fn try_from(bytes: &'a [u8]) -> Result { Self::parse(bytes) } } /// Check for PEM headers in the input, as they are disallowed by RFC7468. /// /// Returns `Error::HeaderDisallowed` if headers are encountered. fn check_for_headers(pem: &[u8], err: Error) -> Error { if err == Error::Base64(base64ct::Error::InvalidEncoding) && pem.iter().any(|&b| b == grammar::CHAR_COLON) { Error::HeaderDisallowed } else { err } } #[cfg(test)] mod tests { use super::Encapsulation; #[test] fn pkcs8_example() { let pem = include_bytes!("../tests/examples/pkcs8.pem"); let encapsulation = Encapsulation::parse(pem).unwrap(); assert_eq!(encapsulation.label, "PRIVATE KEY"); assert_eq!( encapsulation.encapsulated_text, &[ 77, 67, 52, 67, 65, 81, 65, 119, 66, 81, 89, 68, 75, 50, 86, 119, 66, 67, 73, 69, 73, 66, 102, 116, 110, 72, 80, 112, 50, 50, 83, 101, 119, 89, 109, 109, 69, 111, 77, 99, 88, 56, 86, 119, 73, 52, 73, 72, 119, 97, 113, 100, 43, 57, 76, 70, 80, 106, 47, 49, 53, 101, 113, 70 ] ); } } pem-rfc7468-0.7.0/src/encoder.rs000064400000000000000000000237541046102023000143010ustar 00000000000000//! PEM encoder. use crate::{ grammar, Base64Encoder, Error, LineEnding, Result, BASE64_WRAP_WIDTH, ENCAPSULATION_BOUNDARY_DELIMITER, POST_ENCAPSULATION_BOUNDARY, PRE_ENCAPSULATION_BOUNDARY, }; use base64ct::{Base64, Encoding}; use core::str; #[cfg(feature = "alloc")] use alloc::string::String; #[cfg(feature = "std")] use std::io; /// Compute the length of a PEM encoded document which encapsulates a /// Base64-encoded body including line endings every 64 characters. /// /// The `input_len` parameter specifies the length of the raw input /// bytes prior to Base64 encoding. /// /// Note that the current implementation of this function computes an upper /// bound of the length and the actual encoded document may be slightly shorter /// (typically 1-byte). Downstream consumers of this function should check the /// actual encoded length and potentially truncate buffers allocated using this /// function to estimate the encapsulated size. /// /// Use [`encoded_len`] (when possible) to obtain a precise length. /// /// ## Returns /// - `Ok(len)` on success /// - `Err(Error::Length)` on length overflow pub fn encapsulated_len(label: &str, line_ending: LineEnding, input_len: usize) -> Result { encapsulated_len_wrapped(label, BASE64_WRAP_WIDTH, line_ending, input_len) } /// Compute the length of a PEM encoded document with the Base64 body /// line wrapped at the specified `width`. /// /// This is the same as [`encapsulated_len`], which defaults to a width of 64. /// /// Note that per [RFC7468 § 2] encoding PEM with any other wrap width besides /// 64 is technically non-compliant: /// /// > Generators MUST wrap the base64-encoded lines so that each line /// > consists of exactly 64 characters except for the final line, which /// > will encode the remainder of the data (within the 64-character line /// > boundary) /// /// [RFC7468 § 2]: https://datatracker.ietf.org/doc/html/rfc7468#section-2 pub fn encapsulated_len_wrapped( label: &str, line_width: usize, line_ending: LineEnding, input_len: usize, ) -> Result { if line_width < 4 { return Err(Error::Length); } let base64_len = input_len .checked_mul(4) .and_then(|n| n.checked_div(3)) .and_then(|n| n.checked_add(3)) .ok_or(Error::Length)? & !3; let base64_len_wrapped = base64_len_wrapped(base64_len, line_width, line_ending)?; encapsulated_len_inner(label, line_ending, base64_len_wrapped) } /// Get the length of a PEM encoded document with the given bytes and label. /// /// This function computes a precise length of the PEM encoding of the given /// `input` data. /// /// ## Returns /// - `Ok(len)` on success /// - `Err(Error::Length)` on length overflow pub fn encoded_len(label: &str, line_ending: LineEnding, input: &[u8]) -> Result { let base64_len = Base64::encoded_len(input); let base64_len_wrapped = base64_len_wrapped(base64_len, BASE64_WRAP_WIDTH, line_ending)?; encapsulated_len_inner(label, line_ending, base64_len_wrapped) } /// Encode a PEM document according to RFC 7468's "Strict" grammar. pub fn encode<'o>( type_label: &str, line_ending: LineEnding, input: &[u8], buf: &'o mut [u8], ) -> Result<&'o str> { let mut encoder = Encoder::new(type_label, line_ending, buf)?; encoder.encode(input)?; let encoded_len = encoder.finish()?; let output = &buf[..encoded_len]; // Sanity check debug_assert!(str::from_utf8(output).is_ok()); // Ensure `output` contains characters from the lower 7-bit ASCII set if output.iter().fold(0u8, |acc, &byte| acc | (byte & 0x80)) == 0 { // Use unchecked conversion to avoid applying UTF-8 checks to potentially // secret PEM documents (and therefore introducing a potential timing // sidechannel) // // SAFETY: contents of this buffer are controlled entirely by the encoder, // which ensures the contents are always a valid (ASCII) subset of UTF-8. // It's also additionally sanity checked by two assertions above to ensure // the validity (with the always-on runtime check implemented in a // constant time-ish manner. #[allow(unsafe_code)] Ok(unsafe { str::from_utf8_unchecked(output) }) } else { Err(Error::CharacterEncoding) } } /// Encode a PEM document according to RFC 7468's "Strict" grammar, returning /// the result as a [`String`]. #[cfg(feature = "alloc")] pub fn encode_string(label: &str, line_ending: LineEnding, input: &[u8]) -> Result { let expected_len = encoded_len(label, line_ending, input)?; let mut buf = vec![0u8; expected_len]; let actual_len = encode(label, line_ending, input, &mut buf)?.len(); debug_assert_eq!(expected_len, actual_len); String::from_utf8(buf).map_err(|_| Error::CharacterEncoding) } /// Compute the encapsulated length of Base64 data of the given length. fn encapsulated_len_inner( label: &str, line_ending: LineEnding, base64_len: usize, ) -> Result { [ PRE_ENCAPSULATION_BOUNDARY.len(), label.as_bytes().len(), ENCAPSULATION_BOUNDARY_DELIMITER.len(), line_ending.len(), base64_len, line_ending.len(), POST_ENCAPSULATION_BOUNDARY.len(), label.as_bytes().len(), ENCAPSULATION_BOUNDARY_DELIMITER.len(), line_ending.len(), ] .into_iter() .try_fold(0usize, |acc, len| acc.checked_add(len)) .ok_or(Error::Length) } /// Compute Base64 length line-wrapped at the specified width with the given /// line ending. fn base64_len_wrapped( base64_len: usize, line_width: usize, line_ending: LineEnding, ) -> Result { base64_len .saturating_sub(1) .checked_div(line_width) .and_then(|lines| lines.checked_mul(line_ending.len())) .and_then(|len| len.checked_add(base64_len)) .ok_or(Error::Length) } /// Buffered PEM encoder. /// /// Stateful buffered encoder type which encodes an input PEM document according /// to RFC 7468's "Strict" grammar. pub struct Encoder<'l, 'o> { /// PEM type label. type_label: &'l str, /// Line ending used to wrap Base64. line_ending: LineEnding, /// Buffered Base64 encoder. base64: Base64Encoder<'o>, } impl<'l, 'o> Encoder<'l, 'o> { /// Create a new PEM [`Encoder`] with the default options which /// writes output into the provided buffer. /// /// Uses the default 64-character line wrapping. pub fn new(type_label: &'l str, line_ending: LineEnding, out: &'o mut [u8]) -> Result { Self::new_wrapped(type_label, BASE64_WRAP_WIDTH, line_ending, out) } /// Create a new PEM [`Encoder`] which wraps at the given line width. /// /// Note that per [RFC7468 § 2] encoding PEM with any other wrap width besides /// 64 is technically non-compliant: /// /// > Generators MUST wrap the base64-encoded lines so that each line /// > consists of exactly 64 characters except for the final line, which /// > will encode the remainder of the data (within the 64-character line /// > boundary) /// /// This method is provided with the intended purpose of implementing the /// OpenSSH private key format, which uses a non-standard wrap width of 70. /// /// [RFC7468 § 2]: https://datatracker.ietf.org/doc/html/rfc7468#section-2 pub fn new_wrapped( type_label: &'l str, line_width: usize, line_ending: LineEnding, mut out: &'o mut [u8], ) -> Result { grammar::validate_label(type_label.as_bytes())?; for boundary_part in [ PRE_ENCAPSULATION_BOUNDARY, type_label.as_bytes(), ENCAPSULATION_BOUNDARY_DELIMITER, line_ending.as_bytes(), ] { if out.len() < boundary_part.len() { return Err(Error::Length); } let (part, rest) = out.split_at_mut(boundary_part.len()); out = rest; part.copy_from_slice(boundary_part); } let base64 = Base64Encoder::new_wrapped(out, line_width, line_ending)?; Ok(Self { type_label, line_ending, base64, }) } /// Get the PEM type label used for this document. pub fn type_label(&self) -> &'l str { self.type_label } /// Encode the provided input data. /// /// This method can be called as many times as needed with any sized input /// to write data encoded data into the output buffer, so long as there is /// sufficient space in the buffer to handle the resulting Base64 encoded /// data. pub fn encode(&mut self, input: &[u8]) -> Result<()> { self.base64.encode(input)?; Ok(()) } /// Borrow the inner [`Base64Encoder`]. pub fn base64_encoder(&mut self) -> &mut Base64Encoder<'o> { &mut self.base64 } /// Finish encoding PEM, writing the post-encapsulation boundary. /// /// On success, returns the total number of bytes written to the output /// buffer. pub fn finish(self) -> Result { let (base64, mut out) = self.base64.finish_with_remaining()?; for boundary_part in [ self.line_ending.as_bytes(), POST_ENCAPSULATION_BOUNDARY, self.type_label.as_bytes(), ENCAPSULATION_BOUNDARY_DELIMITER, self.line_ending.as_bytes(), ] { if out.len() < boundary_part.len() { return Err(Error::Length); } let (part, rest) = out.split_at_mut(boundary_part.len()); out = rest; part.copy_from_slice(boundary_part); } encapsulated_len_inner(self.type_label, self.line_ending, base64.len()) } } #[cfg(feature = "std")] impl<'l, 'o> io::Write for Encoder<'l, 'o> { fn write(&mut self, buf: &[u8]) -> io::Result { self.encode(buf)?; Ok(buf.len()) } fn flush(&mut self) -> io::Result<()> { // TODO(tarcieri): return an error if there's still data remaining in the buffer? Ok(()) } } pem-rfc7468-0.7.0/src/error.rs000064400000000000000000000062571046102023000140120ustar 00000000000000//! Error types use core::fmt; /// Result type with the `pem-rfc7468` crate's [`Error`] type. pub type Result = core::result::Result; /// PEM errors. #[derive(Copy, Clone, Debug, Eq, PartialEq)] #[non_exhaustive] pub enum Error { /// Base64-related errors. Base64(base64ct::Error), /// Character encoding-related errors. CharacterEncoding, /// Errors in the encapsulated text (which aren't specifically Base64-related). EncapsulatedText, /// Header detected in the encapsulated text. HeaderDisallowed, /// Invalid label. Label, /// Invalid length. Length, /// "Preamble" (text before pre-encapsulation boundary) contains invalid data. Preamble, /// Errors in the pre-encapsulation boundary. PreEncapsulationBoundary, /// Errors in the post-encapsulation boundary. PostEncapsulationBoundary, /// Unexpected PEM type label. UnexpectedTypeLabel { /// Type label that was expected. expected: &'static str, }, } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Error::Base64(err) => write!(f, "PEM Base64 error: {}", err), Error::CharacterEncoding => f.write_str("PEM character encoding error"), Error::EncapsulatedText => f.write_str("PEM error in encapsulated text"), Error::HeaderDisallowed => f.write_str("PEM headers disallowed by RFC7468"), Error::Label => f.write_str("PEM type label invalid"), Error::Length => f.write_str("PEM length invalid"), Error::Preamble => f.write_str("PEM preamble contains invalid data (NUL byte)"), Error::PreEncapsulationBoundary => { f.write_str("PEM error in pre-encapsulation boundary") } Error::PostEncapsulationBoundary => { f.write_str("PEM error in post-encapsulation boundary") } Error::UnexpectedTypeLabel { expected } => { write!(f, "unexpected PEM type label: expecting \"{}\"", expected) } } } } #[cfg(feature = "std")] impl std::error::Error for Error {} impl From for Error { fn from(err: base64ct::Error) -> Error { Error::Base64(err) } } impl From for Error { fn from(_: base64ct::InvalidLengthError) -> Error { Error::Length } } impl From for Error { fn from(_: core::str::Utf8Error) -> Error { Error::CharacterEncoding } } #[cfg(feature = "std")] impl From for std::io::Error { fn from(err: Error) -> std::io::Error { let kind = match err { Error::Base64(err) => return err.into(), // Use existing conversion Error::CharacterEncoding | Error::EncapsulatedText | Error::Label | Error::Preamble | Error::PreEncapsulationBoundary | Error::PostEncapsulationBoundary => std::io::ErrorKind::InvalidData, Error::Length => std::io::ErrorKind::UnexpectedEof, _ => std::io::ErrorKind::Other, }; std::io::Error::new(kind, err) } } pem-rfc7468-0.7.0/src/grammar.rs000064400000000000000000000161631046102023000143040ustar 00000000000000//! Helper functions and rules for enforcing the ABNF grammar for //! RFC 7468-flavored PEM as described in Section 3. //! //! The grammar described below is intended to follow the "ABNF (Strict)" //! subset of the grammar as described in Section 3 Figure 3. use crate::{Error, Result, PRE_ENCAPSULATION_BOUNDARY}; use core::str; /// NUL char pub(crate) const CHAR_NUL: u8 = 0x00; /// Horizontal tab pub(crate) const CHAR_HT: u8 = 0x09; /// Space pub(crate) const CHAR_SP: u8 = 0x20; /// Carriage return pub(crate) const CHAR_CR: u8 = 0x0d; /// Line feed pub(crate) const CHAR_LF: u8 = 0x0a; /// Colon ':' pub(crate) const CHAR_COLON: u8 = 0x3A; /// Any printable character except hyphen-minus, as defined in the /// 'labelchar' production in the RFC 7468 ABNF grammar pub(crate) fn is_labelchar(char: u8) -> bool { matches!(char, 0x21..=0x2C | 0x2E..=0x7E) } /// Does the provided byte match a character allowed in a label? // TODO: allow hyphen-minus to match the 'label' production in the ABNF grammar pub(crate) fn is_allowed_in_label(char: u8) -> bool { is_labelchar(char) || matches!(char, CHAR_HT | CHAR_SP) } /// Does the provided byte match the "WSP" ABNF production from Section 3? /// /// > The common ABNF production WSP is congruent with "blank"; /// > a new production W is used for "whitespace" pub(crate) fn is_wsp(char: u8) -> bool { matches!(char, CHAR_HT | CHAR_SP) } /// Strip the "preamble", i.e. data that appears before the PEM /// pre-encapsulation boundary. /// /// Presently no attempt is made to ensure the preamble decodes successfully /// under any particular character encoding. The only byte which is disallowed /// is the NUL byte. This restriction does not appear in RFC7468, but rather /// is inspired by the OpenSSL PEM decoder. /// /// Returns a slice which starts at the beginning of the encapsulated text. /// /// From RFC7468: /// > Data before the encapsulation boundaries are permitted, and /// > parsers MUST NOT malfunction when processing such data. pub(crate) fn strip_preamble(mut bytes: &[u8]) -> Result<&[u8]> { if bytes.starts_with(PRE_ENCAPSULATION_BOUNDARY) { return Ok(bytes); } while let Some((byte, remaining)) = bytes.split_first() { match *byte { CHAR_NUL => { return Err(Error::Preamble); } CHAR_LF if remaining.starts_with(PRE_ENCAPSULATION_BOUNDARY) => { return Ok(remaining); } _ => (), } bytes = remaining; } Err(Error::Preamble) } /// Strip a newline (`eol`) from the beginning of the provided byte slice. /// /// The newline is considered mandatory and a decoding error will occur if it /// is not present. /// /// From RFC 7468 Section 3: /// > lines are divided with CRLF, CR, or LF. pub(crate) fn strip_leading_eol(bytes: &[u8]) -> Option<&[u8]> { match bytes { [CHAR_LF, rest @ ..] => Some(rest), [CHAR_CR, CHAR_LF, rest @ ..] => Some(rest), [CHAR_CR, rest @ ..] => Some(rest), _ => None, } } /// Strip a newline (`eol`) from the end of the provided byte slice. /// /// The newline is considered mandatory and a decoding error will occur if it /// is not present. /// /// From RFC 7468 Section 3: /// > lines are divided with CRLF, CR, or LF. pub(crate) fn strip_trailing_eol(bytes: &[u8]) -> Option<&[u8]> { match bytes { [head @ .., CHAR_CR, CHAR_LF] => Some(head), [head @ .., CHAR_LF] => Some(head), [head @ .., CHAR_CR] => Some(head), _ => None, } } /// Split a slice beginning with a type label as located in an encapsulation /// boundary. Returns the label as a `&str`, and slice beginning with the /// encapsulated text with leading `-----` and newline removed. /// /// This implementation follows the rules put forth in Section 2, which are /// stricter than those found in the ABNF grammar: /// /// > Labels are formally case-sensitive, uppercase, and comprised of zero or more /// > characters; they do not contain consecutive spaces or hyphen-minuses, /// > nor do they contain spaces or hyphen-minuses at either end. /// /// We apply a slightly stricter interpretation: /// - Labels MAY be empty /// - Non-empty labels MUST start with an upper-case letter: `'A'..='Z'` /// - The only allowable characters subsequently are `'A'..='Z'` or WSP. /// (NOTE: this is an overly strict initial implementation and should be relaxed) /// - Whitespace MUST NOT contain more than one consecutive WSP character // TODO(tarcieri): evaluate whether this is too strict; support '-' pub(crate) fn split_label(bytes: &[u8]) -> Option<(&str, &[u8])> { let mut n = 0usize; // TODO(tarcieri): handle hyphens in labels as well as spaces let mut last_was_wsp = false; for &char in bytes { // Validate character if is_labelchar(char) { last_was_wsp = false; } else if char == b'-' { // Possible start of encapsulation boundary delimiter break; } else if n != 0 && is_wsp(char) { // Repeated whitespace disallowed if last_was_wsp { return None; } last_was_wsp = true; } else { return None; } n = n.checked_add(1)?; } let (raw_label, rest) = bytes.split_at(n); let label = str::from_utf8(raw_label).ok()?; match rest { [b'-', b'-', b'-', b'-', b'-', body @ ..] => Some((label, strip_leading_eol(body)?)), _ => None, } } /// Validate that the given bytes are allowed as a PEM type label, i.e. the /// label encoded in the `BEGIN` and `END` encapsulation boundaries. pub(crate) fn validate_label(label: &[u8]) -> Result<()> { // TODO(tarcieri): handle hyphens in labels as well as spaces let mut last_was_wsp = false; for &char in label { if !is_allowed_in_label(char) { return Err(Error::Label); } if is_wsp(char) { // Double sequential whitespace characters disallowed if last_was_wsp { return Err(Error::Label); } last_was_wsp = true; } else { last_was_wsp = false; } } Ok(()) } #[cfg(test)] mod tests { use super::*; /// Empty label is OK. #[test] fn split_label_empty() { let (label, body) = split_label(b"-----\nBODY").unwrap(); assert_eq!(label, ""); assert_eq!(body, b"BODY"); } /// Label containing text. #[test] fn split_label_with_text() { let (label, body) = split_label(b"PRIVATE KEY-----\nBODY").unwrap(); assert_eq!(label, "PRIVATE KEY"); assert_eq!(body, b"BODY"); } /// Reject labels containing repeated spaces #[test] fn split_label_with_repeat_wsp_is_err() { assert!(split_label(b"PRIVATE KEY-----\nBODY").is_none()); } /// Basic validation of a label #[test] fn validate_private_key_label() { assert_eq!(validate_label(b"PRIVATE KEY"), Ok(())); } /// Reject labels with double spaces #[test] fn validate_private_key_label_reject_double_space() { assert_eq!(validate_label(b"PRIVATE KEY"), Err(Error::Label)); } } pem-rfc7468-0.7.0/src/lib.rs000064400000000000000000000075611046102023000134260ustar 00000000000000#![no_std] #![cfg_attr(docsrs, feature(doc_auto_cfg))] #![doc = include_str!("../README.md")] #![doc( html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/6ee8e381/logo.svg", html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/6ee8e381/logo.svg" )] #![deny(unsafe_code)] #![warn( clippy::integer_arithmetic, clippy::mod_module_files, clippy::panic, clippy::panic_in_result_fn, clippy::unwrap_used, missing_docs, rust_2018_idioms, unused_lifetimes, unused_qualifications )] //! # Usage //! #![cfg_attr(feature = "std", doc = " ```")] #![cfg_attr(not(feature = "std"), doc = " ```ignore")] //! # fn main() -> Result<(), Box> { //! /// Example PEM document //! /// NOTE: do not actually put private key literals into your source code!!! //! let example_pem = "\ //! -----BEGIN PRIVATE KEY----- //! MC4CAQAwBQYDK2VwBCIEIBftnHPp22SewYmmEoMcX8VwI4IHwaqd+9LFPj/15eqF //! -----END PRIVATE KEY----- //! "; //! //! // Decode PEM //! let (type_label, data) = pem_rfc7468::decode_vec(example_pem.as_bytes())?; //! assert_eq!(type_label, "PRIVATE KEY"); //! assert_eq!( //! data, //! &[ //! 48, 46, 2, 1, 0, 48, 5, 6, 3, 43, 101, 112, 4, 34, 4, 32, 23, 237, 156, 115, 233, 219, //! 100, 158, 193, 137, 166, 18, 131, 28, 95, 197, 112, 35, 130, 7, 193, 170, 157, 251, //! 210, 197, 62, 63, 245, 229, 234, 133 //! ] //! ); //! //! // Encode PEM //! use pem_rfc7468::LineEnding; //! let encoded_pem = pem_rfc7468::encode_string(type_label, LineEnding::default(), &data)?; //! assert_eq!(&encoded_pem, example_pem); //! # Ok(()) //! # } //! ``` #[cfg(feature = "alloc")] #[macro_use] extern crate alloc; #[cfg(feature = "std")] extern crate std; mod decoder; mod encoder; mod error; mod grammar; pub use crate::{ decoder::{decode, decode_label, Decoder}, encoder::{encapsulated_len, encapsulated_len_wrapped, encode, encoded_len, Encoder}, error::{Error, Result}, }; pub use base64ct::LineEnding; #[cfg(feature = "alloc")] pub use crate::{decoder::decode_vec, encoder::encode_string}; /// The pre-encapsulation boundary appears before the encapsulated text. /// /// From RFC 7468 Section 2: /// > There are exactly five hyphen-minus (also known as dash) characters ("-") /// > on both ends of the encapsulation boundaries, no more, no less. const PRE_ENCAPSULATION_BOUNDARY: &[u8] = b"-----BEGIN "; /// The post-encapsulation boundary appears immediately after the encapsulated text. const POST_ENCAPSULATION_BOUNDARY: &[u8] = b"-----END "; /// Delimiter of encapsulation boundaries. const ENCAPSULATION_BOUNDARY_DELIMITER: &[u8] = b"-----"; /// Width at which the Base64 body of RFC7468-compliant PEM is wrapped. /// /// From [RFC7468 § 2]: /// /// > Generators MUST wrap the base64-encoded lines so that each line /// > consists of exactly 64 characters except for the final line, which /// > will encode the remainder of the data (within the 64-character line /// > boundary), and they MUST NOT emit extraneous whitespace. Parsers MAY /// > handle other line sizes. /// /// [RFC7468 § 2]: https://datatracker.ietf.org/doc/html/rfc7468#section-2 pub const BASE64_WRAP_WIDTH: usize = 64; /// Buffered Base64 decoder type. pub type Base64Decoder<'i> = base64ct::Decoder<'i, base64ct::Base64>; /// Buffered Base64 encoder type. pub type Base64Encoder<'o> = base64ct::Encoder<'o, base64ct::Base64>; /// Marker trait for types with an associated PEM type label. pub trait PemLabel { /// Expected PEM type label for a given document, e.g. `"PRIVATE KEY"` const PEM_LABEL: &'static str; /// Validate that a given label matches the expected label. fn validate_pem_label(actual: &str) -> Result<()> { if Self::PEM_LABEL == actual { Ok(()) } else { Err(Error::UnexpectedTypeLabel { expected: Self::PEM_LABEL, }) } } } pem-rfc7468-0.7.0/tests/decode.rs000064400000000000000000000067551046102023000144620ustar 00000000000000//! PEM decoding tests #[test] fn pkcs1_example() { let pem = include_bytes!("examples/pkcs1.pem"); let mut buf = [0u8; 2048]; let (label, decoded) = pem_rfc7468::decode(pem, &mut buf).unwrap(); assert_eq!(label, "RSA PRIVATE KEY"); assert_eq!(decoded, include_bytes!("examples/pkcs1.der")); } #[test] fn binary_example() { let der = include_bytes!("examples/pkcs1.der"); let mut buf = [0u8; 2048]; match pem_rfc7468::decode(der, &mut buf) { Err(pem_rfc7468::Error::Preamble) => (), _ => panic!("Expected Preamble error"), } } #[test] fn pkcs1_example_with_preceeding_junk() { let pem = include_bytes!("examples/pkcs1_with_preceeding_junk.pem"); let mut buf = [0u8; 2048]; let (label, decoded) = pem_rfc7468::decode(pem, &mut buf).unwrap(); assert_eq!(label, "RSA PRIVATE KEY"); assert_eq!(decoded, include_bytes!("examples/pkcs1.der")); } #[test] fn pkcs1_enc_example() { let pem = include_bytes!("examples/ssh_rsa_pem_password.pem"); let mut buf = [0u8; 2048]; let result = pem_rfc7468::decode(pem, &mut buf); assert_eq!(result, Err(pem_rfc7468::Error::HeaderDisallowed)); let label = pem_rfc7468::decode_label(pem).unwrap(); assert_eq!(label, "RSA PRIVATE KEY"); } #[test] #[cfg(feature = "alloc")] fn pkcs1_enc_example_with_vec() { let pem = include_bytes!("examples/ssh_rsa_pem_password.pem"); let result = pem_rfc7468::decode_vec(pem); assert_eq!(result, Err(pem_rfc7468::Error::HeaderDisallowed)); } #[test] fn header_of_length_64() { let pem = include_bytes!("examples/chosen_header.pem"); let mut buf = [0u8; 2048]; let result = pem_rfc7468::decode(pem, &mut buf); assert_eq!(result, Err(pem_rfc7468::Error::HeaderDisallowed)); let label = pem_rfc7468::decode_label(pem).unwrap(); assert_eq!(label, "RSA PRIVATE KEY"); } #[test] #[cfg(feature = "alloc")] fn header_of_length_64_with_vec() { let pem = include_bytes!("examples/chosen_header.pem"); match pem_rfc7468::decode_vec(pem) { Err(pem_rfc7468::Error::HeaderDisallowed) => (), res => panic!("Expected HeaderDisallowed error; Found {:?}", res), } } #[test] fn pkcs8_example() { let pem = include_bytes!("examples/pkcs8.pem"); let mut buf = [0u8; 2048]; let (label, decoded) = pem_rfc7468::decode(pem, &mut buf).unwrap(); assert_eq!(label, "PRIVATE KEY"); assert_eq!(decoded, include_bytes!("examples/pkcs8.der")); } #[test] fn pkcs8_enc_example() { let pem = include_bytes!("examples/pkcs8-enc.pem"); let mut buf = [0u8; 2048]; let (label, decoded) = pem_rfc7468::decode(pem, &mut buf).unwrap(); assert_eq!(label, "ENCRYPTED PRIVATE KEY"); assert_eq!(decoded, include_bytes!("examples/pkcs8-enc.der")); } #[test] #[cfg(feature = "alloc")] fn pkcs1_example_with_vec() { let pem = include_bytes!("examples/pkcs1.pem"); let (label, decoded) = pem_rfc7468::decode_vec(pem).unwrap(); assert_eq!(label, "RSA PRIVATE KEY"); assert_eq!(decoded, include_bytes!("examples/pkcs1.der")); } #[test] #[cfg(feature = "alloc")] fn pkcs8_enc_example_with_vec() { let pem = include_bytes!("examples/pkcs8-enc.pem"); let (label, decoded) = pem_rfc7468::decode_vec(pem).unwrap(); assert_eq!(label, "ENCRYPTED PRIVATE KEY"); assert_eq!(decoded, include_bytes!("examples/pkcs8-enc.der")); } #[test] fn ed25519_example() { let pem = include_bytes!("examples/ed25519_id.pem"); let label = pem_rfc7468::decode_label(pem).unwrap(); assert_eq!(label, "ED25519 CERT"); } pem-rfc7468-0.7.0/tests/encode.rs000064400000000000000000000011451046102023000144600ustar 00000000000000//! PEM decoding tests #![cfg(feature = "alloc")] use pem_rfc7468::LineEnding; #[test] fn pkcs1_example() { let label = "RSA PRIVATE KEY"; let bytes = include_bytes!("examples/pkcs1.der"); let encoded = pem_rfc7468::encode_string(label, LineEnding::LF, bytes).unwrap(); assert_eq!(&encoded, include_str!("examples/pkcs1.pem")); } #[test] fn pkcs8_example() { let label = "PRIVATE KEY"; let bytes = include_bytes!("examples/pkcs8.der"); let encoded = pem_rfc7468::encode_string(label, LineEnding::LF, bytes).unwrap(); assert_eq!(&encoded, include_str!("examples/pkcs8.pem")); } pem-rfc7468-0.7.0/tests/examples/chosen_header.pem000064400000000000000000000034471046102023000177740ustar 00000000000000-----BEGIN RSA PRIVATE KEY----- A-Header-That-Happens-To-Be-Exactly: 64 characters long......... Proc-Type: 4,ENCRYPTED DEK-Info: AES-128-CBC,15670D76FD184D46C40C971733E0543F /lVaZdZ2a6x5d3b96F6XpFzcnP35pUrwpKxEB07nF15Jc81jwEAg72OpFTp5QRMu WXbbZ/dKF7ucGHvLQ/VvCNkbl6oqowSme94fzFsa/xuKRHAGDHVi/TQylIOBBJFv vru/3EZkO8mAQRDTNfuSl0Y5Ir7uqAQy0E/xKfOdY73BO4//KEDEIshRwBxbOm3K D2sU1Kp8RnnBgSNydG8AH/LrtBnFs9HWrb9JD0Nj5bIxZDzil5CYmTB8PRgb2Qy7 bckVc+0Y/h8Ai+NjSc/rJVw0smKJbmNSoPyJH1WjDPW8wWtngCFQWaVCTubm08N1 nqrzIclT3fnq8YFSbFJYZVPaADxjv2HW7dLH7grYqXx/5FTE34ixXTcwQ1KR0ZQX uaGZhkiDVWf/q82JPREqH5hwbeGL9QwZHF4/74vKIsddEuVFp8EW7jn9INRoVBtK /OBiVXmVELFhmVBqvQU7GSci7+fCntXIx6W3hGiJL2WyXfuP16u9BhF5kd+c3pAm tOZ3Lc5XsceBIYq0rKhy7rDhEg0V8wF1jHeeiW0VKDt2cFePSAd4CIbHiRWbvwh+ zIoNAB34k4cYShmjOHKem9FMHVHSwfRE39Vrwssj0HWVOp7KdXYv64w4Ywmn6wvA r6p8IZWg7KqA5UApPpiBVs0BAx1KtZk3o1dvXAazklw23icnnZF6XqaH6EmnVsf9 gbyK1NcH3lIalTYhs+hMwizkw/XDb1uU8G7Rz1QFKBiL56J8ePIA2NWRUwvdMEAv rZXSq4Icwy566GIqdtMRNLcz6LthNEg9qg+fD5aGLrtTk8ACSQpb/ELMMzqDVTkI 07dB1Nhzx9nd9mUlIuA030I5w7f//5pS6/lGmmPZblygY1PBludl+p/P9OKJ+Jr0 HTAI4SVxoYdp6YHDBJ9J7Wt6UnIe+/3WarY9d9X1XNGOE4K+nRFihSShtKHDtMY6 eBEV1sBTXJ1KANG683CU+uDx2XpOVAwDGl5hyRdzOovNC1iWjSu+CvppDvZLuIMj zIllu5E8PR2Zd1wIT1gnU/7HiVdM0m4jf6ptkGSWNSCLA0ipii0YYarXoyu0kbMY BKKpp5QRXv6OwmSDMwQTPuRIWyk839X1ABE1XeTKt43Ns+Wtdboi8Cu/aO/Z5AoA gbJ+CdyKJIJxDXA11cPq9SF2daYmqHV3agrrKmAwWBRwpCKvotv0Hxw2M1+91ZoU NY52RraoNVQPOAEfhYNS0ltVPzxcDU5bA2WczO6QzmMl7So6dysw+fxtxaEUGt4m Fj+p+rE64Okq4wWDlEQya/xu4KMZwzyDncgJHHyYahs+vCv9KbQLW8R0iHTbxQzX Vhomq++Cm8kg5aA/UsLas/l6ZyfNIcA99U8shFFA5urOKMl/jSRd9v1c7H3nOPZ7 +eN10E7hcRruwOkoBlpd2It3Y2M+1qBDWXLVSHSXmIuzdE+MZ8CZfvxe+FcfpvJU BFsZbSEF2PQC+zhd1HjV6DUe3jCz88/rjUnXQCvEJ7z7Tuz3C7kKdR3OYYYLwuLW LTy2VS0p3QuUeMnNRl0HxpB16BZax9mzFr0UvFKp2QQYzOkIghg2sLNEbtaJvHNh -----END RSA PRIVATE KEY----- pem-rfc7468-0.7.0/tests/examples/ed25519_id.pem000064400000000000000000000003671046102023000166550ustar 00000000000000-----BEGIN ED25519 CERT----- AQQABrknAdj5BeHBAd0mq1KD3ABvDzpBvUD0zU88DASbkRuV0WiaAQAgBADPc8aR rUUolIsrKFMKy7SVCxKvpGrcdFAni+Bah1WZHnac5JP3LnPc2/0G7dTSlSTeBk5k XqIySdIqtfYbW0kQinA0PaxDzzX5g1q3CclY9lNTAglR5fP71kunXh7ntwk= -----END ED25519 CERT----- pem-rfc7468-0.7.0/tests/examples/pkcs1.der000064400000000000000000000022471046102023000162140ustar 000000000000000,Q_>BCp&3H3cGOo6:])9 K9AOG8,v=Syr~ _'^|KOYp("ܼʻ|CɂrEX2&mt:큒шLG9fܗǬE = |W_SOWN0ǻțxRUϰ'ȟN<򈎓2A֕ܐN> _pX%?^)\m8Pa☗[/~̃btd!^"Jip 6Mϻ.?i*gz?^R喎d|n@d-XO_HeQpOVzcd(m%UU+Ovd?x~eĪн'` \J^2uٗ,n}ѓ_n`a+\|ɜ=:$ãt vj}x T%M|LEL eY$۫5Rhn@mcdsva$-N*ep6~naF~eB%<~\1znZnX3ֻ"ܾ׏Nخ܆G<8ᄆCaнj3y@_&4;k-gu~@To$#jFSg`m\> 7Gb6IhSjNi "ߧ@u Rvd{xWwˋ"& O;ӪŐd1_āVv"!BC'h  cnUHSCp]hWɽe xa~.H$ m`T{d??6cI~4/.&ӡ4jֹw NbM]fi%]1|j}5M%B (kMB5XCQ { m>s.I *li@)q2u8׾LƹۥOV4Ĝ'}Fxcnj|hr1Xz˯OQqE/J|r0mcuZH.Qx.(tV$ol?S>1mɘ<*pem-rfc7468-0.7.0/tests/examples/pkcs1.pem000064400000000000000000000032131046102023000162150ustar 00000000000000-----BEGIN RSA PRIVATE KEY----- MIIEowIBAAKCAQEAtsQsUV8QpqrygsY+2+JCQ6Fw8/omM71IM2N/R8pPbzbgOl0p 78MZGsgPOQ2HSznjD0FPzsH8oO2B5Uftws04LHb2HJAYlz25+lN5cqfHAfa3fgmC 38FfwBkn7l582UtPWZ/wcBOnyCgb3yLcvJrXyrt8QxHJgvWO23ITrUVYszImbXQ6 7YGS0YhMrbixRzmo2tpm3JcIBtnHrEUMsT0NfFdfsZhTT8YbxBvA8FdODgEwx7u/ vf3J9qbi4+Kv8cvqyJuleIRSjVXPsIMnoejIn04APPKIjpMyQdnWlby7rNyQtE4+ CV+jcFjqJbE/Xilcvqxt6DirjFCvYeKYl1uHLwIDAQABAoIBAH7Mg2LA7bB0EWQh XiL3SrnZG6BpAHAM9jaQ5RFNjua9z7suP5YUaSpnegg/FopeUuWWjmQHudl8bg5A ZPgtoLdYoU8XubfUH19I4o1lUXBPVuaeeqn6Yw/HZCjAbSXkVdz8VbesK092ZD/e 0/4V/3irsn5lrMSq0L322yfvYKaRDFxKCF7UMnWrGcHZl6Msbv/OffLRk19uYB7t 4WGhK1zCfKIfgdLJnD0eoI6Q4wU6sJvvpyTe8NDDo8HpdAwNn3YSahSewKp9gHgg VIQlTZUdsHxM+R+2RUwJZYj9WSTbq+s1nKICUmjQBPnWbrPW963BE5utQPFt3mOe EWRzdsECgYEA3MBhJC1Okq+u5yrFE8plufdwNvm9fg5uYUYafvdlQiXsFTx+XDGm FXpuWhP/bheOh1jByzPZ1rvjF57xiZjkIuzcvtePTs/b5fT82K7CydDchkc8qb0W 2dI40h+13e++sUPKYdC9aqjZHzOgl3kOlkDbyRCF3F8mNDujE49rLWcCgYEA0/MU dX5A6VSDb5K+JCNq8vDaBKNGU8GAr2fpYAhtk/3mXLI+/Z0JN0di9ZgeNhhJr2jN 11OU/2pOButpsgnkIo2y36cOQPf5dQpSgXZke3iNDld3osuLIuPNJn/3C087AtOq +w4YxZClZLAxiLCqX8SBVrB2IiFCQ70SJ++n8vkCgYEAzmi3rBsNEA1jblVIh1PF wJhD/bOQ4nBd92iUV8m9jZdl4wl4YX4u/IBI9MMkIG24YIe2VOl7s9Rk5+4/jNg/ 4QQ2998Y6aljxOZJEdZ+3jQELy4m49OhrTRq2ta5t/Z3CMsJTmLe6f9NXWZpr5iK 8iVdHOjtMXxqfYaR2jVNEtsCgYAl9uWUQiAoa037v0I1wO5YQ9IZgJGJUSDWynsg C4JtPs5zji4ASY+sCipsqWnH8MPKGrC8QClxMr51ONe+30yw78a5jvfbpU9Wqpmq vOU0xJwnlH1GeMUcY8eMfOFocjG0yOtYeubvBIDLr0/AFzz9WHp+Z69RX7m53nUR GDlyKQKBgDGZVAbUBiB8rerqNbONBAxfipoa4IJ+ntBrFT2DtoIZNbSzaoK+nVbH kbWMJycaV5PVOh1lfAiZeWCxQz5RcZh/RS8USnxyMG1j4dP/wLcbdasI8uRaSC6Y hFHL5HjhLrIo0HRWySS2b2ztBI2FP1M+MaaGFPHDzm2OyZg85yr3 -----END RSA PRIVATE KEY----- pem-rfc7468-0.7.0/tests/examples/pkcs1_with_preceeding_junk.pem000064400000000000000000000050661046102023000224740ustar 00000000000000Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum lacinia euismod gravida. Sed non suscipit mauris. Sed ac purus sem. Cras ipsum velit, egestas eu lorem at, viverra pellentesque nulla. Cras posuere commodo tortor, id viverra velit pellentesque sit amet. Suspendisse bibendum eleifend lacus, ac venenatis elit commodo vulputate. Maecenas dapibus libero a nulla aliquet pulvinar. Vivamus scelerisque elit ac ex rhoncus, ac lacinia enim iaculis. Vivamus ultrices, sapien vel sodales rhoncus, augue turpis congue turpis, ut laoreet orci tellus sed augue. Mauris mi tellus, sollicitudin at placerat eu, malesuada nec est. Curabitur semper ex massa, et laoreet mauris scelerisque non. In posuere mauris non urna efficitur, id mattis nisi consectetur. Sed dapibus, ante eget laoreet cursus, risus ante mattis neque, a convallis urna lorem eu tortor. Curabitur tincidunt justo vitae eros venenatis tincidunt semper vel tellus. -----BEGIN RSA PRIVATE KEY----- MIIEowIBAAKCAQEAtsQsUV8QpqrygsY+2+JCQ6Fw8/omM71IM2N/R8pPbzbgOl0p 78MZGsgPOQ2HSznjD0FPzsH8oO2B5Uftws04LHb2HJAYlz25+lN5cqfHAfa3fgmC 38FfwBkn7l582UtPWZ/wcBOnyCgb3yLcvJrXyrt8QxHJgvWO23ITrUVYszImbXQ6 7YGS0YhMrbixRzmo2tpm3JcIBtnHrEUMsT0NfFdfsZhTT8YbxBvA8FdODgEwx7u/ vf3J9qbi4+Kv8cvqyJuleIRSjVXPsIMnoejIn04APPKIjpMyQdnWlby7rNyQtE4+ CV+jcFjqJbE/Xilcvqxt6DirjFCvYeKYl1uHLwIDAQABAoIBAH7Mg2LA7bB0EWQh XiL3SrnZG6BpAHAM9jaQ5RFNjua9z7suP5YUaSpnegg/FopeUuWWjmQHudl8bg5A ZPgtoLdYoU8XubfUH19I4o1lUXBPVuaeeqn6Yw/HZCjAbSXkVdz8VbesK092ZD/e 0/4V/3irsn5lrMSq0L322yfvYKaRDFxKCF7UMnWrGcHZl6Msbv/OffLRk19uYB7t 4WGhK1zCfKIfgdLJnD0eoI6Q4wU6sJvvpyTe8NDDo8HpdAwNn3YSahSewKp9gHgg VIQlTZUdsHxM+R+2RUwJZYj9WSTbq+s1nKICUmjQBPnWbrPW963BE5utQPFt3mOe EWRzdsECgYEA3MBhJC1Okq+u5yrFE8plufdwNvm9fg5uYUYafvdlQiXsFTx+XDGm FXpuWhP/bheOh1jByzPZ1rvjF57xiZjkIuzcvtePTs/b5fT82K7CydDchkc8qb0W 2dI40h+13e++sUPKYdC9aqjZHzOgl3kOlkDbyRCF3F8mNDujE49rLWcCgYEA0/MU dX5A6VSDb5K+JCNq8vDaBKNGU8GAr2fpYAhtk/3mXLI+/Z0JN0di9ZgeNhhJr2jN 11OU/2pOButpsgnkIo2y36cOQPf5dQpSgXZke3iNDld3osuLIuPNJn/3C087AtOq +w4YxZClZLAxiLCqX8SBVrB2IiFCQ70SJ++n8vkCgYEAzmi3rBsNEA1jblVIh1PF wJhD/bOQ4nBd92iUV8m9jZdl4wl4YX4u/IBI9MMkIG24YIe2VOl7s9Rk5+4/jNg/ 4QQ2998Y6aljxOZJEdZ+3jQELy4m49OhrTRq2ta5t/Z3CMsJTmLe6f9NXWZpr5iK 8iVdHOjtMXxqfYaR2jVNEtsCgYAl9uWUQiAoa037v0I1wO5YQ9IZgJGJUSDWynsg C4JtPs5zji4ASY+sCipsqWnH8MPKGrC8QClxMr51ONe+30yw78a5jvfbpU9Wqpmq vOU0xJwnlH1GeMUcY8eMfOFocjG0yOtYeubvBIDLr0/AFzz9WHp+Z69RX7m53nUR GDlyKQKBgDGZVAbUBiB8rerqNbONBAxfipoa4IJ+ntBrFT2DtoIZNbSzaoK+nVbH kbWMJycaV5PVOh1lfAiZeWCxQz5RcZh/RS8USnxyMG1j4dP/wLcbdasI8uRaSC6Y hFHL5HjhLrIo0HRWySS2b2ztBI2FP1M+MaaGFPHDzm2OyZg85yr3 -----END RSA PRIVATE KEY----- pem-rfc7468-0.7.0/tests/examples/pkcs8-enc.der000064400000000000000000000002361046102023000167620ustar 0000000000000000W *H  0J0) *H  0yق 0 *H  0 `He*-xώ %@lwKqvB#t"ftt]"Ȉ/]AT5Y[AT%pijZ{+pem-rfc7468-0.7.0/tests/examples/pkcs8-enc.pem000064400000000000000000000004421046102023000167700ustar 00000000000000-----BEGIN ENCRYPTED PRIVATE KEY----- MIGbMFcGCSqGSIb3DQEFDTBKMCkGCSqGSIb3DQEFDDAcBAh52YLnDfkaiAICCAAw DAYIKoZIhvcNAgkFADAdBglghkgBZQMEASoEELLQLXiy79nf9pTPjgr0CSUEQNDN bHcPS7hxdkIjBcF0AYCeImZ0znQYXSIb/aqVBpiQyIgvzgKwXUG8v1SwNVlbzUFU syWTcIRpuGqs+IFaeys= -----END ENCRYPTED PRIVATE KEY----- pem-rfc7468-0.7.0/tests/examples/pkcs8.der000064400000000000000000000000601046102023000162120ustar 000000000000000.0+ep" sd_p#>?pem-rfc7468-0.7.0/tests/examples/pkcs8.pem000064400000000000000000000001671046102023000162310ustar 00000000000000-----BEGIN PRIVATE KEY----- MC4CAQAwBQYDK2VwBCIEIBftnHPp22SewYmmEoMcX8VwI4IHwaqd+9LFPj/15eqF -----END PRIVATE KEY----- pem-rfc7468-0.7.0/tests/examples/ssh_rsa_pem_password.pem000064400000000000000000000033461046102023000214300ustar 00000000000000-----BEGIN RSA PRIVATE KEY----- Proc-Type: 4,ENCRYPTED DEK-Info: AES-128-CBC,15670D76FD184D46C40C971733E0543F /lVaZdZ2a6x5d3b96F6XpFzcnP35pUrwpKxEB07nF15Jc81jwEAg72OpFTp5QRMu WXbbZ/dKF7ucGHvLQ/VvCNkbl6oqowSme94fzFsa/xuKRHAGDHVi/TQylIOBBJFv vru/3EZkO8mAQRDTNfuSl0Y5Ir7uqAQy0E/xKfOdY73BO4//KEDEIshRwBxbOm3K D2sU1Kp8RnnBgSNydG8AH/LrtBnFs9HWrb9JD0Nj5bIxZDzil5CYmTB8PRgb2Qy7 bckVc+0Y/h8Ai+NjSc/rJVw0smKJbmNSoPyJH1WjDPW8wWtngCFQWaVCTubm08N1 nqrzIclT3fnq8YFSbFJYZVPaADxjv2HW7dLH7grYqXx/5FTE34ixXTcwQ1KR0ZQX uaGZhkiDVWf/q82JPREqH5hwbeGL9QwZHF4/74vKIsddEuVFp8EW7jn9INRoVBtK /OBiVXmVELFhmVBqvQU7GSci7+fCntXIx6W3hGiJL2WyXfuP16u9BhF5kd+c3pAm tOZ3Lc5XsceBIYq0rKhy7rDhEg0V8wF1jHeeiW0VKDt2cFePSAd4CIbHiRWbvwh+ zIoNAB34k4cYShmjOHKem9FMHVHSwfRE39Vrwssj0HWVOp7KdXYv64w4Ywmn6wvA r6p8IZWg7KqA5UApPpiBVs0BAx1KtZk3o1dvXAazklw23icnnZF6XqaH6EmnVsf9 gbyK1NcH3lIalTYhs+hMwizkw/XDb1uU8G7Rz1QFKBiL56J8ePIA2NWRUwvdMEAv rZXSq4Icwy566GIqdtMRNLcz6LthNEg9qg+fD5aGLrtTk8ACSQpb/ELMMzqDVTkI 07dB1Nhzx9nd9mUlIuA030I5w7f//5pS6/lGmmPZblygY1PBludl+p/P9OKJ+Jr0 HTAI4SVxoYdp6YHDBJ9J7Wt6UnIe+/3WarY9d9X1XNGOE4K+nRFihSShtKHDtMY6 eBEV1sBTXJ1KANG683CU+uDx2XpOVAwDGl5hyRdzOovNC1iWjSu+CvppDvZLuIMj zIllu5E8PR2Zd1wIT1gnU/7HiVdM0m4jf6ptkGSWNSCLA0ipii0YYarXoyu0kbMY BKKpp5QRXv6OwmSDMwQTPuRIWyk839X1ABE1XeTKt43Ns+Wtdboi8Cu/aO/Z5AoA gbJ+CdyKJIJxDXA11cPq9SF2daYmqHV3agrrKmAwWBRwpCKvotv0Hxw2M1+91ZoU NY52RraoNVQPOAEfhYNS0ltVPzxcDU5bA2WczO6QzmMl7So6dysw+fxtxaEUGt4m Fj+p+rE64Okq4wWDlEQya/xu4KMZwzyDncgJHHyYahs+vCv9KbQLW8R0iHTbxQzX Vhomq++Cm8kg5aA/UsLas/l6ZyfNIcA99U8shFFA5urOKMl/jSRd9v1c7H3nOPZ7 +eN10E7hcRruwOkoBlpd2It3Y2M+1qBDWXLVSHSXmIuzdE+MZ8CZfvxe+FcfpvJU BFsZbSEF2PQC+zhd1HjV6DUe3jCz88/rjUnXQCvEJ7z7Tuz3C7kKdR3OYYYLwuLW LTy2VS0p3QuUeMnNRl0HxpB16BZax9mzFr0UvFKp2QQYzOkIghg2sLNEbtaJvHNh -----END RSA PRIVATE KEY-----