From ef26f07fa09b91269b15c1321efd0c05fa88792c Mon Sep 17 00:00:00 2001 From: William Brown Date: Sun, 4 Feb 2024 15:15:50 +1000 Subject: [PATCH 1/4] 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 --- tss-esapi/examples/aes_encrypt_decrypt.rs | 231 ++++++++++++++++++++++ 1 file changed, 231 insertions(+) create mode 100644 tss-esapi/examples/aes_encrypt_decrypt.rs diff --git a/tss-esapi/examples/aes_encrypt_decrypt.rs b/tss-esapi/examples/aes_encrypt_decrypt.rs new file mode 100644 index 000000000..27d331985 --- /dev/null +++ b/tss-esapi/examples/aes_encrypt_decrypt.rs @@ -0,0 +1,231 @@ +// Copyright 2020 Contributors to the Parsec project. +// SPDX-License-Identifier: Apache-2.0 + +/* + * This example demonstrates how to use AES for symmetric encryption and decryption + */ + +use tss_esapi::{ + attributes::ObjectAttributesBuilder, + interface_types::{ + algorithm::{HashingAlgorithm, PublicAlgorithm, SymmetricMode}, + key_bits::AesKeyBits, + resource_handles::Hierarchy, + }, + structures::{ + CreatePrimaryKeyResult, Digest, InitialValue, MaxBuffer, PublicBuilder, + SymmetricCipherParameters, SymmetricDefinitionObject, + }, + Context, TctiNameConf, +}; + +use std::convert::TryFrom; + +fn main() { + // Create a new TPM context. This reads from the environment variable `TPM2TOOLS_TCTI` or `TCTI` + // + // It's recommended you use `TCTI=device:/dev/tpmrm0` for the linux kernel + // tpm resource manager. + let mut context = Context::new( + TctiNameConf::from_environment_variable() + .expect("Failed to get TCTI / TPM2TOOLS_TCTI from environment. Try `export TCTI=device:/dev/tpmrm0`"), + ) + .expect("Failed to create Context"); + + // This example won't go over the process to create a new parent. For more detail see `examples/hmac.rs`. + + let primary = create_primary(&mut context); + + // Begin to create our new AES symmetric key + + let object_attributes = ObjectAttributesBuilder::new() + .with_fixed_tpm(true) + .with_fixed_parent(true) + .with_st_clear(false) + .with_sensitive_data_origin(true) + .with_user_with_auth(true) + .with_sign_encrypt(true) + .with_decrypt(true) + // Note that we don't set the key as restricted. + .build() + .expect("Failed to build object attributes"); + + let aes_params = SymmetricCipherParameters::new(SymmetricDefinitionObject::Aes { + key_bits: AesKeyBits::Aes128, + mode: SymmetricMode::Cbc, + }); + + let key_pub = PublicBuilder::new() + .with_public_algorithm(PublicAlgorithm::SymCipher) + .with_name_hashing_algorithm(HashingAlgorithm::Sha256) + .with_object_attributes(object_attributes) + .with_symmetric_cipher_parameters(aes_params) + .with_symmetric_cipher_unique_identifier(Digest::default()) + .build() + .unwrap(); + + let (enc_private, public) = context + .execute_with_nullauth_session(|ctx| { + ctx.create(primary.key_handle, key_pub, None, None, None, None) + .map(|key| (key.out_private, key.out_public)) + }) + .unwrap(); + + // The data we wish to encrypt. Be aware that there is a limit to the size of this data + // that can be encrypted or decrypted (1024 bytes). In some cases you may need to encrypt a + // "content encryption key", which can be decrypted and released and then used to decrypt + // the actual data in question outside of the TPM. + // + // TPMs also tend to be "slower" for encryption/decryption, so you may consider the + // CEK pattern for performance reasons. + let data_to_encrypt = "TPMs are super cool, you should use them!" + .as_bytes() + .to_vec(); + + eprintln!("{:?}", data_to_encrypt.len()); + + // Input data needs to always be a multiple of AES_BLOCK_SIZE, so we implement PKCS7 padding + // to achieve this. + + // REVIEW NOTE: Tss-esapi likely should expose these as constants from AesKeyBits::Aes128 + // to prevent ambiguity! + const AES_BLOCK_SIZE: usize = 16; + + // REVIEW NOTE: Should we added PKCS7 padding as a function to MaxBuffer to prevent + // people needing to "roll their own"? + + let need_k_bytes = AES_BLOCK_SIZE - (data_to_encrypt.len() % AES_BLOCK_SIZE); + // PKCS7 always pads to remove ambiguous situations. + let need_k_bytes = if need_k_bytes == 0 { + AES_BLOCK_SIZE + } else { + need_k_bytes + }; + + let new_len = data_to_encrypt.len() + need_k_bytes; + + let mut padded_data_to_encrypt = data_to_encrypt.to_vec(); + padded_data_to_encrypt.resize(new_len, need_k_bytes as u8); + + let padded_data_to_encrypt = MaxBuffer::try_from(padded_data_to_encrypt).unwrap(); + + // Padding always has to be added. + assert_ne!( + data_to_encrypt.as_slice(), + padded_data_to_encrypt.as_slice() + ); + + // AES requires a random initial_value before any encryption or decryption. This must + // be persisted with the encrypted data, else decryption can not be performed. + // This value MUST be random, and should never be reused between different encryption + // operations. + let initial_value = context + .execute_with_nullauth_session(|ctx| { + ctx.get_random(InitialValue::MAX_SIZE) + .and_then(|random| InitialValue::try_from(random.to_vec())) + }) + .unwrap(); + + // Since AES is symmetric, we need the private component of the key to encrypt or decrypt + // any values. + let (encrypted_data, _initial_value) = context + .execute_with_nullauth_session(|ctx| { + let aes_key = ctx + .load(primary.key_handle, enc_private.clone(), public.clone()) + .unwrap(); + + let decrypt = false; + + ctx.encrypt_decrypt_2( + aes_key, + decrypt, + SymmetricMode::Cbc, + padded_data_to_encrypt.clone(), + initial_value.clone(), + ) + }) + .unwrap(); + + // The data is now encrypted. + println!("encrypted_data = {:?}", encrypted_data); + assert_ne!(encrypted_data.as_slice(), padded_data_to_encrypt.as_slice()); + + // Decryption is the identical process with the "decrypt" flag set to true. + let (decrypted_data, _initial_value) = context + .execute_with_nullauth_session(|ctx| { + let aes_key = ctx + .load(primary.key_handle, enc_private.clone(), public.clone()) + .unwrap(); + + let decrypt = true; + + ctx.encrypt_decrypt_2( + aes_key, + decrypt, + SymmetricMode::Cbc, + encrypted_data.clone(), + initial_value, + ) + }) + .unwrap(); + + // Now we have to un-pad the output. + if decrypted_data.is_empty() { + panic!("Should not be empty"); + } + + let last_byte = decrypted_data.len() - 1; + let k_byte = decrypted_data[last_byte]; + // Since pkcs7 padding repeats this byte k times, we check that this byte + // is repeated as many times as expected. In theory we don't need this check + // but it's better to be defensive. + + if k_byte as usize > AES_BLOCK_SIZE { + panic!("Invalid pad byte, exceeds AES_BLOCK_SIZE"); + } + + for i in 0..k_byte { + if decrypted_data[last_byte - i as usize] != k_byte { + panic!("Invalid pad byte, is not equal to k_byte"); + } + } + + let truncate_to = decrypted_data.len().checked_sub(k_byte as usize).unwrap(); + let mut decrypted_data = decrypted_data.to_vec(); + decrypted_data.truncate(truncate_to); + + println!("data_to_encrypt = {:?}", data_to_encrypt); + println!("decrypted_data = {:?}", decrypted_data); + // They are the same! + assert_eq!(data_to_encrypt, decrypted_data); +} + +fn create_primary(context: &mut Context) -> CreatePrimaryKeyResult { + let object_attributes = ObjectAttributesBuilder::new() + .with_fixed_tpm(true) + .with_fixed_parent(true) + .with_st_clear(false) + .with_sensitive_data_origin(true) + .with_user_with_auth(true) + .with_decrypt(true) + .with_restricted(true) + .build() + .expect("Failed to build object attributes"); + + let primary_pub = PublicBuilder::new() + .with_public_algorithm(PublicAlgorithm::SymCipher) + .with_name_hashing_algorithm(HashingAlgorithm::Sha256) + .with_object_attributes(object_attributes) + .with_symmetric_cipher_parameters(SymmetricCipherParameters::new( + SymmetricDefinitionObject::AES_128_CFB, + )) + .with_symmetric_cipher_unique_identifier(Digest::default()) + .build() + .unwrap(); + + context + .execute_with_nullauth_session(|ctx| { + ctx.create_primary(Hierarchy::Owner, primary_pub, None, None, None, None) + }) + .unwrap() +} From 62567cf3da014d89d7771e4ec257cf21380e3461 Mon Sep 17 00:00:00 2001 From: William Brown Date: Tue, 20 Feb 2024 13:17:00 +1000 Subject: [PATCH 2/4] Update copyright year --- tss-esapi/examples/aes_encrypt_decrypt.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tss-esapi/examples/aes_encrypt_decrypt.rs b/tss-esapi/examples/aes_encrypt_decrypt.rs index 27d331985..a0079c102 100644 --- a/tss-esapi/examples/aes_encrypt_decrypt.rs +++ b/tss-esapi/examples/aes_encrypt_decrypt.rs @@ -1,4 +1,4 @@ -// Copyright 2020 Contributors to the Parsec project. +// Copyright 2024 Contributors to the Parsec project. // SPDX-License-Identifier: Apache-2.0 /* From 32a3338f6d4c9a9770b18741f02c8cc7909b3957 Mon Sep 17 00:00:00 2001 From: William Brown Date: Wed, 1 May 2024 15:28:17 +1000 Subject: [PATCH 3/4] Latest Updates --- tss-esapi/examples/aes_encrypt_decrypt.rs | 26 +++++++++++++---------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/tss-esapi/examples/aes_encrypt_decrypt.rs b/tss-esapi/examples/aes_encrypt_decrypt.rs index a0079c102..7347ad11d 100644 --- a/tss-esapi/examples/aes_encrypt_decrypt.rs +++ b/tss-esapi/examples/aes_encrypt_decrypt.rs @@ -10,7 +10,7 @@ use tss_esapi::{ interface_types::{ algorithm::{HashingAlgorithm, PublicAlgorithm, SymmetricMode}, key_bits::AesKeyBits, - resource_handles::Hierarchy, + reserved_handles::Hierarchy, }, structures::{ CreatePrimaryKeyResult, Digest, InitialValue, MaxBuffer, PublicBuilder, @@ -73,10 +73,10 @@ fn main() { // The data we wish to encrypt. Be aware that there is a limit to the size of this data // that can be encrypted or decrypted (1024 bytes). In some cases you may need to encrypt a - // "content encryption key", which can be decrypted and released and then used to decrypt + // content encryption key (CEK), which can be decrypted and released and then used to decrypt // the actual data in question outside of the TPM. // - // TPMs also tend to be "slower" for encryption/decryption, so you may consider the + // TPMs also tend to be "slower" for encryption/decryption, so you should consider the // CEK pattern for performance reasons. let data_to_encrypt = "TPMs are super cool, you should use them!" .as_bytes() @@ -84,16 +84,14 @@ fn main() { eprintln!("{:?}", data_to_encrypt.len()); - // Input data needs to always be a multiple of AES_BLOCK_SIZE, so we implement PKCS7 padding - // to achieve this. + // Input data needs to always be a multiple of the AES block size, in this case which is 16 + // bytes for AES-128-CBC. Normally you *MUST* implement a secure padding scheme such as pkcs7 + // but in this example we will *manually* pad the data. - // REVIEW NOTE: Tss-esapi likely should expose these as constants from AesKeyBits::Aes128 - // to prevent ambiguity! + // WARNING: Manually implemented pkcs7 follows. This has not been audited. Don't use this + // in production. const AES_BLOCK_SIZE: usize = 16; - // REVIEW NOTE: Should we added PKCS7 padding as a function to MaxBuffer to prevent - // people needing to "roll their own"? - let need_k_bytes = AES_BLOCK_SIZE - (data_to_encrypt.len() % AES_BLOCK_SIZE); // PKCS7 always pads to remove ambiguous situations. let need_k_bytes = if need_k_bytes == 0 { @@ -109,11 +107,12 @@ fn main() { let padded_data_to_encrypt = MaxBuffer::try_from(padded_data_to_encrypt).unwrap(); - // Padding always has to be added. + // Padding always has to be added in pkcs7 to make it unambiguous. assert_ne!( data_to_encrypt.as_slice(), padded_data_to_encrypt.as_slice() ); + // END WARNING // AES requires a random initial_value before any encryption or decryption. This must // be persisted with the encrypted data, else decryption can not be performed. @@ -174,6 +173,9 @@ fn main() { panic!("Should not be empty"); } + // WARNING: Manually implemented pkcs7 follows. This has not been audited. Don't use this + // in production. + let last_byte = decrypted_data.len() - 1; let k_byte = decrypted_data[last_byte]; // Since pkcs7 padding repeats this byte k times, we check that this byte @@ -194,6 +196,8 @@ fn main() { let mut decrypted_data = decrypted_data.to_vec(); decrypted_data.truncate(truncate_to); + // END WARNING + println!("data_to_encrypt = {:?}", data_to_encrypt); println!("decrypted_data = {:?}", decrypted_data); // They are the same! From ce6dea304bc5763e15d873714bfffec7fe44702f Mon Sep 17 00:00:00 2001 From: William Brown Date: Wed, 15 May 2024 17:55:21 +1000 Subject: [PATCH 4/4] Add rust-crypto encrypt example --- tss-esapi/Cargo.toml | 3 + .../aes_encrypt_decrypt_rust_crypto.rs | 312 ++++++++++++++++++ 2 files changed, 315 insertions(+) create mode 100644 tss-esapi/examples/aes_encrypt_decrypt_rust_crypto.rs diff --git a/tss-esapi/Cargo.toml b/tss-esapi/Cargo.toml index 3740da0fa..f91fc6f54 100644 --- a/tss-esapi/Cargo.toml +++ b/tss-esapi/Cargo.toml @@ -41,6 +41,9 @@ env_logger = "0.9.0" sha2 = "0.10.1" serde_json = "^1.0.108" +cipher = { version = "0.4", features = ["block-padding", "alloc"] } + + [build-dependencies] semver = "1.0.7" diff --git a/tss-esapi/examples/aes_encrypt_decrypt_rust_crypto.rs b/tss-esapi/examples/aes_encrypt_decrypt_rust_crypto.rs new file mode 100644 index 000000000..d9362597b --- /dev/null +++ b/tss-esapi/examples/aes_encrypt_decrypt_rust_crypto.rs @@ -0,0 +1,312 @@ +// Copyright 2024 Contributors to the Parsec project. +// SPDX-License-Identifier: Apache-2.0 + +/* + * This example demonstrates how to use AES for symmetric encryption and decryption + */ + +use tss_esapi::{ + attributes::ObjectAttributesBuilder, + interface_types::{ + algorithm::{HashingAlgorithm, PublicAlgorithm, SymmetricMode}, + key_bits::AesKeyBits, + reserved_handles::Hierarchy, + }, + structures::{ + CreatePrimaryKeyResult, Digest, InitialValue, MaxBuffer, PublicBuilder, + SymmetricCipherParameters, SymmetricDefinitionObject, + }, + Context, TctiNameConf, + handles::KeyHandle, +}; + +use cipher::BlockEncryptMut; + +use std::convert::TryFrom; + +fn main() { + // Create a new TPM context. This reads from the environment variable `TPM2TOOLS_TCTI` or `TCTI` + // + // It's recommended you use `TCTI=device:/dev/tpmrm0` for the linux kernel + // tpm resource manager. + let mut context = Context::new( + TctiNameConf::from_environment_variable() + .expect("Failed to get TCTI / TPM2TOOLS_TCTI from environment. Try `export TCTI=device:/dev/tpmrm0`"), + ) + .expect("Failed to create Context"); + + // This example won't go over the process to create a new parent. For more detail see `examples/hmac.rs`. + + let primary = create_primary(&mut context); + + // Begin to create our new AES symmetric key + + let object_attributes = ObjectAttributesBuilder::new() + .with_fixed_tpm(true) + .with_fixed_parent(true) + .with_st_clear(false) + .with_sensitive_data_origin(true) + .with_user_with_auth(true) + .with_sign_encrypt(true) + .with_decrypt(true) + // Note that we don't set the key as restricted. + .build() + .expect("Failed to build object attributes"); + + let aes_params = SymmetricCipherParameters::new(SymmetricDefinitionObject::Aes { + key_bits: AesKeyBits::Aes128, + mode: SymmetricMode::Cbc, + }); + + let key_pub = PublicBuilder::new() + .with_public_algorithm(PublicAlgorithm::SymCipher) + .with_name_hashing_algorithm(HashingAlgorithm::Sha256) + .with_object_attributes(object_attributes) + .with_symmetric_cipher_parameters(aes_params) + .with_symmetric_cipher_unique_identifier(Digest::default()) + .build() + .unwrap(); + + let (enc_private, public) = context + .execute_with_nullauth_session(|ctx| { + ctx.create(primary.key_handle, key_pub, None, None, None, None) + .map(|key| (key.out_private, key.out_public)) + }) + .unwrap(); + + // The data we wish to encrypt. Be aware that there is a limit to the size of this data + // that can be encrypted or decrypted (1024 bytes). In some cases you may need to encrypt a + // content encryption key (CEK), which can be decrypted and released and then used to decrypt + // the actual data in question outside of the TPM. + // + // TPMs also tend to be "slower" for encryption/decryption, so you should consider the + // CEK pattern for performance reasons. + 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!" + .as_bytes() + .to_vec(); + + eprintln!("{:?}", data_to_encrypt.len()); + eprintln!("{:?}", data_to_encrypt); + eprintln!("--"); + + // AES requires a random initial_value before any encryption or decryption. This must + // be persisted with the encrypted data, else decryption can not be performed. + // This value MUST be random, and should never be reused between different encryption + // operations. + let initial_value = context + .execute_with_nullauth_session(|ctx| { + ctx.get_random(InitialValue::MAX_SIZE) + .and_then(|random| InitialValue::try_from(random.to_vec())) + }) + .unwrap(); + + let encrypted_data = context + .execute_with_nullauth_session(|ctx| { + let aes_key = ctx + .load(primary.key_handle, enc_private.clone(), public.clone()) + .unwrap(); + + let aes_128_cbc_enc = TpmAes128CbcEnc { + cipher: TpmEnc { + ctx, + handle: aes_key, + iv: initial_value.clone(), + }, + }; + + let enc_data = aes_128_cbc_enc + .encrypt_padded_vec_mut::(&data_to_encrypt); + + Ok::<_, tss_esapi::Error>(enc_data) + }) + .unwrap(); + + + /* + // Since AES is symmetric, we need the private component of the key to encrypt or decrypt + // any values. + let (encrypted_data, _initial_value) = + context + .execute_with_nullauth_session(|ctx| { + let aes_key = ctx + .load(primary.key_handle, enc_private.clone(), public.clone()) + .unwrap(); + + let decrypt = false; + + ctx.encrypt_decrypt_2( + aes_key, + decrypt, + SymmetricMode::Cbc, + padded_data_to_encrypt.clone(), + initial_value.clone(), + ) + }) + .unwrap(); + + assert_ne!(encrypted_data.as_slice(), padded_data_to_encrypt.as_slice()); + + */ + + let encrypted_data = MaxBuffer::try_from(encrypted_data).unwrap(); + + // The data is now encrypted. + println!("encrypted_data = {:?}", encrypted_data); + + // Decryption is the identical process with the "decrypt" flag set to true. + let (decrypted_data, _initial_value) = context + .execute_with_nullauth_session(|ctx| { + let aes_key = ctx + .load(primary.key_handle, enc_private.clone(), public.clone()) + .unwrap(); + + let decrypt = true; + + ctx.encrypt_decrypt_2( + aes_key, + decrypt, + SymmetricMode::Cbc, + encrypted_data.clone(), + initial_value, + ) + }) + .unwrap(); + + // Now we have to un-pad the output. + if decrypted_data.is_empty() { + panic!("Should not be empty"); + } + + const AES_BLOCK_SIZE: usize = 16; + + // WARNING: Manually implemented pkcs7 follows. This has not been audited. Don't use this + // in production. + + let last_byte = decrypted_data.len() - 1; + let k_byte = decrypted_data[last_byte]; + // Since pkcs7 padding repeats this byte k times, we check that this byte + // is repeated as many times as expected. In theory we don't need this check + // but it's better to be defensive. + + eprintln!("{:?}", decrypted_data); + + if k_byte as usize > AES_BLOCK_SIZE { + eprintln!("{}", k_byte); + panic!("Invalid pad byte, exceeds AES_BLOCK_SIZE"); + } + + for i in 0..k_byte { + if decrypted_data[last_byte - i as usize] != k_byte { + panic!("Invalid pad byte, is not equal to k_byte"); + } + } + + let truncate_to = decrypted_data.len().checked_sub(k_byte as usize).unwrap(); + let mut decrypted_data = decrypted_data.to_vec(); + decrypted_data.truncate(truncate_to); + + // END WARNING + + println!("data_to_encrypt = {:?}", data_to_encrypt); + println!("decrypted_data = {:?}", decrypted_data); + // They are the same! + assert_eq!(data_to_encrypt, decrypted_data); +} + +fn create_primary(context: &mut Context) -> CreatePrimaryKeyResult { + let object_attributes = ObjectAttributesBuilder::new() + .with_fixed_tpm(true) + .with_fixed_parent(true) + .with_st_clear(false) + .with_sensitive_data_origin(true) + .with_user_with_auth(true) + .with_decrypt(true) + .with_restricted(true) + .build() + .expect("Failed to build object attributes"); + + let primary_pub = PublicBuilder::new() + .with_public_algorithm(PublicAlgorithm::SymCipher) + .with_name_hashing_algorithm(HashingAlgorithm::Sha256) + .with_object_attributes(object_attributes) + .with_symmetric_cipher_parameters(SymmetricCipherParameters::new( + SymmetricDefinitionObject::AES_128_CFB, + )) + .with_symmetric_cipher_unique_identifier(Digest::default()) + .build() + .unwrap(); + + context + .execute_with_nullauth_session(|ctx| { + ctx.create_primary(Hierarchy::Owner, primary_pub, None, None, None, None) + }) + .unwrap() +} + + +// In the future I would probably do this where the object is actually a +// stored context that is loaded/unloaded as required. We would also check +// the handle is suitable for this purpose etc. +struct TpmAes128CbcEnc<'a> { + cipher: TpmEnc<'a>, +} + +struct TpmEnc<'a> { + ctx: &'a mut Context, + handle: KeyHandle, + iv: InitialValue, +} + +impl<'a> cipher::BlockSizeUser for TpmAes128CbcEnc<'a> { + type BlockSize = cipher::consts::U16; +} + +impl<'a> cipher::BlockEncryptMut for TpmAes128CbcEnc<'a> { + fn encrypt_with_backend_mut( + &mut self, + f: impl cipher::BlockClosure + ) { + // No errors? I'll raise an issue. + let Self { cipher } = self; + + f.call(cipher) + } +} + +impl<'a> cipher::BlockSizeUser for TpmEnc<'a> { + type BlockSize = cipher::consts::U16; +} + +impl<'a> cipher::ParBlocksSizeUser for TpmEnc<'a> { + // 1024 / 16 bytes + type ParBlocksSize = cipher::consts::U16; +} + +impl<'a> cipher::BlockBackend for TpmEnc<'a> { + fn proc_block(&mut self, mut block: cipher::inout::InOut<'_, '_, cipher::Block>) { + // TODO: Do we need to mutate current iv as we go? + let decrypt = false; + + let data_in = MaxBuffer::try_from(block.clone_in().to_vec()).unwrap(); + + eprintln!("data_in: {:?}", data_in); + + let (encrypted_data, initial_value) = self.ctx + .encrypt_decrypt_2( + self.handle, + decrypt, + SymmetricMode::Cbc, + data_in, + self.iv.clone(), + ) + .unwrap(); + + self.iv = initial_value; + + eprintln!("encrypted: {:?}", encrypted_data); + + block.get_out().copy_from_slice(encrypted_data.as_slice()); + } +} + +