[PATCH v3 1/2] rust: crypto: add AES-128, AES-CMAC, SHA-256, and HMAC bindings

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]>
Expose the synchronous lib/crypto AES-128, AES-CMAC, SHA-256, and
HMAC-SHA256 primitives through safe Rust APIs.

Aes128 prepares the key schedule once and reuses it for block encryption
and CMAC. The one-shot hash and HMAC helpers operate on slices, and
every API uses fixed-size outputs. C shims cover interfaces that bindgen
cannot represent and clear temporary key material before returning.

Assisted-by: Claude:claude-opus-5
Signed-off-by: Mike Lothian <[email protected]>
---
 rust/bindings/bindings_helper.h |   3 +
 rust/helpers/crypto.c           |  37 ++++++++++
 rust/helpers/helpers.c          |   1 +
 rust/kernel/crypto.rs           | 115 ++++++++++++++++++++++++++++++++
 rust/kernel/lib.rs              |   1 +
 5 files changed, 157 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 2d079f278a04..8d7489b8cce8 100644
--- a/rust/bindings/bindings_helper.h
+++ b/rust/bindings/bindings_helper.h
@@ -28,6 +28,9 @@
  */
 #include <linux/hrtimer_types.h>
 
+#include <crypto/aes.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..a18780231ce0
--- /dev/null
+++ b/rust/helpers/crypto.c
@@ -0,0 +1,37 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <crypto/aes.h>
+#include <crypto/aes-cbc-macs.h>
+#include <linux/string.h>
+
+/*
+ * 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 void
+rust_helper_aes_enckey_encrypt_block(const struct aes_enckey *key, u8 *out,
+				     const u8 *in)
+{
+	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;
+
+	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/helpers/helpers.c b/rust/helpers/helpers.c
index 0d85b5e68ec2..cb7c668bffa2 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..5f7c301b2bb8
--- /dev/null
+++ b/rust/kernel/crypto.rs
@@ -0,0 +1,115 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Safe wrappers over the kernel's synchronous library crypto.
+//!
+//! 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::*};
+
+/// 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
+}
+
+/// 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]);
+/// # Ok::<(), Error>(())
+/// ```
+pub struct Aes128(bindings::aes_enckey);
+
+impl Aes128 {
+    /// 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 with the prepared key: returns
+    /// `AES-128-ECB(key, block)`.
+    pub fn encrypt_block(&self, block: &[u8; AES128_BLOCK_SIZE]) -> [u8; AES128_BLOCK_SIZE] {
+        let mut out = [0u8; AES128_BLOCK_SIZE];
+        // 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()) };
+        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()) };
+    }
+}
diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs
index d7ced2a4c11f..3c45e4730646 100644
--- a/rust/kernel/lib.rs
+++ b/rust/kernel/lib.rs
@@ -62,6 +62,7 @@
 pub mod cpufreq;
 pub mod cpumask;
 pub mod cred;
+pub mod crypto;
 pub mod debugfs;
 pub mod device;
 pub mod device_id;
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.