From 8f8a4be95733a14639612dc3921cd3ca6d722bd1 Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Fri, 4 Dec 2020 14:08:33 -0800 Subject: [PATCH] elliptic-curve: PKCS#8 PEM support (#382) Support for decoding PEM-encoded PKCS#8 `SecretKey`s --- .github/workflows/elliptic-curve.yml | 2 + Cargo.lock | 15 +++++++- elliptic-curve/Cargo.toml | 1 + elliptic-curve/src/secret_key.rs | 57 +++++++++++++++++++++++----- 4 files changed, 64 insertions(+), 11 deletions(-) diff --git a/.github/workflows/elliptic-curve.yml b/.github/workflows/elliptic-curve.yml index 3da08cdd0..78dc890dc 100644 --- a/.github/workflows/elliptic-curve.yml +++ b/.github/workflows/elliptic-curve.yml @@ -38,6 +38,8 @@ jobs: - run: cargo build --no-default-features --release --target ${{ matrix.target }} - run: cargo build --no-default-features --release --target ${{ matrix.target }} --features arithmetic - run: cargo build --no-default-features --release --target ${{ matrix.target }} --features ecdh + - run: cargo build --no-default-features --release --target ${{ matrix.target }} --features pem + - run: cargo build --no-default-features --release --target ${{ matrix.target }} --features pkcs8 - run: cargo build --no-default-features --release --target ${{ matrix.target }} --features zeroize test: runs-on: ubuntu-latest diff --git a/Cargo.lock b/Cargo.lock index c206ef0ed..5cbf5b989 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -94,7 +94,7 @@ dependencies = [ [[package]] name = "const-oid" version = "0.3.0-pre" -source = "git+https://github.com/RustCrypto/utils.git#a9ebedcd9be0af538c9aef319d2025fa607e9566" +source = "git+https://github.com/RustCrypto/utils.git#8285d34b124c726a350e6108f9fb284787fd3976" [[package]] name = "cpuid-bool" @@ -263,9 +263,11 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "pkcs8" version = "0.0.0" -source = "git+https://github.com/RustCrypto/utils.git#a9ebedcd9be0af538c9aef319d2025fa607e9566" +source = "git+https://github.com/RustCrypto/utils.git#8285d34b124c726a350e6108f9fb284787fd3976" dependencies = [ "const-oid", + "subtle-encoding", + "zeroize", ] [[package]] @@ -350,6 +352,15 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "343f3f510c2915908f155e94f17220b19ccfacf2a64a2a5d8004f2c3e311e7fd" +[[package]] +name = "subtle-encoding" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dcb1ed7b8330c5eed5441052651dd7a12c75e2ed88f2ec024ae1fa3a5e59945" +dependencies = [ + "zeroize", +] + [[package]] name = "syn" version = "1.0.40" diff --git a/elliptic-curve/Cargo.toml b/elliptic-curve/Cargo.toml index 97d2a551a..45495ba77 100644 --- a/elliptic-curve/Cargo.toml +++ b/elliptic-curve/Cargo.toml @@ -33,6 +33,7 @@ default = ["arithmetic"] alloc = [] arithmetic = ["bitvec", "ff", "group"] ecdh = ["arithmetic", "zeroize"] +pem = ["pkcs8/pem"] std = ["alloc"] [package.metadata.docs.rs] diff --git a/elliptic-curve/src/secret_key.rs b/elliptic-curve/src/secret_key.rs index a514335ba..d9252cc9d 100644 --- a/elliptic-curve/src/secret_key.rs +++ b/elliptic-curve/src/secret_key.rs @@ -28,6 +28,9 @@ use crate::{ #[cfg(feature = "pkcs8")] use crate::{generic_array::typenum::Unsigned, AlgorithmParameters, ALGORITHM_OID}; +#[cfg(feature = "pem")] +use core::str::FromStr; + /// Inner value stored by a [`SecretKey`]. pub trait SecretValue: Curve { /// Inner secret value. @@ -115,31 +118,51 @@ where } /// Deserialize PKCS#8-encoded private key from ASN.1 DER + /// (binary format). #[cfg(feature = "pkcs8")] #[cfg_attr(docsrs, doc(cfg(feature = "pkcs8")))] pub fn from_pkcs8_der(bytes: &[u8]) -> Result where C: AlgorithmParameters, { - let private_key_info = pkcs8::PrivateKeyInfo::from_der(bytes)?; - - if private_key_info.algorithm.oid != ALGORITHM_OID - || private_key_info.algorithm.parameters != Some(C::OID) - { - return Err(Error); - } + Self::from_pkcs8_private_key_info(pkcs8::PrivateKeyInfo::from_der(bytes)?) + } - Self::from_pkcs8_private_key(private_key_info.private_key) + /// 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")))] + pub fn from_pkcs8_pem(s: &str) -> Result + where + C: AlgorithmParameters, + { + let pkcs8_doc = pkcs8::Document::from_pem(s)?; + Self::from_pkcs8_private_key_info(pkcs8_doc.private_key_info()) } /// Parse the `private_key` field of a PKCS#8-encoded private key's /// `PrivateKeyInfo`. #[cfg(feature = "pkcs8")] #[cfg_attr(docsrs, doc(cfg(feature = "pkcs8")))] - fn from_pkcs8_private_key(bytes: &[u8]) -> Result + fn from_pkcs8_private_key_info( + private_key_info: pkcs8::PrivateKeyInfo<'_>, + ) -> Result where C: AlgorithmParameters, { + if private_key_info.algorithm.oid != ALGORITHM_OID + || private_key_info.algorithm.parameters != Some(C::OID) + { + return Err(Error); + } + + let bytes = private_key_info.private_key; + // Ensure private key is AT LEAST as long as a scalar field element // for this curve along with the following overhead: // @@ -231,11 +254,27 @@ where } } +#[cfg(feature = "pem")] +#[cfg_attr(docsrs, doc(cfg(feature = "pem")))] +impl FromStr for SecretKey +where + C: Curve + AlgorithmParameters + SecretValue, + C::Secret: Clone + Zeroize, + FieldBytes: From, +{ + type Err = Error; + + fn from_str(s: &str) -> Result { + Self::from_pkcs8_pem(s) + } +} + impl Debug for SecretKey where C: Curve + SecretValue, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // TODO(tarcieri): use `debug_struct` and `finish_non_exhaustive` when stable write!(f, "SecretKey<{:?}>{{ ... }}", C::default()) } }