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}