rust: Unifying PKCS #8 private key parsing To support KeyProvider trait from `rustls` we have to be able to parse PKCS #8 encoded private signing keys. With this we are able to first parse a key via EVP_PKEY facility, then dispatch the key to the right signing algorithms safely. Change-Id: Iccbfceb97dd0c2940ebba83b92a2ed426c1a97be Signed-off-by: Xiangfei Ding <xfding@google.com> Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/87688 Reviewed-by: Adam Langley <agl@google.com>
diff --git a/gen/sources.bzl b/gen/sources.bzl index 7671157..3eb86d2 100644 --- a/gen/sources.bzl +++ b/gen/sources.bzl
@@ -2840,6 +2840,7 @@ "rust/bssl-crypto/src/mem.rs", "rust/bssl-crypto/src/mldsa.rs", "rust/bssl-crypto/src/mlkem.rs", + "rust/bssl-crypto/src/pkcs8.rs", "rust/bssl-crypto/src/rand.rs", "rust/bssl-crypto/src/rsa.rs", "rust/bssl-crypto/src/scoped.rs",
diff --git a/gen/sources.cmake b/gen/sources.cmake index 32ff3e7..2353b0a 100644 --- a/gen/sources.cmake +++ b/gen/sources.cmake
@@ -2898,6 +2898,7 @@ rust/bssl-crypto/src/mem.rs rust/bssl-crypto/src/mldsa.rs rust/bssl-crypto/src/mlkem.rs + rust/bssl-crypto/src/pkcs8.rs rust/bssl-crypto/src/rand.rs rust/bssl-crypto/src/rsa.rs rust/bssl-crypto/src/scoped.rs
diff --git a/gen/sources.gni b/gen/sources.gni index 7a4cd9a..21af335 100644 --- a/gen/sources.gni +++ b/gen/sources.gni
@@ -2840,6 +2840,7 @@ "rust/bssl-crypto/src/mem.rs", "rust/bssl-crypto/src/mldsa.rs", "rust/bssl-crypto/src/mlkem.rs", + "rust/bssl-crypto/src/pkcs8.rs", "rust/bssl-crypto/src/rand.rs", "rust/bssl-crypto/src/rsa.rs", "rust/bssl-crypto/src/scoped.rs",
diff --git a/gen/sources.json b/gen/sources.json index c3218bd..6b24ac8 100644 --- a/gen/sources.json +++ b/gen/sources.json
@@ -2822,6 +2822,7 @@ "rust/bssl-crypto/src/mem.rs", "rust/bssl-crypto/src/mldsa.rs", "rust/bssl-crypto/src/mlkem.rs", + "rust/bssl-crypto/src/pkcs8.rs", "rust/bssl-crypto/src/rand.rs", "rust/bssl-crypto/src/rsa.rs", "rust/bssl-crypto/src/scoped.rs",
diff --git a/gen/sources.mk b/gen/sources.mk index aa8237d..94d51b7 100644 --- a/gen/sources.mk +++ b/gen/sources.mk
@@ -2812,6 +2812,7 @@ rust/bssl-crypto/src/mem.rs \ rust/bssl-crypto/src/mldsa.rs \ rust/bssl-crypto/src/mlkem.rs \ + rust/bssl-crypto/src/pkcs8.rs \ rust/bssl-crypto/src/rand.rs \ rust/bssl-crypto/src/rsa.rs \ rust/bssl-crypto/src/scoped.rs \
diff --git a/rust/bssl-crypto/src/ec.rs b/rust/bssl-crypto/src/ec.rs index d939bf75..52b59f1 100644 --- a/rust/bssl-crypto/src/ec.rs +++ b/rust/bssl-crypto/src/ec.rs
@@ -71,7 +71,7 @@ } } -#[derive(Copy, Clone)] +#[derive(Copy, Clone, Eq, PartialEq, Debug)] #[doc(hidden)] pub enum Group { P256, @@ -420,20 +420,36 @@ /// Parses a PrivateKeyInfo structure (from RFC 5208). pub fn from_der_private_key_info(group: Group, der: &[u8]) -> Option<Self> { let alg = group.as_evp_pkey_alg(); - let mut pkey = - scoped::EvpPkey::from_der_private_key_info(der, core::slice::from_ref(&alg))?; - let ec_key = unsafe { bssl_sys::EVP_PKEY_get1_EC_KEY(pkey.as_ffi_ptr()) }; - // We only passed in one allowed algorithm, an EC algorithm. - assert!(!ec_key.is_null()); - // Safety: `ec_key` is now owned by this function. - let parsed_group = unsafe { bssl_sys::EC_KEY_get0_group(ec_key) }; + let pkey = scoped::EvpPkey::from_der_private_key_info(der, core::slice::from_ref(&alg))?; + // Safety: the pkey is not aliased + let ec_key = Self::from_evp_pkey(pkey)?; // We only passed in one allowed algorithm, this EC group. - assert!(parsed_group == group.as_ffi_ptr()); - // Safety: `EVP_PKEY_get1_EC_KEY` returned ownership, which we can move - // into the returned object. + (ec_key.get_group()? == group).then_some(ec_key) + } + + // Safety: the pkey must not be aliased via `as_ffi_ptr` + pub(crate) fn from_evp_pkey(mut pkey: scoped::EvpPkey) -> Option<Self> { + let ec_key = unsafe { bssl_sys::EVP_PKEY_get1_EC_KEY(pkey.as_ffi_ptr()) }; + if ec_key.is_null() { + return None; + } + // Safety: `EVP_PKEY_get1_EC_KEY` returned owned key, which we can move + // into the returned object and whose lifetime is independent of the EVP pkey. Some(Self(ec_key)) } + pub(crate) fn get_group(&self) -> Option<Group> { + // Safety: we own the `EC_KEY` + let id = unsafe { bssl_sys::EC_KEY_get0_group(self.0) }; + if id == Group::P256.as_ffi_ptr() { + Some(Group::P256) + } else if id == Group::P384.as_ffi_ptr() { + Some(Group::P384) + } else { + None + } + } + /// Serializes this private key as a PrivateKeyInfo structure from RFC 5208. pub fn to_der_private_key_info(&self) -> Buffer { let mut pkey = scoped::EvpPkey::new();
diff --git a/rust/bssl-crypto/src/ecdsa.rs b/rust/bssl-crypto/src/ecdsa.rs index a07bf93..dc5d33a 100644 --- a/rust/bssl-crypto/src/ecdsa.rs +++ b/rust/bssl-crypto/src/ecdsa.rs
@@ -206,6 +206,16 @@ }) } + // Caller must make sure that the group of the key matches `C`, + // or else it panics. + pub(crate) fn from_ec_key(key: ec::Key) -> Self { + assert_eq!(key.get_group().unwrap(), C::group()); + Self { + key, + marker: PhantomData, + } + } + /// Serialize this private key as a PrivateKeyInfo structure (from RFC 5208), /// commonly called "PKCS#8 format". pub fn to_der_private_key_info(&self) -> Buffer {
diff --git a/rust/bssl-crypto/src/ed25519.rs b/rust/bssl-crypto/src/ed25519.rs index cf76fef..e5f1063 100644 --- a/rust/bssl-crypto/src/ed25519.rs +++ b/rust/bssl-crypto/src/ed25519.rs
@@ -139,6 +139,24 @@ .expect("The slice is always the correct size for a public key"), ) } + + // Safety: caller must make sure that the key type is ED25519 + pub(crate) unsafe fn from_evp_pkey(mut pkey: scoped::EvpPkey) -> Self { + let mut seed = [0; SEED_LEN]; + let len = &mut { SEED_LEN }; + // Safety: pkey is now owned and len is set + let ret = unsafe { + bssl_sys::EVP_PKEY_get_raw_private_key( + pkey.as_ffi_ptr(), + seed.as_mut_ptr(), + len as *mut _, + ) + }; + // Sanity check, in case the seed is not as long as expected. + assert_eq!(ret, 1); + assert_eq!(*len, SEED_LEN); + Self::from_seed(&seed) + } } impl PublicKey { @@ -183,6 +201,7 @@ PUBLIC_KEY_LEN, ) }); + // Safety: we are only testing pointer nullness, we do not mutate the data assert!(!pkey.as_ffi_ptr().is_null()); cbb_to_buffer(PUBLIC_KEY_LEN + 32, |cbb| unsafe {
diff --git a/rust/bssl-crypto/src/lib.rs b/rust/bssl-crypto/src/lib.rs index 166403f..dfa7ef6 100644 --- a/rust/bssl-crypto/src/lib.rs +++ b/rust/bssl-crypto/src/lib.rs
@@ -54,6 +54,7 @@ pub mod mldsa; #[cfg(feature = "mlalgs")] pub mod mlkem; +pub mod pkcs8; pub mod rsa; pub mod slhdsa; pub mod tls12_prf;
diff --git a/rust/bssl-crypto/src/pkcs8.rs b/rust/bssl-crypto/src/pkcs8.rs new file mode 100644 index 0000000..37940cd --- /dev/null +++ b/rust/bssl-crypto/src/pkcs8.rs
@@ -0,0 +1,71 @@ +// Copyright 2026 The BoringSSL Authors +// +// 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 +// +// https://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. + +//! PKCS#8 support +//! +//! This module houses PKCS#8 private key parsing facility. + +use crate::{ec, ecdsa, ed25519, rsa, scoped::EvpPkey}; + +/// PKCS#8 Signing Key +pub enum SigningKey { + /// An RSA private key + Rsa(rsa::PrivateKey), + /// An NIST P-256 private key + EcP256(ecdsa::PrivateKey<ec::P256>), + /// An NIST P-384 private key + EcP384(ecdsa::PrivateKey<ec::P384>), + /// An Ed25519 key + Ed25519(ed25519::PrivateKey), +} + +impl SigningKey { + /// Parse a DER-encoded PKCS#8 PrivateKeyInfo structure. + pub fn from_der_private_key_info(data: &[u8]) -> Option<Self> { + // Safety: + // - data buffer is guaranteed to be initialised + // - all the algorithm descriptors are static items and remain live at call time + let mut pkey = unsafe { + EvpPkey::from_der_private_key_info( + data, + &[ + bssl_sys::EVP_pkey_rsa(), + bssl_sys::EVP_pkey_ec_p256(), + bssl_sys::EVP_pkey_ec_p384(), + bssl_sys::EVP_pkey_ed25519(), + ], + ) + }?; + // Safety: pkey is initialised + let id = unsafe { bssl_sys::EVP_PKEY_id(pkey.as_ffi_ptr()) }; + unsafe { + // Safety: the pkey is completely owned here + Some(match id { + bssl_sys::EVP_PKEY_RSA => Self::Rsa(rsa::PrivateKey::from_evp_pkey(pkey)?), + bssl_sys::EVP_PKEY_EC => { + let key = ec::Key::from_evp_pkey(pkey)?; + match key.get_group()? { + ec::Group::P256 => Self::EcP256(ecdsa::PrivateKey::from_ec_key(key)), + ec::Group::P384 => Self::EcP384(ecdsa::PrivateKey::from_ec_key(key)), + } + } + bssl_sys::EVP_PKEY_ED25519 => { + // Safety: we are sure that the key is for ED25519 + Self::Ed25519(ed25519::PrivateKey::from_evp_pkey(pkey)) + } + _ => return None, + }) + } + } +}
diff --git a/rust/bssl-crypto/src/rsa.rs b/rust/bssl-crypto/src/rsa.rs index 614f942..728d304 100644 --- a/rust/bssl-crypto/src/rsa.rs +++ b/rust/bssl-crypto/src/rsa.rs
@@ -93,6 +93,7 @@ let alg = unsafe { bssl_sys::EVP_pkey_rsa() }; let mut pkey = scoped::EvpPkey::from_der_subject_public_key_info(spki, core::slice::from_ref(&alg))?; + // Safety: we exclusively own pkey here let rsa = unsafe { bssl_sys::EVP_PKEY_get1_RSA(pkey.as_ffi_ptr()) }; if !rsa.is_null() { // Safety: `EVP_PKEY_get1_RSA` adds a reference so we are not @@ -107,7 +108,9 @@ /// in, for example, X.509 certificates. pub fn to_der_subject_public_key_info(&self) -> Buffer { let mut pkey = scoped::EvpPkey::new(); - // Safety: this takes a reference to `self.0` and so doesn't steal ownership. + // Safety: + // - This takes a reference to `self.0` and so doesn't steal ownership. + // - The pkey is still exclusively owned. assert_eq!(1, unsafe { bssl_sys::EVP_PKEY_set1_RSA(pkey.as_ffi_ptr(), self.0) }); @@ -232,13 +235,16 @@ pub fn from_der_private_key_info(der: &[u8]) -> Option<Self> { // Safety: `EVP_pkey_rsa` is always safe to call. let alg = unsafe { bssl_sys::EVP_pkey_rsa() }; - let mut pkey = - scoped::EvpPkey::from_der_private_key_info(der, core::slice::from_ref(&alg))?; - // Safety: `pkey` is valid and was created just above. + let pkey = scoped::EvpPkey::from_der_private_key_info(der, core::slice::from_ref(&alg))?; + // Safety: `pkey` is valid and exclusively owned. + unsafe { Self::from_evp_pkey(pkey) } + } + + // Safety: the EVP_PKEY must not be aliased through the `as_ffi_ptr` + pub(crate) unsafe fn from_evp_pkey(mut pkey: scoped::EvpPkey) -> Option<Self> { let rsa = unsafe { bssl_sys::EVP_PKEY_get1_RSA(pkey.as_ffi_ptr()) }; // We only passed one allowed algorithm, an RSA algorithm. - assert!(!rsa.is_null()); - Some(Self(rsa)) + (!rsa.is_null()).then_some(Self(rsa)) } /// Serialize to a DER-encrypted PrivateKeyInfo struct (from RFC 5208). This is often called "PKCS#8 format".