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".