aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHasini Gunasinghe <hasinitg@google.com>2022-10-05 18:51:36 +0000
committerAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>2022-10-05 18:51:36 +0000
commit4aca7fbdcae8e0a0dc836230134763a2eb5eabf3 (patch)
treebf13c3231bd0b49a9b948e61ee9cb93cccc5f220
parentd8b568e586f5546ed7b157ebfa72bf6e34c38349 (diff)
parent391fa512edff338e01a5a4d1b88cdd8a6f332c2c (diff)
downloadpkcs1-4aca7fbdcae8e0a0dc836230134763a2eb5eabf3.tar.gz
Import platform/external/rust/crates/pkcs1 am: 2388160425 am: 391fa512ed
Original change: https://android-review.googlesource.com/c/platform/external/rust/crates/pkcs1/+/2240587 Change-Id: I39fbee31774ee76f5ba4b158128ec8b074384fc6 Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
-rw-r--r--.cargo_vcs_info.json6
-rw-r--r--Android.bp28
-rw-r--r--CHANGELOG.md100
-rw-r--r--Cargo.toml82
-rw-r--r--Cargo.toml.orig35
l---------LICENSE1
-rw-r--r--LICENSE-APACHE201
-rw-r--r--METADATA20
-rw-r--r--MODULE_LICENSE_APACHE20
-rw-r--r--OWNERS1
-rw-r--r--README.md70
-rw-r--r--cargo2android.json9
-rw-r--r--patches/std.diff15
-rw-r--r--src/error.rs95
-rw-r--r--src/lib.rs61
-rw-r--r--src/private_key.rs220
-rw-r--r--src/private_key/other_prime_info.rs50
-rw-r--r--src/public_key.rs85
-rw-r--r--src/traits.rs213
-rw-r--r--src/version.rs72
-rw-r--r--tests/examples/rsa2048-priv-3prime.derbin0 -> 1244 bytes
-rw-r--r--tests/examples/rsa2048-priv-3prime.pem28
-rw-r--r--tests/examples/rsa2048-priv.derbin0 -> 1191 bytes
-rw-r--r--tests/examples/rsa2048-priv.pem27
-rw-r--r--tests/examples/rsa2048-pub.derbin0 -> 270 bytes
-rw-r--r--tests/examples/rsa2048-pub.pem8
-rw-r--r--tests/examples/rsa4096-priv.derbin0 -> 2349 bytes
-rw-r--r--tests/examples/rsa4096-priv.pem51
-rw-r--r--tests/examples/rsa4096-pub.derbin0 -> 526 bytes
-rw-r--r--tests/examples/rsa4096-pub.pem13
-rw-r--r--tests/private_key.rs94
-rw-r--r--tests/public_key.rs64
-rw-r--r--tests/traits.rs100
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"]
diff --git a/LICENSE b/LICENSE
new file mode 120000
index 0000000..6b579aa
--- /dev/null
+++ b/LICENSE
@@ -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
diff --git a/OWNERS b/OWNERS
new file mode 100644
index 0000000..45dc4dd
--- /dev/null
+++ b/OWNERS
@@ -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
new file mode 100644
index 0000000..eaff675
--- /dev/null
+++ b/tests/examples/rsa2048-priv-3prime.der
Binary files differ
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
new file mode 100644
index 0000000..bbf1876
--- /dev/null
+++ b/tests/examples/rsa2048-priv.der
Binary files differ
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
new file mode 100644
index 0000000..e75261c
--- /dev/null
+++ b/tests/examples/rsa2048-pub.der
Binary files differ
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
new file mode 100644
index 0000000..b154e59
--- /dev/null
+++ b/tests/examples/rsa4096-priv.der
Binary files differ
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-----
+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
+-----END RSA PRIVATE KEY-----
diff --git a/tests/examples/rsa4096-pub.der b/tests/examples/rsa4096-pub.der
new file mode 100644
index 0000000..d8b7c7d
--- /dev/null
+++ b/tests/examples/rsa4096-pub.der
Binary files differ
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);
+}