aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHasini Gunasinghe <hasinitg@google.com>2022-09-10 01:26:49 +0000
committerHasini Gunasinghe <hasinitg@google.com>2022-10-03 23:46:53 +0000
commit985cf046c1b22a092a238bba2ff9d727ba316040 (patch)
tree29e43e56680d1dc6e512bdb3f6b7cff9e0173096
parentbeecde5adda6322530d8004bfc9e0e1dd4257f32 (diff)
downloadpkcs8-985cf046c1b22a092a238bba2ff9d727ba316040.tar.gz
Import platform/external/rust/crates/pkcs8
Bug: 239549209 Test: N/A Change-Id: Iee40a7672e1c8717bd6153b1a5dc868db3fd2532
-rw-r--r--.cargo_vcs_info.json6
-rw-r--r--Android.bp23
-rw-r--r--CHANGELOG.md210
-rw-r--r--Cargo.toml109
-rw-r--r--Cargo.toml.orig43
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.md94
-rw-r--r--cargo2android.json9
-rw-r--r--patches/std.diff15
-rw-r--r--src/encrypted_private_key_info.rs166
-rw-r--r--src/error.rs93
-rw-r--r--src/lib.rs108
-rw-r--r--src/private_key_info.rs293
-rw-r--r--src/traits.rs145
-rw-r--r--src/version.rs63
-rw-r--r--tests/encrypted_private_key.rs234
-rw-r--r--tests/examples/ed25519-encpriv-aes128-pbkdf2-sha1.derbin0 -> 144 bytes
-rw-r--r--tests/examples/ed25519-encpriv-aes256-pbkdf2-sha256.derbin0 -> 158 bytes
-rw-r--r--tests/examples/ed25519-encpriv-aes256-pbkdf2-sha256.pem6
-rw-r--r--tests/examples/ed25519-encpriv-aes256-scrypt.derbin0 -> 151 bytes
-rw-r--r--tests/examples/ed25519-encpriv-aes256-scrypt.pem6
-rw-r--r--tests/examples/ed25519-encpriv-des-pbkdf2-sha256.derbin0 -> 138 bytes
-rw-r--r--tests/examples/ed25519-encpriv-des3-pbkdf2-sha256.derbin0 -> 141 bytes
-rw-r--r--tests/examples/ed25519-priv-pkcs8v1.derbin0 -> 48 bytes
-rw-r--r--tests/examples/ed25519-priv-pkcs8v1.pem3
-rw-r--r--tests/examples/ed25519-priv-pkcs8v2.derbin0 -> 116 bytes
-rw-r--r--tests/examples/ed25519-priv-pkcs8v2.pem5
-rw-r--r--tests/examples/ed25519-pub.derbin0 -> 44 bytes
-rw-r--r--tests/examples/ed25519-pub.pem3
-rw-r--r--tests/examples/p256-priv.derbin0 -> 138 bytes
-rw-r--r--tests/examples/p256-priv.pem5
-rw-r--r--tests/examples/p256-pub.derbin0 -> 91 bytes
-rw-r--r--tests/examples/p256-pub.pem4
-rw-r--r--tests/examples/rsa2048-priv.derbin0 -> 1217 bytes
-rw-r--r--tests/examples/rsa2048-priv.pem28
-rw-r--r--tests/examples/rsa2048-pub.derbin0 -> 294 bytes
-rw-r--r--tests/examples/rsa2048-pub.pem9
-rw-r--r--tests/examples/x25519-priv.derbin0 -> 48 bytes
-rw-r--r--tests/examples/x25519-priv.pem3
-rw-r--r--tests/private_key.rs182
-rw-r--r--tests/traits.rs108
45 files changed, 2196 insertions, 0 deletions
diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json
new file mode 100644
index 0000000..68186e0
--- /dev/null
+++ b/.cargo_vcs_info.json
@@ -0,0 +1,6 @@
+{
+ "git": {
+ "sha1": "974c2c7c88bc8d0541895a6e200c3ea9f91a33af"
+ },
+ "path_in_vcs": "pkcs8"
+} \ No newline at end of file
diff --git a/Android.bp b/Android.bp
new file mode 100644
index 0000000..a838f58
--- /dev/null
+++ b/Android.bp
@@ -0,0 +1,23 @@
+// 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: "libpkcs8",
+ crate_name: "pkcs8",
+ cargo_env_compat: true,
+ cargo_pkg_version: "0.9.0",
+ srcs: ["src/lib.rs"],
+ edition: "2021",
+ features: ["alloc"],
+ rustlibs: [
+ "libder",
+ "libspki",
+ ],
+ 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..15889d9
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,210 @@
+# 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.9.0 (2022-05-08)
+### Added
+- Error conversion support to `pkcs8::spki::Error` ([#335])
+- Re-export `AssociatedOid` ([#645])
+
+### Changed
+- Use `finish_non_exhaustive` in `Debug` impls ([#245])
+- Replace `PrivateKeyDocument` with `der::SecretDocument` ([#571])
+- Bump `der` to v0.6 ([#653])
+- Bump `spki` to v0.6 ([#654])
+- Bump `pkcs5` to v0.5 ([#655])
+
+### Removed
+- `PrivateKeyDocument` ([#571])
+
+[#245]: https://github.com/RustCrypto/formats/pull/245
+[#335]: https://github.com/RustCrypto/formats/pull/335
+[#571]: https://github.com/RustCrypto/formats/pull/571
+[#645]: https://github.com/RustCrypto/formats/pull/645
+[#653]: https://github.com/RustCrypto/formats/pull/653
+[#654]: https://github.com/RustCrypto/formats/pull/654
+[#655]: https://github.com/RustCrypto/formats/pull/655
+
+## 0.8.0 (2021-11-16)
+### Added
+- Re-export `spki` crate ([#210])
+
+### Changed
+- Replace usages of `expect` with fallible methods ([#108])
+- Impl `From*Key`/`To*Key` traits on `Document` types ([#110])
+- Rename `From/ToPrivateKey` => `DecodePrivateKey`/`EncodePrivateKey` ([#121])
+- Rust 2021 edition upgrade; MSRV 1.56 ([#136])
+- Use `der::Document` to impl `*PrivateKeyDocument` ([#140])
+- Rename `Error::Crypto` => `Error::EncryptedPrivateKey` ([#213], [#214])
+- Bump `der` dependency to v0.5 ([#222])
+- Bump `spki` dependency to v0.5 ([#223])
+- Bump `pkcs5` dependency to v0.4 ([#224])
+- Replace `from_pkcs8_private_key_info` with `TryFrom` ([#230])
+
+### Removed
+- `*_with_le` PEM encoding methods ([#109])
+- PKCS#1 support; moved to `pkcs1` crate ([#124])
+- I/O related errors from key format crates ([#158])
+- `der::pem` export ([#211])
+
+[#108]: https://github.com/RustCrypto/formats/pull/108
+[#109]: https://github.com/RustCrypto/formats/pull/109
+[#110]: https://github.com/RustCrypto/formats/pull/110
+[#121]: https://github.com/RustCrypto/formats/pull/121
+[#124]: https://github.com/RustCrypto/formats/pull/124
+[#136]: https://github.com/RustCrypto/formats/pull/136
+[#140]: https://github.com/RustCrypto/formats/pull/140
+[#158]: https://github.com/RustCrypto/formats/pull/158
+[#210]: https://github.com/RustCrypto/formats/pull/210
+[#211]: https://github.com/RustCrypto/formats/pull/211
+[#213]: https://github.com/RustCrypto/formats/pull/213
+[#214]: https://github.com/RustCrypto/formats/pull/214
+[#222]: https://github.com/RustCrypto/formats/pull/222
+[#223]: https://github.com/RustCrypto/formats/pull/223
+[#224]: https://github.com/RustCrypto/formats/pull/224
+[#230]: https://github.com/RustCrypto/formats/pull/230
+
+## 0.7.6 (2021-09-14)
+### Added
+- `3des` and `des-insecure` features
+- `sha1` feature
+- Support for AES-192-CBC
+
+### Changed
+- Moved to `formats` repo ([#2])
+
+[#2]: https://github.com/RustCrypto/formats/pull/2
+
+## 0.7.5 (2021-07-26)
+### Added
+- Support for customizing PEM `LineEnding`
+
+### Changed
+- Bump `pem-rfc7468` dependency to v0.2
+
+## 0.7.4 (2021-07-25)
+### Added
+- PKCS#1 support
+
+## 0.7.3 (2021-07-24)
+### Changed
+- Use `pem-rfc7468` crate
+
+## 0.7.2 (2021-07-20)
+### Added
+- `Error::ParametersMalformed` variant
+
+## 0.7.1 (2021-07-20)
+### Added
+- `Error::KeyMalformed` variant
+
+## 0.7.0 (2021-06-07)
+### Added
+- ASN.1 error improvements
+
+### Changed
+- Merge `OneAsymmetricKey` into `PrivateKeyInfo`
+- Use scrypt as the default PBES2 KDF
+- Return `Result`(s) when encoding
+- Bump `der` to v0.4
+- Bump `spki` to v0.4
+- Bump `pkcs5` to v0.3
+
+## 0.6.1 (2021-05-24)
+### Added
+- Support for RFC5958's `OneAsymmetricKey`
+
+### Changed
+- Bump `der` to v0.3.5
+
+## 0.6.0 (2021-03-22)
+### Changed
+- Bump `der` dependency to v0.3
+- Bump `spki` dependency to v0.3
+- Bump `pkcs5` dependency to v0.2
+
+## 0.5.5 (2021-03-17)
+### Changed
+- Bump `base64ct` dependency to v1.0
+
+## 0.5.4 (2021-02-24)
+### Added
+- Encryption helper methods for `FromPrivateKey`/`ToPrivateKey`
+
+## 0.5.3 (2021-02-23)
+### Added
+- Support for decrypting/encrypting `EncryptedPrivateKeyInfo`
+- PEM support for `EncryptedPrivateKeyInfo`
+- `Error::Crypto` variant
+
+## 0.5.2 (2021-02-20)
+### Changed
+- Use `pkcs5` crate
+
+## 0.5.1 (2021-02-18) [YANKED]
+### Added
+- `pkcs5` feature
+
+### Changed
+- Bump `spki` dependency to v0.2.0
+
+## 0.5.0 (2021-02-16) [YANKED]
+### Added
+- Initial `EncryptedPrivateKeyInfo` support
+
+### Changed
+- Extract SPKI-related types into the `spki` crate
+
+## 0.4.1 (2021-02-01)
+### Changed
+- Bump `basec4ct` dependency to v0.2
+
+## 0.4.0 (2021-01-26)
+### Changed
+- Bump `der` crate dependency to v0.2
+- Use `base64ct` v0.1 for PEM encoding
+
+## 0.3.3 (2020-12-21)
+### Changed
+- Use `der` crate for decoding/encoding ASN.1 DER
+
+## 0.3.2 (2020-12-16)
+### Added
+- `AlgorithmIdentifier::parameters_oid` method
+
+## 0.3.1 (2020-12-16)
+### Changed
+- Bump `const-oid` dependency to v0.4
+
+## 0.3.0 (2020-12-16) [YANKED]
+### Added
+- `AlgorithmParameters` enum
+
+## 0.2.2 (2020-12-14)
+### Fixed
+- Decoding/encoding support for Ed25519 keys
+
+## 0.2.1 (2020-12-14)
+### Added
+- rustdoc improvements
+
+## 0.2.0 (2020-12-14)
+### Added
+- File writing methods for public/private keys
+- Methods for loading `*Document` types from files
+- DER encoding support
+- PEM encoding support
+- `ToPrivateKey`/`ToPublicKey` traits
+
+### Changed
+- `Error` enum
+- Rename `load_*_file` methods to `read_*_file`
+
+## 0.1.1 (2020-12-06)
+### Added
+- Helper methods to load keys from the local filesystem
+
+## 0.1.0 (2020-12-05)
+- Initial release
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..6de007a
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,109 @@
+# 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 = "pkcs8"
+version = "0.9.0"
+authors = ["RustCrypto Developers"]
+description = """
+Pure Rust implementation of Public-Key Cryptography Standards (PKCS) #8:
+Private-Key Information Syntax Specification (RFC 5208), with additional
+support for PKCS#8v2 asymmetric key packages (RFC 5958)
+"""
+readme = "README.md"
+keywords = [
+ "crypto",
+ "key",
+ "pkcs",
+ "private",
+]
+categories = [
+ "cryptography",
+ "data-structures",
+ "encoding",
+ "no-std",
+ "parser-implementations",
+]
+license = "Apache-2.0 OR MIT"
+repository = "https://github.com/RustCrypto/formats/tree/master/pkcs8"
+resolver = "2"
+
+[package.metadata.docs.rs]
+all-features = true
+rustdoc-args = [
+ "--cfg",
+ "docsrs",
+]
+
+[dependencies.der]
+version = "0.6"
+features = ["oid"]
+
+[dependencies.pkcs5]
+version = "0.5"
+optional = true
+
+[dependencies.rand_core]
+version = "0.6"
+optional = true
+default-features = false
+
+[dependencies.spki]
+version = "0.6"
+
+[dependencies.subtle]
+version = "2"
+optional = true
+default-features = false
+
+[dev-dependencies.hex-literal]
+version = "0.3"
+
+[dev-dependencies.tempfile]
+version = "3"
+
+[features]
+3des = [
+ "encryption",
+ "pkcs5/3des",
+]
+alloc = [
+ "der/alloc",
+ "der/zeroize",
+ "spki/alloc",
+]
+des-insecure = [
+ "encryption",
+ "pkcs5/des-insecure",
+]
+encryption = [
+ "alloc",
+ "pkcs5/alloc",
+ "pkcs5/pbes2",
+ "rand_core",
+]
+getrandom = ["rand_core/getrandom"]
+pem = [
+ "alloc",
+ "der/pem",
+ "spki/pem",
+]
+sha1 = [
+ "encryption",
+ "pkcs5/sha1",
+]
+std = [
+ "alloc",
+ "der/std",
+ "spki/std",
+]
diff --git a/Cargo.toml.orig b/Cargo.toml.orig
new file mode 100644
index 0000000..c5e821a
--- /dev/null
+++ b/Cargo.toml.orig
@@ -0,0 +1,43 @@
+[package]
+name = "pkcs8"
+version = "0.9.0" # Also update html_root_url in lib.rs when bumping this
+description = """
+Pure Rust implementation of Public-Key Cryptography Standards (PKCS) #8:
+Private-Key Information Syntax Specification (RFC 5208), with additional
+support for PKCS#8v2 asymmetric key packages (RFC 5958)
+"""
+authors = ["RustCrypto Developers"]
+license = "Apache-2.0 OR MIT"
+repository = "https://github.com/RustCrypto/formats/tree/master/pkcs8"
+categories = ["cryptography", "data-structures", "encoding", "no-std", "parser-implementations"]
+keywords = ["crypto", "key", "pkcs", "private"]
+readme = "README.md"
+edition = "2021"
+rust-version = "1.57"
+
+[dependencies]
+der = { version = "0.6", features = ["oid"], path = "../der" }
+spki = { version = "0.6", path = "../spki" }
+
+# optional dependencies
+rand_core = { version = "0.6", optional = true, default-features = false }
+pkcs5 = { version = "0.5", optional = true, path = "../pkcs5" }
+subtle = { version = "2", optional = true, default-features = false }
+
+[dev-dependencies]
+hex-literal = "0.3"
+tempfile = "3"
+
+[features]
+alloc = ["der/alloc", "der/zeroize", "spki/alloc"]
+3des = ["encryption", "pkcs5/3des"]
+des-insecure = ["encryption", "pkcs5/des-insecure"]
+encryption = ["alloc", "pkcs5/alloc", "pkcs5/pbes2", "rand_core"]
+getrandom = ["rand_core/getrandom"]
+pem = ["alloc", "der/pem", "spki/pem"]
+sha1 = ["encryption", "pkcs5/sha1"]
+std = ["alloc", "der/std", "spki/std"]
+
+[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..38059a2
--- /dev/null
+++ b/METADATA
@@ -0,0 +1,20 @@
+name: "pkcs8"
+description: "Pure Rust implementation of Public-Key Cryptography Standards (PKCS) #8: Private-Key Information Syntax Specification (RFC 5208)."
+third_party {
+ url {
+ type: HOMEPAGE
+ value: "https://crates.io/crates/pkcs8"
+ }
+ url {
+ type: ARCHIVE
+ value: "https://static.crates.io/crates/pkcs8/pkcs8-0.9.0.crate"
+ }
+ version: "0.9.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..2fa301c
--- /dev/null
+++ b/README.md
@@ -0,0 +1,94 @@
+# [RustCrypto]: PKCS#8 (Private Keys)
+
+[![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) #8:
+Private-Key Information Syntax Specification ([RFC 5208]).
+
+[Documentation][docs-link]
+
+## About PKCS#8
+
+PKCS#8 is a format for cryptographic private keys, often containing pairs
+of private and public keys.
+
+You can identify a PKCS#8 private key encoded as PEM (i.e. text) by the
+following:
+
+```text
+-----BEGIN PRIVATE KEY-----
+```
+
+PKCS#8 private keys can optionally be encrypted under a password using
+key derivation algorithms like PBKDF2 and [scrypt], and encrypted with
+ciphers like AES-CBC. When a PKCS#8 private key has been encrypted,
+it starts with the following:
+
+```text
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+```
+
+PKCS#8 private keys can also be serialized in an ASN.1-based binary format.
+The PEM text encoding is a Base64 representation of this format.
+
+## Supported Algorithms
+
+This crate is implemented in an algorithm-agnostic manner with the goal of
+enabling PKCS#8 support for any algorithm.
+
+That said, it has been tested for interoperability against keys generated by
+OpenSSL for the following algorithms:
+
+- ECC (`id-ecPublicKey`)
+- Ed25519 (`id-Ed25519`)
+- RSA (`id-rsaEncryption`)
+- X25519 (`id-X25519`)
+
+Please open an issue if you encounter trouble using it with a particular
+algorithm, including the ones listed above or other algorithms.
+
+## 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/pkcs8
+[crate-link]: https://crates.io/crates/pkcs8
+[docs-image]: https://docs.rs/pkcs8/badge.svg
+[docs-link]: https://docs.rs/pkcs8/
+[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/pkcs8/badge.svg?branch=master&event=push
+[build-link]: https://github.com/RustCrypto/formats/actions
+
+[//]: # (links)
+
+[RustCrypto]: https://github.com/rustcrypto
+[RFC 5208]: https://tools.ietf.org/html/rfc5208
+[scrypt]: https://en.wikipedia.org/wiki/Scrypt
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..6a318cb
--- /dev/null
+++ b/patches/std.diff
@@ -0,0 +1,15 @@
+diff --git a/src/lib.rs b/src/lib.rs
+index 1d2dfa2..9fcae3a 100644
+--- a/src/lib.rs
++++ b/src/lib.rs
+@@ -64,6 +64,10 @@
+ //! [PKCS#5v2 Password Based Encryption Scheme 2 (RFC 8018)]: https://tools.ietf.org/html/rfc8018#section-6.2
+ //! [scrypt]: https://en.wikipedia.org/wiki/Scrypt
+
++/// Local Android change: Use std to allow building as a dylib.
++#[cfg(android_dylib)]
++extern crate std;
++
+ #[cfg(feature = "pem")]
+ extern crate alloc;
+ #[cfg(feature = "std")]
diff --git a/src/encrypted_private_key_info.rs b/src/encrypted_private_key_info.rs
new file mode 100644
index 0000000..460e3f6
--- /dev/null
+++ b/src/encrypted_private_key_info.rs
@@ -0,0 +1,166 @@
+//! PKCS#8 `EncryptedPrivateKeyInfo`
+
+use crate::{Error, Result};
+use core::fmt;
+use der::{asn1::OctetStringRef, Decode, DecodeValue, Encode, Header, Reader, Sequence};
+use pkcs5::EncryptionScheme;
+
+#[cfg(feature = "alloc")]
+use der::SecretDocument;
+
+#[cfg(feature = "encryption")]
+use {
+ pkcs5::pbes2,
+ rand_core::{CryptoRng, RngCore},
+};
+
+#[cfg(feature = "pem")]
+use der::pem::PemLabel;
+
+/// PKCS#8 `EncryptedPrivateKeyInfo`.
+///
+/// ASN.1 structure containing a PKCS#5 [`EncryptionScheme`] identifier for a
+/// password-based symmetric encryption scheme and encrypted private key data.
+///
+/// ## Schema
+/// Structure described in [RFC 5208 Section 6]:
+///
+/// ```text
+/// EncryptedPrivateKeyInfo ::= SEQUENCE {
+/// encryptionAlgorithm EncryptionAlgorithmIdentifier,
+/// encryptedData EncryptedData }
+///
+/// EncryptionAlgorithmIdentifier ::= AlgorithmIdentifier
+///
+/// EncryptedData ::= OCTET STRING
+/// ```
+///
+/// [RFC 5208 Section 6]: https://tools.ietf.org/html/rfc5208#section-6
+#[cfg_attr(docsrs, doc(cfg(feature = "pkcs5")))]
+#[derive(Clone, Eq, PartialEq)]
+pub struct EncryptedPrivateKeyInfo<'a> {
+ /// Algorithm identifier describing a password-based symmetric encryption
+ /// scheme used to encrypt the `encrypted_data` field.
+ pub encryption_algorithm: EncryptionScheme<'a>,
+
+ /// Private key data
+ pub encrypted_data: &'a [u8],
+}
+
+impl<'a> EncryptedPrivateKeyInfo<'a> {
+ /// Attempt to decrypt this encrypted private key using the provided
+ /// password to derive an encryption key.
+ #[cfg(feature = "encryption")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "encryption")))]
+ pub fn decrypt(&self, password: impl AsRef<[u8]>) -> Result<SecretDocument> {
+ Ok(self
+ .encryption_algorithm
+ .decrypt(password, self.encrypted_data)?
+ .try_into()?)
+ }
+
+ /// Encrypt the given ASN.1 DER document using a symmetric encryption key
+ /// derived from the provided password.
+ #[cfg(feature = "encryption")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "encryption")))]
+ pub(crate) fn encrypt(
+ mut rng: impl CryptoRng + RngCore,
+ password: impl AsRef<[u8]>,
+ doc: &[u8],
+ ) -> Result<SecretDocument> {
+ let mut salt = [0u8; 16];
+ rng.fill_bytes(&mut salt);
+
+ let mut iv = [0u8; 16];
+ rng.fill_bytes(&mut iv);
+
+ let pbes2_params = pbes2::Parameters::scrypt_aes256cbc(Default::default(), &salt, &iv)?;
+ EncryptedPrivateKeyInfo::encrypt_with(pbes2_params, password, doc)
+ }
+
+ /// Encrypt this private key using a symmetric encryption key derived
+ /// from the provided password and [`pbes2::Parameters`].
+ #[cfg(feature = "encryption")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "encryption")))]
+ pub(crate) fn encrypt_with(
+ pbes2_params: pbes2::Parameters<'a>,
+ password: impl AsRef<[u8]>,
+ doc: &[u8],
+ ) -> Result<SecretDocument> {
+ let encrypted_data = pbes2_params.encrypt(password, doc)?;
+
+ EncryptedPrivateKeyInfo {
+ encryption_algorithm: pbes2_params.into(),
+ encrypted_data: &encrypted_data,
+ }
+ .try_into()
+ }
+}
+
+impl<'a> DecodeValue<'a> for EncryptedPrivateKeyInfo<'a> {
+ fn decode_value<R: Reader<'a>>(
+ reader: &mut R,
+ header: Header,
+ ) -> der::Result<EncryptedPrivateKeyInfo<'a>> {
+ reader.read_nested(header.length, |reader| {
+ Ok(Self {
+ encryption_algorithm: reader.decode()?,
+ encrypted_data: OctetStringRef::decode(reader)?.as_bytes(),
+ })
+ })
+ }
+}
+
+impl<'a> Sequence<'a> for EncryptedPrivateKeyInfo<'a> {
+ fn fields<F, T>(&self, f: F) -> der::Result<T>
+ where
+ F: FnOnce(&[&dyn Encode]) -> der::Result<T>,
+ {
+ f(&[
+ &self.encryption_algorithm,
+ &OctetStringRef::new(self.encrypted_data)?,
+ ])
+ }
+}
+
+impl<'a> TryFrom<&'a [u8]> for EncryptedPrivateKeyInfo<'a> {
+ type Error = Error;
+
+ fn try_from(bytes: &'a [u8]) -> Result<Self> {
+ Ok(Self::from_der(bytes)?)
+ }
+}
+
+impl<'a> fmt::Debug for EncryptedPrivateKeyInfo<'a> {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ f.debug_struct("EncryptedPrivateKeyInfo")
+ .field("encryption_algorithm", &self.encryption_algorithm)
+ .finish_non_exhaustive()
+ }
+}
+
+#[cfg(feature = "alloc")]
+#[cfg_attr(docsrs, doc(cfg(all(feature = "alloc", feature = "pkcs5"))))]
+impl TryFrom<EncryptedPrivateKeyInfo<'_>> for SecretDocument {
+ type Error = Error;
+
+ fn try_from(encrypted_private_key: EncryptedPrivateKeyInfo<'_>) -> Result<SecretDocument> {
+ SecretDocument::try_from(&encrypted_private_key)
+ }
+}
+
+#[cfg(feature = "alloc")]
+#[cfg_attr(docsrs, doc(cfg(all(feature = "alloc", feature = "pkcs5"))))]
+impl TryFrom<&EncryptedPrivateKeyInfo<'_>> for SecretDocument {
+ type Error = Error;
+
+ fn try_from(encrypted_private_key: &EncryptedPrivateKeyInfo<'_>) -> Result<SecretDocument> {
+ Ok(Self::encode_msg(encrypted_private_key)?)
+ }
+}
+
+#[cfg(feature = "pem")]
+#[cfg_attr(docsrs, doc(cfg(feature = "pem")))]
+impl PemLabel for EncryptedPrivateKeyInfo<'_> {
+ const PEM_LABEL: &'static str = "ENCRYPTED PRIVATE KEY";
+}
diff --git a/src/error.rs b/src/error.rs
new file mode 100644
index 0000000..bc4c2ea
--- /dev/null
+++ b/src/error.rs
@@ -0,0 +1,93 @@
+//! 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),
+
+ /// Errors relating to PKCS#5-encrypted keys.
+ #[cfg(feature = "pkcs5")]
+ EncryptedPrivateKey(pkcs5::Error),
+
+ /// Malformed cryptographic key contained in a PKCS#8 document.
+ ///
+ /// This is intended for relaying errors related to the raw data contained
+ /// within [`PrivateKeyInfo::private_key`][`crate::PrivateKeyInfo::private_key`]
+ /// or [`SubjectPublicKeyInfo::subject_public_key`][`crate::SubjectPublicKeyInfo::subject_public_key`].
+ KeyMalformed,
+
+ /// [`AlgorithmIdentifier::parameters`][`crate::AlgorithmIdentifier::parameters`]
+ /// is malformed or otherwise encoded in an unexpected manner.
+ ParametersMalformed,
+
+ /// Public key errors propagated from the [`spki::Error`] type.
+ PublicKey(spki::Error),
+}
+
+impl fmt::Display for Error {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Error::Asn1(err) => write!(f, "PKCS#8 ASN.1 error: {}", err),
+ #[cfg(feature = "pkcs5")]
+ Error::EncryptedPrivateKey(err) => write!(f, "{}", err),
+ Error::KeyMalformed => f.write_str("PKCS#8 cryptographic key data malformed"),
+ Error::ParametersMalformed => f.write_str("PKCS#8 algorithm parameters malformed"),
+ Error::PublicKey(err) => write!(f, "public key error: {}", err),
+ }
+ }
+}
+
+#[cfg(feature = "std")]
+impl std::error::Error for Error {}
+
+impl From<der::Error> for Error {
+ fn from(err: der::Error) -> Error {
+ Error::Asn1(err)
+ }
+}
+
+impl From<der::ErrorKind> for Error {
+ fn from(err: der::ErrorKind) -> Error {
+ Error::Asn1(err.into())
+ }
+}
+
+#[cfg(feature = "pem")]
+impl From<pem::Error> for Error {
+ fn from(err: pem::Error) -> Error {
+ der::Error::from(err).into()
+ }
+}
+
+#[cfg(feature = "pkcs5")]
+impl From<pkcs5::Error> for Error {
+ fn from(err: pkcs5::Error) -> Error {
+ Error::EncryptedPrivateKey(err)
+ }
+}
+
+impl From<spki::Error> for Error {
+ fn from(err: spki::Error) -> Error {
+ Error::PublicKey(err)
+ }
+}
+
+impl From<Error> for spki::Error {
+ fn from(err: Error) -> spki::Error {
+ match err {
+ Error::Asn1(e) => spki::Error::Asn1(e),
+ Error::PublicKey(e) => e,
+ _ => spki::Error::KeyMalformed,
+ }
+ }
+}
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..9fcae3a
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,108 @@
+#![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/pkcs8/0.9.0-pre"
+)]
+#![forbid(unsafe_code, clippy::unwrap_used)]
+#![warn(missing_docs, rust_2018_idioms, unused_qualifications)]
+
+//! ## About this crate
+//! This library provides generalized PKCS#8 support designed to work with a
+//! number of different algorithms. It supports `no_std` platforms including
+//! ones without a heap (albeit with reduced functionality).
+//!
+//! It supports decoding/encoding the following types:
+//!
+//! - [`EncryptedPrivateKeyInfo`]: (with `pkcs5` feature) encrypted key.
+//! - [`PrivateKeyInfo`]: algorithm identifier and data representing a private key.
+//! Optionally also includes public key data for asymmetric keys.
+//! - [`SubjectPublicKeyInfo`]: algorithm identifier and data representing a public key
+//! (re-exported from the [`spki`] crate)
+//!
+//! When the `pem` feature is enabled, it also supports decoding/encoding
+//! documents from "PEM encoding" format as defined in RFC 7468.
+//!
+//! ## Encrypted Private Key Support
+//! [`EncryptedPrivateKeyInfo`] supports decoding/encoding encrypted PKCS#8
+//! private keys and is gated under the `pkcs5` feature.
+//!
+//! When the `encryption` feature of this crate is enabled, it provides
+//! [`EncryptedPrivateKeyInfo::decrypt`] and [`PrivateKeyInfo::encrypt`]
+//! functions which are able to decrypt/encrypt keys using the following
+//! algorithms:
+//!
+//! - [PKCS#5v2 Password Based Encryption Scheme 2 (RFC 8018)]
+//! - Key derivation functions:
+//! - [scrypt] ([RFC 7914])
+//! - PBKDF2 ([RFC 8018](https://datatracker.ietf.org/doc/html/rfc8018#section-5.2))
+//! - SHA-2 based PRF with HMAC-SHA224, HMAC-SHA256, HMAC-SHA384, or HMAC-SHA512
+//! - SHA-1 based PRF with HMAC-SHA1, when the `sha1` feature of this crate is enabled.
+//! - Symmetric encryption: AES-128-CBC, AES-192-CBC, or AES-256-CBC
+//! (best available options for PKCS#5v2)
+//!
+//! ## Legacy DES-CBC and DES-EDE3-CBC (3DES) support (optional)
+//! When the `des-insecure` and/or `3des` features are enabled this crate provides support for
+//! private keys encrypted with with DES-CBC and DES-EDE3-CBC (3DES or Triple DES) symmetric
+//! encryption, respectively.
+//!
+//! ⚠️ WARNING ⚠️
+//!
+//! DES support (gated behind the `des-insecure` feature) is implemented to
+//! allow for decryption of legacy PKCS#8 files only.
+//!
+//! Such PKCS#8 documents should be considered *INSECURE* due to the short
+//! 56-bit key size of DES.
+//!
+//! New keys should use AES instead.
+//!
+//! [RFC 5208]: https://tools.ietf.org/html/rfc5208
+//! [RFC 5958]: https://tools.ietf.org/html/rfc5958
+//! [RFC 7914]: https://datatracker.ietf.org/doc/html/rfc7914
+//! [PKCS#5v2 Password Based Encryption Scheme 2 (RFC 8018)]: https://tools.ietf.org/html/rfc8018#section-6.2
+//! [scrypt]: https://en.wikipedia.org/wiki/Scrypt
+
+/// Local Android change: Use std to allow building as a dylib.
+#[cfg(android_dylib)]
+extern crate std;
+
+#[cfg(feature = "pem")]
+extern crate alloc;
+#[cfg(feature = "std")]
+extern crate std;
+
+mod error;
+mod private_key_info;
+mod traits;
+mod version;
+
+#[cfg(feature = "pkcs5")]
+pub(crate) mod encrypted_private_key_info;
+
+pub use crate::{
+ error::{Error, Result},
+ private_key_info::PrivateKeyInfo,
+ traits::DecodePrivateKey,
+ version::Version,
+};
+pub use der::{self, asn1::ObjectIdentifier, oid::AssociatedOid};
+pub use spki::{self, AlgorithmIdentifier, DecodePublicKey, SubjectPublicKeyInfo};
+
+#[cfg(feature = "alloc")]
+pub use {
+ crate::traits::EncodePrivateKey,
+ der::{Document, SecretDocument},
+ spki::EncodePublicKey,
+};
+
+#[cfg(feature = "pem")]
+#[cfg_attr(docsrs, doc(cfg(feature = "pem")))]
+pub use der::pem::LineEnding;
+
+#[cfg(feature = "pkcs5")]
+pub use {encrypted_private_key_info::EncryptedPrivateKeyInfo, pkcs5};
+
+#[cfg(feature = "rand_core")]
+pub use rand_core;
diff --git a/src/private_key_info.rs b/src/private_key_info.rs
new file mode 100644
index 0000000..52f0878
--- /dev/null
+++ b/src/private_key_info.rs
@@ -0,0 +1,293 @@
+//! PKCS#8 `PrivateKeyInfo`.
+
+use crate::{AlgorithmIdentifier, Error, Result, Version};
+use core::fmt;
+use der::{
+ asn1::{AnyRef, BitStringRef, ContextSpecific, OctetStringRef},
+ Decode, DecodeValue, Encode, Header, Reader, Sequence, TagMode, TagNumber,
+};
+
+#[cfg(feature = "alloc")]
+use der::SecretDocument;
+
+#[cfg(feature = "encryption")]
+use {
+ crate::EncryptedPrivateKeyInfo,
+ der::zeroize::Zeroizing,
+ pkcs5::pbes2,
+ rand_core::{CryptoRng, RngCore},
+};
+
+#[cfg(feature = "pem")]
+use der::pem::PemLabel;
+
+#[cfg(feature = "subtle")]
+use subtle::{Choice, ConstantTimeEq};
+
+/// Context-specific tag number for the public key.
+const PUBLIC_KEY_TAG: TagNumber = TagNumber::N1;
+
+/// PKCS#8 `PrivateKeyInfo`.
+///
+/// ASN.1 structure containing an [`AlgorithmIdentifier`], private key
+/// data in an algorithm specific format, and optional attributes
+/// (ignored by this implementation).
+///
+/// Supports PKCS#8 v1 as described in [RFC 5208] and PKCS#8 v2 as described
+/// in [RFC 5958]. PKCS#8 v2 keys include an additional public key field.
+///
+/// # PKCS#8 v1 `PrivateKeyInfo`
+///
+/// Described in [RFC 5208 Section 5]:
+///
+/// ```text
+/// PrivateKeyInfo ::= SEQUENCE {
+/// version Version,
+/// privateKeyAlgorithm PrivateKeyAlgorithmIdentifier,
+/// privateKey PrivateKey,
+/// attributes [0] IMPLICIT Attributes OPTIONAL }
+///
+/// Version ::= INTEGER
+///
+/// PrivateKeyAlgorithmIdentifier ::= AlgorithmIdentifier
+///
+/// PrivateKey ::= OCTET STRING
+///
+/// Attributes ::= SET OF Attribute
+/// ```
+///
+/// # PKCS#8 v2 `OneAsymmetricKey`
+///
+/// PKCS#8 `OneAsymmetricKey` as described in [RFC 5958 Section 2]:
+///
+/// ```text
+/// PrivateKeyInfo ::= OneAsymmetricKey
+///
+/// OneAsymmetricKey ::= SEQUENCE {
+/// version Version,
+/// privateKeyAlgorithm PrivateKeyAlgorithmIdentifier,
+/// privateKey PrivateKey,
+/// attributes [0] Attributes OPTIONAL,
+/// ...,
+/// [[2: publicKey [1] PublicKey OPTIONAL ]],
+/// ...
+/// }
+///
+/// Version ::= INTEGER { v1(0), v2(1) } (v1, ..., v2)
+///
+/// PrivateKeyAlgorithmIdentifier ::= AlgorithmIdentifier
+///
+/// PrivateKey ::= OCTET STRING
+///
+/// Attributes ::= SET OF Attribute
+///
+/// PublicKey ::= BIT STRING
+/// ```
+///
+/// [RFC 5208]: https://tools.ietf.org/html/rfc5208
+/// [RFC 5958]: https://datatracker.ietf.org/doc/html/rfc5958
+/// [RFC 5208 Section 5]: https://tools.ietf.org/html/rfc5208#section-5
+/// [RFC 5958 Section 2]: https://datatracker.ietf.org/doc/html/rfc5958#section-2
+#[derive(Clone)]
+pub struct PrivateKeyInfo<'a> {
+ /// X.509 [`AlgorithmIdentifier`] for the private key type.
+ pub algorithm: AlgorithmIdentifier<'a>,
+
+ /// Private key data.
+ pub private_key: &'a [u8],
+
+ /// Public key data, optionally available if version is V2.
+ pub public_key: Option<&'a [u8]>,
+}
+
+impl<'a> PrivateKeyInfo<'a> {
+ /// Create a new PKCS#8 [`PrivateKeyInfo`] message.
+ ///
+ /// This is a helper method which initializes `attributes` and `public_key`
+ /// to `None`, helpful if you aren't using those.
+ pub fn new(algorithm: AlgorithmIdentifier<'a>, private_key: &'a [u8]) -> Self {
+ Self {
+ algorithm,
+ private_key,
+ public_key: None,
+ }
+ }
+
+ /// Get the PKCS#8 [`Version`] for this structure.
+ ///
+ /// [`Version::V1`] if `public_key` is `None`, [`Version::V2`] if `Some`.
+ pub fn version(&self) -> Version {
+ if self.public_key.is_some() {
+ Version::V2
+ } else {
+ Version::V1
+ }
+ }
+
+ /// Encrypt this private key using a symmetric encryption key derived
+ /// from the provided password.
+ ///
+ /// Uses the following algorithms for encryption:
+ /// - PBKDF: scrypt with default parameters:
+ /// - log₂(N): 15
+ /// - r: 8
+ /// - p: 1
+ /// - Cipher: AES-256-CBC (best available option for PKCS#5 encryption)
+ #[cfg(feature = "encryption")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "encryption")))]
+ pub fn encrypt(
+ &self,
+ rng: impl CryptoRng + RngCore,
+ password: impl AsRef<[u8]>,
+ ) -> Result<SecretDocument> {
+ let der = Zeroizing::new(self.to_vec()?);
+ EncryptedPrivateKeyInfo::encrypt(rng, password, der.as_ref())
+ }
+
+ /// Encrypt this private key using a symmetric encryption key derived
+ /// from the provided password and [`pbes2::Parameters`].
+ #[cfg(feature = "encryption")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "encryption")))]
+ pub fn encrypt_with_params(
+ &self,
+ pbes2_params: pbes2::Parameters<'_>,
+ password: impl AsRef<[u8]>,
+ ) -> Result<SecretDocument> {
+ let der = Zeroizing::new(self.to_vec()?);
+ EncryptedPrivateKeyInfo::encrypt_with(pbes2_params, password, der.as_ref())
+ }
+}
+
+impl<'a> DecodeValue<'a> for PrivateKeyInfo<'a> {
+ fn decode_value<R: Reader<'a>>(
+ reader: &mut R,
+ header: Header,
+ ) -> der::Result<PrivateKeyInfo<'a>> {
+ reader.read_nested(header.length, |reader| {
+ // Parse and validate `version` INTEGER.
+ let version = Version::decode(reader)?;
+ let algorithm = reader.decode()?;
+ let private_key = OctetStringRef::decode(reader)?.into();
+ let public_key = reader
+ .context_specific::<BitStringRef<'_>>(PUBLIC_KEY_TAG, TagMode::Implicit)?
+ .map(|bs| {
+ bs.as_bytes()
+ .ok_or_else(|| der::Tag::BitString.value_error())
+ })
+ .transpose()?;
+
+ if version.has_public_key() != public_key.is_some() {
+ return Err(reader.error(
+ der::Tag::ContextSpecific {
+ constructed: true,
+ number: PUBLIC_KEY_TAG,
+ }
+ .value_error()
+ .kind(),
+ ));
+ }
+
+ // Ignore any remaining extension fields
+ while !reader.is_finished() {
+ reader.decode::<ContextSpecific<AnyRef<'_>>>()?;
+ }
+
+ Ok(Self {
+ algorithm,
+ private_key,
+ public_key,
+ })
+ })
+ }
+}
+
+impl<'a> Sequence<'a> for PrivateKeyInfo<'a> {
+ fn fields<F, T>(&self, f: F) -> der::Result<T>
+ where
+ F: FnOnce(&[&dyn Encode]) -> der::Result<T>,
+ {
+ f(&[
+ &u8::from(self.version()),
+ &self.algorithm,
+ &OctetStringRef::new(self.private_key)?,
+ &self
+ .public_key
+ .map(|pk| {
+ BitStringRef::from_bytes(pk).map(|value| ContextSpecific {
+ tag_number: PUBLIC_KEY_TAG,
+ tag_mode: TagMode::Implicit,
+ value,
+ })
+ })
+ .transpose()?,
+ ])
+ }
+}
+
+impl<'a> TryFrom<&'a [u8]> for PrivateKeyInfo<'a> {
+ type Error = Error;
+
+ fn try_from(bytes: &'a [u8]) -> Result<Self> {
+ Ok(Self::from_der(bytes)?)
+ }
+}
+
+impl<'a> fmt::Debug for PrivateKeyInfo<'a> {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ f.debug_struct("PrivateKeyInfo")
+ .field("version", &self.version())
+ .field("algorithm", &self.algorithm)
+ .field("public_key", &self.public_key)
+ .finish_non_exhaustive()
+ }
+}
+
+#[cfg(feature = "alloc")]
+#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
+impl TryFrom<PrivateKeyInfo<'_>> for SecretDocument {
+ type Error = Error;
+
+ fn try_from(private_key: PrivateKeyInfo<'_>) -> Result<SecretDocument> {
+ SecretDocument::try_from(&private_key)
+ }
+}
+
+#[cfg(feature = "alloc")]
+#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
+impl TryFrom<&PrivateKeyInfo<'_>> for SecretDocument {
+ type Error = Error;
+
+ fn try_from(private_key: &PrivateKeyInfo<'_>) -> Result<SecretDocument> {
+ Ok(Self::encode_msg(private_key)?)
+ }
+}
+
+#[cfg(feature = "pem")]
+#[cfg_attr(docsrs, doc(cfg(feature = "pem")))]
+impl PemLabel for PrivateKeyInfo<'_> {
+ const PEM_LABEL: &'static str = "PRIVATE KEY";
+}
+
+#[cfg(feature = "subtle")]
+#[cfg_attr(docsrs, doc(cfg(feature = "subtle")))]
+impl<'a> ConstantTimeEq for PrivateKeyInfo<'a> {
+ fn ct_eq(&self, other: &Self) -> Choice {
+ // NOTE: public fields are not compared in constant time
+ let public_fields_eq =
+ self.algorithm == other.algorithm && self.public_key == other.public_key;
+
+ self.private_key.ct_eq(other.private_key) & Choice::from(public_fields_eq as u8)
+ }
+}
+
+#[cfg(feature = "subtle")]
+#[cfg_attr(docsrs, doc(cfg(feature = "subtle")))]
+impl<'a> Eq for PrivateKeyInfo<'a> {}
+
+#[cfg(feature = "subtle")]
+#[cfg_attr(docsrs, doc(cfg(feature = "subtle")))]
+impl<'a> PartialEq for PrivateKeyInfo<'a> {
+ fn eq(&self, other: &Self) -> bool {
+ self.ct_eq(other).into()
+ }
+}
diff --git a/src/traits.rs b/src/traits.rs
new file mode 100644
index 0000000..dd86b90
--- /dev/null
+++ b/src/traits.rs
@@ -0,0 +1,145 @@
+//! Traits for parsing objects from PKCS#8 encoded documents
+
+use crate::{Error, PrivateKeyInfo, Result};
+
+#[cfg(feature = "alloc")]
+use der::SecretDocument;
+
+#[cfg(feature = "encryption")]
+use {
+ crate::EncryptedPrivateKeyInfo,
+ rand_core::{CryptoRng, RngCore},
+};
+
+#[cfg(feature = "pem")]
+use {crate::LineEnding, alloc::string::String, der::zeroize::Zeroizing};
+
+#[cfg(feature = "pem")]
+use der::pem::PemLabel;
+
+#[cfg(feature = "std")]
+use std::path::Path;
+
+/// Parse a private key object from a PKCS#8 encoded document.
+pub trait DecodePrivateKey: for<'a> TryFrom<PrivateKeyInfo<'a>, Error = Error> + Sized {
+ /// Deserialize PKCS#8 private key from ASN.1 DER-encoded data
+ /// (binary format).
+ fn from_pkcs8_der(bytes: &[u8]) -> Result<Self> {
+ Self::try_from(PrivateKeyInfo::try_from(bytes)?)
+ }
+
+ /// Deserialize encrypted PKCS#8 private key from ASN.1 DER-encoded data
+ /// (binary format) and attempt to decrypt it using the provided password.
+ #[cfg(feature = "encryption")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "encryption")))]
+ fn from_pkcs8_encrypted_der(bytes: &[u8], password: impl AsRef<[u8]>) -> Result<Self> {
+ let doc = EncryptedPrivateKeyInfo::try_from(bytes)?.decrypt(password)?;
+ Self::from_pkcs8_der(doc.as_bytes())
+ }
+
+ /// Deserialize PKCS#8-encoded private key from PEM.
+ ///
+ /// Keys in this format begin with the following delimiter:
+ ///
+ /// ```text
+ /// -----BEGIN PRIVATE KEY-----
+ /// ```
+ #[cfg(feature = "pem")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "pem")))]
+ fn from_pkcs8_pem(s: &str) -> Result<Self> {
+ let (label, doc) = SecretDocument::from_pem(s)?;
+ PrivateKeyInfo::validate_pem_label(label)?;
+ Self::from_pkcs8_der(doc.as_bytes())
+ }
+
+ /// Deserialize encrypted PKCS#8-encoded private key from PEM and attempt
+ /// to decrypt it using the provided password.
+ ///
+ /// Keys in this format begin with the following delimiter:
+ ///
+ /// ```text
+ /// -----BEGIN ENCRYPTED PRIVATE KEY-----
+ /// ```
+ #[cfg(all(feature = "encryption", feature = "pem"))]
+ #[cfg_attr(docsrs, doc(cfg(all(feature = "encryption", feature = "pem"))))]
+ fn from_pkcs8_encrypted_pem(s: &str, password: impl AsRef<[u8]>) -> Result<Self> {
+ let (label, doc) = SecretDocument::from_pem(s)?;
+ EncryptedPrivateKeyInfo::validate_pem_label(label)?;
+ Self::from_pkcs8_encrypted_der(doc.as_bytes(), password)
+ }
+
+ /// Load PKCS#8 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_pkcs8_der_file(path: impl AsRef<Path>) -> Result<Self> {
+ Self::from_pkcs8_der(SecretDocument::read_der_file(path)?.as_bytes())
+ }
+
+ /// Load PKCS#8 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_pkcs8_pem_file(path: impl AsRef<Path>) -> Result<Self> {
+ let (label, doc) = SecretDocument::read_pem_file(path)?;
+ PrivateKeyInfo::validate_pem_label(&label)?;
+ Self::from_pkcs8_der(doc.as_bytes())
+ }
+}
+
+/// Serialize a private key object to a PKCS#8 encoded document.
+#[cfg(feature = "alloc")]
+#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
+pub trait EncodePrivateKey {
+ /// Serialize a [`SecretDocument`] containing a PKCS#8-encoded private key.
+ fn to_pkcs8_der(&self) -> Result<SecretDocument>;
+
+ /// Create an [`SecretDocument`] containing the ciphertext of
+ /// a PKCS#8 encoded private key encrypted under the given `password`.
+ #[cfg(feature = "encryption")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "encryption")))]
+ fn to_pkcs8_encrypted_der(
+ &self,
+ rng: impl CryptoRng + RngCore,
+ password: impl AsRef<[u8]>,
+ ) -> Result<SecretDocument> {
+ EncryptedPrivateKeyInfo::encrypt(rng, password, self.to_pkcs8_der()?.as_bytes())
+ }
+
+ /// Serialize this private key as PEM-encoded PKCS#8 with the given [`LineEnding`].
+ #[cfg(feature = "pem")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "pem")))]
+ fn to_pkcs8_pem(&self, line_ending: LineEnding) -> Result<Zeroizing<String>> {
+ let doc = self.to_pkcs8_der()?;
+ Ok(doc.to_pem(PrivateKeyInfo::PEM_LABEL, line_ending)?)
+ }
+
+ /// Serialize this private key as an encrypted PEM-encoded PKCS#8 private
+ /// key using the `provided` to derive an encryption key.
+ #[cfg(all(feature = "encryption", feature = "pem"))]
+ #[cfg_attr(docsrs, doc(cfg(all(feature = "encryption", feature = "pem"))))]
+ fn to_pkcs8_encrypted_pem(
+ &self,
+ rng: impl CryptoRng + RngCore,
+ password: impl AsRef<[u8]>,
+ line_ending: LineEnding,
+ ) -> Result<Zeroizing<String>> {
+ let doc = self.to_pkcs8_encrypted_der(rng, password)?;
+ Ok(doc.to_pem(EncryptedPrivateKeyInfo::PEM_LABEL, line_ending)?)
+ }
+
+ /// Write ASN.1 DER-encoded PKCS#8 private key to the given path
+ #[cfg(feature = "std")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "std")))]
+ fn write_pkcs8_der_file(&self, path: impl AsRef<Path>) -> Result<()> {
+ Ok(self.to_pkcs8_der()?.write_der_file(path)?)
+ }
+
+ /// Write ASN.1 DER-encoded PKCS#8 private key to the given path
+ #[cfg(all(feature = "pem", feature = "std"))]
+ #[cfg_attr(docsrs, doc(cfg(all(feature = "pem", feature = "std"))))]
+ fn write_pkcs8_pem_file(&self, path: impl AsRef<Path>, line_ending: LineEnding) -> Result<()> {
+ let doc = self.to_pkcs8_der()?;
+ Ok(doc.write_pem_file(path, PrivateKeyInfo::PEM_LABEL, line_ending)?)
+ }
+}
diff --git a/src/version.rs b/src/version.rs
new file mode 100644
index 0000000..3393683
--- /dev/null
+++ b/src/version.rs
@@ -0,0 +1,63 @@
+//! PKCS#8 version identifier.
+
+use crate::Error;
+use der::{Decode, Encode, FixedTag, Reader, Tag, Writer};
+
+/// Version identifier for PKCS#8 documents.
+///
+/// (RFC 5958 designates `0` and `1` as the only valid versions for PKCS#8 documents)
+#[derive(Clone, Debug, Copy, PartialEq)]
+pub enum Version {
+ /// Denotes PKCS#8 v1: no public key field.
+ V1 = 0,
+
+ /// Denotes PKCS#8 v2: `OneAsymmetricKey` with public key field.
+ V2 = 1,
+}
+
+impl Version {
+ /// Is this version expected to have a public key?
+ pub fn has_public_key(self) -> bool {
+ match self {
+ Version::V1 => false,
+ Version::V2 => true,
+ }
+ }
+}
+
+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::from(1u8).for_tlv()
+ }
+
+ fn encode(&self, writer: &mut dyn Writer) -> der::Result<()> {
+ u8::from(*self).encode(writer)
+ }
+}
+
+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::V1),
+ 1 => Ok(Version::V2),
+ _ => Err(Self::TAG.value_error().into()),
+ }
+ }
+}
+
+impl FixedTag for Version {
+ const TAG: Tag = Tag::Integer;
+}
diff --git a/tests/encrypted_private_key.rs b/tests/encrypted_private_key.rs
new file mode 100644
index 0000000..2bd72ae
--- /dev/null
+++ b/tests/encrypted_private_key.rs
@@ -0,0 +1,234 @@
+//! Encrypted PKCS#8 private key tests.
+
+#![cfg(feature = "pkcs5")]
+
+use hex_literal::hex;
+use pkcs8::{pkcs5::pbes2, EncryptedPrivateKeyInfo, PrivateKeyInfo};
+
+#[cfg(feature = "alloc")]
+use der::Encode;
+
+#[cfg(feature = "pem")]
+use der::EncodePem;
+
+/// Ed25519 PKCS#8 private key plaintext encoded as ASN.1 DER
+#[cfg(feature = "encryption")]
+const ED25519_DER_PLAINTEXT_EXAMPLE: &[u8] = include_bytes!("examples/ed25519-priv-pkcs8v1.der");
+
+/// Ed25519 PKCS#8 encrypted private key (PBES2 + AES-128-CBC + PBKDF2-SHA1) encoded as ASN.1 DER.
+///
+/// Generated using:
+///
+/// ```
+/// $ openssl pkcs8 -v2 aes256-cbc -v2prf hmacWithSHA1 -topk8 -inform der -in ed25519-priv.der -outform der -out ed25519-encpriv-aes128-pbkdf2-sha1.der
+/// ```
+const ED25519_DER_AES128_PBKDF2_SHA1_EXAMPLE: &[u8] =
+ include_bytes!("examples/ed25519-encpriv-aes128-pbkdf2-sha1.der");
+
+/// Ed25519 PKCS#8 encrypted private key (PBES2 + AES-256-CBC + PBKDF2-SHA256) encoded as ASN.1 DER.
+///
+/// Generated using:
+///
+/// ```
+/// $ openssl pkcs8 -v2 aes256-cbc -v2prf hmacWithSHA256 -topk8 -inform der -in ed25519-priv.der -outform der -out ed25519-encpriv-aes256-pbkdf2-sha256.der
+/// ```
+const ED25519_DER_AES256_PBKDF2_SHA256_EXAMPLE: &[u8] =
+ include_bytes!("examples/ed25519-encpriv-aes256-pbkdf2-sha256.der");
+
+/// Ed25519 PKCS#8 encrypted private key (PBES2 + AES-256-CBC + scrypt) encoded as ASN.1 DER.
+///
+/// Generated using:
+///
+/// ```
+/// $ openssl pkcs8 -v2 aes256-cbc -scrypt -topk8 -inform der -in ed25519-priv.der -outform der -out ed25519-encpriv-aes256-scrypt.der
+/// ```
+#[cfg(feature = "encryption")]
+const ED25519_DER_AES256_SCRYPT_EXAMPLE: &[u8] =
+ include_bytes!("examples/ed25519-encpriv-aes256-scrypt.der");
+
+/// Ed25519 PKCS#8 encrypted private key encoded as PEM
+#[cfg(feature = "pem")]
+const ED25519_PEM_AES256_PBKDF2_SHA256_EXAMPLE: &str =
+ include_str!("examples/ed25519-encpriv-aes256-pbkdf2-sha256.pem");
+
+/// Ed25519 PKCS#8 encrypted private key (PBES2 + 3DES + PBKDF2-SHA256) encoded as ASN.1 DER
+///
+/// Generated using:
+///
+/// ```
+/// $ openssl pkcs8 -v2 des3 -topk8 -inform der -in ed25519-priv-pkcs8v1.der -outform der -out ed25519-encpriv-des3-pbkdf2-sha256.der
+/// ```
+#[cfg(feature = "3des")]
+const ED25519_DER_DES3_PBKDF2_SHA256_EXAMPLE: &[u8] =
+ include_bytes!("examples/ed25519-encpriv-des3-pbkdf2-sha256.der");
+
+/// Ed25519 PKCS#8 encrypted private key (PBES2 + DES + PBKDF2-SHA256) encoded as ASN.1 DER
+///
+/// Generated using:
+///
+/// ```
+/// $ openssl pkcs8 -v2 des -topk8 -inform der -in ed25519-priv-pkcs8v1.der -outform der -out ed25519-encpriv-des3-pbkdf2-sha256.der
+/// ```
+#[cfg(feature = "des-insecure")]
+const ED25519_DER_DES_PBKDF2_SHA256_EXAMPLE: &[u8] =
+ include_bytes!("examples/ed25519-encpriv-des-pbkdf2-sha256.der");
+
+/// Password used to encrypt the keys.
+#[cfg(feature = "encryption")]
+const PASSWORD: &[u8] = b"hunter42"; // Bad password; don't actually use outside tests!
+
+#[test]
+fn decode_ed25519_encpriv_aes128_pbkdf2_sha1_der() {
+ let pk = EncryptedPrivateKeyInfo::try_from(ED25519_DER_AES128_PBKDF2_SHA1_EXAMPLE).unwrap();
+
+ assert_eq!(
+ pk.encryption_algorithm.oid(),
+ "1.2.840.113549.1.5.13".parse().unwrap()
+ ); // PBES2
+
+ let pbes2_params = pk.encryption_algorithm.pbes2().unwrap();
+ let pbkdf2_params = pbes2_params.kdf.pbkdf2().unwrap();
+
+ assert_eq!(pbkdf2_params.salt, hex!("e8765e01e43b6bad"));
+ assert_eq!(pbkdf2_params.iteration_count, 2048);
+ assert_eq!(pbkdf2_params.key_length, None);
+ assert_eq!(pbkdf2_params.prf, pbes2::Pbkdf2Prf::HmacWithSha1);
+
+ match pbes2_params.encryption {
+ pbes2::EncryptionScheme::Aes128Cbc { iv } => {
+ assert_eq!(iv, &hex!("223080a71bcd2b9a256d876c924979d2"));
+ }
+ other => panic!("unexpected encryption scheme: {:?}", other),
+ }
+
+ // Extracted with:
+ // $ openssl asn1parse -inform der -in tests/examples/ed25519-encpriv-aes128-sha1.der
+ assert_eq!(
+ pk.encrypted_data,
+ &hex!("4B4D091548EAC381EE7663B21234CD4FF3C9DF664D713394CACCEA7C9B982BD8F29910FABCA4BF7BE0431FAC5C4D657BE997C1F5BF40E2DA465AC1FCC2E30470")
+ );
+}
+
+#[test]
+fn decode_ed25519_encpriv_aes256_pbkdf2_sha256_der() {
+ let pk = EncryptedPrivateKeyInfo::try_from(ED25519_DER_AES256_PBKDF2_SHA256_EXAMPLE).unwrap();
+
+ assert_eq!(
+ pk.encryption_algorithm.oid(),
+ "1.2.840.113549.1.5.13".parse().unwrap()
+ ); // PBES2
+
+ let pbes2_params = pk.encryption_algorithm.pbes2().unwrap();
+ let pbkdf2_params = pbes2_params.kdf.pbkdf2().unwrap();
+
+ assert_eq!(pbkdf2_params.salt, hex!("79d982e70df91a88"));
+ assert_eq!(pbkdf2_params.iteration_count, 2048);
+ assert_eq!(pbkdf2_params.key_length, None);
+ assert_eq!(pbkdf2_params.prf, pbes2::Pbkdf2Prf::HmacWithSha256);
+
+ match pbes2_params.encryption {
+ pbes2::EncryptionScheme::Aes256Cbc { iv } => {
+ assert_eq!(iv, &hex!("b2d02d78b2efd9dff694cf8e0af40925"));
+ }
+ other => panic!("unexpected encryption scheme: {:?}", other),
+ }
+
+ // Extracted with:
+ // $ openssl asn1parse -inform der -in tests/examples/ed25519-encpriv-aes256-sha256.der
+ assert_eq!(
+ pk.encrypted_data,
+ &hex!("D0CD6C770F4BB87176422305C17401809E226674CE74185D221BFDAA95069890C8882FCE02B05D41BCBF54B035595BCD4154B32593708469B86AACF8815A7B2B")
+ );
+}
+
+#[cfg(feature = "encryption")]
+#[test]
+fn decrypt_ed25519_der_encpriv_aes256_pbkdf2_sha256() {
+ let enc_pk =
+ EncryptedPrivateKeyInfo::try_from(ED25519_DER_AES256_PBKDF2_SHA256_EXAMPLE).unwrap();
+ let pk = enc_pk.decrypt(PASSWORD).unwrap();
+ assert_eq!(pk.as_bytes(), ED25519_DER_PLAINTEXT_EXAMPLE);
+}
+
+#[cfg(feature = "encryption")]
+#[test]
+fn decrypt_ed25519_der_encpriv_aes256_scrypt() {
+ let enc_pk = EncryptedPrivateKeyInfo::try_from(ED25519_DER_AES256_SCRYPT_EXAMPLE).unwrap();
+ let pk = enc_pk.decrypt(PASSWORD).unwrap();
+ assert_eq!(pk.as_bytes(), ED25519_DER_PLAINTEXT_EXAMPLE);
+}
+
+#[cfg(feature = "encryption")]
+#[test]
+fn encrypt_ed25519_der_encpriv_aes256_pbkdf2_sha256() {
+ let pbes2_params = pkcs5::pbes2::Parameters::pbkdf2_sha256_aes256cbc(
+ 2048,
+ &hex!("79d982e70df91a88"),
+ &hex!("b2d02d78b2efd9dff694cf8e0af40925"),
+ )
+ .unwrap();
+
+ let pk_plaintext = PrivateKeyInfo::try_from(ED25519_DER_PLAINTEXT_EXAMPLE).unwrap();
+ let pk_encrypted = pk_plaintext
+ .encrypt_with_params(pbes2_params, PASSWORD)
+ .unwrap();
+
+ assert_eq!(
+ pk_encrypted.as_bytes(),
+ ED25519_DER_AES256_PBKDF2_SHA256_EXAMPLE
+ );
+}
+
+#[cfg(feature = "encryption")]
+#[test]
+fn encrypt_ed25519_der_encpriv_aes256_scrypt() {
+ let scrypt_params = pkcs5::pbes2::Parameters::scrypt_aes256cbc(
+ Default::default(),
+ &hex!("E6211E2348AD69E0"),
+ &hex!("9BD0A6251F2254F9FD5963887C27CF01"),
+ )
+ .unwrap();
+
+ let pk_plaintext = PrivateKeyInfo::try_from(ED25519_DER_PLAINTEXT_EXAMPLE).unwrap();
+ let pk_encrypted = pk_plaintext
+ .encrypt_with_params(scrypt_params, PASSWORD)
+ .unwrap();
+
+ assert_eq!(pk_encrypted.as_bytes(), ED25519_DER_AES256_SCRYPT_EXAMPLE);
+}
+
+#[test]
+#[cfg(feature = "alloc")]
+fn encode_ed25519_encpriv_aes256_pbkdf2_sha256_der() {
+ let pk = EncryptedPrivateKeyInfo::try_from(ED25519_DER_AES256_PBKDF2_SHA256_EXAMPLE).unwrap();
+ assert_eq!(
+ ED25519_DER_AES256_PBKDF2_SHA256_EXAMPLE,
+ &pk.to_vec().unwrap()
+ );
+}
+
+#[test]
+#[cfg(feature = "pem")]
+fn encode_ed25519_encpriv_aes256_pbkdf2_sha256_pem() {
+ let pk = EncryptedPrivateKeyInfo::try_from(ED25519_DER_AES256_PBKDF2_SHA256_EXAMPLE).unwrap();
+ assert_eq!(
+ ED25519_PEM_AES256_PBKDF2_SHA256_EXAMPLE,
+ pk.to_pem(Default::default()).unwrap()
+ );
+}
+
+#[test]
+#[cfg(feature = "3des")]
+fn decrypt_ed25519_der_encpriv_des3_pbkdf2_sha256() {
+ let enc_pk = EncryptedPrivateKeyInfo::try_from(ED25519_DER_DES3_PBKDF2_SHA256_EXAMPLE).unwrap();
+ let pk = enc_pk.decrypt(PASSWORD).unwrap();
+ assert_eq!(pk.as_bytes(), ED25519_DER_PLAINTEXT_EXAMPLE);
+}
+
+#[test]
+#[cfg(feature = "des-insecure")]
+fn decrypt_ed25519_der_encpriv_des_pbkdf2_sha256() {
+ let enc_pk = EncryptedPrivateKeyInfo::try_from(ED25519_DER_DES_PBKDF2_SHA256_EXAMPLE).unwrap();
+ let pk = enc_pk.decrypt(PASSWORD).unwrap();
+ assert_eq!(pk.as_bytes(), ED25519_DER_PLAINTEXT_EXAMPLE);
+}
diff --git a/tests/examples/ed25519-encpriv-aes128-pbkdf2-sha1.der b/tests/examples/ed25519-encpriv-aes128-pbkdf2-sha1.der
new file mode 100644
index 0000000..c8d6edf
--- /dev/null
+++ b/tests/examples/ed25519-encpriv-aes128-pbkdf2-sha1.der
Binary files differ
diff --git a/tests/examples/ed25519-encpriv-aes256-pbkdf2-sha256.der b/tests/examples/ed25519-encpriv-aes256-pbkdf2-sha256.der
new file mode 100644
index 0000000..5170c06
--- /dev/null
+++ b/tests/examples/ed25519-encpriv-aes256-pbkdf2-sha256.der
Binary files differ
diff --git a/tests/examples/ed25519-encpriv-aes256-pbkdf2-sha256.pem b/tests/examples/ed25519-encpriv-aes256-pbkdf2-sha256.pem
new file mode 100644
index 0000000..e5d3207
--- /dev/null
+++ b/tests/examples/ed25519-encpriv-aes256-pbkdf2-sha256.pem
@@ -0,0 +1,6 @@
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MIGbMFcGCSqGSIb3DQEFDTBKMCkGCSqGSIb3DQEFDDAcBAh52YLnDfkaiAICCAAw
+DAYIKoZIhvcNAgkFADAdBglghkgBZQMEASoEELLQLXiy79nf9pTPjgr0CSUEQNDN
+bHcPS7hxdkIjBcF0AYCeImZ0znQYXSIb/aqVBpiQyIgvzgKwXUG8v1SwNVlbzUFU
+syWTcIRpuGqs+IFaeys=
+-----END ENCRYPTED PRIVATE KEY-----
diff --git a/tests/examples/ed25519-encpriv-aes256-scrypt.der b/tests/examples/ed25519-encpriv-aes256-scrypt.der
new file mode 100644
index 0000000..a045982
--- /dev/null
+++ b/tests/examples/ed25519-encpriv-aes256-scrypt.der
Binary files differ
diff --git a/tests/examples/ed25519-encpriv-aes256-scrypt.pem b/tests/examples/ed25519-encpriv-aes256-scrypt.pem
new file mode 100644
index 0000000..1f0562d
--- /dev/null
+++ b/tests/examples/ed25519-encpriv-aes256-scrypt.pem
@@ -0,0 +1,6 @@
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MIGTME8GCSqGSIb3DQEFDTBCMCEGCSsGAQQB2kcECzAUBAjmIR4jSK1p4AICQAAC
+AQgCAQEwHQYJYIZIAWUDBAEqBBCb0KYlHyJU+f1ZY4h8J88BBEDMYrp3PA9JX6s2
+aOT8782wjnig7hXgoVAT9iq+CNqnQgZe6zZtbmyYzDsOfmm9yGHIiv648D26Hixt
+mdBtFzYM
+-----END ENCRYPTED PRIVATE KEY-----
diff --git a/tests/examples/ed25519-encpriv-des-pbkdf2-sha256.der b/tests/examples/ed25519-encpriv-des-pbkdf2-sha256.der
new file mode 100644
index 0000000..85d3b83
--- /dev/null
+++ b/tests/examples/ed25519-encpriv-des-pbkdf2-sha256.der
Binary files differ
diff --git a/tests/examples/ed25519-encpriv-des3-pbkdf2-sha256.der b/tests/examples/ed25519-encpriv-des3-pbkdf2-sha256.der
new file mode 100644
index 0000000..aed05ab
--- /dev/null
+++ b/tests/examples/ed25519-encpriv-des3-pbkdf2-sha256.der
Binary files differ
diff --git a/tests/examples/ed25519-priv-pkcs8v1.der b/tests/examples/ed25519-priv-pkcs8v1.der
new file mode 100644
index 0000000..0cfccc3
--- /dev/null
+++ b/tests/examples/ed25519-priv-pkcs8v1.der
Binary files differ
diff --git a/tests/examples/ed25519-priv-pkcs8v1.pem b/tests/examples/ed25519-priv-pkcs8v1.pem
new file mode 100644
index 0000000..0c0ee10
--- /dev/null
+++ b/tests/examples/ed25519-priv-pkcs8v1.pem
@@ -0,0 +1,3 @@
+-----BEGIN PRIVATE KEY-----
+MC4CAQAwBQYDK2VwBCIEIBftnHPp22SewYmmEoMcX8VwI4IHwaqd+9LFPj/15eqF
+-----END PRIVATE KEY-----
diff --git a/tests/examples/ed25519-priv-pkcs8v2.der b/tests/examples/ed25519-priv-pkcs8v2.der
new file mode 100644
index 0000000..3358e8a
--- /dev/null
+++ b/tests/examples/ed25519-priv-pkcs8v2.der
Binary files differ
diff --git a/tests/examples/ed25519-priv-pkcs8v2.pem b/tests/examples/ed25519-priv-pkcs8v2.pem
new file mode 100644
index 0000000..8496108
--- /dev/null
+++ b/tests/examples/ed25519-priv-pkcs8v2.pem
@@ -0,0 +1,5 @@
+-----BEGIN PRIVATE KEY-----
+MHICAQEwBQYDK2VwBCIEINTuctv5E1hK1bbY8fdp+K06/nwoy/HU++CXqI9EdVhC
+oB8wHQYKKoZIhvcNAQkJFDEPDA1DdXJkbGUgQ2hhaXJzgSEAGb9ECWmEzf6FQbrB
+Z9w7lshQhqowtrbLDFw4rXAxZuE=
+-----END PRIVATE KEY-----
diff --git a/tests/examples/ed25519-pub.der b/tests/examples/ed25519-pub.der
new file mode 100644
index 0000000..1b602ee
--- /dev/null
+++ b/tests/examples/ed25519-pub.der
Binary files differ
diff --git a/tests/examples/ed25519-pub.pem b/tests/examples/ed25519-pub.pem
new file mode 100644
index 0000000..6891701
--- /dev/null
+++ b/tests/examples/ed25519-pub.pem
@@ -0,0 +1,3 @@
+-----BEGIN PUBLIC KEY-----
+MCowBQYDK2VwAyEATSkWfz8ZEqb3rfopOgUaFcBexnuPFyZ7HFVQ3OhTvQ0=
+-----END PUBLIC KEY-----
diff --git a/tests/examples/p256-priv.der b/tests/examples/p256-priv.der
new file mode 100644
index 0000000..c0de45e
--- /dev/null
+++ b/tests/examples/p256-priv.der
Binary files differ
diff --git a/tests/examples/p256-priv.pem b/tests/examples/p256-priv.pem
new file mode 100644
index 0000000..09b9343
--- /dev/null
+++ b/tests/examples/p256-priv.pem
@@ -0,0 +1,5 @@
+-----BEGIN PRIVATE KEY-----
+MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgaWJBcVYaYzQN4OfY
+afKgVJJVjhoEhotqn4VKhmeIGI2hRANCAAQcrP+1Xy8s79idies3SyaBFSRSgC3u
+oJkWBoE32DnPf8SBpESSME1+9mrBF77+g6jQjxVfK1L59hjdRHApBI4P
+-----END PRIVATE KEY-----
diff --git a/tests/examples/p256-pub.der b/tests/examples/p256-pub.der
new file mode 100644
index 0000000..67c719c
--- /dev/null
+++ b/tests/examples/p256-pub.der
Binary files differ
diff --git a/tests/examples/p256-pub.pem b/tests/examples/p256-pub.pem
new file mode 100644
index 0000000..ee7e5b6
--- /dev/null
+++ b/tests/examples/p256-pub.pem
@@ -0,0 +1,4 @@
+-----BEGIN PUBLIC KEY-----
+MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEHKz/tV8vLO/YnYnrN0smgRUkUoAt
+7qCZFgaBN9g5z3/EgaREkjBNfvZqwRe+/oOo0I8VXytS+fYY3URwKQSODw==
+-----END PUBLIC KEY-----
diff --git a/tests/examples/rsa2048-priv.der b/tests/examples/rsa2048-priv.der
new file mode 100644
index 0000000..f4590bb
--- /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..e2a218c
--- /dev/null
+++ b/tests/examples/rsa2048-priv.pem
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC2xCxRXxCmqvKC
+xj7b4kJDoXDz+iYzvUgzY39Hyk9vNuA6XSnvwxkayA85DYdLOeMPQU/Owfyg7YHl
+R+3CzTgsdvYckBiXPbn6U3lyp8cB9rd+CYLfwV/AGSfuXnzZS09Zn/BwE6fIKBvf
+Ity8mtfKu3xDEcmC9Y7bchOtRVizMiZtdDrtgZLRiEytuLFHOaja2mbclwgG2ces
+RQyxPQ18V1+xmFNPxhvEG8DwV04OATDHu7+9/cn2puLj4q/xy+rIm6V4hFKNVc+w
+gyeh6MifTgA88oiOkzJB2daVvLus3JC0Tj4JX6NwWOolsT9eKVy+rG3oOKuMUK9h
+4piXW4cvAgMBAAECggEAfsyDYsDtsHQRZCFeIvdKudkboGkAcAz2NpDlEU2O5r3P
+uy4/lhRpKmd6CD8Wil5S5ZaOZAe52XxuDkBk+C2gt1ihTxe5t9QfX0jijWVRcE9W
+5p56qfpjD8dkKMBtJeRV3PxVt6wrT3ZkP97T/hX/eKuyfmWsxKrQvfbbJ+9gppEM
+XEoIXtQydasZwdmXoyxu/8598tGTX25gHu3hYaErXMJ8oh+B0smcPR6gjpDjBTqw
+m++nJN7w0MOjwel0DA2fdhJqFJ7Aqn2AeCBUhCVNlR2wfEz5H7ZFTAlliP1ZJNur
+6zWcogJSaNAE+dZus9b3rcETm61A8W3eY54RZHN2wQKBgQDcwGEkLU6Sr67nKsUT
+ymW593A2+b1+Dm5hRhp+92VCJewVPH5cMaYVem5aE/9uF46HWMHLM9nWu+MXnvGJ
+mOQi7Ny+149Oz9vl9PzYrsLJ0NyGRzypvRbZ0jjSH7Xd776xQ8ph0L1qqNkfM6CX
+eQ6WQNvJEIXcXyY0O6MTj2stZwKBgQDT8xR1fkDpVINvkr4kI2ry8NoEo0ZTwYCv
+Z+lgCG2T/eZcsj79nQk3R2L1mB42GEmvaM3XU5T/ak4G62myCeQijbLfpw5A9/l1
+ClKBdmR7eI0OV3eiy4si480mf/cLTzsC06r7DhjFkKVksDGIsKpfxIFWsHYiIUJD
+vRIn76fy+QKBgQDOaLesGw0QDWNuVUiHU8XAmEP9s5DicF33aJRXyb2Nl2XjCXhh
+fi78gEj0wyQgbbhgh7ZU6Xuz1GTn7j+M2D/hBDb33xjpqWPE5kkR1n7eNAQvLibj
+06GtNGra1rm39ncIywlOYt7p/01dZmmvmIryJV0c6O0xfGp9hpHaNU0S2wKBgCX2
+5ZRCIChrTfu/QjXA7lhD0hmAkYlRINbKeyALgm0+znOOLgBJj6wKKmypacfww8oa
+sLxAKXEyvnU4177fTLDvxrmO99ulT1aqmaq85TTEnCeUfUZ4xRxjx4x84WhyMbTI
+61h65u8EgMuvT8AXPP1Yen5nr1FfubnedREYOXIpAoGAMZlUBtQGIHyt6uo1s40E
+DF+Kmhrggn6e0GsVPYO2ghk1tLNqgr6dVseRtYwnJxpXk9U6HWV8CJl5YLFDPlFx
+mH9FLxRKfHIwbWPh0//Atxt1qwjy5FpILpiEUcvkeOEusijQdFbJJLZvbO0EjYU/
+Uz4xpoYU8cPObY7JmDznKvc=
+-----END PRIVATE KEY-----
diff --git a/tests/examples/rsa2048-pub.der b/tests/examples/rsa2048-pub.der
new file mode 100644
index 0000000..4148aaa
--- /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..5ecd892
--- /dev/null
+++ b/tests/examples/rsa2048-pub.pem
@@ -0,0 +1,9 @@
+-----BEGIN PUBLIC KEY-----
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtsQsUV8QpqrygsY+2+JC
+Q6Fw8/omM71IM2N/R8pPbzbgOl0p78MZGsgPOQ2HSznjD0FPzsH8oO2B5Uftws04
+LHb2HJAYlz25+lN5cqfHAfa3fgmC38FfwBkn7l582UtPWZ/wcBOnyCgb3yLcvJrX
+yrt8QxHJgvWO23ITrUVYszImbXQ67YGS0YhMrbixRzmo2tpm3JcIBtnHrEUMsT0N
+fFdfsZhTT8YbxBvA8FdODgEwx7u/vf3J9qbi4+Kv8cvqyJuleIRSjVXPsIMnoejI
+n04APPKIjpMyQdnWlby7rNyQtE4+CV+jcFjqJbE/Xilcvqxt6DirjFCvYeKYl1uH
+LwIDAQAB
+-----END PUBLIC KEY-----
diff --git a/tests/examples/x25519-priv.der b/tests/examples/x25519-priv.der
new file mode 100644
index 0000000..79355d2
--- /dev/null
+++ b/tests/examples/x25519-priv.der
Binary files differ
diff --git a/tests/examples/x25519-priv.pem b/tests/examples/x25519-priv.pem
new file mode 100644
index 0000000..501f95d
--- /dev/null
+++ b/tests/examples/x25519-priv.pem
@@ -0,0 +1,3 @@
+-----BEGIN PRIVATE KEY-----
+MC4CAQAwBQYDK2VuBCIEIHBgJSkzrG56SpsOsmMsWgQKhyV624aaPszD0WtyTyZH
+-----END PRIVATE KEY-----
diff --git a/tests/private_key.rs b/tests/private_key.rs
new file mode 100644
index 0000000..15d6694
--- /dev/null
+++ b/tests/private_key.rs
@@ -0,0 +1,182 @@
+//! PKCS#8 private key tests
+
+use hex_literal::hex;
+use pkcs8::{PrivateKeyInfo, Version};
+
+#[cfg(feature = "alloc")]
+use der::Encode;
+
+#[cfg(feature = "pem")]
+use der::{pem::LineEnding, EncodePem};
+
+/// Elliptic Curve (P-256) PKCS#8 private key encoded as ASN.1 DER
+const EC_P256_DER_EXAMPLE: &[u8] = include_bytes!("examples/p256-priv.der");
+
+/// Ed25519 PKCS#8 v1 private key encoded as ASN.1 DER
+const ED25519_DER_V1_EXAMPLE: &[u8] = include_bytes!("examples/ed25519-priv-pkcs8v1.der");
+
+/// Ed25519 PKCS#8 v2 private key + public key encoded as ASN.1 DER
+const ED25519_DER_V2_EXAMPLE: &[u8] = include_bytes!("examples/ed25519-priv-pkcs8v2.der");
+
+/// RSA-2048 PKCS#8 private key encoded as ASN.1 DER
+const RSA_2048_DER_EXAMPLE: &[u8] = include_bytes!("examples/rsa2048-priv.der");
+
+/// X25519 PKCS#8 private key encoded as ASN.1 DER
+const X25519_DER_EXAMPLE: &[u8] = include_bytes!("examples/x25519-priv.der");
+
+/// Elliptic Curve (P-256) PKCS#8 private key encoded as PEM
+#[cfg(feature = "pem")]
+const EC_P256_PEM_EXAMPLE: &str = include_str!("examples/p256-priv.pem");
+
+/// Ed25519 PKCS#8 private key encoded as PEM
+#[cfg(feature = "pem")]
+const ED25519_PEM_V1_EXAMPLE: &str = include_str!("examples/ed25519-priv-pkcs8v1.pem");
+
+/// RSA-2048 PKCS#8 private key encoded as PEM
+#[cfg(feature = "pem")]
+const RSA_2048_PEM_EXAMPLE: &str = include_str!("examples/rsa2048-priv.pem");
+
+/// X25519 PKCS#8 private key encoded as PEM
+#[cfg(feature = "pem")]
+const X25519_PEM_EXAMPLE: &str = include_str!("examples/x25519-priv.pem");
+
+#[test]
+fn decode_ec_p256_der() {
+ let pk = PrivateKeyInfo::try_from(EC_P256_DER_EXAMPLE).unwrap();
+
+ assert_eq!(pk.version(), Version::V1);
+ assert_eq!(pk.algorithm.oid, "1.2.840.10045.2.1".parse().unwrap());
+
+ assert_eq!(
+ pk.algorithm.parameters.unwrap().oid().unwrap(),
+ "1.2.840.10045.3.1.7".parse().unwrap()
+ );
+
+ // Extracted with:
+ // $ openssl asn1parse -inform der -in tests/examples/p256-priv.der
+ assert_eq!(pk.private_key, &hex!("306B020101042069624171561A63340DE0E7D869F2A05492558E1A04868B6A9F854A866788188DA144034200041CACFFB55F2F2CEFD89D89EB374B2681152452802DEEA09916068137D839CF7FC481A44492304D7EF66AC117BEFE83A8D08F155F2B52F9F618DD447029048E0F")[..]);
+}
+
+// Test vector from RFC8410 Section 10.3:
+// https://datatracker.ietf.org/doc/html/rfc8410#section-10.3
+#[test]
+fn decode_ed25519_der_v1() {
+ let pk = PrivateKeyInfo::try_from(ED25519_DER_V1_EXAMPLE).unwrap();
+ assert_eq!(pk.version(), Version::V1);
+ assert_eq!(pk.algorithm.oid, "1.3.101.112".parse().unwrap());
+ assert_eq!(pk.algorithm.parameters, None);
+
+ // Extracted with:
+ // $ openssl asn1parse -inform der -in tests/examples/ed25519-priv.der
+ assert_eq!(
+ pk.private_key,
+ &hex!("042017ED9C73E9DB649EC189A612831C5FC570238207C1AA9DFBD2C53E3FF5E5EA85")[..]
+ );
+}
+
+// Test vector from RFC8410 Section 10.3:
+// https://datatracker.ietf.org/doc/html/rfc8410#section-10.3
+#[test]
+fn decode_ed25519_der_v2() {
+ // Extracted with:
+ // $ openssl asn1parse -inform der -in tests/examples/ed25519-priv-pkcs8v2.der
+ const PRIV_KEY: [u8; 34] =
+ hex!("0420D4EE72DBF913584AD5B6D8F1F769F8AD3AFE7C28CBF1D4FBE097A88F44755842");
+ const PUB_KEY: [u8; 32] =
+ hex!("19BF44096984CDFE8541BAC167DC3B96C85086AA30B6B6CB0C5C38AD703166E1");
+
+ let pk = PrivateKeyInfo::try_from(ED25519_DER_V2_EXAMPLE).unwrap();
+ assert_eq!(pk.version(), Version::V2);
+ assert_eq!(pk.algorithm.oid, "1.3.101.112".parse().unwrap());
+ assert_eq!(pk.algorithm.parameters, None);
+ assert_eq!(pk.private_key, PRIV_KEY);
+ assert_eq!(pk.public_key, Some(&PUB_KEY[..]));
+}
+
+#[test]
+fn decode_rsa_2048_der() {
+ let pk = PrivateKeyInfo::try_from(RSA_2048_DER_EXAMPLE).unwrap();
+ assert_eq!(pk.version(), Version::V1);
+ assert_eq!(pk.algorithm.oid, "1.2.840.113549.1.1.1".parse().unwrap());
+ assert!(pk.algorithm.parameters.unwrap().is_null());
+
+ // Extracted with:
+ // $ openssl asn1parse -inform der -in tests/examples/rsa2048-priv.der
+ assert_eq!(pk.private_key, &hex!("308204A30201000282010100B6C42C515F10A6AAF282C63EDBE24243A170F3FA2633BD4833637F47CA4F6F36E03A5D29EFC3191AC80F390D874B39E30F414FCEC1FCA0ED81E547EDC2CD382C76F61C9018973DB9FA537972A7C701F6B77E0982DFC15FC01927EE5E7CD94B4F599FF07013A7C8281BDF22DCBC9AD7CABB7C4311C982F58EDB7213AD4558B332266D743AED8192D1884CADB8B14739A8DADA66DC970806D9C7AC450CB13D0D7C575FB198534FC61BC41BC0F0574E0E0130C7BBBFBDFDC9F6A6E2E3E2AFF1CBEAC89BA57884528D55CFB08327A1E8C89F4E003CF2888E933241D9D695BCBBACDC90B44E3E095FA37058EA25B13F5E295CBEAC6DE838AB8C50AF61E298975B872F0203010001028201007ECC8362C0EDB0741164215E22F74AB9D91BA06900700CF63690E5114D8EE6BDCFBB2E3F9614692A677A083F168A5E52E5968E6407B9D97C6E0E4064F82DA0B758A14F17B9B7D41F5F48E28D6551704F56E69E7AA9FA630FC76428C06D25E455DCFC55B7AC2B4F76643FDED3FE15FF78ABB27E65ACC4AAD0BDF6DB27EF60A6910C5C4A085ED43275AB19C1D997A32C6EFFCE7DF2D1935F6E601EEDE161A12B5CC27CA21F81D2C99C3D1EA08E90E3053AB09BEFA724DEF0D0C3A3C1E9740C0D9F76126A149EC0AA7D8078205484254D951DB07C4CF91FB6454C096588FD5924DBABEB359CA2025268D004F9D66EB3D6F7ADC1139BAD40F16DDE639E11647376C102818100DCC061242D4E92AFAEE72AC513CA65B9F77036F9BD7E0E6E61461A7EF7654225EC153C7E5C31A6157A6E5A13FF6E178E8758C1CB33D9D6BBE3179EF18998E422ECDCBED78F4ECFDBE5F4FCD8AEC2C9D0DC86473CA9BD16D9D238D21FB5DDEFBEB143CA61D0BD6AA8D91F33A097790E9640DBC91085DC5F26343BA3138F6B2D6702818100D3F314757E40E954836F92BE24236AF2F0DA04A34653C180AF67E960086D93FDE65CB23EFD9D09374762F5981E361849AF68CDD75394FF6A4E06EB69B209E4228DB2DFA70E40F7F9750A528176647B788D0E5777A2CB8B22E3CD267FF70B4F3B02D3AAFB0E18C590A564B03188B0AA5FC48156B07622214243BD1227EFA7F2F902818100CE68B7AC1B0D100D636E55488753C5C09843FDB390E2705DF7689457C9BD8D9765E30978617E2EFC8048F4C324206DB86087B654E97BB3D464E7EE3F8CD83FE10436F7DF18E9A963C4E64911D67EDE34042F2E26E3D3A1AD346ADAD6B9B7F67708CB094E62DEE9FF4D5D6669AF988AF2255D1CE8ED317C6A7D8691DA354D12DB02818025F6E5944220286B4DFBBF4235C0EE5843D2198091895120D6CA7B200B826D3ECE738E2E00498FAC0A2A6CA969C7F0C3CA1AB0BC40297132BE7538D7BEDF4CB0EFC6B98EF7DBA54F56AA99AABCE534C49C27947D4678C51C63C78C7CE1687231B4C8EB587AE6EF0480CBAF4FC0173CFD587A7E67AF515FB9B9DE75111839722902818031995406D406207CADEAEA35B38D040C5F8A9A1AE0827E9ED06B153D83B6821935B4B36A82BE9D56C791B58C27271A5793D53A1D657C08997960B1433E5171987F452F144A7C72306D63E1D3FFC0B71B75AB08F2E45A482E988451CBE478E12EB228D07456C924B66F6CED048D853F533E31A68614F1C3CE6D8EC9983CE72AF7")[..]);
+}
+
+#[test]
+fn decode_x25519_der() {
+ let pk = PrivateKeyInfo::try_from(X25519_DER_EXAMPLE).unwrap();
+ assert_eq!(pk.version(), Version::V1);
+ assert_eq!(pk.algorithm.oid, "1.3.101.110".parse().unwrap());
+ assert_eq!(pk.algorithm.parameters, None);
+
+ // Extracted with:
+ // $ openssl asn1parse -inform der -in tests/examples/x25519-priv.der
+ assert_eq!(
+ pk.private_key,
+ &hex!("04207060252933AC6E7A4A9B0EB2632C5A040A87257ADB869A3ECCC3D16B724F2647")[..]
+ );
+}
+
+#[test]
+#[cfg(feature = "alloc")]
+fn encode_ec_p256_der() {
+ let pk = PrivateKeyInfo::try_from(EC_P256_DER_EXAMPLE).unwrap();
+ let pk_encoded = pk.to_vec().unwrap();
+ assert_eq!(EC_P256_DER_EXAMPLE, pk_encoded);
+}
+
+#[test]
+#[cfg(feature = "alloc")]
+fn encode_ed25519_der_v1() {
+ let pk = PrivateKeyInfo::try_from(ED25519_DER_V1_EXAMPLE).unwrap();
+ assert_eq!(ED25519_DER_V1_EXAMPLE, pk.to_vec().unwrap());
+}
+
+#[test]
+#[cfg(all(feature = "alloc", feature = "subtle"))]
+fn encode_ed25519_der_v2() {
+ let private_key = PrivateKeyInfo::try_from(ED25519_DER_V2_EXAMPLE).unwrap();
+ let private_der = private_key.to_vec().unwrap();
+ assert_eq!(
+ private_key,
+ PrivateKeyInfo::try_from(private_der.as_ref()).unwrap()
+ );
+}
+
+#[test]
+#[cfg(feature = "alloc")]
+fn encode_rsa_2048_der() {
+ let pk = PrivateKeyInfo::try_from(RSA_2048_DER_EXAMPLE).unwrap();
+ assert_eq!(RSA_2048_DER_EXAMPLE, &pk.to_vec().unwrap());
+}
+
+#[test]
+#[cfg(feature = "pem")]
+fn encode_ec_p256_pem() {
+ let pk = PrivateKeyInfo::try_from(EC_P256_DER_EXAMPLE).unwrap();
+ assert_eq!(EC_P256_PEM_EXAMPLE, pk.to_pem(LineEnding::LF).unwrap());
+}
+
+#[test]
+#[cfg(feature = "pem")]
+fn encode_ed25519_pem() {
+ let pk = PrivateKeyInfo::try_from(ED25519_DER_V1_EXAMPLE).unwrap();
+ assert_eq!(ED25519_PEM_V1_EXAMPLE, pk.to_pem(LineEnding::LF).unwrap());
+}
+
+#[test]
+#[cfg(feature = "pem")]
+fn encode_rsa_2048_pem() {
+ let pk = PrivateKeyInfo::try_from(RSA_2048_DER_EXAMPLE).unwrap();
+ assert_eq!(RSA_2048_PEM_EXAMPLE, pk.to_pem(LineEnding::LF).unwrap());
+}
+
+#[test]
+#[cfg(feature = "pem")]
+fn encode_x25519_pem() {
+ let pk = PrivateKeyInfo::try_from(X25519_DER_EXAMPLE).unwrap();
+ assert_eq!(X25519_PEM_EXAMPLE, pk.to_pem(LineEnding::LF).unwrap());
+}
diff --git a/tests/traits.rs b/tests/traits.rs
new file mode 100644
index 0000000..1c8a969
--- /dev/null
+++ b/tests/traits.rs
@@ -0,0 +1,108 @@
+//! Tests for PKCS#8 encoding/decoding traits.
+
+#![cfg(any(feature = "pem", feature = "std"))]
+
+use der::Encode;
+use pkcs8::{DecodePrivateKey, EncodePrivateKey, Error, PrivateKeyInfo, Result, SecretDocument};
+
+#[cfg(feature = "pem")]
+use pkcs8::der::pem::LineEnding;
+
+#[cfg(feature = "std")]
+use tempfile::tempdir;
+
+#[cfg(all(feature = "pem", feature = "std"))]
+use std::fs;
+
+/// Ed25519 `PrivateKeyInfo` encoded as ASN.1 DER
+const ED25519_DER_EXAMPLE: &[u8] = include_bytes!("examples/ed25519-priv-pkcs8v1.der");
+
+/// Ed25519 private key encoded as PEM
+#[cfg(feature = "pem")]
+const ED25519_PEM_EXAMPLE: &str = include_str!("examples/ed25519-priv-pkcs8v1.pem");
+
+/// Mock key type for testing trait impls against.
+pub struct MockKey(Vec<u8>);
+
+impl AsRef<[u8]> for MockKey {
+ fn as_ref(&self) -> &[u8] {
+ self.0.as_ref()
+ }
+}
+
+impl DecodePrivateKey for MockKey {
+ fn from_pkcs8_der(bytes: &[u8]) -> Result<MockKey> {
+ Ok(MockKey(bytes.to_vec()))
+ }
+}
+
+impl EncodePrivateKey for MockKey {
+ fn to_pkcs8_der(&self) -> Result<SecretDocument> {
+ Ok(SecretDocument::try_from(self.as_ref())?)
+ }
+}
+
+impl TryFrom<PrivateKeyInfo<'_>> for MockKey {
+ type Error = Error;
+
+ fn try_from(pkcs8: PrivateKeyInfo<'_>) -> Result<MockKey> {
+ Ok(MockKey(pkcs8.to_vec()?))
+ }
+}
+
+#[cfg(feature = "pem")]
+#[test]
+fn from_pkcs8_pem() {
+ let key = MockKey::from_pkcs8_pem(ED25519_PEM_EXAMPLE).unwrap();
+ assert_eq!(key.as_ref(), ED25519_DER_EXAMPLE);
+}
+
+#[cfg(feature = "std")]
+#[test]
+fn read_pkcs8_der_file() {
+ let key = MockKey::read_pkcs8_der_file("tests/examples/ed25519-priv-pkcs8v1.der").unwrap();
+ assert_eq!(key.as_ref(), ED25519_DER_EXAMPLE);
+}
+
+#[cfg(all(feature = "pem", feature = "std"))]
+#[test]
+fn read_pkcs8_pem_file() {
+ let key = MockKey::read_pkcs8_pem_file("tests/examples/ed25519-priv-pkcs8v1.pem").unwrap();
+ assert_eq!(key.as_ref(), ED25519_DER_EXAMPLE);
+}
+
+#[cfg(feature = "pem")]
+#[test]
+fn to_pkcs8_pem() {
+ let pem = MockKey(ED25519_DER_EXAMPLE.to_vec())
+ .to_pkcs8_pem(LineEnding::LF)
+ .unwrap();
+
+ assert_eq!(&*pem, ED25519_PEM_EXAMPLE);
+}
+
+#[cfg(feature = "std")]
+#[test]
+fn write_pkcs8_der_file() {
+ let dir = tempdir().unwrap();
+ let path = dir.path().join("example.der");
+ MockKey(ED25519_DER_EXAMPLE.to_vec())
+ .write_pkcs8_der_file(&path)
+ .unwrap();
+
+ let key = MockKey::read_pkcs8_der_file(&path).unwrap();
+ assert_eq!(key.as_ref(), ED25519_DER_EXAMPLE);
+}
+
+#[cfg(all(feature = "pem", feature = "std"))]
+#[test]
+fn write_pkcs8_pem_file() {
+ let dir = tempdir().unwrap();
+ let path = dir.path().join("example.pem");
+ MockKey(ED25519_DER_EXAMPLE.to_vec())
+ .write_pkcs8_pem_file(&path, LineEnding::LF)
+ .unwrap();
+
+ let pem = fs::read_to_string(path).unwrap();
+ assert_eq!(&pem, ED25519_PEM_EXAMPLE);
+}