diff options
author | Hasini Gunasinghe <hasinitg@google.com> | 2022-09-10 01:21:56 +0000 |
---|---|---|
committer | Hasini Gunasinghe <hasinitg@google.com> | 2022-10-03 21:21:01 +0000 |
commit | 238816042521dbf5897e4dfe87716b0b4a999e01 (patch) | |
tree | bf13c3231bd0b49a9b948e61ee9cb93cccc5f220 | |
parent | d8b568e586f5546ed7b157ebfa72bf6e34c38349 (diff) | |
download | pkcs1-238816042521dbf5897e4dfe87716b0b4a999e01.tar.gz |
Import platform/external/rust/crates/pkcs1
Bug: 239549209
Test: N/A
Change-Id: Ice7d2ca2fc7110a051a1cf07215a074e5862deba
33 files changed, 1749 insertions, 0 deletions
diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json new file mode 100644 index 0000000..c6845d7 --- /dev/null +++ b/.cargo_vcs_info.json @@ -0,0 +1,6 @@ +{ + "git": { + "sha1": "edbfb4db0ddfd1f95c7ed29ca480e4a93ec1737e" + }, + "path_in_vcs": "pkcs1" +}
\ No newline at end of file diff --git a/Android.bp b/Android.bp new file mode 100644 index 0000000..0a832ba --- /dev/null +++ b/Android.bp @@ -0,0 +1,28 @@ +// This file is generated by cargo2android.py --config cargo2android.json. +// Do not modify this file as changes will be overridden on upgrade. + + + +rust_library_host { + name: "libpkcs1", + crate_name: "pkcs1", + cargo_env_compat: true, + cargo_pkg_version: "0.4.0", + srcs: ["src/lib.rs"], + edition: "2021", + features: [ + "alloc", + "pkcs8", + "zeroize", + ], + rustlibs: [ + "libder", + "libpkcs8", + "libzeroize", + ], + apex_available: [ + "//apex_available:platform", + "com.android.virt", + ], + vendor_available: true, +} diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..e607ae7 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,100 @@ +# 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.4.0 (2022-05-08) +### Changed +- Replace document types with `doc::{Document, SecretDocument}` types ([#571]) +- Bump `der` to v0.6 ([#653]) +- Bump `pkcs8` to v0.9 ([#656]) + +[#571]: https://github.com/RustCrypto/formats/pull/571 +[#653]: https://github.com/RustCrypto/formats/pull/653 +[#656]: https://github.com/RustCrypto/formats/pull/656 + +## 0.3.3 (2022-01-16) +### Added +- Error conversion support to `pkcs8::spki::Error` ([#333]) + +[#333]: https://github.com/RustCrypto/formats/pull/331 + +## 0.3.2 (2022-01-16) +### Added +- Error conversion support to `pkcs8::Error` ([#331]) + +[#331]: https://github.com/RustCrypto/formats/pull/331 + +## 0.3.1 (2021-11-29) +### Changed +- Use `finish_non_exhaustive` in Debug impls ([#245]) + +[#245]: https://github.com/RustCrypto/formats/pull/245 + +## 0.3.0 (2021-11-17) +### Added +- Support for multi-prime RSA keys ([#115]) +- `pkcs8` feature ([#227], [#233]) + +### Changed +- Rename `From/ToRsa*Key` => `DecodeRsa*Key`/`EncodeRsa*Key` ([#120]) +- Use `der::Document` to impl `RsaPrivateKeyDocument` ([#131]) +- Rust 2021 edition upgrade; MSRV 1.56 ([#136]) +- Make `RsaPrivateKey::version` implicit ([#188]) +- Bump `der` crate dependency to v0.5 ([#222]) +- Activate `pkcs8/pem` when `pem` feature is enabled ([#232]) + +### Removed +- `*_with_le` PEM encoding methods ([#109]) +- I/O related errors ([#158]) + +[#109]: https://github.com/RustCrypto/formats/pull/109 +[#115]: https://github.com/RustCrypto/formats/pull/115 +[#120]: https://github.com/RustCrypto/formats/pull/120 +[#131]: https://github.com/RustCrypto/formats/pull/131 +[#136]: https://github.com/RustCrypto/formats/pull/136 +[#158]: https://github.com/RustCrypto/formats/pull/158 +[#188]: https://github.com/RustCrypto/formats/pull/188 +[#222]: https://github.com/RustCrypto/formats/pull/222 +[#227]: https://github.com/RustCrypto/formats/pull/227 +[#232]: https://github.com/RustCrypto/formats/pull/232 +[#233]: https://github.com/RustCrypto/formats/pull/233 + +## 0.2.4 (2021-09-14) +### Changed +- Moved to `formats` repo ([#2]) + +[#2]: https://github.com/RustCrypto/formats/pull/2 + +## 0.2.3 (2021-07-26) +### Added +- Support for customizing PEM `LineEnding` + +### Changed +- Bump `pem-rfc7468` dependency to v0.2 + +## 0.2.2 (2021-07-25) +### Fixed +- `Version` encoder + +## 0.2.1 (2021-07-25) +### Added +- `Error::Crypto` variant + +## 0.2.0 (2021-07-25) +### Added +- `From*`/`To*` traits for `RsaPrivateKey`/`RsaPublicKey` + +### Changed +- Use `FromRsa*`/`ToRsa*` traits with `*Document` types + +## 0.1.1 (2021-07-24) +### Added +- Re-export `der` crate and `der::UIntBytes` + +### Changed +- Replace `Error::{Decode, Encode}` with `Error::Asn1` + +## 0.1.0 (2021-07-24) [YANKED] +- Initial release diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..e193698 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,82 @@ +# 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.57" +name = "pkcs1" +version = "0.4.0" +authors = ["RustCrypto Developers"] +description = """ +Pure Rust implementation of Public-Key Cryptography Standards (PKCS) #1: +RSA Cryptography Specifications Version 2.2 (RFC 8017) +""" +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/pkcs1" +resolver = "2" + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = [ + "--cfg", + "docsrs", +] + +[dependencies.der] +version = "0.6" +features = ["oid"] + +[dependencies.pkcs8] +version = "0.9" +optional = true +default-features = false + +[dependencies.zeroize] +version = "1" +optional = true +default-features = false + +[dev-dependencies.hex-literal] +version = "0.3" + +[dev-dependencies.tempfile] +version = "3" + +[features] +alloc = [ + "der/alloc", + "pkcs8/alloc", + "zeroize/alloc", +] +pem = [ + "alloc", + "der/pem", + "pkcs8/pem", +] +std = [ + "der/std", + "alloc", +] diff --git a/Cargo.toml.orig b/Cargo.toml.orig new file mode 100644 index 0000000..00f6414 --- /dev/null +++ b/Cargo.toml.orig @@ -0,0 +1,35 @@ +[package] +name = "pkcs1" +version = "0.4.0" # Also update html_root_url in lib.rs when bumping this +description = """ +Pure Rust implementation of Public-Key Cryptography Standards (PKCS) #1: +RSA Cryptography Specifications Version 2.2 (RFC 8017) +""" +authors = ["RustCrypto Developers"] +license = "Apache-2.0 OR MIT" +repository = "https://github.com/RustCrypto/formats/tree/master/pkcs1" +categories = ["cryptography", "data-structures", "encoding", "no-std", "parser-implementations"] +keywords = ["crypto", "key", "pem", "pkcs", "rsa"] +readme = "README.md" +edition = "2021" +rust-version = "1.57" + +[dependencies] +der = { version = "0.6", features = ["oid"], path = "../der" } + +# optional dependencies +pkcs8 = { version = "0.9", optional = true, default-features = false, path = "../pkcs8" } +zeroize = { version = "1", optional = true, default-features = false } + +[dev-dependencies] +hex-literal = "0.3" +tempfile = "3" + +[features] +alloc = ["der/alloc", "pkcs8/alloc", "zeroize/alloc"] +pem = ["alloc", "der/pem", "pkcs8/pem"] +std = ["der/std", "alloc"] + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs"] @@ -0,0 +1 @@ +LICENSE-APACHE
\ No newline at end of file diff --git a/LICENSE-APACHE b/LICENSE-APACHE new file mode 100644 index 0000000..78173fa --- /dev/null +++ b/LICENSE-APACHE @@ -0,0 +1,201 @@ + 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. diff --git a/METADATA b/METADATA new file mode 100644 index 0000000..a0641dd --- /dev/null +++ b/METADATA @@ -0,0 +1,20 @@ +name: "pkcs1" +description: "Pure Rust implementation of Public-Key Cryptography Standards (PKCS) #1: RSA Cryptography Specifications Version 2.2 (RFC 8017)." +third_party { + url { + type: HOMEPAGE + value: "https://crates.io/crates/pkcs1" + } + url { + type: ARCHIVE + value: "https://static.crates.io/crates/pkcs1/pkcs1-0.4.0.crate" + } + version: "0.4.0" + # Dual-licensed, using the least restrictive per go/thirdpartylicenses#same. + license_type: NOTICE + last_upgrade_date { + year: 2022 + month: 9 + day: 6 + } +} diff --git a/MODULE_LICENSE_APACHE2 b/MODULE_LICENSE_APACHE2 new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/MODULE_LICENSE_APACHE2 @@ -0,0 +1 @@ +include platform/prebuilts/rust:master:/OWNERS diff --git a/README.md b/README.md new file mode 100644 index 0000000..8342e20 --- /dev/null +++ b/README.md @@ -0,0 +1,70 @@ +# [RustCrypto]: PKCS#1 (RSA) + +[![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 Public-Key Cryptography Standards (PKCS) #1: +RSA Cryptography Specifications Version 2.2 ([RFC 8017]). + +[Documentation][docs-link] + +## About + +This crate supports encoding and decoding RSA private and public keys +in either PKCS#1 DER (binary) or PEM (text) formats. + +PEM encoded RSA private keys begin with: + +```text +-----BEGIN RSA PRIVATE KEY----- +``` + +PEM encoded RSA public keys begin with: + +```text +-----BEGIN RSA PUBLIC KEY----- +``` + +## Minimum Supported Rust Version + +This crate requires **Rust 1.57** 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/pkcs1 +[crate-link]: https://crates.io/crates/pkcs1 +[docs-image]: https://docs.rs/pkcs1/badge.svg +[docs-link]: https://docs.rs/pkcs1/ +[license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg +[rustc-image]: https://img.shields.io/badge/rustc-1.57+-blue.svg +[chat-image]: https://img.shields.io/badge/zulip-join_chat-blue.svg +[chat-link]: https://rustcrypto.zulipchat.com/#narrow/stream/300570-formats +[build-image]: https://github.com/RustCrypto/formats/workflows/pkcs1/badge.svg?branch=master&event=push +[build-link]: https://github.com/RustCrypto/formats/actions + +[//]: # (links) + +[RustCrypto]: https://github.com/rustcrypto +[RFC 8017]: https://datatracker.ietf.org/doc/html/rfc8017 diff --git a/cargo2android.json b/cargo2android.json new file mode 100644 index 0000000..e26e552 --- /dev/null +++ b/cargo2android.json @@ -0,0 +1,9 @@ +{ + "apex-available": [ + "//apex_available:platform", + "com.android.virt" + ], + "run": true, + "vendor-available": true, + "features": "alloc" +} diff --git a/patches/std.diff b/patches/std.diff new file mode 100644 index 0000000..abd6960 --- /dev/null +++ b/patches/std.diff @@ -0,0 +1,15 @@ +diff --git a/src/lib.rs b/src/lib.rs +index 8b51d17..edbb128 100644 +--- a/src/lib.rs ++++ b/src/lib.rs +@@ -9,6 +9,10 @@ + #![forbid(unsafe_code, clippy::unwrap_used)] + #![warn(missing_docs, rust_2018_idioms, unused_qualifications)] + ++/// Local Android change: Use std to allow building as a dylib. ++#[cfg(android_dylib)] ++extern crate std; ++ + #[cfg(feature = "alloc")] + extern crate alloc; + #[cfg(feature = "std")] diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..135bcd7 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,95 @@ +//! Error types + +use core::fmt; + +#[cfg(feature = "pem")] +use der::pem; + +/// Result type +pub type Result<T> = core::result::Result<T, Error>; + +/// Error type +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +#[non_exhaustive] +pub enum Error { + /// ASN.1 DER-related errors. + Asn1(der::Error), + + /// Cryptographic errors. + /// + /// These can be used by RSA implementations to signal that a key is + /// invalid for cryptographic reasons. This means the document parsed + /// correctly, but one of the values contained within was invalid, e.g. + /// a number expected to be a prime was not a prime. + Crypto, + + /// PKCS#8 errors. + #[cfg(feature = "pkcs8")] + Pkcs8(pkcs8::Error), + + /// Version errors + Version, +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Error::Asn1(err) => write!(f, "PKCS#1 ASN.1 error: {}", err), + Error::Crypto => f.write_str("PKCS#1 cryptographic error"), + #[cfg(feature = "pkcs8")] + Error::Pkcs8(err) => write!(f, "{}", err), + Error::Version => f.write_str("PKCS#1 version error"), + } + } +} + +impl From<der::Error> for Error { + fn from(err: der::Error) -> Error { + Error::Asn1(err) + } +} + +#[cfg(feature = "pem")] +impl From<pem::Error> for Error { + fn from(err: pem::Error) -> Error { + der::Error::from(err).into() + } +} + +#[cfg(feature = "pkcs8")] +impl From<Error> for pkcs8::Error { + fn from(err: Error) -> pkcs8::Error { + match err { + Error::Asn1(e) => pkcs8::Error::Asn1(e), + Error::Crypto | Error::Version => pkcs8::Error::KeyMalformed, + Error::Pkcs8(e) => e, + } + } +} + +#[cfg(feature = "pkcs8")] +impl From<pkcs8::Error> for Error { + fn from(err: pkcs8::Error) -> Error { + Error::Pkcs8(err) + } +} + +#[cfg(feature = "pkcs8")] +impl From<Error> for pkcs8::spki::Error { + fn from(err: Error) -> pkcs8::spki::Error { + match err { + Error::Asn1(e) => pkcs8::spki::Error::Asn1(e), + _ => pkcs8::spki::Error::KeyMalformed, + } + } +} + +#[cfg(feature = "pkcs8")] +impl From<pkcs8::spki::Error> for Error { + fn from(err: pkcs8::spki::Error) -> Error { + Error::Pkcs8(pkcs8::Error::PublicKey(err)) + } +} + +#[cfg(feature = "std")] +impl std::error::Error for Error {} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..edbb128 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,61 @@ +#![no_std] +#![cfg_attr(docsrs, feature(doc_cfg))] +#![doc = include_str!("../README.md")] +#![doc( + html_logo_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo.svg", + html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo.svg", + html_root_url = "https://docs.rs/pkcs1/0.4.0-pre" +)] +#![forbid(unsafe_code, clippy::unwrap_used)] +#![warn(missing_docs, rust_2018_idioms, unused_qualifications)] + +/// Local Android change: Use std to allow building as a dylib. +#[cfg(android_dylib)] +extern crate std; + +#[cfg(feature = "alloc")] +extern crate alloc; +#[cfg(feature = "std")] +extern crate std; + +mod error; +mod private_key; +mod public_key; +mod traits; +mod version; + +pub use der::{ + self, + asn1::{ObjectIdentifier, UIntRef}, +}; + +pub use self::{ + error::{Error, Result}, + private_key::RsaPrivateKey, + public_key::RsaPublicKey, + traits::{DecodeRsaPrivateKey, DecodeRsaPublicKey}, + version::Version, +}; + +#[cfg(feature = "alloc")] +pub use crate::{ + private_key::{other_prime_info::OtherPrimeInfo, OtherPrimeInfos}, + traits::{EncodeRsaPrivateKey, EncodeRsaPublicKey}, +}; + +#[cfg(feature = "pem")] +#[cfg_attr(docsrs, doc(cfg(feature = "pem")))] +pub use der::pem::{self, LineEnding}; + +/// `rsaEncryption` Object Identifier (OID) +#[cfg(feature = "pkcs8")] +#[cfg_attr(docsrs, doc(cfg(feature = "pkcs8")))] +pub const ALGORITHM_OID: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.2.840.113549.1.1.1"); + +/// `AlgorithmIdentifier` for RSA. +#[cfg(feature = "pkcs8")] +#[cfg_attr(docsrs, doc(cfg(feature = "pkcs8")))] +pub const ALGORITHM_ID: pkcs8::AlgorithmIdentifier<'static> = pkcs8::AlgorithmIdentifier { + oid: ALGORITHM_OID, + parameters: Some(der::asn1::AnyRef::NULL), +}; diff --git a/src/private_key.rs b/src/private_key.rs new file mode 100644 index 0000000..043ed02 --- /dev/null +++ b/src/private_key.rs @@ -0,0 +1,220 @@ +//! PKCS#1 RSA Private Keys. + +#[cfg(feature = "alloc")] +pub(crate) mod other_prime_info; + +use crate::{Error, Result, RsaPublicKey, Version}; +use core::fmt; +use der::{asn1::UIntRef, Decode, DecodeValue, Encode, Header, Reader, Sequence, Tag}; + +#[cfg(feature = "alloc")] +use {self::other_prime_info::OtherPrimeInfo, alloc::vec::Vec, der::SecretDocument}; + +#[cfg(feature = "pem")] +use der::pem::PemLabel; + +/// PKCS#1 RSA Private Keys as defined in [RFC 8017 Appendix 1.2]. +/// +/// ASN.1 structure containing a serialized RSA private key: +/// +/// ```text +/// RSAPrivateKey ::= SEQUENCE { +/// version Version, +/// modulus INTEGER, -- n +/// publicExponent INTEGER, -- e +/// privateExponent INTEGER, -- d +/// prime1 INTEGER, -- p +/// prime2 INTEGER, -- q +/// exponent1 INTEGER, -- d mod (p-1) +/// exponent2 INTEGER, -- d mod (q-1) +/// coefficient INTEGER, -- (inverse of q) mod p +/// otherPrimeInfos OtherPrimeInfos OPTIONAL +/// } +/// ``` +/// +/// Note: the `version` field is selected automatically based on the absence or +/// presence of the `other_prime_infos` field. +/// +/// [RFC 8017 Appendix 1.2]: https://datatracker.ietf.org/doc/html/rfc8017#appendix-A.1.2 +#[derive(Clone)] +pub struct RsaPrivateKey<'a> { + /// `n`: RSA modulus. + pub modulus: UIntRef<'a>, + + /// `e`: RSA public exponent. + pub public_exponent: UIntRef<'a>, + + /// `d`: RSA private exponent. + pub private_exponent: UIntRef<'a>, + + /// `p`: first prime factor of `n`. + pub prime1: UIntRef<'a>, + + /// `q`: Second prime factor of `n`. + pub prime2: UIntRef<'a>, + + /// First exponent: `d mod (p-1)`. + pub exponent1: UIntRef<'a>, + + /// Second exponent: `d mod (q-1)`. + pub exponent2: UIntRef<'a>, + + /// CRT coefficient: `(inverse of q) mod p`. + pub coefficient: UIntRef<'a>, + + /// Additional primes `r_3`, ..., `r_u`, in order, if this is a multi-prime + /// RSA key (i.e. `version` is `multi`). + pub other_prime_infos: Option<OtherPrimeInfos<'a>>, +} + +impl<'a> RsaPrivateKey<'a> { + /// Get the public key that corresponds to this [`RsaPrivateKey`]. + pub fn public_key(&self) -> RsaPublicKey<'a> { + RsaPublicKey { + modulus: self.modulus, + public_exponent: self.public_exponent, + } + } + + /// Get the [`Version`] for this key. + /// + /// Determined by the presence or absence of the + /// [`RsaPrivateKey::other_prime_infos`] field. + pub fn version(&self) -> Version { + if self.other_prime_infos.is_some() { + Version::Multi + } else { + Version::TwoPrime + } + } +} + +impl<'a> DecodeValue<'a> for RsaPrivateKey<'a> { + fn decode_value<R: Reader<'a>>(reader: &mut R, header: Header) -> der::Result<Self> { + reader.read_nested(header.length, |reader| { + let version = Version::decode(reader)?; + + let result = Self { + modulus: reader.decode()?, + public_exponent: reader.decode()?, + private_exponent: reader.decode()?, + prime1: reader.decode()?, + prime2: reader.decode()?, + exponent1: reader.decode()?, + exponent2: reader.decode()?, + coefficient: reader.decode()?, + other_prime_infos: reader.decode()?, + }; + + // Ensure version is set correctly for two-prime vs multi-prime key. + if version.is_multi() != result.other_prime_infos.is_some() { + return Err(reader.error(der::ErrorKind::Value { tag: Tag::Integer })); + } + + Ok(result) + }) + } +} + +impl<'a> Sequence<'a> for RsaPrivateKey<'a> { + fn fields<F, T>(&self, f: F) -> der::Result<T> + where + F: FnOnce(&[&dyn Encode]) -> der::Result<T>, + { + f(&[ + &self.version(), + &self.modulus, + &self.public_exponent, + &self.private_exponent, + &self.prime1, + &self.prime2, + &self.exponent1, + &self.exponent2, + &self.coefficient, + #[cfg(feature = "alloc")] + &self.other_prime_infos, + ]) + } +} + +impl<'a> From<RsaPrivateKey<'a>> for RsaPublicKey<'a> { + fn from(private_key: RsaPrivateKey<'a>) -> RsaPublicKey<'a> { + private_key.public_key() + } +} + +impl<'a> From<&RsaPrivateKey<'a>> for RsaPublicKey<'a> { + fn from(private_key: &RsaPrivateKey<'a>) -> RsaPublicKey<'a> { + private_key.public_key() + } +} + +impl<'a> TryFrom<&'a [u8]> for RsaPrivateKey<'a> { + type Error = Error; + + fn try_from(bytes: &'a [u8]) -> Result<Self> { + Ok(Self::from_der(bytes)?) + } +} + +impl fmt::Debug for RsaPrivateKey<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("RsaPrivateKey") + .field("version", &self.version()) + .field("modulus", &self.modulus) + .field("public_exponent", &self.public_exponent) + .finish_non_exhaustive() + } +} + +#[cfg(feature = "alloc")] +#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] +impl TryFrom<RsaPrivateKey<'_>> for SecretDocument { + type Error = Error; + + fn try_from(private_key: RsaPrivateKey<'_>) -> Result<SecretDocument> { + SecretDocument::try_from(&private_key) + } +} + +#[cfg(feature = "alloc")] +#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] +impl TryFrom<&RsaPrivateKey<'_>> for SecretDocument { + type Error = Error; + + fn try_from(private_key: &RsaPrivateKey<'_>) -> Result<SecretDocument> { + Ok(Self::encode_msg(private_key)?) + } +} + +#[cfg(feature = "pem")] +#[cfg_attr(docsrs, doc(cfg(feature = "pem")))] +impl PemLabel for RsaPrivateKey<'_> { + const PEM_LABEL: &'static str = "RSA PRIVATE KEY"; +} + +/// Placeholder struct for `OtherPrimeInfos` in the no-`alloc` case. +#[cfg(not(feature = "alloc"))] +#[derive(Clone)] +#[non_exhaustive] +pub struct OtherPrimeInfos<'a> { + _lifetime: core::marker::PhantomData<&'a ()>, +} + +#[cfg(not(feature = "alloc"))] +impl<'a> Decode<'a> for OtherPrimeInfos<'a> { + fn decode<R: Reader<'a>>(reader: &mut R) -> der::Result<Self> { + // Placeholder decoder that always returns an error. + // Use `Tag::Integer` to signal an unsupported version. + Err(reader.error(der::ErrorKind::Value { tag: Tag::Integer })) + } +} + +#[cfg(not(feature = "alloc"))] +impl<'a> der::FixedTag for OtherPrimeInfos<'a> { + const TAG: Tag = Tag::Sequence; +} + +/// Additional RSA prime info in a multi-prime RSA key. +#[cfg(feature = "alloc")] +pub type OtherPrimeInfos<'a> = Vec<OtherPrimeInfo<'a>>; diff --git a/src/private_key/other_prime_info.rs b/src/private_key/other_prime_info.rs new file mode 100644 index 0000000..8980aa1 --- /dev/null +++ b/src/private_key/other_prime_info.rs @@ -0,0 +1,50 @@ +//! PKCS#1 OtherPrimeInfo support. + +use der::{asn1::UIntRef, DecodeValue, Encode, Header, Reader, Sequence}; + +/// PKCS#1 OtherPrimeInfo as defined in [RFC 8017 Appendix 1.2]. +/// +/// ASN.1 structure containing an additional prime in a multi-prime RSA key. +/// +/// ```text +/// OtherPrimeInfo ::= SEQUENCE { +/// prime INTEGER, -- ri +/// exponent INTEGER, -- di +/// coefficient INTEGER -- ti +/// } +/// ``` +/// +/// [RFC 8017 Appendix 1.2]: https://datatracker.ietf.org/doc/html/rfc8017#appendix-A.1.2 +#[derive(Clone)] +#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] +pub struct OtherPrimeInfo<'a> { + /// Prime factor `r_i` of `n`, where `i` >= 3. + pub prime: UIntRef<'a>, + + /// Exponent: `d_i = d mod (r_i - 1)`. + pub exponent: UIntRef<'a>, + + /// CRT coefficient: `t_i = (r_1 * r_2 * ... * r_(i-1))^(-1) mod r_i`. + pub coefficient: UIntRef<'a>, +} + +impl<'a> DecodeValue<'a> for OtherPrimeInfo<'a> { + fn decode_value<R: Reader<'a>>(reader: &mut R, header: Header) -> der::Result<Self> { + reader.read_nested(header.length, |reader| { + Ok(Self { + prime: reader.decode()?, + exponent: reader.decode()?, + coefficient: reader.decode()?, + }) + }) + } +} + +impl<'a> Sequence<'a> for OtherPrimeInfo<'a> { + fn fields<F, T>(&self, f: F) -> der::Result<T> + where + F: FnOnce(&[&dyn Encode]) -> der::Result<T>, + { + f(&[&self.prime, &self.exponent, &self.coefficient]) + } +} diff --git a/src/public_key.rs b/src/public_key.rs new file mode 100644 index 0000000..b6b8c87 --- /dev/null +++ b/src/public_key.rs @@ -0,0 +1,85 @@ +//! PKCS#1 RSA Public Keys. + +use crate::{Error, Result}; +use der::{asn1::UIntRef, Decode, DecodeValue, Encode, Header, Reader, Sequence}; + +#[cfg(feature = "alloc")] +use der::Document; + +#[cfg(feature = "pem")] +use der::pem::PemLabel; + +/// PKCS#1 RSA Public Keys as defined in [RFC 8017 Appendix 1.1]. +/// +/// ASN.1 structure containing a serialized RSA public key: +/// +/// ```text +/// RSAPublicKey ::= SEQUENCE { +/// modulus INTEGER, -- n +/// publicExponent INTEGER -- e +/// } +/// ``` +/// +/// [RFC 8017 Appendix 1.1]: https://datatracker.ietf.org/doc/html/rfc8017#appendix-A.1.1 +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub struct RsaPublicKey<'a> { + /// `n`: RSA modulus + pub modulus: UIntRef<'a>, + + /// `e`: RSA public exponent + pub public_exponent: UIntRef<'a>, +} + +impl<'a> DecodeValue<'a> for RsaPublicKey<'a> { + fn decode_value<R: Reader<'a>>(reader: &mut R, header: Header) -> der::Result<Self> { + reader.read_nested(header.length, |reader| { + Ok(Self { + modulus: reader.decode()?, + public_exponent: reader.decode()?, + }) + }) + } +} + +impl<'a> Sequence<'a> for RsaPublicKey<'a> { + fn fields<F, T>(&self, f: F) -> der::Result<T> + where + F: FnOnce(&[&dyn Encode]) -> der::Result<T>, + { + f(&[&self.modulus, &self.public_exponent]) + } +} + +impl<'a> TryFrom<&'a [u8]> for RsaPublicKey<'a> { + type Error = Error; + + fn try_from(bytes: &'a [u8]) -> Result<Self> { + Ok(Self::from_der(bytes)?) + } +} + +#[cfg(feature = "alloc")] +#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] +impl TryFrom<RsaPublicKey<'_>> for Document { + type Error = Error; + + fn try_from(spki: RsaPublicKey<'_>) -> Result<Document> { + Self::try_from(&spki) + } +} + +#[cfg(feature = "alloc")] +#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] +impl TryFrom<&RsaPublicKey<'_>> for Document { + type Error = Error; + + fn try_from(spki: &RsaPublicKey<'_>) -> Result<Document> { + Ok(Self::encode_msg(spki)?) + } +} + +#[cfg(feature = "pem")] +#[cfg_attr(docsrs, doc(cfg(feature = "pem")))] +impl PemLabel for RsaPublicKey<'_> { + const PEM_LABEL: &'static str = "RSA PUBLIC KEY"; +} diff --git a/src/traits.rs b/src/traits.rs new file mode 100644 index 0000000..edf06c1 --- /dev/null +++ b/src/traits.rs @@ -0,0 +1,213 @@ +//! Traits for parsing objects from PKCS#1 encoded documents + +use crate::Result; + +#[cfg(feature = "alloc")] +use { + crate::{RsaPrivateKey, RsaPublicKey}, + der::SecretDocument, +}; + +#[cfg(feature = "pem")] +use { + crate::LineEnding, + alloc::string::String, + der::{pem::PemLabel, zeroize::Zeroizing}, +}; + +#[cfg(feature = "pkcs8")] +use crate::{ALGORITHM_ID, ALGORITHM_OID}; + +#[cfg(feature = "std")] +use std::path::Path; + +#[cfg(all(feature = "alloc", feature = "pkcs8"))] +use der::{Decode, Document}; + +/// Parse an [`RsaPrivateKey`] from a PKCS#1-encoded document. +pub trait DecodeRsaPrivateKey: Sized { + /// Deserialize PKCS#1 private key from ASN.1 DER-encoded data + /// (binary format). + fn from_pkcs1_der(bytes: &[u8]) -> Result<Self>; + + /// Deserialize PKCS#1-encoded private key from PEM. + /// + /// Keys in this format begin with the following: + /// + /// ```text + /// -----BEGIN RSA PRIVATE KEY----- + /// ``` + #[cfg(feature = "pem")] + #[cfg_attr(docsrs, doc(cfg(feature = "pem")))] + fn from_pkcs1_pem(s: &str) -> Result<Self> { + let (label, doc) = SecretDocument::from_pem(s)?; + RsaPrivateKey::validate_pem_label(label)?; + Self::from_pkcs1_der(doc.as_bytes()) + } + + /// Load PKCS#1 private key from an ASN.1 DER-encoded file on the local + /// filesystem (binary format). + #[cfg(feature = "std")] + #[cfg_attr(docsrs, doc(cfg(feature = "std")))] + fn read_pkcs1_der_file(path: impl AsRef<Path>) -> Result<Self> { + Self::from_pkcs1_der(SecretDocument::read_der_file(path)?.as_bytes()) + } + + /// Load PKCS#1 private key from a PEM-encoded file on the local filesystem. + #[cfg(all(feature = "pem", feature = "std"))] + #[cfg_attr(docsrs, doc(cfg(feature = "pem")))] + #[cfg_attr(docsrs, doc(cfg(feature = "std")))] + fn read_pkcs1_pem_file(path: impl AsRef<Path>) -> Result<Self> { + let (label, doc) = SecretDocument::read_pem_file(path)?; + RsaPrivateKey::validate_pem_label(&label)?; + Self::from_pkcs1_der(doc.as_bytes()) + } +} + +/// Parse a [`RsaPublicKey`] from a PKCS#1-encoded document. +pub trait DecodeRsaPublicKey: Sized { + /// Deserialize object from ASN.1 DER-encoded [`RsaPublicKey`] + /// (binary format). + fn from_pkcs1_der(bytes: &[u8]) -> Result<Self>; + + /// Deserialize PEM-encoded [`RsaPublicKey`]. + /// + /// Keys in this format begin with the following: + /// + /// ```text + /// -----BEGIN RSA PUBLIC KEY----- + /// ``` + #[cfg(feature = "pem")] + #[cfg_attr(docsrs, doc(cfg(feature = "pem")))] + fn from_pkcs1_pem(s: &str) -> Result<Self> { + let (label, doc) = Document::from_pem(s)?; + RsaPublicKey::validate_pem_label(label)?; + Self::from_pkcs1_der(doc.as_bytes()) + } + + /// Load [`RsaPublicKey`] from an ASN.1 DER-encoded file on the local + /// filesystem (binary format). + #[cfg(feature = "std")] + #[cfg_attr(docsrs, doc(cfg(feature = "std")))] + fn read_pkcs1_der_file(path: impl AsRef<Path>) -> Result<Self> { + let doc = Document::read_der_file(path)?; + Self::from_pkcs1_der(doc.as_bytes()) + } + + /// Load [`RsaPublicKey`] from a PEM-encoded file on the local filesystem. + #[cfg(all(feature = "pem", feature = "std"))] + #[cfg_attr(docsrs, doc(cfg(feature = "pem")))] + #[cfg_attr(docsrs, doc(cfg(feature = "std")))] + fn read_pkcs1_pem_file(path: impl AsRef<Path>) -> Result<Self> { + let (label, doc) = Document::read_pem_file(path)?; + RsaPublicKey::validate_pem_label(&label)?; + Self::from_pkcs1_der(doc.as_bytes()) + } +} + +/// Serialize a [`RsaPrivateKey`] to a PKCS#1 encoded document. +#[cfg(feature = "alloc")] +#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] +pub trait EncodeRsaPrivateKey { + /// Serialize a [`SecretDocument`] containing a PKCS#1-encoded private key. + fn to_pkcs1_der(&self) -> Result<SecretDocument>; + + /// Serialize this private key as PEM-encoded PKCS#1 with the given [`LineEnding`]. + #[cfg(feature = "pem")] + #[cfg_attr(docsrs, doc(cfg(feature = "pem")))] + fn to_pkcs1_pem(&self, line_ending: LineEnding) -> Result<Zeroizing<String>> { + let doc = self.to_pkcs1_der()?; + Ok(doc.to_pem(RsaPrivateKey::PEM_LABEL, line_ending)?) + } + + /// Write ASN.1 DER-encoded PKCS#1 private key to the given path. + #[cfg(feature = "std")] + #[cfg_attr(docsrs, doc(cfg(feature = "std")))] + fn write_pkcs1_der_file(&self, path: impl AsRef<Path>) -> Result<()> { + Ok(self.to_pkcs1_der()?.write_der_file(path)?) + } + + /// Write ASN.1 DER-encoded PKCS#1 private key to the given path. + #[cfg(all(feature = "pem", feature = "std"))] + #[cfg_attr(docsrs, doc(cfg(all(feature = "pem", feature = "std"))))] + fn write_pkcs1_pem_file(&self, path: impl AsRef<Path>, line_ending: LineEnding) -> Result<()> { + let doc = self.to_pkcs1_der()?; + Ok(doc.write_pem_file(path, RsaPrivateKey::PEM_LABEL, line_ending)?) + } +} + +/// Serialize a [`RsaPublicKey`] to a PKCS#1-encoded document. +#[cfg(feature = "alloc")] +#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] +pub trait EncodeRsaPublicKey { + /// Serialize a [`Document`] containing a PKCS#1-encoded public key. + fn to_pkcs1_der(&self) -> Result<Document>; + + /// Serialize this public key as PEM-encoded PKCS#1 with the given line ending. + #[cfg(feature = "pem")] + #[cfg_attr(docsrs, doc(cfg(feature = "pem")))] + fn to_pkcs1_pem(&self, line_ending: LineEnding) -> Result<String> { + let doc = self.to_pkcs1_der()?; + Ok(doc.to_pem(RsaPublicKey::PEM_LABEL, line_ending)?) + } + + /// Write ASN.1 DER-encoded public key to the given path. + #[cfg(feature = "std")] + #[cfg_attr(docsrs, doc(cfg(feature = "std")))] + fn write_pkcs1_der_file(&self, path: impl AsRef<Path>) -> Result<()> { + Ok(self.to_pkcs1_der()?.write_der_file(path)?) + } + + /// Write ASN.1 DER-encoded public key to the given path. + #[cfg(all(feature = "pem", feature = "std"))] + #[cfg_attr(docsrs, doc(cfg(all(feature = "pem", feature = "std"))))] + fn write_pkcs1_pem_file(&self, path: impl AsRef<Path>, line_ending: LineEnding) -> Result<()> { + let doc = self.to_pkcs1_der()?; + Ok(doc.write_pem_file(path, RsaPublicKey::PEM_LABEL, line_ending)?) + } +} + +#[cfg(feature = "pkcs8")] +#[cfg_attr(docsrs, doc(cfg(feature = "pkcs8")))] +impl<T: pkcs8::DecodePrivateKey> DecodeRsaPrivateKey for T { + fn from_pkcs1_der(private_key: &[u8]) -> Result<Self> { + Ok(Self::try_from(pkcs8::PrivateKeyInfo { + algorithm: ALGORITHM_ID, + private_key, + public_key: None, + })?) + } +} + +#[cfg(feature = "pkcs8")] +#[cfg_attr(docsrs, doc(cfg(feature = "pkcs8")))] +impl<T: pkcs8::DecodePublicKey> DecodeRsaPublicKey for T { + fn from_pkcs1_der(public_key: &[u8]) -> Result<Self> { + Ok(Self::try_from(pkcs8::SubjectPublicKeyInfo { + algorithm: ALGORITHM_ID, + subject_public_key: public_key, + })?) + } +} + +#[cfg(all(feature = "alloc", feature = "pkcs8"))] +#[cfg_attr(docsrs, doc(all(feature = "alloc", feature = "pkcs8")))] +impl<T: pkcs8::EncodePrivateKey> EncodeRsaPrivateKey for T { + fn to_pkcs1_der(&self) -> Result<SecretDocument> { + let pkcs8_doc = self.to_pkcs8_der()?; + let pkcs8_key = pkcs8::PrivateKeyInfo::from_der(pkcs8_doc.as_bytes())?; + pkcs8_key.algorithm.assert_algorithm_oid(ALGORITHM_OID)?; + RsaPrivateKey::from_der(pkcs8_key.private_key)?.try_into() + } +} + +#[cfg(all(feature = "alloc", feature = "pkcs8"))] +#[cfg_attr(docsrs, doc(all(feature = "alloc", feature = "pkcs8")))] +impl<T: pkcs8::EncodePublicKey> EncodeRsaPublicKey for T { + fn to_pkcs1_der(&self) -> Result<Document> { + let doc = self.to_public_key_der()?; + let spki = pkcs8::SubjectPublicKeyInfo::from_der(doc.as_bytes())?; + spki.algorithm.assert_algorithm_oid(ALGORITHM_OID)?; + RsaPublicKey::from_der(spki.subject_public_key)?.try_into() + } +} diff --git a/src/version.rs b/src/version.rs new file mode 100644 index 0000000..6b253e8 --- /dev/null +++ b/src/version.rs @@ -0,0 +1,72 @@ +//! PKCS#1 version identifier. + +use crate::Error; +use der::{Decode, Encode, FixedTag, Reader, Tag, Writer}; + +/// Version identifier for PKCS#1 documents as defined in +/// [RFC 8017 Appendix 1.2]. +/// +/// > version is the version number, for compatibility with future +/// > revisions of this document. It SHALL be 0 for this version of the +/// > document, unless multi-prime is used; in which case, it SHALL be 1. +/// +/// ```text +/// Version ::= INTEGER { two-prime(0), multi(1) } +/// (CONSTRAINED BY +/// {-- version must be multi if otherPrimeInfos present --}) +/// ``` +/// +/// [RFC 8017 Appendix 1.2]: https://datatracker.ietf.org/doc/html/rfc8017#appendix-A.1.2 +#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] +#[repr(u8)] +pub enum Version { + /// Denotes a `two-prime` key + TwoPrime = 0, + + /// Denotes a `multi` (i.e. multi-prime) key + Multi = 1, +} + +impl Version { + /// Is this a multi-prime RSA key? + pub fn is_multi(self) -> bool { + self == Self::Multi + } +} + +impl From<Version> for u8 { + fn from(version: Version) -> Self { + version as u8 + } +} + +impl TryFrom<u8> for Version { + type Error = Error; + fn try_from(byte: u8) -> Result<Version, Error> { + match byte { + 0 => Ok(Version::TwoPrime), + 1 => Ok(Version::Multi), + _ => Err(Error::Version), + } + } +} + +impl<'a> Decode<'a> for Version { + fn decode<R: Reader<'a>>(decoder: &mut R) -> der::Result<Self> { + Version::try_from(u8::decode(decoder)?).map_err(|_| Self::TAG.value_error()) + } +} + +impl Encode for Version { + fn encoded_len(&self) -> der::Result<der::Length> { + der::Length::ONE.for_tlv() + } + + fn encode(&self, writer: &mut dyn Writer) -> der::Result<()> { + u8::from(*self).encode(writer) + } +} + +impl FixedTag for Version { + const TAG: Tag = Tag::Integer; +} diff --git a/tests/examples/rsa2048-priv-3prime.der b/tests/examples/rsa2048-priv-3prime.der Binary files differnew file mode 100644 index 0000000..eaff675 --- /dev/null +++ b/tests/examples/rsa2048-priv-3prime.der diff --git a/tests/examples/rsa2048-priv-3prime.pem b/tests/examples/rsa2048-priv-3prime.pem new file mode 100644 index 0000000..4a932b7 --- /dev/null +++ b/tests/examples/rsa2048-priv-3prime.pem @@ -0,0 +1,28 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIE2AIBAQKCAQEA6L9GSA2RROk9DnlxVU9+9F+yW4OhBpQW247HxlTrSJxpWjJ0 +7ktfOgjjW6bR/y7Eh85URkHqDAmUzFVNIgEwjjrGSRMmGD9HfTDxZQAYALOHcb+W +9p3m1GGm4viAVxBPzdbGobaef5WifawMuuOcwfkmTAnf7qaEd0gsKBYTMBK6KaDW +F/psa3D6w77csCDLH26jN1FjmI5/jJR0tUYsJNK1a8A/ntAC5Dx2Sx57BcMex9jp +FYLOwJQvOW2rlPMWLRqqBB2pnO6QuB/0CLt1t7IzsZ005QTiWMHXdHBuTF/CsR+X +yeTO6sNF+MV8PTcC9z/Jj37taIgSvptWBhjCRQIDAQABAoIBAFGtpmVs1XkgfPvS +ZJJytnPeDYKOG/lqCOd+IN+aN4Og2Fv97wkdTEraiadFUNbDu9aI8wxA33jf9+cJ +XGs9jaOsPp+wZ6MEufrWLTCrqsC8QCEMAldZo3QDSGQwivbkRMa+6k/qEtt4qXBa +DwyLcys/1zPFh3qHHsxY+rJdQjbQyR8gfW/HntVWhlhiOYTvmSyF6nv/x/jBg2jm +RCiRmLZM82jA/e/6fiS2XYlbcB+K9wrqsLt9LhTENU4Vvkcj7Ihd8UbXsMbShEwU +32Od2px5M//71a4jCLKpzn5sUuN2YQa4/vjVk7iEkbztaGMgReDi+4iaWO0v8otu +XiCCuwECVgfqQeOwtvpjN9jSVLIfBsI3/apkQrOydWpHmycnPOzPu19HKqXZQeEd +oarrF2RQ/AROpjnvbmt3tTDZikOs8tOVcXFO7VFrv0OnB55kDKRq5kDMrka1AlYH +fpIQOZRT8fnWrYYrTYPuhlH5I1S8TyKxpTdu02/oDc/d9zzusyaXImW1ysIeL1/y +Lwnm6grzWFQABNbR7zqCviFNIGsl+Y8PO6+M1PJdcWmsyZgJqQJWAjYimMJ8LXNh +XDRQM8BVfBh2yI/gzyJyifJd2E+vRx83ZASXVuVf8cz8qaurynySHYX4DcHnISG+ +P1rYtdXxtspPevApgJFRTE6z4z5jBeFUWhCWNO0CVgCDD/1VwaE+bX/R2NGKeRHJ +UT5AwKAgk9Eo5TGH9iQXFXUnV51C1ccLTYFuuXYQbHCB8B47LMZU6WAa9IXg3tFh +paq8JTWx56j1vddUEL57aemo198JAlYDtUxSGZYE2akgsberlfC0dLpGeWzEbD5j +xINJkHG/rcDScyHr1T4JJx8oZGt1kMlxAvnkWKk7OgJKA1Zbyv+hX3PAW7+WU74j +IkoeNVJKmJVkHHb9LjCCAQswggEHAlYD7HVTL7DSyhQmMmGOpphUjYSJ/jCoaNY3 +odU4lcefFqY622DbIX3cDZVpSj2V0CnRqaLfVI7RBbLGqEhVc57bw86CkGUAJB1R +ijFy+NRw+t1OHf5ZaQJWA6EVCKPlL6RBLO+O806/Of5IaQdYZH3MH1suiQ9pvIpL +qcc/eJErBH8AA4rrGgaYl9kL0P06uLZHnZ8MgRXYC7i67GO5OH8vKzvi71Cf1/0C +9H2jxXkCVTnqImyr+zF+QaVZO5Fo0aASSZO0XZzRSiK9FVfNy0PSgCSsJu0shTC1 +PpuTqHj0KIB8UoLruBE5n5EwF83yFJAT2Azfc/YJ1saSR163oSPQ6T5qYPw= +-----END RSA PRIVATE KEY----- diff --git a/tests/examples/rsa2048-priv.der b/tests/examples/rsa2048-priv.der Binary files differnew file mode 100644 index 0000000..bbf1876 --- /dev/null +++ b/tests/examples/rsa2048-priv.der diff --git a/tests/examples/rsa2048-priv.pem b/tests/examples/rsa2048-priv.pem new file mode 100644 index 0000000..3b924f5 --- /dev/null +++ b/tests/examples/rsa2048-priv.pem @@ -0,0 +1,27 @@ +-----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----- diff --git a/tests/examples/rsa2048-pub.der b/tests/examples/rsa2048-pub.der Binary files differnew file mode 100644 index 0000000..e75261c --- /dev/null +++ b/tests/examples/rsa2048-pub.der diff --git a/tests/examples/rsa2048-pub.pem b/tests/examples/rsa2048-pub.pem new file mode 100644 index 0000000..f2f72d7 --- /dev/null +++ b/tests/examples/rsa2048-pub.pem @@ -0,0 +1,8 @@ +-----BEGIN RSA PUBLIC KEY----- +MIIBCgKCAQEAtsQsUV8QpqrygsY+2+JCQ6Fw8/omM71IM2N/R8pPbzbgOl0p78MZ +GsgPOQ2HSznjD0FPzsH8oO2B5Uftws04LHb2HJAYlz25+lN5cqfHAfa3fgmC38Ff +wBkn7l582UtPWZ/wcBOnyCgb3yLcvJrXyrt8QxHJgvWO23ITrUVYszImbXQ67YGS +0YhMrbixRzmo2tpm3JcIBtnHrEUMsT0NfFdfsZhTT8YbxBvA8FdODgEwx7u/vf3J +9qbi4+Kv8cvqyJuleIRSjVXPsIMnoejIn04APPKIjpMyQdnWlby7rNyQtE4+CV+j +cFjqJbE/Xilcvqxt6DirjFCvYeKYl1uHLwIDAQAB +-----END RSA PUBLIC KEY----- diff --git a/tests/examples/rsa4096-priv.der b/tests/examples/rsa4096-priv.der Binary files differnew file mode 100644 index 0000000..b154e59 --- /dev/null +++ b/tests/examples/rsa4096-priv.der diff --git a/tests/examples/rsa4096-priv.pem b/tests/examples/rsa4096-priv.pem new file mode 100644 index 0000000..9156bf0 --- /dev/null +++ b/tests/examples/rsa4096-priv.pem @@ -0,0 +1,51 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIJKQIBAAKCAgEAp6dFcoEeomF+Sehb1zDd4w8QP32I7j92XlQNPdmTu7C6FAAC +hZ0LQIl0NmN/WLgo6nTfgyFjQHf5nUqi1UyjdYUu9ZdmHTcTzh7ztP1qjiICOORn +ZoosfuOGHSISrmoevd+oi2LfEPa8957/SsKY+yVj3xuHZDga+bH7DM0IXgJrCtn2 +chojUXfQOWtIdUrUp1JCJQqHO/L25+48dd1hPjZbpPMhCmzGa5Ci+j92LKaIQIe2 +v4Fh6xRIGfD1cvIfbI4nPnDUWjZbiygZznNGE8wjsBMpoXkB8XB4QDhh9UxSoFHi +pYx1wtnYAJG7mAihBsH37LQDThUFi+7HJcX5GdYuqiNLYmKNNGxgu5GecIUdqzhX +Hm8O12NBKfmU6jaP7nNz397AREXrykf6IO0VQKhgyUi6vJjaWRyh3i4uJVQO+bfL +NT9gITuBSkXTWe+puBHu/wjGWZO/ioXCv+qqftXmtD4YrmBEZM5flhUBNufQn4sk ++tQ9eHARjPp7wkh1UG67wyG5d+CGGupQEoYgEh8LOUqc3QpCQRoTUMB3DZddcbAK +kENiQMlnoMOlwgoPbed/Pyyv2pTtAUPB9uNPc+DKwnnu63xjdyOisCbIKALhpK66 +qIRt+Y55GUmHc+DU8xmVb03jqtAO+5oUfWazrBoB01ss+0jUALDnqA3JdVECAwEA +AQKCAgEAn+MJeyMiuQ+rZgbAF6CV6+ZAw5wQC87gLyOPoU2v846eV1aPESftRDYS +a5BGMbEn7Dlbs+4SfrgsiNJWKn+1X+2NFFC35OLS839XQmNvzG8omWNSLVtXBggs +rfoBwO6ZtNDpJ006mS4Gl0y+AWlGhjVpYqwZWf2b1EfluZaMBUPfG/E0dCrzRc2y ++h+TcbDUz2HGjRbWU9jpmdT9OhbPl4o1qkDoYM3OCWVd2LTPGdQUGx6SrV5RqOSl +wn+nRWEdkOSdDpKCIiq28SZkPhx3V4gW/OO5jzIdJUnylKRw34RTRGvzb5hd8l7Y +/en98wc/sncn30jp4fxwVrx4llCQt4UBJkBkYsglMFHvhONO48POuPlsZYw4vkVV +jS9k4p0iM1BVX8Hvoo7B9K+1ukCA8JqGzcNTjBrXyXLm16NhLmhFupr73xnwkGDR +p3neljXi0vjgxRC6JMbESzDJvfr4W+kXrsXUOvqxqjrdM8yD2pPKxpIY9qNutH8Z +nVQkyV/Z7Xsei+KuqmQzsickExbCDueSZQzrSL/WNERrGdKGtOoXIkmNoaNpcyEO +w4JHUaWAjZqu9ZxEnhmlB3z+yhJr2ajdSZZWHU4ns2Cf+CxbGyHmJ4RdRJYbM7h1 +1cT6n/NX72vjNklp4TN8kbKaB7mpE83kDOLVUwyQDnN1FoXmVDECggEBANAhOnlC +W2ZbcZEYRIiT7DJ1YA9j2/hbd/To6Z7zAvboJZYEj23Kdy3mu/ESTbhLCv5hsDqG +BKsAee1T8zBHl60Bs4xE/ielpF43hIOoBLVqSpZ/SPAahm5yHmfkyaEEivaJJ/qk +PWqF2T579wdNunl1Y/yr4SMJt2ZTxtthTcIxzFVtnyWsSEGgLTHN8wFbISMH+dDH +n+tdOVbOU8yPoWUb5gdh8Z90ZySJ6vnyFUCfOZVud6ghg/H3K7L+3fG5+/xK2J6k +RYCd29W9WVJ3mQwL6TZvuy7PewV8wcPcj7d7+EVtB7vJWzwYFfSOYrgUaMPU2dls +D0jasEmTvo2R7eUCggEBAM42xoEFIqvl1kZfNusTfaO56kpfHSfGYUcp645eLly4 +jj7xpHOiGUS2ZVez3CzkYuS/NEbLSZADflZysXBcuugbZbr5Z6Jm3Bjv6A9Nu/4a +WQYyBc4pQ8rfQhzOdK9wY/0ag688Oa+EUl9ZvcH/VIFfUq/R6NSGKyw2VPbPqD3A +jiqdUrn4M8ZGr3aURn38X31617RBiV/Lf/vtUmMksBVKFYI/UQfIlUjt3LYdpTCM +bMg01KDBbfpsodZ7YaZWd+sXGc0SXQ7w24gC+3bPwXV3vLJRCuKU4b+KkXOiuFwW +prUIyY8tdwt/PeSNnnIMU+JjaAtX5xCUEAFXRVcGUv0CggEAdItGzQPlXmmyLEdk +iP4b8x1azwNh965we4m42DLH5C6WbWzcS+Rl3CQp9ZIER0BuRYe6QOsuzfqUS9sI +gG52doBPZCp2DwloAwIfiAGbsWJ1pdRcqWaRBGOOtyqb5ThAAFFJO8agRXfx8FVG +PKa/1qdvd9tfVFlqgzhCUDIqcqWj/+pEhbn1NBpXdF4YxxeadJ1QvCIsYIVxSDR9 +JD0BaTa4FkY4IMvzvbglBhUS5X7DpfOXuWQbGHEJ3U9uRJ+ahOn8ZskhyiWbJhLD +Y7Ro1SAOVVc3f7za7HWxotVs/JfErEujWvojxoDOOoVIrj9vcslLu74QyQD8WhcL +Swb+KQKCAQB1yk4LBp7uZ8PEwMCC+MgsjJbq0ne575RDbQuTb/K1neoKxEa2kmIy +oKk0tpVOw0pF9X3r7lTfwU8aHDuEvkM5L+UlLy9mUbDpQahhjXqTxAMUCeDNCT8j +E/IUuE1opR9IRSvxHcqpmkDfHEjLFojzuTpnGdUQCG+Cuqo/rRAh7eqHJwRJHCCe +4mN5rWqyrkTxTQkHeuP4Zyp9AeusnBlEn+O3WWl0s7uqQ8xt7nMcTyoYFi1aggLL +J+Atvp5hwESRccmYHSQw053ijCmNjVCpQ7LyfF5mXLqyiXlZ/xml6H5jLFjNwx+b +3pvBAK//31DPIQ8eY6CmFJ0r1ujRs9gVAoIBAQCMVXxINei0BmYGpdwlXbw+tfFY +bHMomIyOJCQD+Vde+w0oASwGNLckF2itciBJOCkG1jWvyx0qBb3/yAX+ZwOJt/qQ +1l+Ur7wgCNm8DTzO5CXfxyQCBQYDyfV57oUQ7MaTrG3TKDa24V49xSA2ahukr8kd +Pkik1nJMpCRD9IA+OxJjSwQ4Vy2OE7xy8agoU7jZ/KYZnXqQq2TZ5595OsKH+aGQ +EQgqUmjyCTMtVNmNbhkDCQf9nmuC7xJGd7oIrWeXpEIhPQl6NRxQoIko3XMtaymm +z2cG2vXyD3j4cXD7J5QDxSIbXlxwwrqg5ppy4NHzJn29jJvd/yivqS83yY02 +-----END RSA PRIVATE KEY----- diff --git a/tests/examples/rsa4096-pub.der b/tests/examples/rsa4096-pub.der Binary files differnew file mode 100644 index 0000000..d8b7c7d --- /dev/null +++ b/tests/examples/rsa4096-pub.der diff --git a/tests/examples/rsa4096-pub.pem b/tests/examples/rsa4096-pub.pem new file mode 100644 index 0000000..d60e8eb --- /dev/null +++ b/tests/examples/rsa4096-pub.pem @@ -0,0 +1,13 @@ +-----BEGIN RSA PUBLIC KEY----- +MIICCgKCAgEAp6dFcoEeomF+Sehb1zDd4w8QP32I7j92XlQNPdmTu7C6FAAChZ0L +QIl0NmN/WLgo6nTfgyFjQHf5nUqi1UyjdYUu9ZdmHTcTzh7ztP1qjiICOORnZoos +fuOGHSISrmoevd+oi2LfEPa8957/SsKY+yVj3xuHZDga+bH7DM0IXgJrCtn2choj +UXfQOWtIdUrUp1JCJQqHO/L25+48dd1hPjZbpPMhCmzGa5Ci+j92LKaIQIe2v4Fh +6xRIGfD1cvIfbI4nPnDUWjZbiygZznNGE8wjsBMpoXkB8XB4QDhh9UxSoFHipYx1 +wtnYAJG7mAihBsH37LQDThUFi+7HJcX5GdYuqiNLYmKNNGxgu5GecIUdqzhXHm8O +12NBKfmU6jaP7nNz397AREXrykf6IO0VQKhgyUi6vJjaWRyh3i4uJVQO+bfLNT9g +ITuBSkXTWe+puBHu/wjGWZO/ioXCv+qqftXmtD4YrmBEZM5flhUBNufQn4sk+tQ9 +eHARjPp7wkh1UG67wyG5d+CGGupQEoYgEh8LOUqc3QpCQRoTUMB3DZddcbAKkENi +QMlnoMOlwgoPbed/Pyyv2pTtAUPB9uNPc+DKwnnu63xjdyOisCbIKALhpK66qIRt ++Y55GUmHc+DU8xmVb03jqtAO+5oUfWazrBoB01ss+0jUALDnqA3JdVECAwEAAQ== +-----END RSA PUBLIC KEY----- diff --git a/tests/private_key.rs b/tests/private_key.rs new file mode 100644 index 0000000..f650534 --- /dev/null +++ b/tests/private_key.rs @@ -0,0 +1,94 @@ +//! PKCS#1 private key tests + +use hex_literal::hex; +use pkcs1::{RsaPrivateKey, Version}; + +/// RSA-2048 PKCS#1 private key encoded as ASN.1 DER. +/// +/// Note: this key is extracted from the corresponding `rsa2048-priv.der` +/// example key in the `pkcs8` crate. +const RSA_2048_DER_EXAMPLE: &[u8] = include_bytes!("examples/rsa2048-priv.der"); + +/// RSA-4096 PKCS#1 private key encoded as ASN.1 DER +const RSA_4096_DER_EXAMPLE: &[u8] = include_bytes!("examples/rsa4096-priv.der"); + +/// RSA-2048 PKCS#1 private key with 3 primes encoded as ASN.1 DER +const RSA_2048_MULTI_PRIME_DER_EXAMPLE: &[u8] = include_bytes!("examples/rsa2048-priv-3prime.der"); + +#[test] +fn decode_rsa2048_der() { + let key = RsaPrivateKey::try_from(RSA_2048_DER_EXAMPLE).unwrap(); + assert_eq!(key.version(), Version::TwoPrime); + + // Extracted using: + // $ openssl asn1parse -in tests/examples/rsa2048-priv.pem + assert_eq!(key.modulus.as_bytes(), hex!("B6C42C515F10A6AAF282C63EDBE24243A170F3FA2633BD4833637F47CA4F6F36E03A5D29EFC3191AC80F390D874B39E30F414FCEC1FCA0ED81E547EDC2CD382C76F61C9018973DB9FA537972A7C701F6B77E0982DFC15FC01927EE5E7CD94B4F599FF07013A7C8281BDF22DCBC9AD7CABB7C4311C982F58EDB7213AD4558B332266D743AED8192D1884CADB8B14739A8DADA66DC970806D9C7AC450CB13D0D7C575FB198534FC61BC41BC0F0574E0E0130C7BBBFBDFDC9F6A6E2E3E2AFF1CBEAC89BA57884528D55CFB08327A1E8C89F4E003CF2888E933241D9D695BCBBACDC90B44E3E095FA37058EA25B13F5E295CBEAC6DE838AB8C50AF61E298975B872F")); + assert_eq!(key.public_exponent.as_bytes(), hex!("010001")); + assert_eq!(key.private_exponent.as_bytes(), hex!("7ECC8362C0EDB0741164215E22F74AB9D91BA06900700CF63690E5114D8EE6BDCFBB2E3F9614692A677A083F168A5E52E5968E6407B9D97C6E0E4064F82DA0B758A14F17B9B7D41F5F48E28D6551704F56E69E7AA9FA630FC76428C06D25E455DCFC55B7AC2B4F76643FDED3FE15FF78ABB27E65ACC4AAD0BDF6DB27EF60A6910C5C4A085ED43275AB19C1D997A32C6EFFCE7DF2D1935F6E601EEDE161A12B5CC27CA21F81D2C99C3D1EA08E90E3053AB09BEFA724DEF0D0C3A3C1E9740C0D9F76126A149EC0AA7D8078205484254D951DB07C4CF91FB6454C096588FD5924DBABEB359CA2025268D004F9D66EB3D6F7ADC1139BAD40F16DDE639E11647376C1")); + assert_eq!(key.prime1.as_bytes(), hex!("DCC061242D4E92AFAEE72AC513CA65B9F77036F9BD7E0E6E61461A7EF7654225EC153C7E5C31A6157A6E5A13FF6E178E8758C1CB33D9D6BBE3179EF18998E422ECDCBED78F4ECFDBE5F4FCD8AEC2C9D0DC86473CA9BD16D9D238D21FB5DDEFBEB143CA61D0BD6AA8D91F33A097790E9640DBC91085DC5F26343BA3138F6B2D67")); + assert_eq!(key.prime2.as_bytes(), hex!("D3F314757E40E954836F92BE24236AF2F0DA04A34653C180AF67E960086D93FDE65CB23EFD9D09374762F5981E361849AF68CDD75394FF6A4E06EB69B209E4228DB2DFA70E40F7F9750A528176647B788D0E5777A2CB8B22E3CD267FF70B4F3B02D3AAFB0E18C590A564B03188B0AA5FC48156B07622214243BD1227EFA7F2F9")); + assert_eq!(key.exponent1.as_bytes(), hex!("CE68B7AC1B0D100D636E55488753C5C09843FDB390E2705DF7689457C9BD8D9765E30978617E2EFC8048F4C324206DB86087B654E97BB3D464E7EE3F8CD83FE10436F7DF18E9A963C4E64911D67EDE34042F2E26E3D3A1AD346ADAD6B9B7F67708CB094E62DEE9FF4D5D6669AF988AF2255D1CE8ED317C6A7D8691DA354D12DB")); + assert_eq!(key.exponent2.as_bytes(), hex!("25F6E5944220286B4DFBBF4235C0EE5843D2198091895120D6CA7B200B826D3ECE738E2E00498FAC0A2A6CA969C7F0C3CA1AB0BC40297132BE7538D7BEDF4CB0EFC6B98EF7DBA54F56AA99AABCE534C49C27947D4678C51C63C78C7CE1687231B4C8EB587AE6EF0480CBAF4FC0173CFD587A7E67AF515FB9B9DE751118397229")); + assert_eq!(key.coefficient.as_bytes(), hex!("31995406D406207CADEAEA35B38D040C5F8A9A1AE0827E9ED06B153D83B6821935B4B36A82BE9D56C791B58C27271A5793D53A1D657C08997960B1433E5171987F452F144A7C72306D63E1D3FFC0B71B75AB08F2E45A482E988451CBE478E12EB228D07456C924B66F6CED048D853F533E31A68614F1C3CE6D8EC9983CE72AF7")); + assert!(key.other_prime_infos.is_none()); +} + +#[test] +fn decode_rsa4096_der() { + let key = RsaPrivateKey::try_from(RSA_4096_DER_EXAMPLE).unwrap(); + assert_eq!(key.version(), Version::TwoPrime); + + // Extracted using: + // $ openssl asn1parse -in tests/examples/rsa4096-priv.pem + assert_eq!(key.modulus.as_bytes(), hex!("A7A74572811EA2617E49E85BD730DDE30F103F7D88EE3F765E540D3DD993BBB0BA140002859D0B40897436637F58B828EA74DF8321634077F99D4AA2D54CA375852EF597661D3713CE1EF3B4FD6A8E220238E467668A2C7EE3861D2212AE6A1EBDDFA88B62DF10F6BCF79EFF4AC298FB2563DF1B8764381AF9B1FB0CCD085E026B0AD9F6721A235177D0396B48754AD4A75242250A873BF2F6E7EE3C75DD613E365BA4F3210A6CC66B90A2FA3F762CA6884087B6BF8161EB144819F0F572F21F6C8E273E70D45A365B8B2819CE734613CC23B01329A17901F17078403861F54C52A051E2A58C75C2D9D80091BB9808A106C1F7ECB4034E15058BEEC725C5F919D62EAA234B62628D346C60BB919E70851DAB38571E6F0ED7634129F994EA368FEE7373DFDEC04445EBCA47FA20ED1540A860C948BABC98DA591CA1DE2E2E25540EF9B7CB353F60213B814A45D359EFA9B811EEFF08C65993BF8A85C2BFEAAA7ED5E6B43E18AE604464CE5F96150136E7D09F8B24FAD43D7870118CFA7BC24875506EBBC321B977E0861AEA50128620121F0B394A9CDD0A42411A1350C0770D975D71B00A90436240C967A0C3A5C20A0F6DE77F3F2CAFDA94ED0143C1F6E34F73E0CAC279EEEB7C637723A2B026C82802E1A4AEBAA8846DF98E7919498773E0D4F319956F4DE3AAD00EFB9A147D66B3AC1A01D35B2CFB48D400B0E7A80DC97551")); + assert_eq!(key.public_exponent.as_bytes(), hex!("010001")); + assert_eq!(key.private_exponent.as_bytes(), hex!("9FE3097B2322B90FAB6606C017A095EBE640C39C100BCEE02F238FA14DAFF38E9E57568F1127ED4436126B904631B127EC395BB3EE127EB82C88D2562A7FB55FED8D1450B7E4E2D2F37F5742636FCC6F289963522D5B5706082CADFA01C0EE99B4D0E9274D3A992E06974CBE01694686356962AC1959FD9BD447E5B9968C0543DF1BF134742AF345CDB2FA1F9371B0D4CF61C68D16D653D8E999D4FD3A16CF978A35AA40E860CDCE09655DD8B4CF19D4141B1E92AD5E51A8E4A5C27FA745611D90E49D0E9282222AB6F126643E1C77578816FCE3B98F321D2549F294A470DF8453446BF36F985DF25ED8FDE9FDF3073FB27727DF48E9E1FC7056BC78965090B7850126406462C8253051EF84E34EE3C3CEB8F96C658C38BE45558D2F64E29D223350555FC1EFA28EC1F4AFB5BA4080F09A86CDC3538C1AD7C972E6D7A3612E6845BA9AFBDF19F09060D1A779DE9635E2D2F8E0C510BA24C6C44B30C9BDFAF85BE917AEC5D43AFAB1AA3ADD33CC83DA93CAC69218F6A36EB47F199D5424C95FD9ED7B1E8BE2AEAA6433B227241316C20EE792650CEB48BFD634446B19D286B4EA1722498DA1A36973210EC3824751A5808D9AAEF59C449E19A5077CFECA126BD9A8DD4996561D4E27B3609FF82C5B1B21E627845D44961B33B875D5C4FA9FF357EF6BE3364969E1337C91B29A07B9A913CDE40CE2D5530C900E73751685E65431")); + assert_eq!(key.prime1.as_bytes(), hex!("D0213A79425B665B719118448893EC3275600F63DBF85B77F4E8E99EF302F6E82596048F6DCA772DE6BBF1124DB84B0AFE61B03A8604AB0079ED53F3304797AD01B38C44FE27A5A45E378483A804B56A4A967F48F01A866E721E67E4C9A1048AF68927FAA43D6A85D93E7BF7074DBA797563FCABE12309B76653C6DB614DC231CC556D9F25AC4841A02D31CDF3015B212307F9D0C79FEB5D3956CE53CC8FA1651BE60761F19F74672489EAF9F215409F39956E77A82183F1F72BB2FEDDF1B9FBFC4AD89EA445809DDBD5BD595277990C0BE9366FBB2ECF7B057CC1C3DC8FB77BF8456D07BBC95B3C1815F48E62B81468C3D4D9D96C0F48DAB04993BE8D91EDE5")); + assert_eq!(key.prime2.as_bytes(), hex!("CE36C6810522ABE5D6465F36EB137DA3B9EA4A5F1D27C6614729EB8E5E2E5CB88E3EF1A473A21944B66557B3DC2CE462E4BF3446CB4990037E5672B1705CBAE81B65BAF967A266DC18EFE80F4DBBFE1A59063205CE2943CADF421CCE74AF7063FD1A83AF3C39AF84525F59BDC1FF54815F52AFD1E8D4862B2C3654F6CFA83DC08E2A9D52B9F833C646AF7694467DFC5F7D7AD7B441895FCB7FFBED526324B0154A15823F5107C89548EDDCB61DA5308C6CC834D4A0C16DFA6CA1D67B61A65677EB1719CD125D0EF0DB8802FB76CFC17577BCB2510AE294E1BF8A9173A2B85C16A6B508C98F2D770B7F3DE48D9E720C53E263680B57E7109410015745570652FD")); + assert_eq!(key.exponent1.as_bytes(), hex!("748B46CD03E55E69B22C476488FE1BF31D5ACF0361F7AE707B89B8D832C7E42E966D6CDC4BE465DC2429F5920447406E4587BA40EB2ECDFA944BDB08806E7676804F642A760F096803021F88019BB16275A5D45CA9669104638EB72A9BE538400051493BC6A04577F1F055463CA6BFD6A76F77DB5F54596A83384250322A72A5A3FFEA4485B9F5341A57745E18C7179A749D50BC222C60857148347D243D016936B816463820CBF3BDB825061512E57EC3A5F397B9641B187109DD4F6E449F9A84E9FC66C921CA259B2612C363B468D5200E5557377FBCDAEC75B1A2D56CFC97C4AC4BA35AFA23C680CE3A8548AE3F6F72C94BBBBE10C900FC5A170B4B06FE29")); + assert_eq!(key.exponent2.as_bytes(), hex!("75CA4E0B069EEE67C3C4C0C082F8C82C8C96EAD277B9EF94436D0B936FF2B59DEA0AC446B6926232A0A934B6954EC34A45F57DEBEE54DFC14F1A1C3B84BE43392FE5252F2F6651B0E941A8618D7A93C4031409E0CD093F2313F214B84D68A51F48452BF11DCAA99A40DF1C48CB1688F3B93A6719D510086F82BAAA3FAD1021EDEA872704491C209EE26379AD6AB2AE44F14D09077AE3F8672A7D01EBAC9C19449FE3B7596974B3BBAA43CC6DEE731C4F2A18162D5A8202CB27E02DBE9E61C0449171C9981D2430D39DE28C298D8D50A943B2F27C5E665CBAB2897959FF19A5E87E632C58CDC31F9BDE9BC100AFFFDF50CF210F1E63A0A6149D2BD6E8D1B3D815")); + assert_eq!(key.coefficient.as_bytes(), hex!("8C557C4835E8B4066606A5DC255DBC3EB5F1586C7328988C8E242403F9575EFB0D28012C0634B7241768AD722049382906D635AFCB1D2A05BDFFC805FE670389B7FA90D65F94AFBC2008D9BC0D3CCEE425DFC72402050603C9F579EE8510ECC693AC6DD32836B6E15E3DC520366A1BA4AFC91D3E48A4D6724CA42443F4803E3B12634B0438572D8E13BC72F1A82853B8D9FCA6199D7A90AB64D9E79F793AC287F9A19011082A5268F209332D54D98D6E19030907FD9E6B82EF124677BA08AD6797A442213D097A351C50A08928DD732D6B29A6CF6706DAF5F20F78F87170FB279403C5221B5E5C70C2BAA0E69A72E0D1F3267DBD8C9BDDFF28AFA92F37C98D36")); + assert!(key.other_prime_infos.is_none()); +} + +#[cfg(not(feature = "alloc"))] +#[test] +fn decode_rsa2048_multi_prime_der() { + // Multi-prime RSA keys are unsupported when the alloc feature is disabled + assert!(RsaPrivateKey::try_from(RSA_2048_MULTI_PRIME_DER_EXAMPLE).is_err()); +} + +#[cfg(feature = "alloc")] +#[test] +fn decode_rsa2048_multi_prime_der() { + let key = RsaPrivateKey::try_from(RSA_2048_MULTI_PRIME_DER_EXAMPLE).unwrap(); + assert_eq!(key.version(), Version::Multi); + + // Extracted using: + // $ openssl asn1parse -in tests/examples/rsa2048-priv-3prime.pem + assert_eq!(key.modulus.as_bytes(), hex!("E8BF46480D9144E93D0E7971554F7EF45FB25B83A1069416DB8EC7C654EB489C695A3274EE4B5F3A08E35BA6D1FF2EC487CE544641EA0C0994CC554D2201308E3AC6491326183F477D30F165001800B38771BF96F69DE6D461A6E2F88057104FCDD6C6A1B69E7F95A27DAC0CBAE39CC1F9264C09DFEEA68477482C2816133012BA29A0D617FA6C6B70FAC3BEDCB020CB1F6EA3375163988E7F8C9474B5462C24D2B56BC03F9ED002E43C764B1E7B05C31EC7D8E91582CEC0942F396DAB94F3162D1AAA041DA99CEE90B81FF408BB75B7B233B19D34E504E258C1D774706E4C5FC2B11F97C9E4CEEAC345F8C57C3D3702F73FC98F7EED688812BE9B560618C245")); + assert_eq!(key.public_exponent.as_bytes(), hex!("010001")); + assert_eq!(key.private_exponent.as_bytes(), hex!("51ADA6656CD579207CFBD2649272B673DE0D828E1BF96A08E77E20DF9A3783A0D85BFDEF091D4C4ADA89A74550D6C3BBD688F30C40DF78DFF7E7095C6B3D8DA3AC3E9FB067A304B9FAD62D30ABAAC0BC40210C025759A374034864308AF6E444C6BEEA4FEA12DB78A9705A0F0C8B732B3FD733C5877A871ECC58FAB25D4236D0C91F207D6FC79ED5568658623984EF992C85EA7BFFC7F8C18368E644289198B64CF368C0FDEFFA7E24B65D895B701F8AF70AEAB0BB7D2E14C4354E15BE4723EC885DF146D7B0C6D2844C14DF639DDA9C7933FFFBD5AE2308B2A9CE7E6C52E3766106B8FEF8D593B88491BCED68632045E0E2FB889A58ED2FF28B6E5E2082BB01")); + assert_eq!(key.prime1.as_bytes(), hex!("07EA41E3B0B6FA6337D8D254B21F06C237FDAA6442B3B2756A479B27273CECCFBB5F472AA5D941E11DA1AAEB176450FC044EA639EF6E6B77B530D98A43ACF2D39571714EED516BBF43A7079E640CA46AE640CCAE46B5")); + assert_eq!(key.prime2.as_bytes(), hex!("077E9210399453F1F9D6AD862B4D83EE8651F92354BC4F22B1A5376ED36FE80DCFDDF73CEEB326972265B5CAC21E2F5FF22F09E6EA0AF358540004D6D1EF3A82BE214D206B25F98F0F3BAF8CD4F25D7169ACC99809A9")); + assert_eq!(key.exponent1.as_bytes(), hex!("02362298C27C2D73615C345033C0557C1876C88FE0CF227289F25DD84FAF471F3764049756E55FF1CCFCA9ABABCA7C921D85F80DC1E72121BE3F5AD8B5D5F1B6CA4F7AF0298091514C4EB3E33E6305E1545A109634ED")); + assert_eq!(key.exponent2.as_bytes(), hex!("830FFD55C1A13E6D7FD1D8D18A7911C9513E40C0A02093D128E53187F62417157527579D42D5C70B4D816EB976106C7081F01E3B2CC654E9601AF485E0DED161A5AABC2535B1E7A8F5BDD75410BE7B69E9A8D7DF09")); + assert_eq!(key.coefficient.as_bytes(), hex!("03B54C52199604D9A920B1B7AB95F0B474BA46796CC46C3E63C483499071BFADC0D27321EBD53E09271F28646B7590C97102F9E458A93B3A024A03565BCAFFA15F73C05BBF9653BE23224A1E35524A9895641C76FD2E")); + + let other_prime_infos = key.other_prime_infos.unwrap(); + assert_eq!(other_prime_infos.len(), 1); + assert_eq!(other_prime_infos[0].prime.as_bytes(), hex!("03EC75532FB0D2CA142632618EA698548D8489FE30A868D637A1D53895C79F16A63ADB60DB217DDC0D95694A3D95D029D1A9A2DF548ED105B2C6A84855739EDBC3CE82906500241D518A3172F8D470FADD4E1DFE5969")); + assert_eq!(other_prime_infos[0].exponent.as_bytes(), hex!("03A11508A3E52FA4412CEF8EF34EBF39FE48690758647DCC1F5B2E890F69BC8A4BA9C73F78912B047F00038AEB1A069897D90BD0FD3AB8B6479D9F0C8115D80BB8BAEC63B9387F2F2B3BE2EF509FD7FD02F47DA3C579")); + assert_eq!(other_prime_infos[0].coefficient.as_bytes(), hex!("39EA226CABFB317E41A5593B9168D1A0124993B45D9CD14A22BD1557CDCB43D28024AC26ED2C8530B53E9B93A878F428807C5282EBB811399F913017CDF2149013D80CDF73F609D6C692475EB7A123D0E93E6A60FC")); +} + +#[test] +fn private_key_to_public_key() { + let private_key = RsaPrivateKey::try_from(RSA_2048_DER_EXAMPLE).unwrap(); + let public_key = private_key.public_key(); + + // Extracted using: + // $ openssl asn1parse -in tests/examples/rsa2048-pub.pem + assert_eq!(public_key.modulus.as_bytes(), hex!("B6C42C515F10A6AAF282C63EDBE24243A170F3FA2633BD4833637F47CA4F6F36E03A5D29EFC3191AC80F390D874B39E30F414FCEC1FCA0ED81E547EDC2CD382C76F61C9018973DB9FA537972A7C701F6B77E0982DFC15FC01927EE5E7CD94B4F599FF07013A7C8281BDF22DCBC9AD7CABB7C4311C982F58EDB7213AD4558B332266D743AED8192D1884CADB8B14739A8DADA66DC970806D9C7AC450CB13D0D7C575FB198534FC61BC41BC0F0574E0E0130C7BBBFBDFDC9F6A6E2E3E2AFF1CBEAC89BA57884528D55CFB08327A1E8C89F4E003CF2888E933241D9D695BCBBACDC90B44E3E095FA37058EA25B13F5E295CBEAC6DE838AB8C50AF61E298975B872F")); + assert_eq!(public_key.public_exponent.as_bytes(), hex!("010001")); +} diff --git a/tests/public_key.rs b/tests/public_key.rs new file mode 100644 index 0000000..cde6e41 --- /dev/null +++ b/tests/public_key.rs @@ -0,0 +1,64 @@ +//! PKCS#1 public key tests + +use hex_literal::hex; +use pkcs1::RsaPublicKey; + +/// RSA-2048 PKCS#1 public key encoded as ASN.1 DER. +/// +/// Note: this key is extracted from the corresponding `rsa2048-priv.der` +/// example key in the `pkcs8` crate. +const RSA_2048_DER_EXAMPLE: &[u8] = include_bytes!("examples/rsa2048-pub.der"); + +/// RSA-4096 PKCS#1 public key encoded as ASN.1 DER +const RSA_4096_DER_EXAMPLE: &[u8] = include_bytes!("examples/rsa4096-pub.der"); + +// /// RSA-2048 PKCS#1 public key encoded as PEM +// #[cfg(feature = "pem")] +// const RSA_2048_PEM_EXAMPLE: &str = include_str!("examples/rsa2048-pub.pem"); +// +// /// RSA-4096 PKCS#1 public key encoded as PEM +// #[cfg(feature = "pem")] +// const RSA_4096_PEM_EXAMPLE: &str = include_str!("examples/rsa4096-pub.pem"); + +#[test] +fn decode_rsa2048_der() { + let key = RsaPublicKey::try_from(RSA_2048_DER_EXAMPLE).unwrap(); + + // Extracted using: + // $ openssl asn1parse -in tests/examples/rsa2048-pub.pem + assert_eq!(key.modulus.as_bytes(), hex!("B6C42C515F10A6AAF282C63EDBE24243A170F3FA2633BD4833637F47CA4F6F36E03A5D29EFC3191AC80F390D874B39E30F414FCEC1FCA0ED81E547EDC2CD382C76F61C9018973DB9FA537972A7C701F6B77E0982DFC15FC01927EE5E7CD94B4F599FF07013A7C8281BDF22DCBC9AD7CABB7C4311C982F58EDB7213AD4558B332266D743AED8192D1884CADB8B14739A8DADA66DC970806D9C7AC450CB13D0D7C575FB198534FC61BC41BC0F0574E0E0130C7BBBFBDFDC9F6A6E2E3E2AFF1CBEAC89BA57884528D55CFB08327A1E8C89F4E003CF2888E933241D9D695BCBBACDC90B44E3E095FA37058EA25B13F5E295CBEAC6DE838AB8C50AF61E298975B872F")); + assert_eq!(key.public_exponent.as_bytes(), hex!("010001")); +} + +#[test] +fn decode_rsa4096_der() { + let key = RsaPublicKey::try_from(RSA_4096_DER_EXAMPLE).unwrap(); + + // Extracted using: + // $ openssl asn1parse -in tests/examples/rsa4096-pub.pem + assert_eq!(key.modulus.as_bytes(), hex!("A7A74572811EA2617E49E85BD730DDE30F103F7D88EE3F765E540D3DD993BBB0BA140002859D0B40897436637F58B828EA74DF8321634077F99D4AA2D54CA375852EF597661D3713CE1EF3B4FD6A8E220238E467668A2C7EE3861D2212AE6A1EBDDFA88B62DF10F6BCF79EFF4AC298FB2563DF1B8764381AF9B1FB0CCD085E026B0AD9F6721A235177D0396B48754AD4A75242250A873BF2F6E7EE3C75DD613E365BA4F3210A6CC66B90A2FA3F762CA6884087B6BF8161EB144819F0F572F21F6C8E273E70D45A365B8B2819CE734613CC23B01329A17901F17078403861F54C52A051E2A58C75C2D9D80091BB9808A106C1F7ECB4034E15058BEEC725C5F919D62EAA234B62628D346C60BB919E70851DAB38571E6F0ED7634129F994EA368FEE7373DFDEC04445EBCA47FA20ED1540A860C948BABC98DA591CA1DE2E2E25540EF9B7CB353F60213B814A45D359EFA9B811EEFF08C65993BF8A85C2BFEAAA7ED5E6B43E18AE604464CE5F96150136E7D09F8B24FAD43D7870118CFA7BC24875506EBBC321B977E0861AEA50128620121F0B394A9CDD0A42411A1350C0770D975D71B00A90436240C967A0C3A5C20A0F6DE77F3F2CAFDA94ED0143C1F6E34F73E0CAC279EEEB7C637723A2B026C82802E1A4AEBAA8846DF98E7919498773E0D4F319956F4DE3AAD00EFB9A147D66B3AC1A01D35B2CFB48D400B0E7A80DC97551")); + assert_eq!(key.public_exponent.as_bytes(), hex!("010001")); +} + +// TODO(tarcieri): test trait-based PEM decoding +// #[test] +// #[cfg(feature = "pem")] +// fn decode_rsa_2048_pem() { +// let pkcs1_doc: Document = RSA_2048_PEM_EXAMPLE.parse().unwrap(); +// assert_eq!(pkcs1_doc.as_ref(), RSA_2048_DER_EXAMPLE); +// +// // Ensure `Document` parses successfully +// let pk = RsaPublicKey::try_from(RSA_2048_DER_EXAMPLE).unwrap(); +// assert_eq!(pkcs1_doc.decode().modulus.as_bytes(), pk.modulus.as_bytes()); +// } +// +// #[test] +// #[cfg(feature = "pem")] +// fn decode_rsa_4096_pem() { +// let pkcs1_doc: Document = RSA_4096_PEM_EXAMPLE.parse().unwrap(); +// assert_eq!(pkcs1_doc.as_ref(), RSA_4096_DER_EXAMPLE); +// +// // Ensure `Document` parses successfully +// let pk = RsaPublicKey::try_from(RSA_4096_DER_EXAMPLE).unwrap(); +// assert_eq!(pkcs1_doc.decode().modulus.as_bytes(), pk.modulus.as_bytes()); +// } diff --git a/tests/traits.rs b/tests/traits.rs new file mode 100644 index 0000000..65bef26 --- /dev/null +++ b/tests/traits.rs @@ -0,0 +1,100 @@ +//! Tests for PKCS#1 encoding/decoding traits. + +#![cfg(any(feature = "pem", feature = "std"))] + +use der::SecretDocument; +use pkcs1::{DecodeRsaPrivateKey, EncodeRsaPrivateKey, Result}; + +#[cfg(feature = "pem")] +use pkcs1::der::pem::LineEnding; + +#[cfg(feature = "std")] +use tempfile::tempdir; + +#[cfg(all(feature = "pem", feature = "std"))] +use std::fs; + +/// PKCS#1 `RsaPrivateKey` encoded as ASN.1 DER +const RSA_2048_PRIV_DER_EXAMPLE: &[u8] = include_bytes!("examples/rsa2048-priv.der"); + +/// PKCS#1 `RsaPrivateKey` encoded as PEM +#[cfg(feature = "pem")] +const RSA_2048_PRIV_PEM_EXAMPLE: &str = include_str!("examples/rsa2048-priv.pem"); + +/// Mock RSA private key type for testing trait impls against. +pub struct MockPrivateKey(Vec<u8>); + +impl AsRef<[u8]> for MockPrivateKey { + fn as_ref(&self) -> &[u8] { + self.0.as_ref() + } +} + +impl DecodeRsaPrivateKey for MockPrivateKey { + fn from_pkcs1_der(bytes: &[u8]) -> Result<MockPrivateKey> { + Ok(MockPrivateKey(bytes.to_vec())) + } +} + +impl EncodeRsaPrivateKey for MockPrivateKey { + fn to_pkcs1_der(&self) -> Result<SecretDocument> { + Ok(SecretDocument::try_from(self.as_ref())?) + } +} + +#[cfg(feature = "pem")] +#[test] +fn from_pkcs1_pem() { + let key = MockPrivateKey::from_pkcs1_pem(RSA_2048_PRIV_PEM_EXAMPLE).unwrap(); + assert_eq!(key.as_ref(), RSA_2048_PRIV_DER_EXAMPLE); +} + +#[cfg(feature = "std")] +#[test] +fn read_pkcs1_der_file() { + let key = MockPrivateKey::read_pkcs1_der_file("tests/examples/rsa2048-priv.der").unwrap(); + assert_eq!(key.as_ref(), RSA_2048_PRIV_DER_EXAMPLE); +} + +#[cfg(all(feature = "pem", feature = "std"))] +#[test] +fn read_pkcs1_pem_file() { + let key = MockPrivateKey::read_pkcs1_pem_file("tests/examples/rsa2048-priv.pem").unwrap(); + assert_eq!(key.as_ref(), RSA_2048_PRIV_DER_EXAMPLE); +} + +#[cfg(feature = "pem")] +#[test] +fn to_pkcs1_pem() { + let pem = MockPrivateKey(RSA_2048_PRIV_DER_EXAMPLE.to_vec()) + .to_pkcs1_pem(LineEnding::LF) + .unwrap(); + + assert_eq!(&*pem, RSA_2048_PRIV_PEM_EXAMPLE); +} + +#[cfg(feature = "std")] +#[test] +fn write_pkcs1_der_file() { + let dir = tempdir().unwrap(); + let path = dir.path().join("example.der"); + MockPrivateKey(RSA_2048_PRIV_DER_EXAMPLE.to_vec()) + .write_pkcs1_der_file(&path) + .unwrap(); + + let key = MockPrivateKey::read_pkcs1_der_file(&path).unwrap(); + assert_eq!(key.as_ref(), RSA_2048_PRIV_DER_EXAMPLE); +} + +#[cfg(all(feature = "pem", feature = "std"))] +#[test] +fn write_pkcs1_pem_file() { + let dir = tempdir().unwrap(); + let path = dir.path().join("example.pem"); + MockPrivateKey(RSA_2048_PRIV_DER_EXAMPLE.to_vec()) + .write_pkcs1_pem_file(&path, LineEnding::LF) + .unwrap(); + + let pem = fs::read_to_string(path).unwrap(); + assert_eq!(&pem, RSA_2048_PRIV_PEM_EXAMPLE); +} |