Skip to content

Commit afdff20

Browse files
committed
Add AES encrypt/decrypt example
This adds an example of how to use AES-128-CBC encryption and decryption with a TPM. Signed-off-by: William Brown <[email protected]>
1 parent f4a3b77 commit afdff20

File tree

1 file changed

+231
-0
lines changed

1 file changed

+231
-0
lines changed
Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
// Copyright 2020 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+
resource_handles::Hierarchy,
14+
},
15+
structures::{
16+
CreatePrimaryKeyResult, Digest, InitialValue, MaxBuffer, PublicBuilder,
17+
SymmetricCipherParameters, SymmetricDefinitionObject,
18+
},
19+
Context, TctiNameConf,
20+
};
21+
22+
use std::convert::TryFrom;
23+
24+
fn main() {
25+
// Create a new TPM context. This reads from the environment variable `TPM2TOOLS_TCTI` or `TCTI`
26+
//
27+
// It's recommended you use `TCTI=device:/dev/tpmrm0` for the linux kernel
28+
// tpm resource manager.
29+
let mut context = Context::new(
30+
TctiNameConf::from_environment_variable()
31+
.expect("Failed to get TCTI / TPM2TOOLS_TCTI from environment. Try `export TCTI=device:/dev/tpmrm0`"),
32+
)
33+
.expect("Failed to create Context");
34+
35+
// This example won't go over the process to create a new parent. For more detail see `examples/hmac.rs`.
36+
37+
let primary = create_primary(&mut context);
38+
39+
// Begin to create our new AES symmetric key
40+
41+
let object_attributes = ObjectAttributesBuilder::new()
42+
.with_fixed_tpm(true)
43+
.with_fixed_parent(true)
44+
.with_st_clear(false)
45+
.with_sensitive_data_origin(true)
46+
.with_user_with_auth(true)
47+
.with_sign_encrypt(true)
48+
.with_decrypt(true)
49+
// Note that we don't set the key as restricted.
50+
.build()
51+
.expect("Failed to build object attributes");
52+
53+
let aes_params = SymmetricCipherParameters::new(SymmetricDefinitionObject::Aes {
54+
key_bits: AesKeyBits::Aes128,
55+
mode: SymmetricMode::Cbc,
56+
});
57+
58+
let key_pub = PublicBuilder::new()
59+
.with_public_algorithm(PublicAlgorithm::SymCipher)
60+
.with_name_hashing_algorithm(HashingAlgorithm::Sha256)
61+
.with_object_attributes(object_attributes)
62+
.with_symmetric_cipher_parameters(aes_params)
63+
.with_symmetric_cipher_unique_identifier(Digest::default())
64+
.build()
65+
.unwrap();
66+
67+
let (enc_private, public) = context
68+
.execute_with_nullauth_session(|ctx| {
69+
ctx.create(primary.key_handle, key_pub, None, None, None, None)
70+
.map(|key| (key.out_private, key.out_public))
71+
})
72+
.unwrap();
73+
74+
// The data we wish to encrypt. Be aware that there is a limit to the size of this data
75+
// that can be encrypted or decrypted (1024 bytes). In some cases you may need to encrypt a
76+
// "content encryption key", which can be decrypted and released and then used to decrypt
77+
// the actual data in question outside of the TPM.
78+
//
79+
// TPMs also tend to be "slower" for encryption/decryption, so you may consider the
80+
// CEK pattern for performance reasons.
81+
let data_to_encrypt = "TPMs are super cool, you should use them!"
82+
.as_bytes()
83+
.to_vec();
84+
85+
eprintln!("{:?}", data_to_encrypt.len());
86+
87+
// Input data needs to always be a multiple of AES_BLOCK_SIZE, so we implement PKCS7 padding
88+
// to achieve this.
89+
90+
// REVIEW NOTE: Tss-esapi likely should expose these as constants from AesKeyBits::Aes128
91+
// to prevent ambiguity!
92+
const AES_BLOCK_SIZE: usize = 16;
93+
94+
// REVIEW NOTE: Should we added PKCS7 padding as a function to MaxBuffer to prevent
95+
// people needing to "roll their own"?
96+
97+
let need_k_bytes = AES_BLOCK_SIZE - (data_to_encrypt.len() % AES_BLOCK_SIZE);
98+
// PKCS7 always pads to remove ambiguous situations.
99+
let need_k_bytes = if need_k_bytes == 0 {
100+
AES_BLOCK_SIZE
101+
} else {
102+
need_k_bytes
103+
};
104+
105+
let new_len = data_to_encrypt.len() + need_k_bytes;
106+
107+
let mut padded_data_to_encrypt = data_to_encrypt.to_vec();
108+
padded_data_to_encrypt.resize(new_len, need_k_bytes as u8);
109+
110+
let padded_data_to_encrypt = MaxBuffer::try_from(padded_data_to_encrypt).unwrap();
111+
112+
// Padding always has to be added.
113+
assert_ne!(
114+
data_to_encrypt.as_slice(),
115+
padded_data_to_encrypt.as_slice()
116+
);
117+
118+
// AES requires a random initial_value before any encryption or decryption. This must
119+
// be persisted with the encrypted data, else decryption can not be performed.
120+
// This value MUST be random, and should never be reused between different encryption
121+
// operations.
122+
let initial_value = context
123+
.execute_with_nullauth_session(|ctx| {
124+
ctx.get_random(InitialValue::MAX_SIZE)
125+
.and_then(|random| InitialValue::try_from(random.to_vec()))
126+
})
127+
.unwrap();
128+
129+
// Since AES is symmetric, we need the private component of the key to encrypt or decrypt
130+
// any values.
131+
let (encrypted_data, _initial_value) = context
132+
.execute_with_nullauth_session(|ctx| {
133+
let aes_key = ctx
134+
.load(primary.key_handle, enc_private.clone(), public.clone())
135+
.unwrap();
136+
137+
let decrypt = false;
138+
139+
ctx.encrypt_decrypt_2(
140+
aes_key,
141+
decrypt,
142+
SymmetricMode::Cbc,
143+
padded_data_to_encrypt.clone(),
144+
initial_value.clone(),
145+
)
146+
})
147+
.unwrap();
148+
149+
// The data is now encrypted.
150+
println!("encrypted_data = {:?}", encrypted_data);
151+
assert_ne!(encrypted_data.as_slice(), padded_data_to_encrypt.as_slice());
152+
153+
// Decryption is the identical process with the "decrypt" flag set to true.
154+
let (decrypted_data, _initial_value) = context
155+
.execute_with_nullauth_session(|ctx| {
156+
let aes_key = ctx
157+
.load(primary.key_handle, enc_private.clone(), public.clone())
158+
.unwrap();
159+
160+
let decrypt = true;
161+
162+
ctx.encrypt_decrypt_2(
163+
aes_key,
164+
decrypt,
165+
SymmetricMode::Cbc,
166+
encrypted_data.clone(),
167+
initial_value,
168+
)
169+
})
170+
.unwrap();
171+
172+
// Now we have to un-pad the output.
173+
if decrypted_data.is_empty() {
174+
panic!("Should not be empty");
175+
}
176+
177+
let last_byte = decrypted_data.len() - 1;
178+
let k_byte = decrypted_data[last_byte];
179+
// Since pkcs7 padding repeats this byte k times, we check that this byte
180+
// is repeated as many times as expected. In theory we don't need this check
181+
// but it's better to be defensive.
182+
183+
if k_byte as usize > AES_BLOCK_SIZE {
184+
panic!("Invalid pad byte, exceeds AES_BLOCK_SIZE");
185+
}
186+
187+
for i in 0..k_byte {
188+
if decrypted_data[last_byte - i as usize] != k_byte {
189+
panic!("Invalid pad byte, is not equal to k_byte");
190+
}
191+
}
192+
193+
let truncate_to = decrypted_data.len().checked_sub(k_byte as usize).unwrap();
194+
let mut decrypted_data = decrypted_data.to_vec();
195+
decrypted_data.truncate(truncate_to);
196+
197+
println!("data_to_encrypt = {:?}", data_to_encrypt);
198+
println!("decrypted_data = {:?}", decrypted_data);
199+
// They are the same!
200+
assert_eq!(data_to_encrypt, decrypted_data);
201+
}
202+
203+
fn create_primary(context: &mut Context) -> CreatePrimaryKeyResult {
204+
let object_attributes = ObjectAttributesBuilder::new()
205+
.with_fixed_tpm(true)
206+
.with_fixed_parent(true)
207+
.with_st_clear(false)
208+
.with_sensitive_data_origin(true)
209+
.with_user_with_auth(true)
210+
.with_decrypt(true)
211+
.with_restricted(true)
212+
.build()
213+
.expect("Failed to build object attributes");
214+
215+
let primary_pub = PublicBuilder::new()
216+
.with_public_algorithm(PublicAlgorithm::SymCipher)
217+
.with_name_hashing_algorithm(HashingAlgorithm::Sha256)
218+
.with_object_attributes(object_attributes)
219+
.with_symmetric_cipher_parameters(SymmetricCipherParameters::new(
220+
SymmetricDefinitionObject::AES_128_CFB,
221+
))
222+
.with_symmetric_cipher_unique_identifier(Digest::default())
223+
.build()
224+
.unwrap();
225+
226+
context
227+
.execute_with_nullauth_session(|ctx| {
228+
ctx.create_primary(Hierarchy::Owner, primary_pub, None, None, None, None)
229+
})
230+
.unwrap()
231+
}

0 commit comments

Comments
 (0)