* [RFC PATCH 0/2] rust: crypto: library AES-128 / SHA-256 / HMAC + RSA
@ 2026-06-17 15:01 Mike Lothian
2026-06-17 15:01 ` [RFC PATCH 1/2] rust: crypto: add library AES-128 / SHA-256 / HMAC-SHA256 bindings Mike Lothian
` (3 more replies)
0 siblings, 4 replies; 11+ messages in thread
From: Mike Lothian @ 2026-06-17 15:01 UTC (permalink / raw)
To: rust-for-linux
Cc: Mike Lothian, linux-crypto, Eric Biggers, Herbert Xu,
David S. Miller, Ard Biesheuvel, Miguel Ojeda, Boqun Feng,
Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg,
Alice Ryhl, Trevor Gross, Danilo Krummrich, linux-kernel
This RFC series adds a small, reusable kernel::crypto module so in-kernel
Rust code can hash, encrypt a single AES block, and do RSA public-key
encryption:
1/2 sha256(), hmac_sha256(), Aes128 (single-block ECB)
2/2 Akcipher + rsa_pubkey_encrypt() over crypto_akcipher
Patch 1 binds the library crypto (lib/crypto) functions directly
(SHA-256 / HMAC-SHA256) and uses one rust_helper_ shim for aes_encrypt()
(its transparent union is unbindable). It runs synchronously in the
calling context with no allocation and is the independently-mergeable,
self-contained contribution.
Patch 2 adds crypto::Akcipher, a thin wrapper over the asynchronous
public-key API (crypto_akcipher) driven synchronously, and a
crypto::rsa_pubkey_encrypt() convenience built on it: it DER-encodes the
RSAPublicKey the "rsa" transform expects, runs one encrypt, and leaves
padding to the caller. The request/scatterlist/completion plumbing (all
static-inline or on-stack) plus a kmalloc bounce for the DMA data path
live in one rust_helper_ shim; crypto_free_akcipher() and
crypto_akcipher_set_pub_key() are exposed through 1:1 shims. Going
through crypto_akcipher rather than the MPI math library means it
composes with any registered RSA implementation, including hardware
offload. It is kept a separate patch so the public-key surface can be
reviewed (or deferred) on its own without touching patch 1.
Both were factored out of an out-of-tree in-kernel Rust DisplayLink DL3
dock driver (which needs SHA/HMAC/AES for HDCP 2.2 and RSA for the AKE),
but the module is generic. Compile-tested in-tree against drm-next.
Mike Lothian (2):
rust: crypto: add library AES-128 / SHA-256 / HMAC-SHA256 bindings
rust: crypto: add RSA public-key encryption via crypto_akcipher
rust/bindings/bindings_helper.h | 3 +
rust/helpers/crypto.c | 95 +++++++++++++++++++++++++++
rust/helpers/helpers.c | 1 +
rust/kernel/crypto.rs | 255 ++++++++++++++++++++++++++++++++++
rust/kernel/lib.rs | 1 +
5 files changed, 355 insertions(+)
--
2.54.0
^ permalink raw reply [flat|nested] 11+ messages in thread* [RFC PATCH 1/2] rust: crypto: add library AES-128 / SHA-256 / HMAC-SHA256 bindings 2026-06-17 15:01 [RFC PATCH 0/2] rust: crypto: library AES-128 / SHA-256 / HMAC + RSA Mike Lothian @ 2026-06-17 15:01 ` Mike Lothian 2026-06-17 17:18 ` Eric Biggers 2026-06-17 15:01 ` [RFC PATCH 2/2] rust: crypto: add RSA public-key encryption via crypto_akcipher Mike Lothian ` (2 subsequent siblings) 3 siblings, 1 reply; 11+ messages in thread From: Mike Lothian @ 2026-06-17 15:01 UTC (permalink / raw) To: rust-for-linux Cc: Mike Lothian, linux-crypto, Eric Biggers, Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross, Danilo Krummrich, Daniel Almeida, Greg Kroah-Hartman, Yury Norov (NVIDIA), Asahi Lina, Lorenzo Stoakes, Joel Fernandes, Alexandre Courbot, FUJITA Tomonori, Krishna Ketan Rai, linux-kernel Add a small `kernel::crypto` module exposing the kernel's synchronous library crypto (lib/crypto) to Rust: SHA-256, HMAC-SHA256 and single-block AES-128 ECB. These are one-shot, allocation-free and run in the calling context, suitable for in-kernel Rust users that need a hash or a block cipher without the full asynchronous crypto API. SHA-256 (`sha256()`) and HMAC-SHA256 (`hmac_sha256_usingrawkey()`) are plain exported functions and are bound directly via bindgen, so their header `<crypto/sha2.h>` is added to bindings_helper.h. AES single-block encryption goes through a `rust_helper_` shim because `aes_encrypt()` takes a transparent union (`aes_encrypt_arg`) that bindgen cannot express; the shim also zeroes the expanded key schedule before returning. The Rust API: `crypto::sha256(&[u8]) -> [u8; 32]`, `crypto::hmac_sha256(key, data) -> [u8; 32]`, and the `crypto::Aes128` type, created with `Aes128::new(key)` and used via `encrypt_block(&[u8; 16]) -> Result<[u8; 16]>`. Signed-off-by: Mike Lothian <mike@fireburn.co.uk> Assisted-by: Claude:claude-opus-4-8 [Claude-Code] --- rust/bindings/bindings_helper.h | 2 + rust/helpers/crypto.c | 25 +++++++++++ rust/helpers/helpers.c | 1 + rust/kernel/crypto.rs | 77 +++++++++++++++++++++++++++++++++ rust/kernel/lib.rs | 1 + 5 files changed, 106 insertions(+) create mode 100644 rust/helpers/crypto.c create mode 100644 rust/kernel/crypto.rs diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h index 1124785e210b..14671e1825bb 100644 --- a/rust/bindings/bindings_helper.h +++ b/rust/bindings/bindings_helper.h @@ -28,6 +28,8 @@ */ #include <linux/hrtimer_types.h> +#include <crypto/sha2.h> + #include <linux/acpi.h> #include <linux/gpu_buddy.h> #include <drm/drm_device.h> diff --git a/rust/helpers/crypto.c b/rust/helpers/crypto.c new file mode 100644 index 000000000000..dc9614f6fc8e --- /dev/null +++ b/rust/helpers/crypto.c @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include <crypto/aes.h> +#include <linux/string.h> + +/* + * AES-128 single-block ECB encryption: out = AES(key, in). + * + * A helper because aes_encrypt() takes a transparent union (aes_encrypt_arg) + * that bindgen cannot express. SHA-256 and HMAC-SHA256 are plain extern + * functions and are bound directly. + */ +__rust_helper int +rust_helper_aes128_encrypt_block(const u8 *key, const u8 *in, u8 *out) +{ + struct aes_enckey enckey; + int ret; + + ret = aes_prepareenckey(&enckey, key, AES_KEYSIZE_128); + if (ret) + return ret; + aes_encrypt(&enckey, out, in); + memzero_explicit(&enckey, sizeof(enckey)); + return 0; +} diff --git a/rust/helpers/helpers.c b/rust/helpers/helpers.c index 4488a87223b9..45e67929251e 100644 --- a/rust/helpers/helpers.c +++ b/rust/helpers/helpers.c @@ -55,6 +55,7 @@ #include "cpufreq.c" #include "cpumask.c" #include "cred.c" +#include "crypto.c" #include "device.c" #include "dma.c" #include "dma-resv.c" diff --git a/rust/kernel/crypto.rs b/rust/kernel/crypto.rs new file mode 100644 index 000000000000..c8f2cb994cfd --- /dev/null +++ b/rust/kernel/crypto.rs @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Safe wrappers over the kernel's synchronous library crypto. +//! +//! Exposes the one-shot `lib/crypto` primitives — AES-128 single-block ECB, +//! SHA-256 and HMAC-SHA256 — for use from Rust. They run synchronously in the +//! calling context with no allocation; the hashes are infallible. +//! +//! C headers: [`include/crypto/aes.h`](srctree/include/crypto/aes.h), +//! [`include/crypto/sha2.h`](srctree/include/crypto/sha2.h). + +use crate::{bindings, error::to_result, prelude::*}; + +/// 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; + +/// Returns the SHA-256 digest of `data`. +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 + // `SHA256_DIGEST_SIZE`-byte output buffer, as `sha256()` requires. + unsafe { bindings::sha256(data.as_ptr(), data.len(), out.as_mut_ptr()) }; + out +} + +/// Returns `HMAC-SHA256(key, data)`. +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` + // is a valid `SHA256_DIGEST_SIZE`-byte output buffer, as required. + unsafe { + bindings::hmac_sha256_usingrawkey( + key.as_ptr(), + key.len(), + data.as_ptr(), + data.len(), + out.as_mut_ptr(), + ) + }; + out +} + +/// An AES-128 key usable for single-block ECB encryption. +/// +/// # Examples +/// +/// ``` +/// use kernel::crypto::Aes128; +/// let cipher = Aes128::new([0u8; 16]); +/// let _ct = cipher.encrypt_block(&[0u8; 16])?; +/// # Ok::<(), Error>(()) +/// ``` +pub struct Aes128([u8; AES128_BLOCK_SIZE]); + +impl Aes128 { + /// Creates an AES-128 key from 16 raw key bytes. + pub fn new(key: [u8; AES128_BLOCK_SIZE]) -> Self { + Self(key) + } + + /// Encrypts one 16-byte block: returns `AES-128-ECB(key, block)`. + pub fn encrypt_block( + &self, + block: &[u8; AES128_BLOCK_SIZE], + ) -> Result<[u8; AES128_BLOCK_SIZE]> { + let mut out = [0u8; AES128_BLOCK_SIZE]; + // SAFETY: `self.0`, `block` and `out` are all valid 16-byte buffers, as + // the helper requires. + let ret = unsafe { + bindings::aes128_encrypt_block(self.0.as_ptr(), block.as_ptr(), out.as_mut_ptr()) + }; + to_result(ret)?; + Ok(out) + } +} diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs index b72b2fbe046d..3448fa3a0e9e 100644 --- a/rust/kernel/lib.rs +++ b/rust/kernel/lib.rs @@ -58,6 +58,7 @@ pub mod cpufreq; pub mod cpumask; pub mod cred; +pub mod crypto; pub mod debugfs; pub mod device; pub mod device_id; -- 2.54.0 ^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [RFC PATCH 1/2] rust: crypto: add library AES-128 / SHA-256 / HMAC-SHA256 bindings 2026-06-17 15:01 ` [RFC PATCH 1/2] rust: crypto: add library AES-128 / SHA-256 / HMAC-SHA256 bindings Mike Lothian @ 2026-06-17 17:18 ` Eric Biggers 0 siblings, 0 replies; 11+ messages in thread From: Eric Biggers @ 2026-06-17 17:18 UTC (permalink / raw) To: Mike Lothian Cc: rust-for-linux, linux-crypto, Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross, Danilo Krummrich, Daniel Almeida, Greg Kroah-Hartman, Yury Norov (NVIDIA), Asahi Lina, Lorenzo Stoakes, Joel Fernandes, Alexandre Courbot, FUJITA Tomonori, Krishna Ketan Rai, linux-kernel On Wed, Jun 17, 2026 at 04:01:32PM +0100, Mike Lothian wrote: > +/* > + * AES-128 single-block ECB encryption: out = AES(key, in). > + * > + * A helper because aes_encrypt() takes a transparent union (aes_encrypt_arg) > + * that bindgen cannot express. SHA-256 and HMAC-SHA256 are plain extern > + * functions and are bound directly. > + */ > +__rust_helper int > +rust_helper_aes128_encrypt_block(const u8 *key, const u8 *in, u8 *out) > +{ > + struct aes_enckey enckey; > + int ret; > + > + ret = aes_prepareenckey(&enckey, key, AES_KEYSIZE_128); > + if (ret) > + return ret; > + aes_encrypt(&enckey, out, in); > + memzero_explicit(&enckey, sizeof(enckey)); > + return 0; > +} This is kind of an anti-pattern, both in expanding the key for every block and also exposing bare AES instead of AES modes of operation. It's true that lib/crypto/ is missing a lot of AES modes (I'm working on that), but AES-CMAC is there already which is one of the two you need. - Eric ^ permalink raw reply [flat|nested] 11+ messages in thread
* [RFC PATCH 2/2] rust: crypto: add RSA public-key encryption via crypto_akcipher 2026-06-17 15:01 [RFC PATCH 0/2] rust: crypto: library AES-128 / SHA-256 / HMAC + RSA Mike Lothian 2026-06-17 15:01 ` [RFC PATCH 1/2] rust: crypto: add library AES-128 / SHA-256 / HMAC-SHA256 bindings Mike Lothian @ 2026-06-17 15:01 ` Mike Lothian 2026-06-17 17:52 ` Eric Biggers 2026-06-17 15:13 ` [RFC PATCH 0/2] rust: crypto: library AES-128 / SHA-256 / HMAC + RSA Miguel Ojeda 2026-07-03 3:00 ` [RFC PATCH v2 0/3] " Mike Lothian 3 siblings, 1 reply; 11+ messages in thread From: Mike Lothian @ 2026-06-17 15:01 UTC (permalink / raw) To: rust-for-linux Cc: Mike Lothian, linux-crypto, Eric Biggers, Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross, Danilo Krummrich, Daniel Almeida, Greg Kroah-Hartman, Yury Norov (NVIDIA), Asahi Lina, Lorenzo Stoakes, Joel Fernandes, Alexandre Courbot, FUJITA Tomonori, Krishna Ketan Rai, linux-kernel Add a Rust binding for the asynchronous public-key cipher API (crypto_akcipher), driven synchronously, and a crypto::rsa_pubkey_encrypt() convenience built on it. crypto::Akcipher wraps a tfm allocated with crypto_alloc_akcipher(): new() selects the algorithm by name, set_pub_key() installs the key in the algorithm's wire format, and encrypt() runs one public-key operation and blocks until it completes. crypto::rsa_pubkey_encrypt() DER-encodes the RSAPublicKey { modulus, publicExponent } that the "rsa" transform's set_pub_key expects and computes out = (input ^ e) mod n; the caller applies any padding (PKCS#1 v1.5, EME-OAEP, ...) to the input first, and out is zeroed on any error so it never retains data from a partial computation. The request object, scatterlists and the completion wait are static-inline or on-stack state that cannot be expressed from Rust, and the akcipher data path needs DMA-capable (kmalloc, not vmap-stack) buffers, so the canonical synchronous encrypt sequence and a kmalloc bounce live in a single rust_helper_ shim; crypto_free_akcipher() and crypto_akcipher_set_pub_key() are exposed through 1:1 shims as they too are static inlines. This goes through the crypto_akcipher subsystem rather than the raw MPI math library, so it composes with any registered akcipher implementation (including hardware offload) and does not open-code modular exponentiation. Signed-off-by: Mike Lothian <mike@fireburn.co.uk> Assisted-by: Claude:claude-opus-4-8 [Claude-Code] --- rust/bindings/bindings_helper.h | 1 + rust/helpers/crypto.c | 70 +++++++++++++ rust/kernel/crypto.rs | 180 +++++++++++++++++++++++++++++++- 3 files changed, 250 insertions(+), 1 deletion(-) diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h index 14671e1825bb..e6add9754acd 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/sha2.h> #include <linux/acpi.h> diff --git a/rust/helpers/crypto.c b/rust/helpers/crypto.c index dc9614f6fc8e..6681e9155c84 100644 --- a/rust/helpers/crypto.c +++ b/rust/helpers/crypto.c @@ -1,6 +1,10 @@ // SPDX-License-Identifier: GPL-2.0 #include <crypto/aes.h> +#include <crypto/akcipher.h> +#include <linux/crypto.h> +#include <linux/scatterlist.h> +#include <linux/slab.h> #include <linux/string.h> /* @@ -23,3 +27,69 @@ rust_helper_aes128_encrypt_block(const u8 *key, const u8 *in, u8 *out) memzero_explicit(&enckey, sizeof(enckey)); return 0; } + +/* + * crypto_free_akcipher() and crypto_akcipher_set_pub_key() are static inlines, + * so they are exposed to Rust through these 1:1 shims (the same convention as + * the other rust_helper_ wrappers). + */ +__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 keylen) +{ + return crypto_akcipher_set_pub_key(tfm, key, keylen); +} + +/* + * One-shot synchronous public-key encrypt over the akcipher API: encrypts + * @src_len bytes at @src into @dst (capacity @dst_len) and returns the + * ciphertext length or -errno. + * + * The request object, the scatterlists and the completion wait are all + * static-inline or on-stack state that cannot be expressed from Rust, so the + * canonical synchronous akcipher sequence lives here. The akcipher data path + * also needs DMA-capable (kmalloc, not vmap-stack) buffers, so @src/@dst are + * bounced through kmalloc'd copies; @dst is overwritten only on success and + * the bounce buffers are wiped on free. + */ +__rust_helper int +rust_helper_akcipher_encrypt_oneshot(struct crypto_akcipher *tfm, + const u8 *src, unsigned int src_len, + u8 *dst, unsigned int dst_len) +{ + struct akcipher_request *req; + struct scatterlist src_sg, dst_sg; + DECLARE_CRYPTO_WAIT(wait); + u8 *in, *out; + int ret = -ENOMEM; + + req = akcipher_request_alloc(tfm, GFP_KERNEL); + in = kmemdup(src, src_len, GFP_KERNEL); + out = kzalloc(dst_len, GFP_KERNEL); + if (!req || !in || !out) + goto out; + + sg_init_one(&src_sg, in, src_len); + sg_init_one(&dst_sg, out, dst_len); + akcipher_request_set_crypt(req, &src_sg, &dst_sg, src_len, dst_len); + akcipher_request_set_callback(req, CRYPTO_TFM_REQ_MAY_BACKLOG, + crypto_req_done, &wait); + + ret = crypto_wait_req(crypto_akcipher_encrypt(req), &wait); + if (ret == 0) { + memcpy(dst, out, dst_len); + ret = req->dst_len; + } +out: + kfree_sensitive(in); + kfree_sensitive(out); + if (req) + akcipher_request_free(req); + return ret; +} diff --git a/rust/kernel/crypto.rs b/rust/kernel/crypto.rs index c8f2cb994cfd..0824f6ec81df 100644 --- a/rust/kernel/crypto.rs +++ b/rust/kernel/crypto.rs @@ -6,10 +6,21 @@ //! SHA-256 and HMAC-SHA256 — for use from Rust. They run synchronously in the //! calling context with no allocation; the hashes are infallible. //! +//! Also exposes the asynchronous public-key API ([`Akcipher`]) over +//! `crypto_akcipher`, driven synchronously, and a convenience RSA public-key +//! primitive built on it for callers that do their own padding (see +//! [`rsa_pubkey_encrypt`]). +//! //! C headers: [`include/crypto/aes.h`](srctree/include/crypto/aes.h), +//! [`include/crypto/akcipher.h`](srctree/include/crypto/akcipher.h), //! [`include/crypto/sha2.h`](srctree/include/crypto/sha2.h). -use crate::{bindings, error::to_result, prelude::*}; +use crate::{ + bindings, + error::{from_err_ptr, to_result}, + prelude::*, +}; +use core::ptr::NonNull; /// Size of a SHA-256 / HMAC-SHA256 digest, in bytes. pub const SHA256_DIGEST_SIZE: usize = 32; @@ -75,3 +86,170 @@ pub fn encrypt_block( Ok(out) } } + +/// An asynchronous public-key cipher transform (`struct crypto_akcipher`), +/// driven synchronously. +/// +/// Wraps a tfm allocated with `crypto_alloc_akcipher()`; the underlying +/// algorithm (e.g. `"rsa"`) is selected by name in [`Akcipher::new`]. After a +/// key is installed with [`set_pub_key`](Akcipher::set_pub_key), [`encrypt`] +/// performs one public-key operation, blocking until the request completes. +/// +/// [`encrypt`]: Akcipher::encrypt +/// +/// # Examples +/// +/// ``` +/// use kernel::crypto::Akcipher; +/// // `der` is a DER-encoded RSAPublicKey; `pt` is already padded to key size. +/// # fn f(der: &[u8], pt: &[u8]) -> Result { +/// let mut tfm = Akcipher::new(c"rsa")?; +/// tfm.set_pub_key(der)?; +/// let mut ct = [0u8; 256]; +/// let n = tfm.encrypt(pt, &mut ct)?; +/// # let _ = n; Ok(()) +/// # } +/// ``` +pub struct Akcipher(NonNull<bindings::crypto_akcipher>); + +// SAFETY: a tfm is a self-contained kernel object with no thread affinity; the +// synchronous request path takes its own per-call state, so the handle may be +// moved and used from any thread. +unsafe impl Send for Akcipher {} + +impl Akcipher { + /// Allocates a transform for the named akcipher algorithm (e.g. `c"rsa"`). + pub fn new(alg_name: &core::ffi::CStr) -> Result<Self> { + // SAFETY: `alg_name` is a valid NUL-terminated C string; the call + // returns a valid tfm pointer or an `ERR_PTR`. + let tfm = from_err_ptr(unsafe { + bindings::crypto_alloc_akcipher(alg_name.as_ptr().cast(), 0, 0) + })?; + Ok(Self(NonNull::new(tfm).ok_or(ENOMEM)?)) + } + + /// Installs the public key, encoded in the algorithm's expected wire format + /// (for `"rsa"`, a DER-encoded `RSAPublicKey`). + pub fn set_pub_key(&mut self, key: &[u8]) -> Result { + // SAFETY: `self.0` is a live tfm; `key` is valid for `key.len()` reads. + to_result(unsafe { + bindings::crypto_akcipher_set_pub_key( + self.0.as_ptr(), + key.as_ptr().cast(), + key.len() as u32, + ) + }) + } + + /// Encrypts `src` into `dst`, returning the ciphertext length. Blocks until + /// the operation completes. `dst` must be at least the algorithm's maximum + /// output size (the key/modulus size for RSA). + pub fn encrypt(&self, src: &[u8], dst: &mut [u8]) -> Result<usize> { + // SAFETY: `self.0` is a live tfm; `src`/`dst` are valid for their + // lengths. The helper bounces them through kmalloc'd buffers, runs one + // synchronous akcipher encrypt, and returns the length or a negative + // errno. + let ret = unsafe { + bindings::akcipher_encrypt_oneshot( + self.0.as_ptr(), + src.as_ptr(), + src.len() as u32, + dst.as_mut_ptr(), + dst.len() as u32, + ) + }; + to_result(ret)?; + Ok(ret as usize) + } +} + +impl Drop for Akcipher { + fn drop(&mut self) { + // SAFETY: `self.0` was allocated by `crypto_alloc_akcipher()` and is + // freed exactly once here. + unsafe { bindings::crypto_free_akcipher(self.0.as_ptr()) }; + } +} + +/// Appends a DER definite length to `out`. +fn der_len(out: &mut KVec<u8>, len: usize) -> Result { + if len < 0x80 { + out.push(len as u8, GFP_KERNEL)?; + return Ok(()); + } + let mut bytes = [0u8; core::mem::size_of::<usize>()]; + let mut n = 0; + let mut rest = len; + while rest > 0 { + bytes[n] = rest as u8; + rest >>= 8; + n += 1; + } + out.push(0x80 | n as u8, GFP_KERNEL)?; + for i in (0..n).rev() { + out.push(bytes[i], GFP_KERNEL)?; + } + Ok(()) +} + +/// Appends a DER `INTEGER` carrying the unsigned big-endian magnitude `bytes`. +fn der_integer(out: &mut KVec<u8>, bytes: &[u8]) -> Result { + // Canonicalise: drop leading zero octets, keeping at least one. + let mut mag = bytes; + while mag.len() > 1 && mag[0] == 0 { + mag = &mag[1..]; + } + // A leading zero is needed to keep the value positive if the top bit is set + // (or to represent zero when the magnitude is empty). + let pad = mag.is_empty() || mag[0] & 0x80 != 0; + out.push(0x02, GFP_KERNEL)?; + der_len(out, mag.len() + pad as usize)?; + if pad { + out.push(0x00, GFP_KERNEL)?; + } + out.extend_from_slice(mag, GFP_KERNEL)?; + Ok(()) +} + +/// DER-encodes `RSAPublicKey ::= SEQUENCE { modulus INTEGER, publicExponent +/// INTEGER }` from big-endian `modulus`/`exponent`, as `rsa`'s `set_pub_key` +/// expects. +fn der_rsa_pubkey(modulus: &[u8], exponent: &[u8]) -> Result<KVec<u8>> { + let mut body = KVec::new(); + der_integer(&mut body, modulus)?; + der_integer(&mut body, exponent)?; + let mut der = KVec::new(); + der.push(0x30, GFP_KERNEL)?; + der_len(&mut der, body.len())?; + der.extend_from_slice(&body, GFP_KERNEL)?; + Ok(der) +} + +/// Computes the RSA public-key operation `out = (input ^ exponent) mod modulus` +/// through the `crypto_akcipher` `"rsa"` transform. +/// +/// All buffers are unsigned big-endian. `out` is written fixed-width to exactly +/// `out.len()` bytes (left zero-padded by the cipher); pass `out.len()` equal to +/// the modulus size (e.g. 128 for RSA-1024). This is the bare primitive: the +/// caller applies any padding (PKCS#1 v1.5, EME-OAEP, …) to `input` first. +/// +/// `input` interpreted as an integer must be less than `modulus`, as RSA +/// requires; otherwise an error is returned. On any error `out` is zeroed, so +/// it never retains data from a partial computation. +pub fn rsa_pubkey_encrypt( + modulus: &[u8], + exponent: &[u8], + input: &[u8], + out: &mut [u8], +) -> Result { + let der = der_rsa_pubkey(modulus, exponent)?; + let mut tfm = Akcipher::new(c"rsa")?; + tfm.set_pub_key(&der)?; + match tfm.encrypt(input, out) { + Ok(_) => Ok(()), + Err(e) => { + out.fill(0); + Err(e) + } + } +} -- 2.54.0 ^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [RFC PATCH 2/2] rust: crypto: add RSA public-key encryption via crypto_akcipher 2026-06-17 15:01 ` [RFC PATCH 2/2] rust: crypto: add RSA public-key encryption via crypto_akcipher Mike Lothian @ 2026-06-17 17:52 ` Eric Biggers 0 siblings, 0 replies; 11+ messages in thread From: Eric Biggers @ 2026-06-17 17:52 UTC (permalink / raw) To: Mike Lothian Cc: rust-for-linux, linux-crypto, Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross, Danilo Krummrich, Daniel Almeida, Greg Kroah-Hartman, Yury Norov (NVIDIA), Asahi Lina, Lorenzo Stoakes, Joel Fernandes, Alexandre Courbot, FUJITA Tomonori, Krishna Ketan Rai, linux-kernel On Wed, Jun 17, 2026 at 04:01:33PM +0100, Mike Lothian wrote: > Add a Rust binding for the asynchronous public-key cipher API > (crypto_akcipher), driven synchronously, and a crypto::rsa_pubkey_encrypt() > convenience built on it. Could you keep the crypto_akcipher support private to the file and just expose rsa_pubkey_encrypt()? I don't want the use of crypto_akcipher to be growing significantly. It's a very bad API, as I think is clear by all the workarounds you had to implement to use it. I'd like to replace it with lib/crypto/ APIs for RSA eventually. - Eric ^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [RFC PATCH 0/2] rust: crypto: library AES-128 / SHA-256 / HMAC + RSA 2026-06-17 15:01 [RFC PATCH 0/2] rust: crypto: library AES-128 / SHA-256 / HMAC + RSA Mike Lothian 2026-06-17 15:01 ` [RFC PATCH 1/2] rust: crypto: add library AES-128 / SHA-256 / HMAC-SHA256 bindings Mike Lothian 2026-06-17 15:01 ` [RFC PATCH 2/2] rust: crypto: add RSA public-key encryption via crypto_akcipher Mike Lothian @ 2026-06-17 15:13 ` Miguel Ojeda 2026-06-17 15:19 ` Mike Lothian 2026-07-03 3:00 ` [RFC PATCH v2 0/3] " Mike Lothian 3 siblings, 1 reply; 11+ messages in thread From: Miguel Ojeda @ 2026-06-17 15:13 UTC (permalink / raw) To: Mike Lothian Cc: rust-for-linux, linux-crypto, Eric Biggers, Herbert Xu, David S. Miller, Ard Biesheuvel, Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross, Danilo Krummrich, linux-kernel On Wed, Jun 17, 2026 at 5:01 PM Mike Lothian <mike@fireburn.co.uk> wrote: > > Both were factored out of an out-of-tree in-kernel Rust DisplayLink DL3 > dock driver (which needs SHA/HMAC/AES for HDCP 2.2 and RSA for the AKE), > but the module is generic. Compile-tested in-tree against drm-next. Same question as in the other patch series you just sent: is this only expected for an out-of-tree driver? Maintainers generally cannot take dead code, unless there is a good justification behind it -- is the driver expected to land upstream? Do you have a link to the code? Thanks! Cheers, Miguel ^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [RFC PATCH 0/2] rust: crypto: library AES-128 / SHA-256 / HMAC + RSA 2026-06-17 15:13 ` [RFC PATCH 0/2] rust: crypto: library AES-128 / SHA-256 / HMAC + RSA Miguel Ojeda @ 2026-06-17 15:19 ` Mike Lothian 0 siblings, 0 replies; 11+ messages in thread From: Mike Lothian @ 2026-06-17 15:19 UTC (permalink / raw) To: Miguel Ojeda Cc: rust-for-linux, linux-crypto, Eric Biggers, Herbert Xu, David S. Miller, Ard Biesheuvel, Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross, Danilo Krummrich, linux-kernel On Wed, 17 Jun 2026 at 16:13, Miguel Ojeda <miguel.ojeda.sandonis@gmail.com> wrote: > > On Wed, Jun 17, 2026 at 5:01 PM Mike Lothian <mike@fireburn.co.uk> wrote: > > > > Both were factored out of an out-of-tree in-kernel Rust DisplayLink DL3 > > dock driver (which needs SHA/HMAC/AES for HDCP 2.2 and RSA for the AKE), > > but the module is generic. Compile-tested in-tree against drm-next. > > Same question as in the other patch series you just sent: is this only > expected for an out-of-tree driver? Maintainers generally cannot take > dead code, unless there is a good justification behind it -- is the > driver expected to land upstream? Do you have a link to the code? > > Thanks! > > Cheers, > Miguel Hi I've just posted it https://lore.kernel.org/r/20260617151249.2937-1-mike@fireburn.co.uk I'd like it upstream if I get it working Chers Mike ^ permalink raw reply [flat|nested] 11+ messages in thread
* [RFC PATCH v2 0/3] rust: crypto: library AES-128 / SHA-256 / HMAC + RSA 2026-06-17 15:01 [RFC PATCH 0/2] rust: crypto: library AES-128 / SHA-256 / HMAC + RSA Mike Lothian ` (2 preceding siblings ...) 2026-06-17 15:13 ` [RFC PATCH 0/2] rust: crypto: library AES-128 / SHA-256 / HMAC + RSA Miguel Ojeda @ 2026-07-03 3:00 ` Mike Lothian 2026-07-03 3:00 ` [RFC PATCH v2 1/3] rust: crypto: add library AES-128 / SHA-256 / HMAC-SHA256 bindings Mike Lothian ` (2 more replies) 3 siblings, 3 replies; 11+ messages in thread From: Mike Lothian @ 2026-07-03 3:00 UTC (permalink / raw) To: rust-for-linux Cc: linux-crypto, Eric Biggers, Herbert Xu, David S. Miller, Ard Biesheuvel, Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross, Danilo Krummrich, linux-kernel, Mike Lothian This is v2 of the crypto bindings, rebased onto current drm-next -- no functional change from the version prepared but never sent after v1's review (see "Changes since v1" below for what that review fixed). It adds a small, reusable kernel::crypto module so in-kernel Rust code can hash, MAC, encrypt a single AES block, and do RSA public-key encryption: 1/3 sha256(), hmac_sha256(), Aes128 (key-prepared-once block cipher) 2/3 aes_cmac() over the in-tree AES-CMAC library 3/3 rsa_pubkey_encrypt() over a new lib/crypto RSA primitive Patch 1 binds the library crypto (lib/crypto) functions directly (SHA-256 / HMAC-SHA256) and uses one rust_helper_ shim for aes_encrypt() (its transparent union is unbindable). It runs synchronously in the calling context with no allocation and is the independently-mergeable, self-contained contribution. Patch 2 adds crypto::aes_cmac() over the in-tree AES-CMAC library (<crypto/aes-cbc-macs.h>) -- the one mode of operation the consumer needs that lib/crypto already ships -- rather than building CMAC out of bare AES. The 128-bit key is prepared once and wiped after use. Patch 3 adds an RSA public-key primitive. Rather than bind crypto_akcipher (which Eric flagged as a very bad API not to grow), it adds a small lib/crypto entry point -- lib/crypto/rsa.c, rsa_pubkey_encrypt(), the bare RSAEP primitive c = m^e mod n [RFC8017 sec 5.1.1] over the MPI library -- and binds that directly: no akcipher tfm, DER key encoding, scatterlists or async completion. The caller applies its own padding (RSAES-OAEP, ...). It is gated by a new bool CONFIG_CRYPTO_LIB_RSA (selects MPILIB); because the Rust wrapper is exported from the built-in kernel crate the symbol must be in vmlinux, so the option is a bool and the wrapper is #[cfg(CONFIG_CRYPTO_LIB_RSA)]-gated -- a kernel that does not configure it gains no dependency. A from-scratch constant-time/allocation-free lib/crypto RSA (Eric's longer-term direction) is left as future work. All three were factored out of an in-kernel Rust DisplayLink DL3 dock driver (which needs SHA/HMAC/AES-CMAC for HDCP 2.2 and RSA for the AKE), posted alongside as drm/vino ("[RFC PATCH v2 00/6] drm/vino: DisplayLink DL3 dock driver"); the intent is to land both upstream. The module is generic. Compile-tested in-tree against current drm-next. Source: https://github.com/FireBurn/vino-scripts https://github.com/FireBurn/linux/tree/vino https://gitlab.freedesktop.org/FireBurn/linux/-/tree/vino Changes since v1 (Eric Biggers): - Drop the bare single-block ECB helper that re-expanded the key per block; Aes128 now prepares the key schedule once and reuses it for a keystream. - Add aes_cmac() over the in-tree AES-CMAC library (<crypto/aes-cbc-macs.h>) instead of building CMAC out of bare AES (now patch 2/3); the vino driver drops its hand-rolled CMAC for it. - RSA no longer binds crypto_akcipher. v1 exposed an Akcipher transform; following Eric's guidance to drop that API, v2 adds a small lib/crypto RSA primitive instead (lib/crypto/rsa.c, now patch 3/3) and binds it directly. A full constant-time lib/crypto RSA is left as future work. - Rebased onto current drm-next; no other functional change. Mike Lothian (3): rust: crypto: add library AES-128 / SHA-256 / HMAC-SHA256 bindings rust: crypto: use the in-tree AES-CMAC library rust: crypto: add an RSA public-key primitive in lib/crypto include/crypto/rsa.h | 15 +++ lib/crypto/Kconfig | 9 ++ lib/crypto/Makefile | 3 + lib/crypto/rsa.c | 102 +++++++++++++++++++ rust/bindings/bindings_helper.h | 4 + rust/helpers/crypto.c | 37 +++++++ rust/helpers/helpers.c | 1 + rust/kernel/crypto.rs | 169 ++++++++++++++++++++++++++++++++ rust/kernel/lib.rs | 1 + 9 files changed, 341 insertions(+) create mode 100644 include/crypto/rsa.h create mode 100644 lib/crypto/rsa.c create mode 100644 rust/helpers/crypto.c create mode 100644 rust/kernel/crypto.rs -- 2.55.0 ^ permalink raw reply [flat|nested] 11+ messages in thread
* [RFC PATCH v2 1/3] rust: crypto: add library AES-128 / SHA-256 / HMAC-SHA256 bindings 2026-07-03 3:00 ` [RFC PATCH v2 0/3] " Mike Lothian @ 2026-07-03 3:00 ` Mike Lothian 2026-07-03 3:00 ` [RFC PATCH v2 2/3] rust: crypto: use the in-tree AES-CMAC library Mike Lothian 2026-07-03 3:00 ` [RFC PATCH v2 3/3] rust: crypto: add an RSA public-key primitive in lib/crypto Mike Lothian 2 siblings, 0 replies; 11+ messages in thread From: Mike Lothian @ 2026-07-03 3:00 UTC (permalink / raw) To: rust-for-linux Cc: linux-crypto, Eric Biggers, Herbert Xu, David S. Miller, Ard Biesheuvel, Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross, Danilo Krummrich, linux-kernel, Mike Lothian Add a small `kernel::crypto` module exposing the kernel's synchronous library crypto (lib/crypto) to Rust: SHA-256, HMAC-SHA256 and single-block AES-128 ECB. These are one-shot, allocation-free and run in the calling context, suitable for in-kernel Rust users that need a hash or a block cipher without the full asynchronous crypto API. SHA-256 (`sha256()`) and HMAC-SHA256 (`hmac_sha256_usingrawkey()`) are plain exported functions and are bound directly via bindgen, so their header `<crypto/sha2.h>` is added to bindings_helper.h. AES single-block encryption goes through a `rust_helper_` shim because `aes_encrypt()` takes a transparent union (`aes_encrypt_arg`) that bindgen cannot express; the shim also zeroes the expanded key schedule before returning. The Rust API: `crypto::sha256(&[u8]) -> [u8; 32]`, `crypto::hmac_sha256(key, data) -> [u8; 32]`, and the `crypto::Aes128` type, created with `Aes128::new(key)` and used via `encrypt_block(&[u8; 16]) -> Result<[u8; 16]>`. Signed-off-by: Mike Lothian <mike@fireburn.co.uk> Assisted-by: Claude:claude-opus-4-8 [Claude-Code] --- rust/bindings/bindings_helper.h | 2 + rust/helpers/crypto.c | 25 +++++++++++ rust/helpers/helpers.c | 1 + rust/kernel/crypto.rs | 77 +++++++++++++++++++++++++++++++++ rust/kernel/lib.rs | 1 + 5 files changed, 106 insertions(+) create mode 100644 rust/helpers/crypto.c create mode 100644 rust/kernel/crypto.rs diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h index 1124785e210b..14671e1825bb 100644 --- a/rust/bindings/bindings_helper.h +++ b/rust/bindings/bindings_helper.h @@ -28,6 +28,8 @@ */ #include <linux/hrtimer_types.h> +#include <crypto/sha2.h> + #include <linux/acpi.h> #include <linux/gpu_buddy.h> #include <drm/drm_device.h> diff --git a/rust/helpers/crypto.c b/rust/helpers/crypto.c new file mode 100644 index 000000000000..dc9614f6fc8e --- /dev/null +++ b/rust/helpers/crypto.c @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include <crypto/aes.h> +#include <linux/string.h> + +/* + * AES-128 single-block ECB encryption: out = AES(key, in). + * + * A helper because aes_encrypt() takes a transparent union (aes_encrypt_arg) + * that bindgen cannot express. SHA-256 and HMAC-SHA256 are plain extern + * functions and are bound directly. + */ +__rust_helper int +rust_helper_aes128_encrypt_block(const u8 *key, const u8 *in, u8 *out) +{ + struct aes_enckey enckey; + int ret; + + ret = aes_prepareenckey(&enckey, key, AES_KEYSIZE_128); + if (ret) + return ret; + aes_encrypt(&enckey, out, in); + memzero_explicit(&enckey, sizeof(enckey)); + return 0; +} diff --git a/rust/helpers/helpers.c b/rust/helpers/helpers.c index 998e31052e66..4f8a1d6d129c 100644 --- a/rust/helpers/helpers.c +++ b/rust/helpers/helpers.c @@ -56,6 +56,7 @@ #include "cpufreq.c" #include "cpumask.c" #include "cred.c" +#include "crypto.c" #include "device.c" #include "dma.c" #include "dma-resv.c" diff --git a/rust/kernel/crypto.rs b/rust/kernel/crypto.rs new file mode 100644 index 000000000000..c8f2cb994cfd --- /dev/null +++ b/rust/kernel/crypto.rs @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Safe wrappers over the kernel's synchronous library crypto. +//! +//! Exposes the one-shot `lib/crypto` primitives — AES-128 single-block ECB, +//! SHA-256 and HMAC-SHA256 — for use from Rust. They run synchronously in the +//! calling context with no allocation; the hashes are infallible. +//! +//! C headers: [`include/crypto/aes.h`](srctree/include/crypto/aes.h), +//! [`include/crypto/sha2.h`](srctree/include/crypto/sha2.h). + +use crate::{bindings, error::to_result, prelude::*}; + +/// 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; + +/// Returns the SHA-256 digest of `data`. +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 + // `SHA256_DIGEST_SIZE`-byte output buffer, as `sha256()` requires. + unsafe { bindings::sha256(data.as_ptr(), data.len(), out.as_mut_ptr()) }; + out +} + +/// Returns `HMAC-SHA256(key, data)`. +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` + // is a valid `SHA256_DIGEST_SIZE`-byte output buffer, as required. + unsafe { + bindings::hmac_sha256_usingrawkey( + key.as_ptr(), + key.len(), + data.as_ptr(), + data.len(), + out.as_mut_ptr(), + ) + }; + out +} + +/// An AES-128 key usable for single-block ECB encryption. +/// +/// # Examples +/// +/// ``` +/// use kernel::crypto::Aes128; +/// let cipher = Aes128::new([0u8; 16]); +/// let _ct = cipher.encrypt_block(&[0u8; 16])?; +/// # Ok::<(), Error>(()) +/// ``` +pub struct Aes128([u8; AES128_BLOCK_SIZE]); + +impl Aes128 { + /// Creates an AES-128 key from 16 raw key bytes. + pub fn new(key: [u8; AES128_BLOCK_SIZE]) -> Self { + Self(key) + } + + /// Encrypts one 16-byte block: returns `AES-128-ECB(key, block)`. + pub fn encrypt_block( + &self, + block: &[u8; AES128_BLOCK_SIZE], + ) -> Result<[u8; AES128_BLOCK_SIZE]> { + let mut out = [0u8; AES128_BLOCK_SIZE]; + // SAFETY: `self.0`, `block` and `out` are all valid 16-byte buffers, as + // the helper requires. + let ret = unsafe { + bindings::aes128_encrypt_block(self.0.as_ptr(), block.as_ptr(), out.as_mut_ptr()) + }; + to_result(ret)?; + Ok(out) + } +} diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs index 9512af7156df..7fcbf3e7d7af 100644 --- a/rust/kernel/lib.rs +++ b/rust/kernel/lib.rs @@ -59,6 +59,7 @@ pub mod cpufreq; pub mod cpumask; pub mod cred; +pub mod crypto; pub mod debugfs; pub mod device; pub mod device_id; -- 2.55.0 ^ permalink raw reply [flat|nested] 11+ messages in thread
* [RFC PATCH v2 2/3] rust: crypto: use the in-tree AES-CMAC library 2026-07-03 3:00 ` [RFC PATCH v2 0/3] " Mike Lothian 2026-07-03 3:00 ` [RFC PATCH v2 1/3] rust: crypto: add library AES-128 / SHA-256 / HMAC-SHA256 bindings Mike Lothian @ 2026-07-03 3:00 ` Mike Lothian 2026-07-03 3:00 ` [RFC PATCH v2 3/3] rust: crypto: add an RSA public-key primitive in lib/crypto Mike Lothian 2 siblings, 0 replies; 11+ messages in thread From: Mike Lothian @ 2026-07-03 3:00 UTC (permalink / raw) To: rust-for-linux Cc: linux-crypto, Eric Biggers, Herbert Xu, David S. Miller, Ard Biesheuvel, Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross, Danilo Krummrich, linux-kernel, Mike Lothian Address the v1 RFC review (Eric Biggers): - Drop the bare single-block ECB helper (`aes128_encrypt_block`), which re-expanded the AES key on every block and exposed bare ECB instead of a mode of operation. `Aes128::new()` now prepares the key schedule once (via `aes_prepareenckey()`) and `encrypt_block()` reuses it, so a keystream loop (e.g. AES-CTR, which `lib/crypto` does not yet provide) no longer re-expands the key per block. This stays a low-level building block for the modes the library is missing. - Add `crypto::aes_cmac()` over the in-tree AES-CMAC library (<crypto/aes-cbc-macs.h>) instead of building CMAC out of bare AES, so the one mode of operation vino needs that the library already ships comes from the library. Signed-off-by: Mike Lothian <mike@fireburn.co.uk> Assisted-by: Claude:claude-opus-4-8 [Claude-Code] --- rust/bindings/bindings_helper.h | 1 + rust/helpers/crypto.c | 40 ++++++++++------ rust/kernel/crypto.rs | 85 ++++++++++++++++++++++++++------- 3 files changed, 94 insertions(+), 32 deletions(-) diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h index 14671e1825bb..60effaf3af16 100644 --- a/rust/bindings/bindings_helper.h +++ b/rust/bindings/bindings_helper.h @@ -28,6 +28,7 @@ */ #include <linux/hrtimer_types.h> +#include <crypto/aes.h> #include <crypto/sha2.h> #include <linux/acpi.h> diff --git a/rust/helpers/crypto.c b/rust/helpers/crypto.c index dc9614f6fc8e..a18780231ce0 100644 --- a/rust/helpers/crypto.c +++ b/rust/helpers/crypto.c @@ -1,25 +1,37 @@ // SPDX-License-Identifier: GPL-2.0 #include <crypto/aes.h> +#include <crypto/aes-cbc-macs.h> #include <linux/string.h> /* - * AES-128 single-block ECB encryption: out = AES(key, in). - * - * A helper because aes_encrypt() takes a transparent union (aes_encrypt_arg) - * that bindgen cannot express. SHA-256 and HMAC-SHA256 are plain extern + * 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 + * is prepared once (aes_prepareenckey() is a plain extern bound directly) and + * the resulting struct aes_enckey is reused across blocks by the caller, so the + * key is not re-expanded per block. SHA-256 and HMAC-SHA256 are plain extern * functions and are bound directly. */ -__rust_helper int -rust_helper_aes128_encrypt_block(const u8 *key, const u8 *in, u8 *out) +__rust_helper void +rust_helper_aes_enckey_encrypt_block(const struct aes_enckey *key, u8 *out, + const u8 *in) { - struct aes_enckey enckey; - int ret; + aes_encrypt(key, out, in); +} + +/* + * 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 + * because both aes_cmac_preparekey()'s struct and the aes_cmac() one-shot are + * not expressible from Rust directly. The key length is fixed at 128 bits, so + * aes_cmac_preparekey() cannot fail; the prepared key is wiped before return. + */ +__rust_helper void +rust_helper_aes_cmac(const u8 *key, const u8 *data, size_t data_len, u8 *out) +{ + struct aes_cmac_key cmac_key; - ret = aes_prepareenckey(&enckey, key, AES_KEYSIZE_128); - if (ret) - return ret; - aes_encrypt(&enckey, out, in); - memzero_explicit(&enckey, sizeof(enckey)); - return 0; + aes_cmac_preparekey(&cmac_key, key, AES_KEYSIZE_128); + aes_cmac(&cmac_key, data, data_len, out); + memzero_explicit(&cmac_key, sizeof(cmac_key)); } diff --git a/rust/kernel/crypto.rs b/rust/kernel/crypto.rs index c8f2cb994cfd..7d96c1c710a4 100644 --- a/rust/kernel/crypto.rs +++ b/rust/kernel/crypto.rs @@ -2,11 +2,15 @@ //! Safe wrappers over the kernel's synchronous library crypto. //! -//! Exposes the one-shot `lib/crypto` primitives — AES-128 single-block ECB, -//! SHA-256 and HMAC-SHA256 — for use from Rust. They run synchronously in the -//! calling context with no allocation; the hashes are infallible. +//! Exposes the one-shot `lib/crypto` primitives — AES-128 (an [`Aes128`] key +//! prepared once for single-block encryption, the building block for modes the +//! library does not yet provide such as AES-CTR), the in-tree AES-CMAC +//! ([`aes_cmac`]), SHA-256 and HMAC-SHA256 — for use from Rust. They run +//! 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), +//! [`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::*}; @@ -42,36 +46,81 @@ out } -/// An AES-128 key usable for single-block ECB encryption. +/// Returns `AES-CMAC-128(key, data)` (RFC 4493), computed by the in-tree +/// AES-CMAC library ([`include/crypto/aes-cbc-macs.h`]). The 128-bit key is +/// prepared and wiped internally; the call is infallible. +/// +/// [`include/crypto/aes-cbc-macs.h`]: srctree/include/crypto/aes-cbc-macs.h +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()` + // reads, and `out` is a valid `AES128_BLOCK_SIZE`-byte output buffer, as the + // helper requires. + unsafe { + bindings::aes_cmac(key.as_ptr(), data.as_ptr(), data.len(), out.as_mut_ptr()) + }; + out +} + +/// An AES-128 key, expanded once for single-block encryption. +/// +/// The key schedule is computed in [`Aes128::new`] and reused across every +/// [`encrypt_block`](Aes128::encrypt_block) call, so encrypting a stream of +/// blocks (e.g. an AES-CTR keystream) does not re-expand the key per block. This +/// is a low-level building block: prefer a full mode of operation where the +/// library provides one (see [`aes_cmac`]); the bare block cipher is here only +/// for modes `lib/crypto` does not yet expose, such as AES-CTR. /// /// # Examples /// /// ``` /// use kernel::crypto::Aes128; -/// let cipher = Aes128::new([0u8; 16]); -/// let _ct = cipher.encrypt_block(&[0u8; 16])?; +/// let cipher = Aes128::new(&[0u8; 16])?; +/// let _ct = cipher.encrypt_block(&[0u8; 16]); /// # Ok::<(), Error>(()) /// ``` -pub struct Aes128([u8; AES128_BLOCK_SIZE]); +pub struct Aes128(bindings::aes_enckey); impl Aes128 { - /// Creates an AES-128 key from 16 raw key bytes. - pub fn new(key: [u8; AES128_BLOCK_SIZE]) -> Self { - Self(key) + /// Expands an AES-128 key from 16 raw key bytes. + pub fn new(key: &[u8; AES128_BLOCK_SIZE]) -> Result<Self> { + // SAFETY: `aes_enckey` is a plain-old-data key schedule (integer arrays + // in a union of integer arrays); an all-zero bit pattern is a valid, + // inert initial value, fully overwritten by `aes_prepareenckey()` below. + let mut enckey: bindings::aes_enckey = unsafe { core::mem::zeroed() }; + // SAFETY: `enckey` is a valid, owned `aes_enckey`; `key` is a valid + // 16-byte buffer; `AES128_BLOCK_SIZE` (16) is a supported key length. + let ret = unsafe { + bindings::aes_prepareenckey(&mut enckey, key.as_ptr(), AES128_BLOCK_SIZE) + }; + to_result(ret)?; + Ok(Self(enckey)) } - /// Encrypts one 16-byte block: returns `AES-128-ECB(key, block)`. + /// Encrypts one 16-byte block with the prepared key: returns + /// `AES-128-ECB(key, block)`. pub fn encrypt_block( &self, block: &[u8; AES128_BLOCK_SIZE], - ) -> Result<[u8; AES128_BLOCK_SIZE]> { + ) -> [u8; AES128_BLOCK_SIZE] { let mut out = [0u8; AES128_BLOCK_SIZE]; - // SAFETY: `self.0`, `block` and `out` are all valid 16-byte buffers, as - // the helper requires. - let ret = unsafe { - bindings::aes128_encrypt_block(self.0.as_ptr(), block.as_ptr(), out.as_mut_ptr()) + // SAFETY: `self.0` is a prepared encryption key; `block` and `out` are + // valid 16-byte buffers, as the helper requires. + unsafe { + bindings::aes_enckey_encrypt_block(&self.0, out.as_mut_ptr(), block.as_ptr()) }; - to_result(ret)?; - Ok(out) + out + } +} + +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()) }; } } -- 2.55.0 ^ permalink raw reply [flat|nested] 11+ messages in thread
* [RFC PATCH v2 3/3] rust: crypto: add an RSA public-key primitive in lib/crypto 2026-07-03 3:00 ` [RFC PATCH v2 0/3] " Mike Lothian 2026-07-03 3:00 ` [RFC PATCH v2 1/3] rust: crypto: add library AES-128 / SHA-256 / HMAC-SHA256 bindings Mike Lothian 2026-07-03 3:00 ` [RFC PATCH v2 2/3] rust: crypto: use the in-tree AES-CMAC library Mike Lothian @ 2026-07-03 3:00 ` Mike Lothian 2 siblings, 0 replies; 11+ messages in thread From: Mike Lothian @ 2026-07-03 3:00 UTC (permalink / raw) To: rust-for-linux Cc: linux-crypto, Eric Biggers, Herbert Xu, David S. Miller, Ard Biesheuvel, Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross, Danilo Krummrich, linux-kernel, Mike Lothian Address the v1 RFC review (Eric Biggers): rather than binding crypto_akcipher ("a very bad API"), add the RSA public-key operation as a small lib/crypto primitive and bind that. New lib/crypto/rsa.c exports int rsa_pubkey_encrypt(const u8 *n, size_t n_len, const u8 *e, size_t e_len, const u8 *in, size_t in_len, u8 *out, size_t out_len); the bare RSAEP primitive c = m^e mod n [RFC8017 sec 5.1.1], computed with the MPI big-integer library (the same arithmetic crypto/rsa.c uses) but without the akcipher tfm, DER key encoding, scatterlists or async completion. It rejects m >= n (which mpi_powm() would otherwise silently reduce, yielding an undecryptable ciphertext), writes the result fixed-width big-endian left zero-padded to out_len, and zeroes out on every error path. Callers apply their own padding (RSAES-OAEP, ...). Declared in include/crypto/rsa.h, gated by a new CONFIG_CRYPTO_LIB_RSA (selects MPILIB). crypto::rsa_pubkey_encrypt() wraps it directly. Because the wrapper is compiled into and exported from the built-in kernel crate, the C symbol must live in vmlinux: CONFIG_CRYPTO_LIB_RSA is therefore a bool (a select forces it built-in) and the wrapper is #[cfg(CONFIG_CRYPTO_LIB_RSA)]-gated so no vmlinux dependency is introduced for kernels that do not configure it. A consumer (e.g. the vino driver) selects it. A from-scratch constant-time/allocation-free RSA is out of scope; this is the lib/crypto entry point that lets a Rust caller avoid crypto_akcipher. Signed-off-by: Mike Lothian <mike@fireburn.co.uk> Assisted-by: Claude:claude-opus-4-8 [Claude-Code] --- include/crypto/rsa.h | 15 +++++ lib/crypto/Kconfig | 9 +++ lib/crypto/Makefile | 3 + lib/crypto/rsa.c | 102 ++++++++++++++++++++++++++++++++ rust/bindings/bindings_helper.h | 1 + rust/kernel/crypto.rs | 43 ++++++++++++++ 6 files changed, 173 insertions(+) create mode 100644 include/crypto/rsa.h create mode 100644 lib/crypto/rsa.c diff --git a/include/crypto/rsa.h b/include/crypto/rsa.h new file mode 100644 index 000000000000..c11d7b5cae9a --- /dev/null +++ b/include/crypto/rsa.h @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * RSA public-key primitive (RSAEP), library interface. + * + * Copyright (c) 2026 Mike Lothian <mike@fireburn.co.uk> + */ +#ifndef _CRYPTO_RSA_H +#define _CRYPTO_RSA_H + +#include <linux/types.h> + +int rsa_pubkey_encrypt(const u8 *n, size_t n_len, const u8 *e, size_t e_len, + const u8 *in, size_t in_len, u8 *out, size_t out_len); + +#endif /* _CRYPTO_RSA_H */ diff --git a/lib/crypto/Kconfig b/lib/crypto/Kconfig index 591c1c2a7fb3..04655c104e65 100644 --- a/lib/crypto/Kconfig +++ b/lib/crypto/Kconfig @@ -45,6 +45,15 @@ config CRYPTO_LIB_AESGCM config CRYPTO_LIB_ARC4 tristate +config CRYPTO_LIB_RSA + bool + select MPILIB + help + The RSA public-key primitive (RSAEP, out = in^e mod n), built on the + MPI big-integer library. The bare primitive only; callers apply their + own padding. Bool rather than tristate: the in-kernel consumer is the + built-in Rust crypto bindings, so it must live in vmlinux. + config CRYPTO_LIB_GF128MUL tristate diff --git a/lib/crypto/Makefile b/lib/crypto/Makefile index f1e9bf89785f..486557df59e3 100644 --- a/lib/crypto/Makefile +++ b/lib/crypto/Makefile @@ -69,6 +69,9 @@ libaesgcm-y := aesgcm.o obj-$(CONFIG_CRYPTO_LIB_ARC4) += libarc4.o libarc4-y := arc4.o +obj-$(CONFIG_CRYPTO_LIB_RSA) += librsa.o +librsa-y := rsa.o + obj-$(CONFIG_CRYPTO_LIB_GF128MUL) += gf128mul.o ################################################################################ diff --git a/lib/crypto/rsa.c b/lib/crypto/rsa.c new file mode 100644 index 000000000000..21c1d6c9ea26 --- /dev/null +++ b/lib/crypto/rsa.c @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * RSA public-key primitive (RSAEP) [RFC8017 sec 5.1.1]. + * + * A minimal synchronous library entry point for the RSA public-key operation, + * built on the MPI big-integer library (the same arithmetic the crypto_akcipher + * "rsa" transform uses), but without the akcipher tfm allocation, DER key + * encoding, scatterlists or asynchronous completion machinery. It performs only + * the bare RSAEP primitive; callers apply any encoding/padding (RSAES-OAEP, + * RSAES-PKCS1-v1_5, ...) themselves. + * + * Copyright (c) 2026 Mike Lothian <mike@fireburn.co.uk> + */ + +#include <crypto/rsa.h> +#include <linux/errno.h> +#include <linux/export.h> +#include <linux/mpi.h> +#include <linux/module.h> +#include <linux/string.h> + +/** + * rsa_pubkey_encrypt() - RSA public-key operation, out = (in ^ e) mod n + * @n: modulus, unsigned big-endian + * @n_len: length of @n, in bytes + * @e: public exponent, unsigned big-endian + * @e_len: length of @e, in bytes + * @in: input, unsigned big-endian; already padded by the caller + * @in_len: length of @in, in bytes + * @out: output buffer; receives the result big-endian, left zero-padded to + * exactly @out_len bytes + * @out_len: size of @out; pass the modulus length (e.g. 128 for RSA-1024) + * + * Computes the bare RSAEP primitive c = m^e mod n, where m is @in interpreted + * as a big-endian integer. This is the raw public-key operation [RFC8017 sec + * 5.1.1]; the caller is responsible for any message encoding/padding. + * + * @in interpreted as an integer must be numerically less than @n, as RSA + * requires: a larger value would be silently reduced mod n by the modular + * exponentiation and yield a ciphertext the peer cannot decrypt, so it is + * rejected. On any error path @out is zeroed, so it never retains data from a + * partial computation. + * + * Return: 0 on success; -EINVAL on a malformed key or out-of-range input; + * -EOVERFLOW if the result does not fit in @out_len; -ENOMEM on allocation + * failure. + */ +int rsa_pubkey_encrypt(const u8 *n, size_t n_len, const u8 *e, size_t e_len, + const u8 *in, size_t in_len, u8 *out, size_t out_len) +{ + MPI mn, me, mbase, mres; + unsigned int nbytes; + int ret = -ENOMEM; + + if (!n_len || !e_len || !in_len || !out_len) + return -EINVAL; + + mn = mpi_read_raw_data(n, n_len); + me = mpi_read_raw_data(e, e_len); + mbase = mpi_read_raw_data(in, in_len); + mres = mpi_alloc(0); + if (!mn || !me || !mbase || !mres) + goto out; + + /* + * RSA requires 0 <= m < n; reject m >= n rather than let mpi_powm() + * silently reduce it and produce an undecryptable ciphertext. + */ + if (mpi_cmp(mbase, mn) >= 0) { + ret = -EINVAL; + goto out; + } + + ret = mpi_powm(mres, mbase, me, mn); + if (ret) + goto out; + + /* + * mpi_read_buffer() writes the minimal big-endian form left-aligned + * (and returns -EOVERFLOW without writing if @out_len is too small); + * right-align it into the fixed-width output and zero-pad the front. + */ + ret = mpi_read_buffer(mres, out, out_len, &nbytes, NULL); + if (ret) + goto out; + if (nbytes < out_len) { + memmove(out + (out_len - nbytes), out, nbytes); + memset(out, 0, out_len - nbytes); + } +out: + if (ret) + memzero_explicit(out, out_len); + mpi_free(mn); + mpi_free(me); + mpi_free(mbase); + mpi_free(mres); + return ret; +} +EXPORT_SYMBOL_GPL(rsa_pubkey_encrypt); + +MODULE_DESCRIPTION("RSA public-key primitive (RSAEP)"); +MODULE_LICENSE("GPL"); diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h index 60effaf3af16..7bb04d68f9d2 100644 --- a/rust/bindings/bindings_helper.h +++ b/rust/bindings/bindings_helper.h @@ -29,6 +29,7 @@ #include <linux/hrtimer_types.h> #include <crypto/aes.h> +#include <crypto/rsa.h> #include <crypto/sha2.h> #include <linux/acpi.h> diff --git a/rust/kernel/crypto.rs b/rust/kernel/crypto.rs index 7d96c1c710a4..b6836e771cfa 100644 --- a/rust/kernel/crypto.rs +++ b/rust/kernel/crypto.rs @@ -9,8 +9,12 @@ //! synchronously in the calling context with no allocation; the hashes and the //! MAC are infallible. //! +//! Also exposes the `lib/crypto` RSA public-key primitive +//! ([`rsa_pubkey_encrypt`]) for callers that do their own padding. +//! //! C headers: [`include/crypto/aes.h`](srctree/include/crypto/aes.h), //! [`include/crypto/aes-cbc-macs.h`](srctree/include/crypto/aes-cbc-macs.h), +//! [`include/crypto/rsa.h`](srctree/include/crypto/rsa.h), //! [`include/crypto/sha2.h`](srctree/include/crypto/sha2.h). use crate::{bindings, error::to_result, prelude::*}; @@ -124,3 +128,42 @@ fn drop(&mut self) { unsafe { core::ptr::write_volatile(&mut self.0, core::mem::zeroed()) }; } } + +/// Computes the RSA public-key primitive `out = (input ^ exponent) mod modulus` +/// using the in-tree `lib/crypto` RSA library (`rsa_pubkey_encrypt()` in +/// [`lib/crypto/rsa.c`](srctree/lib/crypto/rsa.c)). +/// +/// All buffers are unsigned big-endian. `out` is written fixed-width to exactly +/// `out.len()` bytes (left zero-padded); pass `out.len()` equal to the modulus +/// size (e.g. 128 for RSA-1024). This is the bare primitive: the caller applies +/// any padding (PKCS#1 v1.5, EME-OAEP, …) to `input` first. +/// +/// `input` interpreted as an integer must be less than `modulus`, as RSA +/// requires; otherwise an error is returned. On any error `out` is zeroed by the +/// library, so it never retains data from a partial computation. +/// +/// Only available when `CONFIG_CRYPTO_LIB_RSA` is enabled (a consumer selects +/// it); the backing library is then built into the kernel. +#[cfg(CONFIG_CRYPTO_LIB_RSA)] +pub fn rsa_pubkey_encrypt( + modulus: &[u8], + exponent: &[u8], + input: &[u8], + out: &mut [u8], +) -> Result { + // SAFETY: each slice is valid for its stated length; the library function + // reads `modulus`/`exponent`/`input` and writes exactly `out.len()` bytes + // into `out` (left zero-padded), zeroing it on any error. + to_result(unsafe { + bindings::rsa_pubkey_encrypt( + modulus.as_ptr(), + modulus.len(), + exponent.as_ptr(), + exponent.len(), + input.as_ptr(), + input.len(), + out.as_mut_ptr(), + out.len(), + ) + }) +} -- 2.55.0 ^ permalink raw reply [flat|nested] 11+ messages in thread
end of thread, other threads:[~2026-07-03 3:01 UTC | newest] Thread overview: 11+ messages (download: mbox.gz / follow: Atom feed) -- links below jump to the message on this page -- 2026-06-17 15:01 [RFC PATCH 0/2] rust: crypto: library AES-128 / SHA-256 / HMAC + RSA Mike Lothian 2026-06-17 15:01 ` [RFC PATCH 1/2] rust: crypto: add library AES-128 / SHA-256 / HMAC-SHA256 bindings Mike Lothian 2026-06-17 17:18 ` Eric Biggers 2026-06-17 15:01 ` [RFC PATCH 2/2] rust: crypto: add RSA public-key encryption via crypto_akcipher Mike Lothian 2026-06-17 17:52 ` Eric Biggers 2026-06-17 15:13 ` [RFC PATCH 0/2] rust: crypto: library AES-128 / SHA-256 / HMAC + RSA Miguel Ojeda 2026-06-17 15:19 ` Mike Lothian 2026-07-03 3:00 ` [RFC PATCH v2 0/3] " Mike Lothian 2026-07-03 3:00 ` [RFC PATCH v2 1/3] rust: crypto: add library AES-128 / SHA-256 / HMAC-SHA256 bindings Mike Lothian 2026-07-03 3:00 ` [RFC PATCH v2 2/3] rust: crypto: use the in-tree AES-CMAC library Mike Lothian 2026-07-03 3:00 ` [RFC PATCH v2 3/3] rust: crypto: add an RSA public-key primitive in lib/crypto Mike Lothian
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox
Powered by JetHome