Skip to main content

wolftpm/
rsa.rs

1//! RSA-OAEP encrypt and decrypt on a loaded RSA decryption key. This is the
2//! primitive the Microsoft device-enrollment path uses to wrap and unwrap
3//! session keys.
4
5use crate::key::{HashAlg, Key};
6use crate::{check_rc, sys, Result, Secret, TpmError};
7use alloc::vec;
8use alloc::vec::Vec;
9use core::ffi::c_int;
10
11impl<'d> Key<'d> {
12    /// Encrypt `msg` to this key's public part with RSA-OAEP.
13    pub fn rsa_encrypt(&self, msg: &[u8]) -> Result<Vec<u8>> {
14        let msg_len = crate::checked_c_int(msg.len())?;
15        let mut out = vec![0u8; sys::MAX_RSA_KEY_BYTES as usize];
16        let mut out_sz = out.len() as c_int;
17        // SAFETY: self.dev()/self.kptr() are live, msg ptr+len bound its slice, and out.as_mut_ptr()/out_sz describe the full out capacity.
18        let rc = unsafe {
19            sys::wolfTPM2_RsaEncrypt(
20                self.dev(),
21                self.kptr(),
22                sys::TPM_ALG_ID_T_TPM_ALG_OAEP as sys::TPM_ALG_ID,
23                msg.as_ptr(),
24                msg_len,
25                out.as_mut_ptr(),
26                &mut out_sz,
27            )
28        };
29        check_rc(rc)?;
30        out.truncate(out_sz as usize);
31        Ok(out)
32    }
33
34    /// Decrypt an RSA-OAEP ciphertext with this key's private part.
35    pub fn rsa_decrypt(&self, ciphertext: &[u8]) -> Result<Secret> {
36        let ct_len = crate::checked_c_int(ciphertext.len())?;
37        let mut out = vec![0u8; sys::MAX_RSA_KEY_BYTES as usize];
38        let mut out_sz = out.len() as c_int;
39        // SAFETY: self.dev()/self.kptr() are live, ciphertext ptr+len bound its slice, and out.as_mut_ptr()/out_sz describe the full out capacity.
40        let rc = unsafe {
41            sys::wolfTPM2_RsaDecrypt(
42                self.dev(),
43                self.kptr(),
44                sys::TPM_ALG_ID_T_TPM_ALG_OAEP as sys::TPM_ALG_ID,
45                ciphertext.as_ptr(),
46                ct_len,
47                out.as_mut_ptr(),
48                &mut out_sz,
49            )
50        };
51        check_rc(rc)?;
52        out.truncate(out_sz as usize);
53        Ok(Secret::new(out))
54    }
55
56    /// Encrypt `msg` with RSA-OAEP using an explicit label-hash algorithm.
57    ///
58    /// The plain [`rsa_encrypt`](Key::rsa_encrypt) uses the TPM's default OAEP
59    /// hash (SHA-256). This variant issues the low-level command directly so any
60    /// hash the TPM supports can be selected, including `HashAlg::Sha1`, which
61    /// Microsoft device enrollment (MS-OAPXBC) requires for its session-key
62    /// wrap. SHA-1 is legacy and cryptographically weak; select it only for that
63    /// interop, not for new designs.
64    pub fn rsa_encrypt_with_hash(&self, msg: &[u8], hash: HashAlg) -> Result<Vec<u8>> {
65        // SAFETY: RSA_Encrypt_In is a C POD struct; all-zero is a valid starting state, and the length check above bounds the copy into cin.message.buffer.
66        let mut cin: sys::RSA_Encrypt_In = unsafe { core::mem::zeroed() };
67        if msg.len() > cin.message.buffer.len() {
68            return Err(TpmError(crate::BUFFER_E));
69        }
70        // SAFETY: self.dev() is live and self.handle_ptr() remains valid for this call.
71        unsafe { sys::wolfTPM2_SetAuthHandle(self.dev(), 0, self.handle_ptr()) };
72        cin.keyHandle = self.handle();
73        cin.message.size = msg.len() as u16;
74        cin.message.buffer[..msg.len()].copy_from_slice(msg);
75        cin.inScheme.scheme = sys::TPM_ALG_ID_T_TPM_ALG_OAEP as sys::TPMI_ALG_RSA_DECRYPT;
76        cin.inScheme.details.anySig.hashAlg = hash.alg_id() as sys::TPMI_ALG_HASH;
77
78        // SAFETY: RSA_Encrypt_Out is a C POD struct; all-zero is a valid starting state for TPM2_RSA_Encrypt to fill.
79        let mut cout: sys::RSA_Encrypt_Out = unsafe { core::mem::zeroed() };
80        // SAFETY: &mut cin/&mut cout are valid, exclusively-borrowed in/out-params; cin.inScheme.details.anySig was just set to match the OAEP scheme above.
81        let rc = unsafe { sys::TPM2_RSA_Encrypt(&mut cin, &mut cout) };
82        // SAFETY: self.dev() is live; clear the auth slot and scrub the plaintext
83        // copy left in cin, on both the success and error paths.
84        unsafe {
85            sys::wolfTPM2_UnsetAuth(self.dev(), 0);
86            crate::zeroize_raw(&mut cin);
87        }
88        check_rc(rc)?;
89        let n = cout.outData.size as usize;
90        Ok(cout.outData.buffer[..n].to_vec())
91    }
92
93    /// Decrypt an RSA-OAEP ciphertext using an explicit label-hash algorithm
94    /// (see [`rsa_encrypt_with_hash`](Key::rsa_encrypt_with_hash)). Supports
95    /// `HashAlg::Sha1` for Microsoft enrollment interop.
96    pub fn rsa_decrypt_with_hash(&self, ciphertext: &[u8], hash: HashAlg) -> Result<Secret> {
97        // SAFETY: RSA_Decrypt_In is a C POD struct; all-zero is a valid starting state, and the length check above bounds the copy into cin.cipherText.buffer.
98        let mut cin: sys::RSA_Decrypt_In = unsafe { core::mem::zeroed() };
99        if ciphertext.len() > cin.cipherText.buffer.len() {
100            return Err(TpmError(crate::BUFFER_E));
101        }
102        // SAFETY: self.dev() is live and self.handle_ptr() remains valid for this call.
103        unsafe { sys::wolfTPM2_SetAuthHandle(self.dev(), 0, self.handle_ptr()) };
104        cin.keyHandle = self.handle();
105        cin.cipherText.size = ciphertext.len() as u16;
106        cin.cipherText.buffer[..ciphertext.len()].copy_from_slice(ciphertext);
107        cin.inScheme.scheme = sys::TPM_ALG_ID_T_TPM_ALG_OAEP as sys::TPMI_ALG_RSA_DECRYPT;
108        cin.inScheme.details.anySig.hashAlg = hash.alg_id() as sys::TPMI_ALG_HASH;
109
110        // SAFETY: RSA_Decrypt_Out is a C POD struct; all-zero is a valid starting state for TPM2_RSA_Decrypt to fill.
111        let mut cout: sys::RSA_Decrypt_Out = unsafe { core::mem::zeroed() };
112        // SAFETY: &mut cin/&mut cout are valid, exclusively-borrowed in/out-params; cin.inScheme.details.anySig was just set to match the OAEP scheme above.
113        let rc = unsafe { sys::TPM2_RSA_Decrypt(&mut cin, &mut cout) };
114        // SAFETY: self.dev() is live; this clears the auth slot set above regardless of the decrypt outcome.
115        unsafe { sys::wolfTPM2_UnsetAuth(self.dev(), 0) };
116        let out = if rc == 0 {
117            cout.message.buffer[..cout.message.size as usize].to_vec()
118        } else {
119            Vec::new()
120        };
121        // SAFETY: cout is a live, fully-owned local; zeroizing it after copying out scrubs the recovered plaintext.
122        unsafe { crate::zeroize_raw(&mut cout) };
123        check_rc(rc)?;
124        Ok(Secret::new(out))
125    }
126}