Skip to content

Commit ce6dea3

Browse files
committed
Add rust-crypto encrypt example
1 parent 32a3338 commit ce6dea3

2 files changed

Lines changed: 315 additions & 0 deletions

File tree

tss-esapi/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ env_logger = "0.9.0"
4141
sha2 = "0.10.1"
4242
serde_json = "^1.0.108"
4343

44+
cipher = { version = "0.4", features = ["block-padding", "alloc"] }
45+
46+
4447
[build-dependencies]
4548
semver = "1.0.7"
4649

Lines changed: 312 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,312 @@
1+
// Copyright 2024 Contributors to the Parsec project.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
/*
5+
* This example demonstrates how to use AES for symmetric encryption and decryption
6+
*/
7+
8+
use tss_esapi::{
9+
attributes::ObjectAttributesBuilder,
10+
interface_types::{
11+
algorithm::{HashingAlgorithm, PublicAlgorithm, SymmetricMode},
12+
key_bits::AesKeyBits,
13+
reserved_handles::Hierarchy,
14+
},
15+
structures::{
16+
CreatePrimaryKeyResult, Digest, InitialValue, MaxBuffer, PublicBuilder,
17+
SymmetricCipherParameters, SymmetricDefinitionObject,
18+
},
19+
Context, TctiNameConf,
20+
handles::KeyHandle,
21+
};
22+
23+
use cipher::BlockEncryptMut;
24+
25+
use std::convert::TryFrom;
26+
27+
fn main() {
28+
// Create a new TPM context. This reads from the environment variable `TPM2TOOLS_TCTI` or `TCTI`
29+
//
30+
// It's recommended you use `TCTI=device:/dev/tpmrm0` for the linux kernel
31+
// tpm resource manager.
32+
let mut context = Context::new(
33+
TctiNameConf::from_environment_variable()
34+
.expect("Failed to get TCTI / TPM2TOOLS_TCTI from environment. Try `export TCTI=device:/dev/tpmrm0`"),
35+
)
36+
.expect("Failed to create Context");
37+
38+
// This example won't go over the process to create a new parent. For more detail see `examples/hmac.rs`.
39+
40+
let primary = create_primary(&mut context);
41+
42+
// Begin to create our new AES symmetric key
43+
44+
let object_attributes = ObjectAttributesBuilder::new()
45+
.with_fixed_tpm(true)
46+
.with_fixed_parent(true)
47+
.with_st_clear(false)
48+
.with_sensitive_data_origin(true)
49+
.with_user_with_auth(true)
50+
.with_sign_encrypt(true)
51+
.with_decrypt(true)
52+
// Note that we don't set the key as restricted.
53+
.build()
54+
.expect("Failed to build object attributes");
55+
56+
let aes_params = SymmetricCipherParameters::new(SymmetricDefinitionObject::Aes {
57+
key_bits: AesKeyBits::Aes128,
58+
mode: SymmetricMode::Cbc,
59+
});
60+
61+
let key_pub = PublicBuilder::new()
62+
.with_public_algorithm(PublicAlgorithm::SymCipher)
63+
.with_name_hashing_algorithm(HashingAlgorithm::Sha256)
64+
.with_object_attributes(object_attributes)
65+
.with_symmetric_cipher_parameters(aes_params)
66+
.with_symmetric_cipher_unique_identifier(Digest::default())
67+
.build()
68+
.unwrap();
69+
70+
let (enc_private, public) = context
71+
.execute_with_nullauth_session(|ctx| {
72+
ctx.create(primary.key_handle, key_pub, None, None, None, None)
73+
.map(|key| (key.out_private, key.out_public))
74+
})
75+
.unwrap();
76+
77+
// The data we wish to encrypt. Be aware that there is a limit to the size of this data
78+
// that can be encrypted or decrypted (1024 bytes). In some cases you may need to encrypt a
79+
// content encryption key (CEK), which can be decrypted and released and then used to decrypt
80+
// the actual data in question outside of the TPM.
81+
//
82+
// TPMs also tend to be "slower" for encryption/decryption, so you should consider the
83+
// CEK pattern for performance reasons.
84+
let data_to_encrypt = "TPMs are super cool, you should use them! They are even better when you can use other interfaces like Rust Crypto!"
85+
.as_bytes()
86+
.to_vec();
87+
88+
eprintln!("{:?}", data_to_encrypt.len());
89+
eprintln!("{:?}", data_to_encrypt);
90+
eprintln!("--");
91+
92+
// AES requires a random initial_value before any encryption or decryption. This must
93+
// be persisted with the encrypted data, else decryption can not be performed.
94+
// This value MUST be random, and should never be reused between different encryption
95+
// operations.
96+
let initial_value = context
97+
.execute_with_nullauth_session(|ctx| {
98+
ctx.get_random(InitialValue::MAX_SIZE)
99+
.and_then(|random| InitialValue::try_from(random.to_vec()))
100+
})
101+
.unwrap();
102+
103+
let encrypted_data = context
104+
.execute_with_nullauth_session(|ctx| {
105+
let aes_key = ctx
106+
.load(primary.key_handle, enc_private.clone(), public.clone())
107+
.unwrap();
108+
109+
let aes_128_cbc_enc = TpmAes128CbcEnc {
110+
cipher: TpmEnc {
111+
ctx,
112+
handle: aes_key,
113+
iv: initial_value.clone(),
114+
},
115+
};
116+
117+
let enc_data = aes_128_cbc_enc
118+
.encrypt_padded_vec_mut::<cipher::block_padding::Pkcs7>(&data_to_encrypt);
119+
120+
Ok::<_, tss_esapi::Error>(enc_data)
121+
})
122+
.unwrap();
123+
124+
125+
/*
126+
// Since AES is symmetric, we need the private component of the key to encrypt or decrypt
127+
// any values.
128+
let (encrypted_data, _initial_value) =
129+
context
130+
.execute_with_nullauth_session(|ctx| {
131+
let aes_key = ctx
132+
.load(primary.key_handle, enc_private.clone(), public.clone())
133+
.unwrap();
134+
135+
let decrypt = false;
136+
137+
ctx.encrypt_decrypt_2(
138+
aes_key,
139+
decrypt,
140+
SymmetricMode::Cbc,
141+
padded_data_to_encrypt.clone(),
142+
initial_value.clone(),
143+
)
144+
})
145+
.unwrap();
146+
147+
assert_ne!(encrypted_data.as_slice(), padded_data_to_encrypt.as_slice());
148+
149+
*/
150+
151+
let encrypted_data = MaxBuffer::try_from(encrypted_data).unwrap();
152+
153+
// The data is now encrypted.
154+
println!("encrypted_data = {:?}", encrypted_data);
155+
156+
// Decryption is the identical process with the "decrypt" flag set to true.
157+
let (decrypted_data, _initial_value) = context
158+
.execute_with_nullauth_session(|ctx| {
159+
let aes_key = ctx
160+
.load(primary.key_handle, enc_private.clone(), public.clone())
161+
.unwrap();
162+
163+
let decrypt = true;
164+
165+
ctx.encrypt_decrypt_2(
166+
aes_key,
167+
decrypt,
168+
SymmetricMode::Cbc,
169+
encrypted_data.clone(),
170+
initial_value,
171+
)
172+
})
173+
.unwrap();
174+
175+
// Now we have to un-pad the output.
176+
if decrypted_data.is_empty() {
177+
panic!("Should not be empty");
178+
}
179+
180+
const AES_BLOCK_SIZE: usize = 16;
181+
182+
// WARNING: Manually implemented pkcs7 follows. This has not been audited. Don't use this
183+
// in production.
184+
185+
let last_byte = decrypted_data.len() - 1;
186+
let k_byte = decrypted_data[last_byte];
187+
// Since pkcs7 padding repeats this byte k times, we check that this byte
188+
// is repeated as many times as expected. In theory we don't need this check
189+
// but it's better to be defensive.
190+
191+
eprintln!("{:?}", decrypted_data);
192+
193+
if k_byte as usize > AES_BLOCK_SIZE {
194+
eprintln!("{}", k_byte);
195+
panic!("Invalid pad byte, exceeds AES_BLOCK_SIZE");
196+
}
197+
198+
for i in 0..k_byte {
199+
if decrypted_data[last_byte - i as usize] != k_byte {
200+
panic!("Invalid pad byte, is not equal to k_byte");
201+
}
202+
}
203+
204+
let truncate_to = decrypted_data.len().checked_sub(k_byte as usize).unwrap();
205+
let mut decrypted_data = decrypted_data.to_vec();
206+
decrypted_data.truncate(truncate_to);
207+
208+
// END WARNING
209+
210+
println!("data_to_encrypt = {:?}", data_to_encrypt);
211+
println!("decrypted_data = {:?}", decrypted_data);
212+
// They are the same!
213+
assert_eq!(data_to_encrypt, decrypted_data);
214+
}
215+
216+
fn create_primary(context: &mut Context) -> CreatePrimaryKeyResult {
217+
let object_attributes = ObjectAttributesBuilder::new()
218+
.with_fixed_tpm(true)
219+
.with_fixed_parent(true)
220+
.with_st_clear(false)
221+
.with_sensitive_data_origin(true)
222+
.with_user_with_auth(true)
223+
.with_decrypt(true)
224+
.with_restricted(true)
225+
.build()
226+
.expect("Failed to build object attributes");
227+
228+
let primary_pub = PublicBuilder::new()
229+
.with_public_algorithm(PublicAlgorithm::SymCipher)
230+
.with_name_hashing_algorithm(HashingAlgorithm::Sha256)
231+
.with_object_attributes(object_attributes)
232+
.with_symmetric_cipher_parameters(SymmetricCipherParameters::new(
233+
SymmetricDefinitionObject::AES_128_CFB,
234+
))
235+
.with_symmetric_cipher_unique_identifier(Digest::default())
236+
.build()
237+
.unwrap();
238+
239+
context
240+
.execute_with_nullauth_session(|ctx| {
241+
ctx.create_primary(Hierarchy::Owner, primary_pub, None, None, None, None)
242+
})
243+
.unwrap()
244+
}
245+
246+
247+
// In the future I would probably do this where the object is actually a
248+
// stored context that is loaded/unloaded as required. We would also check
249+
// the handle is suitable for this purpose etc.
250+
struct TpmAes128CbcEnc<'a> {
251+
cipher: TpmEnc<'a>,
252+
}
253+
254+
struct TpmEnc<'a> {
255+
ctx: &'a mut Context,
256+
handle: KeyHandle,
257+
iv: InitialValue,
258+
}
259+
260+
impl<'a> cipher::BlockSizeUser for TpmAes128CbcEnc<'a> {
261+
type BlockSize = cipher::consts::U16;
262+
}
263+
264+
impl<'a> cipher::BlockEncryptMut for TpmAes128CbcEnc<'a> {
265+
fn encrypt_with_backend_mut(
266+
&mut self,
267+
f: impl cipher::BlockClosure<BlockSize = Self::BlockSize>
268+
) {
269+
// No errors? I'll raise an issue.
270+
let Self { cipher } = self;
271+
272+
f.call(cipher)
273+
}
274+
}
275+
276+
impl<'a> cipher::BlockSizeUser for TpmEnc<'a> {
277+
type BlockSize = cipher::consts::U16;
278+
}
279+
280+
impl<'a> cipher::ParBlocksSizeUser for TpmEnc<'a> {
281+
// 1024 / 16 bytes
282+
type ParBlocksSize = cipher::consts::U16;
283+
}
284+
285+
impl<'a> cipher::BlockBackend for TpmEnc<'a> {
286+
fn proc_block(&mut self, mut block: cipher::inout::InOut<'_, '_, cipher::Block<Self>>) {
287+
// TODO: Do we need to mutate current iv as we go?
288+
let decrypt = false;
289+
290+
let data_in = MaxBuffer::try_from(block.clone_in().to_vec()).unwrap();
291+
292+
eprintln!("data_in: {:?}", data_in);
293+
294+
let (encrypted_data, initial_value) = self.ctx
295+
.encrypt_decrypt_2(
296+
self.handle,
297+
decrypt,
298+
SymmetricMode::Cbc,
299+
data_in,
300+
self.iv.clone(),
301+
)
302+
.unwrap();
303+
304+
self.iv = initial_value;
305+
306+
eprintln!("encrypted: {:?}", encrypted_data);
307+
308+
block.get_out().copy_from_slice(encrypted_data.as_slice());
309+
}
310+
}
311+
312+

0 commit comments

Comments
 (0)