[PATCH v3 2/2] rust: crypto: add synchronous RSA akcipher support

Mike Lothian <[email protected]>
Newsgroups org.kernel.vger.rust-for-linux,org.kernel.vger.linux-crypto,org.kernel.vger.linux-kernel
Message-ID <[email protected]>
Add an RAII Rust wrapper for RSA public keys backed by the kernel
crypto_akcipher implementation. Encode modulus and exponent components
in the PKCS#1 DER form accepted by the existing RSA transform and
expose synchronous public-key encryption.

Provide RSAES-OAEP with SHA-256 for consumers which need that standard
encoding, while keeping the OAEP seed explicit for use with the kernel
CSPRNG and deterministic known-answer tests. Wipe encoded messages,
expanded AES keys, and Secret byte strings with memzero_explicit().

Gate the AES, SHA-256, and akcipher bindings with dedicated Rust
Kconfig symbols. Cover the DER encoder and OAEP path with a published
Wycheproof vector.

Assisted-by: Claude:claude-opus-5
Signed-off-by: Mike Lothian <[email protected]>
---
 crypto/Kconfig                  |  10 ++
 lib/crypto/Kconfig              |  19 ++
 rust/bindings/bindings_helper.h |   1 +
 rust/helpers/crypto.c           |  27 +++
 rust/kernel/Kconfig.test        |  14 ++
 rust/kernel/crypto.rs           |  72 +++++++-
 rust/kernel/crypto/akcipher.rs  | 298 ++++++++++++++++++++++++++++++++
 7 files changed, 435 insertions(+), 6 deletions(-)
 create mode 100644 rust/kernel/crypto/akcipher.rs

diff --git a/crypto/Kconfig b/crypto/Kconfig
index f1e372195273..9b7cfdbef7c4 100644
--- a/crypto/Kconfig
+++ b/crypto/Kconfig
@@ -116,6 +116,16 @@ config CRYPTO_AKCIPHER
 	select CRYPTO_AKCIPHER2
 	select CRYPTO_ALGAPI
 
+config RUST_CRYPTO_AKCIPHER
+	bool
+	depends on RUST
+	select CRYPTO_AKCIPHER
+	help
+	  Enable safe Rust abstractions for the public-key cipher API. The
+	  crypto API core is built into the kernel because Rust abstractions
+	  are part of the built-in kernel crate; individual algorithms may
+	  remain modules.
+
 config CRYPTO_KPP2
 	tristate
 	select CRYPTO_ALGAPI2
diff --git a/lib/crypto/Kconfig b/lib/crypto/Kconfig
index 591c1c2a7fb3..9d47fce62f54 100644
--- a/lib/crypto/Kconfig
+++ b/lib/crypto/Kconfig
@@ -36,6 +36,16 @@ config CRYPTO_LIB_AES_CBC_MACS
 	  this if your module uses any of the functions from
 	  <crypto/aes-cbc-macs.h>.
 
+config RUST_CRYPTO_LIB_AES
+	bool
+	depends on RUST
+	select CRYPTO_LIB_AES
+	select CRYPTO_LIB_AES_CBC_MACS
+	help
+	  Enable the Rust bindings for the synchronous AES library functions.
+	  The selected C libraries are built into the kernel because Rust
+	  abstractions are part of the built-in kernel crate.
+
 config CRYPTO_LIB_AESGCM
 	tristate
 	select CRYPTO_LIB_AES
@@ -216,6 +226,15 @@ config CRYPTO_LIB_SHA256
 	  Select this if your module uses any of these functions from
 	  <crypto/sha2.h>.
 
+config RUST_CRYPTO_LIB_SHA256
+	bool
+	depends on RUST
+	select CRYPTO_LIB_SHA256
+	help
+	  Enable the Rust bindings for the synchronous SHA-256 and HMAC-SHA256
+	  library functions. The selected C library is built into the kernel
+	  because Rust abstractions are part of the built-in kernel crate.
+
 config CRYPTO_LIB_SHA256_ARCH
 	bool
 	depends on CRYPTO_LIB_SHA256 && !UML
diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h
index 8d7489b8cce8..bb46317898c1 100644
--- a/rust/bindings/bindings_helper.h
+++ b/rust/bindings/bindings_helper.h
@@ -28,6 +28,7 @@
  */
 #include <linux/hrtimer_types.h>
 
+#include <crypto/akcipher.h>
 #include <crypto/aes.h>
 #include <crypto/sha2.h>
 
diff --git a/rust/helpers/crypto.c b/rust/helpers/crypto.c
index a18780231ce0..f0cdf7fa8a76 100644
--- a/rust/helpers/crypto.c
+++ b/rust/helpers/crypto.c
@@ -1,9 +1,30 @@
 // SPDX-License-Identifier: GPL-2.0
 
+#include <crypto/akcipher.h>
 #include <crypto/aes.h>
 #include <crypto/aes-cbc-macs.h>
 #include <linux/string.h>
 
+__rust_helper void rust_helper_memzero_explicit(void *s, size_t count)
+{
+	memzero_explicit(s, count);
+}
+
+#ifdef CONFIG_RUST_CRYPTO_AKCIPHER
+__rust_helper void rust_helper_crypto_free_akcipher(struct crypto_akcipher *tfm)
+{
+	crypto_free_akcipher(tfm);
+}
+
+__rust_helper int
+rust_helper_crypto_akcipher_set_pub_key(struct crypto_akcipher *tfm,
+					const void *key, unsigned int key_len)
+{
+	return crypto_akcipher_set_pub_key(tfm, key, key_len);
+}
+#endif
+
+#ifdef CONFIG_RUST_CRYPTO_LIB_AES
 /*
  * aes_encrypt() takes a transparent union (aes_encrypt_arg) that bindgen cannot
  * express, so the single-block encrypt step is wrapped here. The key schedule
@@ -19,6 +40,11 @@ rust_helper_aes_enckey_encrypt_block(const struct aes_enckey *key, u8 *out,
 	aes_encrypt(key, out, in);
 }
 
+__rust_helper void rust_helper_aes_enckey_zero(struct aes_enckey *key)
+{
+	memzero_explicit(key, sizeof(*key));
+}
+
 /*
  * AES-CMAC one-shot over the in-tree library (crypto/aes-cbc-macs.h): prepares
  * the 128-bit key, MACs @data and writes the 16-byte tag to @out. A helper
@@ -35,3 +61,4 @@ rust_helper_aes_cmac(const u8 *key, const u8 *data, size_t data_len, u8 *out)
 	aes_cmac(&cmac_key, data, data_len, out);
 	memzero_explicit(&cmac_key, sizeof(cmac_key));
 }
+#endif
diff --git a/rust/kernel/Kconfig.test b/rust/kernel/Kconfig.test
index e6a5c7a795f0..32e1a0c17d08 100644
--- a/rust/kernel/Kconfig.test
+++ b/rust/kernel/Kconfig.test
@@ -83,4 +83,18 @@ config RUST_BITFIELD_KUNIT_TEST
 
 	  If unsure, say N.
 
+config RUST_CRYPTO_KUNIT_TEST
+	bool "KUnit tests for Rust crypto APIs" if !KUNIT_ALL_TESTS
+	default KUNIT_ALL_TESTS
+	select CRYPTO_RSA
+	select RUST_CRYPTO_AKCIPHER
+	select RUST_CRYPTO_LIB_SHA256
+	help
+	  This option enables KUnit tests for the safe Rust crypto APIs,
+	  including public-key encoding and encryption. The tests include
+	  published known-answer vectors and are intended for development and
+	  testing rather than regular kernel use cases.
+
+	  If unsure, say N.
+
 endif
diff --git a/rust/kernel/crypto.rs b/rust/kernel/crypto.rs
index 5f7c301b2bb8..0cf38541c8d1 100644
--- a/rust/kernel/crypto.rs
+++ b/rust/kernel/crypto.rs
@@ -9,18 +9,75 @@
 //! synchronously in the calling context with no allocation; the hashes and the
 //! MAC are infallible.
 //!
-//! C headers: [`include/crypto/aes.h`](srctree/include/crypto/aes.h),
+//! Public-key ciphers are available through [`akcipher`] when
+//! `CONFIG_RUST_CRYPTO_AKCIPHER` is enabled.
+//!
+//! C headers: [`include/crypto/akcipher.h`](srctree/include/crypto/akcipher.h),
+//! [`include/crypto/aes.h`](srctree/include/crypto/aes.h),
 //! [`include/crypto/aes-cbc-macs.h`](srctree/include/crypto/aes-cbc-macs.h),
 //! [`include/crypto/sha2.h`](srctree/include/crypto/sha2.h).
 
-use crate::{bindings, error::to_result, prelude::*};
+use core::ops::{Deref, DerefMut};
+
+use crate::bindings;
+#[cfg(any(
+    CONFIG_RUST_CRYPTO_AKCIPHER,
+    CONFIG_RUST_CRYPTO_LIB_AES,
+    CONFIG_RUST_CRYPTO_LIB_SHA256
+))]
+use crate::{error::to_result, prelude::*};
+
+#[cfg(CONFIG_RUST_CRYPTO_AKCIPHER)]
+pub mod akcipher;
 
 /// Size of a SHA-256 / HMAC-SHA256 digest, in bytes.
 pub const SHA256_DIGEST_SIZE: usize = 32;
 /// AES-128 block and key size, in bytes.
 pub const AES128_BLOCK_SIZE: usize = 16;
 
+/// A fixed-size byte string which is wiped when dropped.
+///
+/// This is intended for cryptographic keys and other sensitive intermediate
+/// values. Borrowing the contained bytes can still create copies which this
+/// type cannot track; callers should avoid copying them unnecessarily.
+pub struct Secret<const N: usize>([u8; N]);
+
+impl<const N: usize> Secret<N> {
+    /// Wraps bytes which should be wiped when their owner is dropped.
+    pub const fn new(bytes: [u8; N]) -> Self {
+        Self(bytes)
+    }
+
+    /// Creates an all-zero byte string.
+    pub const fn zeroed() -> Self {
+        Self([0; N])
+    }
+}
+
+impl<const N: usize> Deref for Secret<N> {
+    type Target = [u8; N];
+
+    fn deref(&self) -> &Self::Target {
+        &self.0
+    }
+}
+
+impl<const N: usize> DerefMut for Secret<N> {
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        &mut self.0
+    }
+}
+
+impl<const N: usize> Drop for Secret<N> {
+    fn drop(&mut self) {
+        // SAFETY: `self.0` is valid for exactly `N` writable bytes. The helper
+        // uses `memzero_explicit()`, so the wipe is not optimised away.
+        unsafe { bindings::memzero_explicit(self.0.as_mut_ptr().cast(), N) };
+    }
+}
+
 /// Returns the SHA-256 digest of `data`.
+#[cfg(CONFIG_RUST_CRYPTO_LIB_SHA256)]
 pub fn sha256(data: &[u8]) -> [u8; SHA256_DIGEST_SIZE] {
     let mut out = [0u8; SHA256_DIGEST_SIZE];
     // SAFETY: `data` is valid for `data.len()` reads and `out` is a valid
@@ -30,6 +87,7 @@
 }
 
 /// Returns `HMAC-SHA256(key, data)`.
+#[cfg(CONFIG_RUST_CRYPTO_LIB_SHA256)]
 pub fn hmac_sha256(key: &[u8], data: &[u8]) -> [u8; SHA256_DIGEST_SIZE] {
     let mut out = [0u8; SHA256_DIGEST_SIZE];
     // SAFETY: `key` and `data` are valid for their respective lengths and `out`
@@ -51,6 +109,7 @@
 /// prepared and wiped internally; the call is infallible.
 ///
 /// [`include/crypto/aes-cbc-macs.h`]: srctree/include/crypto/aes-cbc-macs.h
+#[cfg(CONFIG_RUST_CRYPTO_LIB_AES)]
 pub fn aes_cmac(key: &[u8; AES128_BLOCK_SIZE], data: &[u8]) -> [u8; AES128_BLOCK_SIZE] {
     let mut out = [0u8; AES128_BLOCK_SIZE];
     // SAFETY: `key` is a valid 16-byte key, `data` is valid for `data.len()`
@@ -77,8 +136,10 @@
 /// let _ct = cipher.encrypt_block(&[0u8; 16]);
 /// # Ok::<(), Error>(())
 /// ```
+#[cfg(CONFIG_RUST_CRYPTO_LIB_AES)]
 pub struct Aes128(bindings::aes_enckey);
 
+#[cfg(CONFIG_RUST_CRYPTO_LIB_AES)]
 impl Aes128 {
     /// Expands an AES-128 key from 16 raw key bytes.
     pub fn new(key: &[u8; AES128_BLOCK_SIZE]) -> Result<Self> {
@@ -105,11 +166,10 @@ impl Aes128 {
     }
 }
 
+#[cfg(CONFIG_RUST_CRYPTO_LIB_AES)]
 impl Drop for Aes128 {
     fn drop(&mut self) {
-        // SAFETY: `self.0` is a valid, owned `aes_enckey`; overwriting it with
-        // an all-zero `aes_enckey` clears the expanded key schedule.
-        // `write_volatile` keeps the store from being optimised away.
-        unsafe { core::ptr::write_volatile(&mut self.0, core::mem::zeroed()) };
+        // SAFETY: `self.0` is a valid, owned `aes_enckey`.
+        unsafe { bindings::aes_enckey_zero(&mut self.0) };
     }
 }
diff --git a/rust/kernel/crypto/akcipher.rs b/rust/kernel/crypto/akcipher.rs
new file mode 100644
index 000000000000..c5fc2ca0cfbe
--- /dev/null
+++ b/rust/kernel/crypto/akcipher.rs
@@ -0,0 +1,298 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Safe wrappers for the kernel public-key cipher API.
+//!
+//! C header: [`include/crypto/akcipher.h`](srctree/include/crypto/akcipher.h)
+
+use core::ptr::NonNull;
+
+use crate::{
+    alloc::{Flags, KVec},
+    bindings, c_str,
+    crypto::{sha256, SHA256_DIGEST_SIZE},
+    error::{from_err_ptr, to_result},
+    prelude::*,
+};
+
+/// A configured RSA public key.
+///
+/// The key is backed by the existing kernel `"rsa"` akcipher implementation.
+/// Creating it converts unsigned big-endian modulus and exponent components
+/// into the PKCS#1 DER form consumed by the crypto API.
+pub struct RsaPublicKey {
+    tfm: NonNull<bindings::crypto_akcipher>,
+    size: usize,
+}
+
+impl RsaPublicKey {
+    /// Create an RSA public key from unsigned big-endian components.
+    pub fn new(modulus: &[u8], exponent: &[u8], flags: Flags) -> Result<Self> {
+        let modulus = trim_unsigned(modulus).ok_or(EINVAL)?;
+        let exponent = trim_unsigned(exponent).ok_or(EINVAL)?;
+        let der = encode_rsa_public_key(modulus, exponent, flags)?;
+
+        // SAFETY: The name is NUL-terminated and remains live for the call.
+        let tfm = from_err_ptr(unsafe {
+            bindings::crypto_alloc_akcipher(c_str!("rsa").as_char_ptr(), 0, 0)
+        })?;
+        let tfm = NonNull::new(tfm).ok_or(ENOMEM)?;
+
+        // SAFETY: `tfm` is a live akcipher transform and `der` contains
+        // `der.len()` initialized bytes.
+        let result = to_result(unsafe {
+            bindings::crypto_akcipher_set_pub_key(
+                tfm.as_ptr(),
+                der.as_ptr().cast(),
+                der.len().try_into()?,
+            )
+        });
+        if let Err(err) = result {
+            // SAFETY: `tfm` was returned by `crypto_alloc_akcipher()` and has
+            // not been freed.
+            unsafe { bindings::crypto_free_akcipher(tfm.as_ptr()) };
+            return Err(err);
+        }
+
+        Ok(Self {
+            tfm,
+            size: modulus.len(),
+        })
+    }
+
+    /// Return the RSA modulus size in bytes.
+    pub fn size(&self) -> usize {
+        self.size
+    }
+
+    /// Apply the raw RSA public-key operation to one already-encoded message.
+    ///
+    /// Both buffers must have the modulus size. The output is fixed-width,
+    /// unsigned, and big-endian. Prefer a padded scheme such as
+    /// [`oaep_sha256_encrypt`](Self::oaep_sha256_encrypt).
+    pub fn encrypt(&mut self, encoded: &[u8], out: &mut [u8]) -> Result {
+        if encoded.len() != self.size || out.len() != self.size {
+            return Err(EINVAL);
+        }
+
+        out.fill(0);
+        // SAFETY: `self.tfm` remains live and exclusively borrowed for the
+        // synchronous operation; both buffers are valid for their lengths.
+        to_result(unsafe {
+            bindings::crypto_akcipher_sync_encrypt(
+                self.tfm.as_ptr(),
+                encoded.as_ptr().cast(),
+                encoded.len().try_into()?,
+                out.as_mut_ptr().cast(),
+                out.len().try_into()?,
+            )
+        })
+    }
+
+    /// Encrypt a message using RSAES-OAEP with SHA-256 and an empty label.
+    ///
+    /// `seed` is a caller-provided random OAEP seed. It is explicit so callers
+    /// can use the kernel CSPRNG while tests can use published deterministic
+    /// vectors.
+    #[cfg(CONFIG_RUST_CRYPTO_LIB_SHA256)]
+    pub fn oaep_sha256_encrypt(
+        &mut self,
+        message: &[u8],
+        seed: &[u8; SHA256_DIGEST_SIZE],
+        out: &mut [u8],
+        flags: Flags,
+    ) -> Result {
+        let overhead = 2 * SHA256_DIGEST_SIZE + 2;
+        if out.len() != self.size || self.size < overhead || message.len() > self.size - overhead {
+            return Err(EINVAL);
+        }
+
+        let mut encoded = KVec::from_elem(0u8, self.size, flags)?;
+        let result = (|| {
+            encoded[1..1 + SHA256_DIGEST_SIZE].copy_from_slice(seed);
+            let db = &mut encoded[1 + SHA256_DIGEST_SIZE..];
+            db[..SHA256_DIGEST_SIZE].copy_from_slice(&sha256(&[]));
+            let separator = db.len() - message.len() - 1;
+            db[separator] = 1;
+            db[separator + 1..].copy_from_slice(message);
+
+            mgf1_sha256_xor(seed, db, flags)?;
+            let (seed_block, masked_db) = encoded[1..].split_at_mut(SHA256_DIGEST_SIZE);
+            mgf1_sha256_xor(masked_db, seed_block, flags)?;
+
+            self.encrypt(&encoded, out)
+        })();
+        encoded.fill(0);
+        result
+    }
+}
+
+impl Drop for RsaPublicKey {
+    fn drop(&mut self) {
+        // SAFETY: `self.tfm` was returned by `crypto_alloc_akcipher()` and is
+        // owned by this object.
+        unsafe { bindings::crypto_free_akcipher(self.tfm.as_ptr()) };
+    }
+}
+
+#[cfg(CONFIG_RUST_CRYPTO_LIB_SHA256)]
+fn mgf1_sha256_xor(seed: &[u8], output: &mut [u8], flags: Flags) -> Result {
+    let mut input = KVec::with_capacity(seed.len().checked_add(4).ok_or(EOVERFLOW)?, flags)?;
+    let result = (|| {
+        let mut counter = 0u32;
+        for chunk in output.chunks_mut(SHA256_DIGEST_SIZE) {
+            input.clear();
+            input.extend_from_slice(seed, flags)?;
+            input.extend_from_slice(&counter.to_be_bytes(), flags)?;
+            let digest = sha256(&input);
+            for (byte, mask) in chunk.iter_mut().zip(digest) {
+                *byte ^= mask;
+            }
+            counter = counter.checked_add(1).ok_or(EOVERFLOW)?;
+        }
+        Ok(())
+    })();
+    input.fill(0);
+    result
+}
+
+fn trim_unsigned(value: &[u8]) -> Option<&[u8]> {
+    let value = value
+        .iter()
+        .position(|byte| *byte != 0)
+        .map(|i| &value[i..])?;
+    Some(value)
+}
+
+fn der_length_size(length: usize) -> Result<usize> {
+    if length < 128 {
+        return Ok(1);
+    }
+
+    let bytes = (usize::BITS - length.leading_zeros()).div_ceil(8) as usize;
+    if bytes > 126 {
+        return Err(EOVERFLOW);
+    }
+    Ok(1 + bytes)
+}
+
+fn push_der_length(out: &mut KVec<u8>, length: usize, flags: Flags) -> Result {
+    if length < 128 {
+        out.push(length as u8, flags)?;
+        return Ok(());
+    }
+
+    let bytes = der_length_size(length)? - 1;
+    out.push(0x80 | bytes as u8, flags)?;
+    for shift in (0..bytes).rev() {
+        out.push((length >> (shift * 8)) as u8, flags)?;
+    }
+    Ok(())
+}
+
+fn der_integer_size(value: &[u8]) -> Result<usize> {
+    let leading_zero = usize::from(value[0] & 0x80 != 0);
+    1usize
+        .checked_add(der_length_size(value.len() + leading_zero)?)
+        .and_then(|size| size.checked_add(value.len() + leading_zero))
+        .ok_or(EOVERFLOW)
+}
+
+fn push_der_integer(out: &mut KVec<u8>, value: &[u8], flags: Flags) -> Result {
+    let leading_zero = value[0] & 0x80 != 0;
+    out.push(0x02, flags)?;
+    push_der_length(out, value.len() + usize::from(leading_zero), flags)?;
+    if leading_zero {
+        out.push(0, flags)?;
+    }
+    out.extend_from_slice(value, flags)?;
+    Ok(())
+}
+
+fn encode_rsa_public_key(modulus: &[u8], exponent: &[u8], flags: Flags) -> Result<KVec<u8>> {
+    let content_len = der_integer_size(modulus)?
+        .checked_add(der_integer_size(exponent)?)
+        .ok_or(EOVERFLOW)?;
+    let total_len = 1usize
+        .checked_add(der_length_size(content_len)?)
+        .and_then(|size| size.checked_add(content_len))
+        .ok_or(EOVERFLOW)?;
+    let mut der = KVec::with_capacity(total_len, flags)?;
+    der.push(0x30, flags)?;
+    push_der_length(&mut der, content_len, flags)?;
+    push_der_integer(&mut der, modulus, flags)?;
+    push_der_integer(&mut der, exponent, flags)?;
+    Ok(der)
+}
+
+#[cfg(CONFIG_RUST_CRYPTO_KUNIT_TEST)]
+#[crate::macros::kunit_tests(rust_kernel_crypto_akcipher)]
+mod tests {
+    use super::*;
+    use crate::alloc::flags::GFP_KERNEL;
+
+    // Wycheproof rsa_oaep_2048_sha256_mgf1sha256_test.json, test case 3.
+    const OAEP_MODULUS: [u8; 256] = [
+        0xa2, 0xb4, 0x51, 0xa0, 0x7d, 0x0a, 0xa5, 0xf9, 0x6e, 0x45, 0x56, 0x71, 0x51, 0x35, 0x50,
+        0x51, 0x4a, 0x8a, 0x5b, 0x46, 0x2e, 0xbe, 0xf7, 0x17, 0x09, 0x4f, 0xa1, 0xfe, 0xe8, 0x22,
+        0x24, 0xe6, 0x37, 0xf9, 0x74, 0x6d, 0x3f, 0x7c, 0xaf, 0xd3, 0x18, 0x78, 0xd8, 0x03, 0x25,
+        0xb6, 0xef, 0x5a, 0x17, 0x00, 0xf6, 0x59, 0x03, 0xb4, 0x69, 0x42, 0x9e, 0x89, 0xd6, 0xea,
+        0xc8, 0x84, 0x50, 0x97, 0xb5, 0xab, 0x39, 0x31, 0x89, 0xdb, 0x92, 0x51, 0x2e, 0xd8, 0xa7,
+        0x71, 0x1a, 0x12, 0x53, 0xfa, 0xcd, 0x20, 0xf7, 0x9c, 0x15, 0xe8, 0x24, 0x7f, 0x3d, 0x3e,
+        0x42, 0xe4, 0x6e, 0x48, 0xc9, 0x8e, 0x25, 0x4a, 0x2f, 0xe9, 0x76, 0x53, 0x13, 0xa0, 0x3e,
+        0xff, 0x8f, 0x17, 0xe1, 0xa0, 0x29, 0x39, 0x7a, 0x1f, 0xa2, 0x6a, 0x8d, 0xce, 0x26, 0xf4,
+        0x90, 0xed, 0x81, 0x29, 0x96, 0x15, 0xd9, 0x81, 0x4c, 0x22, 0xda, 0x61, 0x04, 0x28, 0xe0,
+        0x9c, 0x7d, 0x96, 0x58, 0x59, 0x42, 0x66, 0xf5, 0xc0, 0x21, 0xd0, 0xfc, 0xec, 0xa0, 0x8d,
+        0x94, 0x5a, 0x12, 0xbe, 0x82, 0xde, 0x4d, 0x1e, 0xce, 0x6b, 0x4c, 0x03, 0x14, 0x5b, 0x5d,
+        0x34, 0x95, 0xd4, 0xed, 0x54, 0x11, 0xeb, 0x87, 0x8d, 0xaf, 0x05, 0xfd, 0x7a, 0xfc, 0x3e,
+        0x09, 0xad, 0xa0, 0xf1, 0x12, 0x64, 0x22, 0xf5, 0x90, 0x97, 0x5a, 0x19, 0x69, 0x81, 0x6f,
+        0x48, 0x69, 0x8b, 0xcb, 0xba, 0x1b, 0x4d, 0x9c, 0xae, 0x79, 0xd4, 0x60, 0xd8, 0xf9, 0xf8,
+        0x5e, 0x79, 0x75, 0x00, 0x5d, 0x9b, 0xc2, 0x2c, 0x4e, 0x5a, 0xc0, 0xf7, 0xc1, 0xa4, 0x5d,
+        0x12, 0x56, 0x9a, 0x62, 0x80, 0x7d, 0x3b, 0x9a, 0x02, 0xe5, 0xa5, 0x30, 0xe7, 0x73, 0x06,
+        0x6f, 0x45, 0x3d, 0x1f, 0x5b, 0x4c, 0x2e, 0x9c, 0xf7, 0x82, 0x02, 0x83, 0xf7, 0x42, 0xb9,
+        0xd5,
+    ];
+    const OAEP_SEED: [u8; 32] = [
+        0x70, 0x97, 0x14, 0xb0, 0x48, 0xc3, 0x69, 0x73, 0x22, 0x69, 0xa3, 0xd8, 0xf9, 0x23, 0x02,
+        0x50, 0x87, 0x70, 0xa4, 0x43, 0x68, 0x01, 0x4b, 0x3a, 0x5c, 0xb1, 0x85, 0xc0, 0xc9, 0x1d,
+        0x97, 0x2c,
+    ];
+    const OAEP_CIPHERTEXT: [u8; 256] = [
+        0x5e, 0xab, 0x3f, 0x07, 0x41, 0xe6, 0x39, 0x86, 0xed, 0x64, 0x7d, 0x53, 0xe1, 0xcd, 0x71,
+        0xdf, 0x04, 0x19, 0x86, 0x90, 0x08, 0x03, 0xd0, 0xf9, 0x9c, 0x68, 0x35, 0x5d, 0x24, 0x9a,
+        0x15, 0xa4, 0x7d, 0xc5, 0xb4, 0xf7, 0x0a, 0x19, 0x14, 0x77, 0x65, 0x42, 0x99, 0xe5, 0xa2,
+        0x73, 0x1f, 0x3b, 0x4e, 0xec, 0x76, 0xde, 0xa1, 0x82, 0x62, 0xfc, 0x69, 0x6a, 0xc7, 0x94,
+        0xe5, 0xf6, 0x6c, 0xbf, 0xcd, 0xda, 0xc4, 0x47, 0x2c, 0x57, 0x8e, 0x24, 0x6c, 0x26, 0x70,
+        0x75, 0x98, 0x05, 0x55, 0x84, 0x54, 0x0b, 0x83, 0x98, 0x36, 0xb1, 0x40, 0x4c, 0x56, 0x11,
+        0xae, 0x55, 0x8a, 0x98, 0x4c, 0xee, 0x8f, 0xd0, 0x36, 0xce, 0xa9, 0x24, 0xe0, 0xbe, 0x24,
+        0x74, 0xa9, 0x40, 0xf6, 0x1e, 0x0a, 0xcc, 0x14, 0xfc, 0xae, 0x95, 0xeb, 0xdc, 0x59, 0x94,
+        0x2a, 0x9c, 0xe9, 0xaf, 0x9a, 0x9c, 0x81, 0x99, 0x9f, 0x7f, 0x68, 0x15, 0xf0, 0x57, 0xff,
+        0xdc, 0x25, 0x33, 0xcb, 0x15, 0xd6, 0x39, 0x1d, 0x1e, 0x2d, 0x95, 0xf1, 0x6f, 0x9c, 0x04,
+        0x20, 0x9c, 0x88, 0x9a, 0x4c, 0x35, 0x9c, 0x7d, 0x29, 0x26, 0xd2, 0x8a, 0x66, 0xe2, 0xb0,
+        0x30, 0xa4, 0x16, 0xb9, 0x28, 0xd2, 0x82, 0x56, 0x27, 0x99, 0x8e, 0x51, 0x91, 0xfb, 0x49,
+        0x83, 0xa6, 0xe6, 0x50, 0x24, 0x26, 0x2d, 0x94, 0xfc, 0x09, 0x18, 0x7a, 0x2d, 0x78, 0x16,
+        0x21, 0x22, 0x43, 0x32, 0x51, 0xd1, 0xbf, 0xcc, 0x8e, 0x50, 0x7d, 0x06, 0xeb, 0xa2, 0xd2,
+        0x29, 0xc1, 0x00, 0x31, 0x26, 0x1d, 0xa3, 0x2a, 0xb8, 0xcc, 0xd1, 0x5f, 0x1c, 0x5f, 0x9f,
+        0xbf, 0x07, 0xed, 0x15, 0x84, 0x83, 0xd7, 0x36, 0xa1, 0x10, 0xaf, 0x4b, 0x44, 0xd6, 0xa4,
+        0xda, 0x60, 0xd6, 0xcb, 0x51, 0x9b, 0x44, 0x54, 0x21, 0x3c, 0xf9, 0xf0, 0xdc, 0x56, 0x0f,
+        0x2b,
+    ];
+
+    #[test]
+    fn rsa_der_encoding() -> Result {
+        let der = encode_rsa_public_key(&[0x80, 0x01], &[0x01, 0x00, 0x01], GFP_KERNEL)?;
+        assert_eq!(
+            der.as_slice(),
+            &[0x30, 0x0a, 0x02, 0x03, 0x00, 0x80, 0x01, 0x02, 0x03, 0x01, 0x00, 0x01]
+        );
+        Ok(())
+    }
+
+    #[test]
+    fn rsa_oaep_sha256_wycheproof() -> Result {
+        let mut key = RsaPublicKey::new(&OAEP_MODULUS, &[0x01, 0x00, 0x01], GFP_KERNEL)?;
+        let mut out = [0u8; 256];
+        key.oaep_sha256_encrypt(b"Test", &OAEP_SEED, &mut out, GFP_KERNEL)?;
+        assert_eq!(out, OAEP_CIPHERTEXT);
+        Ok(())
+    }
+}
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.