From dfd8346a2282bd835a954040e47497d4a7505dd0 Mon Sep 17 00:00:00 2001 From: noah-pe <164938052+noah-pe@users.noreply.github.com> Date: Mon, 13 May 2024 15:21:29 +0200 Subject: [PATCH 001/121] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1e81de27..1de2ae9a 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Work in Progress +# Work in Progress (Knox) Not fully implemented yet From 0f582ce58c0a61ed1c6f835ef29ced41af6e6685 Mon Sep 17 00:00:00 2001 From: MasterChief06 <164910544+MasterChief06@users.noreply.github.com> Date: Fri, 17 May 2024 15:26:25 +0200 Subject: [PATCH 002/121] Add files via upload --- src/tpm/android/knox/key_handle.rs | 366 +++++++++++++++++++++++++++++ src/tpm/android/knox/provider.rs | 155 ++++++++++++ 2 files changed, 521 insertions(+) create mode 100644 src/tpm/android/knox/key_handle.rs create mode 100644 src/tpm/android/knox/provider.rs diff --git a/src/tpm/android/knox/key_handle.rs b/src/tpm/android/knox/key_handle.rs new file mode 100644 index 00000000..c438e398 --- /dev/null +++ b/src/tpm/android/knox/key_handle.rs @@ -0,0 +1,366 @@ +use super::TpmProvider; +use crate::{ + common::{error::SecurityModuleError, traits::key_handle::KeyHandle}, + tpm::core::error::TpmError, +}; +use tracing::instrument; +use windows::{ + core::PCWSTR, + Win32::Security::Cryptography::{ + BCryptCloseAlgorithmProvider, BCryptCreateHash, BCryptDestroyHash, BCryptFinishHash, + BCryptGetProperty, BCryptHashData, BCryptOpenAlgorithmProvider, NCryptDecrypt, + NCryptEncrypt, NCryptSignHash, NCryptVerifySignature, BCRYPT_ALG_HANDLE, + BCRYPT_HASH_HANDLE, BCRYPT_HASH_LENGTH, BCRYPT_OBJECT_LENGTH, + BCRYPT_OPEN_ALGORITHM_PROVIDER_FLAGS, NCRYPT_FLAGS, NCRYPT_PAD_PKCS1_FLAG, + }, +}; + +/// Provides cryptographic operations for asymmetric keys on Windows, +/// such as signing, encryption, decryption, and signature verification. +impl KeyHandle for TpmProvider { + /// Signs data using the cryptographic key. + /// + /// This method hashes the input data using SHA-256 and then signs the hash. + /// It leverages the NCryptSignHash function from the Windows CNG API. + /// + /// # Arguments + /// + /// * `data` - The data to be signed. + /// + /// # Returns + /// + /// A `Result` containing the signature as a `Vec` on success, or a `SecurityModuleError` on failure. + #[instrument] + fn sign_data(&self, data: &[u8]) -> Result, SecurityModuleError> { + // Open an algorithm provider for SHA-512 + let mut alg_handle: BCRYPT_ALG_HANDLE = self.hash.clone().unwrap().into(); + let hash_algo: PCWSTR = self.hash.clone().unwrap().into(); + + if unsafe { + BCryptOpenAlgorithmProvider( + &mut alg_handle, + hash_algo, + None, + BCRYPT_OPEN_ALGORITHM_PROVIDER_FLAGS(0), + ) + } + .is_err() + { + return Err(TpmError::Win(windows::core::Error::from_win32()).into()); + } + + // Get the size of the hash object + let mut hash_object_size: u32 = 0; + if unsafe { + BCryptGetProperty( + alg_handle, + BCRYPT_OBJECT_LENGTH, + None, + &mut hash_object_size, + 0, + ) + } + .is_err() + { + return Err(TpmError::Win(windows::core::Error::from_win32()).into()); + } + + let mut hash_object = Vec::with_capacity(hash_object_size as usize); + + // Get the length of the hash + let mut hash_length: u32 = 0; + if unsafe { BCryptGetProperty(alg_handle, BCRYPT_HASH_LENGTH, None, &mut hash_length, 0) } + .is_err() + { + return Err(TpmError::Win(windows::core::Error::from_win32()).into()); + } + + let mut hash_handle = BCRYPT_HASH_HANDLE::default(); + if unsafe { + BCryptCreateHash( + alg_handle, + &mut hash_handle, + Some(hash_object.as_mut_slice()), // Pass the hash object buffer + None, + 0, // Flags + ) + } + .is_err() + { + return Err(TpmError::Win(windows::core::Error::from_win32()).into()); + } + + // Hash the data + if unsafe { BCryptHashData(hash_handle, data, 0) }.is_err() { + return Err(TpmError::Win(windows::core::Error::from_win32()).into()); + } + + // Finalize the hash + let mut hash = Vec::with_capacity(hash_length as usize); + if unsafe { BCryptFinishHash(hash_handle, &mut hash, 0) }.is_err() { + return Err(TpmError::Win(windows::core::Error::from_win32()).into()); + } + + // Determine the size of the signature + let mut signature_size: u32 = 0; + if unsafe { + NCryptSignHash( + self.key_handle.as_ref(), + None, // No padding info + &hash, // Hash as a slice + None, // No signature buffer yet + &mut signature_size, // Pointer to receive the size of the signature + NCRYPT_PAD_PKCS1_FLAG, // Padding flag + ) + } + .is_err() + { + return Err(TpmError::Win(windows::core::Error::from_win32()).into()); + } + + // Allocate a buffer for the signature + let mut signature = Vec::with_capacity(signature_size as usize); + + // Sign the hash + if unsafe { + NCryptSignHash( + self.key_handle.as_ref(), + None, // No padding info + &hash, // Hash as a slice + Some(&mut signature), // Signature buffer as a mutable slice + &mut signature_size, // Pointer to receive the actual size of the signature + NCRYPT_PAD_PKCS1_FLAG, // Padding flag + ) + } + .is_err() + { + return Err(TpmError::Win(windows::core::Error::from_win32()).into()); + } + + // Resize the signature buffer to the actual size + signature.truncate(signature_size as usize); + + Ok(signature) + } + + /// Decrypts data encrypted with the corresponding public key. + /// + /// Utilizes the NCryptDecrypt function from the Windows CNG API. + /// + /// # Arguments + /// + /// * `encrypted_data` - The data to be decrypted. + /// + /// # Returns + /// + /// A `Result` containing the decrypted data as a `Vec` on success, or a `SecurityModuleError` on failure. + #[instrument] + fn decrypt_data(&self, encrypted_data: &[u8]) -> Result, SecurityModuleError> { + let mut decrypted_data_len: u32 = 0; + + // First, determine the size of the decrypted data without actually decrypting + if unsafe { + NCryptDecrypt( + self.key_handle.as_ref(), + Some(encrypted_data), // Pass encrypted data as an Option<&[u8]> + None, // Padding information as Option<*const c_void>, adjust based on your encryption scheme + None, // Initially, no output buffer to get the required size + &mut decrypted_data_len, // Receives the required size of the output buffer + NCRYPT_FLAGS(0), // Flags, adjust as necessary + ) + } + .is_err() + { + return Err(TpmError::Win(windows::core::Error::from_win32()).into()); + } + + // Allocate a buffer for the decrypted data + let mut decrypted_data = vec![0u8; decrypted_data_len as usize]; + + // Perform the actual decryption + if unsafe { + NCryptDecrypt( + self.key_handle.as_ref(), + Some(encrypted_data), // Again, pass encrypted data as an Option<&[u8]> + None, // Padding information as Option<*const c_void>, adjust based on your encryption scheme + Some(&mut decrypted_data), // Now provide the output buffer + &mut decrypted_data_len, // Receives the size of the decrypted data + NCRYPT_FLAGS(0), // Flags, adjust as necessary + ) + } + .is_err() + { + return Err(TpmError::Win(windows::core::Error::from_win32()).into()); + } + + // Resize the buffer to match the actual decrypted data length + decrypted_data.resize(decrypted_data_len as usize, 0); + + Ok(decrypted_data) + } + + /// Encrypts data with the cryptographic key. + /// + /// Uses the NCryptEncrypt function from the Windows CNG API. + /// + /// # Arguments + /// + /// * `data` - The data to be encrypted. + /// + /// # Returns + /// + /// A `Result` containing the encrypted data as a `Vec` on success, or a `SecurityModuleError` on failure. + #[instrument] + fn encrypt_data(&self, data: &[u8]) -> Result, SecurityModuleError> { + // First call to determine the size of the encrypted data + let mut encrypted_data_len: u32 = 0; + if unsafe { + NCryptEncrypt( + self.key_handle.as_ref(), + Some(data), // Input data as a slice + None, // Padding information, adjust based on your encryption scheme + None, // Initially, no output buffer to get the required size + &mut encrypted_data_len, // Receive the required size of the output buffer + NCRYPT_FLAGS(0), // Flags, adjust as necessary + ) + } + .is_err() + { + return Err(TpmError::Win(windows::core::Error::from_win32()).into()); + } + + // Allocate a buffer for the encrypted data + let mut encrypted_data = vec![0u8; encrypted_data_len as usize]; + + // Actual call to encrypt the data + if unsafe { + NCryptEncrypt( + self.key_handle.as_ref(), + Some(data), // Input data as a slice + None, // Padding information, adjust based on your encryption scheme + Some(&mut encrypted_data), // Provide the output buffer + &mut encrypted_data_len, // Receives the size of the encrypted data + NCRYPT_FLAGS(0), // Flags, adjust as necessary + ) + } + .is_err() + { + return Err(TpmError::Win(windows::core::Error::from_win32()).into()); + } + + // Resize the buffer to match the actual encrypted data length + encrypted_data.resize(encrypted_data_len as usize, 0); + + Ok(encrypted_data) + } + + /// Verifies a signature against the provided data. + /// + /// This method hashes the input data using SHA-256 and then verifies the signature. + /// It relies on the NCryptVerifySignature function from the Windows CNG API. + /// + /// # Arguments + /// + /// * `data` - The original data associated with the signature. + /// * `signature` - The signature to be verified. + /// + /// # Returns + /// + /// A `Result` indicating whether the signature is valid (`true`) or not (`false`), + /// or a `SecurityModuleError` on failure. + #[instrument] + fn verify_signature(&self, data: &[u8], signature: &[u8]) -> Result { + // Open an algorithm provider for SHA-256, just like in sign_data + let mut alg_handle = BCRYPT_ALG_HANDLE::default(); + let alg_id: PCWSTR = self.hash.clone().unwrap().into(); + + if unsafe { + BCryptOpenAlgorithmProvider( + &mut alg_handle, + alg_id, + None, + BCRYPT_OPEN_ALGORITHM_PROVIDER_FLAGS(0), + ) + } + .is_err() + { + return Err(TpmError::Win(windows::core::Error::from_win32()).into()); + } + + // Get the size of the hash object and hash length, just like in sign_data + let mut hash_object_size: u32 = 0; + if unsafe { + BCryptGetProperty( + alg_handle, + BCRYPT_OBJECT_LENGTH, + None, + &mut hash_object_size, + 0, + ) + } + .is_err() + { + return Err(TpmError::Win(windows::core::Error::from_win32()).into()); + } + + let mut hash_handle = BCRYPT_HASH_HANDLE::default(); + let mut hash_object = Vec::with_capacity(hash_object_size as usize); + let mut hash_length: u32 = 0; + + if unsafe { BCryptGetProperty(alg_handle, BCRYPT_HASH_LENGTH, None, &mut hash_length, 0) } + .is_err() + { + return Err(TpmError::Win(windows::core::Error::from_win32()).into()); + }; + + if unsafe { + BCryptCreateHash( + alg_handle, + &mut hash_handle, + Some(hash_object.as_mut_slice()), + None, + 0, + ) + } + .is_err() + { + return Err(TpmError::Win(windows::core::Error::from_win32()).into()); + } + + // Hash the data + if unsafe { BCryptHashData(hash_handle, data, 0) }.is_err() { + return Err(TpmError::Win(windows::core::Error::from_win32()).into()); + } + + // Finalize the hash + let mut hash = Vec::with_capacity(hash_length as usize); + if unsafe { BCryptFinishHash(hash_handle, &mut hash, 0) }.is_err() { + return Err(TpmError::Win(windows::core::Error::from_win32()).into()); + } + + // Verify the signature + let status = unsafe { + NCryptVerifySignature( + self.key_handle.as_ref(), + None, // No padding info + hash.as_slice(), + signature, + NCRYPT_PAD_PKCS1_FLAG, + ) + }; + + // Cleanup + if unsafe { BCryptDestroyHash(hash_handle) }.is_err() { + return Err(TpmError::Win(windows::core::Error::from_win32()).into()); + }; + if unsafe { BCryptCloseAlgorithmProvider(alg_handle, 0) }.is_err() { + return Err(TpmError::Win(windows::core::Error::from_win32()).into()); + }; + + // Check if the signature is valid + match status { + Ok(_) => Ok(true), + Err(_) => Ok(false), + } + } +} diff --git a/src/tpm/android/knox/provider.rs b/src/tpm/android/knox/provider.rs new file mode 100644 index 00000000..24098016 --- /dev/null +++ b/src/tpm/android/knox/provider.rs @@ -0,0 +1,155 @@ +use super::TpmProvider; +use crate::{ + common::{ + crypto::{ + algorithms::{ + encryption::{AsymmetricEncryption, BlockCiphers, EccSchemeAlgorithm}, + hashes::Hash, + }, + KeyUsage, + }, + error::SecurityModuleError, + traits::module_provider::Provider, + }, + tpm::core::error::TpmError, +}; +use tracing::instrument; +use windows::{ + core::PCWSTR, + Win32::Security::Cryptography::{ + NCryptCreatePersistedKey, NCryptFinalizeKey, NCryptOpenKey, NCryptOpenStorageProvider, + NCryptSetProperty, BCRYPT_ECDH_ALGORITHM, BCRYPT_ECDSA_ALGORITHM, CERT_KEY_SPEC, + MS_PLATFORM_CRYPTO_PROVIDER, NCRYPT_ALLOW_DECRYPT_FLAG, NCRYPT_ALLOW_SIGNING_FLAG, + NCRYPT_CERTIFICATE_PROPERTY, NCRYPT_FLAGS, NCRYPT_KEY_HANDLE, NCRYPT_KEY_USAGE_PROPERTY, + NCRYPT_LENGTH_PROPERTY, NCRYPT_MACHINE_KEY_FLAG, NCRYPT_OVERWRITE_KEY_FLAG, + NCRYPT_PROV_HANDLE, NCRYPT_SILENT_FLAG, + }, +}; +use std::error::Error; +use std::fmt; +use serde::de::Unexpected::Option; +use tss_esapi::constants::Tss2ResponseCodeKind::KeySize; +use tss_esapi::interface_types::algorithm::SymmetricAlgorithm; +use crate::common::crypto::algorithms::KeyBits; +use crate::tpm::linux::TpmProvider; + + +/// Implements the `Provider` trait, providing cryptographic operations utilizing a TPM. +/// +/// This implementation is specific to the Windows platform and utilizes the Windows CNG API +/// to interact with the Trusted Platform Module (TPM) for key management and cryptographic +/// operations. +impl Provider for TpmProvider { + /// Creates a new cryptographic key identified by `key_id`. + /// + /// This method creates a persisted cryptographic key using the specified algorithm + /// and identifier, making it retrievable for future operations. The key is created + /// with the specified key usages and stored in the TPM. + /// + /// # Arguments + /// + /// * `key_id` - A string slice that uniquely identifies the key to be created. + /// * `key_algorithm` - The asymmetric encryption algorithm to be used for the key. + /// * `sym_algorithm` - An optional symmetric encryption algorithm to be used with the key. + /// * `hash` - An optional hash algorithm to be used with the key. + /// * `key_usages` - A vector of `AppKeyUsage` values specifying the intended usages for the key. + /// + /// # Returns + /// + /// A `Result` that, on success, contains `Ok(())`, indicating that the key was created successfully. + /// On failure, it returns a `SecurityModuleError`. + #[instrument] + fn create_key(&mut self, key_id: &str) -> Result<(), SecurityModuleError> { + + Ok(()) + } + + /// Loads an existing cryptographic key identified by `key_id`. + /// + /// This method attempts to load a persisted cryptographic key by its identifier from the TPM. + /// If successful, it sets the key usages and returns a handle to the key for further + /// cryptographic operations. + /// + /// # Arguments + /// + /// * `key_id` - A string slice that uniquely identifies the key to be loaded. + /// * `key_algorithm` - The asymmetric encryption algorithm used for the key. + /// * `sym_algorithm` - An optional symmetric encryption algorithm used with the key. + /// * `hash` - An optional hash algorithm used with the key. + /// * `key_usages` - A vector of `AppKeyUsage` values specifying the intended usages for the key. + /// + /// # Returns + /// + /// A `Result` that, on success, contains `Ok(())`, indicating that the key was loaded successfully. + /// On failure, it returns a `SecurityModuleError`. + #[instrument] + fn load_key(&mut self, key_id: &str) -> Result<(), SecurityModuleError> { + + Ok(()) + } + + /// Initializes the TPM module and returns a handle for cryptographic operations. + /// + /// This method opens a storage provider using the Windows CNG API and wraps it in a + /// `WindowsProviderHandle`. This handle is used for subsequent cryptographic operations + /// with the TPM. + /// + /// # Returns + /// + /// A `Result` that, on success, contains `Ok(())`, indicating that the module was initialized successfully. + /// On failure, it returns a `SecurityModuleError`. + + + + fn initialize_module( + mut self, + key_algorithm: AsymmetricEncryption, + sym_algorithm: Option, + hash: Option, + key_usages: Vec, + ) -> Result<(), SecurityModuleError> { + + let asymString; + match key_algorithm { + AsymmetricEncryption::Rsa(bitslength) => { + match bitslength { + KeyBits::Bits128 => {asymString = String::from("RSA;128;HmacSHA1;PKCS1")} + KeyBits::Bits192 => {asymString = String::from("RSA;192;HmacSHA224;PKCS1")} + KeyBits::Bits256 => {asymString = String::from("RSA;256;HmacSHA384;PKCS1")} + KeyBits::Bits512 => {asymString = String::from("RSA;512;HmacSHA512;PKCS1")} + KeyBits::Bits1024 => {asymString = String::from("RSA;1024;HmacSHA512;PKCS1")} + KeyBits::Bits2048 => {asymString = String::from("RSA;2048;HmacSHA512;PKCS1")} + KeyBits::Bits3072 => {asymString = String::from("RSA;3072;HmacSHA512;PKCS1")} + KeyBits::Bits4096 => {asymString = String::from("RSA;4096;HmacSHA512;PKCS1")} + KeyBits::Bits8192 => {asymString = String::from("RSA;8192;HmacSHA512;PKCS1")} + } + } + _ => {return Err(SecurityModuleError::UnsupportedAlgorithm(format!("Unsupported asymmetric encryption algorithm:")))} + } + + let symString; + match sym_algorithm { + Option::Aes(bitslength) => { + match bitslength { + KeyBits::Bits128 => {symString = String::from("AES;128;GCM;NoPadding")}, + KeyBits::Bits128 => {symString = String::from("AES;128;ECB;PKCS7")}, + KeyBits::Bits128 => {symString = String::from("AES;128;CBC;PKCS7")}, + KeyBits::Bits128 => {symString = String::from("AES;128;CTR;PKCS7")}, + KeyBits::Bits192 => {symString = String::from("AES;192;GCM;NoPadding")}, + KeyBits::Bits192 => {symString = String::from("AES;192;ECB;PKCS7")}, + KeyBits::Bits192 => {symString = String::from("AES;192;CBC;PKCS7")}, + KeyBits::Bits192 => {symString = String::from("AES;192;CTR;PKCS7")}, + KeyBits::Bits256 => {symString = String::from("AES;256;GCM;NoPadding")}, + KeyBits::Bits256 => {symString = String::from("AES;256;ECB;PKCS7")}, + KeyBits::Bits256 => {symString = String::from("AES;256;CBC;PKCS7")}, + KeyBits::Bits256 => {symString = String::from("AES;256;CTR;PKCS7")}, + + } + }, + _ => {return Err(SecurityModuleError::UnsupportedAlgorithm(format!("Unsupported symmetric encryption algorithm:")))} + } + + Ok(()) + } + +} From f0acb0432d1d052c8af1e4da38a33d46d1ef7bbf Mon Sep 17 00:00:00 2001 From: Umut Date: Fri, 17 May 2024 15:42:06 +0200 Subject: [PATCH 003/121] clean up --- src/tpm/android/knox/provider.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tpm/android/knox/provider.rs b/src/tpm/android/knox/provider.rs index 24098016..9bdb3d09 100644 --- a/src/tpm/android/knox/provider.rs +++ b/src/tpm/android/knox/provider.rs @@ -141,7 +141,7 @@ impl Provider for TpmProvider { KeyBits::Bits192 => {symString = String::from("AES;192;CTR;PKCS7")}, KeyBits::Bits256 => {symString = String::from("AES;256;GCM;NoPadding")}, KeyBits::Bits256 => {symString = String::from("AES;256;ECB;PKCS7")}, - KeyBits::Bits256 => {symString = String::from("AES;256;CBC;PKCS7")}, + KeyBits::Bits256 => {symString = String::from("AES;256;CBC;PKCS7")}, KeyBits::Bits256 => {symString = String::from("AES;256;CTR;PKCS7")}, } From 8f742f5aac617c0d1ebb084f38cfeabb8786adad Mon Sep 17 00:00:00 2001 From: Umut Date: Fri, 17 May 2024 16:30:05 +0200 Subject: [PATCH 004/121] added DESede & changed Hashing algo. --- src/tpm/android/knox/provider.rs | 58 +++++++++++++++++++++++++++----- 1 file changed, 49 insertions(+), 9 deletions(-) diff --git a/src/tpm/android/knox/provider.rs b/src/tpm/android/knox/provider.rs index 9bdb3d09..2cbe95c1 100644 --- a/src/tpm/android/knox/provider.rs +++ b/src/tpm/android/knox/provider.rs @@ -94,10 +94,40 @@ impl Provider for TpmProvider { /// `WindowsProviderHandle`. This handle is used for subsequent cryptographic operations /// with the TPM. /// + /// # Parameters + /// + /// - `key_algorithm`: Specifies the asymmetric encryption algorithm to use. Supported algorithms include: + /// - `AsymmetricEncryption::Rsa`: RSA with key lengths specified by `KeyBits`. + /// - `sym_algorithm`: An optional parameter specifying the block cipher algorithm to use. Supported algorithms include: + /// - `BlockCiphers::Aes`: AES with key lengths specified by `KeyBits` and modes like GCM, ECB, CBC, CTR. + /// - `hash`: An optional parameter specifying the hash algorithm to use. + /// - `key_usages`: A vector specifying the purposes for which the key can be used (e.g., encrypt, decrypt, sign, verify). + /// /// # Returns /// /// A `Result` that, on success, contains `Ok(())`, indicating that the module was initialized successfully. /// On failure, it returns a `SecurityModuleError`. + /// + /// # Errors + /// + /// This function returns a `SecurityModuleError` in the following cases: + /// - If an unsupported asymmetric encryption algorithm is specified. + /// - If an unsupported symmetric encryption algorithm is specified. + /// + /// # Examples + /// + /// ```rust + /// let result = module.initialize_module( + /// AsymmetricEncryption::Rsa(KeyBits::Bits2048), + /// Some(BlockCiphers::Aes(KeyBits::Bits256)), + /// Some(Hash::Sha256), + /// vec![KeyUsage::Encrypt, KeyUsage::Decrypt], + /// ); + /// + /// match result { + /// Ok(()) => println!("Module initialized successfully"), + /// Err(e) => println!("Failed to initialize module: {:?}", e), + /// } @@ -113,19 +143,29 @@ impl Provider for TpmProvider { match key_algorithm { AsymmetricEncryption::Rsa(bitslength) => { match bitslength { - KeyBits::Bits128 => {asymString = String::from("RSA;128;HmacSHA1;PKCS1")} - KeyBits::Bits192 => {asymString = String::from("RSA;192;HmacSHA224;PKCS1")} - KeyBits::Bits256 => {asymString = String::from("RSA;256;HmacSHA384;PKCS1")} - KeyBits::Bits512 => {asymString = String::from("RSA;512;HmacSHA512;PKCS1")} - KeyBits::Bits1024 => {asymString = String::from("RSA;1024;HmacSHA512;PKCS1")} - KeyBits::Bits2048 => {asymString = String::from("RSA;2048;HmacSHA512;PKCS1")} - KeyBits::Bits3072 => {asymString = String::from("RSA;3072;HmacSHA512;PKCS1")} - KeyBits::Bits4096 => {asymString = String::from("RSA;4096;HmacSHA512;PKCS1")} - KeyBits::Bits8192 => {asymString = String::from("RSA;8192;HmacSHA512;PKCS1")} + KeyBits::Bits128 => {asymString = String::from("RSA;128;HmacSHA-1;PKCS1")}, + KeyBits::Bits192 => {asymString = String::from("RSA;192;HmacSHA-224;PKCS1")}, + KeyBits::Bits256 => {asymString = String::from("RSA;256;HmacSHA-384;PKCS1")}, + KeyBits::Bits512 => {asymString = String::from("RSA;512;HmacSHA-512;PKCS1")}, + KeyBits::Bits1024 => {asymString = String::from("RSA;1024;HmacSHA-512;PKCS1")}, + KeyBits::Bits2048 => {asymString = String::from("RSA;2048;HmacSHA-512;PKCS1")}, + KeyBits::Bits3072 => {asymString = String::from("RSA;3072;HmacSHA-512;PKCS1")}, + KeyBits::Bits4096 => {asymString = String::from("RSA;4096;HmacSHA-512;PKCS1")}, + KeyBits::Bits8192 => {asymString = String::from("RSA;8192;HmacSHA-512;PKCS1")}, } } _ => {return Err(SecurityModuleError::UnsupportedAlgorithm(format!("Unsupported asymmetric encryption algorithm:")))} } + let symString; + match sym_algorithm { + Option::DESede(bitslength) => { + match bitslength { + KeyBits::Bits128 => {symString = String::from("DESede;CBC;PKCS7,PKCS5Padding")}, + KeyBits::Bits128 => {symString = String::from("DESede;ECB;PKCS7,NoPadding")}, + } + } + _ => {return Err(SecurityModuleError::UnsupportedAlgorithm(format!("Unsupported symmetric encryption algorithm:")))} + } let symString; match sym_algorithm { From 7f22487120d6d877f9bdb13ac2954b11b998ab77 Mon Sep 17 00:00:00 2001 From: ErikFribus Date: Mon, 27 May 2024 09:04:42 +0200 Subject: [PATCH 005/121] factory.rs incomplete match --- src/common/factory.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/factory.rs b/src/common/factory.rs index 6dd7d448..b5380e03 100644 --- a/src/common/factory.rs +++ b/src/common/factory.rs @@ -132,7 +132,7 @@ impl SecModule { SecurityModule::Hsm(hsm_type) => Some(HsmInstance::create_instance(key_id, hsm_type)), #[cfg(feature = "tpm")] SecurityModule::Tpm(tpm_type) => Some(TpmInstance::create_instance(key_id, tpm_type)), - // _ => unimplemented!(), + _ => unimplemented!(), } } } From 31ef15496d421ee1794696f1c6e0a72d4b112f16 Mon Sep 17 00:00:00 2001 From: segelnhoch3 Date: Mon, 27 May 2024 09:50:51 +0200 Subject: [PATCH 006/121] imported files from old repo --- Cargo.lock | 196 +++++++ Cargo.toml | 1 + src/tpm/android/knox/interface.rs | 536 +++++++++++++++++++ src/tpm/android/knox/java/CryptoManager.java | 460 ++++++++++++++++ src/tpm/android/knox/java/RustDef.java | 273 ++++++++++ 5 files changed, 1466 insertions(+) create mode 100644 src/tpm/android/knox/interface.rs create mode 100644 src/tpm/android/knox/java/CryptoManager.java create mode 100644 src/tpm/android/knox/java/RustDef.java diff --git a/Cargo.lock b/Cargo.lock index 1ddd42e3..3722b364 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,16 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "Inflector" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" +dependencies = [ + "lazy_static", + "regex", +] + [[package]] name = "aho-corasick" version = "1.1.3" @@ -242,12 +252,24 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +[[package]] +name = "bytes" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" + [[package]] name = "cc" version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2678b2e3449475e95b0aa6f9b506a28e61b3dc8996592b983695e8ebb58a8b41" +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + [[package]] name = "cfg-if" version = "1.0.0" @@ -264,6 +286,16 @@ dependencies = [ "inout", ] +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + [[package]] name = "concurrent-queue" version = "2.5.0" @@ -334,6 +366,7 @@ dependencies = [ "futures", "nitrokey", "once_cell", + "robusta_jni", "serde", "serde_json", "test-case", @@ -345,6 +378,41 @@ dependencies = [ "yubikey", ] +[[package]] +name = "darling" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b750cb3417fd1b327431a470f388520309479ab0bf5e323505daf0290cd3850" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "109c1ca6e6b7f82cc233a97004ea8ed7ca123a9af07a8230878fcfda9b158bf0" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 1.0.107", +] + +[[package]] +name = "darling_macro" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e" +dependencies = [ + "darling_core", + "quote", + "syn 1.0.107", +] + [[package]] name = "der" version = "0.7.9" @@ -543,6 +611,12 @@ version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdeb3aa5e95cf9aabc17f060cfa0ced7b83f042390760ca53bf09df9968acaa1" +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "futures" version = "0.3.30" @@ -746,6 +820,12 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f558a64ac9af88b5ba400d99b579451af0d39c6d360980045b91aac966d705e2" +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "inout" version = "0.1.3" @@ -781,6 +861,26 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +[[package]] +name = "jni" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6df18c2e3db7e453d3c6ac5b3e9d5182664d28788126d39b91f2d1e22b017ec" +dependencies = [ + "cesu8", + "combine", + "jni-sys", + "log", + "thiserror", + "walkdir", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + [[package]] name = "js-sys" version = "0.3.69" @@ -1027,6 +1127,12 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + [[package]] name = "pbkdf2" version = "0.12.2" @@ -1211,6 +1317,29 @@ dependencies = [ "elliptic-curve", ] +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + [[package]] name = "proc-macro2" version = "1.0.65" @@ -1307,6 +1436,33 @@ dependencies = [ "subtle", ] +[[package]] +name = "robusta-codegen" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffb512b451472948a204452dfad582bdc48d69caacdd3b1b4571d5e3f11707f3" +dependencies = [ + "Inflector", + "darling", + "proc-macro-error", + "proc-macro2", + "quote", + "rand", + "syn 1.0.107", +] + +[[package]] +name = "robusta_jni" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c080146e0cc733697fe500413871142af91bd879641205c2febbe5f982f304e3" +dependencies = [ + "jni", + "paste", + "robusta-codegen", + "static_assertions", +] + [[package]] name = "rsa" version = "0.9.6" @@ -1370,6 +1526,15 @@ version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "sec1" version = "0.7.3" @@ -1539,6 +1704,18 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + [[package]] name = "subtle" version = "2.5.0" @@ -1842,6 +2019,16 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "317211a0dc0ceedd78fb2ca9a44aed3d7b9b26f81870d485c07122b4350673b7" +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "wasi" version = "0.9.0+wasi-snapshot-preview1" @@ -1946,6 +2133,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" +dependencies = [ + "windows-sys 0.52.0", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" diff --git a/Cargo.toml b/Cargo.toml index 28009807..f95ada91 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,6 +37,7 @@ win = ["tpm", "windows"] yubi = ["hsm", "yubikey"] [dependencies] +robusta_jni = "0.2" anyhow = "*" async-std = "*" futures = "*" diff --git a/src/tpm/android/knox/interface.rs b/src/tpm/android/knox/interface.rs new file mode 100644 index 00000000..017e21a4 --- /dev/null +++ b/src/tpm/android/knox/interface.rs @@ -0,0 +1,536 @@ +use robusta_jni::bridge; + +#[bridge] +pub mod jni { + #[allow(unused_imports)] + use robusta_jni::bridge; + use robusta_jni::convert::{IntoJavaValue, Signature, TryFromJavaValue, TryIntoJavaValue}; + use robusta_jni::jni::errors::Error; + use robusta_jni::jni::JNIEnv; + use robusta_jni::jni::objects::{AutoLocal, JValue}; + use robusta_jni::jni::sys::jbyteArray; + use crate::SecurityModuleError; + + #[derive(Signature, TryIntoJavaValue, IntoJavaValue, TryFromJavaValue)] + #[package(com.example.vulcans_1limes)] + pub struct RustDef<'env: 'borrow, 'borrow> { + #[instance] + pub raw: AutoLocal<'env, 'borrow>, + } + + /// This Implementation provides the method declarations that are the interface for the JNI. + /// The first part are Rust-methods that can be called from other Java-classes, + /// while the second part contains Java-methods that can be called from Rust. + /// + /// All method signatures have to correspond to their counterparts in RustDef.java, with the + /// same method name and corresponding parameters according to this table: + /// | **Rust** | **Java** | + /// |----------------------------------------------------|-----------------------------------| + /// | i32 | int | + /// | bool | boolean | + /// | char | char | + /// | i8 | byte | + /// | f32 | float | + /// | f64 | double | + /// | i64 | long | + /// | i16 | short | + /// | String | String | + /// | Vec\ | ArrayList\ | + /// | Box\<[u8]\> | byte[] | + /// | [jni::JObject<'env>](jni::objects::JObject) | *(any Java object as input type)* | + /// | [jni::jobject](jni::sys::jobject) | *(any Java object as output)* | + /// |----------------------------------------------------------------------------------------| + #[allow(non_snake_case)] + impl<'env: 'borrow, 'borrow> RustDef<'env, 'borrow> { + + //------------------------------------------------------------------------------------------ + // Rust methods that can be called from Java + + ///Proof of concept - shows type conversion + /// DO NOT USE + pub extern "jni" fn special(mut input1: Vec, input2: i32) -> Vec { + input1.push(input2); + input1.push(42); + input1.iter().map(ToString::to_string).collect() + } + + ///Proof of concept method - shows callback from Rust to a java method + /// ONLY USE FOR TESTING + pub extern "jni" fn callRust(environment: &JNIEnv) -> String { + + //example usage of a java method call from rust + Self::create_key(environment, String::from("moin")).unwrap(); + String::from("Success") + } + + ///Demo method used to call functions in Rust from the Java app while testing + pub extern "jni" fn demoCreate(environment: &JNIEnv, key_id: String) -> () { + Self::create_key(environment, key_id).unwrap(); + } + + ///Demo method used to call functions in Rust from the Java app while testing + pub extern "jni" fn demoInit(environment: &JNIEnv, + key_algorithm: String, + sym_algorithm: String, + hash: String, + key_usages: String) + -> () { + let _ = Self::initialize_module(environment, key_algorithm, sym_algorithm, hash, key_usages); + } + + ///Demo method used to call functions in Rust from the Java app while testing + pub extern "jni" fn demoEncrypt(environment: &JNIEnv, data: Box<[u8]>) -> Box<[u8]> { + let result = Self::encrypt_data(environment, data.as_ref()) + .expect("Sign_data failed"); + result.into_boxed_slice() + } + + ///Demo method used to call functions in Rust from the Java app while testing + pub extern "jni" fn demoDecrypt(environment: &JNIEnv, data: Box<[u8]>) -> Box<[u8]> { + let result = Self::decrypt_data(environment, data.as_ref()); + return match result { + Ok(res) => { res.into_boxed_slice() } + Err(_) => { Vec::new().into_boxed_slice() } + }; + } + + ///Demo method used to call functions in Rust from the Java app while testing + pub extern "jni" fn demoSign(environment: &JNIEnv, data: Box<[u8]>) -> Box<[u8]> { + let result = Self::sign_data(environment, data.as_ref()) + .expect("Sign_data failed"); + result.into_boxed_slice() + } + + ///Demo method used to call functions in Rust from the Java app while testing + pub extern "jni" fn demoVerify(environment: &JNIEnv, data: Box<[u8]>) -> bool { + let result = Self::verify_signature(environment, data.as_ref(), data.as_ref()); + return match result { + Ok(value) => { value } + Err(_) => { false } + }; + } + + + //------------------------------------------------------------------------------------------ + // Java methods that can be called from rust + + ///Proof of concept method - shows callback from Rust to a java method + /// DO NOT USE + pub fn callback(environment: &JNIEnv) -> () { + //This calls a method in Java in the Class RustDef, with the method name "callback" + //and no arguments + environment.call_static_method( + "com/example/vulcans_limes/RustDef", + "callback", + "()V", + &[], + ).expect("Java func call failed"); + } + + /// Creates a new cryptographic key identified by `key_id`. + /// + /// This method generates a new cryptographic key within the TPM. + /// The key is made persistent and associated with the provided `key_id`. + /// + /// # Arguments + /// `key_id` - String that uniquely identifies the key so that it can be retrieved later + pub fn create_key(environment: &JNIEnv, key_id: String) -> Result<(), SecurityModuleError> { + let result = environment.call_static_method( + "com/example/vulcans_limes/RustDef", + "create_key", + "(Ljava/lang/String;)V", + &[JValue::from(environment.new_string(key_id).unwrap())], + ); + let _ = Self::check_java_exceptions(&environment); + return match result { + Ok(..) => Ok(()), + Err(e) => { + match e { + Error::WrongJValueType(_, _) => { + Err(SecurityModuleError::InitializationError( + String::from("Failed to create key: Wrong Arguments passed") + )) + } + Error::JavaException => { + Err(SecurityModuleError::InitializationError( + String::from("Failed to create key: Some exception occurred in Java. + Check console for details") + )) + } + _ => { + Err(SecurityModuleError::InitializationError( + String::from("Failed to call Java methods") + )) + } + } + } + }; + } + + /// Loads an existing cryptographic key identified by `key_id`. + /// + /// This method generates a new cryptographic key within the TPM. + /// The loaded key is associated with the provided `key_id`. + /// + /// # Arguments + /// `key_id` - String that uniquely identifies the key so that it can be retrieved later + pub fn load_key(environment: &JNIEnv, key_id: String) -> Result<(), SecurityModuleError> { + let result = environment.call_static_method( + "com/example/vulcans_limes/RustDef", + "create_key", + "(Ljava/lang/String;)V", + &[JValue::from(environment.new_string(key_id).unwrap())], + ); + let _ = Self::check_java_exceptions(&environment); + return match result { + Ok(..) => Ok(()), + Err(e) => { + match e { + Error::WrongJValueType(_, _) => { + Err(SecurityModuleError::InitializationError( + String::from("Failed to load key: Wrong Arguments passed") + )) + } + Error::JavaException => { + Err(SecurityModuleError::InitializationError( + String::from("Failed to load key: Some exception occurred in Java. + Check console for details") + )) + } + _ => { + Err(SecurityModuleError::InitializationError( + String::from("Failed to call Java methods") + )) + } + } + } + }; + } + + + /// Initializes the TPM module and returns a handle for further operations. + /// + /// This method initializes the TPM context and prepares it for use. It should be called + /// before performing any other operations with the TPM. + /// + /// # Arguments + /// + /// * `key_id` - A string slice that uniquely identifies the key to be loaded. + /// * `key_algorithm` - The asymmetric encryption algorithm used for the key. + /// * `sym_algorithm` - An optional symmetric encryption algorithm used with the key. + /// * `hash` - An optional hash algorithm used with the key. + /// * `key_usages` - A vector of `AppKeyUsage` values specifying the intended usages for the key. + /// + /// # Returns + /// + /// A `Result` that, on success, contains `()`, + /// indicating that the module was initialized successfully. + /// On failure, it returns an Error + pub fn initialize_module(environment: &JNIEnv, + key_algorithm: String, + sym_algorithm: String, + hash: String, + key_usages: String) + -> Result<(), SecurityModuleError> { + let result = environment.call_static_method( + "com/example/vulcans_limes/RustDef", + "initialize_module", + "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V", + &[JValue::from(environment.new_string(key_algorithm).unwrap()), + JValue::from(environment.new_string(sym_algorithm).unwrap()), + JValue::from(environment.new_string(hash).unwrap()), + JValue::from(environment.new_string(key_usages).unwrap())], + ); + let _ = Self::check_java_exceptions(&environment); + return match result { + Ok(..) => Ok(()), + Err(e) => { + match e { + Error::WrongJValueType(_, _) => { + Err(SecurityModuleError::InitializationError( + String::from("Failed to initialise Module: Wrong Arguments passed") + )) + } + Error::JavaException => { + Err(SecurityModuleError::InitializationError( + String::from("Failed to initialise Module: Some exception occurred in Java. + Check console for details") + )) + } + _ => { + Err(SecurityModuleError::InitializationError( + String::from("Failed to call Java methods") + )) + } + } + } + }; + } + + /// Signs the given data using the cryptographic key managed by the TPM provider. + /// + /// # Arguments + /// + /// * `data` - A byte slice representing the data to be signed. + /// + /// # Returns + /// + /// A `Result` containing the signature as a `Vec` on success, + /// or an `Error` on failure. + fn sign_data(environment: &JNIEnv, data: &[u8]) -> Result, SecurityModuleError> { + let result = environment.call_static_method( + "com/example/vulcans_limes/RustDef", + "sign_data", + "([B)[B", + &[JValue::from(environment.byte_array_from_slice(data).unwrap())], + ); + let _ = Self::check_java_exceptions(&environment); + return match result { + Ok(value) => { + let vector = Self::convert_to_Vec_u8(environment, value); + match vector { + Ok(v) => { Ok(v) } + Err(_) => {Err(SecurityModuleError::SigningError( + String::from("Failed to convert return type to rust-compatible format") + ))} + } + }, + Err(e) => { + match e { + Error::WrongJValueType(_, _) => { + Err(SecurityModuleError::SigningError( + String::from("Failed to sign data: Wrong Arguments passed") + )) + } + Error::JavaException => { + Err(SecurityModuleError::SigningError( + String::from("Failed to sign data: Some exception occurred in Java. + Check console for details") + )) + } + _ => { + Err(SecurityModuleError::SigningError( + String::from("Failed to call Java methods") + )) + } + } + } + }; + } + + /// Verifies the signature of the given data using the key managed by the TPM + /// + /// # Arguments + /// + /// * `data` - A byte slice representing the data whose signature is to be verified + /// * `signature` - A byte slice representing the signature to be verified. + /// + /// # Returns + /// + /// A `Result` containing a `bool` signifying whether the signature is valid, + /// or an `Error` on failure to determine the validity. + fn verify_signature(environment: &JNIEnv, data: &[u8], signature: &[u8]) -> Result { + let result = environment.call_static_method( + "com/example/vulcans_limes/RustDef", + "verify_signature", + "([B[B)Z", + &[JValue::from(environment.byte_array_from_slice(data).unwrap()), + JValue::from(environment.byte_array_from_slice(signature).unwrap())], + ); + let _ = Self::check_java_exceptions(&environment); + return match result { + Ok(res) => { + match res.z() { + Ok(value) => { Ok(value) } + Err(_) => { Err(SecurityModuleError::SignatureVerificationError( + String::from("Failed to convert return type to rust-compatible format") + )) } + } + } + Err(e) => { + match e { + Error::WrongJValueType(_, _) => { + Err(SecurityModuleError::SignatureVerificationError( + String::from("Failed to verify signature: Wrong Arguments passed") + )) + } + Error::JavaException => { + Err(SecurityModuleError::SignatureVerificationError( + String::from("Failed to verify signature: Some exception occurred in Java. + Check console for details") + )) + } + _ => { + Err(SecurityModuleError::SignatureVerificationError( + String::from("Failed to call Java methods") + )) + } + } + } + }; + } + + /// Encrypts the given data using the key managed by the TPM + /// + /// # Arguments + /// + /// * `data` - A byte slice representing the data to be encrypted. + /// + /// # Returns + /// + /// A `Result` containing the encrypted data as a `Vec` on success, + /// or an `Error` on failure. + fn encrypt_data(environment: &JNIEnv, data: &[u8]) -> Result, SecurityModuleError> { + let result = environment.call_static_method( + "com/example/vulcans_limes/RustDef", + "encrypt_data", + "([B)[B", + &[JValue::from(environment.byte_array_from_slice(data).unwrap())], + ); + let _ = Self::check_java_exceptions(&environment); + return match result { + Ok(value) => { + let vector = Self::convert_to_Vec_u8(environment, value); + match vector { + Ok(v) => { Ok(v) } + Err(_) => {Err(SecurityModuleError::EncryptionError( + String::from("Failed to convert return type to rust-compatible format") + ))} + } + }, + Err(e) => { + match e { + Error::WrongJValueType(_, _) => { + Err(SecurityModuleError::EncryptionError( + String::from("Failed to encrypt data: Wrong Arguments passed") + )) + } + Error::JavaException => { + Err(SecurityModuleError::EncryptionError( + String::from("Failed to encrypt data: Some exception occurred in Java. + Check console for details") + )) + } + _ => { + Err(SecurityModuleError::EncryptionError( + String::from("Failed to call Java methods") + )) + } + } + } + }; + } + + + /// Decrypts the given data using the key managed by the TPM + /// + /// # Arguments + /// + /// * `data` - A byte slice representing the data to be Decrypted. + /// + /// # Returns + /// + /// A `Result` containing the Decrypted data as a `Vec` on success, + /// or an `Error` on failure. + fn decrypt_data(environment: &JNIEnv, data: &[u8]) -> Result, SecurityModuleError> { + let result = environment.call_static_method( + "com/example/vulcans_limes/RustDef", + "decrypt_data", + "([B)[B", + &[JValue::from(environment.byte_array_from_slice(data).unwrap())], + ); + let _ = Self::check_java_exceptions(&environment); + return match result { + Ok(value) => { + let vector = Self::convert_to_Vec_u8(environment, value); + match vector { + Ok(v) => { Ok(v) } + Err(_) => {Err(SecurityModuleError::DecryptionError( + String::from("Failed to convert return type to rust-compatible format") + ))} + } + }, + Err(e) => { + match e { + Error::WrongJValueType(_, _) => { + Err(SecurityModuleError::DecryptionError( + String::from("Failed to decrypt data: Wrong Arguments passed") + )) + } + Error::JavaException => { + Err(SecurityModuleError::DecryptionError( + String::from("Failed to decrypt data: Some exception occurred in Java. + Check console for details") + )) + } + _ => { + Err(SecurityModuleError::DecryptionError( + String::from("Failed to call Java methods") + )) + } + } + } + }; + } + + //------------------------------------------------------------------------------------------ + // Utility Functions that are only used by other Rust functions. + // These functions have no relation to RustDef.java + + /// Converts a `JValue` representing a Java byte array (`jbyteArray`) to a Rust `Vec`. + /// + /// # Parameters + /// - `environment`: A reference to the JNI environment. This is required for JNI operations. + /// - `result`: The `JValue` that is expected to be a `jbyteArray`. + /// + /// # Returns + /// - `Ok(Vec)` if the conversion is successful. + /// - `Err(String)` if there is an error during the conversion process, with a description of the error. + /// + /// # Errors + /// This method can fail in the following cases: + /// - If there is a pending Java exception. In this case, an appropriate error message is returned. + /// - If the `JValue` cannot be converted to a `Vec`. + /// # Safety + /// Ensure that the `JValue` passed is indeed a `jbyteArray` to avoid undefined behavior or unexpected errors. + fn convert_to_Vec_u8(environment: &JNIEnv, result: JValue) -> Result, String> { + Self::check_java_exceptions(environment)?; + let output_array = result.l(); + let jobj; + match output_array { + Ok(o) => { jobj = o; } + Err(_) => { return Err(String::from("Type conversion from JValue to JObject failed")); } + } + let jobj = jobj.into_inner() as jbyteArray; + let output_vec = environment.convert_byte_array(jobj); + Self::check_java_exceptions(environment)?; + match output_vec { + Ok(v) => { Ok(v) } + Err(_) => { Err(String::from("Conversion from jbyteArray to Vec failed")) } + } + } + + /// Checks for any pending Java exceptions in the provided Java environment (`JNIEnv`). + /// If one is detected, it is printed to console and cleared so the program doesn't crash. + /// # Arguments + /// * `environment` - A reference to the Java environment (`JNIEnv`) + /// # Returns + /// * `Result<(), JniError>` - A Result type representing either success (if no exceptions + /// are found) or an error of type `JniError` + /// (if exceptions are found). + /// # Errors + /// This method may return an error of type `JniError` if: + /// * Any pending Java exceptions are found in the provided Java environment. + /// # Panics + /// This method does not panic under normal circumstances. + pub fn check_java_exceptions(environment: &JNIEnv) -> Result<(), String> { + if environment.exception_check().unwrap_or(true) { + let _ = environment.exception_describe(); + let _ = environment.exception_clear(); + return Err(String::from("A Java exception occurred, check console for details")); + } else { + Ok(()) + } + } + } +} \ No newline at end of file diff --git a/src/tpm/android/knox/java/CryptoManager.java b/src/tpm/android/knox/java/CryptoManager.java new file mode 100644 index 00000000..ee9bdd19 --- /dev/null +++ b/src/tpm/android/knox/java/CryptoManager.java @@ -0,0 +1,460 @@ +package com.example.vulcans_limes; + +import android.os.Build; +import android.security.keystore.KeyGenParameterSpec; +import android.security.keystore.KeyInfo; +import android.security.keystore.KeyProperties; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.KeyFactory; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.SecureRandom; +import java.security.Signature; +import java.security.SignatureException; +import java.security.UnrecoverableKeyException; +import java.security.cert.CertificateException; +import java.security.spec.InvalidKeySpecException; +import java.util.ArrayList; +import java.util.Arrays; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.KeyGenerator; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.SecretKey; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.GCMParameterSpec; +import javax.crypto.spec.IvParameterSpec; +import javax.security.auth.x500.X500Principal; + +public class CryptoManager { + // TODO: READ AND APPROVE JAVADOC + private static final String ANDROID_KEY_STORE = "AndroidKeyStore"; + private final KeyStore keyStore; + private String KEY_NAME; + private byte[] encryptCipher; + + /** + * Constructs a new instance of {@code CryptoManager} with the default Android KeyStore. + *

+ * This constructor initializes the {@code CryptoManager} with the default Android KeyStore. The Android KeyStore + * provides a secure storage facility for cryptographic keys and certificates. Upon construction, the key store is + * initialized, enabling the {@code CryptoManager} to interact with cryptographic keys securely stored on the + * Android device. If the initialization of the key store fails, a {@link KeyStoreException} is thrown, indicating + * issues with the key store setup process. + * + * @throws KeyStoreException if the KeyStore Provider does not exist or fails to initialize, indicating issues with + * the key store setup process. + */ + public CryptoManager() throws KeyStoreException { + keyStore = KeyStore.getInstance(ANDROID_KEY_STORE); + } + + /** + * Generates a new symmetric key and saves it into the Android KeyStore. + *

+ * This method initializes a new symmetric key for encryption and decryption purposes using the specified symmetric key algorithm. The key is stored in the Android KeyStore, leveraging the platform's secure storage capabilities. The key is configured to use GCM block mode and PKCS7 encryption padding, enhancing both performance and security. The method also ensures that the key is backed by the strong box feature, adding an extra layer of security against unauthorized access. + *

+ * Before generating the key, the method sets the key name using the provided {@code key_id}. Then, it loads the Android KeyStore and proceeds to generate the key with the specified properties. Finally, the generated key is saved in the KeyStore under the provided {@code key_id}. + * + * @param key_id The unique identifier under which the key will be stored in the KeyStore. + * @throws CertificateException if there is an issue loading the certificate chain. + * @throws IOException for I/O errors such as incorrect passwords. + * @throws NoSuchAlgorithmException if the generation algorithm does not exist or the keystore doesn't exist. + * @throws NoSuchProviderException if the provider does not exist. + * @throws InvalidAlgorithmParameterException for invalid or nonexistent parameters. + * @throws KeyStoreException if there is an error accessing the keystore. + */ + public void genKey(String key_id, String keyGenInfo) throws CertificateException, + IOException, NoSuchAlgorithmException, NoSuchProviderException, + InvalidAlgorithmParameterException, KeyStoreException { + String[] keyGenInfoArr = keyGenInfo.split(";"); + String KEY_ALGORITHM = keyGenInfoArr[0]; + int KEY_SIZE = Integer.parseInt(keyGenInfoArr[1]); + String BLOCKING = keyGenInfoArr[2]; + String PADDING = keyGenInfoArr[3]; + + KEY_NAME = key_id; + keyStore.load(null); + + // Check if a key with the given key_id already exists + if (keyStore.containsAlias(KEY_NAME)) { + throw new KeyStoreException("Key with name " + KEY_NAME + " already exists."); + } + + KeyGenerator keyGen = KeyGenerator.getInstance(KEY_ALGORITHM, ANDROID_KEY_STORE); + keyGen.init(new KeyGenParameterSpec.Builder(KEY_NAME, + KeyProperties.PURPOSE_ENCRYPT | + KeyProperties.PURPOSE_DECRYPT) + .setKeySize(KEY_SIZE) + .setBlockModes(BLOCKING) + .setEncryptionPaddings(PADDING) + .setIsStrongBoxBacked(true) + .build()); + keyGen.generateKey(); + } + + /** + * Encrypts the given data using a symmetric key stored in the Android KeyStore. + *

+ * This method takes plaintext data as input and encrypts it using a symmetric key retrieved from the Android KeyStore. + * The encryption process supports both GCM and non-GCM transformations. For GCM transformations, a new initialization vector (IV) + * is generated using a secure random number generator, and the IV is prepended to the ciphertext. The method initializes a + * {@link Cipher} instance with the appropriate transformation, loads the Android KeyStore, retrieves the symmetric key, and then + * initializes the cipher in encryption mode with the retrieved key and the generated IV. Finally, the plaintext data is encrypted + * using the cipher's {@code doFinal} method, and the resulting ciphertext (with the IV prepended in the case of GCM) is returned + * as a byte array. + * + * @param data The plaintext data to be encrypted, represented as a byte array. + * @return A byte array representing the encrypted data, with the IV prepended in the case of GCM mode. + * @throws NoSuchPaddingException if the requested padding scheme is not available. + * @throws NoSuchAlgorithmException if the requested algorithm is not available. + * @throws CertificateException if there is an issue loading the certificate chain. + * @throws IOException if there is an I/O error during the operation. + * @throws InvalidKeyException if the key cannot be cast to a SecretKey. + * @throws UnrecoverableKeyException if the key cannot be recovered from the keystore. + * @throws KeyStoreException if there is an error accessing the keystore. + * @throws IllegalBlockSizeException if the data length is invalid for the encryption algorithm. + * @throws BadPaddingException if the data could not be padded correctly for encryption. + * @throws InvalidKeySpecException if the key specification is invalid. + * @throws NoSuchProviderException if the requested security provider is not available. + * @throws InvalidAlgorithmParameterException if the algorithm parameters are invalid. + */ + public byte[] encryptData(byte[] data) throws NoSuchPaddingException, NoSuchAlgorithmException, + CertificateException, IOException, InvalidKeyException, UnrecoverableKeyException, + KeyStoreException, IllegalBlockSizeException, BadPaddingException, InvalidKeySpecException, + NoSuchProviderException, InvalidAlgorithmParameterException { + keyStore.load(null); + SecretKey secretKey = (SecretKey) keyStore.getKey(KEY_NAME, null); + String TRANSFORMATION = buildTransformation(secretKey); + Cipher cipher = Cipher.getInstance(TRANSFORMATION); + + if (TRANSFORMATION.contains("/GCM/")) { + byte[] iv = new byte[12]; // GCM standard IV size + SecureRandom secureRandom = new SecureRandom(); + secureRandom.nextBytes(iv); + GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(128, iv); // 128 is the recommended TagSize + cipher.init(Cipher.ENCRYPT_MODE, secretKey, gcmParameterSpec); + byte[] encryptedData = cipher.doFinal(data); + ByteBuffer byteBuffer = ByteBuffer.allocate(iv.length + encryptedData.length); + byteBuffer.put(iv); + byteBuffer.put(encryptedData); + return byteBuffer.array(); + } else { + cipher.init(Cipher.ENCRYPT_MODE, secretKey); + encryptCipher = cipher.getIV(); + return cipher.doFinal(data); + } + } + + /** + * Decrypts the given encrypted data using a symmetric key stored in the Android KeyStore. + *

+ * This method takes encrypted data as input and decrypts it using a symmetric key retrieved from the Android KeyStore. + * The decryption process supports both GCM and non-GCM transformations. For GCM transformations, the initialization vector (IV) + * is extracted from the beginning of the encrypted data. The method initializes a {@link Cipher} instance with the appropriate + * transformation, loads the Android KeyStore, retrieves the symmetric key, and initializes the cipher in decryption mode with the + * retrieved key and the extracted IV. Finally, the encrypted data is decrypted using the cipher's {@code doFinal} method, and the + * original plaintext data is returned as a byte array. + * + * @param encryptedData The encrypted data to be decrypted, represented as a byte array. + * @return A byte array representing the decrypted data. + * @throws NoSuchPaddingException if the requested padding scheme is not available. + * @throws NoSuchAlgorithmException if the requested algorithm is not available. + * @throws CertificateException if there is an issue loading the certificate chain. + * @throws IOException if there is an I/O error during the operation. + * @throws InvalidAlgorithmParameterException if the IV parameter is invalid. + * @throws InvalidKeyException if the key cannot be cast to a SecretKey. + * @throws UnrecoverableKeyException if the key cannot be recovered from the keystore. + * @throws KeyStoreException if there is an error accessing the keystore. + * @throws IllegalBlockSizeException if the data length is invalid for the decryption algorithm. + * @throws BadPaddingException if the data could not be padded correctly for decryption. + * @throws InvalidKeySpecException if the key specification is invalid. + * @throws NoSuchProviderException if the requested security provider is not available. + */ + public byte[] decryptData(byte[] encryptedData) throws NoSuchPaddingException, NoSuchAlgorithmException, + CertificateException, IOException, InvalidAlgorithmParameterException, InvalidKeyException, + UnrecoverableKeyException, KeyStoreException, IllegalBlockSizeException, BadPaddingException, + InvalidKeySpecException, NoSuchProviderException { + keyStore.load(null); + SecretKey secretKey = (SecretKey) keyStore.getKey(KEY_NAME, null); + String TRANSFORMATION = buildTransformation(secretKey); + Cipher cipher = Cipher.getInstance(TRANSFORMATION); + + if (TRANSFORMATION.contains("/GCM/")) { + ByteBuffer byteBuffer = ByteBuffer.wrap(encryptedData); + byte[] iv = new byte[12]; // GCM standard IV size + byteBuffer.get(iv); + encryptedData = new byte[byteBuffer.remaining()]; + byteBuffer.get(encryptedData); + GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(128, iv); // 128 is the recommended TagSize + cipher.init(Cipher.DECRYPT_MODE, secretKey, gcmParameterSpec); + } else { + cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(encryptCipher)); + } + return cipher.doFinal(encryptedData); + } + + /** + * Generates a new asymmetric key pair and saves it into the Android KeyStore. + *

+ * This method generates a new asymmetric key pair suitable for signing and verifying digital signatures. The key pair is stored in the Android KeyStore, leveraging the platform's secure storage capabilities. The method configures the key pair generator with specific parameters, including the subject of the certificate associated with the key pair, the digest algorithms to be supported, the signature padding scheme, and whether the key is backed by the strong box feature for enhanced security. The generated key pair consists of a private key for signing and a corresponding public key for verification. + *

+ * Before generating the key pair, the method sets the key name using the provided {@code key_id}. Then, it loads the Android KeyStore and checks if a key with the specified {@code key_id} already exists. If it does, a {@link KeyStoreException} is thrown. If not, it proceeds to generate the key pair with the specified properties and saves it in the KeyStore under the provided {@code key_id}. + * + * @param key_id The unique identifier under which the key pair will be stored in the KeyStore. + * @throws CertificateException if there is an issue creating the certificate for the key pair. + * @throws IOException for I/O errors such as incorrect passwords. + * @throws NoSuchAlgorithmException if the generation algorithm does not exist or the keystore doesn't exist. + * @throws InvalidAlgorithmParameterException for invalid or nonexistent parameters. + * @throws NoSuchProviderException if the provider does not exist. + * @throws KeyStoreException if there is an error accessing the keystore or the key name is already used. + */ + public void generateKeyPair(String key_id, String keyGenInfo) throws CertificateException, IOException, + NoSuchAlgorithmException, InvalidAlgorithmParameterException, NoSuchProviderException, + KeyStoreException { + String[] keyGenInfoArr = keyGenInfo.split(";"); + String KEY_ALGORITHM = keyGenInfoArr[0]; + int KEY_SIZE = Integer.parseInt(keyGenInfoArr[1]); + String HASH = keyGenInfoArr[2]; + String PADDING = keyGenInfoArr[3]; + + KEY_NAME = key_id; + keyStore.load(null); + + // Check if a key with the given key_id already exists + if (keyStore.containsAlias(KEY_NAME)) { + throw new KeyStoreException("Key with name " + KEY_NAME + " already exists."); + } + + KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(KEY_ALGORITHM, ANDROID_KEY_STORE); + keyPairGen.initialize(new KeyGenParameterSpec.Builder(KEY_NAME, + KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY) + .setCertificateSubject(new X500Principal("CN=" + KEY_NAME)) + .setAttestationChallenge(null) + .setKeySize(KEY_SIZE) + .setDigests(HASH) + .setSignaturePaddings(PADDING) + .setUserAuthenticationRequired(false) + .setIsStrongBoxBacked(true) + .build()); + keyPairGen.generateKeyPair(); + } + + /** + * Signs the given data using a private key stored in the Android KeyStore. + *

+ * This method takes plaintext data as input and signs it using a private key retrieved from the Android KeyStore. The signing process uses a predefined signature algorithm. The method initializes a {@link Signature} instance with this algorithm, loads the Android KeyStore, retrieves the private key, and then initializes the signature object in sign mode with the retrieved private key. The plaintext data is then updated into the signature object, and finally, the data is signed using the signature object's {@code sign} method. The resulting signature is returned as a byte array. + *

+ * Signing data is a critical part of many cryptographic operations, particularly in establishing the integrity and authenticity of data. It allows recipients to verify that the data has not been tampered with and was indeed sent by the holder of the corresponding private key. + * + * @param data The plaintext data to be signed, represented as a byte array. + * @return A byte array representing the signature of the data. + * @throws NoSuchAlgorithmException if the requested algorithm is not available. + * @throws UnrecoverableKeyException if the key cannot be recovered from the keystore. + * @throws KeyStoreException if there is an error accessing the keystore. + * @throws InvalidKeyException if the key cannot be cast to a PrivateKey. + * @throws SignatureException if the signature cannot be processed. + */ + public byte[] signData(byte[] data) throws NoSuchAlgorithmException, UnrecoverableKeyException, + KeyStoreException, InvalidKeyException, SignatureException, InvalidKeySpecException, NoSuchProviderException { + Signature signature = Signature.getInstance(buildSignatureAlgorithm((PrivateKey) keyStore.getKey(KEY_NAME, null))); + signature.initSign((PrivateKey) keyStore.getKey(KEY_NAME, null)); + signature.update(data); + return signature.sign(); + } + + /** + * Verifies the signature of the given data using a public key stored in the Android KeyStore. + *

+ * This method verifies the signature of the given data against a known signature. The verification process + * uses a predefined signature algorithm. The method initializes a {@link Signature} instance with this algorithm, + * loads the Android KeyStore, retrieves the public key associated with the known signature, and then initializes + * the signature object in verify mode with the retrieved public key. The plaintext data is then updated into the + * signature object, and finally, the signature is verified using the signature object's {@code verify} method with + * the provided signed bytes. The method returns true if the signature is valid, indicating that the data has not + * been tampered with and was indeed signed by the holder of the corresponding private key; otherwise, it returns false. + * + * @param data The plaintext data whose signature is to be verified, represented as a byte array. + * @param signedBytes The signature of the data to be verified, represented as a byte array. + * @return True if the signature is valid, false otherwise. + * @throws SignatureException if the signature cannot be processed. + * @throws InvalidKeyException if the key cannot be cast to a PublicKey. + * @throws KeyStoreException if there is an error accessing the keystore. + * @throws NoSuchAlgorithmException if the requested algorithm is not available. + * @throws UnrecoverableKeyException if the key cannot be recovered from the keystore. + * @throws InvalidKeySpecException if the key specification is invalid or cannot be retrieved. + * @throws NoSuchProviderException if the provider is not available. + */ + public boolean verifySignature(byte[] data, byte[] signedBytes) throws SignatureException, InvalidKeyException, + KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException, InvalidKeySpecException, NoSuchProviderException { + Signature verificationSignature = Signature.getInstance(buildSignatureAlgorithm((PrivateKey) keyStore.getKey(KEY_NAME, null))); + verificationSignature.initVerify(keyStore.getCertificate(KEY_NAME).getPublicKey()); + verificationSignature.update(data); + return verificationSignature.verify(signedBytes); + } + + /** + * Converts a primitive byte array to an array of Byte objects. + *

+ * This method takes a primitive byte array as input and returns an array of Byte objects. + * Each element in the input array is wrapped in a Byte object. + * + * @param bytesPrim The primitive byte array to be converted. + * @return An array of Byte objects corresponding to the elements of the input byte array. + * @throws NullPointerException if the input byte array is null. + */ + public Byte[] toByte(byte[] bytesPrim) { + if (bytesPrim == null) { + throw new NullPointerException("Input byte array cannot be null."); + } + Byte[] bytes = new Byte[bytesPrim.length]; + Arrays.setAll(bytes, n -> bytesPrim[n]); + return bytes; + } + + /** + * Sets the `KEY_NAME` to the provided key identifier. + *

+ * This method assigns the `KEY_NAME` field with the given `key_id`. + * This is typically used before loading a key with the specified identifier. + * It ensures that the `KEY_NAME` is set correctly for subsequent cryptographic operations + * involving the specified key. + * + * @param key_id The unique identifier of a key to be set as `KEY_NAME`. + */ + public void loadKey(String key_id) throws KeyStoreException, UnrecoverableKeyException { + if (keyStore.containsAlias(key_id)) KEY_NAME = key_id; + else + throw new UnrecoverableKeyException("The key alias '" + key_id + "' does not exist in the KeyStore."); + } + + /** + * Constructs the transformation string for a given key, which is used to initialize a {@link Cipher} instance. + *

+ * This method loads the Android KeyStore and retrieves key-specific metadata using {@link KeyInfo}. From those it builds a transformation string based on the key's algorithm, block modes, and padding schemes. The transformation + * string follows the format "algorithm/block-mode/padding". It supports both symmetric keys ({@link SecretKey}) and asymmetric keys + * ({@link PrivateKey}). For symmetric keys, it retrieves encryption paddings; for asymmetric keys, it retrieves signature paddings. + * + * @param key The key for which the transformation string is to be built. It can be either a {@link SecretKey} or a {@link PrivateKey}. + * @return A string representing the transformation in the format "algorithm/mode/padding". + * @throws NullPointerException if the key or any retrieved metadata is null. + * @throws CertificateException if there is an issue with the certificate chain. + * @throws IOException if there is an I/O error during the operation. + * @throws NoSuchAlgorithmException if the requested algorithm is not available. + * @throws InvalidKeySpecException if the key specification is invalid. + * @throws NoSuchProviderException if the requested security provider is not available. + * @throws KeyStoreException if there is an error accessing the keystore or if the key type is unsupported. + */ + private String buildTransformation(Key key) throws NullPointerException, CertificateException, + IOException, NoSuchAlgorithmException, InvalidKeySpecException, NoSuchProviderException, KeyStoreException { + keyStore.load(null); + KeyInfo keyInfo; + String keyAlgorithm = key.getAlgorithm(); + String keyPadding = ""; + + if (key instanceof SecretKey) { + SecretKey secretKey = (SecretKey) key; + SecretKeyFactory factory = SecretKeyFactory.getInstance(secretKey.getAlgorithm(), ANDROID_KEY_STORE); + keyInfo = (KeyInfo) factory.getKeySpec(secretKey, KeyInfo.class); + keyPadding = keyInfo.getEncryptionPaddings()[0]; + } else if (key instanceof PrivateKey) { + PrivateKey privateKey = (PrivateKey) key; + KeyFactory factory = KeyFactory.getInstance(privateKey.getAlgorithm(), ANDROID_KEY_STORE); + keyInfo = factory.getKeySpec(privateKey, KeyInfo.class); + keyPadding = keyInfo.getSignaturePaddings()[0]; + } else { + throw new KeyStoreException("Unsupported key type"); + } + return keyAlgorithm + "/" + keyInfo.getBlockModes()[0] + "/" + keyPadding; + } + + /** + * Constructs the signature algorithm string based on the provided private key. + *

+ * This method retrieves metadata from the given {@link PrivateKey} to dynamically construct + * the signature algorithm string. It uses the {@link KeyFactory} to obtain the {@link KeyInfo} + * of the private key, which includes details such as the digest algorithms supported by the key. + * The method then combines the hash algorithm and the private key algorithm to form the signature + * algorithm string. + *

+ * + * @param privateKey The {@link PrivateKey} for which the signature algorithm string is to be constructed. + * @return A string representing the signature algorithm, which combines the hash algorithm and the key algorithm. + * @throws NoSuchAlgorithmException If the algorithm of the private key is not available. + * @throws NoSuchProviderException If the specified provider is not available. + * @throws InvalidKeySpecException If the key specification is invalid or cannot be retrieved. + */ + private String buildSignatureAlgorithm(PrivateKey privateKey) throws NoSuchAlgorithmException, NoSuchProviderException, + InvalidKeySpecException { + KeyFactory keyFactory = KeyFactory.getInstance(privateKey.getAlgorithm(), ANDROID_KEY_STORE); + KeyInfo keyInfo = keyFactory.getKeySpec(privateKey, KeyInfo.class); + String hashAlgorithm = keyInfo.getDigests()[0]; + String algorithm = privateKey.getAlgorithm(); + return hashAlgorithm + "with" + algorithm; + } + + /** + * Prints detailed information about the currently loaded key. + *

+ * This method retrieves and displays information about a key stored in the Android KeyStore. + * It handles both {@link SecretKey} (symmetric keys) and {@link KeyPair} (asymmetric keys). + * The information displayed includes the key ID, block modes, security level, origin, and purpose + * for {@link SecretKey}. For {@link KeyPair}, it displays the key algorithm and format. + *

+ * Note that this method should be used for testing or demonstration purposes and will be removed + * in the release version. + * + * @throws NullPointerException if the specified key does not exist. + * @throws CertificateException if there is an issue loading the certificate chain. + * @throws IOException if there is an I/O error during the operation. + * @throws NoSuchAlgorithmException if the requested algorithm is not available. + * @throws InvalidKeySpecException if the key specification is invalid. + * @throws NoSuchProviderException if the specified provider is not available. + * @throws UnrecoverableKeyException if the key cannot be recovered from the keystore. + * @throws KeyStoreException if there is an error accessing the keystore. + */ // TODO: DELETE BEFORE RELEASE + public void showKeyInfo() throws NullPointerException, CertificateException, IOException, + NoSuchAlgorithmException, InvalidKeySpecException, NoSuchProviderException, UnrecoverableKeyException, + KeyStoreException { + keyStore.load(null); + Key key = keyStore.getKey(KEY_NAME, null); + KeyInfo keyInfo; + + if (key instanceof SecretKey) { + SecretKey secretKey = (SecretKey) key; + SecretKeyFactory factory = SecretKeyFactory.getInstance(secretKey.getAlgorithm(), ANDROID_KEY_STORE); + keyInfo = (KeyInfo) factory.getKeySpec(secretKey, KeyInfo.class); + System.out.println("Key algorithm: " + secretKey.getAlgorithm()); + } else if (key instanceof PrivateKey) { + KeyPair keyPair = new KeyPair(keyStore.getCertificate(KEY_NAME).getPublicKey(), (PrivateKey) key); + KeyFactory factory = KeyFactory.getInstance(keyPair.getPrivate().getAlgorithm(), ANDROID_KEY_STORE); + keyInfo = factory.getKeySpec(keyPair.getPrivate(), KeyInfo.class); + System.out.println("Key algorithm: " + keyPair.getPrivate().getAlgorithm()); + } else { + throw new KeyStoreException("Unsupported key type"); + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + System.out.println("KeyID: " + keyInfo.getKeystoreAlias() + + "\nKey padding: " + Arrays.toString(keyInfo.getEncryptionPaddings()) + + "\nKey size: " + keyInfo.getKeySize() + + "\nBlock modes: " + Arrays.toString(keyInfo.getBlockModes()) + + "\nSecurity-Level: " + keyInfo.getSecurityLevel() + + "\nOrigin: " + keyInfo.getOrigin() + + "\nPurpose: " + keyInfo.getPurposes()); + } + } +} \ No newline at end of file diff --git a/src/tpm/android/knox/java/RustDef.java b/src/tpm/android/knox/java/RustDef.java new file mode 100644 index 00000000..90cbc066 --- /dev/null +++ b/src/tpm/android/knox/java/RustDef.java @@ -0,0 +1,273 @@ +package com.example.vulcans_limes; + + +import java.io.IOException; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.SignatureException; +import java.security.UnrecoverableKeyException; +import java.security.cert.CertificateException; +import java.security.spec.InvalidKeySpecException; +import java.util.ArrayList; + +import javax.crypto.BadPaddingException; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; + +/** + * This class provides the method declarations that are the interface for the JNI. + * The first part are Rust-methods that can be called from other Java-classes, + * while the second part contains full Java-methods that can be called from Rust. + *

+ * This class also loads the compiled Rust coda as a dynamic library + *

+ * All methods defined in this class hava to have a corresponding method defined in lib.rs, + * with the same method name and corresponding input and output parameters, according to this table: + *

+ * Rust Java + * ------------------------ + * i32 int + * bool boolean + * char char + * i8 byte + * f32 float + * f64 double + * i64 long + * i16 short + * String String + * Vec ArrayList + * Box<[u8]> byte[] + * jni::JObject<'env> (any Java object as input type) + * jni::jobject (any Java object as output) + * + * @noinspection unused - Methods called from Rust are not recognized as being in use + */ +class RustDef { + + /* + CryptoManger object for execution of methods + */ + static CryptoManager cryptoManager; + + static { + // This call loads the dynamic library containing the Rust code. + System.loadLibrary("vulcanslimes"); + } + + //---------------------------------------------------------------------------------------------- + //Rust methods that can be called from Java + + /** + * Proof of concept - shows type conversion + * DO NOT USE + */ + static native ArrayList special(ArrayList input1, int input2); + + /** + * Proof of concept method - shows callback from Rust to a java method + * ONLY USE FOR TESTING + */ + static native String callRust(); + + /** + * Is called to start all demo method calls from the Rust side + * --temporary-- + */ + static native byte[] demoEncrypt(byte[] data); + + static native void demoCreate(String key_id, String keyGenInfo); + + static native void demoInit(); + + + static native byte[] demoDecrypt(byte[] data); + + static native byte[] demoSign(byte[] data); + + static native boolean demoVerify(byte[] data); + + static native void demoLoad(String key_id); + + //---------------------------------------------------------------------------------------------- + //Java methods that can be called from Rust + + /* + Proof of concept method - get called from Rust when callRust() gets called + DO NOT USE + */ + static void callback() { + System.out.println("Callback successful"); + } + + /** + * Creates a new cryptographic key identified by {@code key_id}. + *

+ * This method generates a new cryptographic key within the TPM. The key is made persistent + * and associated with the provided {@code key_id}, which uniquely identifies the key + * so that it can be retrieved later for cryptographic operations. + * + * @param key_id a String that uniquely identifies the key to be created within the TPM. + * @param keyGenInfo additional information required for key generation, specifying parameters such as + * key size, algorithm, or other configuration details. + * @throws InvalidAlgorithmParameterException if the specified key generation parameters are invalid or + * incompatible with the key generation process. + * @throws CertificateException if there is an issue with certificate handling, such as failures + * in certificate creation or validation. + * @throws IOException if an I/O error occurs during key generation or processing. + * @throws NoSuchAlgorithmException if the requested cryptographic algorithm for key generation is + * not available or supported. + * @throws KeyStoreException if there is an error accessing the keystore, such as a failure + * to store the newly generated key. + * @throws NoSuchProviderException if the requested security provider is not available or supported. + */ + static void create_key(String key_id, String keyGenInfo) throws InvalidAlgorithmParameterException, CertificateException, + IOException, NoSuchAlgorithmException, KeyStoreException, NoSuchProviderException { + if (keyGenInfo.contains("RSA")) cryptoManager.generateKeyPair(key_id, keyGenInfo); + else cryptoManager.genKey(key_id, keyGenInfo); + } + + /** + * Loads an existing cryptographic key identified by {@code key_id}. + *

+ * This method loads an existing cryptographic key from the TPM. The loaded key is + * associated with the provided {@code key_id}, which uniquely identifies the key + * so that it can be retrieved later for cryptographic operations. + * + * @param key_id a String that uniquely identifies the key to be loaded from the TPM. + * @throws UnrecoverableKeyException if the key cannot be recovered from the keystore, typically due to + * incorrect or inaccessible key information. + * @throws KeyStoreException if there is an error accessing the keystore, such as a failure to load + * or initialize the keystore. + */ + static void load_key(String key_id) throws UnrecoverableKeyException, KeyStoreException { + cryptoManager.loadKey(key_id); + } + + /** + * Initializes the TPM (Trusted Platform Module) module and returns a handle for further operations. + *

+ * This method initializes the TPM context and prepares it for use. It should be called before performing + * any other operations with the TPM. Upon initialization, it sets up the necessary configurations and + * resources required for cryptographic operations involving the TPM. + * + * @throws KeyStoreException if the KeyStore Provider does not exist or fails to initialize, indicating issues + * with the key store setup process. + */ + static void initialize_module() throws KeyStoreException { + cryptoManager = new CryptoManager(); + + } + + /** + * Signs the given data using the key managed by the TPM. + *

+ * This method signs the provided data using the key managed by the TPM (Trusted Platform Module). The data to be + * signed is represented as a byte array. The signing process produces a signature for the data, which is returned as + * a byte array containing the signed data. + * + * @param data a byte array representing the data to be signed. + * @return the signed data as a byte array. + * @throws UnrecoverableKeyException if the key cannot be recovered from the keystore. + * @throws NoSuchAlgorithmException if the requested algorithm is not available. + * @throws KeyStoreException if there is an error accessing the keystore. + * @throws SignatureException if the signature process encounters an error. + * @throws InvalidKeyException if the key used for signing is invalid. + * @throws InvalidKeySpecException if the key specification is invalid. + * @throws NoSuchProviderException if the provider is not available. + */ + static byte[] sign_data(byte[] data) throws UnrecoverableKeyException, NoSuchAlgorithmException, + KeyStoreException, SignatureException, InvalidKeyException, InvalidKeySpecException, NoSuchProviderException { + return cryptoManager.signData(data); + } + + /** + * Verifies the signature of the given data using the key managed by the TPM. + *

+ * This method verifies the signature of the provided data against a known signature using the key managed by the TPM + * (Trusted Platform Module). Both the data and the signature are represented as byte arrays. The verification process + * validates whether the signature matches the data, returning true if the signature is valid and false otherwise. + * + * @param data a byte array representing the data to be verified. + * @param signature a byte array representing the signature to be verified against the data. + * @return true if the signature is valid, false otherwise. + * @throws SignatureException if the signature verification process encounters an error. + * @throws KeyStoreException if there is an error accessing the keystore. + * @throws NoSuchAlgorithmException if the requested algorithm is not available. + * @throws InvalidKeyException if the key used for verification is invalid. + * @throws UnrecoverableKeyException if the key cannot be recovered from the keystore. + * @throws InvalidKeySpecException if the key specification is invalid. + * @throws NoSuchProviderException if the provider is not available. + */ + static boolean verify_signature(byte[] data, byte[] signature) throws SignatureException, KeyStoreException, + NoSuchAlgorithmException, InvalidKeyException, UnrecoverableKeyException, InvalidKeySpecException, + NoSuchProviderException { + return cryptoManager.verifySignature(data, signature); + } + + /** + * Encrypts the given data using the key managed by the TPM. + *

+ * This method encrypts the provided data using the key managed by the TPM (Trusted Platform Module). + * The data to be encrypted is represented as a byte array. The encryption process is performed using cryptographic + * operations managed by the {@link CryptoManager}. The encrypted data is returned as a byte array. + *

+ * This method is called from Rust code, indicating that it may be invoked as part of an integration with a Rust + * application or library. + * + * @param data a byte array representing the data to be encrypted. + * @return a byte array containing the encrypted data. + * @throws InvalidAlgorithmParameterException if the algorithm parameters are invalid for encryption. + * @throws UnrecoverableKeyException if the key cannot be recovered from the keystore. + * @throws NoSuchPaddingException if the padding scheme is not available. + * @throws IllegalBlockSizeException if the block size is invalid for the encryption algorithm. + * @throws CertificateException if there is an issue loading the certificate chain. + * @throws NoSuchAlgorithmException if the requested algorithm is not available. + * @throws IOException if there is an I/O error during the operation. + * @throws KeyStoreException if there is an error accessing the keystore. + * @throws BadPaddingException if the data padding is incorrect for encryption. + * @throws InvalidKeySpecException if the key specification is invalid. + * @throws InvalidKeyException if the key is invalid for encryption. + * @throws NoSuchProviderException if the provider is not available. + */ + static byte[] encrypt_data(byte[] data) throws InvalidAlgorithmParameterException, UnrecoverableKeyException, + NoSuchPaddingException, IllegalBlockSizeException, CertificateException, NoSuchAlgorithmException, + IOException, KeyStoreException, BadPaddingException, InvalidKeySpecException, InvalidKeyException, + NoSuchProviderException { + return cryptoManager.encryptData(data); + } + + /** + * Decrypts the given data using the key managed by the TPM. + *

+ * This method decrypts the provided encrypted data using the key managed by the TPM (Trusted Platform Module). + * The encrypted data is represented as a byte array. The decryption process is performed using cryptographic + * operations managed by the {@link CryptoManager}. The decrypted data is returned as a byte array. + *

+ * This method is called from Rust code, indicating that it may be invoked as part of an integration with a Rust + * application or library. + * + * @param encrypted_data a byte array representing the data to be decrypted. + * @return a byte array containing the decrypted data. + * @throws InvalidAlgorithmParameterException if the algorithm parameters are invalid for decryption. + * @throws UnrecoverableKeyException if the key cannot be recovered from the keystore. + * @throws NoSuchPaddingException if the padding scheme is not available. + * @throws IllegalBlockSizeException if the block size is invalid for the decryption algorithm. + * @throws CertificateException if there is an issue loading the certificate chain. + * @throws NoSuchAlgorithmException if the requested algorithm is not available. + * @throws IOException if there is an I/O error during the operation. + * @throws KeyStoreException if there is an error accessing the keystore. + * @throws BadPaddingException if the data padding is incorrect for decryption. + * @throws InvalidKeySpecException if the key specification is invalid. + * @throws InvalidKeyException if the key is invalid for decryption. + * @throws NoSuchProviderException if the provider is not available. + */ + static byte[] decrypt_data(byte[] encrypted_data) throws InvalidAlgorithmParameterException, UnrecoverableKeyException, + NoSuchPaddingException, IllegalBlockSizeException, CertificateException, NoSuchAlgorithmException, + IOException, KeyStoreException, BadPaddingException, InvalidKeySpecException, InvalidKeyException, + NoSuchProviderException { + return cryptoManager.decryptData(encrypted_data); + } +} \ No newline at end of file From d296b107675cf9a6d595b48af3519f41c8bb3884 Mon Sep 17 00:00:00 2001 From: segelnhoch3 Date: Mon, 27 May 2024 14:15:10 +0200 Subject: [PATCH 007/121] moved parameters from initmodule() to createkey() --- src/tpm/android/knox/interface.rs | 44 ++++++++------------------ src/tpm/android/knox/java/RustDef.java | 7 +--- 2 files changed, 14 insertions(+), 37 deletions(-) diff --git a/src/tpm/android/knox/interface.rs b/src/tpm/android/knox/interface.rs index 017e21a4..a5a84624 100644 --- a/src/tpm/android/knox/interface.rs +++ b/src/tpm/android/knox/interface.rs @@ -56,26 +56,19 @@ pub mod jni { ///Proof of concept method - shows callback from Rust to a java method /// ONLY USE FOR TESTING - pub extern "jni" fn callRust(environment: &JNIEnv) -> String { - - //example usage of a java method call from rust - Self::create_key(environment, String::from("moin")).unwrap(); - String::from("Success") + pub extern "jni" fn callRust(_environment: &JNIEnv) -> String { + String::from("not implemented") } ///Demo method used to call functions in Rust from the Java app while testing - pub extern "jni" fn demoCreate(environment: &JNIEnv, key_id: String) -> () { - Self::create_key(environment, key_id).unwrap(); + pub extern "jni" fn demoCreate(environment: &JNIEnv, key_id: String, key_gen_info: String) -> () { + Self::create_key(environment, key_id, key_gen_info).unwrap(); } ///Demo method used to call functions in Rust from the Java app while testing - pub extern "jni" fn demoInit(environment: &JNIEnv, - key_algorithm: String, - sym_algorithm: String, - hash: String, - key_usages: String) + pub extern "jni" fn demoInit(environment: &JNIEnv) -> () { - let _ = Self::initialize_module(environment, key_algorithm, sym_algorithm, hash, key_usages); + let _ = Self::initialize_module(environment); } ///Demo method used to call functions in Rust from the Java app while testing @@ -134,12 +127,13 @@ pub mod jni { /// /// # Arguments /// `key_id` - String that uniquely identifies the key so that it can be retrieved later - pub fn create_key(environment: &JNIEnv, key_id: String) -> Result<(), SecurityModuleError> { + pub fn create_key(environment: &JNIEnv, key_id: String, key_gen_info: String) -> Result<(), SecurityModuleError> { let result = environment.call_static_method( "com/example/vulcans_limes/RustDef", "create_key", - "(Ljava/lang/String;)V", - &[JValue::from(environment.new_string(key_id).unwrap())], + "(Ljava/lang/String;Ljava/lang/String;)V", + &[JValue::from(environment.new_string(key_id).unwrap()), + JValue::from(environment.new_string(key_gen_info).unwrap())], ); let _ = Self::check_java_exceptions(&environment); return match result { @@ -226,31 +220,19 @@ pub mod jni { /// A `Result` that, on success, contains `()`, /// indicating that the module was initialized successfully. /// On failure, it returns an Error - pub fn initialize_module(environment: &JNIEnv, - key_algorithm: String, - sym_algorithm: String, - hash: String, - key_usages: String) + pub fn initialize_module(environment: &JNIEnv) -> Result<(), SecurityModuleError> { let result = environment.call_static_method( "com/example/vulcans_limes/RustDef", "initialize_module", - "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V", - &[JValue::from(environment.new_string(key_algorithm).unwrap()), - JValue::from(environment.new_string(sym_algorithm).unwrap()), - JValue::from(environment.new_string(hash).unwrap()), - JValue::from(environment.new_string(key_usages).unwrap())], + "()V", + &[], ); let _ = Self::check_java_exceptions(&environment); return match result { Ok(..) => Ok(()), Err(e) => { match e { - Error::WrongJValueType(_, _) => { - Err(SecurityModuleError::InitializationError( - String::from("Failed to initialise Module: Wrong Arguments passed") - )) - } Error::JavaException => { Err(SecurityModuleError::InitializationError( String::from("Failed to initialise Module: Some exception occurred in Java. diff --git a/src/tpm/android/knox/java/RustDef.java b/src/tpm/android/knox/java/RustDef.java index 90cbc066..fddc3a03 100644 --- a/src/tpm/android/knox/java/RustDef.java +++ b/src/tpm/android/knox/java/RustDef.java @@ -72,17 +72,12 @@ class RustDef { */ static native String callRust(); - /** - * Is called to start all demo method calls from the Rust side - * --temporary-- - */ static native byte[] demoEncrypt(byte[] data); - static native void demoCreate(String key_id, String keyGenInfo); + static native void demoCreate(String key_id, String key_gen_info); static native void demoInit(); - static native byte[] demoDecrypt(byte[] data); static native byte[] demoSign(byte[] data); From b5258f884a3a5069b3917abc7d4bdcb085d88925 Mon Sep 17 00:00:00 2001 From: segelnhoch3 Date: Mon, 27 May 2024 15:45:30 +0200 Subject: [PATCH 008/121] create_key() implementation started --- src/tpm/android/knox/provider.rs | 162 +++++++++++++++---------------- 1 file changed, 79 insertions(+), 83 deletions(-) diff --git a/src/tpm/android/knox/provider.rs b/src/tpm/android/knox/provider.rs index 2cbe95c1..026f7436 100644 --- a/src/tpm/android/knox/provider.rs +++ b/src/tpm/android/knox/provider.rs @@ -1,4 +1,3 @@ -use super::TpmProvider; use crate::{ common::{ crypto::{ @@ -11,27 +10,17 @@ use crate::{ error::SecurityModuleError, traits::module_provider::Provider, }, - tpm::core::error::TpmError, }; use tracing::instrument; -use windows::{ - core::PCWSTR, - Win32::Security::Cryptography::{ - NCryptCreatePersistedKey, NCryptFinalizeKey, NCryptOpenKey, NCryptOpenStorageProvider, - NCryptSetProperty, BCRYPT_ECDH_ALGORITHM, BCRYPT_ECDSA_ALGORITHM, CERT_KEY_SPEC, - MS_PLATFORM_CRYPTO_PROVIDER, NCRYPT_ALLOW_DECRYPT_FLAG, NCRYPT_ALLOW_SIGNING_FLAG, - NCRYPT_CERTIFICATE_PROPERTY, NCRYPT_FLAGS, NCRYPT_KEY_HANDLE, NCRYPT_KEY_USAGE_PROPERTY, - NCRYPT_LENGTH_PROPERTY, NCRYPT_MACHINE_KEY_FLAG, NCRYPT_OVERWRITE_KEY_FLAG, - NCRYPT_PROV_HANDLE, NCRYPT_SILENT_FLAG, - }, -}; -use std::error::Error; -use std::fmt; use serde::de::Unexpected::Option; -use tss_esapi::constants::Tss2ResponseCodeKind::KeySize; -use tss_esapi::interface_types::algorithm::SymmetricAlgorithm; +use crate::common::crypto::algorithms::encryption::EccCurves; +use crate::common::crypto::algorithms::hashes::{Sha2Bits, Sha3Bits}; use crate::common::crypto::algorithms::KeyBits; +use crate::common::traits::module_provider_config::ProviderConfig; +use crate::tpm::android::knox::interface::jni::RustDef; +use crate::tpm::core::error::TpmError::UnsupportedOperation; use crate::tpm::linux::TpmProvider; +use crate::tpm::TpmConfig; /// Implements the `Provider` trait, providing cryptographic operations utilizing a TPM. @@ -59,9 +48,78 @@ impl Provider for TpmProvider { /// A `Result` that, on success, contains `Ok(())`, indicating that the key was created successfully. /// On failure, it returns a `SecurityModuleError`. #[instrument] - fn create_key(&mut self, key_id: &str) -> Result<(), SecurityModuleError> { + fn create_key(&mut self, key_id: &str, config: Box) -> Result<(), SecurityModuleError> { + let config = config.as_any().downcast_ref::().unwrap(); - Ok(()) + //Knox Vault only supports SHA256 + if !config.hash == Hash::Sha2(Sha2Bits::Sha256) { + return Err(SecurityModuleError::Tpm(UnsupportedOperation( + format!("Unsupported hashing algorithm: {:?}", config.hash)))); + } + + let asym_string = match config.key_algorithm { + AsymmetricEncryption::Rsa(bitslength) => { + match bitslength { + KeyBits::Bits128 => { String::from("RSA;128;SHA-256;PKCS1") } + KeyBits::Bits192 => { String::from("RSA;192;SHA-256;PKCS1") } + KeyBits::Bits256 => { String::from("RSA;256;SHA-256;PKCS1") } + KeyBits::Bits512 => { String::from("RSA;512;SHA-256;PKCS1") } + KeyBits::Bits1024 => { String::from("RSA;1024;SHA-256;PKCS1") } + KeyBits::Bits2048 => { String::from("RSA;2048;SHA-256;PKCS1") } + KeyBits::Bits3072 => { String::from("RSA;3072;SHA-256;PKCS1") } + KeyBits::Bits4096 => { String::from("RSA;4096;SHA-256;PKCS1") } + KeyBits::Bits8192 => { String::from("RSA;8192;SHA-256;PKCS1") } + } + } + AsymmetricEncryption::Ecc(scheme) => { //todo + match scheme { + EccSchemeAlgorithm::EcDsa(curve) => { + match curve { + EccCurves::P256 => {} + EccCurves::P384 => {} + EccCurves::P521 => {} + EccCurves::Secp256k1 => {} + EccCurves::BrainpoolP256r1 => {} + EccCurves::BrainpoolP384r1 => {} + EccCurves::BrainpoolP512r1 => {} + EccCurves::BrainpoolP638 => {} + EccCurves::Curve25519 => {} + EccCurves::Curve448 => {} + EccCurves::Frp256v1 => {} + } + } + + _ => {} + } + } + _ => { + return Err(SecurityModuleError::Tpm(UnsupportedOperation( + format!("Unsupported asymmetric encryption algorithm: {:?}", + config.key_algorithm)))); + } + }; + + let sym_string = match config.sym_algorithm { + Option::DESede(bitslength) => { + match bitslength { + KeyBits::Bits128 => { String::from("DESede;CBC;PKCS7,PKCS5Padding") } //todo which one? + KeyBits::Bits128 => { String::from("DESede;ECB;PKCS7,NoPadding") } + } + }, + Option::Aes(bitslength) => { + match bitslength { + KeyBits::Bits128 => { String::from("AES;128;GCM;NoPadding") } + KeyBits::Bits192 => { String::from("AES;192;GCM;NoPadding") } + KeyBits::Bits256 => { String::from("AES;256;GCM;NoPadding") } + } + }, + _ => { + return Err(SecurityModuleError::Tpm(UnsupportedOperation( + format!("Unsupported symmetric encryption algorithm: {:?}", config.sym_algorithm)))); + } + }; + + RustDef::create_key(&(), key_id, format!("{};{};")) } /// Loads an existing cryptographic key identified by `key_id`. @@ -83,8 +141,7 @@ impl Provider for TpmProvider { /// A `Result` that, on success, contains `Ok(())`, indicating that the key was loaded successfully. /// On failure, it returns a `SecurityModuleError`. #[instrument] - fn load_key(&mut self, key_id: &str) -> Result<(), SecurityModuleError> { - + fn load_key(&mut self, key_id: &str, _config: Box) -> Result<(), SecurityModuleError> { Ok(()) } @@ -128,68 +185,7 @@ impl Provider for TpmProvider { /// Ok(()) => println!("Module initialized successfully"), /// Err(e) => println!("Failed to initialize module: {:?}", e), /// } - - - - fn initialize_module( - mut self, - key_algorithm: AsymmetricEncryption, - sym_algorithm: Option, - hash: Option, - key_usages: Vec, - ) -> Result<(), SecurityModuleError> { - - let asymString; - match key_algorithm { - AsymmetricEncryption::Rsa(bitslength) => { - match bitslength { - KeyBits::Bits128 => {asymString = String::from("RSA;128;HmacSHA-1;PKCS1")}, - KeyBits::Bits192 => {asymString = String::from("RSA;192;HmacSHA-224;PKCS1")}, - KeyBits::Bits256 => {asymString = String::from("RSA;256;HmacSHA-384;PKCS1")}, - KeyBits::Bits512 => {asymString = String::from("RSA;512;HmacSHA-512;PKCS1")}, - KeyBits::Bits1024 => {asymString = String::from("RSA;1024;HmacSHA-512;PKCS1")}, - KeyBits::Bits2048 => {asymString = String::from("RSA;2048;HmacSHA-512;PKCS1")}, - KeyBits::Bits3072 => {asymString = String::from("RSA;3072;HmacSHA-512;PKCS1")}, - KeyBits::Bits4096 => {asymString = String::from("RSA;4096;HmacSHA-512;PKCS1")}, - KeyBits::Bits8192 => {asymString = String::from("RSA;8192;HmacSHA-512;PKCS1")}, - } - } - _ => {return Err(SecurityModuleError::UnsupportedAlgorithm(format!("Unsupported asymmetric encryption algorithm:")))} - } - let symString; - match sym_algorithm { - Option::DESede(bitslength) => { - match bitslength { - KeyBits::Bits128 => {symString = String::from("DESede;CBC;PKCS7,PKCS5Padding")}, - KeyBits::Bits128 => {symString = String::from("DESede;ECB;PKCS7,NoPadding")}, - } - } - _ => {return Err(SecurityModuleError::UnsupportedAlgorithm(format!("Unsupported symmetric encryption algorithm:")))} - } - - let symString; - match sym_algorithm { - Option::Aes(bitslength) => { - match bitslength { - KeyBits::Bits128 => {symString = String::from("AES;128;GCM;NoPadding")}, - KeyBits::Bits128 => {symString = String::from("AES;128;ECB;PKCS7")}, - KeyBits::Bits128 => {symString = String::from("AES;128;CBC;PKCS7")}, - KeyBits::Bits128 => {symString = String::from("AES;128;CTR;PKCS7")}, - KeyBits::Bits192 => {symString = String::from("AES;192;GCM;NoPadding")}, - KeyBits::Bits192 => {symString = String::from("AES;192;ECB;PKCS7")}, - KeyBits::Bits192 => {symString = String::from("AES;192;CBC;PKCS7")}, - KeyBits::Bits192 => {symString = String::from("AES;192;CTR;PKCS7")}, - KeyBits::Bits256 => {symString = String::from("AES;256;GCM;NoPadding")}, - KeyBits::Bits256 => {symString = String::from("AES;256;ECB;PKCS7")}, - KeyBits::Bits256 => {symString = String::from("AES;256;CBC;PKCS7")}, - KeyBits::Bits256 => {symString = String::from("AES;256;CTR;PKCS7")}, - - } - }, - _ => {return Err(SecurityModuleError::UnsupportedAlgorithm(format!("Unsupported symmetric encryption algorithm:")))} - } - + fn initialize_module(&mut self) -> Result<(), SecurityModuleError> { Ok(()) } - } From c0714205ed0dd11f6246e951a70acf7c175ec69e Mon Sep 17 00:00:00 2001 From: ErikFribus Date: Mon, 27 May 2024 18:23:15 +0200 Subject: [PATCH 009/121] updated provider.rs matches --- src/tpm/android/knox/provider.rs | 60 ++++++++++++++++++++------------ 1 file changed, 37 insertions(+), 23 deletions(-) diff --git a/src/tpm/android/knox/provider.rs b/src/tpm/android/knox/provider.rs index 026f7436..f74ab467 100644 --- a/src/tpm/android/knox/provider.rs +++ b/src/tpm/android/knox/provider.rs @@ -13,7 +13,7 @@ use crate::{ }; use tracing::instrument; use serde::de::Unexpected::Option; -use crate::common::crypto::algorithms::encryption::EccCurves; +use crate::common::crypto::algorithms::encryption::{EccCurves, SymmetricMode}; use crate::common::crypto::algorithms::hashes::{Sha2Bits, Sha3Bits}; use crate::common::crypto::algorithms::KeyBits; use crate::common::traits::module_provider_config::ProviderConfig; @@ -71,21 +71,22 @@ impl Provider for TpmProvider { KeyBits::Bits8192 => { String::from("RSA;8192;SHA-256;PKCS1") } } } - AsymmetricEncryption::Ecc(scheme) => { //todo + AsymmetricEncryption::Ecc(scheme) => { //todo: test in java prototype match scheme { EccSchemeAlgorithm::EcDsa(curve) => { match curve { - EccCurves::P256 => {} - EccCurves::P384 => {} - EccCurves::P521 => {} - EccCurves::Secp256k1 => {} - EccCurves::BrainpoolP256r1 => {} - EccCurves::BrainpoolP384r1 => {} - EccCurves::BrainpoolP512r1 => {} - EccCurves::BrainpoolP638 => {} - EccCurves::Curve25519 => {} - EccCurves::Curve448 => {} - EccCurves::Frp256v1 => {} + EccCurves::P256 => { String::from ("EC/secp256r1/SHA-256") } + EccCurves::P384 => { String::from ("EC/secp384r1/SHA-256") } + EccCurves::P521 => { String::from ("EC/secp384r1/SHA-256") } + EccCurves::Secp256k1 => { String::from ("EC/secp256k1/SHA-256") } + EccCurves::Curve25519 => { String::from ("EC/X25519/SHA-256") } + EccCurves::Curve448 => { String::from ("EC/X448/SHA-256") } + + _ => { + return Err(SecurityModuleError::Tpm(UnsupportedOperation( + format!("Unsupported asymmetric encryption algorithm: {:?}", + config.key_algorithm)))); + } } } @@ -100,18 +101,31 @@ impl Provider for TpmProvider { }; let sym_string = match config.sym_algorithm { - Option::DESede(bitslength) => { + BlockCiphers::Des() => { String::from("DESede;CBC;PKCS7Padding") }, + + BlockCiphers::Aes(block, bitslength) => { + //todo: check for correct rust syntax + let mut rv :String = "AES;"; match bitslength { - KeyBits::Bits128 => { String::from("DESede;CBC;PKCS7,PKCS5Padding") } //todo which one? - KeyBits::Bits128 => { String::from("DESede;ECB;PKCS7,NoPadding") } + KeyBits::Bits128 => { rv += "128;"; } + KeyBits::Bits192 => { rv += "192;"; } + KeyBits::Bits256 => { rv += "256;"; } + _ => { + return Err(SecurityModuleError::Tpm(UnsupportedOperation( + format!("Unsupported symmetric encryption algorithm: {:?}", config.sym_algorithm)))); + } } - }, - Option::Aes(bitslength) => { - match bitslength { - KeyBits::Bits128 => { String::from("AES;128;GCM;NoPadding") } - KeyBits::Bits192 => { String::from("AES;192;GCM;NoPadding") } - KeyBits::Bits256 => { String::from("AES;256;GCM;NoPadding") } + match block { //todo: check if paddings match blocking modes + SymmetricMode::Gcm => {rv += "GCM;NoPadding"} + SymmetricMode::Ecb => {rv += "ECB;PKCS7Padding"} + SymmetricMode::Cbc => {rv += "CBC;PKCS7Padding"} + SymmetricMode::Ctr => {rv += "CTR;PKCS7Padding"} + _ => { + return Err(SecurityModuleError::Tpm(UnsupportedOperation( + format!("Unsupported symmetric encryption algorithm: {:?}", config.sym_algorithm)))); + } } + return rv; }, _ => { return Err(SecurityModuleError::Tpm(UnsupportedOperation( @@ -119,7 +133,7 @@ impl Provider for TpmProvider { } }; - RustDef::create_key(&(), key_id, format!("{};{};")) + RustDef::create_key(&(), key_id, format!("{};{};")) //todo } /// Loads an existing cryptographic key identified by `key_id`. From aac056eee51b73d6a0071bb9bdb27062fae5e607 Mon Sep 17 00:00:00 2001 From: segelnhoch3 Date: Mon, 27 May 2024 19:01:31 +0200 Subject: [PATCH 010/121] create_key() fully implemented but not yet tested --- Cargo.lock | 17 +++- Cargo.toml | 1 + src/common/error.rs | 8 ++ src/tests/common/traits/mod.rs | 7 +- src/tpm/android/knox/mod.rs | 52 +++++++++++ src/tpm/android/knox/provider.rs | 146 ++++++++++++++++--------------- 6 files changed, 159 insertions(+), 72 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3722b364..74eec2a6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -364,6 +364,7 @@ dependencies = [ "anyhow", "async-std", "futures", + "jni 0.20.0", "nitrokey", "once_cell", "robusta_jni", @@ -875,6 +876,20 @@ dependencies = [ "walkdir", ] +[[package]] +name = "jni" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "039022cdf4d7b1cf548d31f60ae783138e5fd42013f6271049d7df7afadef96c" +dependencies = [ + "cesu8", + "combine", + "jni-sys", + "log", + "thiserror", + "walkdir", +] + [[package]] name = "jni-sys" version = "0.3.0" @@ -1457,7 +1472,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c080146e0cc733697fe500413871142af91bd879641205c2febbe5f982f304e3" dependencies = [ - "jni", + "jni 0.19.0", "paste", "robusta-codegen", "static_assertions", diff --git a/Cargo.toml b/Cargo.toml index f95ada91..56413766 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,6 +38,7 @@ yubi = ["hsm", "yubikey"] [dependencies] robusta_jni = "0.2" +jni = "0.20.0" anyhow = "*" async-std = "*" futures = "*" diff --git a/src/common/error.rs b/src/common/error.rs index 789163da..a6f746e9 100644 --- a/src/common/error.rs +++ b/src/common/error.rs @@ -40,6 +40,10 @@ pub enum SecurityModuleError { /// /// This variant contains a descriptive error message. InitializationError(String), + /// Error that occurred during the key generation operation. + /// + /// This variant contains a descriptive error message. + CreationError(String), } impl fmt::Display for SecurityModuleError { @@ -72,6 +76,9 @@ impl fmt::Display for SecurityModuleError { SecurityModuleError::InitializationError(ref error_msg) => { write!(f, "Initialization error: {}", error_msg) } + SecurityModuleError::CreationError(ref error_msg) => { + write!(f, "Creation error: {}", error_msg) + } } } } @@ -96,6 +103,7 @@ impl std::error::Error for SecurityModuleError { SecurityModuleError::EncryptionError(_) => None, SecurityModuleError::SignatureVerificationError(_) => None, SecurityModuleError::InitializationError(_) => None, + SecurityModuleError::CreationError(_) => None } } } diff --git a/src/tests/common/traits/mod.rs b/src/tests/common/traits/mod.rs index ef36d0a7..ddbc8b0e 100644 --- a/src/tests/common/traits/mod.rs +++ b/src/tests/common/traits/mod.rs @@ -58,7 +58,12 @@ fn setup_security_module(module: SecurityModule) -> Arc> { Some(log), ) .unwrap(), - TpmType::None => unimplemented!(), + TpmType::Android(t) => SecModules::get_instance( + "test_key".to_owned(), + SecurityModule::Tpm(TpmType::Android(t)), + Some(log), + ).unwrap(), + _ => unimplemented!(), }, // _ => unimplemented!(), } diff --git a/src/tpm/android/knox/mod.rs b/src/tpm/android/knox/mod.rs index 8b137891..5bc7cc26 100644 --- a/src/tpm/android/knox/mod.rs +++ b/src/tpm/android/knox/mod.rs @@ -1 +1,53 @@ +use std::any::Any; +use std::fmt; +use std::fmt::{Debug, Formatter}; +use robusta_jni::jni; +use crate::common::crypto::algorithms::encryption::{AsymmetricEncryption, BlockCiphers}; +use crate::common::crypto::algorithms::hashes::Hash; +use crate::common::traits::module_provider_config::ProviderConfig; +use jni::JNIEnv; +mod interface; +mod key_handle; +mod provider; +///A struct defining the needed values for the create_key() function in provider.rs +///At any time, either a key_algorithm OR a sym_algorithm must be supplied, not both. +/// For hashing operations, SHA-256 is always used since it is the only one available on Knox Vault +#[derive(Clone)] +pub struct KnoxConfig<'a> { + pub key_algorithm: Option, + pub sym_algorithm: Option, + pub env: JNIEnv<'a> +} + +impl Debug for KnoxConfig { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.debug_struct("KnoxConfig") + .field("key_algorithm", &self.key_algorithm) + .field("sym_algorithm", &self.sym_algorithm) + .field("env", &self.env) + .finish() + } +} + +impl ProviderConfig for KnoxConfig { + fn as_any(&self) -> &dyn Any { + self + } +} + +impl KnoxConfig { + #[allow(clippy::new_ret_no_self)] + pub fn new<'a>( + key_algorithm: Option, + sym_algorithm: Option, + hash: Hash, //todo: Test if necessary for sym keys + env: JNIEnv<'a> + ) -> Box { + Box::new(Self { + key_algorithm, + sym_algorithm, + env, + }) + } +} \ No newline at end of file diff --git a/src/tpm/android/knox/provider.rs b/src/tpm/android/knox/provider.rs index 026f7436..aef99819 100644 --- a/src/tpm/android/knox/provider.rs +++ b/src/tpm/android/knox/provider.rs @@ -2,10 +2,8 @@ use crate::{ common::{ crypto::{ algorithms::{ - encryption::{AsymmetricEncryption, BlockCiphers, EccSchemeAlgorithm}, - hashes::Hash, + encryption::{AsymmetricEncryption, EccSchemeAlgorithm}, }, - KeyUsage, }, error::SecurityModuleError, traits::module_provider::Provider, @@ -14,13 +12,12 @@ use crate::{ use tracing::instrument; use serde::de::Unexpected::Option; use crate::common::crypto::algorithms::encryption::EccCurves; -use crate::common::crypto::algorithms::hashes::{Sha2Bits, Sha3Bits}; use crate::common::crypto::algorithms::KeyBits; use crate::common::traits::module_provider_config::ProviderConfig; use crate::tpm::android::knox::interface::jni::RustDef; +use crate::tpm::android::knox::KnoxConfig; use crate::tpm::core::error::TpmError::UnsupportedOperation; use crate::tpm::linux::TpmProvider; -use crate::tpm::TpmConfig; /// Implements the `Provider` trait, providing cryptographic operations utilizing a TPM. @@ -49,77 +46,86 @@ impl Provider for TpmProvider { /// On failure, it returns a `SecurityModuleError`. #[instrument] fn create_key(&mut self, key_id: &str, config: Box) -> Result<(), SecurityModuleError> { - let config = config.as_any().downcast_ref::().unwrap(); - - //Knox Vault only supports SHA256 - if !config.hash == Hash::Sha2(Sha2Bits::Sha256) { - return Err(SecurityModuleError::Tpm(UnsupportedOperation( - format!("Unsupported hashing algorithm: {:?}", config.hash)))); - } - - let asym_string = match config.key_algorithm { - AsymmetricEncryption::Rsa(bitslength) => { - match bitslength { - KeyBits::Bits128 => { String::from("RSA;128;SHA-256;PKCS1") } - KeyBits::Bits192 => { String::from("RSA;192;SHA-256;PKCS1") } - KeyBits::Bits256 => { String::from("RSA;256;SHA-256;PKCS1") } - KeyBits::Bits512 => { String::from("RSA;512;SHA-256;PKCS1") } - KeyBits::Bits1024 => { String::from("RSA;1024;SHA-256;PKCS1") } - KeyBits::Bits2048 => { String::from("RSA;2048;SHA-256;PKCS1") } - KeyBits::Bits3072 => { String::from("RSA;3072;SHA-256;PKCS1") } - KeyBits::Bits4096 => { String::from("RSA;4096;SHA-256;PKCS1") } - KeyBits::Bits8192 => { String::from("RSA;8192;SHA-256;PKCS1") } - } + let config = match config.as_any().downcast_ref::() { + None => { + return Err(SecurityModuleError::CreationError( + "Wrong type used for ProviderConfig in create_key()")); } - AsymmetricEncryption::Ecc(scheme) => { //todo - match scheme { - EccSchemeAlgorithm::EcDsa(curve) => { - match curve { - EccCurves::P256 => {} - EccCurves::P384 => {} - EccCurves::P521 => {} - EccCurves::Secp256k1 => {} - EccCurves::BrainpoolP256r1 => {} - EccCurves::BrainpoolP384r1 => {} - EccCurves::BrainpoolP512r1 => {} - EccCurves::BrainpoolP638 => {} - EccCurves::Curve25519 => {} - EccCurves::Curve448 => {} - EccCurves::Frp256v1 => {} - } + Some(conf) => { conf } + }; + let key_algo; + if config.key_algorithm.is_some() && config.sym_algorithm.is_none() { + key_algo = match config.key_algorithm { + AsymmetricEncryption::Rsa(bitslength) => { + match bitslength { + KeyBits::Bits128 => { String::from("RSA;128;SHA-256;PKCS1") } + KeyBits::Bits192 => { String::from("RSA;192;SHA-256;PKCS1") } + KeyBits::Bits256 => { String::from("RSA;256;SHA-256;PKCS1") } + KeyBits::Bits512 => { String::from("RSA;512;SHA-256;PKCS1") } + KeyBits::Bits1024 => { String::from("RSA;1024;SHA-256;PKCS1") } + KeyBits::Bits2048 => { String::from("RSA;2048;SHA-256;PKCS1") } + KeyBits::Bits3072 => { String::from("RSA;3072;SHA-256;PKCS1") } + KeyBits::Bits4096 => { String::from("RSA;4096;SHA-256;PKCS1") } + KeyBits::Bits8192 => { String::from("RSA;8192;SHA-256;PKCS1") } } - - _ => {} } - } - _ => { - return Err(SecurityModuleError::Tpm(UnsupportedOperation( - format!("Unsupported asymmetric encryption algorithm: {:?}", - config.key_algorithm)))); - } - }; + AsymmetricEncryption::Ecc(scheme) => { //todo + match scheme { + EccSchemeAlgorithm::EcDsa(curve) => { + match curve { + EccCurves::P256 => {} + EccCurves::P384 => {} + EccCurves::P521 => {} + EccCurves::Secp256k1 => {} + EccCurves::BrainpoolP256r1 => {} + EccCurves::BrainpoolP384r1 => {} + EccCurves::BrainpoolP512r1 => {} + EccCurves::BrainpoolP638 => {} + EccCurves::Curve25519 => {} + EccCurves::Curve448 => {} + EccCurves::Frp256v1 => {} + } + } - let sym_string = match config.sym_algorithm { - Option::DESede(bitslength) => { - match bitslength { - KeyBits::Bits128 => { String::from("DESede;CBC;PKCS7,PKCS5Padding") } //todo which one? - KeyBits::Bits128 => { String::from("DESede;ECB;PKCS7,NoPadding") } + _ => {} + } } - }, - Option::Aes(bitslength) => { - match bitslength { - KeyBits::Bits128 => { String::from("AES;128;GCM;NoPadding") } - KeyBits::Bits192 => { String::from("AES;192;GCM;NoPadding") } - KeyBits::Bits256 => { String::from("AES;256;GCM;NoPadding") } + _ => { + return Err(SecurityModuleError::Tpm(UnsupportedOperation( + format!("Unsupported asymmetric encryption algorithm: {:?}", + config.key_algorithm)))); } - }, - _ => { - return Err(SecurityModuleError::Tpm(UnsupportedOperation( - format!("Unsupported symmetric encryption algorithm: {:?}", config.sym_algorithm)))); - } - }; - - RustDef::create_key(&(), key_id, format!("{};{};")) + }; + } else if config.key_algorithm.is_none() && config.sym_algorithm.is_some() { + key_algo = match config.sym_algorithm { + Option::DESede(bitslength) => { + match bitslength { + KeyBits::Bits128 => { String::from("DESede;CBC;PKCS7,PKCS5Padding") } //todo which one? + KeyBits::Bits128 => { String::from("DESede;ECB;PKCS7,NoPadding") } + } + } + Option::Aes(bitslength) => { + match bitslength { + KeyBits::Bits128 => { String::from("AES;128;GCM;NoPadding") } + KeyBits::Bits192 => { String::from("AES;192;GCM;NoPadding") } + KeyBits::Bits256 => { String::from("AES;256;GCM;NoPadding") } + } + } + _ => { + return Err(SecurityModuleError::Tpm(UnsupportedOperation( + format!("Unsupported symmetric encryption algorithm: {:?}", config.sym_algorithm)))); + } + }; + }else { + return Err(SecurityModuleError::CreationError(format!( + "wrong parameters in KnoxConfig: + Exactly one of either sym_algorithm or key_algorithm must be Some().\ + sym_algorithm: {:?}\ + key_algorithm: {:?}", + config.sym_algorithm, + config.key_algorithm))) + } + RustDef::create_key(config.env,key_id, key_algo) } /// Loads an existing cryptographic key identified by `key_id`. From dd454be8eab1919f8a309a7142d24167b965c142 Mon Sep 17 00:00:00 2001 From: segelnhoch3 Date: Mon, 27 May 2024 19:02:02 +0200 Subject: [PATCH 011/121] fixed typo --- src/tests/common/traits/module_provider.rs | 2 +- src/tpm/android/knox/interface.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tests/common/traits/module_provider.rs b/src/tests/common/traits/module_provider.rs index 5b4cba30..cc843ce1 100644 --- a/src/tests/common/traits/module_provider.rs +++ b/src/tests/common/traits/module_provider.rs @@ -36,7 +36,7 @@ fn test_create_rsa_key(module: SecurityModule) { ], ); - providerw + provider .lock() .unwrap() .initialize_module() diff --git a/src/tpm/android/knox/interface.rs b/src/tpm/android/knox/interface.rs index a5a84624..9f8cd715 100644 --- a/src/tpm/android/knox/interface.rs +++ b/src/tpm/android/knox/interface.rs @@ -56,7 +56,7 @@ pub mod jni { ///Proof of concept method - shows callback from Rust to a java method /// ONLY USE FOR TESTING - pub extern "jni" fn callRust(_environment: &JNIEnv) -> String { + pub extern "jni" fn callRust( _environment: &JNIEnv) -> String { String::from("not implemented") } From 90cb7b8a8e09040d93ec4e6b40f5b3858d1aef3d Mon Sep 17 00:00:00 2001 From: segelnhoch3 Date: Mon, 27 May 2024 19:15:58 +0200 Subject: [PATCH 012/121] merged enum-matches from origin and full implementation of create_key() --- src/tpm/android/knox/provider.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/tpm/android/knox/provider.rs b/src/tpm/android/knox/provider.rs index 051bc353..6c221327 100644 --- a/src/tpm/android/knox/provider.rs +++ b/src/tpm/android/knox/provider.rs @@ -10,9 +10,7 @@ use crate::{ }, }; use tracing::instrument; -use serde::de::Unexpected::Option; use crate::common::crypto::algorithms::encryption::{BlockCiphers, EccCurves, SymmetricMode}; -use crate::common::crypto::algorithms::hashes::{Sha2Bits, Sha3Bits}; use crate::common::crypto::algorithms::KeyBits; use crate::common::traits::module_provider_config::ProviderConfig; use crate::tpm::android::knox::interface::jni::RustDef; @@ -87,7 +85,6 @@ impl Provider for TpmProvider { } } } - _ => {} } } From 317c7bb3042bc45bcb5e4f4cbcc699e926443eb3 Mon Sep 17 00:00:00 2001 From: segelnhoch3 Date: Tue, 28 May 2024 08:05:28 +0200 Subject: [PATCH 013/121] fixed AES String generation syntax in create_key() --- src/tpm/android/knox/provider.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/tpm/android/knox/provider.rs b/src/tpm/android/knox/provider.rs index 6c221327..d00d9619 100644 --- a/src/tpm/android/knox/provider.rs +++ b/src/tpm/android/knox/provider.rs @@ -29,7 +29,7 @@ impl Provider for TpmProvider { /// /// This method creates a persisted cryptographic key using the specified algorithm /// and identifier, making it retrievable for future operations. The key is created - /// with the specified key usages and stored in the TPM. + /// and stored in Knox Vault. /// /// # Arguments /// @@ -99,8 +99,7 @@ impl Provider for TpmProvider { BlockCiphers::Des() => { String::from("DESede;CBC;PKCS7Padding") } BlockCiphers::Aes(block, bitslength) => { - //todo: check for correct rust syntax - let mut rv: String = "AES;"; + let mut rv = String::from("AES;"); match bitslength { KeyBits::Bits128 => { rv += "128;"; } KeyBits::Bits192 => { rv += "192;"; } @@ -120,7 +119,7 @@ impl Provider for TpmProvider { format!("Unsupported symmetric encryption algorithm: {:?}", config.sym_algorithm)))); } } - return rv; + rv }, _ => { return Err(SecurityModuleError::Tpm(UnsupportedOperation( From 5344546aa1ab4cf5855af7b5253f1ae78457f322 Mon Sep 17 00:00:00 2001 From: ErikFribus Date: Tue, 28 May 2024 08:11:58 +0200 Subject: [PATCH 014/121] quickfix to EC Strings --- src/tpm/android/knox/provider.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/tpm/android/knox/provider.rs b/src/tpm/android/knox/provider.rs index d00d9619..61c32ffe 100644 --- a/src/tpm/android/knox/provider.rs +++ b/src/tpm/android/knox/provider.rs @@ -72,12 +72,12 @@ impl Provider for TpmProvider { match scheme { EccSchemeAlgorithm::EcDsa(curve) => { match curve { - EccCurves::P256 => { String::from("EC/secp256r1/SHA-256") } - EccCurves::P384 => { String::from("EC/secp384r1/SHA-256") } - EccCurves::P521 => { String::from("EC/secp384r1/SHA-256") } - EccCurves::Secp256k1 => { String::from("EC/secp256k1/SHA-256") } - EccCurves::Curve25519 => { String::from("EC/X25519/SHA-256") } - EccCurves::Curve448 => { String::from("EC/X448/SHA-256") } + EccCurves::P256 => { String::from("EC;secp256r1;SHA-256") } + EccCurves::P384 => { String::from("EC;secp384r1;SHA-256") } + EccCurves::P521 => { String::from("EC;secp521r1;SHA-256") } + EccCurves::Secp256k1 => { String::from("EC;secp256k1;SHA-256") } + EccCurves::Curve25519 => { String::from("EC;X25519;SHA-256") } + EccCurves::Curve448 => { String::from("EC;X448;SHA-256") } _ => { return Err(SecurityModuleError::Tpm(UnsupportedOperation( format!("Unsupported asymmetric encryption algorithm: {:?}", From bdd98b5fb4e5e116689a986600fe69bdd99eefd5 Mon Sep 17 00:00:00 2001 From: ErikFribus Date: Tue, 28 May 2024 08:24:43 +0200 Subject: [PATCH 015/121] Tested EC curves, deleted unsupported curves --- src/tpm/android/knox/provider.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/tpm/android/knox/provider.rs b/src/tpm/android/knox/provider.rs index 61c32ffe..95151f2e 100644 --- a/src/tpm/android/knox/provider.rs +++ b/src/tpm/android/knox/provider.rs @@ -75,9 +75,7 @@ impl Provider for TpmProvider { EccCurves::P256 => { String::from("EC;secp256r1;SHA-256") } EccCurves::P384 => { String::from("EC;secp384r1;SHA-256") } EccCurves::P521 => { String::from("EC;secp521r1;SHA-256") } - EccCurves::Secp256k1 => { String::from("EC;secp256k1;SHA-256") } - EccCurves::Curve25519 => { String::from("EC;X25519;SHA-256") } - EccCurves::Curve448 => { String::from("EC;X448;SHA-256") } + // EccCurves::Curve25519 => { String::from("EC;X25519;SHA-256") } <- x25519 may ONLY be used for key agreement, not signing _ => { return Err(SecurityModuleError::Tpm(UnsupportedOperation( format!("Unsupported asymmetric encryption algorithm: {:?}", From 166f1ccba74d61fa369e716fe903c328186f9f32 Mon Sep 17 00:00:00 2001 From: segelnhoch3 Date: Tue, 28 May 2024 08:55:56 +0200 Subject: [PATCH 016/121] added KnoxProvider and defined implementations for it --- src/tpm/android/knox/key_handle.rs | 297 +---------------------------- src/tpm/android/knox/mod.rs | 29 ++- src/tpm/android/knox/provider.rs | 20 +- 3 files changed, 43 insertions(+), 303 deletions(-) diff --git a/src/tpm/android/knox/key_handle.rs b/src/tpm/android/knox/key_handle.rs index c438e398..2d0a7d29 100644 --- a/src/tpm/android/knox/key_handle.rs +++ b/src/tpm/android/knox/key_handle.rs @@ -1,23 +1,13 @@ -use super::TpmProvider; +use super::{KnoxProvider}; use crate::{ common::{error::SecurityModuleError, traits::key_handle::KeyHandle}, tpm::core::error::TpmError, }; use tracing::instrument; -use windows::{ - core::PCWSTR, - Win32::Security::Cryptography::{ - BCryptCloseAlgorithmProvider, BCryptCreateHash, BCryptDestroyHash, BCryptFinishHash, - BCryptGetProperty, BCryptHashData, BCryptOpenAlgorithmProvider, NCryptDecrypt, - NCryptEncrypt, NCryptSignHash, NCryptVerifySignature, BCRYPT_ALG_HANDLE, - BCRYPT_HASH_HANDLE, BCRYPT_HASH_LENGTH, BCRYPT_OBJECT_LENGTH, - BCRYPT_OPEN_ALGORITHM_PROVIDER_FLAGS, NCRYPT_FLAGS, NCRYPT_PAD_PKCS1_FLAG, - }, -}; /// Provides cryptographic operations for asymmetric keys on Windows, /// such as signing, encryption, decryption, and signature verification. -impl KeyHandle for TpmProvider { +impl KeyHandle for KnoxProvider { /// Signs data using the cryptographic key. /// /// This method hashes the input data using SHA-256 and then signs the hash. @@ -32,115 +22,7 @@ impl KeyHandle for TpmProvider { /// A `Result` containing the signature as a `Vec` on success, or a `SecurityModuleError` on failure. #[instrument] fn sign_data(&self, data: &[u8]) -> Result, SecurityModuleError> { - // Open an algorithm provider for SHA-512 - let mut alg_handle: BCRYPT_ALG_HANDLE = self.hash.clone().unwrap().into(); - let hash_algo: PCWSTR = self.hash.clone().unwrap().into(); - - if unsafe { - BCryptOpenAlgorithmProvider( - &mut alg_handle, - hash_algo, - None, - BCRYPT_OPEN_ALGORITHM_PROVIDER_FLAGS(0), - ) - } - .is_err() - { - return Err(TpmError::Win(windows::core::Error::from_win32()).into()); - } - - // Get the size of the hash object - let mut hash_object_size: u32 = 0; - if unsafe { - BCryptGetProperty( - alg_handle, - BCRYPT_OBJECT_LENGTH, - None, - &mut hash_object_size, - 0, - ) - } - .is_err() - { - return Err(TpmError::Win(windows::core::Error::from_win32()).into()); - } - - let mut hash_object = Vec::with_capacity(hash_object_size as usize); - - // Get the length of the hash - let mut hash_length: u32 = 0; - if unsafe { BCryptGetProperty(alg_handle, BCRYPT_HASH_LENGTH, None, &mut hash_length, 0) } - .is_err() - { - return Err(TpmError::Win(windows::core::Error::from_win32()).into()); - } - - let mut hash_handle = BCRYPT_HASH_HANDLE::default(); - if unsafe { - BCryptCreateHash( - alg_handle, - &mut hash_handle, - Some(hash_object.as_mut_slice()), // Pass the hash object buffer - None, - 0, // Flags - ) - } - .is_err() - { - return Err(TpmError::Win(windows::core::Error::from_win32()).into()); - } - - // Hash the data - if unsafe { BCryptHashData(hash_handle, data, 0) }.is_err() { - return Err(TpmError::Win(windows::core::Error::from_win32()).into()); - } - - // Finalize the hash - let mut hash = Vec::with_capacity(hash_length as usize); - if unsafe { BCryptFinishHash(hash_handle, &mut hash, 0) }.is_err() { - return Err(TpmError::Win(windows::core::Error::from_win32()).into()); - } - - // Determine the size of the signature - let mut signature_size: u32 = 0; - if unsafe { - NCryptSignHash( - self.key_handle.as_ref(), - None, // No padding info - &hash, // Hash as a slice - None, // No signature buffer yet - &mut signature_size, // Pointer to receive the size of the signature - NCRYPT_PAD_PKCS1_FLAG, // Padding flag - ) - } - .is_err() - { - return Err(TpmError::Win(windows::core::Error::from_win32()).into()); - } - - // Allocate a buffer for the signature - let mut signature = Vec::with_capacity(signature_size as usize); - - // Sign the hash - if unsafe { - NCryptSignHash( - self.key_handle.as_ref(), - None, // No padding info - &hash, // Hash as a slice - Some(&mut signature), // Signature buffer as a mutable slice - &mut signature_size, // Pointer to receive the actual size of the signature - NCRYPT_PAD_PKCS1_FLAG, // Padding flag - ) - } - .is_err() - { - return Err(TpmError::Win(windows::core::Error::from_win32()).into()); - } - - // Resize the signature buffer to the actual size - signature.truncate(signature_size as usize); - - Ok(signature) + unimplemented!() } /// Decrypts data encrypted with the corresponding public key. @@ -156,47 +38,8 @@ impl KeyHandle for TpmProvider { /// A `Result` containing the decrypted data as a `Vec` on success, or a `SecurityModuleError` on failure. #[instrument] fn decrypt_data(&self, encrypted_data: &[u8]) -> Result, SecurityModuleError> { - let mut decrypted_data_len: u32 = 0; - - // First, determine the size of the decrypted data without actually decrypting - if unsafe { - NCryptDecrypt( - self.key_handle.as_ref(), - Some(encrypted_data), // Pass encrypted data as an Option<&[u8]> - None, // Padding information as Option<*const c_void>, adjust based on your encryption scheme - None, // Initially, no output buffer to get the required size - &mut decrypted_data_len, // Receives the required size of the output buffer - NCRYPT_FLAGS(0), // Flags, adjust as necessary - ) - } - .is_err() - { - return Err(TpmError::Win(windows::core::Error::from_win32()).into()); - } - - // Allocate a buffer for the decrypted data - let mut decrypted_data = vec![0u8; decrypted_data_len as usize]; + unimplemented!() - // Perform the actual decryption - if unsafe { - NCryptDecrypt( - self.key_handle.as_ref(), - Some(encrypted_data), // Again, pass encrypted data as an Option<&[u8]> - None, // Padding information as Option<*const c_void>, adjust based on your encryption scheme - Some(&mut decrypted_data), // Now provide the output buffer - &mut decrypted_data_len, // Receives the size of the decrypted data - NCRYPT_FLAGS(0), // Flags, adjust as necessary - ) - } - .is_err() - { - return Err(TpmError::Win(windows::core::Error::from_win32()).into()); - } - - // Resize the buffer to match the actual decrypted data length - decrypted_data.resize(decrypted_data_len as usize, 0); - - Ok(decrypted_data) } /// Encrypts data with the cryptographic key. @@ -212,46 +55,8 @@ impl KeyHandle for TpmProvider { /// A `Result` containing the encrypted data as a `Vec` on success, or a `SecurityModuleError` on failure. #[instrument] fn encrypt_data(&self, data: &[u8]) -> Result, SecurityModuleError> { - // First call to determine the size of the encrypted data - let mut encrypted_data_len: u32 = 0; - if unsafe { - NCryptEncrypt( - self.key_handle.as_ref(), - Some(data), // Input data as a slice - None, // Padding information, adjust based on your encryption scheme - None, // Initially, no output buffer to get the required size - &mut encrypted_data_len, // Receive the required size of the output buffer - NCRYPT_FLAGS(0), // Flags, adjust as necessary - ) - } - .is_err() - { - return Err(TpmError::Win(windows::core::Error::from_win32()).into()); - } - - // Allocate a buffer for the encrypted data - let mut encrypted_data = vec![0u8; encrypted_data_len as usize]; - - // Actual call to encrypt the data - if unsafe { - NCryptEncrypt( - self.key_handle.as_ref(), - Some(data), // Input data as a slice - None, // Padding information, adjust based on your encryption scheme - Some(&mut encrypted_data), // Provide the output buffer - &mut encrypted_data_len, // Receives the size of the encrypted data - NCRYPT_FLAGS(0), // Flags, adjust as necessary - ) - } - .is_err() - { - return Err(TpmError::Win(windows::core::Error::from_win32()).into()); - } + unimplemented!() - // Resize the buffer to match the actual encrypted data length - encrypted_data.resize(encrypted_data_len as usize, 0); - - Ok(encrypted_data) } /// Verifies a signature against the provided data. @@ -270,97 +75,7 @@ impl KeyHandle for TpmProvider { /// or a `SecurityModuleError` on failure. #[instrument] fn verify_signature(&self, data: &[u8], signature: &[u8]) -> Result { - // Open an algorithm provider for SHA-256, just like in sign_data - let mut alg_handle = BCRYPT_ALG_HANDLE::default(); - let alg_id: PCWSTR = self.hash.clone().unwrap().into(); - - if unsafe { - BCryptOpenAlgorithmProvider( - &mut alg_handle, - alg_id, - None, - BCRYPT_OPEN_ALGORITHM_PROVIDER_FLAGS(0), - ) - } - .is_err() - { - return Err(TpmError::Win(windows::core::Error::from_win32()).into()); - } - - // Get the size of the hash object and hash length, just like in sign_data - let mut hash_object_size: u32 = 0; - if unsafe { - BCryptGetProperty( - alg_handle, - BCRYPT_OBJECT_LENGTH, - None, - &mut hash_object_size, - 0, - ) - } - .is_err() - { - return Err(TpmError::Win(windows::core::Error::from_win32()).into()); - } - - let mut hash_handle = BCRYPT_HASH_HANDLE::default(); - let mut hash_object = Vec::with_capacity(hash_object_size as usize); - let mut hash_length: u32 = 0; - - if unsafe { BCryptGetProperty(alg_handle, BCRYPT_HASH_LENGTH, None, &mut hash_length, 0) } - .is_err() - { - return Err(TpmError::Win(windows::core::Error::from_win32()).into()); - }; - - if unsafe { - BCryptCreateHash( - alg_handle, - &mut hash_handle, - Some(hash_object.as_mut_slice()), - None, - 0, - ) - } - .is_err() - { - return Err(TpmError::Win(windows::core::Error::from_win32()).into()); - } - - // Hash the data - if unsafe { BCryptHashData(hash_handle, data, 0) }.is_err() { - return Err(TpmError::Win(windows::core::Error::from_win32()).into()); - } - - // Finalize the hash - let mut hash = Vec::with_capacity(hash_length as usize); - if unsafe { BCryptFinishHash(hash_handle, &mut hash, 0) }.is_err() { - return Err(TpmError::Win(windows::core::Error::from_win32()).into()); - } - - // Verify the signature - let status = unsafe { - NCryptVerifySignature( - self.key_handle.as_ref(), - None, // No padding info - hash.as_slice(), - signature, - NCRYPT_PAD_PKCS1_FLAG, - ) - }; - - // Cleanup - if unsafe { BCryptDestroyHash(hash_handle) }.is_err() { - return Err(TpmError::Win(windows::core::Error::from_win32()).into()); - }; - if unsafe { BCryptCloseAlgorithmProvider(alg_handle, 0) }.is_err() { - return Err(TpmError::Win(windows::core::Error::from_win32()).into()); - }; + unimplemented!() - // Check if the signature is valid - match status { - Ok(_) => Ok(true), - Err(_) => Ok(false), - } } } diff --git a/src/tpm/android/knox/mod.rs b/src/tpm/android/knox/mod.rs index 5bc7cc26..34dde6ea 100644 --- a/src/tpm/android/knox/mod.rs +++ b/src/tpm/android/knox/mod.rs @@ -1,14 +1,34 @@ use std::any::Any; use std::fmt; use std::fmt::{Debug, Formatter}; -use robusta_jni::jni; use crate::common::crypto::algorithms::encryption::{AsymmetricEncryption, BlockCiphers}; -use crate::common::crypto::algorithms::hashes::Hash; use crate::common::traits::module_provider_config::ProviderConfig; use jni::JNIEnv; +use tracing::instrument; + mod interface; -mod key_handle; -mod provider; +pub mod key_handle; +pub mod provider; + +/// A TPM-based cryptographic provider for managing cryptographic keys and performing +/// cryptographic operations in an Samsung environment. This provider uses the Java Native Interface +/// and the Android Keystore API to access the TPM "Knox Vault" developed by Samsung +#[derive(Clone, Debug)] +#[repr(C)] +pub struct KnoxProvider {} + +impl KnoxProvider { + /// Constructs a new `TpmProvider`. + /// + /// + /// # Returns + /// + /// A new instance of `TpmProvider`. + #[instrument] + pub fn new() -> Self { + Self {} + } +} ///A struct defining the needed values for the create_key() function in provider.rs ///At any time, either a key_algorithm OR a sym_algorithm must be supplied, not both. @@ -41,7 +61,6 @@ impl KnoxConfig { pub fn new<'a>( key_algorithm: Option, sym_algorithm: Option, - hash: Hash, //todo: Test if necessary for sym keys env: JNIEnv<'a> ) -> Box { Box::new(Self { diff --git a/src/tpm/android/knox/provider.rs b/src/tpm/android/knox/provider.rs index d00d9619..db0a636c 100644 --- a/src/tpm/android/knox/provider.rs +++ b/src/tpm/android/knox/provider.rs @@ -14,9 +14,9 @@ use crate::common::crypto::algorithms::encryption::{BlockCiphers, EccCurves, Sym use crate::common::crypto::algorithms::KeyBits; use crate::common::traits::module_provider_config::ProviderConfig; use crate::tpm::android::knox::interface::jni::RustDef; -use crate::tpm::android::knox::KnoxConfig; +use crate::tpm::android::knox::{KnoxConfig, KnoxProvider}; use crate::tpm::core::error::TpmError::UnsupportedOperation; -use crate::tpm::linux::TpmProvider; + /// Implements the `Provider` trait, providing cryptographic operations utilizing a TPM. @@ -24,7 +24,7 @@ use crate::tpm::linux::TpmProvider; /// This implementation is specific to the Windows platform and utilizes the Windows CNG API /// to interact with the Trusted Platform Module (TPM) for key management and cryptographic /// operations. -impl Provider for TpmProvider { +impl Provider for KnoxProvider { /// Creates a new cryptographic key identified by `key_id`. /// /// This method creates a persisted cryptographic key using the specified algorithm @@ -34,10 +34,16 @@ impl Provider for TpmProvider { /// # Arguments /// /// * `key_id` - A string slice that uniquely identifies the key to be created. - /// * `key_algorithm` - The asymmetric encryption algorithm to be used for the key. - /// * `sym_algorithm` - An optional symmetric encryption algorithm to be used with the key. - /// * `hash` - An optional hash algorithm to be used with the key. - /// * `key_usages` - A vector of `AppKeyUsage` values specifying the intended usages for the key. + /// * `Box` - A Box containing a KnoxConfig that has all further required parameters: + /// * key_algorithm: Option\, + /// * sym_algorithm: Option\, + /// * env: JNIEnv<'a> + /// + /// There must be exactly one of either key_algorithm or sym_algorithm provided. + /// If both are Some or None, the method returns an Error. + /// The env parameter is necessary to access the Java Virtual Machine from Rust code. When + /// calling Rust code from Java using the Java Native Interface, this value will be provided to the + /// Rust code by the JNI. /// /// # Returns /// From 53817dfca60bc934ac3cfdaa4bddf358e8a2228c Mon Sep 17 00:00:00 2001 From: noah-pe <3001838@stud.hs-mannheim.de> Date: Tue, 28 May 2024 09:45:19 +0200 Subject: [PATCH 017/121] successful abstraction layer integration --- Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index f95ada91..49394a14 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,6 @@ name = "crypto-layer" version = "0.1.0" edition = "2021" license = "MIT" -crate-type = ["staticlib"] [lib] crate-type = ["cdylib"] From 391ed57e2e089716de7391d064bc32139937367c Mon Sep 17 00:00:00 2001 From: segelnhoch3 Date: Tue, 28 May 2024 14:03:19 +0200 Subject: [PATCH 018/121] fixed syntax mistakes --- src/tpm/android/knox/interface.rs | 10 ++++-- src/tpm/android/knox/mod.rs | 16 +++++---- src/tpm/android/knox/provider.rs | 59 ++++++++++++++++++++++--------- 3 files changed, 59 insertions(+), 26 deletions(-) diff --git a/src/tpm/android/knox/interface.rs b/src/tpm/android/knox/interface.rs index 9f8cd715..17775dc1 100644 --- a/src/tpm/android/knox/interface.rs +++ b/src/tpm/android/knox/interface.rs @@ -5,9 +5,13 @@ pub mod jni { #[allow(unused_imports)] use robusta_jni::bridge; use robusta_jni::convert::{IntoJavaValue, Signature, TryFromJavaValue, TryIntoJavaValue}; - use robusta_jni::jni::errors::Error; - use robusta_jni::jni::JNIEnv; - use robusta_jni::jni::objects::{AutoLocal, JValue}; + // use robusta_jni::jni::errors::Error; + // use robusta_jni::jni::JNIEnv; + // use robusta_jni::jni::objects::JValue; + use jni::JNIEnv; + use jni::errors::Error; + use jni::objects::JValue; + use robusta_jni::jni::objects::AutoLocal; use robusta_jni::jni::sys::jbyteArray; use crate::SecurityModuleError; diff --git a/src/tpm/android/knox/mod.rs b/src/tpm/android/knox/mod.rs index 34dde6ea..35668044 100644 --- a/src/tpm/android/knox/mod.rs +++ b/src/tpm/android/knox/mod.rs @@ -3,7 +3,7 @@ use std::fmt; use std::fmt::{Debug, Formatter}; use crate::common::crypto::algorithms::encryption::{AsymmetricEncryption, BlockCiphers}; use crate::common::traits::module_provider_config::ProviderConfig; -use jni::JNIEnv; +use jni::JavaVM; use tracing::instrument; mod interface; @@ -33,11 +33,12 @@ impl KnoxProvider { ///A struct defining the needed values for the create_key() function in provider.rs ///At any time, either a key_algorithm OR a sym_algorithm must be supplied, not both. /// For hashing operations, SHA-256 is always used since it is the only one available on Knox Vault -#[derive(Clone)] +// #[derive(Clone)] pub struct KnoxConfig<'a> { pub key_algorithm: Option, pub sym_algorithm: Option, - pub env: JNIEnv<'a> + // pub env: JNIEnv<'a>, + pub vm: JavaVM } impl Debug for KnoxConfig { @@ -45,7 +46,7 @@ impl Debug for KnoxConfig { f.debug_struct("KnoxConfig") .field("key_algorithm", &self.key_algorithm) .field("sym_algorithm", &self.sym_algorithm) - .field("env", &self.env) + .field("vm", &self.vm) .finish() } } @@ -58,15 +59,16 @@ impl ProviderConfig for KnoxConfig { impl KnoxConfig { #[allow(clippy::new_ret_no_self)] - pub fn new<'a>( + pub fn new( key_algorithm: Option, sym_algorithm: Option, - env: JNIEnv<'a> + // env: JNIEnv<'a> + vm: JavaVM ) -> Box { Box::new(Self { key_algorithm, sym_algorithm, - env, + vm, }) } } \ No newline at end of file diff --git a/src/tpm/android/knox/provider.rs b/src/tpm/android/knox/provider.rs index 8c084737..6d6f61c7 100644 --- a/src/tpm/android/knox/provider.rs +++ b/src/tpm/android/knox/provider.rs @@ -1,3 +1,4 @@ +use jni::errors::Error; use crate::{ common::{ crypto::{ @@ -21,9 +22,8 @@ use crate::tpm::core::error::TpmError::UnsupportedOperation; /// Implements the `Provider` trait, providing cryptographic operations utilizing a TPM. /// -/// This implementation is specific to the Windows platform and utilizes the Windows CNG API -/// to interact with the Trusted Platform Module (TPM) for key management and cryptographic -/// operations. +/// This implementation is specific to Samsung Knox Vault and uses it for all cryptographic operations +/// In theory, this should also work for other TPMs on Android phones, but it is only tested with Samsung Knox Vault impl Provider for KnoxProvider { /// Creates a new cryptographic key identified by `key_id`. /// @@ -51,16 +51,13 @@ impl Provider for KnoxProvider { /// On failure, it returns a `SecurityModuleError`. #[instrument] fn create_key(&mut self, key_id: &str, config: Box) -> Result<(), SecurityModuleError> { - let config = match config.as_any().downcast_ref::() { - None => { - return Err(SecurityModuleError::CreationError( - "Wrong type used for ProviderConfig in create_key()")); - } - Some(conf) => { conf } + let config = match Self::unpack_ProviderConfig(config) { + Ok(value) => value, + Err(value) => return Err(value), }; let key_algo; if config.key_algorithm.is_some() && config.sym_algorithm.is_none() { - key_algo = match config.key_algorithm { + key_algo = match config.key_algorithm.expect("Already checked") { AsymmetricEncryption::Rsa(bitslength) => { match bitslength { KeyBits::Bits128 => { String::from("RSA;128;SHA-256;PKCS1") } @@ -89,7 +86,10 @@ impl Provider for KnoxProvider { } } } - _ => {} + _ => {return Err(SecurityModuleError::Tpm(UnsupportedOperation( + format!("Unsupported asymmetric encryption algorithm: {:?}", + config.key_algorithm)))); + } } } _ => { @@ -99,8 +99,8 @@ impl Provider for KnoxProvider { } }; } else if config.key_algorithm.is_none() && config.sym_algorithm.is_some() { - key_algo = match config.sym_algorithm { - BlockCiphers::Des() => { String::from("DESede;CBC;PKCS7Padding") } + key_algo = match config.sym_algorithm.expect("Already checked") { + BlockCiphers::Des => { String::from("DESede;CBC;PKCS7Padding") }, BlockCiphers::Aes(block, bitslength) => { let mut rv = String::from("AES;"); @@ -139,7 +139,16 @@ impl Provider for KnoxProvider { config.sym_algorithm, config.key_algorithm))); } - RustDef::create_key(config.env, key_id, key_algo) + + let env = match &config.vm.get_env() { + Ok(e) => {e} + Err(_) => { + return Err(SecurityModuleError::CreationError( + String::from("failed to extract JNIEnv from JavaVM"))) + } + }; + + RustDef::create_key(env, String::from(key_id), key_algo) } /// Loads an existing cryptographic key identified by `key_id`. @@ -161,8 +170,12 @@ impl Provider for KnoxProvider { /// A `Result` that, on success, contains `Ok(())`, indicating that the key was loaded successfully. /// On failure, it returns a `SecurityModuleError`. #[instrument] - fn load_key(&mut self, key_id: &str, _config: Box) -> Result<(), SecurityModuleError> { - Ok(()) + fn load_key(&mut self, key_id: &str, config: Box) -> Result<(), SecurityModuleError> { + let conf = match Self::unpack_ProviderConfig(config) { + Ok(value) => value, + Err(value) => return Err(value), + }; + RustDef::load_key(&conf.env, String::from(key_id)) } /// Initializes the TPM module and returns a handle for cryptographic operations. @@ -209,3 +222,17 @@ impl Provider for KnoxProvider { Ok(()) } } + +impl KnoxProvider { + #[allow(non_snake_case)] + fn unpack_ProviderConfig(config: Box) -> Result<&KnoxConfig, SecurityModuleError> { + let config = match config.as_any().downcast_ref::() { + None => { + return Err(SecurityModuleError::CreationError( + String::from("Wrong type used for ProviderConfig in create_key()"))); + } + Some(conf) => { conf } + }; + Ok(config) + } +} From 39f349ddd806c19dba7cc3406dbdac381a582023 Mon Sep 17 00:00:00 2001 From: segelnhoch3 Date: Tue, 28 May 2024 14:29:09 +0200 Subject: [PATCH 019/121] changed jnienv to javaVM for parameters --- src/tpm/android/knox/interface.rs | 6 ++++-- src/tpm/android/knox/provider.rs | 24 +++++++----------------- 2 files changed, 11 insertions(+), 19 deletions(-) diff --git a/src/tpm/android/knox/interface.rs b/src/tpm/android/knox/interface.rs index 17775dc1..e89fbb37 100644 --- a/src/tpm/android/knox/interface.rs +++ b/src/tpm/android/knox/interface.rs @@ -11,6 +11,7 @@ pub mod jni { use jni::JNIEnv; use jni::errors::Error; use jni::objects::JValue; + use jni::JavaVM; use robusta_jni::jni::objects::AutoLocal; use robusta_jni::jni::sys::jbyteArray; use crate::SecurityModuleError; @@ -66,7 +67,7 @@ pub mod jni { ///Demo method used to call functions in Rust from the Java app while testing pub extern "jni" fn demoCreate(environment: &JNIEnv, key_id: String, key_gen_info: String) -> () { - Self::create_key(environment, key_id, key_gen_info).unwrap(); + Self::create_key(environment.get_java_vm().unwrap(), key_id, key_gen_info).unwrap(); } ///Demo method used to call functions in Rust from the Java app while testing @@ -131,7 +132,8 @@ pub mod jni { /// /// # Arguments /// `key_id` - String that uniquely identifies the key so that it can be retrieved later - pub fn create_key(environment: &JNIEnv, key_id: String, key_gen_info: String) -> Result<(), SecurityModuleError> { + pub fn create_key(vm: JavaVM, key_id: String, key_gen_info: String) -> Result<(), SecurityModuleError> { + let environment = vm.get_env().unwrap(); let result = environment.call_static_method( "com/example/vulcans_limes/RustDef", "create_key", diff --git a/src/tpm/android/knox/provider.rs b/src/tpm/android/knox/provider.rs index 6d6f61c7..f9369de9 100644 --- a/src/tpm/android/knox/provider.rs +++ b/src/tpm/android/knox/provider.rs @@ -1,4 +1,3 @@ -use jni::errors::Error; use crate::{ common::{ crypto::{ @@ -55,6 +54,7 @@ impl Provider for KnoxProvider { Ok(value) => value, Err(value) => return Err(value), }; + let key_algo; if config.key_algorithm.is_some() && config.sym_algorithm.is_none() { key_algo = match config.key_algorithm.expect("Already checked") { @@ -92,11 +92,6 @@ impl Provider for KnoxProvider { } } } - _ => { - return Err(SecurityModuleError::Tpm(UnsupportedOperation( - format!("Unsupported asymmetric encryption algorithm: {:?}", - config.key_algorithm)))); - } }; } else if config.key_algorithm.is_none() && config.sym_algorithm.is_some() { key_algo = match config.sym_algorithm.expect("Already checked") { @@ -139,16 +134,7 @@ impl Provider for KnoxProvider { config.sym_algorithm, config.key_algorithm))); } - - let env = match &config.vm.get_env() { - Ok(e) => {e} - Err(_) => { - return Err(SecurityModuleError::CreationError( - String::from("failed to extract JNIEnv from JavaVM"))) - } - }; - - RustDef::create_key(env, String::from(key_id), key_algo) + RustDef::create_key(config.vm, String::from(key_id), key_algo) } /// Loads an existing cryptographic key identified by `key_id`. @@ -175,7 +161,11 @@ impl Provider for KnoxProvider { Ok(value) => value, Err(value) => return Err(value), }; - RustDef::load_key(&conf.env, String::from(key_id)) + let env = match Self::jvm_to_jnienv(&conf) { + Ok(value) => value, + Err(value) => return value, + }; + RustDef::load_key(&env, String::from(key_id)) } /// Initializes the TPM module and returns a handle for cryptographic operations. From 2c065e9bda47ab73030f3044350f6950f4a51619 Mon Sep 17 00:00:00 2001 From: ErikFribus Date: Tue, 28 May 2024 14:40:20 +0200 Subject: [PATCH 020/121] unshelve --- src/tpm/android/knox/provider.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/tpm/android/knox/provider.rs b/src/tpm/android/knox/provider.rs index f9369de9..02501c4b 100644 --- a/src/tpm/android/knox/provider.rs +++ b/src/tpm/android/knox/provider.rs @@ -110,9 +110,8 @@ impl Provider for KnoxProvider { } match block { //todo: check if paddings match blocking modes SymmetricMode::Gcm => { rv += "GCM;NoPadding" } - SymmetricMode::Ecb => { rv += "ECB;PKCS7Padding" } SymmetricMode::Cbc => { rv += "CBC;PKCS7Padding" } - SymmetricMode::Ctr => { rv += "CTR;PKCS7Padding" } + SymmetricMode::Ctr => { rv += "CTR;NoPadding" } _ => { return Err(SecurityModuleError::Tpm(UnsupportedOperation( format!("Unsupported symmetric encryption algorithm: {:?}", config.sym_algorithm)))); From 03ffacf531f96003e9094705d76c0e29c0985189 Mon Sep 17 00:00:00 2001 From: noah-pe <3001838@stud.hs-mannheim.de> Date: Tue, 28 May 2024 16:15:24 +0200 Subject: [PATCH 021/121] moved and updated --- src/tpm/android/knox/mod.rs | 2 +- src/tpm/android/knox/provider.rs | 37 +++++++++++++------------------- 2 files changed, 16 insertions(+), 23 deletions(-) diff --git a/src/tpm/android/knox/mod.rs b/src/tpm/android/knox/mod.rs index 35668044..6617cfe1 100644 --- a/src/tpm/android/knox/mod.rs +++ b/src/tpm/android/knox/mod.rs @@ -34,7 +34,7 @@ impl KnoxProvider { ///At any time, either a key_algorithm OR a sym_algorithm must be supplied, not both. /// For hashing operations, SHA-256 is always used since it is the only one available on Knox Vault // #[derive(Clone)] -pub struct KnoxConfig<'a> { +pub struct KnoxConfig { pub key_algorithm: Option, pub sym_algorithm: Option, // pub env: JNIEnv<'a>, diff --git a/src/tpm/android/knox/provider.rs b/src/tpm/android/knox/provider.rs index 02501c4b..cefa5d94 100644 --- a/src/tpm/android/knox/provider.rs +++ b/src/tpm/android/knox/provider.rs @@ -13,7 +13,7 @@ use tracing::instrument; use crate::common::crypto::algorithms::encryption::{BlockCiphers, EccCurves, SymmetricMode}; use crate::common::crypto::algorithms::KeyBits; use crate::common::traits::module_provider_config::ProviderConfig; -use crate::tpm::android::knox::interface::jni::RustDef; +use crate::tpm::android::knox::interface::RustDef; use crate::tpm::android::knox::{KnoxConfig, KnoxProvider}; use crate::tpm::core::error::TpmError::UnsupportedOperation; @@ -50,9 +50,12 @@ impl Provider for KnoxProvider { /// On failure, it returns a `SecurityModuleError`. #[instrument] fn create_key(&mut self, key_id: &str, config: Box) -> Result<(), SecurityModuleError> { - let config = match Self::unpack_ProviderConfig(config) { - Ok(value) => value, - Err(value) => return Err(value), + let config = match config.as_any().downcast_ref::() { + None => { + return Err(SecurityModuleError::CreationError( + String::from("Wrong type used for ProviderConfig in create_key()"))); + } + Some(conf) => { conf } }; let key_algo; @@ -156,11 +159,15 @@ impl Provider for KnoxProvider { /// On failure, it returns a `SecurityModuleError`. #[instrument] fn load_key(&mut self, key_id: &str, config: Box) -> Result<(), SecurityModuleError> { - let conf = match Self::unpack_ProviderConfig(config) { - Ok(value) => value, - Err(value) => return Err(value), + let config = match config.as_any().downcast_ref::() { + None => { + return Err(SecurityModuleError::CreationError( + String::from("Wrong type used for ProviderConfig in create_key()"))); + } + Some(conf) => { conf } }; - let env = match Self::jvm_to_jnienv(&conf) { + + let env = match Self::jvm_to_jnienv(&config) { Ok(value) => value, Err(value) => return value, }; @@ -211,17 +218,3 @@ impl Provider for KnoxProvider { Ok(()) } } - -impl KnoxProvider { - #[allow(non_snake_case)] - fn unpack_ProviderConfig(config: Box) -> Result<&KnoxConfig, SecurityModuleError> { - let config = match config.as_any().downcast_ref::() { - None => { - return Err(SecurityModuleError::CreationError( - String::from("Wrong type used for ProviderConfig in create_key()"))); - } - Some(conf) => { conf } - }; - Ok(config) - } -} From 2e16e922c50a27d732687a07e1961e21d3c8da90 Mon Sep 17 00:00:00 2001 From: ErikFribus Date: Tue, 28 May 2024 16:48:15 +0200 Subject: [PATCH 022/121] functions in key_handle.rs now call interface functions --- src/tpm/android/knox/key_handle.rs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/tpm/android/knox/key_handle.rs b/src/tpm/android/knox/key_handle.rs index 2d0a7d29..e1017359 100644 --- a/src/tpm/android/knox/key_handle.rs +++ b/src/tpm/android/knox/key_handle.rs @@ -4,6 +4,7 @@ use crate::{ tpm::core::error::TpmError, }; use tracing::instrument; +use crate::tpm::android::knox::interface::jni::RustDef; /// Provides cryptographic operations for asymmetric keys on Windows, /// such as signing, encryption, decryption, and signature verification. @@ -22,7 +23,7 @@ impl KeyHandle for KnoxProvider { /// A `Result` containing the signature as a `Vec` on success, or a `SecurityModuleError` on failure. #[instrument] fn sign_data(&self, data: &[u8]) -> Result, SecurityModuleError> { - unimplemented!() + let result = RustDef::sign_data(data); } /// Decrypts data encrypted with the corresponding public key. @@ -38,8 +39,7 @@ impl KeyHandle for KnoxProvider { /// A `Result` containing the decrypted data as a `Vec` on success, or a `SecurityModuleError` on failure. #[instrument] fn decrypt_data(&self, encrypted_data: &[u8]) -> Result, SecurityModuleError> { - unimplemented!() - + let result = RustDef::decrypt_data(encrypted_data); } /// Encrypts data with the cryptographic key. @@ -55,8 +55,7 @@ impl KeyHandle for KnoxProvider { /// A `Result` containing the encrypted data as a `Vec` on success, or a `SecurityModuleError` on failure. #[instrument] fn encrypt_data(&self, data: &[u8]) -> Result, SecurityModuleError> { - unimplemented!() - + let result = RustDef::encrypt_data(data); } /// Verifies a signature against the provided data. @@ -75,7 +74,6 @@ impl KeyHandle for KnoxProvider { /// or a `SecurityModuleError` on failure. #[instrument] fn verify_signature(&self, data: &[u8], signature: &[u8]) -> Result { - unimplemented!() - + let result = RustDef::verify_signature(data, signature); } } From cde1ab442c07f82a3d956273ee4dc1e393e49cd4 Mon Sep 17 00:00:00 2001 From: segelnhoch3 Date: Tue, 28 May 2024 17:01:05 +0200 Subject: [PATCH 023/121] fixed typo --- src/tpm/android/knox/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tpm/android/knox/mod.rs b/src/tpm/android/knox/mod.rs index 35668044..6617cfe1 100644 --- a/src/tpm/android/knox/mod.rs +++ b/src/tpm/android/knox/mod.rs @@ -34,7 +34,7 @@ impl KnoxProvider { ///At any time, either a key_algorithm OR a sym_algorithm must be supplied, not both. /// For hashing operations, SHA-256 is always used since it is the only one available on Knox Vault // #[derive(Clone)] -pub struct KnoxConfig<'a> { +pub struct KnoxConfig { pub key_algorithm: Option, pub sym_algorithm: Option, // pub env: JNIEnv<'a>, From c64eff2b7d59d9b748e8ecb1d90ae18449e04ce0 Mon Sep 17 00:00:00 2001 From: ErikFribus Date: Tue, 28 May 2024 17:04:54 +0200 Subject: [PATCH 024/121] quickfix for key_handle.rs --- src/tpm/android/knox/key_handle.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/tpm/android/knox/key_handle.rs b/src/tpm/android/knox/key_handle.rs index e1017359..958f0eca 100644 --- a/src/tpm/android/knox/key_handle.rs +++ b/src/tpm/android/knox/key_handle.rs @@ -23,7 +23,7 @@ impl KeyHandle for KnoxProvider { /// A `Result` containing the signature as a `Vec` on success, or a `SecurityModuleError` on failure. #[instrument] fn sign_data(&self, data: &[u8]) -> Result, SecurityModuleError> { - let result = RustDef::sign_data(data); + RustDef::sign_data(data) } /// Decrypts data encrypted with the corresponding public key. @@ -39,7 +39,7 @@ impl KeyHandle for KnoxProvider { /// A `Result` containing the decrypted data as a `Vec` on success, or a `SecurityModuleError` on failure. #[instrument] fn decrypt_data(&self, encrypted_data: &[u8]) -> Result, SecurityModuleError> { - let result = RustDef::decrypt_data(encrypted_data); + RustDef::decrypt_data(encrypted_data) } /// Encrypts data with the cryptographic key. @@ -55,7 +55,7 @@ impl KeyHandle for KnoxProvider { /// A `Result` containing the encrypted data as a `Vec` on success, or a `SecurityModuleError` on failure. #[instrument] fn encrypt_data(&self, data: &[u8]) -> Result, SecurityModuleError> { - let result = RustDef::encrypt_data(data); + RustDef::encrypt_data(data) } /// Verifies a signature against the provided data. @@ -74,6 +74,6 @@ impl KeyHandle for KnoxProvider { /// or a `SecurityModuleError` on failure. #[instrument] fn verify_signature(&self, data: &[u8], signature: &[u8]) -> Result { - let result = RustDef::verify_signature(data, signature); + RustDef::verify_signature(data, signature) } } From 9c00d2a3b69b86d9b56c9c73bdee9a6731f19126 Mon Sep 17 00:00:00 2001 From: segelnhoch3 Date: Tue, 28 May 2024 20:45:42 +0200 Subject: [PATCH 025/121] rust compiles without errors --- src/tpm/android/knox/interface.rs | 32 +++++++++++++++---------------- src/tpm/android/knox/mod.rs | 6 ++---- src/tpm/android/knox/provider.rs | 16 +++++++--------- 3 files changed, 25 insertions(+), 29 deletions(-) diff --git a/src/tpm/android/knox/interface.rs b/src/tpm/android/knox/interface.rs index e89fbb37..f0f92983 100644 --- a/src/tpm/android/knox/interface.rs +++ b/src/tpm/android/knox/interface.rs @@ -5,13 +5,13 @@ pub mod jni { #[allow(unused_imports)] use robusta_jni::bridge; use robusta_jni::convert::{IntoJavaValue, Signature, TryFromJavaValue, TryIntoJavaValue}; - // use robusta_jni::jni::errors::Error; - // use robusta_jni::jni::JNIEnv; - // use robusta_jni::jni::objects::JValue; - use jni::JNIEnv; - use jni::errors::Error; - use jni::objects::JValue; - use jni::JavaVM; + use robusta_jni::jni::errors::Error; + use robusta_jni::jni::JNIEnv; + use robusta_jni::jni::objects::JValue; + // use jni::JNIEnv; + // use jni::errors::Error; + // use jni::objects::JValue; + use robusta_jni::jni::JavaVM; use robusta_jni::jni::objects::AutoLocal; use robusta_jni::jni::sys::jbyteArray; use crate::SecurityModuleError; @@ -66,9 +66,9 @@ pub mod jni { } ///Demo method used to call functions in Rust from the Java app while testing - pub extern "jni" fn demoCreate(environment: &JNIEnv, key_id: String, key_gen_info: String) -> () { - Self::create_key(environment.get_java_vm().unwrap(), key_id, key_gen_info).unwrap(); - } + // pub extern "jni" fn demoCreate(environment: &JNIEnv, key_id: String, key_gen_info: String) -> () { + // Self::create_key(environment.get_java_vm().unwrap(), key_id, key_gen_info).unwrap(); + // } ///Demo method used to call functions in Rust from the Java app while testing pub extern "jni" fn demoInit(environment: &JNIEnv) @@ -132,8 +132,8 @@ pub mod jni { /// /// # Arguments /// `key_id` - String that uniquely identifies the key so that it can be retrieved later - pub fn create_key(vm: JavaVM, key_id: String, key_gen_info: String) -> Result<(), SecurityModuleError> { - let environment = vm.get_env().unwrap(); + pub fn create_key(environment: JNIEnv, key_id: String, key_gen_info: String) -> Result<(), SecurityModuleError> { + // let environment = vm.get_env().unwrap(); let result = environment.call_static_method( "com/example/vulcans_limes/RustDef", "create_key", @@ -265,7 +265,7 @@ pub mod jni { /// /// A `Result` containing the signature as a `Vec` on success, /// or an `Error` on failure. - fn sign_data(environment: &JNIEnv, data: &[u8]) -> Result, SecurityModuleError> { + pub fn sign_data(environment: &JNIEnv, data: &[u8]) -> Result, SecurityModuleError> { let result = environment.call_static_method( "com/example/vulcans_limes/RustDef", "sign_data", @@ -317,7 +317,7 @@ pub mod jni { /// /// A `Result` containing a `bool` signifying whether the signature is valid, /// or an `Error` on failure to determine the validity. - fn verify_signature(environment: &JNIEnv, data: &[u8], signature: &[u8]) -> Result { + pub fn verify_signature(environment: &JNIEnv, data: &[u8], signature: &[u8]) -> Result { let result = environment.call_static_method( "com/example/vulcans_limes/RustDef", "verify_signature", @@ -368,7 +368,7 @@ pub mod jni { /// /// A `Result` containing the encrypted data as a `Vec` on success, /// or an `Error` on failure. - fn encrypt_data(environment: &JNIEnv, data: &[u8]) -> Result, SecurityModuleError> { + pub fn encrypt_data(environment: &JNIEnv, data: &[u8]) -> Result, SecurityModuleError> { let result = environment.call_static_method( "com/example/vulcans_limes/RustDef", "encrypt_data", @@ -420,7 +420,7 @@ pub mod jni { /// /// A `Result` containing the Decrypted data as a `Vec` on success, /// or an `Error` on failure. - fn decrypt_data(environment: &JNIEnv, data: &[u8]) -> Result, SecurityModuleError> { + pub fn decrypt_data(environment: &JNIEnv, data: &[u8]) -> Result, SecurityModuleError> { let result = environment.call_static_method( "com/example/vulcans_limes/RustDef", "decrypt_data", diff --git a/src/tpm/android/knox/mod.rs b/src/tpm/android/knox/mod.rs index 6617cfe1..9b9fffbf 100644 --- a/src/tpm/android/knox/mod.rs +++ b/src/tpm/android/knox/mod.rs @@ -3,7 +3,7 @@ use std::fmt; use std::fmt::{Debug, Formatter}; use crate::common::crypto::algorithms::encryption::{AsymmetricEncryption, BlockCiphers}; use crate::common::traits::module_provider_config::ProviderConfig; -use jni::JavaVM; +use robusta_jni::jni::JavaVM; use tracing::instrument; mod interface; @@ -13,8 +13,8 @@ pub mod provider; /// A TPM-based cryptographic provider for managing cryptographic keys and performing /// cryptographic operations in an Samsung environment. This provider uses the Java Native Interface /// and the Android Keystore API to access the TPM "Knox Vault" developed by Samsung -#[derive(Clone, Debug)] #[repr(C)] +#[derive(Debug)] pub struct KnoxProvider {} impl KnoxProvider { @@ -33,7 +33,6 @@ impl KnoxProvider { ///A struct defining the needed values for the create_key() function in provider.rs ///At any time, either a key_algorithm OR a sym_algorithm must be supplied, not both. /// For hashing operations, SHA-256 is always used since it is the only one available on Knox Vault -// #[derive(Clone)] pub struct KnoxConfig { pub key_algorithm: Option, pub sym_algorithm: Option, @@ -46,7 +45,6 @@ impl Debug for KnoxConfig { f.debug_struct("KnoxConfig") .field("key_algorithm", &self.key_algorithm) .field("sym_algorithm", &self.sym_algorithm) - .field("vm", &self.vm) .finish() } } diff --git a/src/tpm/android/knox/provider.rs b/src/tpm/android/knox/provider.rs index cefa5d94..bc736941 100644 --- a/src/tpm/android/knox/provider.rs +++ b/src/tpm/android/knox/provider.rs @@ -1,3 +1,4 @@ +use std::fmt::{Debug, Formatter}; use crate::{ common::{ crypto::{ @@ -13,12 +14,13 @@ use tracing::instrument; use crate::common::crypto::algorithms::encryption::{BlockCiphers, EccCurves, SymmetricMode}; use crate::common::crypto::algorithms::KeyBits; use crate::common::traits::module_provider_config::ProviderConfig; -use crate::tpm::android::knox::interface::RustDef; use crate::tpm::android::knox::{KnoxConfig, KnoxProvider}; +use crate::tpm::android::knox::interface::jni::RustDef; use crate::tpm::core::error::TpmError::UnsupportedOperation; + /// Implements the `Provider` trait, providing cryptographic operations utilizing a TPM. /// /// This implementation is specific to Samsung Knox Vault and uses it for all cryptographic operations @@ -57,7 +59,6 @@ impl Provider for KnoxProvider { } Some(conf) => { conf } }; - let key_algo; if config.key_algorithm.is_some() && config.sym_algorithm.is_none() { key_algo = match config.key_algorithm.expect("Already checked") { @@ -136,7 +137,8 @@ impl Provider for KnoxProvider { config.sym_algorithm, config.key_algorithm))); } - RustDef::create_key(config.vm, String::from(key_id), key_algo) + let env = config.vm.get_env().unwrap(); + RustDef::create_key(env, String::from(key_id), key_algo) } /// Loads an existing cryptographic key identified by `key_id`. @@ -166,12 +168,8 @@ impl Provider for KnoxProvider { } Some(conf) => { conf } }; - - let env = match Self::jvm_to_jnienv(&config) { - Ok(value) => value, - Err(value) => return value, - }; - RustDef::load_key(&env, String::from(key_id)) + // RustDef::load_key(config.vm, String::from(key_id)) + Ok(()) } /// Initializes the TPM module and returns a handle for cryptographic operations. From 160dc16cbbff14c888eddcbdceb7bc2a22bc3db4 Mon Sep 17 00:00:00 2001 From: segelnhoch3 Date: Tue, 28 May 2024 22:23:46 +0200 Subject: [PATCH 026/121] Rust code compiles - Calling create_key() in crypto layer doesnt work --- Cargo.toml | 2 +- src/tpm/android/knox/interface.rs | 62 ------------------------------- 2 files changed, 1 insertion(+), 63 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 54c98435..9a4252eb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" license = "MIT" [lib] -crate-type = ["cdylib"] +crate-type = ["cdylib", "lib"] [profile.dev] debug-assertions = true diff --git a/src/tpm/android/knox/interface.rs b/src/tpm/android/knox/interface.rs index f0f92983..35cfb9c6 100644 --- a/src/tpm/android/knox/interface.rs +++ b/src/tpm/android/knox/interface.rs @@ -47,68 +47,6 @@ pub mod jni { /// |----------------------------------------------------------------------------------------| #[allow(non_snake_case)] impl<'env: 'borrow, 'borrow> RustDef<'env, 'borrow> { - - //------------------------------------------------------------------------------------------ - // Rust methods that can be called from Java - - ///Proof of concept - shows type conversion - /// DO NOT USE - pub extern "jni" fn special(mut input1: Vec, input2: i32) -> Vec { - input1.push(input2); - input1.push(42); - input1.iter().map(ToString::to_string).collect() - } - - ///Proof of concept method - shows callback from Rust to a java method - /// ONLY USE FOR TESTING - pub extern "jni" fn callRust( _environment: &JNIEnv) -> String { - String::from("not implemented") - } - - ///Demo method used to call functions in Rust from the Java app while testing - // pub extern "jni" fn demoCreate(environment: &JNIEnv, key_id: String, key_gen_info: String) -> () { - // Self::create_key(environment.get_java_vm().unwrap(), key_id, key_gen_info).unwrap(); - // } - - ///Demo method used to call functions in Rust from the Java app while testing - pub extern "jni" fn demoInit(environment: &JNIEnv) - -> () { - let _ = Self::initialize_module(environment); - } - - ///Demo method used to call functions in Rust from the Java app while testing - pub extern "jni" fn demoEncrypt(environment: &JNIEnv, data: Box<[u8]>) -> Box<[u8]> { - let result = Self::encrypt_data(environment, data.as_ref()) - .expect("Sign_data failed"); - result.into_boxed_slice() - } - - ///Demo method used to call functions in Rust from the Java app while testing - pub extern "jni" fn demoDecrypt(environment: &JNIEnv, data: Box<[u8]>) -> Box<[u8]> { - let result = Self::decrypt_data(environment, data.as_ref()); - return match result { - Ok(res) => { res.into_boxed_slice() } - Err(_) => { Vec::new().into_boxed_slice() } - }; - } - - ///Demo method used to call functions in Rust from the Java app while testing - pub extern "jni" fn demoSign(environment: &JNIEnv, data: Box<[u8]>) -> Box<[u8]> { - let result = Self::sign_data(environment, data.as_ref()) - .expect("Sign_data failed"); - result.into_boxed_slice() - } - - ///Demo method used to call functions in Rust from the Java app while testing - pub extern "jni" fn demoVerify(environment: &JNIEnv, data: Box<[u8]>) -> bool { - let result = Self::verify_signature(environment, data.as_ref(), data.as_ref()); - return match result { - Ok(value) => { value } - Err(_) => { false } - }; - } - - //------------------------------------------------------------------------------------------ // Java methods that can be called from rust From e81644b7d00a2b547c30165ac9a5bcec869248a9 Mon Sep 17 00:00:00 2001 From: ErikFribus Date: Wed, 29 May 2024 08:35:59 +0200 Subject: [PATCH 027/121] CryptoManager.java updated --- src/tpm/android/knox/java/CryptoManager.java | 138 ++++++++++--------- 1 file changed, 76 insertions(+), 62 deletions(-) diff --git a/src/tpm/android/knox/java/CryptoManager.java b/src/tpm/android/knox/java/CryptoManager.java index ee9bdd19..ac04e4d6 100644 --- a/src/tpm/android/knox/java/CryptoManager.java +++ b/src/tpm/android/knox/java/CryptoManager.java @@ -15,18 +15,15 @@ import java.security.KeyPairGenerator; import java.security.KeyStore; import java.security.KeyStoreException; -import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.PrivateKey; -import java.security.PublicKey; -import java.security.SecureRandom; import java.security.Signature; import java.security.SignatureException; import java.security.UnrecoverableKeyException; import java.security.cert.CertificateException; +import java.security.spec.ECGenParameterSpec; import java.security.spec.InvalidKeySpecException; -import java.util.ArrayList; import java.util.Arrays; import javax.crypto.BadPaddingException; @@ -38,14 +35,12 @@ import javax.crypto.SecretKeyFactory; import javax.crypto.spec.GCMParameterSpec; import javax.crypto.spec.IvParameterSpec; -import javax.security.auth.x500.X500Principal; public class CryptoManager { // TODO: READ AND APPROVE JAVADOC private static final String ANDROID_KEY_STORE = "AndroidKeyStore"; private final KeyStore keyStore; private String KEY_NAME; - private byte[] encryptCipher; /** * Constructs a new instance of {@code CryptoManager} with the default Android KeyStore. @@ -66,11 +61,13 @@ public CryptoManager() throws KeyStoreException { /** * Generates a new symmetric key and saves it into the Android KeyStore. *

- * This method initializes a new symmetric key for encryption and decryption purposes using the specified symmetric key algorithm. The key is stored in the Android KeyStore, leveraging the platform's secure storage capabilities. The key is configured to use GCM block mode and PKCS7 encryption padding, enhancing both performance and security. The method also ensures that the key is backed by the strong box feature, adding an extra layer of security against unauthorized access. - *

- * Before generating the key, the method sets the key name using the provided {@code key_id}. Then, it loads the Android KeyStore and proceeds to generate the key with the specified properties. Finally, the generated key is saved in the KeyStore under the provided {@code key_id}. + * This method initializes a new symmetric key for encryption and decryption purposes using the specified symmetric key algorithm. + * The key is stored in the Android KeyStore and supports various configurations including the choice of encryption algorithms, + * key sizes, block modes, and padding schemes. + * Additionally, this method ensures that the key is backed by the strong box feature. * - * @param key_id The unique identifier under which the key will be stored in the KeyStore. + * @param key_id The unique identifier under which the key will be stored in the KeyStore. + * @param keyGenInfo A string containing key generation parameters separated by semicolons. Expected format: "KEY_ALGORITHM;KEY_SIZE;BLOCK_MODE;PADDING". * @throws CertificateException if there is an issue loading the certificate chain. * @throws IOException for I/O errors such as incorrect passwords. * @throws NoSuchAlgorithmException if the generation algorithm does not exist or the keystore doesn't exist. @@ -94,7 +91,6 @@ public void genKey(String key_id, String keyGenInfo) throws CertificateException if (keyStore.containsAlias(KEY_NAME)) { throw new KeyStoreException("Key with name " + KEY_NAME + " already exists."); } - KeyGenerator keyGen = KeyGenerator.getInstance(KEY_ALGORITHM, ANDROID_KEY_STORE); keyGen.init(new KeyGenParameterSpec.Builder(KEY_NAME, KeyProperties.PURPOSE_ENCRYPT | @@ -120,44 +116,41 @@ public void genKey(String key_id, String keyGenInfo) throws CertificateException * * @param data The plaintext data to be encrypted, represented as a byte array. * @return A byte array representing the encrypted data, with the IV prepended in the case of GCM mode. - * @throws NoSuchPaddingException if the requested padding scheme is not available. - * @throws NoSuchAlgorithmException if the requested algorithm is not available. - * @throws CertificateException if there is an issue loading the certificate chain. - * @throws IOException if there is an I/O error during the operation. - * @throws InvalidKeyException if the key cannot be cast to a SecretKey. - * @throws UnrecoverableKeyException if the key cannot be recovered from the keystore. - * @throws KeyStoreException if there is an error accessing the keystore. - * @throws IllegalBlockSizeException if the data length is invalid for the encryption algorithm. - * @throws BadPaddingException if the data could not be padded correctly for encryption. - * @throws InvalidKeySpecException if the key specification is invalid. - * @throws NoSuchProviderException if the requested security provider is not available. - * @throws InvalidAlgorithmParameterException if the algorithm parameters are invalid. + * @throws NoSuchPaddingException if the requested padding scheme is not available. + * @throws NoSuchAlgorithmException if the requested algorithm is not available. + * @throws CertificateException if there is an issue loading the certificate chain. + * @throws IOException if there is an I/O error during the operation. + * @throws InvalidKeyException if the key cannot be cast to a SecretKey. + * @throws UnrecoverableKeyException if the key cannot be recovered from the keystore. + * @throws KeyStoreException if there is an error accessing the keystore. + * @throws IllegalBlockSizeException if the data length is invalid for the encryption algorithm. + * @throws BadPaddingException if the data could not be padded correctly for encryption. + * @throws InvalidKeySpecException if the key specification is invalid. + * @throws NoSuchProviderException if the requested security provider is not available. */ public byte[] encryptData(byte[] data) throws NoSuchPaddingException, NoSuchAlgorithmException, CertificateException, IOException, InvalidKeyException, UnrecoverableKeyException, KeyStoreException, IllegalBlockSizeException, BadPaddingException, InvalidKeySpecException, - NoSuchProviderException, InvalidAlgorithmParameterException { + NoSuchProviderException { + keyStore.load(null); SecretKey secretKey = (SecretKey) keyStore.getKey(KEY_NAME, null); String TRANSFORMATION = buildTransformation(secretKey); + Cipher cipher = Cipher.getInstance(TRANSFORMATION); + cipher.init(Cipher.ENCRYPT_MODE, secretKey); + byte[] iv = cipher.getIV(); if (TRANSFORMATION.contains("/GCM/")) { - byte[] iv = new byte[12]; // GCM standard IV size - SecureRandom secureRandom = new SecureRandom(); - secureRandom.nextBytes(iv); - GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(128, iv); // 128 is the recommended TagSize - cipher.init(Cipher.ENCRYPT_MODE, secretKey, gcmParameterSpec); - byte[] encryptedData = cipher.doFinal(data); - ByteBuffer byteBuffer = ByteBuffer.allocate(iv.length + encryptedData.length); - byteBuffer.put(iv); - byteBuffer.put(encryptedData); - return byteBuffer.array(); + assert iv.length == 12; // GCM standard IV size is 12 Byte } else { - cipher.init(Cipher.ENCRYPT_MODE, secretKey); - encryptCipher = cipher.getIV(); - return cipher.doFinal(data); + assert iv.length == 16; // CBC & CTR standard IV size is 16 Byte } + byte[] encryptedData = cipher.doFinal(data); + ByteBuffer byteBuffer = ByteBuffer.allocate(iv.length + encryptedData.length); + byteBuffer.put(iv); + byteBuffer.put(encryptedData); + return byteBuffer.array(); } /** @@ -193,17 +186,21 @@ public byte[] decryptData(byte[] encryptedData) throws NoSuchPaddingException, N SecretKey secretKey = (SecretKey) keyStore.getKey(KEY_NAME, null); String TRANSFORMATION = buildTransformation(secretKey); Cipher cipher = Cipher.getInstance(TRANSFORMATION); - + ByteBuffer byteBuffer = ByteBuffer.wrap(encryptedData); + byte[] iv; if (TRANSFORMATION.contains("/GCM/")) { - ByteBuffer byteBuffer = ByteBuffer.wrap(encryptedData); - byte[] iv = new byte[12]; // GCM standard IV size + iv = new byte[12]; // GCM standard IV size byteBuffer.get(iv); encryptedData = new byte[byteBuffer.remaining()]; byteBuffer.get(encryptedData); GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(128, iv); // 128 is the recommended TagSize cipher.init(Cipher.DECRYPT_MODE, secretKey, gcmParameterSpec); } else { - cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(encryptCipher)); + iv = new byte[16]; // CBC & CTR standard IV size + byteBuffer.get(iv); + encryptedData = new byte[byteBuffer.remaining()]; + byteBuffer.get(encryptedData); + cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(iv)); } return cipher.doFinal(encryptedData); } @@ -211,9 +208,11 @@ public byte[] decryptData(byte[] encryptedData) throws NoSuchPaddingException, N /** * Generates a new asymmetric key pair and saves it into the Android KeyStore. *

- * This method generates a new asymmetric key pair suitable for signing and verifying digital signatures. The key pair is stored in the Android KeyStore, leveraging the platform's secure storage capabilities. The method configures the key pair generator with specific parameters, including the subject of the certificate associated with the key pair, the digest algorithms to be supported, the signature padding scheme, and whether the key is backed by the strong box feature for enhanced security. The generated key pair consists of a private key for signing and a corresponding public key for verification. - *

- * Before generating the key pair, the method sets the key name using the provided {@code key_id}. Then, it loads the Android KeyStore and checks if a key with the specified {@code key_id} already exists. If it does, a {@link KeyStoreException} is thrown. If not, it proceeds to generate the key pair with the specified properties and saves it in the KeyStore under the provided {@code key_id}. + * This method generates a new asymmetric key pair suitable for signing and verifying digital signatures. + * The key pair is stored in the Android KeyStore, leveraging the platform's secure storage capabilities. + * The method configures the key pair generator with specific parameters, like the digest algorithms to be supported, + * the signature padding scheme, and whether the key is backed by the strong box feature for enhanced security. + * The generated key pair consists of a private key for signing and a corresponding public key for verification. * * @param key_id The unique identifier under which the key pair will be stored in the KeyStore. * @throws CertificateException if there is an issue creating the certificate for the key pair. @@ -227,10 +226,9 @@ public void generateKeyPair(String key_id, String keyGenInfo) throws Certificate NoSuchAlgorithmException, InvalidAlgorithmParameterException, NoSuchProviderException, KeyStoreException { String[] keyGenInfoArr = keyGenInfo.split(";"); + System.out.println(Arrays.toString(keyGenInfoArr)); String KEY_ALGORITHM = keyGenInfoArr[0]; - int KEY_SIZE = Integer.parseInt(keyGenInfoArr[1]); String HASH = keyGenInfoArr[2]; - String PADDING = keyGenInfoArr[3]; KEY_NAME = key_id; keyStore.load(null); @@ -241,25 +239,38 @@ public void generateKeyPair(String key_id, String keyGenInfo) throws Certificate } KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(KEY_ALGORITHM, ANDROID_KEY_STORE); - keyPairGen.initialize(new KeyGenParameterSpec.Builder(KEY_NAME, - KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY) - .setCertificateSubject(new X500Principal("CN=" + KEY_NAME)) - .setAttestationChallenge(null) - .setKeySize(KEY_SIZE) - .setDigests(HASH) - .setSignaturePaddings(PADDING) - .setUserAuthenticationRequired(false) - .setIsStrongBoxBacked(true) - .build()); + if (KEY_ALGORITHM.contains("EC")) { + String CURVE = keyGenInfoArr[1]; + keyPairGen.initialize( + new KeyGenParameterSpec.Builder( + KEY_NAME, + KeyProperties.PURPOSE_SIGN) + .setAlgorithmParameterSpec(new ECGenParameterSpec(CURVE)) + .setDigests(HASH) + .build()); + + } else { + int KEY_SIZE = Integer.parseInt(keyGenInfoArr[1]); + String PADDING = keyGenInfoArr[3]; + keyPairGen.initialize(new KeyGenParameterSpec.Builder(KEY_NAME, + KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY) + .setKeySize(KEY_SIZE) + .setDigests(HASH) + .setSignaturePaddings(PADDING) + .setIsStrongBoxBacked(true) + .build()); + } keyPairGen.generateKeyPair(); } /** * Signs the given data using a private key stored in the Android KeyStore. *

- * This method takes plaintext data as input and signs it using a private key retrieved from the Android KeyStore. The signing process uses a predefined signature algorithm. The method initializes a {@link Signature} instance with this algorithm, loads the Android KeyStore, retrieves the private key, and then initializes the signature object in sign mode with the retrieved private key. The plaintext data is then updated into the signature object, and finally, the data is signed using the signature object's {@code sign} method. The resulting signature is returned as a byte array. - *

- * Signing data is a critical part of many cryptographic operations, particularly in establishing the integrity and authenticity of data. It allows recipients to verify that the data has not been tampered with and was indeed sent by the holder of the corresponding private key. + * This method takes plaintext data as input and signs it using a private key retrieved from the Android KeyStore. + * The signing process uses a predefined signature algorithm. The method initializes a {@link Signature} instance with this algorithm, + * loads the Android KeyStore, retrieves the private key, and then initializes the signature object in sign mode with the retrieved private key. + * The plaintext data is then updated into the signature object, and finally, the data is signed using the signature object's {@code sign} method. + * The resulting signature is returned as a byte array. * * @param data The plaintext data to be signed, represented as a byte array. * @return A byte array representing the signature of the data. @@ -317,7 +328,7 @@ public boolean verifySignature(byte[] data, byte[] signedBytes) throws Signature * @return An array of Byte objects corresponding to the elements of the input byte array. * @throws NullPointerException if the input byte array is null. */ - public Byte[] toByte(byte[] bytesPrim) { + public Byte[] toByte(byte[] bytesPrim) { // TODO: still needed? if (bytesPrim == null) { throw new NullPointerException("Input byte array cannot be null."); } @@ -364,7 +375,7 @@ private String buildTransformation(Key key) throws NullPointerException, Certifi keyStore.load(null); KeyInfo keyInfo; String keyAlgorithm = key.getAlgorithm(); - String keyPadding = ""; + String keyPadding; if (key instanceof SecretKey) { SecretKey secretKey = (SecretKey) key; @@ -402,8 +413,11 @@ private String buildSignatureAlgorithm(PrivateKey privateKey) throws NoSuchAlgor InvalidKeySpecException { KeyFactory keyFactory = KeyFactory.getInstance(privateKey.getAlgorithm(), ANDROID_KEY_STORE); KeyInfo keyInfo = keyFactory.getKeySpec(privateKey, KeyInfo.class); - String hashAlgorithm = keyInfo.getDigests()[0]; + String hashAlgorithm = keyInfo.getDigests()[0].replaceAll("-", ""); String algorithm = privateKey.getAlgorithm(); + if (algorithm.contains("EC")) { + algorithm += "DSA"; + } return hashAlgorithm + "with" + algorithm; } From 0e43a9ea755a7f9c21a3af6c9c6b707afb686d9d Mon Sep 17 00:00:00 2001 From: Umut Date: Wed, 29 May 2024 11:01:12 +0200 Subject: [PATCH 028/121] load_key methode ansatz --- src/tpm/android/knox/provider.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/tpm/android/knox/provider.rs b/src/tpm/android/knox/provider.rs index bc736941..4d97e2c1 100644 --- a/src/tpm/android/knox/provider.rs +++ b/src/tpm/android/knox/provider.rs @@ -168,7 +168,12 @@ impl Provider for KnoxProvider { } Some(conf) => { conf } }; - // RustDef::load_key(config.vm, String::from(key_id)) + let environment = config.vm.get_env().unwrap(); + let key_id = key_id.to_string(); + + // Call the create_key method with the correct parameters + RustDef::load_key(environment, key_id)?; + Ok(()) } @@ -200,7 +205,7 @@ impl Provider for KnoxProvider { /// /// # Examples /// - /// ```rust + /// rust /// let result = module.initialize_module( /// AsymmetricEncryption::Rsa(KeyBits::Bits2048), /// Some(BlockCiphers::Aes(KeyBits::Bits256)), From 922050f7730bcd97f7072ded424f2248c7b11f44 Mon Sep 17 00:00:00 2001 From: segelnhoch3 Date: Fri, 31 May 2024 07:44:35 +0200 Subject: [PATCH 029/121] implemented creation of new AndroidProvider --- src/tests/common/traits/mod.rs | 2 +- src/tpm/android/knox/key_handle.rs | 134 ++++++++++++++--------------- src/tpm/core/instance.rs | 6 +- 3 files changed, 73 insertions(+), 69 deletions(-) diff --git a/src/tests/common/traits/mod.rs b/src/tests/common/traits/mod.rs index ddbc8b0e..c5d207e0 100644 --- a/src/tests/common/traits/mod.rs +++ b/src/tests/common/traits/mod.rs @@ -37,7 +37,7 @@ impl LogConfig for Logger { } } -fn setup_security_module(module: SecurityModule) -> Arc> { +pub fn setup_security_module(module: SecurityModule) -> Arc> { let log = Logger::new_boxed(); match module { #[cfg(feature = "hsm")] diff --git a/src/tpm/android/knox/key_handle.rs b/src/tpm/android/knox/key_handle.rs index 958f0eca..745b43ae 100644 --- a/src/tpm/android/knox/key_handle.rs +++ b/src/tpm/android/knox/key_handle.rs @@ -9,71 +9,71 @@ use crate::tpm::android::knox::interface::jni::RustDef; /// Provides cryptographic operations for asymmetric keys on Windows, /// such as signing, encryption, decryption, and signature verification. impl KeyHandle for KnoxProvider { - /// Signs data using the cryptographic key. - /// - /// This method hashes the input data using SHA-256 and then signs the hash. - /// It leverages the NCryptSignHash function from the Windows CNG API. - /// - /// # Arguments - /// - /// * `data` - The data to be signed. - /// - /// # Returns - /// - /// A `Result` containing the signature as a `Vec` on success, or a `SecurityModuleError` on failure. - #[instrument] - fn sign_data(&self, data: &[u8]) -> Result, SecurityModuleError> { - RustDef::sign_data(data) - } - - /// Decrypts data encrypted with the corresponding public key. - /// - /// Utilizes the NCryptDecrypt function from the Windows CNG API. - /// - /// # Arguments - /// - /// * `encrypted_data` - The data to be decrypted. - /// - /// # Returns - /// - /// A `Result` containing the decrypted data as a `Vec` on success, or a `SecurityModuleError` on failure. - #[instrument] - fn decrypt_data(&self, encrypted_data: &[u8]) -> Result, SecurityModuleError> { - RustDef::decrypt_data(encrypted_data) - } - - /// Encrypts data with the cryptographic key. - /// - /// Uses the NCryptEncrypt function from the Windows CNG API. - /// - /// # Arguments - /// - /// * `data` - The data to be encrypted. - /// - /// # Returns - /// - /// A `Result` containing the encrypted data as a `Vec` on success, or a `SecurityModuleError` on failure. - #[instrument] - fn encrypt_data(&self, data: &[u8]) -> Result, SecurityModuleError> { - RustDef::encrypt_data(data) - } - - /// Verifies a signature against the provided data. - /// - /// This method hashes the input data using SHA-256 and then verifies the signature. - /// It relies on the NCryptVerifySignature function from the Windows CNG API. - /// - /// # Arguments - /// - /// * `data` - The original data associated with the signature. - /// * `signature` - The signature to be verified. - /// - /// # Returns - /// - /// A `Result` indicating whether the signature is valid (`true`) or not (`false`), - /// or a `SecurityModuleError` on failure. - #[instrument] - fn verify_signature(&self, data: &[u8], signature: &[u8]) -> Result { - RustDef::verify_signature(data, signature) - } + // /// Signs data using the cryptographic key. + // /// + // /// This method hashes the input data using SHA-256 and then signs the hash. + // /// It leverages the NCryptSignHash function from the Windows CNG API. + // /// + // /// # Arguments + // /// + // /// * `data` - The data to be signed. + // /// + // /// # Returns + // /// + // /// A `Result` containing the signature as a `Vec` on success, or a `SecurityModuleError` on failure. + // #[instrument] + // fn sign_data(&self, data: &[u8]) -> Result, SecurityModuleError> { + // RustDef::sign_data(data) + // } + // + // /// Decrypts data encrypted with the corresponding public key. + // /// + // /// Utilizes the NCryptDecrypt function from the Windows CNG API. + // /// + // /// # Arguments + // /// + // /// * `encrypted_data` - The data to be decrypted. + // /// + // /// # Returns + // /// + // /// A `Result` containing the decrypted data as a `Vec` on success, or a `SecurityModuleError` on failure. + // #[instrument] + // fn decrypt_data(&self, encrypted_data: &[u8]) -> Result, SecurityModuleError> { + // RustDef::decrypt_data(encrypted_data) + // } + // + // /// Encrypts data with the cryptographic key. + // /// + // /// Uses the NCryptEncrypt function from the Windows CNG API. + // /// + // /// # Arguments + // /// + // /// * `data` - The data to be encrypted. + // /// + // /// # Returns + // /// + // /// A `Result` containing the encrypted data as a `Vec` on success, or a `SecurityModuleError` on failure. + // #[instrument] + // fn encrypt_data(&self, data: &[u8]) -> Result, SecurityModuleError> { + // RustDef::encrypt_data(data) + // } + // + // /// Verifies a signature against the provided data. + // /// + // /// This method hashes the input data using SHA-256 and then verifies the signature. + // /// It relies on the NCryptVerifySignature function from the Windows CNG API. + // /// + // /// # Arguments + // /// + // /// * `data` - The original data associated with the signature. + // /// * `signature` - The signature to be verified. + // /// + // /// # Returns + // /// + // /// A `Result` indicating whether the signature is valid (`true`) or not (`false`), + // /// or a `SecurityModuleError` on failure. + // #[instrument] + // fn verify_signature(&self, data: &[u8], signature: &[u8]) -> Result { + // RustDef::verify_signature(data, signature) + // } } diff --git a/src/tpm/core/instance.rs b/src/tpm/core/instance.rs index 631bc857..69bc6dea 100644 --- a/src/tpm/core/instance.rs +++ b/src/tpm/core/instance.rs @@ -123,7 +123,11 @@ impl TpmInstance { Arc::new(Mutex::new(instance)) } #[cfg(feature = "android")] - TpmType::Android(_tpm_type) => todo!(), + TpmType::Android(tpm_type) => match tpm_type { + AndroidTpmType::Knox => Arc::new(Mutex::new( + crate::tpm::android::knox::KnoxProvider::new(), + )), + }, TpmType::None => todo!(), } } From 044724e176797168e0031ca42c4f8cda6c80880c Mon Sep 17 00:00:00 2001 From: MasterChief06 <164910544+MasterChief06@users.noreply.github.com> Date: Fri, 31 May 2024 09:35:55 +0200 Subject: [PATCH 030/121] Create Doc.md --- Documentation/Doc.md | 84 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 Documentation/Doc.md diff --git a/Documentation/Doc.md b/Documentation/Doc.md new file mode 100644 index 00000000..b7a006ee --- /dev/null +++ b/Documentation/Doc.md @@ -0,0 +1,84 @@ +# Project Documentation + +## Introduction + +### Product Description + +### Problem Description + +## Installation Guide + +### Required Software +- **Android Studio** +- Additional tools and dependencies + +## Implementations + +### Supported Devices + +### Devices We Tested On + +### Performance + +### Feature List + +### Supported Algorithms + +### Out of Scope + +## Example Usage with Our Custom App + +### Code Examples + +## Risk Management + +### Retrospective + +### Etc. + +## Architecture + +### Component Diagram + +### Explanation + +### Abstraction Layer + +### Libraries +- **Robusta** +- **JNI** +- **KeyStore API** + +## Implementation + +### Code + +### Connection Documentation + +### Javadoc + +### Rustdoc + +## Next Steps + +### Ideas + +### What Could Be Done + +### What Can Be Improved + +### Etc. + +## Open Source Project + +### License + +### Issue Guide + +### Pull Request Guide + +## References + +### Source Documents + +### Research Documents From 9c8261566ab22ad2d4b39268cf2a77dab48fffab Mon Sep 17 00:00:00 2001 From: MasterChief06 <164910544+MasterChief06@users.noreply.github.com> Date: Fri, 31 May 2024 09:38:53 +0200 Subject: [PATCH 031/121] Update Doc.md --- Documentation/Doc.md | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/Documentation/Doc.md b/Documentation/Doc.md index b7a006ee..fca1c01e 100644 --- a/Documentation/Doc.md +++ b/Documentation/Doc.md @@ -1,5 +1,48 @@ # Project Documentation +## Table of Contents + +1. [Introduction](#introduction) + - [Product Description](#product-description) + - [Problem Description](#problem-description) +2. [Installation Guide](#installation-guide) + - [Required Software](#required-software) +3. [Implementations](#implementations) + - [Supported Devices](#supported-devices) + - [Devices We Tested On](#devices-we-tested-on) + - [Performance](#performance) + - [Feature List](#feature-list) + - [Supported Algorithms](#supported-algorithms) + - [Out of Scope](#out-of-scope) +4. [Example Usage with Our Custom App](#example-usage-with-our-custom-app) + - [Code Examples](#code-examples) +5. [Risk Management](#risk-management) + - [Retrospective](#retrospective) + - [Etc.](#etc) +6. [Architecture](#architecture) + - [Component Diagram](#component-diagram) + - [Explanation](#explanation) + - [Abstraction Layer](#abstraction-layer) + - [Libraries](#libraries) +7. [Implementation](#implementation) + - [Code](#code) + - [Connection Documentation](#connection-documentation) + - [Javadoc](#javadoc) + - [Rustdoc](#rustdoc) +8. [Next Steps](#next-steps) + - [Ideas](#ideas) + - [What Could Be Done](#what-could-be-done) + - [What Can Be Improved](#what-can-be-improved) + - [Etc.](#etc-1) +9. [Open Source Project](#open-source-project) + - [License](#license) + - [Issue Guide](#issue-guide) + - [Pull Request Guide](#pull-request-guide) +10. [References](#references) + - [Source Documents](#source-documents) + - [Research Documents](#research-documents) + + ## Introduction ### Product Description From 4c342830aef46800f0d653a8e93375e605bf9c1c Mon Sep 17 00:00:00 2001 From: MasterChief06 <164910544+MasterChief06@users.noreply.github.com> Date: Fri, 31 May 2024 10:39:48 +0200 Subject: [PATCH 032/121] Update Doc.md new structure --- Documentation/Doc.md | 82 ++++++++++++++++++++------------------------ 1 file changed, 38 insertions(+), 44 deletions(-) diff --git a/Documentation/Doc.md b/Documentation/Doc.md index fca1c01e..9aaca7a1 100644 --- a/Documentation/Doc.md +++ b/Documentation/Doc.md @@ -1,39 +1,36 @@ -# Project Documentation - ## Table of Contents 1. [Introduction](#introduction) - - [Product Description](#product-description) - [Problem Description](#problem-description) -2. [Installation Guide](#installation-guide) + - [Product Description](#product-description) +2. [Architecture](#architecture) + - [Component Diagram](#component-diagram) + - [Explanation](#explanation) + - [Abstraction Layer](#abstraction-layer) + - [Libraries](#libraries) +3. [Installation Guide](#installation-guide) - [Required Software](#required-software) -3. [Implementations](#implementations) +4. [Implementations](#implementations) - [Supported Devices](#supported-devices) - [Devices We Tested On](#devices-we-tested-on) - [Performance](#performance) - [Feature List](#feature-list) - [Supported Algorithms](#supported-algorithms) - [Out of Scope](#out-of-scope) -4. [Example Usage with Our Custom App](#example-usage-with-our-custom-app) - - [Code Examples](#code-examples) -5. [Risk Management](#risk-management) - - [Retrospective](#retrospective) - - [Etc.](#etc) -6. [Architecture](#architecture) - - [Component Diagram](#component-diagram) - - [Explanation](#explanation) - - [Abstraction Layer](#abstraction-layer) - - [Libraries](#libraries) -7. [Implementation](#implementation) +5. [Implementation](#implementation) - [Code](#code) - [Connection Documentation](#connection-documentation) - [Javadoc](#javadoc) - [Rustdoc](#rustdoc) +6. [Example Usage with Our Custom App](#example-usage-with-our-custom-app) + - [Code Examples](#code-examples) +7. [Risk Management](#risk-management) + - [Retrospective](#retrospective) + - [Risk Identification](#risk-identification) 8. [Next Steps](#next-steps) - [Ideas](#ideas) - [What Could Be Done](#what-could-be-done) - [What Can Be Improved](#what-can-be-improved) - - [Etc.](#etc-1) 9. [Open Source Project](#open-source-project) - [License](#license) - [Issue Guide](#issue-guide) @@ -42,12 +39,24 @@ - [Source Documents](#source-documents) - [Research Documents](#research-documents) - ## Introduction +### Problem Description + ### Product Description -### Problem Description +## Architecture + +### Component Diagram + +### Explanation + +### Abstraction Layer + +### Libraries +- **Robusta** +- **JNI** +- **KeyStore API** ## Installation Guide @@ -69,29 +78,6 @@ ### Out of Scope -## Example Usage with Our Custom App - -### Code Examples - -## Risk Management - -### Retrospective - -### Etc. - -## Architecture - -### Component Diagram - -### Explanation - -### Abstraction Layer - -### Libraries -- **Robusta** -- **JNI** -- **KeyStore API** - ## Implementation ### Code @@ -102,6 +88,16 @@ ### Rustdoc +## Example Usage with Our Custom App + +### Code Examples + +## Risk Management + +### Retrospective + +### Risk Identification + ## Next Steps ### Ideas @@ -110,8 +106,6 @@ ### What Can Be Improved -### Etc. - ## Open Source Project ### License From 6971ce71e2b32e482dd74b78cfdc7ab3be9c6658 Mon Sep 17 00:00:00 2001 From: MasterChief06 <164910544+MasterChief06@users.noreply.github.com> Date: Fri, 31 May 2024 11:07:34 +0200 Subject: [PATCH 033/121] Update Doc.md --- Documentation/Doc.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Documentation/Doc.md b/Documentation/Doc.md index 9aaca7a1..42fca8a7 100644 --- a/Documentation/Doc.md +++ b/Documentation/Doc.md @@ -40,15 +40,17 @@ - [Research Documents](#research-documents) ## Introduction +In today's digital era, safeguarding sensitive data on mobile devices is paramount. Our project focuses on enhancing data security by developing a wrapper for the Crypto Abstraction Layer. This wrapper enables access to a Hardware Security Module (HSM) through Samsung Knox Vault. By securely storing encryption keys within the HSM, we ensure robust protection for data stored on Samsung devices. ### Problem Description +In today's digital age, the security of data stored on mobile devices is of paramount importance. Sensitive information, whether personal or professional, is frequently stored on smartphones, making them a prime target for cyber threats. Ensuring the confidentiality, integrity, and accessibility of this data requires robust encryption and secure key management solutions. ### Product Description - +Our project aims to address this critical need by developing a comprehensive solution for encrypting data stored on mobile devices. Specifically, our task is to write a wrapper for the proposed Crypto Abstraction Layer to access a specific hardware security module (HSM) on Samsung devices using Samsung Knox Vault. This solution ensures that encryption keys are securely stored and managed within the HSM, providing an added layer of security for the encrypted data. ## Architecture ### Component Diagram - +![Alt Text](path/to/image.jpg) ### Explanation ### Abstraction Layer From f1bc9bfbddb489fdf31bccda46f15acb06f37928 Mon Sep 17 00:00:00 2001 From: MasterChief06 <164910544+MasterChief06@users.noreply.github.com> Date: Fri, 31 May 2024 11:12:52 +0200 Subject: [PATCH 034/121] Add files via upload --- Documentation/Komp.png | Bin 0 -> 42712 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 Documentation/Komp.png diff --git a/Documentation/Komp.png b/Documentation/Komp.png new file mode 100644 index 0000000000000000000000000000000000000000..b402c0958ef548bd2e49a4cb6ce319acfeab0588 GIT binary patch literal 42712 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 W8Z<@zm{agNc#-!}eJZ^^KKBpzCv?&P literal 0 HcmV?d00001 From 77a2e7f60c7fc96293afa22bed9a59a884069829 Mon Sep 17 00:00:00 2001 From: MasterChief06 <164910544+MasterChief06@users.noreply.github.com> Date: Fri, 31 May 2024 11:33:53 +0200 Subject: [PATCH 035/121] Update Doc.md --- Documentation/Doc.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Documentation/Doc.md b/Documentation/Doc.md index 42fca8a7..d2eb760a 100644 --- a/Documentation/Doc.md +++ b/Documentation/Doc.md @@ -50,7 +50,8 @@ Our project aims to address this critical need by developing a comprehensive sol ## Architecture ### Component Diagram -![Alt Text](path/to/image.jpg) +![Komponentendiagramm](../Documentation/Komp.png) + ### Explanation ### Abstraction Layer From a50d77ecea76daf3cd79435155d9cb87146e2afc Mon Sep 17 00:00:00 2001 From: MasterChief06 <164910544+MasterChief06@users.noreply.github.com> Date: Fri, 31 May 2024 11:43:02 +0200 Subject: [PATCH 036/121] Add files via upload Komponentendiagramm, Hintergrund weiss --- Documentation/BSP.jpg | Bin 0 -> 23673 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 Documentation/BSP.jpg diff --git a/Documentation/BSP.jpg b/Documentation/BSP.jpg new file mode 100644 index 0000000000000000000000000000000000000000..830297518de94cabd9a7ab6072927386b026d7e2 GIT binary patch literal 23673 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 Vj5 Date: Fri, 31 May 2024 11:46:02 +0200 Subject: [PATCH 037/121] Update Doc.md Komponentendiagramm with white background --- Documentation/Doc.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/Doc.md b/Documentation/Doc.md index d2eb760a..d78de894 100644 --- a/Documentation/Doc.md +++ b/Documentation/Doc.md @@ -50,7 +50,7 @@ Our project aims to address this critical need by developing a comprehensive sol ## Architecture ### Component Diagram -![Komponentendiagramm](../Documentation/Komp.png) +![Komponentendiagramm](../Documentation/BSP.jpg) ### Explanation From 3d20994110da0514ec0b58602356fc627be6503d Mon Sep 17 00:00:00 2001 From: segelnhoch3 Date: Fri, 31 May 2024 14:43:17 +0200 Subject: [PATCH 038/121] reworked ProviderConfig to Any create_key() and load_key() now accept Box instead of Box --- src/common/factory.rs | 1 - src/common/traits/module_provider.rs | 7 +- src/tpm/android/knox/interface.rs | 30 +++------ src/tpm/android/knox/mod.rs | 28 +++++--- src/tpm/android/knox/provider.rs | 97 +++++++++++++++------------- 5 files changed, 86 insertions(+), 77 deletions(-) diff --git a/src/common/factory.rs b/src/common/factory.rs index b5380e03..737995a9 100644 --- a/src/common/factory.rs +++ b/src/common/factory.rs @@ -132,7 +132,6 @@ impl SecModule { SecurityModule::Hsm(hsm_type) => Some(HsmInstance::create_instance(key_id, hsm_type)), #[cfg(feature = "tpm")] SecurityModule::Tpm(tpm_type) => Some(TpmInstance::create_instance(key_id, tpm_type)), - _ => unimplemented!(), } } } diff --git a/src/common/traits/module_provider.rs b/src/common/traits/module_provider.rs index 50cc035b..0dd73d7e 100644 --- a/src/common/traits/module_provider.rs +++ b/src/common/traits/module_provider.rs @@ -1,4 +1,5 @@ -use super::{key_handle::KeyHandle, module_provider_config::ProviderConfig}; +use std::any::Any; +use super::key_handle::KeyHandle; use crate::common::error::SecurityModuleError; use std::fmt::Debug; @@ -29,7 +30,7 @@ pub trait Provider: Send + Sync + KeyHandle + Debug { fn create_key( &mut self, key_id: &str, - config: Box, + config: Box, ) -> Result<(), SecurityModuleError>; /// Loads an existing cryptographic key identified by `key_id`. @@ -49,7 +50,7 @@ pub trait Provider: Send + Sync + KeyHandle + Debug { fn load_key( &mut self, key_id: &str, - config: Box, + config: Box, ) -> Result<(), SecurityModuleError>; /// Initializes the security module and returns a handle for further operations. diff --git a/src/tpm/android/knox/interface.rs b/src/tpm/android/knox/interface.rs index 35cfb9c6..1efc3d35 100644 --- a/src/tpm/android/knox/interface.rs +++ b/src/tpm/android/knox/interface.rs @@ -8,10 +8,6 @@ pub mod jni { use robusta_jni::jni::errors::Error; use robusta_jni::jni::JNIEnv; use robusta_jni::jni::objects::JValue; - // use jni::JNIEnv; - // use jni::errors::Error; - // use jni::objects::JValue; - use robusta_jni::jni::JavaVM; use robusta_jni::jni::objects::AutoLocal; use robusta_jni::jni::sys::jbyteArray; use crate::SecurityModuleError; @@ -70,8 +66,7 @@ pub mod jni { /// /// # Arguments /// `key_id` - String that uniquely identifies the key so that it can be retrieved later - pub fn create_key(environment: JNIEnv, key_id: String, key_gen_info: String) -> Result<(), SecurityModuleError> { - // let environment = vm.get_env().unwrap(); + pub fn create_key(environment: &JNIEnv, key_id: String, key_gen_info: String) -> Result<(), SecurityModuleError> { let result = environment.call_static_method( "com/example/vulcans_limes/RustDef", "create_key", @@ -91,8 +86,7 @@ pub mod jni { } Error::JavaException => { Err(SecurityModuleError::InitializationError( - String::from("Failed to create key: Some exception occurred in Java. - Check console for details") + String::from("Failed to create key: Some exception occurred in Java. Check console for details") )) } _ => { @@ -115,7 +109,7 @@ pub mod jni { pub fn load_key(environment: &JNIEnv, key_id: String) -> Result<(), SecurityModuleError> { let result = environment.call_static_method( "com/example/vulcans_limes/RustDef", - "create_key", + "load_key", "(Ljava/lang/String;)V", &[JValue::from(environment.new_string(key_id).unwrap())], ); @@ -131,8 +125,7 @@ pub mod jni { } Error::JavaException => { Err(SecurityModuleError::InitializationError( - String::from("Failed to load key: Some exception occurred in Java. - Check console for details") + String::from("Failed to load key: Some exception occurred in Java. Check console for details") )) } _ => { @@ -179,8 +172,7 @@ pub mod jni { match e { Error::JavaException => { Err(SecurityModuleError::InitializationError( - String::from("Failed to initialise Module: Some exception occurred in Java. - Check console for details") + String::from("Failed to initialise Module: Some exception occurred in Java. Check console for details") )) } _ => { @@ -230,8 +222,7 @@ pub mod jni { } Error::JavaException => { Err(SecurityModuleError::SigningError( - String::from("Failed to sign data: Some exception occurred in Java. - Check console for details") + String::from("Failed to sign data: Some exception occurred in Java. Check console for details") )) } _ => { @@ -282,8 +273,7 @@ pub mod jni { } Error::JavaException => { Err(SecurityModuleError::SignatureVerificationError( - String::from("Failed to verify signature: Some exception occurred in Java. - Check console for details") + String::from("Failed to verify signature: Some exception occurred in Java. Check console for details") )) } _ => { @@ -333,8 +323,7 @@ pub mod jni { } Error::JavaException => { Err(SecurityModuleError::EncryptionError( - String::from("Failed to encrypt data: Some exception occurred in Java. - Check console for details") + String::from("Failed to encrypt data: Some exception occurred in Java. Check console for details") )) } _ => { @@ -385,8 +374,7 @@ pub mod jni { } Error::JavaException => { Err(SecurityModuleError::DecryptionError( - String::from("Failed to decrypt data: Some exception occurred in Java. - Check console for details") + String::from("Failed to decrypt data: Some exception occurred in Java. Check console for details") )) } _ => { diff --git a/src/tpm/android/knox/mod.rs b/src/tpm/android/knox/mod.rs index 9b9fffbf..51039987 100644 --- a/src/tpm/android/knox/mod.rs +++ b/src/tpm/android/knox/mod.rs @@ -14,8 +14,17 @@ pub mod provider; /// cryptographic operations in an Samsung environment. This provider uses the Java Native Interface /// and the Android Keystore API to access the TPM "Knox Vault" developed by Samsung #[repr(C)] -#[derive(Debug)] -pub struct KnoxProvider {} +pub struct KnoxProvider { + config: Option, +} + +impl Debug for KnoxProvider { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("KnoxProvider") + .field("config", &self.config) + .finish() + } +} impl KnoxProvider { /// Constructs a new `TpmProvider`. @@ -26,7 +35,11 @@ impl KnoxProvider { /// A new instance of `TpmProvider`. #[instrument] pub fn new() -> Self { - Self {} + Self { config: None } + } + + fn set_config(&mut self, config: KnoxConfig) -> () { + self.config = Some(config); } } @@ -36,7 +49,6 @@ impl KnoxProvider { pub struct KnoxConfig { pub key_algorithm: Option, pub sym_algorithm: Option, - // pub env: JNIEnv<'a>, pub vm: JavaVM } @@ -45,6 +57,7 @@ impl Debug for KnoxConfig { f.debug_struct("KnoxConfig") .field("key_algorithm", &self.key_algorithm) .field("sym_algorithm", &self.sym_algorithm) + .field("JavaVM", &"Contains a JavaVM to interact with Java") .finish() } } @@ -60,13 +73,12 @@ impl KnoxConfig { pub fn new( key_algorithm: Option, sym_algorithm: Option, - // env: JNIEnv<'a> vm: JavaVM - ) -> Box { - Box::new(Self { + ) -> KnoxConfig { + Self { key_algorithm, sym_algorithm, vm, - }) + } } } \ No newline at end of file diff --git a/src/tpm/android/knox/provider.rs b/src/tpm/android/knox/provider.rs index 4d97e2c1..df9e1711 100644 --- a/src/tpm/android/knox/provider.rs +++ b/src/tpm/android/knox/provider.rs @@ -1,4 +1,5 @@ -use std::fmt::{Debug, Formatter}; +use std::any::Any; +use robusta_jni::jni:: JNIEnv; use crate::{ common::{ crypto::{ @@ -13,14 +14,11 @@ use crate::{ use tracing::instrument; use crate::common::crypto::algorithms::encryption::{BlockCiphers, EccCurves, SymmetricMode}; use crate::common::crypto::algorithms::KeyBits; -use crate::common::traits::module_provider_config::ProviderConfig; use crate::tpm::android::knox::{KnoxConfig, KnoxProvider}; use crate::tpm::android::knox::interface::jni::RustDef; use crate::tpm::core::error::TpmError::UnsupportedOperation; - - /// Implements the `Provider` trait, providing cryptographic operations utilizing a TPM. /// /// This implementation is specific to Samsung Knox Vault and uses it for all cryptographic operations @@ -50,18 +48,18 @@ impl Provider for KnoxProvider { /// /// A `Result` that, on success, contains `Ok(())`, indicating that the key was created successfully. /// On failure, it returns a `SecurityModuleError`. - #[instrument] - fn create_key(&mut self, key_id: &str, config: Box) -> Result<(), SecurityModuleError> { - let config = match config.as_any().downcast_ref::() { - None => { - return Err(SecurityModuleError::CreationError( - String::from("Wrong type used for ProviderConfig in create_key()"))); - } - Some(conf) => { conf } - }; + #[instrument(skip(config))] + fn create_key(&mut self, key_id: &str, config: Box) -> Result<(), SecurityModuleError> { + let config = Self::downcast_config(config)?; + let sym_alg = config.sym_algorithm; + let asym_alg = config.key_algorithm; + + //Stores the vm for future access + self.set_config(config); + let key_algo; - if config.key_algorithm.is_some() && config.sym_algorithm.is_none() { - key_algo = match config.key_algorithm.expect("Already checked") { + if asym_alg.is_some() && sym_alg.is_none() { + key_algo = match asym_alg.expect("Already checked") { AsymmetricEncryption::Rsa(bitslength) => { match bitslength { KeyBits::Bits128 => { String::from("RSA;128;SHA-256;PKCS1") } @@ -82,24 +80,25 @@ impl Provider for KnoxProvider { EccCurves::P256 => { String::from("EC;secp256r1;SHA-256") } EccCurves::P384 => { String::from("EC;secp384r1;SHA-256") } EccCurves::P521 => { String::from("EC;secp521r1;SHA-256") } - // EccCurves::Curve25519 => { String::from("EC;X25519;SHA-256") } <- x25519 may ONLY be used for key agreement, not signing + // EccCurves::Curve25519 => { String::from("EC;X25519;SHA-256") } <- x25519 may ONLY be used for key agreement, not signing _ => { return Err(SecurityModuleError::Tpm(UnsupportedOperation( format!("Unsupported asymmetric encryption algorithm: {:?}", - config.key_algorithm)))); + asym_alg)))); } } } - _ => {return Err(SecurityModuleError::Tpm(UnsupportedOperation( - format!("Unsupported asymmetric encryption algorithm: {:?}", - config.key_algorithm)))); + _ => { + return Err(SecurityModuleError::Tpm(UnsupportedOperation( + format!("Unsupported asymmetric encryption algorithm: {:?}", + asym_alg)))); } } } }; - } else if config.key_algorithm.is_none() && config.sym_algorithm.is_some() { - key_algo = match config.sym_algorithm.expect("Already checked") { - BlockCiphers::Des => { String::from("DESede;CBC;PKCS7Padding") }, + } else if asym_alg.is_none() && sym_alg.is_some() { + key_algo = match sym_alg.expect("Already checked") { + BlockCiphers::Des => { String::from("DESede;CBC;PKCS7Padding") } BlockCiphers::Aes(block, bitslength) => { let mut rv = String::from("AES;"); @@ -109,7 +108,7 @@ impl Provider for KnoxProvider { KeyBits::Bits256 => { rv += "256;"; } _ => { return Err(SecurityModuleError::Tpm(UnsupportedOperation( - format!("Unsupported symmetric encryption algorithm: {:?}", config.sym_algorithm)))); + format!("Unsupported symmetric encryption algorithm: {:?}", sym_alg)))); } } match block { //todo: check if paddings match blocking modes @@ -118,14 +117,14 @@ impl Provider for KnoxProvider { SymmetricMode::Ctr => { rv += "CTR;NoPadding" } _ => { return Err(SecurityModuleError::Tpm(UnsupportedOperation( - format!("Unsupported symmetric encryption algorithm: {:?}", config.sym_algorithm)))); + format!("Unsupported symmetric encryption algorithm: {:?}", sym_alg)))); } } rv - }, + } _ => { return Err(SecurityModuleError::Tpm(UnsupportedOperation( - format!("Unsupported symmetric encryption algorithm: {:?}", config.sym_algorithm)))); + format!("Unsupported symmetric encryption algorithm: {:?}", sym_alg)))); } }; } else { @@ -134,11 +133,10 @@ impl Provider for KnoxProvider { Exactly one of either sym_algorithm or key_algorithm must be Some().\ sym_algorithm: {:?}\ key_algorithm: {:?}", - config.sym_algorithm, - config.key_algorithm))); + sym_alg, + asym_alg))); } - let env = config.vm.get_env().unwrap(); - RustDef::create_key(env, String::from(key_id), key_algo) + RustDef::create_key(&self.get_env()?, String::from(key_id), key_algo) } /// Loads an existing cryptographic key identified by `key_id`. @@ -160,21 +158,14 @@ impl Provider for KnoxProvider { /// A `Result` that, on success, contains `Ok(())`, indicating that the key was loaded successfully. /// On failure, it returns a `SecurityModuleError`. #[instrument] - fn load_key(&mut self, key_id: &str, config: Box) -> Result<(), SecurityModuleError> { - let config = match config.as_any().downcast_ref::() { - None => { - return Err(SecurityModuleError::CreationError( - String::from("Wrong type used for ProviderConfig in create_key()"))); - } - Some(conf) => { conf } - }; - let environment = config.vm.get_env().unwrap(); - let key_id = key_id.to_string(); + fn load_key(&mut self, key_id: &str, config: Box) -> Result<(), SecurityModuleError> { + let config = Self::downcast_config(config)?; - // Call the create_key method with the correct parameters - RustDef::load_key(environment, key_id)?; + //Stores the vm for future access + self.set_config(config); - Ok(()) + // Call the create_key method with the correct parameters + RustDef::load_key(&self.get_env()?, key_id.to_string()) } /// Initializes the TPM module and returns a handle for cryptographic operations. @@ -221,3 +212,21 @@ impl Provider for KnoxProvider { Ok(()) } } + +impl KnoxProvider { + ///Get the JavaVM stored in &self and provides the JNIEnv based on it + fn get_env(&mut self) -> Result { + let conf = self.config.as_ref().ok_or( + SecurityModuleError::CreationError(String::from("failed to store config data")))?; + let env = conf.vm.get_env().unwrap(); + Ok(env) + } + + ///Converts the config parameter to a KnoxConfig + fn downcast_config(config: Box) -> Result { + let config = *config + .downcast::() + .map_err(|err| SecurityModuleError::InitializationError(format!("wrong config provided: {:?}", err)))?; + Ok(config) + } +} From 399f914acaf045c174428e91ba51355d46cf9782 Mon Sep 17 00:00:00 2001 From: segelnhoch3 Date: Fri, 31 May 2024 17:10:00 +0200 Subject: [PATCH 039/121] removed rsa < 512 since it is not supported by knox vault --- src/tpm/android/knox/provider.rs | 56 ++++++++++++++------------------ 1 file changed, 25 insertions(+), 31 deletions(-) diff --git a/src/tpm/android/knox/provider.rs b/src/tpm/android/knox/provider.rs index df9e1711..a7280bde 100644 --- a/src/tpm/android/knox/provider.rs +++ b/src/tpm/android/knox/provider.rs @@ -1,22 +1,32 @@ use std::any::Any; -use robusta_jni::jni:: JNIEnv; use crate::{ common::{ crypto::{ algorithms::{ - encryption::{AsymmetricEncryption, EccSchemeAlgorithm}, - }, + encryption::{ + AsymmetricEncryption, + EccSchemeAlgorithm, + BlockCiphers, + EccCurves, + SymmetricMode + }, + KeyBits + } }, error::SecurityModuleError, traits::module_provider::Provider, }, + tpm::{ + android::{ + knox::{ + KnoxProvider, + interface::jni::RustDef + } + }, + core::error::TpmError::UnsupportedOperation + } }; use tracing::instrument; -use crate::common::crypto::algorithms::encryption::{BlockCiphers, EccCurves, SymmetricMode}; -use crate::common::crypto::algorithms::KeyBits; -use crate::tpm::android::knox::{KnoxConfig, KnoxProvider}; -use crate::tpm::android::knox::interface::jni::RustDef; -use crate::tpm::core::error::TpmError::UnsupportedOperation; /// Implements the `Provider` trait, providing cryptographic operations utilizing a TPM. @@ -62,18 +72,20 @@ impl Provider for KnoxProvider { key_algo = match asym_alg.expect("Already checked") { AsymmetricEncryption::Rsa(bitslength) => { match bitslength { - KeyBits::Bits128 => { String::from("RSA;128;SHA-256;PKCS1") } - KeyBits::Bits192 => { String::from("RSA;192;SHA-256;PKCS1") } - KeyBits::Bits256 => { String::from("RSA;256;SHA-256;PKCS1") } KeyBits::Bits512 => { String::from("RSA;512;SHA-256;PKCS1") } KeyBits::Bits1024 => { String::from("RSA;1024;SHA-256;PKCS1") } KeyBits::Bits2048 => { String::from("RSA;2048;SHA-256;PKCS1") } KeyBits::Bits3072 => { String::from("RSA;3072;SHA-256;PKCS1") } KeyBits::Bits4096 => { String::from("RSA;4096;SHA-256;PKCS1") } KeyBits::Bits8192 => { String::from("RSA;8192;SHA-256;PKCS1") } + _ => { + return Err(SecurityModuleError::Tpm(UnsupportedOperation( + format!("Unsupported asymmetric encryption algorithm: {:?}", + asym_alg)))); + } } } - AsymmetricEncryption::Ecc(scheme) => { //todo: test in java prototype + AsymmetricEncryption::Ecc(scheme) => { match scheme { EccSchemeAlgorithm::EcDsa(curve) => { match curve { @@ -111,7 +123,7 @@ impl Provider for KnoxProvider { format!("Unsupported symmetric encryption algorithm: {:?}", sym_alg)))); } } - match block { //todo: check if paddings match blocking modes + match block { SymmetricMode::Gcm => { rv += "GCM;NoPadding" } SymmetricMode::Cbc => { rv += "CBC;PKCS7Padding" } SymmetricMode::Ctr => { rv += "CTR;NoPadding" } @@ -212,21 +224,3 @@ impl Provider for KnoxProvider { Ok(()) } } - -impl KnoxProvider { - ///Get the JavaVM stored in &self and provides the JNIEnv based on it - fn get_env(&mut self) -> Result { - let conf = self.config.as_ref().ok_or( - SecurityModuleError::CreationError(String::from("failed to store config data")))?; - let env = conf.vm.get_env().unwrap(); - Ok(env) - } - - ///Converts the config parameter to a KnoxConfig - fn downcast_config(config: Box) -> Result { - let config = *config - .downcast::() - .map_err(|err| SecurityModuleError::InitializationError(format!("wrong config provided: {:?}", err)))?; - Ok(config) - } -} From 3d0ade547caf31c7a079ede10508519c9aee5f4a Mon Sep 17 00:00:00 2001 From: segelnhoch3 Date: Fri, 31 May 2024 17:10:20 +0200 Subject: [PATCH 040/121] cleaned up imports --- src/tpm/android/knox/interface.rs | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/tpm/android/knox/interface.rs b/src/tpm/android/knox/interface.rs index 1efc3d35..9f2b1d45 100644 --- a/src/tpm/android/knox/interface.rs +++ b/src/tpm/android/knox/interface.rs @@ -2,14 +2,20 @@ use robusta_jni::bridge; #[bridge] pub mod jni { - #[allow(unused_imports)] - use robusta_jni::bridge; - use robusta_jni::convert::{IntoJavaValue, Signature, TryFromJavaValue, TryIntoJavaValue}; - use robusta_jni::jni::errors::Error; - use robusta_jni::jni::JNIEnv; - use robusta_jni::jni::objects::JValue; - use robusta_jni::jni::objects::AutoLocal; - use robusta_jni::jni::sys::jbyteArray; + #[allow(unused_imports)] //the bridge import is marked as unused, but if removed the compiler throws an error + use robusta_jni::{ + jni::{ + objects::{ + JValue, + AutoLocal + }, + JNIEnv, + errors::Error, + sys::jbyteArray + }, + convert::{IntoJavaValue, Signature, TryFromJavaValue, TryIntoJavaValue}, + bridge, + }; use crate::SecurityModuleError; #[derive(Signature, TryIntoJavaValue, IntoJavaValue, TryFromJavaValue)] From d3b689ccabf8e1c06a1631bfad80c0a2812528fb Mon Sep 17 00:00:00 2001 From: segelnhoch3 Date: Fri, 31 May 2024 17:11:03 +0200 Subject: [PATCH 041/121] added sign/verify + encrypt/decrypt functionality --- src/tpm/android/knox/key_handle.rs | 137 ++++++++++++++--------------- src/tpm/android/knox/mod.rs | 19 +++- 2 files changed, 86 insertions(+), 70 deletions(-) diff --git a/src/tpm/android/knox/key_handle.rs b/src/tpm/android/knox/key_handle.rs index 745b43ae..e21e4551 100644 --- a/src/tpm/android/knox/key_handle.rs +++ b/src/tpm/android/knox/key_handle.rs @@ -1,79 +1,78 @@ use super::{KnoxProvider}; use crate::{ common::{error::SecurityModuleError, traits::key_handle::KeyHandle}, - tpm::core::error::TpmError, + tpm::android::knox::interface::jni::RustDef }; use tracing::instrument; -use crate::tpm::android::knox::interface::jni::RustDef; /// Provides cryptographic operations for asymmetric keys on Windows, /// such as signing, encryption, decryption, and signature verification. impl KeyHandle for KnoxProvider { - // /// Signs data using the cryptographic key. - // /// - // /// This method hashes the input data using SHA-256 and then signs the hash. - // /// It leverages the NCryptSignHash function from the Windows CNG API. - // /// - // /// # Arguments - // /// - // /// * `data` - The data to be signed. - // /// - // /// # Returns - // /// - // /// A `Result` containing the signature as a `Vec` on success, or a `SecurityModuleError` on failure. - // #[instrument] - // fn sign_data(&self, data: &[u8]) -> Result, SecurityModuleError> { - // RustDef::sign_data(data) - // } - // - // /// Decrypts data encrypted with the corresponding public key. - // /// - // /// Utilizes the NCryptDecrypt function from the Windows CNG API. - // /// - // /// # Arguments - // /// - // /// * `encrypted_data` - The data to be decrypted. - // /// - // /// # Returns - // /// - // /// A `Result` containing the decrypted data as a `Vec` on success, or a `SecurityModuleError` on failure. - // #[instrument] - // fn decrypt_data(&self, encrypted_data: &[u8]) -> Result, SecurityModuleError> { - // RustDef::decrypt_data(encrypted_data) - // } - // - // /// Encrypts data with the cryptographic key. - // /// - // /// Uses the NCryptEncrypt function from the Windows CNG API. - // /// - // /// # Arguments - // /// - // /// * `data` - The data to be encrypted. - // /// - // /// # Returns - // /// - // /// A `Result` containing the encrypted data as a `Vec` on success, or a `SecurityModuleError` on failure. - // #[instrument] - // fn encrypt_data(&self, data: &[u8]) -> Result, SecurityModuleError> { - // RustDef::encrypt_data(data) - // } - // - // /// Verifies a signature against the provided data. - // /// - // /// This method hashes the input data using SHA-256 and then verifies the signature. - // /// It relies on the NCryptVerifySignature function from the Windows CNG API. - // /// - // /// # Arguments - // /// - // /// * `data` - The original data associated with the signature. - // /// * `signature` - The signature to be verified. - // /// - // /// # Returns - // /// - // /// A `Result` indicating whether the signature is valid (`true`) or not (`false`), - // /// or a `SecurityModuleError` on failure. - // #[instrument] - // fn verify_signature(&self, data: &[u8], signature: &[u8]) -> Result { - // RustDef::verify_signature(data, signature) - // } + /// Signs data using the cryptographic key. + /// + /// This method hashes the input data using SHA-256 and then signs the hash. + /// It leverages the NCryptSignHash function from the Windows CNG API. + /// + /// # Arguments + /// + /// * `data` - The data to be signed. + /// + /// # Returns + /// + /// A `Result` containing the signature as a `Vec` on success, or a `SecurityModuleError` on failure. + #[instrument] + fn sign_data(&self, data: &[u8]) -> Result, SecurityModuleError> { + RustDef::sign_data(&self.get_env()?, data) + } + + /// Decrypts data encrypted with the corresponding public key. + /// + /// Utilizes the NCryptDecrypt function from the Windows CNG API. + /// + /// # Arguments + /// + /// * `encrypted_data` - The data to be decrypted. + /// + /// # Returns + /// + /// A `Result` containing the decrypted data as a `Vec` on success, or a `SecurityModuleError` on failure. + #[instrument] + fn decrypt_data(&self, encrypted_data: &[u8]) -> Result, SecurityModuleError> { + RustDef::decrypt_data(&self.get_env()?, encrypted_data) + } + + /// Encrypts data with the cryptographic key. + /// + /// Uses the NCryptEncrypt function from the Windows CNG API. + /// + /// # Arguments + /// + /// * `data` - The data to be encrypted. + /// + /// # Returns + /// + /// A `Result` containing the encrypted data as a `Vec` on success, or a `SecurityModuleError` on failure. + #[instrument] + fn encrypt_data(&self, data: &[u8]) -> Result, SecurityModuleError> { + RustDef::encrypt_data(&self.get_env()?, data) + } + + /// Verifies a signature against the provided data. + /// + /// This method hashes the input data using SHA-256 and then verifies the signature. + /// It relies on the NCryptVerifySignature function from the Windows CNG API. + /// + /// # Arguments + /// + /// * `data` - The original data associated with the signature. + /// * `signature` - The signature to be verified. + /// + /// # Returns + /// + /// A `Result` indicating whether the signature is valid (`true`) or not (`false`), + /// or a `SecurityModuleError` on failure. + #[instrument] + fn verify_signature(&self, data: &[u8], signature: &[u8]) -> Result { + RustDef::verify_signature(&self.get_env()?, data, signature) + } } diff --git a/src/tpm/android/knox/mod.rs b/src/tpm/android/knox/mod.rs index 51039987..cfa01228 100644 --- a/src/tpm/android/knox/mod.rs +++ b/src/tpm/android/knox/mod.rs @@ -3,8 +3,9 @@ use std::fmt; use std::fmt::{Debug, Formatter}; use crate::common::crypto::algorithms::encryption::{AsymmetricEncryption, BlockCiphers}; use crate::common::traits::module_provider_config::ProviderConfig; -use robusta_jni::jni::JavaVM; +use robusta_jni::jni::{JavaVM, JNIEnv}; use tracing::instrument; +use crate::SecurityModuleError; mod interface; pub mod key_handle; @@ -41,6 +42,22 @@ impl KnoxProvider { fn set_config(&mut self, config: KnoxConfig) -> () { self.config = Some(config); } + + ///Get the JavaVM stored in &self and provides the JNIEnv based on it + fn get_env(&self) -> Result { + let conf = self.config.as_ref().ok_or( + SecurityModuleError::CreationError(String::from("failed to store config data")))?; + let env = conf.vm.get_env().unwrap(); + Ok(env) + } + + ///Converts the config parameter to a KnoxConfig + fn downcast_config(config: Box) -> Result { + let config = *config + .downcast::() + .map_err(|err| SecurityModuleError::InitializationError(format!("wrong config provided: {:?}", err)))?; + Ok(config) + } } ///A struct defining the needed values for the create_key() function in provider.rs From 102e8774c2102093f590f926b4aff14f16ce07d3 Mon Sep 17 00:00:00 2001 From: ErikFribus <–––erik.fribus@stud.hs-mannheim.de> Date: Fri, 31 May 2024 17:19:25 +0200 Subject: [PATCH 042/121] Updated CryptoManager.java --- src/tpm/android/knox/java/CryptoManager.java | 43 +++++++------------- 1 file changed, 14 insertions(+), 29 deletions(-) diff --git a/src/tpm/android/knox/java/CryptoManager.java b/src/tpm/android/knox/java/CryptoManager.java index ac04e4d6..157ccd94 100644 --- a/src/tpm/android/knox/java/CryptoManager.java +++ b/src/tpm/android/knox/java/CryptoManager.java @@ -107,12 +107,11 @@ public void genKey(String key_id, String keyGenInfo) throws CertificateException * Encrypts the given data using a symmetric key stored in the Android KeyStore. *

* This method takes plaintext data as input and encrypts it using a symmetric key retrieved from the Android KeyStore. - * The encryption process supports both GCM and non-GCM transformations. For GCM transformations, a new initialization vector (IV) - * is generated using a secure random number generator, and the IV is prepended to the ciphertext. The method initializes a + * The encryption process supports supports GCM, CBC and CTR transformations. A new initialization vector (IV) + * is generated and the IV is prepended to the ciphertext. The method initializes a * {@link Cipher} instance with the appropriate transformation, loads the Android KeyStore, retrieves the symmetric key, and then * initializes the cipher in encryption mode with the retrieved key and the generated IV. Finally, the plaintext data is encrypted - * using the cipher's {@code doFinal} method, and the resulting ciphertext (with the IV prepended in the case of GCM) is returned - * as a byte array. + * using the cipher's {@code doFinal} method, and the resulting ciphertext is returned as a byte array. * * @param data The plaintext data to be encrypted, represented as a byte array. * @return A byte array representing the encrypted data, with the IV prepended in the case of GCM mode. @@ -157,7 +156,7 @@ public byte[] encryptData(byte[] data) throws NoSuchPaddingException, NoSuchAlgo * Decrypts the given encrypted data using a symmetric key stored in the Android KeyStore. *

* This method takes encrypted data as input and decrypts it using a symmetric key retrieved from the Android KeyStore. - * The decryption process supports both GCM and non-GCM transformations. For GCM transformations, the initialization vector (IV) + * The decryption process supports GCM, CBC and CTR transformations. The initialization vector (IV) * is extracted from the beginning of the encrypted data. The method initializes a {@link Cipher} instance with the appropriate * transformation, loads the Android KeyStore, retrieves the symmetric key, and initializes the cipher in decryption mode with the * retrieved key and the extracted IV. Finally, the encrypted data is decrypted using the cipher's {@code doFinal} method, and the @@ -213,6 +212,9 @@ public byte[] decryptData(byte[] encryptedData) throws NoSuchPaddingException, N * The method configures the key pair generator with specific parameters, like the digest algorithms to be supported, * the signature padding scheme, and whether the key is backed by the strong box feature for enhanced security. * The generated key pair consists of a private key for signing and a corresponding public key for verification. + * The supported algorithms are RSA and EC. The keyGenInfo String should have the following form: + * For RSA: RSA;key size;hash;padding + * For EC: EC;curve;hash * * @param key_id The unique identifier under which the key pair will be stored in the KeyStore. * @throws CertificateException if there is an issue creating the certificate for the key pair. @@ -226,7 +228,6 @@ public void generateKeyPair(String key_id, String keyGenInfo) throws Certificate NoSuchAlgorithmException, InvalidAlgorithmParameterException, NoSuchProviderException, KeyStoreException { String[] keyGenInfoArr = keyGenInfo.split(";"); - System.out.println(Arrays.toString(keyGenInfoArr)); String KEY_ALGORITHM = keyGenInfoArr[0]; String HASH = keyGenInfoArr[2]; @@ -281,7 +282,8 @@ public void generateKeyPair(String key_id, String keyGenInfo) throws Certificate * @throws SignatureException if the signature cannot be processed. */ public byte[] signData(byte[] data) throws NoSuchAlgorithmException, UnrecoverableKeyException, - KeyStoreException, InvalidKeyException, SignatureException, InvalidKeySpecException, NoSuchProviderException { + KeyStoreException, InvalidKeyException, SignatureException, InvalidKeySpecException, NoSuchProviderException, CertificateException, IOException { + keyStore.load(null); Signature signature = Signature.getInstance(buildSignatureAlgorithm((PrivateKey) keyStore.getKey(KEY_NAME, null))); signature.initSign((PrivateKey) keyStore.getKey(KEY_NAME, null)); signature.update(data); @@ -311,32 +313,14 @@ public byte[] signData(byte[] data) throws NoSuchAlgorithmException, Unrecoverab * @throws NoSuchProviderException if the provider is not available. */ public boolean verifySignature(byte[] data, byte[] signedBytes) throws SignatureException, InvalidKeyException, - KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException, InvalidKeySpecException, NoSuchProviderException { + KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException, InvalidKeySpecException, NoSuchProviderException, CertificateException, IOException { + keyStore.load(null); Signature verificationSignature = Signature.getInstance(buildSignatureAlgorithm((PrivateKey) keyStore.getKey(KEY_NAME, null))); verificationSignature.initVerify(keyStore.getCertificate(KEY_NAME).getPublicKey()); verificationSignature.update(data); return verificationSignature.verify(signedBytes); } - /** - * Converts a primitive byte array to an array of Byte objects. - *

- * This method takes a primitive byte array as input and returns an array of Byte objects. - * Each element in the input array is wrapped in a Byte object. - * - * @param bytesPrim The primitive byte array to be converted. - * @return An array of Byte objects corresponding to the elements of the input byte array. - * @throws NullPointerException if the input byte array is null. - */ - public Byte[] toByte(byte[] bytesPrim) { // TODO: still needed? - if (bytesPrim == null) { - throw new NullPointerException("Input byte array cannot be null."); - } - Byte[] bytes = new Byte[bytesPrim.length]; - Arrays.setAll(bytes, n -> bytesPrim[n]); - return bytes; - } - /** * Sets the `KEY_NAME` to the provided key identifier. *

@@ -347,7 +331,8 @@ public Byte[] toByte(byte[] bytesPrim) { // TODO: still needed? * * @param key_id The unique identifier of a key to be set as `KEY_NAME`. */ - public void loadKey(String key_id) throws KeyStoreException, UnrecoverableKeyException { + public void loadKey(String key_id) throws KeyStoreException, UnrecoverableKeyException, CertificateException, IOException, NoSuchAlgorithmException { + keyStore.load(null); if (keyStore.containsAlias(key_id)) KEY_NAME = key_id; else throw new UnrecoverableKeyException("The key alias '" + key_id + "' does not exist in the KeyStore."); @@ -440,7 +425,7 @@ private String buildSignatureAlgorithm(PrivateKey privateKey) throws NoSuchAlgor * @throws NoSuchProviderException if the specified provider is not available. * @throws UnrecoverableKeyException if the key cannot be recovered from the keystore. * @throws KeyStoreException if there is an error accessing the keystore. - */ // TODO: DELETE BEFORE RELEASE + */ // TODO: DELETE BEFORE FINAL RELEASE public void showKeyInfo() throws NullPointerException, CertificateException, IOException, NoSuchAlgorithmException, InvalidKeySpecException, NoSuchProviderException, UnrecoverableKeyException, KeyStoreException { From b703ab315016605b1cd6b6776a34e15dd2bcd3a8 Mon Sep 17 00:00:00 2001 From: segelnhoch3 Date: Mon, 3 Jun 2024 09:44:25 +0200 Subject: [PATCH 043/121] initmodule gets called by create/load_key --- src/tpm/android/knox/interface.rs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/tpm/android/knox/interface.rs b/src/tpm/android/knox/interface.rs index 9f2b1d45..e97803bb 100644 --- a/src/tpm/android/knox/interface.rs +++ b/src/tpm/android/knox/interface.rs @@ -4,18 +4,19 @@ use robusta_jni::bridge; pub mod jni { #[allow(unused_imports)] //the bridge import is marked as unused, but if removed the compiler throws an error use robusta_jni::{ + bridge, + convert::{IntoJavaValue, Signature, TryFromJavaValue, TryIntoJavaValue}, jni::{ + errors::Error, + JNIEnv, objects::{ - JValue, - AutoLocal + AutoLocal, + JValue }, - JNIEnv, - errors::Error, sys::jbyteArray }, - convert::{IntoJavaValue, Signature, TryFromJavaValue, TryIntoJavaValue}, - bridge, }; + use crate::SecurityModuleError; #[derive(Signature, TryIntoJavaValue, IntoJavaValue, TryFromJavaValue)] @@ -73,6 +74,7 @@ pub mod jni { /// # Arguments /// `key_id` - String that uniquely identifies the key so that it can be retrieved later pub fn create_key(environment: &JNIEnv, key_id: String, key_gen_info: String) -> Result<(), SecurityModuleError> { + RustDef::initialize_module(environment)?; let result = environment.call_static_method( "com/example/vulcans_limes/RustDef", "create_key", @@ -113,6 +115,7 @@ pub mod jni { /// # Arguments /// `key_id` - String that uniquely identifies the key so that it can be retrieved later pub fn load_key(environment: &JNIEnv, key_id: String) -> Result<(), SecurityModuleError> { + RustDef::initialize_module(environment)?; let result = environment.call_static_method( "com/example/vulcans_limes/RustDef", "load_key", From a69ef7ef0c6f991e0c7e64bf2b636ffde21f53aa Mon Sep 17 00:00:00 2001 From: segelnhoch3 Date: Mon, 3 Jun 2024 10:50:48 +0200 Subject: [PATCH 044/121] added error handling if no key is loaded --- src/tpm/android/knox/mod.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/tpm/android/knox/mod.rs b/src/tpm/android/knox/mod.rs index cfa01228..58e7c79a 100644 --- a/src/tpm/android/knox/mod.rs +++ b/src/tpm/android/knox/mod.rs @@ -1,10 +1,12 @@ use std::any::Any; use std::fmt; use std::fmt::{Debug, Formatter}; -use crate::common::crypto::algorithms::encryption::{AsymmetricEncryption, BlockCiphers}; -use crate::common::traits::module_provider_config::ProviderConfig; + use robusta_jni::jni::{JavaVM, JNIEnv}; use tracing::instrument; + +use crate::common::crypto::algorithms::encryption::{AsymmetricEncryption, BlockCiphers}; +use crate::common::traits::module_provider_config::ProviderConfig; use crate::SecurityModuleError; mod interface; @@ -45,6 +47,7 @@ impl KnoxProvider { ///Get the JavaVM stored in &self and provides the JNIEnv based on it fn get_env(&self) -> Result { + if self.config.is_none() { return Err(SecurityModuleError::InitializationError(String::from("No key loaded"))) } let conf = self.config.as_ref().ok_or( SecurityModuleError::CreationError(String::from("failed to store config data")))?; let env = conf.vm.get_env().unwrap(); From 5287b0028e866b271499f68c60d773ab90043428 Mon Sep 17 00:00:00 2001 From: segelnhoch3 Date: Mon, 3 Jun 2024 13:27:27 +0200 Subject: [PATCH 045/121] fixed and completed documentation of methods and structs --- src/tpm/android/knox/interface.rs | 47 ++++++------ src/tpm/android/knox/key_handle.rs | 38 +++++----- src/tpm/android/knox/mod.rs | 33 +++++++-- src/tpm/android/knox/provider.rs | 110 ++++++++--------------------- 4 files changed, 97 insertions(+), 131 deletions(-) diff --git a/src/tpm/android/knox/interface.rs b/src/tpm/android/knox/interface.rs index e97803bb..3d7b6696 100644 --- a/src/tpm/android/knox/interface.rs +++ b/src/tpm/android/knox/interface.rs @@ -19,16 +19,17 @@ pub mod jni { use crate::SecurityModuleError; + /// Contains all methods related to Rust - Java communication and the JNI #[derive(Signature, TryIntoJavaValue, IntoJavaValue, TryFromJavaValue)] - #[package(com.example.vulcans_1limes)] + #[package(com.example.vulcans_1limes)] //the 1 after the "_" is an escape character for the "_" pub struct RustDef<'env: 'borrow, 'borrow> { #[instance] pub raw: AutoLocal<'env, 'borrow>, } /// This Implementation provides the method declarations that are the interface for the JNI. - /// The first part are Rust-methods that can be called from other Java-classes, - /// while the second part contains Java-methods that can be called from Rust. + /// The first part contains Java-methods that can be called from Rust. + /// The second part contains some utility methods. /// /// All method signatures have to correspond to their counterparts in RustDef.java, with the /// same method name and corresponding parameters according to this table: @@ -53,16 +54,19 @@ pub mod jni { //------------------------------------------------------------------------------------------ // Java methods that can be called from rust - ///Proof of concept method - shows callback from Rust to a java method - /// DO NOT USE - pub fn callback(environment: &JNIEnv) -> () { + ///Proof of concept method - shows call from Rust to a java method + /// in order to find the signatures of the class and method, use + /// `javap -s -p file/path/to/compiled/java/class` + /// + /// DO NOT USE THIS METHOD + fn callback(environment: &JNIEnv) -> () { //This calls a method in Java in the Class RustDef, with the method name "callback" //and no arguments environment.call_static_method( - "com/example/vulcans_limes/RustDef", - "callback", - "()V", - &[], + "com/example/vulcans_limes/RustDef", //Class signature + "callback", //method name signature + "()V", //parameter types of the method + &[], //parameters to be passed to the method ).expect("Java func call failed"); } @@ -73,6 +77,8 @@ pub mod jni { /// /// # Arguments /// `key_id` - String that uniquely identifies the key so that it can be retrieved later + /// `key_gen_info` - A string that contains all relevant parameters for the key such as the + /// algorithm used, the length of the key, the type of padding, etc. pub fn create_key(environment: &JNIEnv, key_id: String, key_gen_info: String) -> Result<(), SecurityModuleError> { RustDef::initialize_module(environment)?; let result = environment.call_static_method( @@ -108,9 +114,7 @@ pub mod jni { } /// Loads an existing cryptographic key identified by `key_id`. - /// - /// This method generates a new cryptographic key within the TPM. - /// The loaded key is associated with the provided `key_id`. + /// This key can then be used for cryptographic operations such as encryption or signing. /// /// # Arguments /// `key_id` - String that uniquely identifies the key so that it can be retrieved later @@ -148,19 +152,11 @@ pub mod jni { } - /// Initializes the TPM module and returns a handle for further operations. + /// Initializes the TPM module. /// /// This method initializes the TPM context and prepares it for use. It should be called /// before performing any other operations with the TPM. /// - /// # Arguments - /// - /// * `key_id` - A string slice that uniquely identifies the key to be loaded. - /// * `key_algorithm` - The asymmetric encryption algorithm used for the key. - /// * `sym_algorithm` - An optional symmetric encryption algorithm used with the key. - /// * `hash` - An optional hash algorithm used with the key. - /// * `key_usages` - A vector of `AppKeyUsage` values specifying the intended usages for the key. - /// /// # Returns /// /// A `Result` that, on success, contains `()`, @@ -438,14 +434,11 @@ pub mod jni { /// # Arguments /// * `environment` - A reference to the Java environment (`JNIEnv`) /// # Returns - /// * `Result<(), JniError>` - A Result type representing either success (if no exceptions - /// are found) or an error of type `JniError` - /// (if exceptions are found). + /// * `Result<(), String>` - A Result type representing either success (if no exceptions + /// are found) or an error (if exceptions are found). /// # Errors /// This method may return an error of type `JniError` if: /// * Any pending Java exceptions are found in the provided Java environment. - /// # Panics - /// This method does not panic under normal circumstances. pub fn check_java_exceptions(environment: &JNIEnv) -> Result<(), String> { if environment.exception_check().unwrap_or(true) { let _ = environment.exception_describe(); diff --git a/src/tpm/android/knox/key_handle.rs b/src/tpm/android/knox/key_handle.rs index e21e4551..05c8f303 100644 --- a/src/tpm/android/knox/key_handle.rs +++ b/src/tpm/android/knox/key_handle.rs @@ -1,17 +1,23 @@ -use super::{KnoxProvider}; +use tracing::instrument; + use crate::{ common::{error::SecurityModuleError, traits::key_handle::KeyHandle}, tpm::android::knox::interface::jni::RustDef }; -use tracing::instrument; -/// Provides cryptographic operations for asymmetric keys on Windows, -/// such as signing, encryption, decryption, and signature verification. +use super::KnoxProvider; + +/// Implements the `Provider` trait, providing cryptographic operations +/// such as signing, encryption, decryption, and signature verification for the TPM Knox Vault. +/// +/// This implementation is specific to Samsung Knox Vault and uses the Android Keystore API for all cryptographic operations +/// In theory, this should also work for other TPMs on Android phones, but it is only tested with Samsung Knox Vault impl KeyHandle for KnoxProvider { - /// Signs data using the cryptographic key. + /// Signs data using the previously loaded cryptographic key. /// /// This method hashes the input data using SHA-256 and then signs the hash. - /// It leverages the NCryptSignHash function from the Windows CNG API. + /// The algorithm used for signing is determined by the currently loaded key. + /// If no key is loaded, an Error is returned. /// /// # Arguments /// @@ -25,10 +31,9 @@ impl KeyHandle for KnoxProvider { RustDef::sign_data(&self.get_env()?, data) } - /// Decrypts data encrypted with the corresponding public key. - /// - /// Utilizes the NCryptDecrypt function from the Windows CNG API. - /// + /// Decrypts the data with the currently loaded key. + /// The algorithm used for decryption is determined by the currently loaded key. + /// If no key is loaded, an Error is returned. /// # Arguments /// /// * `encrypted_data` - The data to be decrypted. @@ -41,10 +46,9 @@ impl KeyHandle for KnoxProvider { RustDef::decrypt_data(&self.get_env()?, encrypted_data) } - /// Encrypts data with the cryptographic key. - /// - /// Uses the NCryptEncrypt function from the Windows CNG API. - /// + /// Encrypts the data with the currently loaded key. + /// The algorithm used for Encryption is determined by the currently loaded key. + /// If no key is loaded, an Error is returned. /// # Arguments /// /// * `data` - The data to be encrypted. @@ -59,9 +63,9 @@ impl KeyHandle for KnoxProvider { /// Verifies a signature against the provided data. /// - /// This method hashes the input data using SHA-256 and then verifies the signature. - /// It relies on the NCryptVerifySignature function from the Windows CNG API. - /// + /// This method hashes the input data using SHA-256 and then verifies the signature with the currently loaded key. + /// The algorithm used for verification is determined by the currently loaded key. + /// If no key is loaded, an Error is returned. /// # Arguments /// /// * `data` - The original data associated with the signature. diff --git a/src/tpm/android/knox/mod.rs b/src/tpm/android/knox/mod.rs index 58e7c79a..fddb07bb 100644 --- a/src/tpm/android/knox/mod.rs +++ b/src/tpm/android/knox/mod.rs @@ -14,13 +14,15 @@ pub mod key_handle; pub mod provider; /// A TPM-based cryptographic provider for managing cryptographic keys and performing -/// cryptographic operations in an Samsung environment. This provider uses the Java Native Interface -/// and the Android Keystore API to access the TPM "Knox Vault" developed by Samsung +/// cryptographic operations in a Samsung environment. This provider uses the Java Native Interface +/// and the Android Keystore API to access the TPM "Knox Vault" developed by Samsung. In theory, +/// this code should also work for other TPMs on Android Devices, though it is only tested with Knox Vault #[repr(C)] pub struct KnoxProvider { config: Option, } +///implements the Debug trait for KnoxProvider to facilitate logging impl Debug for KnoxProvider { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { f.debug_struct("KnoxProvider") @@ -29,27 +31,36 @@ impl Debug for KnoxProvider { } } +///Provides functions to manage the KnoxProvider and the stored values within impl KnoxProvider { /// Constructs a new `TpmProvider`. /// - /// /// # Returns /// - /// A new instance of `TpmProvider`. + /// A new empty instance of `TpmProvider`. #[instrument] pub fn new() -> Self { Self { config: None } } + /// Sets the configuration for the `Knox` instance. + /// + /// # Arguments + /// + /// * `config` - A `KnoxConfig` instance that contains the configuration settings. fn set_config(&mut self, config: KnoxConfig) -> () { self.config = Some(config); } ///Get the JavaVM stored in &self and provides the JNIEnv based on it + /// # Returns + /// + /// a JNIEnv on success to be used for JNI method calls. + /// If the KnoxConfig has not been loaded yet or contains an invalid JavaVM, an error is returned fn get_env(&self) -> Result { if self.config.is_none() { return Err(SecurityModuleError::InitializationError(String::from("No key loaded"))) } let conf = self.config.as_ref().ok_or( - SecurityModuleError::CreationError(String::from("failed to store config data")))?; + SecurityModuleError::InitializationError(String::from("failed to store config data")))?; let env = conf.vm.get_env().unwrap(); Ok(env) } @@ -63,15 +74,17 @@ impl KnoxProvider { } } -///A struct defining the needed values for the create_key() function in provider.rs -///At any time, either a key_algorithm OR a sym_algorithm must be supplied, not both. +/// A struct defining the needed values for the create_key() and load_key() functions +/// At any time, either a key_algorithm OR a sym_algorithm must be supplied, not both. /// For hashing operations, SHA-256 is always used since it is the only one available on Knox Vault +/// The last needed parameter is a JavaVM that is needed to call the Android KeystoreAPI pub struct KnoxConfig { pub key_algorithm: Option, pub sym_algorithm: Option, pub vm: JavaVM } +/// implements the debug trait for KnoxConfig for logging impl Debug for KnoxConfig { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { f.debug_struct("KnoxConfig") @@ -82,13 +95,19 @@ impl Debug for KnoxConfig { } } +///implements ProviderConfig for KnoxConfig impl ProviderConfig for KnoxConfig { fn as_any(&self) -> &dyn Any { self } } +/// Implements KnoxConfig and provides a constructor impl KnoxConfig { + /// creates a new KnoxConfig + /// At any time, either a key_algorithm OR a sym_algorithm must be supplied, not both. + /// Otherwise, load_key() or create_key() will return an Error. + /// The last needed parameter is a JavaVM that is needed to call the Android KeystoreAPI #[allow(clippy::new_ret_no_self)] pub fn new( key_algorithm: Option, diff --git a/src/tpm/android/knox/provider.rs b/src/tpm/android/knox/provider.rs index a7280bde..bed9498b 100644 --- a/src/tpm/android/knox/provider.rs +++ b/src/tpm/android/knox/provider.rs @@ -1,62 +1,53 @@ use std::any::Any; + +use tracing::instrument; + use crate::{ common::{ - crypto::{ - algorithms::{ - encryption::{ - AsymmetricEncryption, - EccSchemeAlgorithm, - BlockCiphers, - EccCurves, - SymmetricMode - }, - KeyBits - } + crypto::algorithms::{ + encryption::{ + AsymmetricEncryption, + BlockCiphers, + EccCurves, + EccSchemeAlgorithm, + SymmetricMode, + }, + KeyBits }, error::SecurityModuleError, traits::module_provider::Provider, }, tpm::{ - android::{ - knox::{ - KnoxProvider, - interface::jni::RustDef - } + android::knox::{ + interface::jni::RustDef, + KnoxProvider }, core::error::TpmError::UnsupportedOperation } }; -use tracing::instrument; - /// Implements the `Provider` trait, providing cryptographic operations utilizing a TPM. /// -/// This implementation is specific to Samsung Knox Vault and uses it for all cryptographic operations +/// This implementation is specific to Samsung Knox Vault and uses the Android Keystore API for all cryptographic operations /// In theory, this should also work for other TPMs on Android phones, but it is only tested with Samsung Knox Vault impl Provider for KnoxProvider { /// Creates a new cryptographic key identified by `key_id`. /// /// This method creates a persisted cryptographic key using the specified algorithm /// and identifier, making it retrievable for future operations. The key is created - /// and stored in Knox Vault. + /// and stored in Knox Vault. This method also loads the key for further usage, therefore it is + /// not necessary to load a key after creating it. /// /// # Arguments /// /// * `key_id` - A string slice that uniquely identifies the key to be created. - /// * `Box` - A Box containing a KnoxConfig that has all further required parameters: - /// * key_algorithm: Option\, - /// * sym_algorithm: Option\, - /// * env: JNIEnv<'a> - /// - /// There must be exactly one of either key_algorithm or sym_algorithm provided. - /// If both are Some or None, the method returns an Error. - /// The env parameter is necessary to access the Java Virtual Machine from Rust code. When - /// calling Rust code from Java using the Java Native Interface, this value will be provided to the - /// Rust code by the JNI. + /// * `Box) -> Result<(), SecurityModuleError> { @@ -154,16 +145,14 @@ impl Provider for KnoxProvider { /// Loads an existing cryptographic key identified by `key_id`. /// /// This method attempts to load a persisted cryptographic key by its identifier from the TPM. - /// If successful, it sets the key usages and returns a handle to the key for further - /// cryptographic operations. + /// If successful, it enables the key to be used for cryptographic operations. /// /// # Arguments /// /// * `key_id` - A string slice that uniquely identifies the key to be loaded. - /// * `key_algorithm` - The asymmetric encryption algorithm used for the key. - /// * `sym_algorithm` - An optional symmetric encryption algorithm used with the key. - /// * `hash` - An optional hash algorithm used with the key. - /// * `key_usages` - A vector of `AppKeyUsage` values specifying the intended usages for the key. + /// * `Box) -> Result<(), SecurityModuleError> { - let config = Self::downcast_config(config)?; - //Stores the vm for future access + let config = Self::downcast_config(config)?; self.set_config(config); - // Call the create_key method with the correct parameters RustDef::load_key(&self.get_env()?, key_id.to_string()) } - /// Initializes the TPM module and returns a handle for cryptographic operations. - /// - /// This method opens a storage provider using the Windows CNG API and wraps it in a - /// `WindowsProviderHandle`. This handle is used for subsequent cryptographic operations - /// with the TPM. - /// - /// # Parameters - /// - /// - `key_algorithm`: Specifies the asymmetric encryption algorithm to use. Supported algorithms include: - /// - `AsymmetricEncryption::Rsa`: RSA with key lengths specified by `KeyBits`. - /// - `sym_algorithm`: An optional parameter specifying the block cipher algorithm to use. Supported algorithms include: - /// - `BlockCiphers::Aes`: AES with key lengths specified by `KeyBits` and modes like GCM, ECB, CBC, CTR. - /// - `hash`: An optional parameter specifying the hash algorithm to use. - /// - `key_usages`: A vector specifying the purposes for which the key can be used (e.g., encrypt, decrypt, sign, verify). - /// - /// # Returns - /// - /// A `Result` that, on success, contains `Ok(())`, indicating that the module was initialized successfully. - /// On failure, it returns a `SecurityModuleError`. - /// - /// # Errors - /// - /// This function returns a `SecurityModuleError` in the following cases: - /// - If an unsupported asymmetric encryption algorithm is specified. - /// - If an unsupported symmetric encryption algorithm is specified. - /// - /// # Examples - /// - /// rust - /// let result = module.initialize_module( - /// AsymmetricEncryption::Rsa(KeyBits::Bits2048), - /// Some(BlockCiphers::Aes(KeyBits::Bits256)), - /// Some(Hash::Sha256), - /// vec![KeyUsage::Encrypt, KeyUsage::Decrypt], - /// ); - /// - /// match result { - /// Ok(()) => println!("Module initialized successfully"), - /// Err(e) => println!("Failed to initialize module: {:?}", e), - /// } + ///This function ordinarily initialises the HSM. + /// For our implementation, this is not needed. You do not need to call this method, + /// and it will always return Ok(()) if you do. fn initialize_module(&mut self) -> Result<(), SecurityModuleError> { Ok(()) } From 3f688ee23f9873ac6a1df962f76032a449c83422 Mon Sep 17 00:00:00 2001 From: segelnhoch3 Date: Mon, 3 Jun 2024 14:35:36 +0200 Subject: [PATCH 046/121] added folder for doc images --- Documentation/Doc.md | 3 ++- Documentation/Komp.png | Bin 42712 -> 0 bytes .../{BSP.jpg => images/component diagram.jpg} | Bin Documentation/images/method call structure.png | Bin 0 -> 76699 bytes 4 files changed, 2 insertions(+), 1 deletion(-) delete mode 100644 Documentation/Komp.png rename Documentation/{BSP.jpg => images/component diagram.jpg} (100%) create mode 100644 Documentation/images/method call structure.png diff --git a/Documentation/Doc.md b/Documentation/Doc.md index d78de894..af0482d9 100644 --- a/Documentation/Doc.md +++ b/Documentation/Doc.md @@ -50,7 +50,8 @@ Our project aims to address this critical need by developing a comprehensive sol ## Architecture ### Component Diagram -![Komponentendiagramm](../Documentation/BSP.jpg) + +![component diagram](images/component diagram.jpg) ### Explanation diff --git a/Documentation/Komp.png b/Documentation/Komp.png deleted file mode 100644 index b402c0958ef548bd2e49a4cb6ce319acfeab0588..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 42712 zcmd43c{J4T8$Mo<6tZQPWsH3tWGTirn6Z^LL}Dz-PGu)#U&q$i2_sTuDKZ+MegR@B6y1>%L#nSM)Tg&aj?2cI+4x3W+c{ zc8mmb?AY;Tax(CZ(#*Ug_;cLLKvVVD)3@w%;6J1eD!MAijy+4H*t0$f{(ssXY36n8 z*x6R%zvG>5k8F<}!?~akDn>rnf99OTvKb!+2Wy{XmJT%@ANgpbG$#BMW17lf7!k+frC=P zz`zTg_eyb@LKB|7qAJtvOjbq^b&Z5R^dg5{sy;`e?>lTnL11XQ3sOF zcdbvBNm}&>3$KA~(bOT{Ay&N6$Mnv7)o zK-@b2`=|fEAvwo~b)~D-ylr#pc%{;J*aLD!LoABs7KlS#~5@o;Qrp4CctEDrE zcR7Ohe_h=oWVsov-v9T#`G(LN7n=#$f4NVu$9(qU_k8zpm*?SZt}hDS*ROQ2rBv?k zt@Vcw+mEiyS2RWJE;T7FH0~~1*UzL=?C)+;4_{UaG`&5j-Wqa>gZaAeOxp71WH3|R zq7Qs~I>mjpn?Lf^Qsb*iKF+_Uj`;Y=6vRzIDTPncGZ(c3GwizR)v(e*q3E|x9=Nl9 z`dgfo-s6_K6}#5eUUAOPzOy+kj@3?E-x8HtlDO4cCcOI3n}BOUUo185y3%!mdr-t* z7`3ntqmn`P=A4x5a9A5qqh=VHCLvC!nUY3yDtKlHr2a;#zQ*Y=GO9DrKl?dd^sSYV z{dC({XJ0W=%wvg8kuKPLqe2wORSyb=hC4e!QjiZ;c!rQ=NO_H|J8?oD~EfnH9vWZ}! z8%-jY9%*t0)+{$wdcybDhu8NuKl@iy;_fU5wtEF_O+~h3)~$`ZH5D&S3qO4U3G5Ht z7;^~M{6X=SLaQh$%19A8O{>YSFBf>{tZ;dyswudcmquXXRC1Xt+?$s_pc5HJWWot` z76V*(=R@#h{G}F7Pik8<*j&yrn%^vN?-4Ay|7+ArsT@r4RAC}qNMVih>B}Ew5@41i zWIy}xbgvbq=&>fq6R?$Ff0c~p1Rrd-f_<4=L`O3m&U9Ta4*X=@!Qw#azx3LB3tXc0 zveN!JvHI!c${{V`@RN+DTzL7}Z0l~`2b7xU?-i3@gVGJpybgg}FGH3eeTpbydR?t?tK)9ko zvSH`PV@G91xy~c>>tA(nmOsA)(!GgP(cQ!eizv_qY7L5jgbEU++h3_t)pxVOumLYV4ck~Xi zyG&LAEAET+v$&wXCw-+QxvXd9zT9(PAJi;iyY_@?^7us8!>l)5t2^@*k%#+B!H!(d zw7$Gb$TAmyy>4KwJN||56Sy%>T$bz0<%R102+rU=*e8X@nyE-y+U}D`=)>U*qcB7x za44mjFTN#Qs@t(S89n_p*n}IOFStObErJmvb6h=R#999aeUH73F1qOOyO~2y8po*- z!;{4i??s)^HJQspNnQAI0j&Rg^;Y9$>}o-Msgv)NwBwyQZLZm)ltd6O|6+M8P7p&*=q@v@t|4j8;rG-B0WsweaezxtyQK+y4q!7V>p&%zZ+!DH!_w)HAJWzI3QE}Y4x;V>L zCzy1--NJ9KSZ%T~u(iDUZKlw}^>_p$OOi9#pxXUB>wzU>!hzx;BMj1qvoP0$6)=%M zQ(f^Z`(1CG8W=y3zHp4VLA1*`X0yw|elL$N`|y=S@r@7m-6K>wYd$|lD$7Ig53#g9%Fj%)Y`#r3B2zw&w5q|nJGEZ)iTbZu`q)m}r!FmA3bqAd z4FP3(_ZZ>XiH&6vQ^?Fp!B9}_%()M?wUdTMAq?vKgc4XNpQ@#pEVg*9gkAB}RD9*1 zh!{GTPLXbN)8th<t(iwGGGx$js*vy1{}2lLnj)r6D|_tvBzhz>A#kWj9SX4`9tX1~d%&K4QtUR>ae&o&wliWZBl7bZkwBhe!XU}|)c)4&nuA(CkGV^z|(IC{(txYE~W-Xao~w9JjT zY@_&iG}m&X?yp!hE^R+m8>s^bYduHnBrNPFb4KQ^>oNv1efpRyMHS4M41yk6=}%)TU{rIpT1iBSTw0e#w3X6Mj0B4eR$FODI0OZ zd)$#s<7D!yuNFFX?$)poQ*?1`4J9do>yR@m}D@AlY=`QYyXq zR?zusHdk`i_gXu;C*I5N++DNm@V+F8Ee<|BaBOolzr%$I$ou)MlcQ8JBc$AeC*HoW zeKL!Moc%;p(a)-0vC>Q|Z6Ch?7f<1_p0xQuc*hJ~%fvoLA24|Z;L~B%9qVzJoqT&}vK?8pU_^-L2mme}J?K)z z&_pUc*@(PUV@Ub}1rKsfv3Jvf4`XyvTNqPJ*X_+|qwzfK{Z%W)3r;hy=#CdT9qa7z zhU#AT{Y1zt8Zu%W``Ps)g-~rFQfC`zb(FllqL^<8*iDR}!&IuW~nl7oy z-7M!S)Hesosy`vv3=(30Rod|@_U1_2c_$6ynMulxH(mEwEaJGbEdtIZre-#uJk@7a zlI!%NM~dNDK|OToC9>Ge$AP=qw)KxRVTVSpTW zdTQIET)(uO%u@)n?u}p#j#oKG+j$z@6QUr-q-N~-9RTDl#U_fNNu#{_foVuqQ_iyv z*7?{Mecxc@GYv2k0jIo|>Bt>FDFe+}f&r6tP)xw=@e1|4JDFvx%)7W5vOqzM zRszejZ-<IcSegx&aYoBnIWq%Jl%(~C6nxq*WBsjDKtVl?)2=k|FuWN zFv?PV1nt6L3bn4`!=zZ~PT#rKlPA?Whi9Kz4D-u<&kt*{&vF^@;Z6-f3^~VyaK3n@ zSDX@KD|5@5Y*$_AaCh<$cgEJF0AW727PjoE(%Ek0<^B0pD7%(z9+n%X)ON*~eK21> zrGx$bz<@*m0EUNIT4YRP4Gu+lAsNgTqy@8XVUxm4n*dJD!RRM;*i?4 z5k_%V%n19ru*OY?4^zx^u~zjhG`Z|63f9!N&=y0hoHC-#pLz3PUItC>LCH+nK(RY_ z-%@7J8Z$a><<7(@-bTrSERaVl%&xgYjWTI$;2M|8fj{&@(b>+M$^lOcR#bq zuE=e=S4W=V?MyL-ne`u<)oBU=f9^^)l$SgyYe|!I#Z}eQwJ_0s?%b8Md_+tXQcM|A zPd}dVE)y}-gt@earv+a0d|jG&beS!JPFx9#ExSy zmmPhZ`xwl%md{7spw(!y-+@3WUev2jYT)_=z|NH!(mht%OV%|de~QEB#t)~iMxVf! z%$4W&AuN_`Ns5iZ7?sSO;ic?F^7U5y&ec^jZr?b8tn=e1C`6JH&daM)hOj9q`fup) zG*%^id)R$(IQd*G_dKmFj$B1gUgPsTfbxeGw;YQ9P63B*z$V&@V@yylc6+09ViQIp zf}~eMFB0=DN=|r#`$xR2g{jBY+-5dx`74V_mc<|*jI`Plbx$ZpTKF}TFpNE{e8|#~ zC?6hlU0T%j>9(g+eg@n71EO7SzUa3^GY=ZyinY*!wsdSV$+p~Ub~6=ovG4DCviFGCJt*UvF>L+4UUV`H)sR~5*l3tJup2CvF_YI&Io z6;mQK(lDbBSwvT_Hc)3KNfw`~ilVf)JT94}y5lOzOSg+gI76#_I2)8c)62@4^?R(C z*C=-(q{901grHtdkjW_LdW@a0Ix!A5xLX1={M~_ZCs|SQGe$O7V$Y4Q=YbZ6cLHH6zefG8AuZO|Y z&W8=D@5sG6CPrTSZ*F!13x2M_8Mr<|_z43Ux@o{-e?Z>Q^91{qCsc;V`@nbMO=6`d zi7~bG4w?JI!r*A63dFI@_b&b;jLpPMCTdKQFbD8aW|+`!J$|zzmGmc7U@u>`yDI$g zYa74SPy>4Ruuyp2z74LNOS&V`NsWq0BBzXFJRTao# zLP1Z*G0FF=J8HR1!|YPB($WgOq=fS)y(Zd6eYhJ(>R?%I&bK`C;?Xbk^&LlIR8ZfkqO#-jJ{RCg~3Pq+`z}ds;`>egunE`Z^Ak|q&rmst#TkvxiYCgu;~IQ zIJ+3yBE~tHUpY$Xp{QcW-_HwDMUNv64bVSqBj!#|-1k;GnK-r&2NdFbyL%K(1a)fq z?vLllkMigWB`U0TK`|_pA&C}s_CvdGA^d@09d?}XL5`&g+LBK?0XDGoarjBSR+Rwj zU=FxW(ZMw#mGP3w3$sYSmPRM|d)EvX)1izP0eU=bu`GpAO6vgpz?Rio7B>bERMwd# zOCOyobW`-DO1^<4^jAgqx4fX8xyqUJYY~Da*^*Qy&7|XFZKFn zg^J!9Zak*0PiDMm)b@mPLXPh!w%U2r(%VInTh6P58?IB(vLdy_Utv7awh0I-UBkx` z)a-_|5-%5tS#$1HSJz%G0i?qjU`cH3z4Wuj@JAx5`Wh+qoSnffWv;P)9jU%^CKev;?r-I1?-wRq6cVX?a{?tq zYib*@`C@OiH*)^zD>21M2s#M~wRKLO$pe^)z@y(ishC=t-*Dgaa7l72gABB?k97&+ZtjgN9? z2_A)+T`QHj@T^#oDrB;LesXjTBw(x|Lt1|Z5(-ZwC&*3)&=c9wnu>)$?t=BThJ~u$ zp;hCnmTAznWNk52Me8$tJN0w&*U7$y0C>c!OYvq`RbgAi$1;<2sp;^bG*iCn9CS4j z+YGzZcID?|+yb4KGUY>m8yzpa(r2G%&}~UL9IB!zSQUk4ZJ9MfAFdc;D)vPw0-qfEJFHF&_FS5 zUP+SE)4tMP>N1_@y(iw~VQ%p<=n^Oq8<}BRgeYh-T2se+%u(q;Y=}P#tRL@Ru0_DkQTe?FR8;#h49uwWU^HPrZ99UwTwB{t%Flo13=u@r) zZy!h^`W=4RQ}TF8ipG>KEpLALUY>l!`;W{?M%x}^-;qm@>=Cf88Pss14C}8EbDMvx%r|$4cuM|~q>YCB2mHC=s0}cT} zu1nHxOHw$HeWx&9MvKRWaSd1sUxwP-q)wZgJE$ZLzKGdhra^$7H|NE4F$c1io?X3Y zW`!!tWOHlq@Ut9&*^!g%rN(Rl0Fbj|G&GATBj%Y|X-L~tE$GrSqZ4ys^?*|-);BAC zmkWY>4mak#!o_(3SZ_O*hYrmoyJ4F})Axt5GS{p*ShfPCwA>~(OJU?i(hrH? z`1+SnKU-D92I+l7B8Q^Tnf^kSxh`p@qG-PTy+_}iFwV1TcgslVM(HL6c*f!ERQJ_vu-ae3TDqLPFD2 zcDNvSy6i`m=uq|u6WrPjWzNh`AS1<-jacWi3uPBeYH{jASDR09>hv0!&RamNL$TCo z4lV7z>f3wQ_Ja}?Hv}45>#FgRy?dVkR7w2Sc(~TD9R-}1BjSkkP+q%C=2s9pYVfGb zPKoxhuFMz|#lVx5?mBMqy`oDYA`ZI)=zB)JtS6EB#=GltDVee9Bp%auK1BGk`cmP# zuy*NY$qTS3s;~u#Dq%=w9|Oubaqbdx0Ymh+J2aP}3xKB39+$}DN$Ylb{z>ME+Fk+( z!BuI<&z$>GEXTeBu0^XV8Za{~~!e6 zSXZ3LYZw6Fg6?T)-(uG8bNohZEQ0Bq9YNcl2BsNOyGZvhP{(YG*;|`Plju?;gTpryG#_E1uwAFogX1SGq%+{g$-(;iOG5T{IWO zU1vr`s^9a99b);>bPm9yrF7k1G{U!tlF343a9v?eL~Zu-K*9xeN=Uqh&7*rU^oO;D2L7RQF1(?dd;2TrXNZ435U zg>0(j>6ASl*1P0#3)j0_C_W41vfR4k@I(Mx#gFtOFo|ci#p-R#bQopd&#dLEYAX%e zJi6^!eg}dX5GOpq?%I4$8DXdIw7^fEXqp|e?|%}af0syf>u^!WJ!5flk-SD(SPmZ< z7+Ag%UM~w@I(4ulR`)Fd(k#x zQ3XQ#{QIo5yI<{lirvxoTwVym*6>op>sg~?vKk@fmOGsjlNDA?w^O2Ut52wSRE7cf z+0JyiZ^&_fAamedE}Qwpdh(x3+(QgOtEKcIPKntlsKmwjG1&S~yU$%Po14j|3D;q9 z`P&^>bi(#55FfUgbYti8df5xtOL_Npfdq>RG-A5u{7ebxDcy~E31%|(2!_kuN+`(O z)lX&v&|Jruo4#DNVMm%8&|WsUBI!;?O`~AJ?UeB>G-_5zp|rSZ^PXu~WM>28nil|H zL7BG6sGtm|r}YO|AUPc}yEUu?x!%0FB?#9@>ddjxkWPzwch(YuSHy~d4Ur_`4sU*;L#eOHUz{aUlk)>YX zFVk;251jLqR?$28zt0`&_W()Q@CTaCaCWfdDv|2{=lG;9bb>t2edh;z848jICa3ai zjPTtZ^4i)6Ao>95_-E+RMCTV2a630{6Q(}Z;qSe?pE>;M3xB#N*=XwcnSZl$Iu>3dp87rh6fRjao6>r~q<0J^Dw${XxQP1KbA zviY7-EA6>&ZH&7Ody*ycm{l|#_*v_c2k)(D`T!lQwNG|3dt(zE6%Hu*>gAi{N_)#{ z%refVbS}C6^G}}RW|`a!b^quetLLkL6eftd03M#0t-fch(gL^H+sS@$>OOr$*x8E$ z69AV3VafI89hyr1yZ}j*+0|k5Xrj#83UCF33BbV;{R%*4+yfFbjm(D|VbCyl0M)}C zaT$D;r004+0~L)n{kq>AQJ^lF0rz_`+xxR`so4?{G!wGD-d1jWbczH|D-E!&xMLO| zt>nwAdW0MgiF_p1f3dz504r=6^?RW6-_c9%A}XHyRzARinCEnI1}oJ5@L6s?HLUw+ zBqGZ+!28$Yr&r5OS8g3A?#Wv@$I0o9YZSmeH~c7jY#=q!diIitF;yfrCx=+Wa*G5| z&Lc_A%5m)h@Zp%m0VRsrx`%Ilz86Hc0A28bP_z*>4KlqSh@dr`_c8lo0GpReo(2}B zVH&hOqjq?(pJG9bhf_pTPBaE}4e#Wu+b1poDt%vOCe<*Avx&((Cun~saI8=>QR!>H z1W?>@S%y!#*M~22@&WQpV$3#$;ebi&@9dr4l26gMxR$Paw*ljm03YA24mRFuMvkDR=J*h6@xEjNr3(_%^K&iq3*c*;bV7Iz`xIL_ESW6oX)UXS~ zFBOPlpp-9O2DaO=V5zbZC9g=Bx4b*X%kIDMZ0b>A!mT?A@SX4OAP_MAb2o-p>C7mN zHKCX@JbhOjT{C|z*#;Yl)YB+)fy8*Ya_ehsB*^g;_Vza?OIgIP$Fl*vP$ON`?&HW3DB*!5D^n6J>Lrg{{ow1dQk3qtj(gXP)ywQOF-M^6GT8*PRa(|wVp}4s<+w=uaeLbGY0pu zCus~B>n9cqY+U;ZJO&Tdp#$6y@`t7Pp#>2K60r1hUN5ahqSSDjWjK2QFbJhK*b7$S zhX-4~F~1 z$eD|4Q=RiU%94%P#ufnt7FA#!YNWkdR^Y1)3YZ-KLVo5Ye9)>wWyw<0AfVERU#JE3 zL;tA^GCKj(sYoCX>zdvy2yX!@W=0wI7hnMXHR|m>P@f_*ac9&r+%WGA;CfPaE-p5# zy`2KSMx^{nAXq01D02C;3&@BS6pli%RI(EiR1$_uz$xBoksj8e_uE)tA>;)gRE@v0 z!SZ{(xpy2?JarZPJ@sDg6P#Wtj`(;x1J_sSL+ODvgnH4R)y1tz>;Y;=lCJ{fM$83KEmP!GfjUh4U3BLuwkTYoa=kw4AWG<1X z1D_wUim$wvIQ)M7U;w2tH8Dd21NY?OjC@|F=v^$$=T3GY8Z0d>X2+(VXph-NAS=K-A4%2v9KCH9r)&lDZu$X@w$)_9%qoEmP}bBpvh#0zC`r8BYYN({0lqu8a5J z)iX*3q!{fY9_m<~gExr`GfL{BaAG}1;p@WPsp=UI1yUZ^Rz1`)?}DmtUq zDxvh}B!l!jgu7qx&Rt7|^Rko(yw*3P*gq3K@LMuF)yRk-kUiYi=ex=KAofb zIl=dFk}QR9VbUmtuPEWY>UF{C8SI0YRU7WsGrk2$vu9Y$yQu~fhvc{bO?4Ay(m2W`Qe0N*Qf9a z@CjxwHT*BwXuHL=t_!fMos`k_LWCY{FHxG8j%bS2KC^9w4@Qe6)C&+=uoP$Hl?V-3 zFZA1J`BzERHgDrV{HwtlQpocXrm-nDdU5gvN#D?9?d(im&UhG1lgZNvkAZ2nv2%Et z;tN2~oB(!q?f3EvoqDDWABi>NvHyv^V>2RJeyu5q%0?z`Ou+sdR@X)*eoVxE4J&F> z7dKXx^aNcOJtkxS3i}g%H)`xzQV#lV^w(DrEpU3>z^lMc zF-b}gl9-E5)GJFSQHA<&agm?43C*XmJ?W@&ordK;*)wkB*{ZOuY<+Uc`{%edqv%@h z%W+>J%}_0;5kXK)sW#9^*iypO2A&F#TB}Xa$RW-as4Z++a`_`US0hR74Z4=d%@C}# zNCaIeLIR`PMt@pei}sxexo|gWx~)ML%9ZamxjW;1IeObr3Hs>_TS;BX=C}8=Z0T~N zeffU$Gx4Tu>HI+P?PP3u@!YeOB}1gH-{+=XqoW9P0T z6S29lLPkk*9Z=nnJ4Bg=E0w<5msvSfo`!pBwstkMZ3rRbbWz*uV|l4)pPOTXHvOma zIkA34U`e2YiD*eiXWxsJnXfm|ce9->=W$UzveDA)@(gHLUDPzy-==85B8@6aJiouh zJYxuT``aopJ^9pZVbv1(6oGw+RRDJXIs+w{lC|w+4RX5yI!pst-lHm01VSi`Rw{!r z)RCY0_VkyCOe?J6hs*E)(OB=yl~5DQOWQL%djwl=7RED@ zQL!2g&~!@?19cSAPNHC!2R7Tul?qO=h$TU9INRNgt`{Lh0@x|f?uCa<0UuT_2e8U5 z=8Ly)s;EKx|M(gerLIn}=~*HfAa~wegyo%OlPS-qHTdi2<{FO69oe)}GQx--_u_5- zLQAxm9;^t^ZwzfZY{QY~|AGwQPIOq-obl1H!fMr`v%sYtfjC5wBrd@hPI!U6UQSa= zG7_^5n3zANg5JSe!!$eNdd^_7pZQUG<$Rv#nV^x9CdlHpO>dPJw*X4l%wq(ARQF+B zSq5puDu@KYNgA+7bu^swLg-MNL`}yj?rtt|$^i1V5@b%$dM90?Dsoh#$Y5exfFkne z!B2$yhP1`cYn;FH&K*>6GXCpS)ol%|QW7{eVT5N#?pnW6@Qc}8{}xbi$^qY7Zav!v z$~ezm2Zdw<0|PXmc&p~0=puyPz>cD^<$>o+qHc2=wPxJK(fvP}loMmIFAy96zh&~0n*uk$6t^4gqp(pQ`H zVM-kVjIGajnmIeJ=odRKE;H}=HlAd7bf0884Fgto>|DEzfFN*%@+R+ zCS4K$CByhiM077K#n$r^o?Ywmb_fV z;9csdG4TkMl#PUf$$1W#wB_du5eE@Q`QCA3+yFgg#;-oFG^R9e>O%v647D%BQlVv{ z%#%EMlrU8`*7cwZR0}Ec2=HG z)<~+;wn>t+U94iBUhHAEUHZY?w)7D@kET%#Wrsv}L56%ZDNp!%<6py~ctfLa!q0{I zz*$>YxC@bjRbfTR1+cDp3Ru*FHY`fi>kj0l&hyZ%MusqQ`Zi{j11>UazP-2b_-zG3 zELI%d7_-e6fy=vJcD^d!eV=a43BLpjZsW4aNZq&OQ@Eb3ly!Pnh^j(Ab29UErx9zy zg-f3owyMygXXl6O08BZeaDW{@n&yHUWvsS@!!Ebs>4s^f%!OO>MRa@`~iAv9VUuil;Ld$P92Sxds>%O-upX?UQ{+MVY6w6B!a9y? zbCuSuXIQy^{pHVJwR2&5i;4q4Fu67+K#<}!0NLA-%Y1EfJ>Ede%4-S(B|}0-S>Tf!G0e6L(*QDcod&m+b`t$a!_`KI`&y?C9S<>0L~u5mPXw^Uft0^=XJ%rhx!O!0)WXR;eelq?Uoep{q`eKV9;7y1535{ ze+C5u?DFbAgDBboQgQnCpvs#-K)8ha_nEZYxG>{I|Nf@8t%0-J?mveG;Qe7)#s3~8 z+%8f1md{ZTDB4HQ0fXJzIvp-98IP()0!XUnmXZ6 zR*xv6_y8l+Vvqmlu&1z!4x%Ic=dcA@0GcHKGYAe7?-a@S&mc~)P+$Kus2RAZhyVGe z0M=^52rT)Z2}p=ifiMAM{nt0qzHs}-e^0=dEoP`^?ti{1!p_lX@ce zw&y{&(^KV`sOfv4UHsDiOd$Ut52A?;fJQ;Kyh#q=_(*x$%zcu)GB!7 zvNX=1tpYHcuh)l+LP(D&vx}J;At0HQVnEVj*soZG5G!U{03B}NLsZ0RF$pI_8ZI6f zaj7FbPcU@F%Y1yK&sBZK_DJ@DoLA>dYT`;#I@m%_t%9!3G6tcf4CU)U-Ov?EVmWdU zw@K0M6mc3MH75ii2KzM{Tt|Bivw`zqr4~+tK(_N=P(x3ceY{`Gd~`n_Q=YN0vL-8E zJo7jT3BLh=kIHQgXws2uLHZ9~Iy0TNGV6vXDFsttq(nXqW^CxzpJ(|0uR?nJ_MI87f$4L;#YJj z>Bb6Zfr_YT+$pQX?SJuJ1{P5Kji(_Ysz&M0Jr%vDix%*hy#(ja1|lj7UltuJ_EI43 z(&CHwPGXTN1IK9;`vagZt$!d^-iQ^MQ;e@}6J?L1!h(QF5Jk02L1*WX({;~oUPnvK z)}_tI$LAU4TP>@d!(aQl6i(-mcx|ZiM60ix1Rj5Eg zDbmzBoIw@T0kwc)mdu2-&AjHdZN(J}4q+Eu@5&nd=7`Rg;u@_ppVwGVDh`VnwJ@OP7x{w8knmb*aAZieehDs)lBK1>XM*$aie3DCq-Arj99Zd+(=so znhgtAo`L#!VWPqs-^3I&DIL~#-GNv#W2{-^0W6jp9S74$30yN@SDV=;4_rZm>M(OT z0v4T#6goVZJUkek_Lc(@bqTIz@4Vf>DA5bLOhh!*0?GPD(QZ`CH0>YnQGKxvJ=IhE_0Ae-_E z^T|?kq>>h#Cszrm>JcD@2^gmFp=*#);fYEx$Bs5SM7+ou<_14Ep7(k+|j1s!(~uhQ2c8~*)~ZsAsnsjNuyO1 zhZaslLhphOT^)naWo}#G{I1bm6mssOUT&nmv{1Qj5oFI*gXCZBbUM~2M+?3G14yH~ z^KU`4hekG>)m5NHOvm@nM)4%vEbp-EQz>1wd*5VW4kUGmeMPMF>UCndNs#87*?Y*< zgSU#C6Ez8wEDV~6xNkjTTHE_Npef%P)VXN)-45BWJYTT}8s>3#SoWy61NZbwhfd@P zzjp{K=~Ac&zb6hb)7*=y9%*9>L{`?Rv8CJ|NN3N|0dQ=3dGx{F$RFT^57|%-P(fY~ zeXz|bbt-%`TF{U?QPiH{NN(@; zVb=lDRe;fStFBIzGw3TjXY!;V#mZ^8r|Hcy!D$VZ6ojG{N?yndFCB-y2F@@`+DV;2 zqwwWJsYn&aT=!@1XNVz6h9tWk_94nOVpZbT)C;c>_p4lJIN-4$UMsjnni`5CTI|6d zfx3*r7>BbFi=ewOeqOSwKYJAS^-Rprl(}F(Pjj`d&`bmpr(@;O&S0HxW{>K- zvmM|g3O`jALn+YaFHBYnx`MpRP8jNo!Gm^eRyXx0%G1ieshm2Wx)Ez?;DOkyjl0&$D#j~%C`A<3$yAUQpHBazf* z-I;Xubf`Pz@;2w`!oELK$_#{@&On^lRZXH17fS46s7g1^z1M7!B(V``bmiGYGvO+f zRPP89%48BHpsVkDcK0D&gwE=c4up0T5Jm7Og)Dq&5s4g@WJ^ngd;qYPTD5+u<>ArDCtYbfV)n9 zcTO8>quCxyh^D+BTJtPeV~jLc^H9t7*ox}Ly*a5QiZYugX-i|`mpcacfLk3}OYMafuQss;R=$N@y zGMaDR4ND(NMli}gcX@&^*zP5JH|3pto;r_Y&1UuD#IYOG9~QiB1dDUytaU6;~=W|GIH#5KH59S1ZH0Ybh>h@64gEM!v3%6q4 z9aU&Y-R8C9rE7i&(JW4q2XY+ueR7UIBNlYDQ))!1i+RlPv z2;F|oGqe=8q|wKU_?@11-DNSVdPFJt(E${ARu1%2TU$3lQ;^fCc0xHBm4gqcRi*;% z>AF4a))0(Mc?4*3vuzR^X*!x*R0Z3PwC#RD2)^vo@`gDH{nnV{R z3J#|(A5@)@$fI3Kyd}b*ah-%&Cz957t|ap`dW5~UmJX$!wr_9}=PR`QO2vfs4m7zx zw@G%#YBj^Iu-(&wAGG=GT$=Xo*ezJ%+Z_k*NEw!t5TJe-9VIjRz^G{CNE<2=78J%< z#7PNIoS=K|NJrM!z4L_oEO-M(r6z(VrJS1Ut7cx4&7+|{(6ce;QKq-v)T_+;A-9vc zJ4K6SNcm7@gqvgRn8J(zKStRQRg@N(qyu3|I_X$YD#9SMa}$#79Hsy|miI8gKHx zq$yRW`mYt>*mRn-?`QHoZ(xE`>@Z4-QZ%V)xTs?b45Ft4t1l-!IH=kSyzB7 zhFqG#w62g-r0^Le383PeAJZFOAVsUXsm{PJ2_>@gE6r*5^c@Q-0q*gLqp?I)lgvwe z!4q5i#1_8Y6%;WP?ajPxa8kAI(Zy9c$!OAn3e4=_eb{5?<7pCPZrvO0@95eG_B;T( zaSKq?xLDl8(GneJTbQ3tLW{0sEeq3Ww4!7Xx5Wew<)u&v7Tv#vuH!h3CN;aBhb|f8 z5ACR<3<}GyI$zOT!x+WD1C?CXk=fEFqu*FN_`oqHoZ}aYdoBG*J?H};Ma8Fy{ENz7 z*WiW;)GS3iy}Mn6DZDMMPoHxSEg98`AUAT6_cunQaymdXe6Z2%hdVC_=O}AhpP%+2 zH>|46Wg|1=l^_XGgKX!(_<6^@&_@mYEnRDJu4N+@dywbr&-ye7N6`-r8s)Ys zxu{cXdn4+6CQWOxzNGlcjLUonnyR~ng&^~EdGy>iZ1_*X%Uw$(6OyHz-wHvDPteKV1WlKxkE$Et_;+qtt8CwnZy@h0CpkKm zH3)HK_n{b273ey3P2)!nPojcBbU7fi|GEw!#OYJJc8E+$H<4vA0wl0B5Q2!T<)2$2 z4oQ&vO3quQM|7A+dA~7Oc-%SEC-Eg%AYGFtzG-Og z!ddv%mr8)b!if?S9`u4ufs9$yS@37{e@|M7?SBqh?%cnGoQeR|r%lepxAO!)83#!S z@zrD59Ds({H)ptx235~z19vxcmBzE&8n^k%!8@Z+ti<554(iRX4 z*+4b-gBrb$O#hzF|BtOJ4~MdS+m#Yh_7I{m$W{iGEt0{EHEW1uiKrw?QB<;yeQepK zERjk`sK{;@6e^-5vV>%b?B8|k?ft&rcl`c%kE3_Y@;vu*FV}Tm=XqX}Ix$ViTgvw& z;$?Ge;}(BjS-weEH;)c;ZL$?o={xhjAaOfXd78EbQOeA~DyA0x5%V5)DC+?LA{YpB zz&GPYAs%D};yRLcH7MTHLUiuOC&dCJ^BaAWIRM&1I)Ek2 zxPaYV3m705EkXz(lb^Hn0^5|DU5XRTYqJ^aSp_eCROIbxK2MXeiVl(Y(^hHIKnDqc z%T;*^{z6W@0L%zVAi4_5zBo+tAFVF^(!PK1#=@tofsU)QzA6{G3z432dooY))IPPK ztjSVfhI_o^^ZSoM7bXtDMVb9B9rZfx`|I5<2sJJuLDTXY=!j!{IKsHs+lC@;A~zVD z>TR5iF!ZO(Kq6{*1#Hr!-WeNZE62T7W|6{P4&*SU132Yc89=_ZusiM@2EYn&JF>*Y zThEc}QEF-l?mT1VmX3mtQ2bN|^!l1d&p+=$%#V$X$?{{6-QD(KfI>bEnel@iOvw|- z!o(jruJhJn!X#qytLP+ESWa#|;J>;HWbOz;7zh=AOox1i@?YzLsFNrGmSQb636o*} zaho|gSk~wVfx9jCZ%|zk0=OY!m9;M{Q&@y~m7Tw@)w;MD>jP?0#DpcrHy|8$1p<4g zq~?A|vy8gMum)Mqqzh|^)cy%(S_U@C;f2o>8aJYI*s_h=WJLIkPeAE2R??R%Q!LO0MO_?Dgz2uz@9}uj?&;I(d)aW8S+>v(P7XK%yrQex%P; ztsBZyMr<8WTnYkTGbvt=H>!D0?B#!_$(VbYs3em!HWP8=8jAvKD7RDJ3PZD4y6!Q!=jO&`~FV+(9PB_haSkz>}BbO zMJYoZhm(=v)C$cMNenxazfs-$XN#KD(bcjS^>IX~c;?OZHR4Lq4L^cljXdD;u7ErR z?Kn^-Kosz$N1(}1K)Z57g;*&#PJNc!_zv0l3~mCpDCop0T+Y~w1$Y7k6Y6jIU?A(t zPXJ~&0RJGmF~nEjE02A{-l!T`;mlyQg;X=ConUuMN0YBB49`r>CZeG%Un(;SP(~zj|L?m|SwVvIi zp?+>UA>TdR6+K9^U>FHV-@#oNe(UW^_UXalkEgFs zawJ%~QM%Y={imqN_(%r`Nf#3c>DnswUxf?iGO{p)`q<$jUN(2Lj{YLE8|w>I>n2$8 zOH}<30SgUbr;={l(Ssx|_sM>PR&zeH+&^c7MmB2_y}i;?!RY0c*HeN1( zk$4?;DkJM-mD^bqDvxa6#mj{fQVK?R%UB`MQ7T;JvT|$vOP0 zTHd-_pWWXM9*qB-Va6D;Y{u8)*{7bns5DIZY%SX7yBrp>a?|;-KDN_BDx?k1rpx{w zh6PKK9ZZF3Tmy5R^8hgUOZaNMU}Fgc^ipo8k2|8dFAA_UfmNG=?rOECn>(xbV8oG} z;U_E9V_R5^0z!qeUY^YPc&4NaXuUW~;CtDC;6VGf)~sn={A#Cu%;0@d$yMnjw}4ew z3A%%k<01ARDv|8jo)DJH`)Tw;pR-s(1$XwY$_ zDuiI<>~@Clw12+f7ES&pILRcDV31fCI`BgJ{1$C%b>TrryvdTFn-7$&?DL2DfW1e2 z9Xeu`Rq-4^?UIW9Rb(VqbYe{}5Fzqr_DK%7Wi@D_js8$~9A{ zMjoJiJ)ptd1frRB=Bja)CYP~O*7%|Rt}Og}ae`21bX+Lr{#&GY&byZ!!O;78Klv9b zYsSbRjA>mU8feqU1QtTa(krLc@y(rMtVC;dF-I-?Wi|dbnDCM&e8p?2#+p!ySdx9P z%U4#B;qTen5Q3FvYeEhh{8fVs;bY{=Np*a(2oXEW08-Lc3Gtn+DyMj8A7!>DGbZMmn}?2g`B~Fci_jdq;Mm5 zYHz2w?+bXh(*F$%pCZZ4JJVB=x`$suaOtq6{1kO`EX4|+IVKL%=pIp3G?%~M_z!^2am&s=~F*?dp+ zv!nk8m=b`_A_G$G*waw}Xq#_y3!i&*0Mp@Z9Ee916dQOTOmOzyuxVA&pD)!pAiW)}p+k21;`I|9qNBkYW>;0zYD zp|2x+n1yi;2Gz$QNC&tFK)fajuB8H3bttM~vGeTK`N_!fK@MWR&Kc{Qs~k|jjw%Fn zEV59{f*xjvls#09=xJyf>9i%E8JjqQA1OWJr*c8bpw>QCclT~9kGF+$9olz3mF_tn ze+FvPj39d61v=VG3Ttk?+;%!b=5XrJa9)^RUcLil@`j-nOQ0n@2-UKOoj{4r+%_Gr zaZ5cx`do)MMB3S=UdF>-D@gxBX_hR1Sk%-W zvD5OBzSA%%{d}^pRL7zJi-^dI41L_e|6s z9#^P&@=Xv8)cdf^dQg*axpi^9rF*3JJDj7A0Btmt-N;4JE<8`xXMK`Y=5#nz{tOPq zqnh@99}qwiU!xEnYlG@yRoKWzmRv-TJ_}hK9pon0Kdylxs`CU-lTuj8{!_4DcJ5TF zU=cXgBsT81764)N`Q%_0`apP6hYqD>*VDQ6Sh|};UM%f#(0(+o6~(eS4W6>mL$RDo z3gB-7<{kynHBXT^FmF?Uz?R19!zBkK9+%yKg5g+kH<0W)W>9{BjaeXVn&rK56^@m^^S>=klpBT)4Lk=lGiL5eQD?!Zy94cF5m4m*Wei4V*z}XhhOh27pR}-2!=RG&26z1Z3e7vhduu2=5g+dY4Xv@K#E)^h zkZusXa$EuX6|DQ~!^qs&NC&5$e;RZ9kspJIU99WNq!kE?%*W(LPntJFm>e59oB!~! zR%1}v4cI81PNb5_1ZAGj+wb=lP0a&M(fMqCBB$N1Fp59EBg*K{)n!3EjZ~;xD4V2> zr$+4vo~(H-V%NBn@D7;xX{hdHZ{|5zF?tVs65?dDkJxzSc-hULptg__elE28*JSSz zlifWzhkhy`SlcV`1@BNoP6$Pzq1x8dp0}ifJqy2MOaNyzN0Pi6wg|(gMr|; zMVOTsYrfTx#J*Uyo+3P)ov3Det>|9!AH>Df#wWBkKNDQfPLgxN3#J6!%$2^MJCBuq zh{t$j8(B-$h`N;nhd`D=cv({nN!n$PG3i>h{A!eFgd8|oEOf#@r-+I_#%58+xo^np zoI}_OPGDD&7TZZ|QqGH)j*-()bS1hanP~bs3j-o*MF&%=U597ZjryAF#|;z3Bzzk= z0q7w3{HR4+E|axBE{(}c7rWQW4K(=VE?%8E9Hacs3+*U5QbWIdU`Z@eGQxsp#1Xs4HEYSB>ziQuLI zLHVQ2q*I^`H)4AS=ltWRL^0dOrYAn9Q4w1q2y_jyCY+FHFL}HSFKnKto~2Cr4SwNb zJ{Q3L6z`S^PCsRa24|g_v!0iQ%7gQx;Ug;ZtTS~p&Ktpxr@k&k za?Mt9^&}Xa30pRfWYmJK7StrWi>-Bzz*d`EXST7_oP~+@=QTMlGBHbK(Gky!hnt0V zbF@D$54hlvlRtfi(@r-(jJkueb@k;9 z+{8E}gTz*1-u$zz_g@&kwYTqabJxav*AYDajBJ-%J`RyVQu-`J-?mObadQywysJfb;)+39da!{_j0816Fw^4w++z?(9Dz+g>Bs=GJ3bEqu&3xDk z`x=Dc@3huIm@*#;TSxjHe|%f|pQXVB6l&n|=f|7deGcgn&++3h0b2urMc@77Zt$-8 z0`5}71UVs}A3#1>OyGUL@d*z5iARZ_It+igTQJ@)-r%SjB04izFTkwep4FqaIpr<= znGzpis#^iw*&<2A!HcT*UtT{l{3($SzHEb8P5{}r)(e0$pGjK-%d4k~427-1~Fqrr!{*wZ5a24JIbJYG?-!oU&ztMJY7 z9f7(>K9v+|Ah~~je4w3jc#oK*i3(w;6Sq+`Fcv3bz#EotX?{}CkQ;(3@n(P@{munU zmi9ir%Wt>?-JAsLtO0cxZbDwL;m@aJdh2-k-7RJ0OZWFkt3;9Hogib_0nw^;H`p1A zHAD8v3e1>_+cyXx+t+$lbdK7H$g6*ED|eZYbCP=57&8FgP!Hrbyc-^O(V}|^YxH3) zCc?c)lTr^!lHh>s+I7^HHUc2JZGq9(U_hMDk`ndp4(n;Zi`sZ{&lp&}Q&~j;BsLb; zuJ_qpYy+Q;CeT6(jjHSK3pWtqYzuP*T9Ty-+`VJN29$0XW=4t=-nc=wS?@noxQ=HV zXfce{H{0l@U+8I@GP25R{}Xor*BbEUcg-_6Sx2iQ@Kqbb(Iqzj&leRi*Nzs9XZ^iT z3!tQL{6GPhKmKuALViZE-JeIK{`Z@jK~q!^|97%E1Fs!0Wf(AL7%=bHrZp!b%)zWH zFqi0&mp-eg3B?&mugM!zN-=@nZaBn-fx}K`Tt*dQy;DTi9?<&!M4sz_$yRq=MuBM| zq|Ksa!}tQ0S%U$`y(ch=34X}t9Jw6Uf(hE0a3}Sz>nT;Xkvi-Cc>G6NR`27~JGk)l zj9@8~zDt2pZ5rhlKkk`7#rys(-}rLk!O-^+`@hW_IBCqSyq~y=Z$EhBWuG0!WE;(Q zA1*a@X{J=RS>tfl9vq`3c=p!-Bart10EwK0^wHH>YXUw?*CVP&!NH&RY;58O+w?G-l6ALU5BzATr!~l;TJ7FPi9WY_ zgwM+VqO!alCiCkJwvrN%!2%P0W27-_Ys;6CxxuN`uzfDP{K#-ZNtd_I9ylzo<2s9u z;}xQQ*}@#@rqIB#nVOLG3MPvf(pwN&y>C*}obqPnM2GKiRd7u3b&6}?73j6rry7Cl zoS)%t7uOUCzB5)*aGe=`4 zu6_$K@#2{!E|#&aTn6Kx5;;22rtu$N85t?~h(sh~2 zVbsStvz(}6R~f$esxZ=LkBnz{h=;(b#+Q7toFpaagE1lZ_cNd#V4pR!_%5dxrpkIV z(uO(XqFq_gS^I85yU{y~=NWr{rYTv#riuh_@ zBi{x@@e9-*`Zxl|4-?JyU5v2T*mVQtF&Tj*42Yld8{!IvQ_oePLgK)s?Bf-~^2G}z zsO%`4DulEm*Y!d$uN$EZ_?5Ll)QT_V;l2T&;YQ&CDhvm~DNuO;s7aDaGwX}sWK#>P zU^mK2YW8rmYz%VKxLx$iNZY`~N47cPPG{6NKc;^Ykt86wN~3FjHOyQ9L<{(=)gI;H z*2J?L=SC^D4CmM=we^Y+sQ;cKRJ#Dkbi{k504tfEg?b5NsYq2cRFuE)>RZ>=vls z339iOgQC+>Di9BB;4by!zXE*q@Mrk@>Un*oX?!9igf-A2=gy5wV9+6{w6yLGeiIvI z45y<{EwuPt~G%O#<<$?I72;*%mcm1nRXTRtXDm_;fTa+nCcMtTOKD}NWX!o zfM#doO=7qt0dslOY^W{C@!&YPKgVTSSKT054ujgnblY3!bLZ#~GQGj;z4Kx53lJW6 zxVOnW=F6c6G5~NraiAX(KHF{p1!KFWy{@ZqxIrPt1w%$0wWSH<7dp3Oyn}v=7u0C5 zPw}n*flb!PSiq>Pu%L}h3Z%%{<5vXN%ZBAHh(b5;qud#2=3%y$?%z9uSNnu#6o$ zr?_^FY6$D;DQl1PY6FBtu8@F>m?K84Mk2&J<gs7;9KYeHK{m^TumCoAgaxkKj7rdJPNF=mO0;js4 z3E6rlzhnYN+$7w@UQ~%P_8gWgTAe-edQDqA#V6f=JJYU{mY3BB`?qC?G`5XYUlwSQ z%{Z{oGlKkxu*9KguHj1)7hq>NaJ!8{!15)`-a^9%-BV%%a%7SKzIPKR8yk^x+;s7X zy*nqj%DzzvL@ulwIFeffgdhpYwGJ7I?f0h!0E1wLIq(9jE6bK0CAj?Ma(*h%M6D3J zx(3RsUHi!hP+~R756(soOJknQ>hm4~ zqB|eul`wC~9dxh@c z5m7k}vAmE`80|fb{e=+0aW8&CH`vq0B*-BBmht7R+jNM>!74A75OPTR3Us~+ zfZSA}5B})HBX$)zPO&N}i`@Ns1VaiyguzM0^Br389sq{uWbAvVnxD-WURwdkfv^d$ zQ%>ju!*BQ{uS?(ZZ*Eusyo7ZqB00N^Qp;Mv{8K)|UZ?3O)R+BNE&T?S~$b=;W>7P3| z#Kle<-;Lh4H9|Rm@59c%ND!STH0x0tfC$26Wow@e%&~p z`6H4JND}2Mpl}4fg^yp(H0Sm6>WVtKQTE@J9q5DUUjz1#eOR4GiQwXyit~7fm8HU7jlE0M_-pWPu5`U-V#1TE?J_(gDjjtXY8JN ziEm3M7>H@Gj58J~lc8>J?VL|}dZJ#TJ9DzmY?-^Cx>>_#WU>Bn1VWGt6e*s zmc}OEW|6gBE#O`8_!RitDcZ)@UlffsS?W;n0nxAmHP`cAx*7R}$}SHU-=&wGx?|wH zI5CUFDKXfYn>cnM1s);8tg!AQ!`~9s;iz-h&gV%@sE$v85om8 zVz;dJk>~bF{D^H7IAUy=3pUV*D$VhJO^_VIud5zrqp&MW^n6>g>bsrfoGF3(aUGUd zz+_JMc2k9xP|SysX~6h%pWeji-saxsiEmXE2R5j_{rl&!q5A|EI-+77ij~@Y?wMN~ zd~vtkt6ge$VqboRxsfS8p^7QL%V!s(eVyZBC{AO)HD#l#*N$#lZ;(1+=LpHG2g`E( zn;>5@gn>FN#b>Bn0yA=)hQrJsKExJ1x?g-H8=t_F21m_Y7(SsWPoDXBRxpDFZINa@ z*&&IJ^(S&QBXV>zCi@%Kt9397V_6N=#vIL1V5($2nu zYO|`MMbMN+>B9-K<3} zT?;r7%<;NohjdPJi2%`FOM1X{owk?4hb z$7GXFTurnnH&2o>tfo;81`Uvc?qxsMeK$jf!ISiJ_Ymhk^@>q_Et9C> zw4P%s(+i2f@C?x)5_<(mQ1#adQ>A5*35fIa5e8dbY!K=cbpHZ|tTSD`zho|Z>uG+< zQ1`jp4B5Vzo_S#56K2gja@pkVcM-9%Yvg*tmMzkucw=xpy5Z#*bJ9CcjHh>*84u8H9;{n^9&Zn_Jo$u?3y^L%|>HP)^f zYfKxXI&^L()Wbo*#dx(lNYx`ctAnh?q9qnH5%!iSziX{^-u#?t%eg0W!51F)3vQua zma-Lp()i_A{gZl}U5#}-KHUpFR}V`&3zMlFx6&F4LF4A-OA-h^67LOi57SM5|g(mKJEjnF!Memt{;oSL}MVK@S0I}euN`j^ZyH{l`OwTAv#idJ=TO+IR8$?X|8Qo`3PK7KH0Mi3E} zq@+0PU(!_$Iigh^3$DFUDnGZ2b`>=*gpyertwhq9+w@}1KnlrNYT9@2(azq+54yfI z@l8I**^G1gzg!xIx3zw&TSx8N&;~?JjQV>Cnhi>vGMCULTw=5y7Ks!PVPBx>NVQ_5;_oJ>hD*2p3#$|0m3_vVdq|t}`_Y0JVdlJjx2jSTKn{4ZcE{gqK2Vcb6 z>!?WyS)czm$){}c?f-k^l(`2W==}fwnYi%aQs+NUC>Q)&pz;(0*wdes6ArAWxc=OK z!VfOp9i&uAN#ZMbPR59`3^Vra8kiv}-1heU%_MjJKR*fHF@JxzEyNoGB^OAW{~{0e zfI=B+rt$$^8S@|4ef(B)Me7C#VpEq*O zno+4vL}$OfIGK9t2^(RO{6!FqG9h9F-#owly*4bF#x9z$_9^+Oss9SJ@IoSzg!OZDUI7H_C#3qe6 z@IB6&9)bP}T=QTzi8=}*|I<0^-ou zk=}Aa!{LT$1^lx{jJi>rp-x2MLMG9{tkQ%$i?#<_L4I9 zB$2R-^22k9^|nP0k72w;5HS}^>Jz6W@%sOBY2XHQm3kXzRHt(>UwYN{O1S7_jol;5 z-~zNeBB>W7gJ~PA6X<0Kn2f~V=jKH1#f1#{p4r`q5{++EvD+IZFXoVtNP)G`hIqV0)^cqa?mHXz zgpuAd`e5xEBj>{;B}1~0#BfNWysdQVSGBBIgd@e^f3W-G4i%J5*L|NLwRBlT6? zXujr(0)!C%tIzsh)iRkAK9~a1M(0!GJrM~8i#lWqTS%JerY81TB-f>U7~8* zCXH^coIjHizsk#K&mIFq;e|TJsh(oTMCbgx8l?4y48GL#EHozA2^vUiIcTp^138^# z*~`b^nT-6|&=w;)7~MoP- z@4S)uF%*uuz)tl^&3`P*6ey&`7T;roy%C>(%MZzckww}7YdOS5LYMx`-XmeEnDTua zix*?^f1p+^I(_$g9ygpU3h1;sm2APcr>@rR<{A%!7i!+ zySnUud7Tzs_xLZbUxU}@{>$qX@Ot=vdEEkDr~a4M_u#4;=Kie35M#hN|DG8qqf{}D zb{lK-KVOf4*Zq9?Huy!*VSc)o!=%4q%FcCUxGu4`!>|#d z#{c`yVBWp6?o@5ud3dtg@ZXNQaBg=3bRx`XyujOOfS#xc98??VZr46v+e#8WSYf@~ zE%V^#l#Cs7W0jrPN>U;ayx3X(eT-Rp^0LBg>XkI~>kgkX0&fZ?r__63(OH2Y>y2~I zlfnO+4__?Bu3U+~^X-5B3eIOLK2~$aifW4E1J_xMA%euwCkcGDgh$p5aSt^rc{{oP z--p7RE39?$j;%2lU(|8@&wCsRXQ@3`Q2&mgaeh+bKjziTyT_BK=$qb zjx@_+)oIj@k^H3b4fR>^G0c-lH0vv(E67c#@iAlxJPlbdfwVIKtm&P|HGJS;mM2_Q zN1MPIU%Q-UgPaKYeL;)+qN0*ybNa4K@+phtu#^irq$i5ng29J4RNpMVYhb{QFC+Q0 z$N2l!#JT5%;H(`4X-U;j&JG2*R42!4b*UeL=?_IW9ssuNzAFI)a{9fo89vK7kB5{B z?9v)=*<(Kdgces&XD2JP0ktb>ZEtiF5aSE~{hH_|RYcO@Qu1=`nUXNFmIwBfd^=pN z;k&K~Jo4#7Or#wEUtY9{=#oKH1_Zst13Kv(gGW$o;3e+7s~Zvu&@-f0#56&*(D+Y> z>J1@<+)R@oyglz4v?RBJlZy}!#VI7xpwO6NvJb%EWoe<(6wHI6&EjOkJLt!SPzenr zCIqN7{r~rX?>j3zk$gwEE627|NB#* zR*BgTgO2jzLglg^1s&r zCVbxc6q#`ZK3gA#a^{BJ<+V@NHx{!COJ`w?W0{7uF#Le<79z90>9tTS-GjUvih2)mCMRsb2lq#Ppk)8dqYwHzyO}`|qO%>m9QPscaqKs0X_n?-NMpUF9aP*w@9!d=k&wN78)VLiS zAfS+rpE>pz8}lLOD*W;o=AKN-7;0NP0PV^dH)!g*YcPIUX+6@zEr`yADm6Bl zfxDeOcblE@L!Jt<@(U&GE}`?|ADG_K0e#PEfG6L55rEoSpd=O*IdY_T*>VmL zu>b_kSOPYecf5P|@3rC7Yj7$CBWDP7k3du$05(0{`GTh!cRc)HZf`ueJUX`-v?J6a zto}`=+NpRTmmM%+EvKQhyY7yg|_*#eFjf#HdKx1ihE{hl#Yx) zsNk#+an%IXFt@uw%Z zBEQtJtFaVJFB7*k7FBA9U!C@@Ak=vW{V`Vncl!#VR-g0XTjGm*M>}pc-cAKasY>YR@^oy77KcCvNtugs)+UTB8}c z1*DGsC?q0&iLOTQ3NI)w@<`62U@fnw(BH8XG@O;%g24Sbr8d7l{5Pdq$JdLbP>Cm$ zMf%^CdN4N&K`R^#CFWAfubut`Rz zkVvMJ&m>uyLv5WyuM3p$jvw3Md8htx5e!BRg1Ui2VJ7MHtORmYiHJW_s><${ z(*O(F0hN_HX5|()dU`>}1)|8PGujQV2otfao#w8c(eA$?CNnkb^!{n}8yehay_cfH z_%ebS$f`%1Xr*^k`8Y_O34^Q?uwM{=$X%tvVA@JUMhj=c zM*Gu?(K`0=o=TPA%@c^Yy(Xe$3RDFTPu^KAfM(cvTFtHcTjTn!jX%osG`RAqwmj%JosL7M)^cW;?K2)??y$#;ux>ct>(tHS0fs2FLFIVC-3MyX?7KA^|g z0UkHbAKMWin%w8MxmsCX)cyGa*aI%Qph@02z9T+cxK3`#K#C-;*j-^8<0KA9vJNi&>3j}2$T=!Syre3JbE|6ftc zOXp%#_3j??jNY*+KR854aN?~5N%1A}-T6pccH-{3;MH^7WR7$B&7`9|Z-QUEOm!u{ zwJoFja{HDd<(+ASG1`~%#7RSaP2PSyp{W_aU9!{}MwpXR90bU+R@v*qBP)#9DAR+f z{&LXfE32N7X0-4LW1Cf~_2`w&gdyNACsdqgk4n|Lv$O4TPiregkr=* z@23H$`y&xAQweo959O?Tc@W^mH4Yb`TSCg-T;a?nb0OBGG=Z?pF)@*e0;2fUQ{`%5 zUpD)mD$~|syW5ec$Q2PEI|0?j=!BlgYM-Kkz9g3b2X$k9?aaIh*u&HFli#5nYEtJD z0o(6ia&5pzM?01=AS~xDOh=y>{INN?tDW2Kc`-1z$uKt5MrhYt zZy{^X7AsM5>QGLybG|E(+GG{Q_-0Rc1cazwJ~xvL7@915Eh$y;CE|Nu(@*_9b}%Nb zP@aU$Y=9ryv37^?^J_|+uNsiIjh*1^)n66X&J3jlZeCU7F1)4K8eXC~I!+nCvJJ>l zZ0-#broZKNb=tY0*QW-?siOJnb)TxdXN)89LU$~se`RJsRTWWS-pIYy6DG@YXOFAV z_OJH&*zeidX4fYRlu6x`l6#xmPQFx0E}mIUA!6_6I{1%m`Xo{2WPXh;?t|t5?&<{h zQ%~{IWxJkG=ntWaR=U4Sn=c2W!5stS$r#yo5<=#_G1y4)iPYeA{3c^J#e zMU3~mF< zG*91V#ot)$>&7>ME~QOA^~FXj8t?j_yvgwipw59Rfy2%0N2kx_gfez81P_Y3v^6hA z?N&(b3{_R)W`F!pj0`TF80aT)!S8V#;qE2+*aaTf zW0U*k`cm0;Azr(ax|_kBR8e)Cn}1@O?Jj8cFg%&qZx_^P$tzx8&h^ z+Xq5)!%qI>0BND^(2(pB7+i>0>SYl`b)!OqO*Jv)MlILR)+rs>^}r1i#Yon>8P7;d zyelg4B*jW;0&oJSIAy&F%kH0CY%VfyHq-oK&**vxCGU?(KX9*Xg(JuKi;q(San=nl z`P~WOU5DWwlU6RTWpX=<5(Gz>vdGn!$_$$1@5__2^zBV2?`H@aT@;A%iR@GQreP1b zF^>?eks(W>P;9%u>exB%+6dho0do*G8TM;cR>_d4!w>ASC)>7rJiFVkB6;Q9*XB*0 zE3k**0bPbks*Qc4Gy;)^cIUYhqWJytIJYf1`gT_^-?ow_F3*VXuCpy$)~e!2&UIsL zhbGb2sigGE&@jnTvqaD^8p&V+q7_TI=ge)s1c3Ri>@JsM#l}BIJ65hB|06eTe)hG} z(1)?_fI64!}B#!pHzhC*_=110}3DvFI zZLw5!;)fkBFSp!oS!5*H%bI;zsXMey_q6=TzDFgMHc9RQ$a!bu-VYJ50-=3=>RUiv zM49WsmAg9yM#V~*G!5r;-95M?R?eOCITtoYOGKm}$*K-R}eP zcQ_1Vy8`i;o5s_jmyRdgPMrSKJiYIl?B#5At;-AB+IUhs( z6L@@5?3RK%pMreRDzAz`b(FSeOpaX;0lM|2}#zT zy{(@6DunbFjfftlVM^jb;WUp9>x5G-4)_Edh{^{2<3BP+PgG!=Gl1parlvAPGxp1;`>O;BiaY(1es zgDnELILZ*k+h6fHoZDW>w*Hyj-0Zn?yB@RlD8w}-J63?R_YE_k1g_ni!o8zg(|w5+(cbzUrLx;O{A;C_PMzQRMw!Mw%EHbg5z+Mpc~|7imHD z*i|oRk9j}IcHJeJ{{8q2;_Z6h)fOs}u^DxtB7?HO>95hZ{>o81o!Y-8i4QL%dcu7whX1@i2Ql}l-`X4H8M4xe zldL@^Y}>Ve;m*9i)ph4wIIe*Xfh6h!-ngpzl{3y5mHL&Qy}Iv`a-YGBlRa-Gk|Qrn zQkAU=S;Xb^;gXh`FKK1jLj)PKJ+A36xaj1kFjmxtGYrak%j}G$9a(39e`7w|kX6ch!(s zG!i*6OPp5LxT5mN_lA3v%*ZI)yZvP9U>US54T5sXkhQ%x zn?6Q6lS%0Qk@?ZLf~_MaOc|ayOEa|R5=)CI_a1Iu!apvoVHm^EmkBuOf_g8iKwkX* z`3`%ssT;aiIKQ3p@3!B53Mn6!gQr~k_DN7Petvuh!>I(~y zAxpjj{7RXQsxZpwhvjyD7p&&J#tutFjV^+J2&^9n#b^*TwZ}G1@h+m?CkJJfh_)hT z0yd_x`v-85B$QYUJ57T;9B&i`1qES~p;5h#vv3N|^~>w7fEmD?v_y!|l&v`iRBeS6 zO3aHVh?|FqEuB8m7lN^$KvIMB`gzLdl4X%~Cu&y9w+{T>0VuJe85~N#-Ym6$JGyHq#4Mh)T0l~ zEiZmdXW7mqKNP*;Zo%Rmmg)2ptYG7o(l^IecHRsat^p4@CN^DJgXET2d4eXSEZcU6 zb2s<9U9Yacw6acpE#LZ9F=+VllPnl}fJD8c2|0epzW}?g0=X&dE_H5(9*gAU@*>?Q zr(bpKd_S?g=P@kWxlhOhmlKq1{;8MK<&bPVWHdE}P+r~c^#e6ZK~R*3%$+H>%JspK z#GL6pP!(BvJnWE^d>H{p@rZX_54j;L(9qLX=zwn0T9m8&Dfao3OQJ8uc1V<_cQ=QHTU;X-hdGpCu;5OVW_@E zhU5di20KVQ4UT_}7EJywMOEa?OkKGptVYK#BEOU-5Ic8pD4=0h&cMx01C$F5jKEz= zb{+>0DGMiEUMUaRtds=N3Q|3kxs)!JDFdxPi^(` z9Y{ktLPbGCt!iFXl=<(unhr3IR>~_I-TnkynR2JlZW65Z2(%aT$v4MkY+4FqC`@b=8 zLh@P~qpc?%wS2KBdB8F@A*Q0XJ=myQx2Ba!DPIB_wv8f(RK4z7c*C=5(M4?gXK(Mz zU^J_Bk5;fhtVnbeDCv8oF6GxO!l8nf@b}#pVys&HR+Oxop9Ad*H7(lo=K6F`BP5uz zm)Q_s_WOXlXgw?G_w=>YZgS_>9b1-!rMq{IZ{9rg_@H58^xC`co0 z7dyvT@`ksMKI-Or-WE$IuhWeFzcXdQe+`;q2=#$Z2X^>S4!?lklYV0=B; zA4ALoySqaSlD2XqZ)yTlu0S2$&?ao1$*>`Uu(noQIee^zO)LrF!d^h4?$A}-8p9wo za^j0p!-7KpRLFi;wV2(~eP(B=o0CcIk29qyJ73vuR>yt-y_&Hn)q-YvJ&6G`lUOYu z{A(@`AJDU1(&a(o?&lu|gb$3U=lF?L;+n^i;|npacyNO5-x1#$QuoFuMvTXLC3S0< zzHMLNw>kixSP!_gC0COo*u>lI`1>ZQdNs1+_?*y(&KJUNVIF0-9`$3BUcRR?3{FE` z`)!TKA1I*18#+zeecej^eT9Or?l9x(gKaWop!M{|?y^ z^j?@@3#aeZh6#@1t2=`i?qv53N@hQ5Z}0fHS~5J{aq&uock~XEwT51Xn~i}Ep4%H` z8v}4^&`YC7DCf7=cgaVKuMXdtC>LDzF?<Ga@D5nA_&}&jF{OycQj{;F1WEKS-0Hi$YcNQ3gspA6v59Hi}#3bd0zANFRy-H%1<;FNjcxvyZ3Lv6ivjgZjtdGsB5BxDBe$HCLi0Y2v}nZ z0m~Z1`&2SsHs3L1x9-IUyB$Pi)1#PykM`9`fD(WnkmJmNXGpe(V=KrJ~fKl>4dp8B1YLO=YL%(2|b1gdN_c z---vk34)#Xq_|k7xJlpIT(3;m9BvZkR4^9uzUgzo*d*_B!hE@?1L{_N+~RRnYsi;F zJ6g?|2m4o}@ zhL)M82hA2gAK0zj7Tm-=V|4BF-41=3?h8NVT?#M}B_*51!c#L&B|8KsNLLPo2@1RtLyRwkYpVb{ErK6F++O$W46+re2T}Mmgsy=4(0OQk{5E>ZO}gPko(05 ze36d(zWGj2gLUx-HjWzI_IN828!JIGa>7Pp@_Ezwx4egA37^>vPhQI{XV>e72XPxI z6LPAV!ByV>Fv&RVQRjlyXcAOD`7&!6_pE1ggm=B`zB zV>Q`$P>$aP0ph9eSW_L(c^>a4BnP_L6?rl9wViC*+Z*pcnRsn$&sPmhqp==FejzRv z!B!dNndaa&!bFTUY17_dvUv-fWF<+xod{$*B{Yy#eMTC(%bKztP0o+&eOxXeZeIT+ z5$s+0j#ZQuSHN+V%f_ZsTrMa>vcJov*N`e@en9#1W|1?=-@#?)g0_qawuCE^3D>fu zCoQ+BmxIZaW^SjAVsdd2pRdsaGl$h9GICF4X7-9)d|(VRWXe>O1947#;3?zY!384; zvgNTiSL53K9iD|;xk7Gtyu~g^1>R#GU1wIlhy6#}ID)7RCDBH<{lJm_^V&bxqt|7? zYsHu=SMRz6Q_VGRXbTeFA@;0x6t_-FDyTV;q|8GeptUY{KcPLq`+=qK61K=AozSMa z@~ZQ>5}S(48;pz3Lc-@;LoZSn8sxEpJlM!7ueSL5&WSe+f$uRT%Bpi(13uN`Z$9Rl zVTzT>KKhQUa`KKGwTl^RNl5|@t>$6v^)FRi6pZ*<+pcmOtMxeMrIw1ITCSfZ5gWrE zl|*LhQkn=h**iJeMcZ3c52g#-J3(XJJUc+j^z=Nr(H|}j!OrHU5@C+7_vvidtU8Xlw?7H9YE z_O12^1Cw&aR9^=rPN%GcyD~ULJkx1bhJK$b4aLv$MovSkZ=H8%Xl=O++)afHwMbPT zBnP9S#-`NWODERnLYk5t-2DZW*I)czIb@go@OVuLPG`MZiEMc40?X_+-8HAP22Q_E zQ`;0D?HtMdojjIfulWWxn@h>@kr2}4Rbked?cXqdHrS*dSTTe9omC;Ps6PX2kxMZm4?t5VJQ%%>>4TY8(z?+%glS^kkj zTkp8=by_RG4LJjwp%1?C8BbV|O>)tDh+7W>WPMjFpSu>iXqd zT^5QM%)|uKb%e7I*f-NBdt;m@wx2tUFHSZj;VoHY^b$juO_XwmzO+d+w(;2{FaPw$ zl`%Co5~JCKTcgiu)SY`Q%W_uvU`}UK&hKMxJxn4ePV<=Kx5}T^TQcw0NZ03o^R7ULZLfR5oH`|vsSGe5hIqmcg5a1> z4nfk0{PSunrAi*wi{{EF7rvV>+14Mg<;1l1^WSW&KTfGyRH?CBxf!~Cb1jr}HluOm zBK1JGzKm2_?OU9&_{iD7E_R-JpEg<6lJEPEX4s2fU9-tLU%6eLGfw%vC9|SMrBnpl zNM_`%1i{pka*u6ETkArVU(-ohv2R+mbLG=e`KBQIdBu--fNtm4OUbgIz+q zIR)zPk6q@K-I?RWX3}8m74F9+r%}g3&RGh%HJfzV#^~BWY$m>$&g}4Tb>gM3+r~3i z)`zD;!z}WD7_XFjY0AFv9G^E~Z(9DJ!p=XK%{z|c?QLt-ud4cy_}Te6x*^rwrlM7p zBuJYglPX=CEJck?)*puSbgMRHBU@-FIvXaG(+ZZhs*4V_P!09tswK3P45I9K$T;9JY}P)1 z!tbtIItTy3NNe)tx$ySDy%ATrlXu;CoK0ie?LEYS!{vIp;&_z-&uQbBwj*!qE9Dul z?UrTf#cgDKsm{c(Z_KP9G`)-x@_m`jqJu@v6^?^p$ZqeV>jw9-IeUg$+UTr$iQA6H zTUYXa>+f1_r>v6?YQ1zE1!{z9NTimSuRoo_AIaYEd?Ek_wo~p9cJ9AzKiY;{aN(M$ z7_tk{f7EYMO%8Y<+_G2I?RIP+E#W}%Qx4FZBFo1p2*mu`bvWZ!hXoM(J%Gf;i(h!<;<<)1^awOlN#f$;L zf-K$K4hW{?)O6#;&hkM;^GCaAeu1aMf!&&Mf#`*<0DX$CpZUq)g=0?hXE>03c*~1kfhkVI zKL3k@&)z9A!YB@)P+%S#8+d#ilf22Rhs1>`EF?qxdms2q3LjO<`i*?(xd#>oniqzigjwkPVE z(9j(83=E7K-6_Nl9pi2uOU0d3%>Ft9d8%PI$pc*`pdV&;V1aKoAMyBD;Cuh+8!L<( zoWtNvq>;~8BV3ZmA1)b@J^*ZLZfzAN1AtA;&g$2k6ae~0lj*F=`;!yR*EM7$iWU?!R|b(mZat2sL9yOJ0XApu01` zTh>cp=Gft@Z4|Xp4%77+qZZC}yZuiT41!hy+h7qX5$-5t%UT_?0B6e!aJFC+E3$;F z&VE2QvQirip|*_>r(L$Id%sDXmzuf(e!k%KJUeCfoUrv0p(Km990!*MOlmJFmzOMM W8Z<@zm{agNc#-!}eJZ^^KKBpzCv?&P diff --git a/Documentation/BSP.jpg b/Documentation/images/component diagram.jpg similarity index 100% rename from Documentation/BSP.jpg rename to Documentation/images/component diagram.jpg diff --git a/Documentation/images/method call structure.png b/Documentation/images/method call structure.png new file mode 100644 index 0000000000000000000000000000000000000000..23a59a8efe727252779acac679e6442889120a91 GIT binary patch literal 76699 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%0Al52B3iL#J@n=VjHdYx=h3J+M9Y|5Y}#ZA<27q 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 literal 0 HcmV?d00001 From 3b0fb39edaf2e0ac613273688433a10d802753cb Mon Sep 17 00:00:00 2001 From: noah-pe <3001838@stud.hs-mannheim.de> Date: Mon, 3 Jun 2024 15:13:16 +0200 Subject: [PATCH 047/121] added DES and ECC functionality --- src/tpm/android/knox/provider.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tpm/android/knox/provider.rs b/src/tpm/android/knox/provider.rs index a7280bde..bf616aff 100644 --- a/src/tpm/android/knox/provider.rs +++ b/src/tpm/android/knox/provider.rs @@ -110,7 +110,7 @@ impl Provider for KnoxProvider { }; } else if asym_alg.is_none() && sym_alg.is_some() { key_algo = match sym_alg.expect("Already checked") { - BlockCiphers::Des => { String::from("DESede;CBC;PKCS7Padding") } + BlockCiphers::Des => { String::from("DESede;168;CBC;PKCS7Padding") } BlockCiphers::Aes(block, bitslength) => { let mut rv = String::from("AES;"); From 8b67ee60609b1b02eda1d99318b499c06d05f600 Mon Sep 17 00:00:00 2001 From: ErikFribus Date: Mon, 3 Jun 2024 15:46:22 +0200 Subject: [PATCH 048/121] Updated Doc.md: Rewrote Introduction --- Documentation/Doc.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Documentation/Doc.md b/Documentation/Doc.md index d78de894..eaa58f14 100644 --- a/Documentation/Doc.md +++ b/Documentation/Doc.md @@ -39,14 +39,15 @@ - [Source Documents](#source-documents) - [Research Documents](#research-documents) -## Introduction -In today's digital era, safeguarding sensitive data on mobile devices is paramount. Our project focuses on enhancing data security by developing a wrapper for the Crypto Abstraction Layer. This wrapper enables access to a Hardware Security Module (HSM) through Samsung Knox Vault. By securely storing encryption keys within the HSM, we ensure robust protection for data stored on Samsung devices. +### Introduction +This project is part of a student development project at [Hochschule Mannheim (HSMA)](https://www.english.hs-mannheim.de/the-university.html). The project goal is provided by j&s-soft GmbH as part of their open-source project [enmeshed](https://github.com/nmshd). ### Problem Description -In today's digital age, the security of data stored on mobile devices is of paramount importance. Sensitive information, whether personal or professional, is frequently stored on smartphones, making them a prime target for cyber threats. Ensuring the confidentiality, integrity, and accessibility of this data requires robust encryption and secure key management solutions. +The enmeshed app from j&s-soft GmbH currently secures cryptographic keys in the software. This leads to security vulnerabilities and points of attack. To prevent this, hardware security modules are to be used on which the cryptographic keys are to be securely stored so that they cannot be retrieved even on compressed devices. +Our team was tasked with implementing a solution to this problem in the sub-project for Samsung's secure element, the Knox Vault. ### Product Description -Our project aims to address this critical need by developing a comprehensive solution for encrypting data stored on mobile devices. Specifically, our task is to write a wrapper for the proposed Crypto Abstraction Layer to access a specific hardware security module (HSM) on Samsung devices using Samsung Knox Vault. This solution ensures that encryption keys are securely stored and managed within the HSM, providing an added layer of security for the encrypted data. +The Repository contains a Wrapper that is used to perform cryptographic operations for mobile applications in a Secure Element (SE) on Android devices. Specifically, this project is focused on Samsung Knox Vault as the SE. The interface to the mobile application is provided in Rust, while the communication with the SE will be done using the Android Keystore system. ## Architecture ### Component Diagram From 6199a05f045206efce4a218aa6f47bf99ba045ca Mon Sep 17 00:00:00 2001 From: ErikFribus Date: Mon, 3 Jun 2024 16:04:11 +0200 Subject: [PATCH 049/121] Updated Doc.md: Updated KeyStore API --- Documentation/Doc.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/Doc.md b/Documentation/Doc.md index 7c3c7618..08103b43 100644 --- a/Documentation/Doc.md +++ b/Documentation/Doc.md @@ -61,7 +61,9 @@ The Repository contains a Wrapper that is used to perform cryptographic operatio ### Libraries - **Robusta** - **JNI** + - **KeyStore API** + The [Android Keystore system](https://developer.android.com/privacy-and-security/keystore) manages the handling of cryptographic keys for us. With the help of other APIs, we can use the keys to encrypt and decrypt data, as well as sign and verify it. ## Installation Guide From 026104554edb5d244623a6a3ac47525223f907bf Mon Sep 17 00:00:00 2001 From: noah-pe <3001838@stud.hs-mannheim.de> Date: Mon, 3 Jun 2024 16:52:49 +0200 Subject: [PATCH 050/121] class path variable --- src/tpm/android/knox/interface.rs | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/tpm/android/knox/interface.rs b/src/tpm/android/knox/interface.rs index 3d7b6696..139ab5f4 100644 --- a/src/tpm/android/knox/interface.rs +++ b/src/tpm/android/knox/interface.rs @@ -1,5 +1,6 @@ use robusta_jni::bridge; + #[bridge] pub mod jni { #[allow(unused_imports)] //the bridge import is marked as unused, but if removed the compiler throws an error @@ -19,6 +20,8 @@ pub mod jni { use crate::SecurityModuleError; + const CLASS_SIGNATURE: &str = "com/example/vulcans_limes/RustDef"; + /// Contains all methods related to Rust - Java communication and the JNI #[derive(Signature, TryIntoJavaValue, IntoJavaValue, TryFromJavaValue)] #[package(com.example.vulcans_1limes)] //the 1 after the "_" is an escape character for the "_" @@ -27,6 +30,7 @@ pub mod jni { pub raw: AutoLocal<'env, 'borrow>, } + /// This Implementation provides the method declarations that are the interface for the JNI. /// The first part contains Java-methods that can be called from Rust. /// The second part contains some utility methods. @@ -63,7 +67,7 @@ pub mod jni { //This calls a method in Java in the Class RustDef, with the method name "callback" //and no arguments environment.call_static_method( - "com/example/vulcans_limes/RustDef", //Class signature + CLASS_SIGNATURE, //Class signature "callback", //method name signature "()V", //parameter types of the method &[], //parameters to be passed to the method @@ -82,7 +86,7 @@ pub mod jni { pub fn create_key(environment: &JNIEnv, key_id: String, key_gen_info: String) -> Result<(), SecurityModuleError> { RustDef::initialize_module(environment)?; let result = environment.call_static_method( - "com/example/vulcans_limes/RustDef", + CLASS_SIGNATURE, "create_key", "(Ljava/lang/String;Ljava/lang/String;)V", &[JValue::from(environment.new_string(key_id).unwrap()), @@ -121,7 +125,7 @@ pub mod jni { pub fn load_key(environment: &JNIEnv, key_id: String) -> Result<(), SecurityModuleError> { RustDef::initialize_module(environment)?; let result = environment.call_static_method( - "com/example/vulcans_limes/RustDef", + CLASS_SIGNATURE, "load_key", "(Ljava/lang/String;)V", &[JValue::from(environment.new_string(key_id).unwrap())], @@ -165,7 +169,7 @@ pub mod jni { pub fn initialize_module(environment: &JNIEnv) -> Result<(), SecurityModuleError> { let result = environment.call_static_method( - "com/example/vulcans_limes/RustDef", + CLASS_SIGNATURE, "initialize_module", "()V", &[], @@ -202,7 +206,7 @@ pub mod jni { /// or an `Error` on failure. pub fn sign_data(environment: &JNIEnv, data: &[u8]) -> Result, SecurityModuleError> { let result = environment.call_static_method( - "com/example/vulcans_limes/RustDef", + CLASS_SIGNATURE, "sign_data", "([B)[B", &[JValue::from(environment.byte_array_from_slice(data).unwrap())], @@ -253,7 +257,7 @@ pub mod jni { /// or an `Error` on failure to determine the validity. pub fn verify_signature(environment: &JNIEnv, data: &[u8], signature: &[u8]) -> Result { let result = environment.call_static_method( - "com/example/vulcans_limes/RustDef", + CLASS_SIGNATURE, "verify_signature", "([B[B)Z", &[JValue::from(environment.byte_array_from_slice(data).unwrap()), @@ -303,7 +307,7 @@ pub mod jni { /// or an `Error` on failure. pub fn encrypt_data(environment: &JNIEnv, data: &[u8]) -> Result, SecurityModuleError> { let result = environment.call_static_method( - "com/example/vulcans_limes/RustDef", + CLASS_SIGNATURE, "encrypt_data", "([B)[B", &[JValue::from(environment.byte_array_from_slice(data).unwrap())], @@ -354,7 +358,7 @@ pub mod jni { /// or an `Error` on failure. pub fn decrypt_data(environment: &JNIEnv, data: &[u8]) -> Result, SecurityModuleError> { let result = environment.call_static_method( - "com/example/vulcans_limes/RustDef", + CLASS_SIGNATURE, "decrypt_data", "([B)[B", &[JValue::from(environment.byte_array_from_slice(data).unwrap())], From c75c1a21d6c4c9675cd15a65decd1a47529fd5f0 Mon Sep 17 00:00:00 2001 From: segelnhoch3 Date: Tue, 4 Jun 2024 08:46:43 +0200 Subject: [PATCH 051/121] added JNI Documentation --- Documentation/Doc.md | 62 ++++++++++++++++-- ...nent diagram.jpg => component_diagram.jpg} | Bin ...tructure.png => method_call_structure.png} | Bin 3 files changed, 57 insertions(+), 5 deletions(-) rename Documentation/images/{component diagram.jpg => component_diagram.jpg} (100%) rename Documentation/images/{method call structure.png => method_call_structure.png} (100%) diff --git a/Documentation/Doc.md b/Documentation/Doc.md index af0482d9..cf43c84a 100644 --- a/Documentation/Doc.md +++ b/Documentation/Doc.md @@ -19,7 +19,7 @@ - [Out of Scope](#out-of-scope) 5. [Implementation](#implementation) - [Code](#code) - - [Connection Documentation](#connection-documentation) + - [Connection Documentation](#JNI-Implementation) - [Javadoc](#javadoc) - [Rustdoc](#rustdoc) 6. [Example Usage with Our Custom App](#example-usage-with-our-custom-app) @@ -51,15 +51,21 @@ Our project aims to address this critical need by developing a comprehensive sol ### Component Diagram -![component diagram](images/component diagram.jpg) +![component diagram](images/component_diagram.jpg) ### Explanation ### Abstraction Layer ### Libraries -- **Robusta** -- **JNI** + +- #### JNI + +The Java Native Interface (JNI) is a foreign-function interface (FFI) that supports +cross-communication between Java and native languages such as C or Rust. We use it to +communicate between the Rust- and Java parts of the wrapper by calling Java methods from +the Rust environment and passing parameters that way. The JNI is provided by Oracle and tied directly into the JDK. +To find out more about how the exact communication works, check the [JNI Implementation](#JNI-Implementation). - **KeyStore API** ## Installation Guide @@ -86,7 +92,53 @@ Our project aims to address this critical need by developing a comprehensive sol ### Code -### Connection Documentation +### JNI-Implementation + +The basic premise of our JNI connection is to have a `JavaVM` passed to the Rust Code. With this reference we are able +to call methods provided by the [JNI crate](https://crates.io/crates/jni). Those allow us to call Java functions and +pass parameters to them and receive return values and Java exceptions. + +In order to aid type conversion, we are currently using Robusta as a wrapper around the JNI, but we are only using +functionality that is provided by the JNI itself, in order to make a future conversion to pure JNI easier. + +From the `JavaVM` that is passed in the `KnoxConfig` struct we are able to obtain a `JNIEnv`. For us the most important +method provided by this is this method: + +``` +call_static_method(&self, + class: T, + name: U, + sig: V, + args: &[JValue]) +-> Result +``` + +This method gets the class definition with the full package name, +the name of the method in the class, +a signature of the parameters used by the method, +and finally the parameters themselves as JValues. + +The class and method name can be determined manually, but the signature should always be automatically generated. To do +this, call the following command on the commandline: + + javap -s -p path/to/the/java/file.class + +with the compiled `.class` file. This will print all method signatures to the command line, including the name and the +parameter signature needed for `sig`. + +The conversion of your parameters to JValues can be done through `JValue::from()` most of the time. + +The method returns a JValue containing the return type of the Java method that needs to be converted back to Rust data +types. If a Java exception is thrown, the method returns an Error. + +Example: + + call_static_method( + "com/example/vulcans_limes/RustDef", + "create_key", + "(Ljava/lang/String;Ljava/lang/String;)V", + &[JValue::from(jnienv.new_string(key_id).unwrap()), + JValue::from(jnienv.new_string(key_gen_info).unwrap())]); ### Javadoc diff --git a/Documentation/images/component diagram.jpg b/Documentation/images/component_diagram.jpg similarity index 100% rename from Documentation/images/component diagram.jpg rename to Documentation/images/component_diagram.jpg diff --git a/Documentation/images/method call structure.png b/Documentation/images/method_call_structure.png similarity index 100% rename from Documentation/images/method call structure.png rename to Documentation/images/method_call_structure.png From 2051a2d8e75d3a6fa3577e8da88b1c6d5899b884 Mon Sep 17 00:00:00 2001 From: ErikFribus Date: Tue, 4 Jun 2024 08:52:27 +0200 Subject: [PATCH 052/121] Updated Doc.md: Updated KeyStore API documentation --- Documentation/Doc.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Documentation/Doc.md b/Documentation/Doc.md index cdd4ff43..7dccd8cc 100644 --- a/Documentation/Doc.md +++ b/Documentation/Doc.md @@ -68,7 +68,14 @@ communicate between the Rust- and Java parts of the wrapper by calling Java meth the Rust environment and passing parameters that way. The JNI is provided by Oracle and tied directly into the JDK. To find out more about how the exact communication works, check the [JNI Implementation](#JNI-Implementation). - **KeyStore API** - The [Android Keystore system](https://developer.android.com/privacy-and-security/keystore) manages the handling of cryptographic keys for us. With the help of other APIs, we can use the keys to encrypt and decrypt data, as well as sign and verify it. + The [Android Keystore system](https://developer.android.com/privacy-and-security/keystore) manages the handling of cryptographic keys for us. We chose this over the Knox SDK, as it directly suits our needs, and even Samsung recommends it in their [Knox Vault Whitepaper](https://image-us.samsung.com/SamsungUS/samsungbusiness/solutions/topics/iot/071421/Knox-Whitepaper-v1.5-20210709.pdf). As well as after more research, it also seemed to be the most fitting way to achieve the project goal in the limited time we had. + + With the help of the Keystore and other APIs, we can use the keys to encrypt and decrypt data, as well as sign and verify it. The API also helps us solve the problem from j&s-soft, as we can enforce generated cryptographic keys to be saved in the Knox Vault (or any other Strongbox). + + The Knox Vault doesn't support all the cryptographic algorithms enabled by the Keystore and other API's, and as we found no precise documentation about what Knox Vault supports, we had to test by trial and error. To view all the algorithms that have successfully passed our tests, take a look at [Supported Algorithms](#supported-algorithms) + + More information about the KeyStore API and other API's that are normally used with it can be found in the following repository: [Android-Security-Reference](https://github.com/doridori/Android-Security-Reference/blob/master/framework/keystore.md). + It also contains good general information about security on Android. ## Installation Guide From 6644bb33e6d225d6d23a201bbfa4e1800046efdb Mon Sep 17 00:00:00 2001 From: ErikFribus Date: Tue, 4 Jun 2024 08:55:21 +0200 Subject: [PATCH 053/121] QuickFix: KeyStore API documentation format --- Documentation/Doc.md | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/Doc.md b/Documentation/Doc.md index 7dccd8cc..95b12109 100644 --- a/Documentation/Doc.md +++ b/Documentation/Doc.md @@ -68,6 +68,7 @@ communicate between the Rust- and Java parts of the wrapper by calling Java meth the Rust environment and passing parameters that way. The JNI is provided by Oracle and tied directly into the JDK. To find out more about how the exact communication works, check the [JNI Implementation](#JNI-Implementation). - **KeyStore API** + The [Android Keystore system](https://developer.android.com/privacy-and-security/keystore) manages the handling of cryptographic keys for us. We chose this over the Knox SDK, as it directly suits our needs, and even Samsung recommends it in their [Knox Vault Whitepaper](https://image-us.samsung.com/SamsungUS/samsungbusiness/solutions/topics/iot/071421/Knox-Whitepaper-v1.5-20210709.pdf). As well as after more research, it also seemed to be the most fitting way to achieve the project goal in the limited time we had. With the help of the Keystore and other APIs, we can use the keys to encrypt and decrypt data, as well as sign and verify it. The API also helps us solve the problem from j&s-soft, as we can enforce generated cryptographic keys to be saved in the Knox Vault (or any other Strongbox). From caf8d42775872c8c6583ef1b2f2d7775fd386fc4 Mon Sep 17 00:00:00 2001 From: ErikFribus Date: Tue, 4 Jun 2024 09:00:03 +0200 Subject: [PATCH 054/121] QuickFix: KeyStore API documentation fixed format and spelling --- Documentation/Doc.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Documentation/Doc.md b/Documentation/Doc.md index 95b12109..f8a79058 100644 --- a/Documentation/Doc.md +++ b/Documentation/Doc.md @@ -69,14 +69,13 @@ the Rust environment and passing parameters that way. The JNI is provided by Ora To find out more about how the exact communication works, check the [JNI Implementation](#JNI-Implementation). - **KeyStore API** - The [Android Keystore system](https://developer.android.com/privacy-and-security/keystore) manages the handling of cryptographic keys for us. We chose this over the Knox SDK, as it directly suits our needs, and even Samsung recommends it in their [Knox Vault Whitepaper](https://image-us.samsung.com/SamsungUS/samsungbusiness/solutions/topics/iot/071421/Knox-Whitepaper-v1.5-20210709.pdf). As well as after more research, it also seemed to be the most fitting way to achieve the project goal in the limited time we had. +The [Android Keystore system](https://developer.android.com/privacy-and-security/keystore) handles the cryptographic keys for us. We went with this over the Knox SDK because it's a better fit for our needs, and even Samsung recommends it in their [Knox Vault Whitepaper](https://image-us.samsung.com/SamsungUS/samsungbusiness/solutions/topics/iot/071421/Knox-Whitepaper-v1.5-20210709.pdf). After more research, it also seemed like the best way to achieve the project goal in the limited time we had. - With the help of the Keystore and other APIs, we can use the keys to encrypt and decrypt data, as well as sign and verify it. The API also helps us solve the problem from j&s-soft, as we can enforce generated cryptographic keys to be saved in the Knox Vault (or any other Strongbox). +With the Keystore and other APIs, we can use the keys to encrypt and decrypt data, as well as sign and verify it. The API also helps us solve the problem from j&s-soft, as we can enforce generated cryptographic keys to be saved in the Knox Vault (or any other strongbox). - The Knox Vault doesn't support all the cryptographic algorithms enabled by the Keystore and other API's, and as we found no precise documentation about what Knox Vault supports, we had to test by trial and error. To view all the algorithms that have successfully passed our tests, take a look at [Supported Algorithms](#supported-algorithms) +The Knox Vault doesn't support all the cryptographic algorithms enabled by the Keystore and other APIs. As we couldn't find any detailed documentation about what the Knox Vault supports, we had to test it out by trial and error. You can see all the algorithms that have passed our tests in the [Supported Algorithms](#supported-algorithms) section. - More information about the KeyStore API and other API's that are normally used with it can be found in the following repository: [Android-Security-Reference](https://github.com/doridori/Android-Security-Reference/blob/master/framework/keystore.md). - It also contains good general information about security on Android. +You can find out more about the KeyStore API and other APIs that are normally used with it in the following repository: [Android-Security-Reference](https://github.com/doridori/Android-Security-Reference/blob/master/framework/keystore.md). It also has some useful general info about security on Android. ## Installation Guide From 54e604de71e18b86725af377c1a4affd3f20fa93 Mon Sep 17 00:00:00 2001 From: segelnhoch3 Date: Tue, 4 Jun 2024 12:58:52 +0200 Subject: [PATCH 055/121] added installation guide --- Documentation/Doc.md | 80 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 77 insertions(+), 3 deletions(-) diff --git a/Documentation/Doc.md b/Documentation/Doc.md index cdd4ff43..62278724 100644 --- a/Documentation/Doc.md +++ b/Documentation/Doc.md @@ -44,7 +44,8 @@ This project is part of a student development project at [Hochschule Mannheim (H ### Problem Description The enmeshed app from j&s-soft GmbH currently secures cryptographic keys in the software. This leads to security vulnerabilities and points of attack. To prevent this, hardware security modules are to be used on which the cryptographic keys are to be securely stored so that they cannot be retrieved even on compressed devices. -Our team was tasked with implementing a solution to this problem in the sub-project for Samsung's secure element, the Knox Vault. +Our team was tasked with implementing a solution to this problem in the subproject for Samsung's secure element, the +Knox Vault. ### Product Description The Repository contains a Wrapper that is used to perform cryptographic operations for mobile applications in a Secure Element (SE) on Android devices. Specifically, this project is focused on Samsung Knox Vault as the SE. The interface to the mobile application is provided in Rust, while the communication with the SE will be done using the Android Keystore system. @@ -73,8 +74,81 @@ To find out more about how the exact communication works, check the [JNI Impleme ## Installation Guide ### Required Software -- **Android Studio** -- Additional tools and dependencies + +If you want to build your this project on your own from our source code, these tools are necessary: + +- Android Studio is an IDE specifically for Android app development. While not strictly necessary, having it will make + all further steps easier. This guide will assume that you are using it. +- If you plan on modifying the Rust code, it might be smart to also have RustRover installed, since there is no Rust + plugin for Android Studio. +- RustUp is the easiest way to install Rust and Cargo. Installation is easiest from within RustRover: After starting to + create a new project, there will be a button below the toolchain location selection. Pressing it will install RustUp. + Alternatively you can also install RustUp directly from [their page](https://rustup.rs/). +- You will need the Android NDK (Native Development Kit) to build and run the project. The NDK can be installed from + within Android Studio: Go to Settings > Languages & Frameworks > Android SDK > SDK Tools and select and install the + NDK. +- Cargo and [cargo-ndk](https://docs.rs/crate/cargo-ndk/3.5.4) are necessary to compile the Rust code in a format usable + by Android. Cargo should already be installed by RustUp, cargo-ndk can be installed with `cargo install cargo-ndk`. +- You will need to configure cargo further by using this + command: `rustup target add aarch64-linux-android armv7-linux-androideabi i686-linux-android` (The "linux" in this + command refers to the phone architecture, not your development machine. You can use this command on Windows as well) + This will make cargo compile your Rust code in a format suitable for Android architectures + +### Installing Android Studio + RustRover + +The easiest way to install both IDEs is to use the [JetBrains Toolbox](https://www.jetbrains.com/de-de/toolbox-app/), +where you can install both IDEs with a single click each. +Alternatively you can also refer to +the [Android CodeLab guide](https://developer.android.com/codelabs/basic-android-kotlin-compose-install-android-studio#0), +and the [RustRover download page](https://www.jetbrains.com/rust/nextversion/). +After installing RustRover, you will need to install RustUp in order to compile Rust code. + +### First Test in Android Studio + +The goal of this section is to test Android Studio and run a "Hello World"-App on a real device to confirm that the +connection between the computer and phone is working. We will use a USB connection to get the app onto the device, +although Wi-Fi should also be possible. This step is not strictly necessary, but eliminates a possible source of error +later on. + +- make sure that USB debugging is enabled on the phone. Refer + to [this guide](https://developer.android.com/studio/debug/dev-options) if necessary +- install the proper driver for your smartphone on your PC / Laptop according + to [this list](https://developer.android.com/studio/run/oem-usb#Drivers) +- Open a new project in Android Studio and select Phone/Tablet on the left and use "Empty Activity" as the template +- plug your phone into your PC / Laptop with a USB cable capable of transmitting data +- you should be able to see your phone's storage in your file explorer +- in Android Studio, go to "Running Devices" (on the top right by default), click on the plus sign and select your phone +- the screen of your phone will be mirrored inside Android Studio +- build and run your application and open it on the phone + +### Building and running the project + +To prepare your project, you will need to create a new Android Studio project (or use an existing one). The two +files `CryptoManager.java` and `RustDef.java` +in [`src/tpm/android/knox/java`](https://github.com/cep-sose2024/rust-crypto-knox/tree/main/src/tpm/android/knox/java) +will need to be moved to the other Java files in your project. By default, this is something +like `src/main/java/com/example/example_project`. You will also need to adjust the `const CLASS_SIGNATURE` +in [`src/tpm/android/knox/interface.rs`](https://github.com/cep-sose2024/rust-crypto-knox/blob/main/src/tpm/android/knox/interface.rs) +to match the package that you put `RustDef.java` into. For the example above, that would +be `const CLASS_SIGNATURE: &str = "com/example/example_project/RustDef";` + +Now you are ready to compile everything for the first time. The following command will compile all your Rust code into a +dynamic library (`.so`) that can be used by your android app. + + cargo ndk -t armeabi-v7a -t arm64-v8a -o app/src/main/jniLibs build + +After completing that step, check the directory specified in the command (`app/src/main/jniLibs` in this example). You +should find two files `libexampleproject.so` in a subdirectory. If so, then the last step was successful. Now you will +need to adjust which library Java is looking for to your Project name. This can be found in `RustDef.java` in line 57. +Set the string there to the same as the name of the `.so` files without the "lib" in the +beginning ( `System.loadLibrary("exampleproject");` for this example). + +Afterward, you can compile your Java code. You will need to specify the location that the compiled Rust library is in. +The easiest way to do that is by specifying the library path when building like this: + + .\gradlew -D java.library.path=app/src/main/jniLibs installDebug + +With that, you should have everything complete and compiled the project from scratch. ## Implementations From 6ac02775e5b333f55825f23f3ec08178dbaaf7e5 Mon Sep 17 00:00:00 2001 From: noah-pe <164938052+noah-pe@users.noreply.github.com> Date: Tue, 4 Jun 2024 14:41:10 +0200 Subject: [PATCH 056/121] Update mod.rs trying to solve merge conflict --- src/tests/common/traits/mod.rs | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/tests/common/traits/mod.rs b/src/tests/common/traits/mod.rs index c5d207e0..389d61f1 100644 --- a/src/tests/common/traits/mod.rs +++ b/src/tests/common/traits/mod.rs @@ -2,12 +2,16 @@ use tracing::Level; use tracing_appender::rolling; use tracing_subscriber::FmtSubscriber; +#[cfg(feature = "tpm")] +use crate::tpm::core::instance::TpmType; +#[cfg(feature = "tpm")] +use crate::tpm::linux::TpmProvider; use crate::{ common::{ factory::SecurityModule, traits::{log_config::LogConfig, module_provider::Provider}, }, - tpm::core::instance::TpmType, + // tpm::core::instance::TpmType, SecModules, }; use std::sync::{Arc, Mutex}; @@ -62,9 +66,18 @@ pub fn setup_security_module(module: SecurityModule) -> Arc> "test_key".to_owned(), SecurityModule::Tpm(TpmType::Android(t)), Some(log), - ).unwrap(), + ) + .unwrap(), + TpmType::None => unimplemented!(), _ => unimplemented!(), }, - // _ => unimplemented!(), + #[cfg(feature = "nks")] + SecurityModule::Nks => SecModules::get_instance( + "test_key".to_owned(), + SecurityModule::Nks, + Some(log), + ) + .unwrap(), + _ => unimplemented!(), // Add this line to handle all other cases } } From ea785519dd28c47ff3fe65c318284489a7769034 Mon Sep 17 00:00:00 2001 From: noah-pe <164938052+noah-pe@users.noreply.github.com> Date: Tue, 4 Jun 2024 14:44:04 +0200 Subject: [PATCH 057/121] Update mod.rs again solving merge conflict --- src/tests/common/traits/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tests/common/traits/mod.rs b/src/tests/common/traits/mod.rs index 389d61f1..249ffa25 100644 --- a/src/tests/common/traits/mod.rs +++ b/src/tests/common/traits/mod.rs @@ -77,7 +77,7 @@ pub fn setup_security_module(module: SecurityModule) -> Arc> SecurityModule::Nks, Some(log), ) - .unwrap(), + .unwrap(), _ => unimplemented!(), // Add this line to handle all other cases } } From 825c2e7f22ceee3a757a4159bb79d9a3c87821ee Mon Sep 17 00:00:00 2001 From: ErikFribus Date: Tue, 4 Jun 2024 15:03:46 +0200 Subject: [PATCH 058/121] Updated CryptoManager.java: Updated JavaDoc, removed To-Do's --- src/tpm/android/knox/java/CryptoManager.java | 145 +++++++------------ 1 file changed, 54 insertions(+), 91 deletions(-) diff --git a/src/tpm/android/knox/java/CryptoManager.java b/src/tpm/android/knox/java/CryptoManager.java index 157ccd94..dece854b 100644 --- a/src/tpm/android/knox/java/CryptoManager.java +++ b/src/tpm/android/knox/java/CryptoManager.java @@ -1,4 +1,4 @@ -package com.example.vulcans_limes; +package tpm.android.knox.java; import android.os.Build; import android.security.keystore.KeyGenParameterSpec; @@ -18,6 +18,7 @@ import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.PrivateKey; +import java.security.PublicKey; import java.security.Signature; import java.security.SignatureException; import java.security.UnrecoverableKeyException; @@ -37,7 +38,6 @@ import javax.crypto.spec.IvParameterSpec; public class CryptoManager { - // TODO: READ AND APPROVE JAVADOC private static final String ANDROID_KEY_STORE = "AndroidKeyStore"; private final KeyStore keyStore; private String KEY_NAME; @@ -62,8 +62,7 @@ public CryptoManager() throws KeyStoreException { * Generates a new symmetric key and saves it into the Android KeyStore. *

* This method initializes a new symmetric key for encryption and decryption purposes using the specified symmetric key algorithm. - * The key is stored in the Android KeyStore and supports various configurations including the choice of encryption algorithms, - * key sizes, block modes, and padding schemes. + * The key is stored in the Android KeyStore. * Additionally, this method ensures that the key is backed by the strong box feature. * * @param key_id The unique identifier under which the key will be stored in the KeyStore. @@ -107,14 +106,14 @@ public void genKey(String key_id, String keyGenInfo) throws CertificateException * Encrypts the given data using a symmetric key stored in the Android KeyStore. *

* This method takes plaintext data as input and encrypts it using a symmetric key retrieved from the Android KeyStore. - * The encryption process supports supports GCM, CBC and CTR transformations. A new initialization vector (IV) + * The encryption process supports GCM, CBC and CTR transformations. A new initialization vector (IV) * is generated and the IV is prepended to the ciphertext. The method initializes a * {@link Cipher} instance with the appropriate transformation, loads the Android KeyStore, retrieves the symmetric key, and then * initializes the cipher in encryption mode with the retrieved key and the generated IV. Finally, the plaintext data is encrypted * using the cipher's {@code doFinal} method, and the resulting ciphertext is returned as a byte array. * * @param data The plaintext data to be encrypted, represented as a byte array. - * @return A byte array representing the encrypted data, with the IV prepended in the case of GCM mode. + * @return A byte array representing the encrypted data, with the IV prepended. * @throws NoSuchPaddingException if the requested padding scheme is not available. * @throws NoSuchAlgorithmException if the requested algorithm is not available. * @throws CertificateException if there is an issue loading the certificate chain. @@ -139,9 +138,10 @@ public byte[] encryptData(byte[] data) throws NoSuchPaddingException, NoSuchAlgo Cipher cipher = Cipher.getInstance(TRANSFORMATION); cipher.init(Cipher.ENCRYPT_MODE, secretKey); byte[] iv = cipher.getIV(); - if (TRANSFORMATION.contains("/GCM/")) { assert iv.length == 12; // GCM standard IV size is 12 Byte + } else if(TRANSFORMATION.contains("DESede")) { + assert iv.length == 8; // DES standard IV size is 8 Byte } else { assert iv.length == 16; // CBC & CTR standard IV size is 16 Byte } @@ -195,7 +195,8 @@ public byte[] decryptData(byte[] encryptedData) throws NoSuchPaddingException, N GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(128, iv); // 128 is the recommended TagSize cipher.init(Cipher.DECRYPT_MODE, secretKey, gcmParameterSpec); } else { - iv = new byte[16]; // CBC & CTR standard IV size + if(TRANSFORMATION.contains("DESede")) iv = new byte[8]; // DES standard IV size + else iv = new byte[16]; // CBC & CTR standard IV size byteBuffer.get(iv); encryptedData = new byte[byteBuffer.remaining()]; byteBuffer.get(encryptedData); @@ -213,10 +214,9 @@ public byte[] decryptData(byte[] encryptedData) throws NoSuchPaddingException, N * the signature padding scheme, and whether the key is backed by the strong box feature for enhanced security. * The generated key pair consists of a private key for signing and a corresponding public key for verification. * The supported algorithms are RSA and EC. The keyGenInfo String should have the following form: - * For RSA: RSA;key size;hash;padding - * For EC: EC;curve;hash * * @param key_id The unique identifier under which the key pair will be stored in the KeyStore. + * @param keyGenInfo A string containing key generation parameters separated by semicolons. Expected formats: RSA: "KEY_ALGORITHM;KEY_SIZE;HASH;PADDING", EC: "KEY_ALGORITHM;CURVE;HASH". * @throws CertificateException if there is an issue creating the certificate for the key pair. * @throws IOException for I/O errors such as incorrect passwords. * @throws NoSuchAlgorithmException if the generation algorithm does not exist or the keystore doesn't exist. @@ -267,19 +267,25 @@ public void generateKeyPair(String key_id, String keyGenInfo) throws Certificate /** * Signs the given data using a private key stored in the Android KeyStore. *

- * This method takes plaintext data as input and signs it using a private key retrieved from the Android KeyStore. - * The signing process uses a predefined signature algorithm. The method initializes a {@link Signature} instance with this algorithm, - * loads the Android KeyStore, retrieves the private key, and then initializes the signature object in sign mode with the retrieved private key. - * The plaintext data is then updated into the signature object, and finally, the data is signed using the signature object's {@code sign} method. - * The resulting signature is returned as a byte array. + * This method signs the input data with a private key obtained from the Android KeyStore. + * It initializes a {@link Signature} instance with the appropriate algorithm, which is determined dynamically based on the private key's specifications. + * The Android KeyStore is loaded, and the private key associated with a predefined alias is retrieved. + * The signature object is then initialized in sign mode with this private key. + * After updating the signature object with the plaintext data, + * the method completes the signing process by invoking the signature object's {@code sign} method. + * Finally, the method returns the resulting signature as a byte array, which can be used for verification purposes. * - * @param data The plaintext data to be signed, represented as a byte array. - * @return A byte array representing the signature of the data. - * @throws NoSuchAlgorithmException if the requested algorithm is not available. - * @throws UnrecoverableKeyException if the key cannot be recovered from the keystore. - * @throws KeyStoreException if there is an error accessing the keystore. - * @throws InvalidKeyException if the key cannot be cast to a PrivateKey. - * @throws SignatureException if the signature cannot be processed. + * @param data The data to be signed, provided as a byte array. + * @return A byte array representing the digital signature of the input data. + * @throws NoSuchAlgorithmException if the requested signature algorithm is not supported. + * @throws UnrecoverableKeyException if the private key cannot be retrieved from the keystore. + * @throws KeyStoreException if there is an issue accessing the keystore. + * @throws InvalidKeyException if the key cannot be cast to a {@link PrivateKey}. + * @throws SignatureException if there is an error during the signing process. + * @throws InvalidKeySpecException if the key specification is invalid. + * @throws NoSuchProviderException if the requested security provider is not available. + * @throws CertificateException if there is an error processing certificates. + * @throws IOException if there is an I/O error while interacting with the keystore. */ public byte[] signData(byte[] data) throws NoSuchAlgorithmException, UnrecoverableKeyException, KeyStoreException, InvalidKeyException, SignatureException, InvalidKeySpecException, NoSuchProviderException, CertificateException, IOException { @@ -291,26 +297,27 @@ public byte[] signData(byte[] data) throws NoSuchAlgorithmException, Unrecoverab } /** - * Verifies the signature of the given data using a public key stored in the Android KeyStore. + * Verifies the given data against a signature produced by a private key stored in the Android KeyStore. *

- * This method verifies the signature of the given data against a known signature. The verification process - * uses a predefined signature algorithm. The method initializes a {@link Signature} instance with this algorithm, - * loads the Android KeyStore, retrieves the public key associated with the known signature, and then initializes - * the signature object in verify mode with the retrieved public key. The plaintext data is then updated into the - * signature object, and finally, the signature is verified using the signature object's {@code verify} method with - * the provided signed bytes. The method returns true if the signature is valid, indicating that the data has not - * been tampered with and was indeed signed by the holder of the corresponding private key; otherwise, it returns false. + * This method compares the provided data with the given signature, using the corresponding public key extracted from the Android KeyStore. + * It initializes a {@link Signature} instance with the appropriate algorithm, determined dynamically based on the public key's specifications. + * The method then loads the Android KeyStore, retrieves the public key associated with a predefined alias, + * and initializes the signature object in verify mode with this public key. + * After updating the signature object with the plaintext data, the method verifies the signature against the provided signed bytes + * and returns the result of this verification. * - * @param data The plaintext data whose signature is to be verified, represented as a byte array. - * @param signedBytes The signature of the data to be verified, represented as a byte array. - * @return True if the signature is valid, false otherwise. - * @throws SignatureException if the signature cannot be processed. - * @throws InvalidKeyException if the key cannot be cast to a PublicKey. - * @throws KeyStoreException if there is an error accessing the keystore. - * @throws NoSuchAlgorithmException if the requested algorithm is not available. - * @throws UnrecoverableKeyException if the key cannot be recovered from the keystore. - * @throws InvalidKeySpecException if the key specification is invalid or cannot be retrieved. - * @throws NoSuchProviderException if the provider is not available. + * @param data The data that was originally signed, provided as a byte array. + * @param signedBytes The signature produced by signing the original data, provided as a byte array. + * @return {@code true} if the signature matches the data; {@code false} otherwise. + * @throws SignatureException if there is an error during the verification process. + * @throws InvalidKeyException if the key cannot be cast to a {@link PrivateKey}. + * @throws KeyStoreException if there is an issue accessing the keystore. + * @throws NoSuchAlgorithmException if the requested signature algorithm is not supported. + * @throws UnrecoverableKeyException if the public key cannot be retrieved from the keystore. + * @throws InvalidKeySpecException if the key specification is invalid. + * @throws NoSuchProviderException if the requested security provider is not available. + * @throws CertificateException if there is an error processing certificates. + * @throws IOException if there is an I/O error while interacting with the keystore. */ public boolean verifySignature(byte[] data, byte[] signedBytes) throws SignatureException, InvalidKeyException, KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException, InvalidKeySpecException, NoSuchProviderException, CertificateException, IOException { @@ -330,6 +337,11 @@ public boolean verifySignature(byte[] data, byte[] signedBytes) throws Signature * involving the specified key. * * @param key_id The unique identifier of a key to be set as `KEY_NAME`. + * @throws KeyStoreException if there is an issue accessing the keystore. + * @throws UnrecoverableKeyException if the key cannot be retrieved from the keystore. + * @throws CertificateException if there is an error processing certificates. + * @throws IOException if there is an I/O error while interacting with the keystore. + * @throws NoSuchAlgorithmException if the requested algorithm is not available. */ public void loadKey(String key_id) throws KeyStoreException, UnrecoverableKeyException, CertificateException, IOException, NoSuchAlgorithmException { keyStore.load(null); @@ -341,7 +353,8 @@ public void loadKey(String key_id) throws KeyStoreException, UnrecoverableKeyExc /** * Constructs the transformation string for a given key, which is used to initialize a {@link Cipher} instance. *

- * This method loads the Android KeyStore and retrieves key-specific metadata using {@link KeyInfo}. From those it builds a transformation string based on the key's algorithm, block modes, and padding schemes. The transformation + * This method loads the Android KeyStore and retrieves key-specific metadata using {@link KeyInfo}. + * From those it builds a transformation string based on the key's algorithm, block modes, and padding schemes. The transformation * string follows the format "algorithm/block-mode/padding". It supports both symmetric keys ({@link SecretKey}) and asymmetric keys * ({@link PrivateKey}). For symmetric keys, it retrieves encryption paddings; for asymmetric keys, it retrieves signature paddings. * @@ -406,54 +419,4 @@ private String buildSignatureAlgorithm(PrivateKey privateKey) throws NoSuchAlgor return hashAlgorithm + "with" + algorithm; } - /** - * Prints detailed information about the currently loaded key. - *

- * This method retrieves and displays information about a key stored in the Android KeyStore. - * It handles both {@link SecretKey} (symmetric keys) and {@link KeyPair} (asymmetric keys). - * The information displayed includes the key ID, block modes, security level, origin, and purpose - * for {@link SecretKey}. For {@link KeyPair}, it displays the key algorithm and format. - *

- * Note that this method should be used for testing or demonstration purposes and will be removed - * in the release version. - * - * @throws NullPointerException if the specified key does not exist. - * @throws CertificateException if there is an issue loading the certificate chain. - * @throws IOException if there is an I/O error during the operation. - * @throws NoSuchAlgorithmException if the requested algorithm is not available. - * @throws InvalidKeySpecException if the key specification is invalid. - * @throws NoSuchProviderException if the specified provider is not available. - * @throws UnrecoverableKeyException if the key cannot be recovered from the keystore. - * @throws KeyStoreException if there is an error accessing the keystore. - */ // TODO: DELETE BEFORE FINAL RELEASE - public void showKeyInfo() throws NullPointerException, CertificateException, IOException, - NoSuchAlgorithmException, InvalidKeySpecException, NoSuchProviderException, UnrecoverableKeyException, - KeyStoreException { - keyStore.load(null); - Key key = keyStore.getKey(KEY_NAME, null); - KeyInfo keyInfo; - - if (key instanceof SecretKey) { - SecretKey secretKey = (SecretKey) key; - SecretKeyFactory factory = SecretKeyFactory.getInstance(secretKey.getAlgorithm(), ANDROID_KEY_STORE); - keyInfo = (KeyInfo) factory.getKeySpec(secretKey, KeyInfo.class); - System.out.println("Key algorithm: " + secretKey.getAlgorithm()); - } else if (key instanceof PrivateKey) { - KeyPair keyPair = new KeyPair(keyStore.getCertificate(KEY_NAME).getPublicKey(), (PrivateKey) key); - KeyFactory factory = KeyFactory.getInstance(keyPair.getPrivate().getAlgorithm(), ANDROID_KEY_STORE); - keyInfo = factory.getKeySpec(keyPair.getPrivate(), KeyInfo.class); - System.out.println("Key algorithm: " + keyPair.getPrivate().getAlgorithm()); - } else { - throw new KeyStoreException("Unsupported key type"); - } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - System.out.println("KeyID: " + keyInfo.getKeystoreAlias() + - "\nKey padding: " + Arrays.toString(keyInfo.getEncryptionPaddings()) + - "\nKey size: " + keyInfo.getKeySize() + - "\nBlock modes: " + Arrays.toString(keyInfo.getBlockModes()) + - "\nSecurity-Level: " + keyInfo.getSecurityLevel() + - "\nOrigin: " + keyInfo.getOrigin() + - "\nPurpose: " + keyInfo.getPurposes()); - } - } } \ No newline at end of file From 0f02d874fade38129308a9fe2f1f4556b6681edf Mon Sep 17 00:00:00 2001 From: ErikFribus Date: Tue, 4 Jun 2024 15:34:15 +0200 Subject: [PATCH 059/121] Updated RustDef.java: Updated JavaDoc --- src/tpm/android/knox/java/RustDef.java | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/src/tpm/android/knox/java/RustDef.java b/src/tpm/android/knox/java/RustDef.java index fddc3a03..abfcb2d5 100644 --- a/src/tpm/android/knox/java/RustDef.java +++ b/src/tpm/android/knox/java/RustDef.java @@ -102,7 +102,8 @@ static void callback() { *

* This method generates a new cryptographic key within the TPM. The key is made persistent * and associated with the provided {@code key_id}, which uniquely identifies the key - * so that it can be retrieved later for cryptographic operations. + * so that it can be retrieved later for cryptographic operations, and {@code keyGenInfo}, + * which holds the information about the key to be generated. * * @param key_id a String that uniquely identifies the key to be created within the TPM. * @param keyGenInfo additional information required for key generation, specifying parameters such as @@ -120,7 +121,8 @@ static void callback() { */ static void create_key(String key_id, String keyGenInfo) throws InvalidAlgorithmParameterException, CertificateException, IOException, NoSuchAlgorithmException, KeyStoreException, NoSuchProviderException { - if (keyGenInfo.contains("RSA")) cryptoManager.generateKeyPair(key_id, keyGenInfo); + if (keyGenInfo.contains("RSA") || keyGenInfo.contains("EC")) + cryptoManager.generateKeyPair(key_id, keyGenInfo); else cryptoManager.genKey(key_id, keyGenInfo); } @@ -136,8 +138,11 @@ static void create_key(String key_id, String keyGenInfo) throws InvalidAlgorithm * incorrect or inaccessible key information. * @throws KeyStoreException if there is an error accessing the keystore, such as a failure to load * or initialize the keystore. + * @throws CertificateException if there is an error processing certificates. + * @throws IOException if there is an I/O error while interacting with the keystore. + * @throws NoSuchAlgorithmException if the requested algorithm is not available. */ - static void load_key(String key_id) throws UnrecoverableKeyException, KeyStoreException { + static void load_key(String key_id) throws UnrecoverableKeyException, KeyStoreException, CertificateException, IOException, NoSuchAlgorithmException { cryptoManager.loadKey(key_id); } @@ -172,9 +177,13 @@ static void initialize_module() throws KeyStoreException { * @throws InvalidKeyException if the key used for signing is invalid. * @throws InvalidKeySpecException if the key specification is invalid. * @throws NoSuchProviderException if the provider is not available. + * @throws CertificateException if there is an error processing certificates. + * @throws IOException if there is an I/O error while interacting with the keystore. + * @throws CertificateException if there is an error processing certificates. + * @throws IOException if there is an I/O error while interacting with the keystore. */ static byte[] sign_data(byte[] data) throws UnrecoverableKeyException, NoSuchAlgorithmException, - KeyStoreException, SignatureException, InvalidKeyException, InvalidKeySpecException, NoSuchProviderException { + KeyStoreException, SignatureException, InvalidKeyException, InvalidKeySpecException, NoSuchProviderException, CertificateException, IOException { return cryptoManager.signData(data); } @@ -195,10 +204,12 @@ static byte[] sign_data(byte[] data) throws UnrecoverableKeyException, NoSuchAlg * @throws UnrecoverableKeyException if the key cannot be recovered from the keystore. * @throws InvalidKeySpecException if the key specification is invalid. * @throws NoSuchProviderException if the provider is not available. + * @throws CertificateException if there is an error processing certificates. + * @throws IOException if there is an I/O error while interacting with the keystore. */ static boolean verify_signature(byte[] data, byte[] signature) throws SignatureException, KeyStoreException, NoSuchAlgorithmException, InvalidKeyException, UnrecoverableKeyException, InvalidKeySpecException, - NoSuchProviderException { + NoSuchProviderException, CertificateException, IOException { return cryptoManager.verifySignature(data, signature); } @@ -214,7 +225,6 @@ static boolean verify_signature(byte[] data, byte[] signature) throws SignatureE * * @param data a byte array representing the data to be encrypted. * @return a byte array containing the encrypted data. - * @throws InvalidAlgorithmParameterException if the algorithm parameters are invalid for encryption. * @throws UnrecoverableKeyException if the key cannot be recovered from the keystore. * @throws NoSuchPaddingException if the padding scheme is not available. * @throws IllegalBlockSizeException if the block size is invalid for the encryption algorithm. @@ -227,7 +237,7 @@ static boolean verify_signature(byte[] data, byte[] signature) throws SignatureE * @throws InvalidKeyException if the key is invalid for encryption. * @throws NoSuchProviderException if the provider is not available. */ - static byte[] encrypt_data(byte[] data) throws InvalidAlgorithmParameterException, UnrecoverableKeyException, + static byte[] encrypt_data(byte[] data) throws UnrecoverableKeyException, NoSuchPaddingException, IllegalBlockSizeException, CertificateException, NoSuchAlgorithmException, IOException, KeyStoreException, BadPaddingException, InvalidKeySpecException, InvalidKeyException, NoSuchProviderException { From 27c4eb8448f6460db3db06e45d2ff3cdbe8c8850 Mon Sep 17 00:00:00 2001 From: noah-pe <164938052+noah-pe@users.noreply.github.com> Date: Tue, 4 Jun 2024 15:58:12 +0200 Subject: [PATCH 060/121] Update module_provider.rs solving merge conflict --- src/common/traits/module_provider.rs | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/src/common/traits/module_provider.rs b/src/common/traits/module_provider.rs index 0dd73d7e..a2fe6acc 100644 --- a/src/common/traits/module_provider.rs +++ b/src/common/traits/module_provider.rs @@ -1,7 +1,6 @@ -use std::any::Any; use super::key_handle::KeyHandle; use crate::common::error::SecurityModuleError; -use std::fmt::Debug; +use std::{any::Any, fmt::Debug}; /// Defines the interface for a security module provider. /// @@ -27,11 +26,10 @@ pub trait Provider: Send + Sync + KeyHandle + Debug { /// /// A `Result` that, on success, contains `Ok(())`, indicating that the key was created successfully. /// On failure, it returns a `SecurityModuleError`. - fn create_key( - &mut self, - key_id: &str, - config: Box, - ) -> Result<(), SecurityModuleError>; + + fn create_key(&mut self, key_id: &str, config: Box) + -> Result<(), SecurityModuleError>; + /// Loads an existing cryptographic key identified by `key_id`. /// @@ -47,11 +45,8 @@ pub trait Provider: Send + Sync + KeyHandle + Debug { /// /// A `Result` that, on success, contains `Ok(())`, indicating that the key was loaded successfully. /// On failure, it returns a `SecurityModuleError`. - fn load_key( - &mut self, - key_id: &str, - config: Box, - ) -> Result<(), SecurityModuleError>; + + fn load_key(&mut self, key_id: &str, config: Box) -> Result<(), SecurityModuleError>; /// Initializes the security module and returns a handle for further operations. /// From f38f0c1934055a7067c5325c80eb93ccfb8a53e9 Mon Sep 17 00:00:00 2001 From: noah-pe <164938052+noah-pe@users.noreply.github.com> Date: Tue, 4 Jun 2024 16:03:20 +0200 Subject: [PATCH 061/121] Updated module_provider.rs trying to solve merge conflict From 4989f3eb36552537e00fe58cbd29fa95312a6892 Mon Sep 17 00:00:00 2001 From: noah-pe <3001838@stud.hs-mannheim.de> Date: Tue, 4 Jun 2024 16:29:09 +0200 Subject: [PATCH 062/121] fixing merge conflict --- Cargo.lock | 31 +- Cargo.toml | 5 +- src/tests/mod.rs | 4 + src/tests/tpm/android/mod.rs | 644 ++++++++++++++++++ src/tests/tpm/mod.rs | 2 + src/tpm/android/android_logger.rs | 14 + src/tpm/android/config.rs | 44 ++ src/tpm/android/error.rs | 62 ++ src/tpm/android/mod.rs | 546 +++++++++++++++ src/tpm/android/utils.rs | 196 ++++++ .../android/wrapper/key_generation/builder.rs | 275 ++++++++ src/tpm/android/wrapper/key_generation/key.rs | 57 ++ .../key_generation/key_gen_parameter_spec.rs | 44 ++ .../wrapper/key_generation/key_generator.rs | 39 ++ .../wrapper/key_generation/key_pair.rs | 30 + .../key_generation/key_pair_generator.rs | 104 +++ src/tpm/android/wrapper/key_generation/mod.rs | 9 + .../wrapper/key_generation/secure_random.rs | 30 + src/tpm/android/wrapper/key_store/cipher.rs | 86 +++ .../android/wrapper/key_store/key_store.rs | 131 ++++ src/tpm/android/wrapper/key_store/mod.rs | 5 + .../android/wrapper/key_store/signature.rs | 133 ++++ src/tpm/android/wrapper/mod.rs | 61 ++ src/tpm/core/error.rs | 10 + src/tpm/core/instance.rs | 7 + 25 files changed, 2567 insertions(+), 2 deletions(-) create mode 100644 src/tests/tpm/android/mod.rs create mode 100644 src/tpm/android/android_logger.rs create mode 100644 src/tpm/android/config.rs create mode 100644 src/tpm/android/error.rs create mode 100644 src/tpm/android/utils.rs create mode 100644 src/tpm/android/wrapper/key_generation/builder.rs create mode 100644 src/tpm/android/wrapper/key_generation/key.rs create mode 100644 src/tpm/android/wrapper/key_generation/key_gen_parameter_spec.rs create mode 100644 src/tpm/android/wrapper/key_generation/key_generator.rs create mode 100644 src/tpm/android/wrapper/key_generation/key_pair.rs create mode 100644 src/tpm/android/wrapper/key_generation/key_pair_generator.rs create mode 100644 src/tpm/android/wrapper/key_generation/mod.rs create mode 100644 src/tpm/android/wrapper/key_generation/secure_random.rs create mode 100644 src/tpm/android/wrapper/key_store/cipher.rs create mode 100644 src/tpm/android/wrapper/key_store/key_store.rs create mode 100644 src/tpm/android/wrapper/key_store/mod.rs create mode 100644 src/tpm/android/wrapper/key_store/signature.rs create mode 100644 src/tpm/android/wrapper/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 74eec2a6..a705d68a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -21,6 +21,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "android_log-sys" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85965b6739a430150bdd138e2374a98af0c3ee0d030b3bb7fc3bddff58d0102e" + [[package]] name = "anyhow" version = "1.0.83" @@ -365,6 +371,7 @@ dependencies = [ "async-std", "futures", "jni 0.20.0", + "libloading", "nitrokey", "once_cell", "robusta_jni", @@ -372,6 +379,7 @@ dependencies = [ "serde_json", "test-case", "tracing", + "tracing-android", "tracing-appender", "tracing-subscriber", "tss-esapi", @@ -929,6 +937,16 @@ version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +[[package]] +name = "libloading" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19" +dependencies = [ + "cfg-if", + "windows-targets 0.52.5", +] + [[package]] name = "libm" version = "0.2.8" @@ -1472,7 +1490,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c080146e0cc733697fe500413871142af91bd879641205c2febbe5f982f304e3" dependencies = [ - "jni 0.19.0", + "jni", "paste", "robusta-codegen", "static_assertions", @@ -1892,6 +1910,17 @@ dependencies = [ "tracing-core", ] +[[package]] +name = "tracing-android" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12612be8f868a09c0ceae7113ff26afe79d81a24473a393cb9120ece162e86c0" +dependencies = [ + "android_log-sys", + "tracing", + "tracing-subscriber", +] + [[package]] name = "tracing-appender" version = "0.2.3" diff --git a/Cargo.toml b/Cargo.toml index 9a4252eb..8f131d6f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ debug = false strip = "symbols" [features] -android = [] +android = ["robusta_jni", "libloading", "tracing-android"] debug = [] hsm = [] ffi = [] @@ -51,6 +51,9 @@ tracing = { version = "0.1.40", features = ["std", "log"] } tracing-subscriber = "0.3.18" tracing-appender = "0.2.3" yubikey = { version = "0.8.0", optional = true } +robusta_jni = { version = "0.2", optional = true } +libloading = { version = "0.8.3", optional = true} +tracing-android = { version = "0.2.0", optional = true } [dev-dependencies] test-case = "*" diff --git a/src/tests/mod.rs b/src/tests/mod.rs index 38e784e3..fc32a423 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -1,3 +1,7 @@ pub mod common; + +#[cfg(feature = "hsm")] pub mod hsm; + +#[cfg(feature = "tpm")] mod tpm; diff --git a/src/tests/tpm/android/mod.rs b/src/tests/tpm/android/mod.rs new file mode 100644 index 00000000..6cb02dd9 --- /dev/null +++ b/src/tests/tpm/android/mod.rs @@ -0,0 +1,644 @@ +use robusta_jni::convert::IntoJavaValue; + +use crate::common::crypto::{algorithms, KeyUsage}; +use crate::common::factory::SecModules; +use crate::common::factory::SecurityModule; +use crate::tpm::android::*; +use crate::tpm::core::instance::AndroidTpmType; +use crate::tpm::core::instance::TpmType; + +#[test] +fn initializ_module_test1() { + assert_eq!(true, true); +} + +#[test] +fn initializ_module_test() { + let security_module = SecModules::get_instance( + "2323".to_string(), + SecurityModule::Tpm(TpmType::Android(AndroidTpmType::Keystore)), + None, + ); + + let x = security_module.unwrap(); + let mut provider = x.lock().unwrap(); + let key_algorithm = + algorithms::encryption::AsymmetricEncryption::Rsa(algorithms::KeyBits::Bits1024); + let hash = algorithms::hashes::Hash::Sha1; + let key_usages = vec![KeyUsage::SignEncrypt]; + provider.initialize_module().unwrap(); +} +/* +#[test] +fn key_creation_test() { + let security_module = SecModules::get_instance( + "2323".to_string(), + SecurityModule::Tpm(TpmType::Android(AndroidTpmType::Keystore)), + ); + + let x = security_module.unwrap(); + let mut provider = x.lock().unwrap(); + let key_algorithm = + algorithms::encryption::AsymmetricEncryption::Rsa(algorithms::KeyBits::Bits1024); + let hash = algorithms::hashes::Hash::Sha1; + let key_usages = vec![KeyUsage::SignEncrypt]; + provider + .initialize_module(key_algorithm, None, Some(hash), key_usages) + .unwrap(); + provider.create_key("2320").unwrap(); +} + +#[test] +fn key_load_test() { + let security_module = SecModules::get_instance( + "2323".to_string(), + SecurityModule::Tpm(TpmType::Android(AndroidTpmType::Keystore)), + ); + + let x = security_module.unwrap(); + let mut provider = x.lock().unwrap(); + let key_algorithm = + algorithms::encryption::AsymmetricEncryption::Rsa(algorithms::KeyBits::Bits1024); + let hash = algorithms::hashes::Hash::Sha1; + let key_usages = vec![KeyUsage::SignEncrypt]; + provider + .initialize_module(key_algorithm, None, Some(hash), key_usages) + .unwrap(); + provider.create_key("2322").unwrap(); + provider.load_key("2322").unwrap(); +} + +/* +----------------TESTING different KeyBits------------------------ +*/ + +#[test] +fn key_creation_bit_128_test() { + let security_module = SecModules::get_instance( + "2323".to_string(), + SecurityModule::Tpm(TpmType::Android(AndroidTpmType::Keystore)), + ); + + let x = security_module.unwrap(); + let mut provider = x.lock().unwrap(); + let key_algorithm = + algorithms::encryption::AsymmetricEncryption::Rsa(algorithms::KeyBits::Bits128); + let hash = algorithms::hashes::Hash::Sha1; + let key_usages = vec![KeyUsage::SignEncrypt]; + provider + .initialize_module(key_algorithm, None, Some(hash), key_usages) + .unwrap(); + provider.create_key("23128").unwrap(); +} + +#[test] +fn key_creation_bit_192_test() { + let security_module = SecModules::get_instance( + "2323".to_string(), + SecurityModule::Tpm(TpmType::Android(AndroidTpmType::Keystore)), + ); + + let x = security_module.unwrap(); + let mut provider = x.lock().unwrap(); + let key_algorithm = + algorithms::encryption::AsymmetricEncryption::Rsa(algorithms::KeyBits::Bits192); + let hash = algorithms::hashes::Hash::Sha1; + let key_usages = vec![KeyUsage::SignEncrypt]; + provider + .initialize_module(key_algorithm, None, Some(hash), key_usages) + .unwrap(); + provider.create_key("23192").unwrap(); +} + +#[test] +fn key_creation_bit_256_test() { + let security_module = SecModules::get_instance( + "2323".to_string(), + SecurityModule::Tpm(TpmType::Android(AndroidTpmType::Keystore)), + ); + + let x = security_module.unwrap(); + let mut provider = x.lock().unwrap(); + let key_algorithm = + algorithms::encryption::AsymmetricEncryption::Rsa(algorithms::KeyBits::Bits256); + let hash = algorithms::hashes::Hash::Sha1; + let key_usages = vec![KeyUsage::SignEncrypt]; + provider + .initialize_module(key_algorithm, None, Some(hash), key_usages) + .unwrap(); + provider.create_key("23256").unwrap(); +} + +#[test] +fn key_creation_bit_512_test() { + let security_module = SecModules::get_instance( + "2323".to_string(), + SecurityModule::Tpm(TpmType::Android(AndroidTpmType::Keystore)), + ); + + let x = security_module.unwrap(); + let mut provider = x.lock().unwrap(); + let key_algorithm = + algorithms::encryption::AsymmetricEncryption::Rsa(algorithms::KeyBits::Bits512); + let hash = algorithms::hashes::Hash::Sha1; + let key_usages = vec![KeyUsage::SignEncrypt]; + provider + .initialize_module(key_algorithm, None, Some(hash), key_usages) + .unwrap(); + provider.create_key("23512").unwrap(); +} + +#[test] +fn key_creation_bit_1024_test() { + let security_module = SecModules::get_instance( + "2323".to_string(), + SecurityModule::Tpm(TpmType::Android(AndroidTpmType::Keystore)), + ); + + let x = security_module.unwrap(); + let mut provider = x.lock().unwrap(); + let key_algorithm = + algorithms::encryption::AsymmetricEncryption::Rsa(algorithms::KeyBits::Bits1024); + let hash = algorithms::hashes::Hash::Sha1; + let key_usages = vec![KeyUsage::SignEncrypt]; + provider + .initialize_module(key_algorithm, None, Some(hash), key_usages) + .unwrap(); + provider.create_key("231024").unwrap(); +} + +#[test] +fn key_creation_bit_2048_test() { + let security_module = SecModules::get_instance( + "2323".to_string(), + SecurityModule::Tpm(TpmType::Android(AndroidTpmType::Keystore)), + ); + + let x = security_module.unwrap(); + let mut provider = x.lock().unwrap(); + let key_algorithm = + algorithms::encryption::AsymmetricEncryption::Rsa(algorithms::KeyBits::Bits2048); + let hash = algorithms::hashes::Hash::Sha1; + let key_usages = vec![KeyUsage::SignEncrypt]; + provider + .initialize_module(key_algorithm, None, Some(hash), key_usages) + .unwrap(); + provider.create_key("232048").unwrap(); +} + +#[test] +fn key_creation_bit_3072_test() { + let security_module = SecModules::get_instance( + "2323".to_string(), + SecurityModule::Tpm(TpmType::Android(AndroidTpmType::Keystore)), + ); + + let x = security_module.unwrap(); + let mut provider = x.lock().unwrap(); + let key_algorithm = + algorithms::encryption::AsymmetricEncryption::Rsa(algorithms::KeyBits::Bits3072); + let hash = algorithms::hashes::Hash::Sha1; + let key_usages = vec![KeyUsage::SignEncrypt]; + provider + .initialize_module(key_algorithm, None, Some(hash), key_usages) + .unwrap(); + provider.create_key("233072").unwrap(); +} + +#[test] +fn key_creation_bit_4096_test() { + let security_module = SecModules::get_instance( + "2323".to_string(), + SecurityModule::Tpm(TpmType::Android(AndroidTpmType::Keystore)), + ); + + let x = security_module.unwrap(); + let mut provider = x.lock().unwrap(); + let key_algorithm = + algorithms::encryption::AsymmetricEncryption::Rsa(algorithms::KeyBits::Bits4096); + let hash = algorithms::hashes::Hash::Sha1; + let key_usages = vec![KeyUsage::SignEncrypt]; + provider + .initialize_module(key_algorithm, None, Some(hash), key_usages) + .unwrap(); + provider.create_key("234096").unwrap(); +} + +#[test] +fn key_creation_bit_8192_test() { + let security_module = SecModules::get_instance( + "2323".to_string(), + SecurityModule::Tpm(TpmType::Android(AndroidTpmType::Keystore)), + ); + + let x = security_module.unwrap(); + let mut provider = x.lock().unwrap(); + let key_algorithm = + algorithms::encryption::AsymmetricEncryption::Rsa(algorithms::KeyBits::Bits8192); + let hash = algorithms::hashes::Hash::Sha1; + let key_usages = vec![KeyUsage::SignEncrypt]; + provider + .initialize_module(key_algorithm, None, Some(hash), key_usages) + .unwrap(); + provider.create_key("238192").unwrap(); +} + +/* + +---------------------TESTING Hashes------------------- + +*/ + +#[test] +fn key_creation_hash_md2_test() { + let security_module = SecModules::get_instance( + "2323".to_string(), + SecurityModule::Tpm(TpmType::Android(AndroidTpmType::Keystore)), + ); + + let x = security_module.unwrap(); + let mut provider = x.lock().unwrap(); + let key_algorithm = + algorithms::encryption::AsymmetricEncryption::Rsa(algorithms::KeyBits::Bits1024); + let hash = algorithms::hashes::Hash::Md2; + let key_usages = vec![KeyUsage::SignEncrypt]; + provider + .initialize_module(key_algorithm, None, Some(hash), key_usages) + .unwrap(); + provider.create_key("231025").unwrap(); +} + +#[test] +fn key_creation_hash_md4_test() { + let security_module = SecModules::get_instance( + "2323".to_string(), + SecurityModule::Tpm(TpmType::Android(AndroidTpmType::Keystore)), + ); + + let x = security_module.unwrap(); + let mut provider = x.lock().unwrap(); + let key_algorithm = + algorithms::encryption::AsymmetricEncryption::Rsa(algorithms::KeyBits::Bits1024); + let hash = algorithms::hashes::Hash::Md4; + let key_usages = vec![KeyUsage::SignEncrypt]; + provider + .initialize_module(key_algorithm, None, Some(hash), key_usages) + .unwrap(); + provider.create_key("231026").unwrap(); +} + +#[test] +fn key_creation_hash_md5_test() { + let security_module = SecModules::get_instance( + "2323".to_string(), + SecurityModule::Tpm(TpmType::Android(AndroidTpmType::Keystore)), + ); + + let x = security_module.unwrap(); + let mut provider = x.lock().unwrap(); + let key_algorithm = + algorithms::encryption::AsymmetricEncryption::Rsa(algorithms::KeyBits::Bits1024); + let hash = algorithms::hashes::Hash::Md5; + let key_usages = vec![KeyUsage::SignEncrypt]; + provider + .initialize_module(key_algorithm, None, Some(hash), key_usages) + .unwrap(); + provider.create_key("231027").unwrap(); +} + +/* + +------------Testing KeyUsages-------------- + +*/ + +#[test] +fn key_creation_hash_ripemd160_test() { + let security_module = SecModules::get_instance( + "2323".to_string(), + SecurityModule::Tpm(TpmType::Android(AndroidTpmType::Keystore)), + ); + + let x = security_module.unwrap(); + let mut provider = x.lock().unwrap(); + let key_algorithm = + algorithms::encryption::AsymmetricEncryption::Rsa(algorithms::KeyBits::Bits1024); + let hash = algorithms::hashes::Hash::Ripemd160; + let key_usages = vec![KeyUsage::SignEncrypt]; + provider + .initialize_module(key_algorithm, None, Some(hash), key_usages) + .unwrap(); + provider.create_key("231028").unwrap(); +} + +#[test] +fn key_creation_keyusage_clientauth_test() { + let security_module = SecModules::get_instance( + "2323".to_string(), + SecurityModule::Tpm(TpmType::Android(AndroidTpmType::Keystore)), + ); + + let x = security_module.unwrap(); + let mut provider = x.lock().unwrap(); + let key_algorithm = + algorithms::encryption::AsymmetricEncryption::Rsa(algorithms::KeyBits::Bits1024); + let hash = algorithms::hashes::Hash::Sha1; + let key_usages = vec![KeyUsage::ClientAuth]; + provider + .initialize_module(key_algorithm, None, Some(hash), key_usages) + .unwrap(); + provider.create_key("231029").unwrap(); +} + +#[test] +fn key_creation_keyusage_decrypt_test() { + let security_module = SecModules::get_instance( + "2323".to_string(), + SecurityModule::Tpm(TpmType::Android(AndroidTpmType::Keystore)), + ); + + let x = security_module.unwrap(); + let mut provider = x.lock().unwrap(); + let key_algorithm = + algorithms::encryption::AsymmetricEncryption::Rsa(algorithms::KeyBits::Bits1024); + let hash = algorithms::hashes::Hash::Sha1; + let key_usages = vec![KeyUsage::Decrypt]; + provider + .initialize_module(key_algorithm, None, Some(hash), key_usages) + .unwrap(); + provider.create_key("231030").unwrap(); +} + +#[test] +fn key_creation_keyusage_createx509_test() { + let security_module = SecModules::get_instance( + "2323".to_string(), + SecurityModule::Tpm(TpmType::Android(AndroidTpmType::Keystore)), + ); + + let x = security_module.unwrap(); + let mut provider = x.lock().unwrap(); + let key_algorithm = + algorithms::encryption::AsymmetricEncryption::Rsa(algorithms::KeyBits::Bits1024); + let hash = algorithms::hashes::Hash::Sha1; + let key_usages = vec![KeyUsage::CreateX509]; + provider + .initialize_module(key_algorithm, None, Some(hash), key_usages) + .unwrap(); + provider.create_key("231031").unwrap(); +} + +/* + +---------------Sign Data------------------ + +*/ + +#[test] +fn sign_data_1_test() { + let security_module = SecModules::get_instance( + "2323".to_string(), + SecurityModule::Tpm(TpmType::Android(AndroidTpmType::Keystore)), + ); + + let x = security_module.unwrap(); + let mut provider = x.lock().unwrap(); + let key_algorithm = + algorithms::encryption::AsymmetricEncryption::Rsa(algorithms::KeyBits::Bits1024); + let hash = algorithms::hashes::Hash::Sha1; + let key_usages = vec![KeyUsage::SignEncrypt]; + provider + .initialize_module(key_algorithm, None, Some(hash), key_usages) + .unwrap(); + + let data = b"testing"; + + provider.sign_data(data); +} + +#[test] +fn sign_data_2_test() { + let security_module = SecModules::get_instance( + "2323".to_string(), + SecurityModule::Tpm(TpmType::Android(AndroidTpmType::Keystore)), + ); + + let x = security_module.unwrap(); + let mut provider = x.lock().unwrap(); + let key_algorithm = + algorithms::encryption::AsymmetricEncryption::Rsa(algorithms::KeyBits::Bits1024); + let hash = algorithms::hashes::Hash::Sha1; + let key_usages = vec![KeyUsage::SignEncrypt]; + provider + .initialize_module(key_algorithm, None, Some(hash), key_usages) + .unwrap(); + + let data = b"h"; + + provider.sign_data(data); +} + +//How to expect a fail?? +#[test] +fn sign_data_3_test() { + let security_module = SecModules::get_instance( + "2323".to_string(), + SecurityModule::Tpm(TpmType::Android(AndroidTpmType::Keystore)), + ); + + let x = security_module.unwrap(); + let mut provider = x.lock().unwrap(); + let key_algorithm = + algorithms::encryption::AsymmetricEncryption::Rsa(algorithms::KeyBits::Bits1024); + let hash = algorithms::hashes::Hash::Sha1; + let key_usages = vec![KeyUsage::SignEncrypt]; + provider + .initialize_module(key_algorithm, None, Some(hash), key_usages) + .unwrap(); + + let data = b""; + + provider.sign_data(data); +} + +//How to expect a fail?? +#[test] +fn sign_data_4_test() { + let security_module = SecModules::get_instance( + "2323".to_string(), + SecurityModule::Tpm(TpmType::Android(AndroidTpmType::Keystore)), + ); + + let x = security_module.unwrap(); + let mut provider = x.lock().unwrap(); + let key_algorithm = + algorithms::encryption::AsymmetricEncryption::Rsa(algorithms::KeyBits::Bits1024); + let hash = algorithms::hashes::Hash::Sha1; + let key_usages = vec![KeyUsage::SignEncrypt]; + provider + .initialize_module(key_algorithm, None, Some(hash), key_usages) + .unwrap(); + + let data = b"overflowing"; + + provider.sign_data(data); +} + +//Test different Key Ids => 0? + +#[test] +fn verify_signature_1_test() { + let security_module = SecModules::get_instance( + "2323".to_string(), + SecurityModule::Tpm(TpmType::Android(AndroidTpmType::Keystore)), + ); + + let x = security_module.unwrap(); + let mut provider = x.lock().unwrap(); + let key_algorithm = + algorithms::encryption::AsymmetricEncryption::Rsa(algorithms::KeyBits::Bits1024); + let hash = algorithms::hashes::Hash::Sha1; + let key_usages = vec![KeyUsage::SignEncrypt]; + provider + .initialize_module(key_algorithm, None, Some(hash), key_usages) + .unwrap(); + + let data = b"test"; + + let signature = provider.sign_data(data).unwrap(); + + //Convert Vec to list u8 + + let mut signature_list: [u8; 8] = [0, 0, 0, 0, 0, 0, 0, 0]; + + for (place, element) in signature_list.iter_mut().zip(signature.into_iter()) { + unsafe { std::ptr::write(place, element) }; + } + let verified = provider + .verify_signature(data, &signature_list) + .unwrap_or_default(); + + assert_eq!(true, verified); +} + +#[test] +fn verify_signature_2_test() { + let security_module = SecModules::get_instance( + "2323".to_string(), + SecurityModule::Tpm(TpmType::Android(AndroidTpmType::Keystore)), + ); + + let x = security_module.unwrap(); + let mut provider = x.lock().unwrap(); + let key_algorithm = + algorithms::encryption::AsymmetricEncryption::Rsa(algorithms::KeyBits::Bits1024); + let hash = algorithms::hashes::Hash::Sha1; + let key_usages = vec![KeyUsage::SignEncrypt]; + provider + .initialize_module(key_algorithm, None, Some(hash), key_usages) + .unwrap(); + + let data = b"testingX"; + + let signature = provider.sign_data(data).unwrap(); + + //Convert Vec to list u8 + + let mut signature_list: [u8; 8] = [0, 0, 0, 0, 0, 0, 0, 0]; + + for (place, element) in signature_list.iter_mut().zip(signature.into_iter()) { + unsafe { std::ptr::write(place, element) }; + } + let verified = provider + .verify_signature(data, &signature_list) + .unwrap_or_default(); + + assert_eq!(true, verified); +} + +#[test] +fn verify_signature_3_test() { + let security_module = SecModules::get_instance( + "2323".to_string(), + SecurityModule::Tpm(TpmType::Android(AndroidTpmType::Keystore)), + ); + + let x = security_module.unwrap(); + let mut provider = x.lock().unwrap(); + let key_algorithm = + algorithms::encryption::AsymmetricEncryption::Rsa(algorithms::KeyBits::Bits1024); + let hash = algorithms::hashes::Hash::Sha1; + let key_usages = vec![KeyUsage::SignEncrypt]; + provider + .initialize_module(key_algorithm, None, Some(hash), key_usages) + .unwrap(); + + let data = b"H"; + + let signature = provider.sign_data(data).unwrap(); + + //Convert Vec to list u8 + + let mut signature_list: [u8; 8] = [0, 0, 0, 0, 0, 0, 0, 0]; + + for (place, element) in signature_list.iter_mut().zip(signature.into_iter()) { + unsafe { std::ptr::write(place, element) }; + } + let verified = provider + .verify_signature(data, &signature_list) + .unwrap_or_default(); + + assert_eq!(true, verified); +} + +#[test] +fn verify_signature_4_test() { + let security_module = SecModules::get_instance( + "2323".to_string(), + SecurityModule::Tpm(TpmType::Android(AndroidTpmType::Keystore)), + ); + + let x = security_module.unwrap(); + let mut provider = x.lock().unwrap(); + let key_algorithm = + algorithms::encryption::AsymmetricEncryption::Rsa(algorithms::KeyBits::Bits1024); + let hash = algorithms::hashes::Hash::Sha1; + let key_usages = vec![KeyUsage::SignEncrypt]; + provider + .initialize_module(key_algorithm, None, Some(hash), key_usages) + .unwrap(); + + let data = b""; + + let signature = provider.sign_data(data).unwrap(); + + let mut signature_list: [u8; 8] = [0, 0, 0, 0, 0, 0, 0, 0]; + + for (place, element) in signature_list.iter_mut().zip(signature.into_iter()) { + unsafe { std::ptr::write(place, element) }; + } + let verified = provider + .verify_signature(data, &signature_list) + .unwrap_or_default(); + + assert_eq!(false, verified); +} + +#[test] +fn encrypt_data_1_test() { + let security_module = SecModules::get_instance( + "2323".to_string(), + SecurityModule::Tpm(TpmType::Android(AndroidTpmType::Keystore)), + ); + + let x = security_module.unwrap(); + let mut provider = x.lock().unwrap(); + let key_algorithm = + algorithms::encryption::AsymmetricEncryption::Rsa(algorithms::KeyBits::Bits1024); + let hash = algorithms::hashes::Hash::Sha1; + let key_usages = vec![KeyUsage::SignEncrypt]; + provider + .initialize_module(key_algorithm, None, Some(hash), key_usages) + .unwrap(); +} +*/ diff --git a/src/tests/tpm/mod.rs b/src/tests/tpm/mod.rs index 9e77b87c..ac82731e 100644 --- a/src/tests/tpm/mod.rs +++ b/src/tests/tpm/mod.rs @@ -2,3 +2,5 @@ mod linux; #[cfg(feature = "win")] mod win; +#[cfg(feature = "android")] +mod android; diff --git a/src/tpm/android/android_logger.rs b/src/tpm/android/android_logger.rs new file mode 100644 index 00000000..457db609 --- /dev/null +++ b/src/tpm/android/android_logger.rs @@ -0,0 +1,14 @@ +use tracing_subscriber::{layer::SubscriberExt, Registry}; + +use crate::common::traits::log_config::LogConfig; + +#[derive(Debug)] +pub struct DefaultAndroidLogger; + +impl LogConfig for DefaultAndroidLogger { + fn setup_logging(&self) { + let subscriber = Registry::default().with(tracing_android::layer("RUST").unwrap()); + tracing::subscriber::set_global_default(subscriber) + .expect("setting default subscriber failed"); + } +} diff --git a/src/tpm/android/config.rs b/src/tpm/android/config.rs new file mode 100644 index 00000000..98f7d523 --- /dev/null +++ b/src/tpm/android/config.rs @@ -0,0 +1,44 @@ +use std::any::Any; + +use robusta_jni::jni::JavaVM; + +use crate::common::{ + crypto::{ + algorithms::encryption::{AsymmetricEncryption, BlockCiphers}, + algorithms::hashes::Hash, + KeyUsage, + }, + traits::module_provider_config::ProviderConfig, +}; + +#[derive(Debug, Clone, Copy)] +pub enum EncryptionMode { + Sym(BlockCiphers), + ASym { + algo: AsymmetricEncryption, + digest: Hash, + }, +} + +pub struct AndroidConfig { + pub mode: EncryptionMode, + pub key_usages: Vec, + pub hardware_backed: bool, + pub vm: Option, +} + +impl std::fmt::Debug for AndroidConfig { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("AndroidProvider") + .field("mode", &self.mode) + .field("key_usages", &self.key_usages) + .field("hardware_backed", &self.hardware_backed) + .finish() + } +} + +impl ProviderConfig for AndroidConfig { + fn as_any(&self) -> &dyn Any { + self + } +} diff --git a/src/tpm/android/error.rs b/src/tpm/android/error.rs new file mode 100644 index 00000000..92682c07 --- /dev/null +++ b/src/tpm/android/error.rs @@ -0,0 +1,62 @@ +use crate::tpm::core::error::{ToTpmError, TpmError}; + +use super::wrapper; + +#[derive(Debug)] +pub(crate) struct JavaException(String); + +impl std::fmt::Display for JavaException { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Java Exception: {}", self.0) + } +} + +impl std::error::Error for JavaException {} + +/// This allows converting the JNI result into a `TpmError` result. +impl ToTpmError for robusta_jni::jni::errors::Result { + /// Converts the JNI result into a `TpmError` result. + /// If a Java exception was thrown, it retrieves the exception message and puts it into the error. + /// If no exception was thrown, it returns the JNI error as the `TpmError`. + fn err_internal(self) -> Result { + match self { + Ok(v) => Ok(v), + Err(e) => { + // check if a java exception was thrown + let vm = + wrapper::get_java_vm().map_err(|e| TpmError::InternalError(Box::new(e)))?; + let env = vm + .get_env() + .map_err(|e| TpmError::InternalError(Box::new(e)))?; + if env + .exception_check() + .map_err(|e| TpmError::InternalError(Box::new(e)))? + { + // get the exception message and put it into the error + env.exception_describe() + .map_err(|e| TpmError::InternalError(Box::new(e)))?; + let ex = env + .exception_occurred() + .map_err(|e| TpmError::InternalError(Box::new(e)))?; + env.exception_clear() + .map_err(|e| TpmError::InternalError(Box::new(e)))?; + let message = env + .call_method(ex, "getMessage", "()Ljava/lang/String;", &[]) + .and_then(|v| v.l()) + .map_err(|e| TpmError::InternalError(Box::new(e)))?; + + let message = env + .get_string(Into::into(message)) + .map_err(|e| TpmError::InternalError(Box::new(e)))? + .to_str() + .map_err(|e| TpmError::InternalError(Box::new(e)))? + .to_string(); + Err(TpmError::InternalError(Box::new(JavaException(message)))) + } else { + // there was no exception, return the jni error + Err(TpmError::InternalError(Box::new(e))) + } + } + } + } +} diff --git a/src/tpm/android/mod.rs b/src/tpm/android/mod.rs index 07d47505..1a8fb7f9 100644 --- a/src/tpm/android/mod.rs +++ b/src/tpm/android/mod.rs @@ -1 +1,547 @@ +pub mod android_logger; +pub mod config; +pub(crate) mod error; pub mod knox; +pub(crate) mod utils; +pub(crate) mod wrapper; + +use std::any::Any; + +use robusta_jni::jni::objects::JObject; +use tracing::{debug, info, instrument}; +use utils::{ + get_algorithm, get_cipher_mode, get_digest, get_key_size, get_padding, get_signature_algorithm, + get_signature_padding, get_sym_block_mode, +}; + +use crate::common::crypto::KeyUsage; +use crate::common::error::SecurityModuleError; +use crate::common::traits::key_handle::KeyHandle; +use crate::common::{ + crypto::algorithms::encryption::{AsymmetricEncryption, BlockCiphers}, + traits::module_provider::Provider, +}; +use crate::tpm::android::config::AndroidConfig; +use crate::tpm::android::wrapper::key_store::key_store::jni::KeyStore; +use crate::tpm::android::wrapper::key_store::signature::jni::Signature; +use crate::tpm::core::error::ToTpmError; +use crate::tpm::core::error::TpmError; + +const ANDROID_KEYSTORE: &str = "AndroidKeyStore"; + +/// A TPM-based cryptographic provider for managing cryptographic keys and performing +/// cryptographic operations in an Android environment. +/// +/// This provider uses the Android Keystore API to interact +/// with the Trusted Execution Environment (TEE), or the devices Secure Element(Like the Titan M chip in a Google Pixel) +/// for operations like signing, encryption, and decryption. +/// It provides a secure and hardware-backed solution for managing cryptographic keys and performing +/// cryptographic operations on Android. +#[derive(Debug)] +pub(crate) struct AndroidProvider { + key_id: String, + config: Option, +} + +impl AndroidProvider { + /// Constructs a new `AndroidProvider`. + /// + /// # Arguments + /// + /// * `key_id` - A string identifier for the cryptographic key to be managed by this provider. + /// * `config` - Configuration + /// + /// # Returns + /// + /// A new instance of `AndroidProvider` with the specified `key_id`. + #[instrument] + pub fn new(key_id: String) -> Self { + Self { + key_id, + config: None, + } + } + + fn apply_config(&mut self, config: AndroidConfig) -> Result<(), SecurityModuleError> { + // TODO: verify config + self.config = Some(config); + Ok(()) + } +} + +/// Implementation of the `Provider` trait for the Android platform. +/// +/// This struct provides methods for key generation, key loading, and module initialization +/// specific to Android. +impl Provider for AndroidProvider { + /// Generates a key with the parameters specified when the module was initialized. + /// + /// The key is generated using the Android Keystore API and is stored securely in the device's + /// Trusted Execution Environment (TEE) or Secure Element. It first attempts to generate a key + /// withing the devices StrongBox (Secure Element), and if that fails, because it is not available, + /// it falls back to the TEE. We have to do this because the KeyStore does not automatically select + /// the highest security level available. + /// + /// # Java Example + /// + /// ```java + /// KeyPairGenerator kpg = KeyPairGenerator.getInstance( + /// KeyProperties.KEY_ALGORITHM_EC, "AndroidKeyStore"); + /// kpg.initialize(new KeyGenParameterSpec.Builder( + /// alias, + /// KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY) + /// .setDigests(KeyProperties.DIGEST_SHA256, + /// KeyProperties.DIGEST_SHA512) + /// .build()); + /// KeyPair kp = kpg.generateKeyPair(); + /// ``` + /// + /// # Arguments + /// + /// * `key_id` - The identifier for the key. + /// + /// # Returns + /// + /// Returns `Ok(())` if the key generation is successful, otherwise returns an error of type `SecurityModuleError`. + #[instrument] + fn create_key( + &mut self, + key_id: &str, + config: Box, + ) -> Result<(), SecurityModuleError> { + info!("generating key! {}", key_id); + + // load config + let config = *config + .downcast::() + .map_err(|_| SecurityModuleError::InitializationError("Wrong Config".to_owned()))?; + + let env = config + .vm + .as_ref() + .expect("cannot happen, already checked") + .get_env() + .map_err(|_| { + TpmError::InitializationError( + "Could not get java environment, this should never happen".to_owned(), + ) + })?; + + // build up key specs + let mut kps_builder = + wrapper::key_generation::builder::Builder::new(&env, key_id.to_owned(), 1 | 2 | 4 | 8) + .err_internal()?; + + match config.mode { + config::EncryptionMode::Sym(cipher) => { + match cipher { + BlockCiphers::Aes(mode, size) => { + kps_builder = kps_builder + .set_block_modes(&env, vec![get_sym_block_mode(mode)?]) + .err_internal()? + .set_encryption_paddings(&env, vec![get_padding(config.mode)?]) + .err_internal()? + .set_key_size(&env, Into::::into(size) as i32) + .err_internal()?; + } + BlockCiphers::Des => { + kps_builder = kps_builder + .set_block_modes(&env, vec!["CBC".to_owned()]) + .err_internal()? + .set_encryption_paddings(&env, vec![get_padding(config.mode)?]) + .err_internal()?; + } + BlockCiphers::TripleDes(_) + | BlockCiphers::Rc2(_) + | BlockCiphers::Camellia(_, _) => { + Err(TpmError::UnsupportedOperation("not supported".to_owned()))? + } + }; + kps_builder = kps_builder + .set_is_strongbox_backed(&env, config.hardware_backed) + .err_internal()?; + + let kps = kps_builder.build(&env).err_internal()?; + + let kg = wrapper::key_generation::key_generator::jni::KeyGenerator::getInstance( + &env, + get_algorithm(config.mode)?, + ANDROID_KEYSTORE.to_owned(), + ) + .err_internal()?; + kg.init(&env, kps.raw.as_obj()).err_internal()?; + + kg.generateKey(&env).err_internal()?; + } + config::EncryptionMode::ASym { algo, digest } => { + match algo { + AsymmetricEncryption::Rsa(_key_bits) => { + kps_builder = kps_builder + .set_digests(&env, vec![get_digest(digest)?]) + .err_internal()? + .set_signature_paddings(&env, vec![get_signature_padding()?]) + .err_internal()? + .set_encryption_paddings(&env, vec![get_padding(config.mode)?]) + .err_internal()? + .set_key_size(&env, get_key_size(algo)? as i32) + .err_internal()?; + } + AsymmetricEncryption::Ecc(_scheme) => { + kps_builder = kps_builder + .set_digests(&env, vec![get_digest(digest)?]) + .err_internal()?; + } + }; + kps_builder = kps_builder + .set_is_strongbox_backed(&env, config.hardware_backed) + .err_internal()?; + + let kps = kps_builder.build(&env).err_internal()?; + + let kpg = wrapper::key_generation::key_pair_generator::jni::KeyPairGenerator::getInstance( + &env, + get_algorithm(config.mode)?, + ANDROID_KEYSTORE.to_owned(), + ) + .err_internal()?; + + kpg.initialize(&env, kps.raw.as_obj()).err_internal()?; + + kpg.generateKeyPair(&env).err_internal()?; + } + } + + debug!("key generated"); + self.apply_config(config)?; + + Ok(()) + } + + /// Loads a key with the specified `key_id`. + /// + /// # Arguments + /// + /// * `key_id` - The identifier for the key. + /// + /// # Returns + /// + /// Returns `Ok(())` if the key loading is successful, otherwise returns an error of type `SecurityModuleError`. + #[instrument] + fn load_key(&mut self, key_id: &str, config: Box) -> Result<(), SecurityModuleError> { + key_id.clone_into(&mut self.key_id); + + // load config + let config = *config + .downcast::() + .map_err(|_| SecurityModuleError::InitializationError("Wrong Config".to_owned()))?; + self.apply_config(config)?; + + Ok(()) + } + + /// Initializes the module with the specified parameters. + /// + /// # Arguments + /// + /// * `key_algorithm` - The asymmetric encryption algorithm to be used. + /// * `sym_algorithm` - The block cipher algorithm to be used (optional). + /// * `hash` - The hash algorithm to be used (optional). + /// * `key_usages` - The list of key usages. + /// + /// # Returns + /// + /// Returns `Ok(())` if the module initialization is successful, otherwise returns an error of type `SecurityModuleError`. + #[instrument] + fn initialize_module(&mut self) -> Result<(), SecurityModuleError> { + Ok(()) + } +} + +/// Implementation of the `KeyHandle` trait for the `AndroidProvider` struct. +/// All of the functions in this KeyHandle are basically re-implementations +/// of the equivalent Java functions in the Android KeyStore API. +impl KeyHandle for AndroidProvider { + /// Signs the given data using the Android KeyStore. + /// + /// # Arguments + /// + /// * `data` - Byte array of data to be signed. + /// + /// # Java Example + /// + /// ```java + /// KeyStore ks = KeyStore.getInstance("AndroidKeyStore"); + /// ks.load(null); + /// KeyStore.Entry entry = ks.getEntry(alias, null); + /// if (!(entry instanceof PrivateKeyEntry)) { + /// Log.w(TAG, "Not an instance of a PrivateKeyEntry"); + /// return null; + /// } + /// Signature s = Signature.getInstance("SHA256withECDSA"); + /// s.initSign(((PrivateKeyEntry) entry).getPrivateKey()); + /// s.update(data); + /// byte[] signature = s.sign(); + /// ``` + /// + /// # Returns + /// + /// Returns a `Result` containing the signed data as a `Vec` if successful, or a `SecurityModuleError` if an error occurs. + #[instrument] + fn sign_data(&self, data: &[u8]) -> Result, SecurityModuleError> { + // check that signing is allowed + let config = self + .config + .as_ref() + .ok_or(SecurityModuleError::InitializationError( + "Module is not initialized".to_owned(), + ))?; + + if !config.key_usages.contains(&KeyUsage::SignEncrypt) { + return Err(TpmError::UnsupportedOperation( + "KeyUsage::SignEncrypt was not provided".to_owned(), + ) + .into()); + } + + let env = config + .vm + .as_ref() + .ok_or_else(|| TpmError::InitializationError("Module is not initialized".to_owned()))? + .get_env() + .map_err(|_| { + TpmError::InitializationError( + "Could not get java environment, this should never happen".to_owned(), + ) + })?; + + let key_store = KeyStore::getInstance(&env, ANDROID_KEYSTORE.to_string()).err_internal()?; + key_store.load(&env, None).err_internal()?; + + let private_key = key_store + .getKey(&env, self.key_id.clone(), JObject::null()) + .err_internal()?; + + let signature_algorithm = get_signature_algorithm(config.mode)?; + debug!("Signature Algorithm: {}", signature_algorithm); + + let s = Signature::getInstance(&env, signature_algorithm.to_string()).err_internal()?; + + s.initSign(&env, private_key.raw.as_obj()).err_internal()?; + + let data_bytes = data.to_vec().into_boxed_slice(); + + s.update(&env, data_bytes).err_internal()?; + debug!("Signature Init: {}", s.toString(&env).unwrap()); + + let output = s.sign(&env).err_internal()?; + + Ok(output) + } + + /// Decrypts the given encrypted data using the Android KeyStore. + /// + /// # Arguments + /// + /// * `encrypted_data` - The encrypted data to be decrypted. + /// + /// # Java Example + /// + /// ```java + /// KeyStore keyStore = KeyStore.getInstance(ANDROID_KEYSTORE); + /// keyStore.load(null); + /// PrivateKey privateKey = (PrivateKey) keyStore.getKey(KEYNAME, null); + /// PublicKey publicKey = keyStore.getCertificate(KEYNAME).getPublicKey(); + /// Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding"); + /// cipher.init(Cipher.DECRYPT_MODE, privateKey); + /// byte[] decrypted = cipher.doFinal(encrypted); + /// ``` + /// + /// # Returns + /// + /// Returns a `Result` containing the decrypted data as a `Vec` if successful, or a `SecurityModuleError` if an error occurs. + #[instrument] + fn decrypt_data(&self, encrypted_data: &[u8]) -> Result, SecurityModuleError> { + info!("decrypting data"); + + let config = self + .config + .as_ref() + .ok_or(SecurityModuleError::InitializationError( + "Module is not initialized".to_owned(), + ))?; + + let env = config + .vm + .as_ref() + .ok_or_else(|| TpmError::InitializationError("Module is not initialized".to_owned()))? + .get_env() + .map_err(|_| { + TpmError::InitializationError( + "Could not get java environment, this should never happen".to_owned(), + ) + })?; + + let cipher_mode = get_cipher_mode(config.mode)?; + + let key_store = KeyStore::getInstance(&env, ANDROID_KEYSTORE.to_owned()).err_internal()?; + key_store.load(&env, None).err_internal()?; + + let key = key_store + .getKey(&env, self.key_id.to_owned(), JObject::null()) + .err_internal()?; + + let cipher = wrapper::key_store::cipher::jni::Cipher::getInstance(&env, cipher_mode) + .err_internal()?; + cipher.init(&env, 2, key.raw.as_obj()).err_internal()?; + + let decrypted = cipher + .doFinal(&env, encrypted_data.to_vec()) + .err_internal()?; + + debug!("decrypted data: {:?}", decrypted); + Ok(decrypted) + } + + /// Encrypts the given data using the Android KeyStore. + /// + /// # Arguments + /// + /// * `data` - The data to be encrypted. + /// + /// # Java Example + /// + /// ```java + /// KeyStore keyStore = KeyStore.getInstance(ANDROID_KEYSTORE); + /// keyStore.load(null); + /// PrivateKey privateKey = (PrivateKey) keyStore.getKey(KEYNAME, null); + /// PublicKey publicKey = keyStore.getCertificate(KEYNAME).getPublicKey(); + /// Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding"); + /// byte[] encrypted; + /// cipher.init(Cipher.ENCRYPT_MODE, publicKey); + /// encrypted = cipher.doFinal(text.getBytes()); + /// ``` + /// + /// # Returns + /// + /// Returns a `Result` containing the encrypted data as a `Vec` if successful, or a `SecurityModuleError` if an error occurs. + #[instrument] + fn encrypt_data(&self, data: &[u8]) -> Result, SecurityModuleError> { + info!("encrypting"); + + let config = self + .config + .as_ref() + .ok_or(SecurityModuleError::InitializationError( + "Module is not initialized".to_owned(), + ))?; + + let env = config + .vm + .as_ref() + .ok_or_else(|| TpmError::InitializationError("Module is not initialized".to_owned()))? + .get_env() + .map_err(|_| { + TpmError::InitializationError( + "Could not get java environment, this should never happen".to_owned(), + ) + })?; + + let key_store = KeyStore::getInstance(&env, ANDROID_KEYSTORE.to_owned()).err_internal()?; + key_store.load(&env, None).err_internal()?; + + let key = key_store + .getCertificate(&env, self.key_id.to_owned()) + .err_internal()? + .getPublicKey(&env) + .err_internal()?; + + let public_alg = key.getAlgorithm(&env).unwrap(); + debug!("Public Alg: {}", public_alg); + + let cipher = wrapper::key_store::cipher::jni::Cipher::getInstance( + &env, + get_cipher_mode(config.mode)?, + ) + .err_internal()?; + + cipher.init(&env, 1, key.raw.as_obj()).err_internal()?; + + let encrypted = cipher.doFinal(&env, data.to_vec()).err_internal()?; + + debug!("encrypted: {:?}", encrypted); + Ok(encrypted) + } + + /// Verifies the signature of the given data using the Android KeyStore. + /// + /// # Arguments + /// + /// * `data` - The data whose signature needs to be verified. + /// * `signature` - The signature to be verified. + /// + /// # Java Example + /// + /// ```java + /// KeyStore ks = KeyStore.getInstance("AndroidKeyStore"); + /// ks.load(null); + /// KeyStore.Entry entry = ks.getEntry(alias, null); + /// if (!(entry instanceof PrivateKeyEntry)) { + /// Log.w(TAG, "Not an instance of a PrivateKeyEntry"); + /// return false; + /// } + /// Signature s = Signature.getInstance("SHA256withECDSA"); + /// s.initVerify(((PrivateKeyEntry) entry).getCertificate()); + /// s.update(data); + /// boolean valid = s.verify(signature); + /// ``` + /// + /// # Returns + /// + /// Returns a `Result` containing `true` if the signature is valid, `false` otherwise, or a `SecurityModuleError` if an error occurs. + #[instrument] + fn verify_signature(&self, data: &[u8], signature: &[u8]) -> Result { + info!("verifiying"); + + let config = self + .config + .as_ref() + .ok_or(SecurityModuleError::InitializationError( + "Module is not initialized".to_owned(), + ))?; + + let env = config + .vm + .as_ref() + .ok_or_else(|| TpmError::InitializationError("Module is not initialized".to_owned()))? + .get_env() + .map_err(|_| { + TpmError::InitializationError( + "Could not get java environment, this should never happen".to_owned(), + ) + })?; + + let key_store = KeyStore::getInstance(&env, ANDROID_KEYSTORE.to_string()).err_internal()?; + key_store.load(&env, None).err_internal()?; + + let signature_algorithm = get_signature_algorithm(config.mode)?; + debug!("Signature Algorithm: {}", signature_algorithm); + + let s = Signature::getInstance(&env, signature_algorithm.to_string()).err_internal()?; + + let cert = key_store + .getCertificate(&env, self.key_id.clone()) + .err_internal()?; + + s.initVerify(&env, cert).err_internal()?; + debug!("Signature Init: {}", s.toString(&env).unwrap()); + + let data_bytes = data.to_vec().into_boxed_slice(); + s.update(&env, data_bytes).err_internal()?; + + let signature_boxed = signature.to_vec().into_boxed_slice(); + let output = s.verify(&env, signature_boxed).err_internal()?; + debug!("Signature verified: {:?}", output); + + Ok(output) + } +} diff --git a/src/tpm/android/utils.rs b/src/tpm/android/utils.rs new file mode 100644 index 00000000..96abf8f1 --- /dev/null +++ b/src/tpm/android/utils.rs @@ -0,0 +1,196 @@ +use crate::{ + common::{ + crypto::algorithms::{ + encryption::{AsymmetricEncryption, BlockCiphers, SymmetricMode}, + hashes::{Hash, Sha2Bits}, + }, + error::SecurityModuleError, + }, + tpm::core::error::TpmError, +}; + +use super::config::EncryptionMode; + +pub fn get_algorithm(enc: EncryptionMode) -> Result { + Ok(match enc { + EncryptionMode::Sym(algo) => match algo { + BlockCiphers::Aes(_, _) => "AES", + BlockCiphers::TripleDes(_) => "DESede", + BlockCiphers::Des | BlockCiphers::Rc2(_) | BlockCiphers::Camellia(_, _) => { + Err(TpmError::UnsupportedOperation("not supported".to_owned()))? + } + }, + EncryptionMode::ASym { algo, digest: _ } => match algo { + AsymmetricEncryption::Rsa(_) => "RSA", + AsymmetricEncryption::Ecc(_) => "EC", + }, + } + .to_owned()) +} + +pub fn get_cipher_mode(e_mode: EncryptionMode) -> Result { + match e_mode { + EncryptionMode::Sym(cipher) => match cipher { + BlockCiphers::Aes(mode, _) => Ok(format!( + "AES/{}/{}", + get_sym_block_mode(mode)?, + get_padding(e_mode)? + )), + BlockCiphers::TripleDes(_) => { + Err(TpmError::UnsupportedOperation("not supported".to_owned()).into()) + } + BlockCiphers::Des => Ok("DES".to_owned()), + BlockCiphers::Rc2(_) => { + Err(TpmError::UnsupportedOperation("not supported".to_owned()).into()) + } + BlockCiphers::Camellia(_, _) => { + Err(TpmError::UnsupportedOperation("not supported".to_owned()).into()) + } + }, + EncryptionMode::ASym { algo, digest: _ } => match algo { + AsymmetricEncryption::Rsa(_) => Ok(format!("RSA/ECB/{}", get_padding(e_mode)?)), + AsymmetricEncryption::Ecc(_) => { + Err(TpmError::UnsupportedOperation("not supported".to_owned()).into()) + } + }, + } +} + +pub fn get_sym_block_mode(mode: SymmetricMode) -> Result { + Ok(match mode { + SymmetricMode::Gcm => "GCM", + SymmetricMode::Ecb => "ECB", + SymmetricMode::Cbc => "CBC", + SymmetricMode::Ctr => "CTR", + SymmetricMode::Cfb | SymmetricMode::Ofb | SymmetricMode::Ccm => { + Err(TpmError::UnsupportedOperation( + "Only GCM, ECB, CBC and CTR as blockmodes supported".to_owned(), + ))? + } + } + .to_owned()) +} + +pub fn get_padding(mode: EncryptionMode) -> Result { + Ok(match mode { + EncryptionMode::Sym(BlockCiphers::Aes(_, _)) => "PKCS5Padding", + EncryptionMode::ASym { algo: _, digest: _ } => "PKCS1Padding", + _ => "PKCS1Padding", + } + .to_owned()) +} + +pub fn get_signature_padding() -> Result { + Ok("PKCS1".to_owned()) +} + +pub fn get_digest(digest: Hash) -> Result { + match digest { + Hash::Sha1 => Ok("SHA-1".to_owned()), + Hash::Sha2(size) => match size { + Sha2Bits::Sha224 => Ok("SHA-224".to_owned()), + Sha2Bits::Sha256 => Ok("SHA-256".to_owned()), + Sha2Bits::Sha384 => Ok("SHA-384".to_owned()), + Sha2Bits::Sha512 => Ok("SHA-512".to_owned()), + Sha2Bits::Sha512_224 | Sha2Bits::Sha512_256 => { + Err(TpmError::UnsupportedOperation("not supported".to_owned()).into()) + } + }, + Hash::Md5 => Ok("MD5".to_owned()), + Hash::Sha3(_) | Hash::Md2 | Hash::Md4 | Hash::Ripemd160 => { + Err(TpmError::UnsupportedOperation("not supported".to_owned()).into()) + } + } +} + +pub fn get_hash_name(hash: Hash) -> Result { + match hash { + Hash::Sha1 => Ok("SHA1".to_owned()), + Hash::Sha2(size) => match size { + Sha2Bits::Sha224 => Ok("SHA224".to_owned()), + Sha2Bits::Sha256 => Ok("SHA256".to_owned()), + Sha2Bits::Sha384 => Ok("SHA384".to_owned()), + Sha2Bits::Sha512 => Ok("SHA512".to_owned()), + Sha2Bits::Sha512_224 | Sha2Bits::Sha512_256 => { + Err(TpmError::UnsupportedOperation("not supported".to_owned()).into()) + } + }, + Hash::Md5 => Ok("MD5".to_owned()), + Hash::Sha3(_) | Hash::Md2 | Hash::Md4 | Hash::Ripemd160 => { + Err(TpmError::UnsupportedOperation("not supported".to_owned()).into()) + } + } +} + +pub fn get_key_size(algo: AsymmetricEncryption) -> Result { + match algo { + AsymmetricEncryption::Rsa(size) => Ok(Into::::into(size)), + AsymmetricEncryption::Ecc(_) => { + Err(TpmError::UnsupportedOperation("not supported".to_owned()).into()) + } + } +} + +pub fn get_curve(algo: AsymmetricEncryption) -> Result { + match algo { + AsymmetricEncryption::Rsa(_) => { + Err(TpmError::UnsupportedOperation("not supported".to_owned()).into()) + } + AsymmetricEncryption::Ecc(scheme) => { + let curve = match scheme { + crate::common::crypto::algorithms::encryption::EccSchemeAlgorithm::EcDsa(v) => v, + crate::common::crypto::algorithms::encryption::EccSchemeAlgorithm::EcDh(v) => v, + crate::common::crypto::algorithms::encryption::EccSchemeAlgorithm::EcDaa(v) => v, + crate::common::crypto::algorithms::encryption::EccSchemeAlgorithm::Sm2(v) => v, + crate::common::crypto::algorithms::encryption::EccSchemeAlgorithm::EcSchnorr(v) => { + v + } + crate::common::crypto::algorithms::encryption::EccSchemeAlgorithm::EcMqv(v) => v, + crate::common::crypto::algorithms::encryption::EccSchemeAlgorithm::Null => { + return Err(TpmError::UnsupportedOperation("not supported".to_owned()).into()) + } + }; + Ok(match curve { + crate::common::crypto::algorithms::encryption::EccCurves::P256 => "secp256r1", + crate::common::crypto::algorithms::encryption::EccCurves::P384 => "secp384r1", + crate::common::crypto::algorithms::encryption::EccCurves::P521 => "secp521r1", + crate::common::crypto::algorithms::encryption::EccCurves::Secp256k1 => "secp256k1", + crate::common::crypto::algorithms::encryption::EccCurves::BrainpoolP256r1 => { + "brainpoolP256r1" + } + crate::common::crypto::algorithms::encryption::EccCurves::BrainpoolP384r1 => { + "brainpoolP384r1" + } + crate::common::crypto::algorithms::encryption::EccCurves::BrainpoolP512r1 => { + "brainpoolP512r1" + } + crate::common::crypto::algorithms::encryption::EccCurves::BrainpoolP638 => { + return Err(TpmError::UnsupportedOperation("not supported".to_owned()).into()) + } + crate::common::crypto::algorithms::encryption::EccCurves::Curve25519 => "X25519", + crate::common::crypto::algorithms::encryption::EccCurves::Curve448 => "X448", + crate::common::crypto::algorithms::encryption::EccCurves::Frp256v1 => { + return Err(TpmError::UnsupportedOperation("not supported".to_owned()).into()) + } + } + .to_owned()) + } + } +} + +pub fn get_signature_algorithm(mode: EncryptionMode) -> Result { + match mode { + EncryptionMode::Sym(_) => { + Err(TpmError::UnsupportedOperation("not supported".to_owned()).into()) + } + EncryptionMode::ASym { algo, digest } => { + let part1 = match algo { + AsymmetricEncryption::Rsa(_) => "RSA", + AsymmetricEncryption::Ecc(_) => "ECDSA", + }; + let part2 = get_hash_name(digest)?; + + Ok(format!("{part2}with{part1}")) + } + } +} diff --git a/src/tpm/android/wrapper/key_generation/builder.rs b/src/tpm/android/wrapper/key_generation/builder.rs new file mode 100644 index 00000000..2d799fe2 --- /dev/null +++ b/src/tpm/android/wrapper/key_generation/builder.rs @@ -0,0 +1,275 @@ +use crate::tpm::android::wrapper::key_generation::key_gen_parameter_spec::jni::KeyGenParameterSpec; + +use robusta_jni::jni::errors::Result as JniResult; +use robusta_jni::jni::objects::{AutoLocal, JObject, JValue}; +use robusta_jni::jni::sys::jsize; +use robusta_jni::jni::JNIEnv; + +/// Builder for creating `KeyGenParameterSpec` objects. +/// This class is an inner class of `KeyGenParameterSpec`. For that reason, it could not +/// be implemented using the help of `robusta_jni`. `robusta_jni` does not support inner classes. +pub struct Builder<'env: 'borrow, 'borrow> { + raw: AutoLocal<'env, 'borrow>, +} + +impl<'env: 'borrow, 'borrow> Builder<'env, 'borrow> { + /// Creates a new `Builder` instance. + /// + /// # Arguments + /// + /// * `env` - The JNI environment. + /// * `keystore_alias` - The alias for the keystore. + /// * `purposes` - The purposes for which the key can be used. + /// + /// # Returns + /// + /// A `JniResult` containing the new `Builder` instance. + pub fn new( + env: &'borrow JNIEnv<'env>, + keystore_alias: String, + purposes: i32, + ) -> JniResult { + let class = env.find_class("android/security/keystore/KeyGenParameterSpec$Builder")?; + let jstring_keystore_alias = env.new_string(keystore_alias)?; + let args = [Into::into(jstring_keystore_alias), JValue::from(purposes)]; + let obj = env.new_object(class, "(Ljava/lang/String;I)V", &args)?; + Ok(Self { + raw: AutoLocal::new(env, Into::::into(obj)), + }) + } + + /// Sets the digests for the key. + /// + /// # Arguments + /// + /// * `self` - The `Builder` instance. + /// * `env` - The JNI environment. + /// * `digests` - The digests to set. + /// + /// # Returns + /// + /// A `JniResult` containing the updated `Builder` instance. + pub fn set_digests( + mut self, + env: &'borrow JNIEnv<'env>, + digests: Vec, + ) -> JniResult { + let string_class = env.find_class("java/lang/String")?; + let digest_array = + env.new_object_array(digests.len() as jsize, string_class, JObject::null())?; + for (i, digest) in digests.iter().enumerate() { + let jstring_digest = env.new_string(digest)?; + env.set_object_array_element(digest_array, i as jsize, jstring_digest)?; + } + + let result = env.call_method( + self.raw.as_obj(), + "setDigests", + "([Ljava/lang/String;)Landroid/security/keystore/KeyGenParameterSpec$Builder;", + &[digest_array.into()], + )?; + self.raw = AutoLocal::new(env, result.l()?); + Ok(self) + } + + /// Sets the encryption paddings for the key. + /// + /// # Arguments + /// + /// * `self` - The `Builder` instance. + /// * `env` - The JNI environment. + /// * `paddings` - The encryption paddings to set. + /// + /// # Returns + /// + /// A `JniResult` containing the updated `Builder` instance. + pub fn set_encryption_paddings( + mut self, + env: &'borrow JNIEnv<'env>, + paddings: Vec, + ) -> JniResult { + let string_class = env.find_class("java/lang/String")?; + let padding_array = + env.new_object_array(paddings.len() as jsize, string_class, JObject::null())?; + for (i, padding) in paddings.iter().enumerate() { + let jstring_padding = env.new_string(padding)?; + env.set_object_array_element(padding_array, i as jsize, jstring_padding)?; + } + + let result = env.call_method( + self.raw.as_obj(), + "setEncryptionPaddings", + "([Ljava/lang/String;)Landroid/security/keystore/KeyGenParameterSpec$Builder;", + &[padding_array.into()], + )?; + self.raw = AutoLocal::new(env, result.l()?); + Ok(self) + } + + /// Sets the signature paddings for the key. + /// + /// # Arguments + /// + /// * `self` - The `Builder` instance. + /// * `env` - The JNI environment. + /// * `paddings` - The signature paddings to set. + /// + /// # Returns + /// + /// A `JniResult` containing the updated `Builder` instance. + pub fn set_signature_paddings( + mut self, + env: &'borrow JNIEnv<'env>, + paddings: Vec, + ) -> JniResult { + let string_class = env.find_class("java/lang/String")?; + let padding_array = + env.new_object_array(paddings.len() as jsize, string_class, JObject::null())?; + for (i, padding) in paddings.iter().enumerate() { + let jstring_padding = env.new_string(padding)?; + env.set_object_array_element(padding_array, i as jsize, jstring_padding)?; + } + + let result = env.call_method( + self.raw.as_obj(), + "setSignaturePaddings", + "([Ljava/lang/String;)Landroid/security/keystore/KeyGenParameterSpec$Builder;", + &[padding_array.into()], + )?; + self.raw = AutoLocal::new(env, result.l()?); + Ok(self) + } + + /// Sets the block modes for the key. + /// + /// # Arguments + /// + /// * `self` - The `Builder` instance. + /// * `env` - The JNI environment. + /// * `block_modes` - The block modes to set. + /// + /// # Returns + /// + /// A `JniResult` containing the updated `Builder` instance. + pub fn set_block_modes( + mut self, + env: &'borrow JNIEnv<'env>, + block_modes: Vec, + ) -> JniResult { + let string_class = env.find_class("java/lang/String")?; + let block_mode_array = + env.new_object_array(block_modes.len() as jsize, string_class, JObject::null())?; + for (i, block_mode) in block_modes.iter().enumerate() { + let jstring_block_mode = env.new_string(block_mode)?; + env.set_object_array_element(block_mode_array, i as jsize, jstring_block_mode)?; + } + + let result = env.call_method( + self.raw.as_obj(), + "setBlockModes", + "([Ljava/lang/String;)Landroid/security/keystore/KeyGenParameterSpec$Builder;", + &[block_mode_array.into()], + )?; + self.raw = AutoLocal::new(env, result.l()?); + Ok(self) + } + + /// Sets the key size for the key. + /// + /// # Arguments + /// + /// * `self` - The `Builder` instance. + /// * `env` - The JNI environment. + /// * `key_size` - The key size to set. + /// + /// # Returns + /// + /// A `JniResult` containing the updated `Builder` instance. + pub fn set_key_size(mut self, env: &'borrow JNIEnv<'env>, key_size: i32) -> JniResult { + let result = env.call_method( + self.raw.as_obj(), + "setKeySize", + "(I)Landroid/security/keystore/KeyGenParameterSpec$Builder;", + &[JValue::Int(key_size)], + )?; + self.raw = AutoLocal::new(env, result.l()?); + Ok(self) + } + + /// Sets the algorithm parameter specification for the key. + /// + /// # Arguments + /// + /// * `self` - The `Builder` instance. + /// * `env` - The JNI environment. + /// * `spec` - The algorithm parameter specification to set. + /// + /// # Returns + /// + /// A `JniResult` containing the updated `Builder` instance. + pub fn set_algorithm_parameter_spec( + mut self, + env: &'borrow JNIEnv<'env>, + spec: JObject, + ) -> JniResult { + let result = env.call_method( + self.raw.as_obj(), + "setAlgorithmParameterSpec", + "(Ljavax/crypto/spec/AlgorithmParameterSpec;)Landroid/security/keystore/KeyGenParameterSpec$Builder;", + &[JValue::Object(spec)], + )?; + self.raw = AutoLocal::new(env, result.l()?); + Ok(self) + } + + /// Sets whether the key is backed by a strongbox. + /// + /// # Arguments + /// + /// * `self` - The `Builder` instance. + /// * `env` - The JNI environment. + /// * `is_strongbox_backed` - Whether the key is strongbox backed. + /// + /// # Returns + /// + /// A `JniResult` containing the updated `Builder` instance. + pub fn set_is_strongbox_backed( + mut self, + env: &'borrow JNIEnv<'env>, + is_strongbox_backed: bool, + ) -> JniResult { + let result = env.call_method( + self.raw.as_obj(), + "setIsStrongBoxBacked", + "(Z)Landroid/security/keystore/KeyGenParameterSpec$Builder;", + &[JValue::Bool(is_strongbox_backed.into())], + )?; + self.raw = AutoLocal::new(env, result.l()?); + Ok(self) + } + + /// Builds the `KeyGenParameterSpec` object. + /// + /// # Arguments + /// + /// * `self` - The `Builder` instance. + /// * `env` - The JNI environment. + /// + /// # Returns + /// + /// A `JniResult` containing the built `KeyGenParameterSpec` object. + pub fn build( + self, + env: &'borrow JNIEnv<'env>, + ) -> JniResult> { + let result = env.call_method( + self.raw.as_obj(), + "build", + "()Landroid/security/keystore/KeyGenParameterSpec;", + &[], + )?; + Ok(KeyGenParameterSpec { + raw: AutoLocal::new(env, result.l()?), + }) + } +} diff --git a/src/tpm/android/wrapper/key_generation/key.rs b/src/tpm/android/wrapper/key_generation/key.rs new file mode 100644 index 00000000..cefbe246 --- /dev/null +++ b/src/tpm/android/wrapper/key_generation/key.rs @@ -0,0 +1,57 @@ +use robusta_jni::bridge; + +#[bridge] +/// This module contains the JNI bindings for key generation in Android. +pub mod jni { + use robusta_jni::{ + convert::{IntoJavaValue, Signature, TryFromJavaValue, TryIntoJavaValue}, + jni::{errors::Result as JniResult, objects::AutoLocal, JNIEnv}, + }; + + /// Represents a key in Java's `java.security` package. + #[derive(Signature, TryIntoJavaValue, IntoJavaValue, TryFromJavaValue)] + #[package(java.security)] + pub struct Key<'env: 'borrow, 'borrow> { + #[instance] + pub raw: AutoLocal<'env, 'borrow>, + } + + #[derive(Signature, TryIntoJavaValue, IntoJavaValue, TryFromJavaValue)] + #[package(javax.crypto)] + pub struct SecretKey<'env: 'borrow, 'borrow> { + #[instance] + pub raw: AutoLocal<'env, 'borrow>, + } + + /// Represents a public key in Java's `java.security` package. + #[derive(Signature, TryIntoJavaValue, IntoJavaValue, TryFromJavaValue)] + #[package(java.security)] + pub struct PublicKey<'env: 'borrow, 'borrow> { + #[instance] + pub raw: AutoLocal<'env, 'borrow>, + } + + impl<'env: 'borrow, 'borrow> PublicKey<'env, 'borrow> { + /// Converts the public key to its string representation. + pub extern "java" fn toString(&self, _env: &JNIEnv) -> JniResult {} + + /// Retrieves the algorithm used by the public key. + pub extern "java" fn getAlgorithm(&self, _env: &JNIEnv) -> JniResult {} + } + + /// Represents a private key in Java's `java.security` package. + #[derive(Signature, TryIntoJavaValue, IntoJavaValue, TryFromJavaValue)] + #[package(java.security)] + pub struct PrivateKey<'env: 'borrow, 'borrow> { + #[instance] + pub raw: AutoLocal<'env, 'borrow>, + } + + impl<'env: 'borrow, 'borrow> PrivateKey<'env, 'borrow> { + /// Converts the private key to its string representation. + pub extern "java" fn toString(&self, _env: &JNIEnv) -> JniResult {} + + /// Retrieves the algorithm used by the private key. + pub extern "java" fn getAlgorithm(&self, _env: &JNIEnv) -> JniResult {} + } +} diff --git a/src/tpm/android/wrapper/key_generation/key_gen_parameter_spec.rs b/src/tpm/android/wrapper/key_generation/key_gen_parameter_spec.rs new file mode 100644 index 00000000..1bf683a4 --- /dev/null +++ b/src/tpm/android/wrapper/key_generation/key_gen_parameter_spec.rs @@ -0,0 +1,44 @@ +use robusta_jni::bridge; + +#[bridge] +/// This module contains the JNI bindings for the KeyGenParameterSpec struct/class. +pub mod jni { + use robusta_jni::{ + convert::{IntoJavaValue, Signature, TryFromJavaValue, TryIntoJavaValue}, + jni::{errors::Result as JniResult, objects::AutoLocal, JNIEnv}, + }; + + /// Represents the KeyGenParameterSpec struct in the android.security.keystore package. + #[derive(Signature, TryIntoJavaValue, IntoJavaValue, TryFromJavaValue)] + #[package(android.security.keystore)] + pub struct KeyGenParameterSpec<'env: 'borrow, 'borrow> { + #[instance] + pub raw: AutoLocal<'env, 'borrow>, + } + + impl<'env: 'borrow, 'borrow> KeyGenParameterSpec<'env, 'borrow> { + /// Retrieves the supported digest algorithms for the key generation. + /// + /// # Arguments + /// + /// * `env` - The JNI environment. + /// + /// # Returns + /// + /// A Result containing a vector of strings representing the supported digest algorithms, + /// or an error if the JNI call fails. + pub extern "java" fn getDigests(&self, env: &JNIEnv) -> JniResult> {} + + /// Checks if the key generation is backed by a StrongBox. + /// + /// # Arguments + /// + /// * `env` - The JNI environment. + /// + /// # Returns + /// + /// A Result containing a boolean value indicating whether the key generation is backed by a StrongBox, + /// or an error if the JNI call fails. + pub extern "java" fn isStrongBoxBacked(&self, env: &JNIEnv) -> JniResult {} + } +} diff --git a/src/tpm/android/wrapper/key_generation/key_generator.rs b/src/tpm/android/wrapper/key_generation/key_generator.rs new file mode 100644 index 00000000..f200c7b8 --- /dev/null +++ b/src/tpm/android/wrapper/key_generation/key_generator.rs @@ -0,0 +1,39 @@ +use robusta_jni::bridge; + +#[bridge] +pub mod jni { + use crate::tpm::android::wrapper::key_generation::key::jni::SecretKey; + use robusta_jni::{ + convert::{IntoJavaValue, Signature, TryFromJavaValue, TryIntoJavaValue}, + jni::{ + errors::Result as JniResult, + objects::{AutoLocal, JObject}, + JNIEnv, + }, + }; + + #[derive(Signature, TryIntoJavaValue, IntoJavaValue, TryFromJavaValue)] + #[package(javax.crypto)] + pub struct KeyGenerator<'env: 'borrow, 'borrow> { + #[instance] + pub raw: AutoLocal<'env, 'borrow>, + } + + impl<'env: 'borrow, 'borrow> KeyGenerator<'env, 'borrow> { + pub extern "java" fn getInstance( + env: &'borrow JNIEnv<'env>, + algorithm: String, + provider: String, + ) -> JniResult { + } + + pub extern "java" fn init( + &self, + env: &JNIEnv, + #[input_type("Ljava/security/spec/AlgorithmParameterSpec;")] params: JObject, + ) -> JniResult<()> { + } + + pub extern "java" fn generateKey(&self, _env: &'borrow JNIEnv) -> JniResult {} + } +} diff --git a/src/tpm/android/wrapper/key_generation/key_pair.rs b/src/tpm/android/wrapper/key_generation/key_pair.rs new file mode 100644 index 00000000..b8934478 --- /dev/null +++ b/src/tpm/android/wrapper/key_generation/key_pair.rs @@ -0,0 +1,30 @@ +use robusta_jni::bridge; + +#[bridge] +/// This module contains the JNI bindings for the `KeyPair` struct used in Android TPM key generation. +pub mod jni { + use crate::tpm::android::wrapper::key_generation::key::jni::{PrivateKey, PublicKey}; + use robusta_jni::{ + convert::{IntoJavaValue, Signature, TryFromJavaValue, TryIntoJavaValue}, + jni::{errors::Result as JniResult, objects::AutoLocal, JNIEnv}, + }; + + /// Represents a Java `KeyPair` object. + #[derive(Signature, TryIntoJavaValue, IntoJavaValue, TryFromJavaValue)] + #[package(java.security)] + pub struct KeyPair<'env: 'borrow, 'borrow> { + #[instance] + pub raw: AutoLocal<'env, 'borrow>, + } + + impl<'env: 'borrow, 'borrow> KeyPair<'env, 'borrow> { + /// Returns a string representation of the `KeyPair` object. + pub extern "java" fn toString(&self, _env: &JNIEnv) -> JniResult {} + + /// Returns the public key associated with the `KeyPair` object. + pub extern "java" fn getPublic(&self, _env: &'borrow JNIEnv) -> JniResult {} + + /// Returns the private key associated with the `KeyPair` object. + pub extern "java" fn getPrivate(&self, _env: &'borrow JNIEnv) -> JniResult {} + } +} diff --git a/src/tpm/android/wrapper/key_generation/key_pair_generator.rs b/src/tpm/android/wrapper/key_generation/key_pair_generator.rs new file mode 100644 index 00000000..67671a14 --- /dev/null +++ b/src/tpm/android/wrapper/key_generation/key_pair_generator.rs @@ -0,0 +1,104 @@ +use robusta_jni::bridge; + +#[bridge] +/// This module contains the JNI bindings for the `KeyPairGenerator` class in the Android TPM wrapper. +pub mod jni { + use crate::tpm::android::wrapper::key_generation::key_pair::jni::KeyPair; + use robusta_jni::{ + convert::{IntoJavaValue, Signature, TryFromJavaValue, TryIntoJavaValue}, + jni::{ + errors::Result as JniResult, + objects::{AutoLocal, JObject}, + JNIEnv, + }, + }; + + /// Represents a Java `KeyPairGenerator` object. + #[derive(Signature, TryIntoJavaValue, IntoJavaValue, TryFromJavaValue)] + #[package(java.security)] + pub struct KeyPairGenerator<'env: 'borrow, 'borrow> { + #[instance] + pub raw: AutoLocal<'env, 'borrow>, + } + + impl<'env: 'borrow, 'borrow> KeyPairGenerator<'env, 'borrow> { + /// Returns an instance of `KeyPairGenerator` for the specified algorithm and provider. + /// + /// # Arguments + /// + /// * `env` - The JNI environment. + /// * `algorithm` - The name of the algorithm. + /// * `provider` - The name of the provider. + /// + /// # Returns + /// + /// Returns a `JniResult` containing the `KeyPairGenerator` instance. + pub extern "java" fn getInstance( + env: &'borrow JNIEnv<'env>, + algorithm: String, + provider: String, + ) -> JniResult { + } + + /// Returns a string representation of the `KeyPairGenerator` object. + /// + /// # Arguments + /// + /// * `_env` - The JNI environment. + /// + /// # Returns + /// + /// Returns a `JniResult` containing the string representation. + pub extern "java" fn toString(&self, _env: &JNIEnv) -> JniResult {} + + /// Returns the algorithm name associated with the `KeyPairGenerator` object. + /// + /// # Arguments + /// + /// * `_env` - The JNI environment. + /// + /// # Returns + /// + /// Returns a `JniResult` containing the algorithm name. + pub extern "java" fn getAlgorithm(&self, _env: &JNIEnv) -> JniResult {} + + /// Initializes the `KeyPairGenerator` object with the specified algorithm parameters. + /// + /// Initializes the key pair generator using the specified parameter set and the SecureRandom + /// implementation of the highest-priority installed provider as the source of randomness. + /// (If none of the installed providers supply an implementation of SecureRandom, a system-provided source of randomness is used.) + /// + /// Could not be implemented using `robusta_jni` because the params parameter is the class + /// AlgorithmParameterSpec. AlgorithmParameterSpec is an interface and we need to pass an object + /// of type KeyGenParameterSpec. This causes the signatures to not match, meaning the jni call fails. + /// # Arguments + /// + /// * `env` - The JNI environment. + /// * `params` - The algorithm parameter specification. + /// + /// # Returns + /// + /// Returns a `JniResult` indicating success or failure. + pub extern "java" fn initialize( + &self, + env: &JNIEnv, + #[input_type("Ljava/security/spec/AlgorithmParameterSpec;")] params: JObject, + ) -> JniResult<()> { + } + + /// Generates a key pair using the `KeyPairGenerator` object. + /// + /// If this KeyPairGenerator has not been initialized explicitly, provider-specific defaults + /// will be used for the size and other (algorithm-specific) values of the generated keys. + /// This will generate a new key pair every time it is called. + /// + /// # Arguments + /// + /// * `_env` - The JNI environment. + /// + /// # Returns + /// + /// Returns a `JniResult` containing the generated `KeyPair`. + pub extern "java" fn generateKeyPair(&self, _env: &'borrow JNIEnv) -> JniResult {} + } +} diff --git a/src/tpm/android/wrapper/key_generation/mod.rs b/src/tpm/android/wrapper/key_generation/mod.rs new file mode 100644 index 00000000..a190cf00 --- /dev/null +++ b/src/tpm/android/wrapper/key_generation/mod.rs @@ -0,0 +1,9 @@ +#![allow(clippy::needless_borrow)] + +pub mod builder; +pub mod key; +pub mod key_gen_parameter_spec; +pub mod key_generator; +pub mod key_pair; +pub mod key_pair_generator; +pub mod secure_random; diff --git a/src/tpm/android/wrapper/key_generation/secure_random.rs b/src/tpm/android/wrapper/key_generation/secure_random.rs new file mode 100644 index 00000000..8a762a7b --- /dev/null +++ b/src/tpm/android/wrapper/key_generation/secure_random.rs @@ -0,0 +1,30 @@ +use robusta_jni::bridge; + +#[bridge] +/// This module contains the JNI bindings for the `SecureRandom` class in the `java.security` package. +/// This might not even be necessary. The `SecureRandom` is automatically generated by `KeyPairGenerator.initialize` +pub mod jni { + use robusta_jni::{ + convert::{IntoJavaValue, Signature, TryFromJavaValue, TryIntoJavaValue}, + jni::errors::Result as JniResult, + jni::objects::AutoLocal, + jni::JNIEnv, + }; + + /// Represents the `SecureRandom` class in Java. + #[derive(Signature, TryIntoJavaValue, IntoJavaValue, TryFromJavaValue)] + #[package(java.security)] + pub struct SecureRandom<'env: 'borrow, 'borrow> { + #[instance] + pub raw: AutoLocal<'env, 'borrow>, + } + + impl<'env: 'borrow, 'borrow> SecureRandom<'env, 'borrow> { + /// Constructs a new `SecureRandom` instance. + #[constructor] + pub extern "java" fn new(env: &'borrow JNIEnv<'env>) -> JniResult {} + + /// Returns the algorithm name of the `SecureRandom` instance. + pub extern "java" fn getAlgorithm(&self, env: &JNIEnv<'env>) -> JniResult {} + } +} diff --git a/src/tpm/android/wrapper/key_store/cipher.rs b/src/tpm/android/wrapper/key_store/cipher.rs new file mode 100644 index 00000000..a3cfb939 --- /dev/null +++ b/src/tpm/android/wrapper/key_store/cipher.rs @@ -0,0 +1,86 @@ +use robusta_jni::bridge; + +#[bridge] +/// This module contains the JNI bindings for the Cipher class in the javax.crypto package. +pub mod jni { + use robusta_jni::{ + convert::{IntoJavaValue, Signature, TryFromJavaValue, TryIntoJavaValue}, + jni::{ + errors::Result as JniResult, + objects::{AutoLocal, JObject, JValue}, + sys::jbyteArray, + JNIEnv, + }, + }; + + /// Represents a Cipher object in Java. + #[derive(Signature, TryIntoJavaValue, IntoJavaValue, TryFromJavaValue)] + #[package(javax.crypto)] + pub struct Cipher<'env: 'borrow, 'borrow> { + #[instance] + pub raw: AutoLocal<'env, 'borrow>, + } + + impl<'env: 'borrow, 'borrow> Cipher<'env, 'borrow> { + /// Creates a new instance of the Cipher class. + /// + /// # Arguments + /// + /// * `env` - The JNIEnv object. + /// * `transformation` - the name of the transformation, e.g., DES/CBC/PKCS5Padding. See the Cipher section in the Java Cryptography Architecture Standard Algorithm Name Documentation for information about standard transformation names. + /// + /// # Returns + /// + /// Returns a Cipher object that implements the specified transformation. + pub extern "java" fn getInstance( + env: &'borrow JNIEnv<'env>, + transformation: String, + ) -> JniResult { + } + + /// Initializes the Cipher object with the specified operation mode and key. + /// + /// # Arguments + /// + /// * `env` - The JNIEnv object. + /// * `opmode` - The operation mode. + /// * `key` - The key object. + /// + /// # Returns + /// + /// Returns a JniResult indicating success or failure. + pub extern "java" fn init( + &self, + env: &'borrow JNIEnv<'env>, + opmode: i32, + #[input_type("Ljava/security/Key;")] key: JObject, + ) -> JniResult<()> { + } + + /// Performs the final operation of the Cipher, processing any remaining data. + /// + /// # Arguments + /// + /// * `env` - The JNIEnv object. + /// * `input` - The input data. + /// + /// # Returns + /// + /// Returns a JniResult containing the output data. + pub fn doFinal(&self, env: &JNIEnv, input: Vec) -> JniResult> { + let input_array = env.byte_array_from_slice(&input)?; + + let output = env.call_method( + self.raw.as_obj(), + "doFinal", + "([B)[B", + &[JValue::from(input_array)], + )?; + + let output_array = output.l()?.into_inner() as jbyteArray; + let output_vec = env.convert_byte_array(output_array).unwrap(); + + Ok(output_vec) + } + } +} diff --git a/src/tpm/android/wrapper/key_store/key_store.rs b/src/tpm/android/wrapper/key_store/key_store.rs new file mode 100644 index 00000000..3291df36 --- /dev/null +++ b/src/tpm/android/wrapper/key_store/key_store.rs @@ -0,0 +1,131 @@ +use robusta_jni::bridge; + +#[bridge] +/// This module contains the JNI bindings for the KeyStore functionality in Android. +pub mod jni { + use crate::tpm::android::wrapper::key_generation::key::jni::{Key, PublicKey}; + use robusta_jni::{ + convert::{IntoJavaValue, Signature, TryFromJavaValue, TryIntoJavaValue}, + jni::{ + errors::Result as JniResult, + objects::{AutoLocal, JObject}, + JNIEnv, + }, + }; + + /// Represents a KeyStore object in Java. + #[derive(Signature, TryIntoJavaValue, IntoJavaValue, TryFromJavaValue)] + #[package(java.security)] + pub struct KeyStore<'env: 'borrow, 'borrow> { + #[instance] + pub raw: AutoLocal<'env, 'borrow>, + } + + impl<'env: 'borrow, 'borrow> KeyStore<'env, 'borrow> { + /// Retrieves an instance of the KeyStore class. + /// + /// # Arguments + /// + /// * `env` - The JNI environment. + /// * `type1` - The type of the KeyStore. In java the paramenter is just 'type', but we have to use 'type1' because 'type' is a reserved keyword in Rust. + /// + /// # Returns + /// + /// Returns a keystore object of the specified type. + pub extern "java" fn getInstance( + env: &'borrow JNIEnv<'env>, + type1: String, + ) -> JniResult { + } + + /// Retrieves a certificate from the KeyStore. + /// + /// Returns the certificate associated with the given alias. + /// If the given alias name identifies an entry created by a call to setCertificateEntry, + /// or created by a call to setEntry with a TrustedCertificateEntry, then the trusted certificate + /// contained in that entry is returned. + /// + /// # Arguments + /// + /// * `env` - The JNI environment. + /// * `alias` - The alias name. + /// + /// # Returns + /// + /// Returns a `JniResult` containing the Certificate instance. + pub extern "java" fn getCertificate( + &self, + env: &'borrow JNIEnv<'env>, + alias: String, + ) -> JniResult { + } + + /// Retrieves a key from the KeyStore. + /// + /// # Arguments + /// + /// * `env` - The JNI environment. + /// * `alias` - The alias of the key. + /// * `password` - The password for the key. + /// + /// # Returns + /// + /// Returns a `JniResult` containing the Key instance. + pub extern "java" fn getKey( + &self, + env: &'borrow JNIEnv<'env>, + alias: String, + #[input_type("[C")] password: JObject, + ) -> JniResult { + } + + /// Loads the KeyStore. + /// + /// # Arguments + /// + /// * `env` - The JNI environment. + /// * `param` - An optional parameter for loading the KeyStore. + /// + /// # Returns + /// + /// Returns a `JniResult` indicating the success or failure of the operation. + pub fn load(&self, env: &JNIEnv, param: Option) -> JniResult<()> { + let param_obj = param.unwrap_or(JObject::null()); + env.call_method( + self.raw.as_obj(), + "load", + "(Ljava/security/KeyStore$LoadStoreParameter;)V", + &[Into::into(param_obj)], + )?; + Ok(()) + } + } + + /// Represents a Certificate object in Java. + #[derive(Signature, TryIntoJavaValue, IntoJavaValue, TryFromJavaValue)] + #[package(java.security.cert)] + pub struct Certificate<'env: 'borrow, 'borrow> { + #[instance] + pub raw: AutoLocal<'env, 'borrow>, + } + + impl<'env: 'borrow, 'borrow> Certificate<'env, 'borrow> { + /// Retrieves the public key from the Certificate. + /// + /// # Arguments + /// + /// * `env` - The JNI environment. + /// + /// # Returns + /// + /// Returns a `JniResult` containing the PublicKey instance. + pub extern "java" fn getPublicKey( + &self, + env: &'borrow JNIEnv<'env>, + ) -> JniResult> { + } + + /// toString Java method of the Certificate class. + pub extern "java" fn toString(&self, _env: &JNIEnv) -> JniResult {} + } +} diff --git a/src/tpm/android/wrapper/key_store/mod.rs b/src/tpm/android/wrapper/key_store/mod.rs new file mode 100644 index 00000000..75a40b47 --- /dev/null +++ b/src/tpm/android/wrapper/key_store/mod.rs @@ -0,0 +1,5 @@ +#![allow(clippy::needless_borrow)] + +pub mod cipher; +pub mod key_store; +pub mod signature; diff --git a/src/tpm/android/wrapper/key_store/signature.rs b/src/tpm/android/wrapper/key_store/signature.rs new file mode 100644 index 00000000..fc49b3ea --- /dev/null +++ b/src/tpm/android/wrapper/key_store/signature.rs @@ -0,0 +1,133 @@ +use robusta_jni::bridge; + +#[bridge] +/// This module contains the JNI bindings for the Signature class in the Java security package. +pub mod jni { + use crate::tpm::android::wrapper::key_store::key_store::jni::Certificate; + use robusta_jni::{ + convert::{IntoJavaValue, Signature as JavaSignature, TryFromJavaValue, TryIntoJavaValue}, + jni::{ + errors::Result as JniResult, + objects::{AutoLocal, JObject, JValue}, + JNIEnv, + }, + }; + + /// Represents a Signature object in Java. + #[derive(JavaSignature, TryIntoJavaValue, IntoJavaValue, TryFromJavaValue)] + #[package(java.security)] + pub struct Signature<'env: 'borrow, 'borrow> { + #[instance] + pub raw: AutoLocal<'env, 'borrow>, + } + + impl<'env: 'borrow, 'borrow> Signature<'env, 'borrow> { + /// Creates a new instance of the Signature class with the specified algorithm. + /// + /// # Arguments + /// + /// * `env` - The JNI environment. + /// * `algorithm` - The algorithm to use for the Signature instance. + /// + /// # Returns + /// + /// Returns a Result containing the Signature instance if successful, or an error if it fails. + pub extern "java" fn getInstance( + env: &'borrow JNIEnv<'env>, + algorithm: String, + ) -> JniResult { + } + + /// Signs the data using the Signature instance. + /// + /// Could not be implemented using `robusta_jni` because the Java method returns a byte array, + /// and byte arrays are not supported as a return value by `robusta_jni`. + /// + /// # Arguments + /// + /// * `env` - The JNI environment. + /// + /// # Returns + /// + /// Returns a Result containing the signed data as a Vec if successful, or an error if it fails. + pub fn sign(&self, env: &JNIEnv) -> JniResult> { + let result = env.call_method(self.raw.as_obj(), "sign", "()[B", &[])?; + + let byte_array = result.l()?.into_inner(); + let output = env.convert_byte_array(byte_array)?; + + Ok(output) + } + + /// Initializes the Signature instance for signing with the specified private key. + /// + /// # Arguments + /// + /// * `env` - The JNI environment. + /// * `privateKey` - The private key to use for signing. + /// + /// # Returns + /// + /// Returns a Result indicating success or failure. + pub extern "java" fn initSign( + &self, + env: &JNIEnv, + #[input_type("Ljava/security/PrivateKey;")] privateKey: JObject, + ) -> JniResult<()> { + } + + /// Initializes the Signature instance for verification with the specified certificate. + /// + /// Could not be implemented using `robusta_jni` because for some reason it doesn't + /// recognize the `Certificate` signature correctly. + /// + /// # Arguments + /// + /// * `env` - The JNI environment. + /// * `certificate` - The certificate to use for verification. + /// + /// # Returns + /// + /// Returns a Result indicating success or failure. + pub fn initVerify(&self, env: &JNIEnv, certificate: Certificate) -> JniResult<()> { + let certificate_obj = certificate.raw.as_obj(); + + env.call_method( + self.raw.as_obj(), + "initVerify", + "(Ljava/security/cert/Certificate;)V", + &[JValue::from(certificate_obj)], + )?; + + Ok(()) + } + + /// Verifies the signature against the specified data. + /// + /// # Arguments + /// + /// * `_env` - The JNI environment. + /// * `signature` - The signature to verify. + /// + /// # Returns + /// + /// Returns a Result indicating whether the signature is valid or not. + pub extern "java" fn verify(&self, _env: &JNIEnv, signature: Box<[u8]>) -> JniResult { + } + + /// Updates the Signature instance with additional data to be signed or verified. + /// + /// # Arguments + /// + /// * `_env` - The JNI environment. + /// * `data` - The data to update the Signature instance with. + /// + /// # Returns + /// + /// Returns a Result indicating success or failure. + pub extern "java" fn update(&self, _env: &JNIEnv, data: Box<[u8]>) -> JniResult<()> {} + + /// toString Java method of the Signature class. + pub extern "java" fn toString(&self, _env: &JNIEnv) -> JniResult {} + } +} diff --git a/src/tpm/android/wrapper/mod.rs b/src/tpm/android/wrapper/mod.rs new file mode 100644 index 00000000..9f90f251 --- /dev/null +++ b/src/tpm/android/wrapper/mod.rs @@ -0,0 +1,61 @@ +use robusta_jni::jni::{ + self, + sys::{jint, jsize}, + JavaVM, +}; + +use crate::tpm::core::error::TpmError; + +pub(crate) mod key_generation; +pub(crate) mod key_store; + +/// This function gets the current Java VM running for the Android app. +/// Every Android app can have only 1 JVM running, so we can't just create a new one. +/// Normally it would be possible to just call the "JNI_GetCreatedJavaVMs" C function, but we can't link against it for some reason +/// so we have to load the symbol manually using the libloading crate. +pub(super) fn get_java_vm() -> Result { + // using jni_sys::JNI_GetCreatedJavaVMs crashes, bc the symbol is not loaded into the process for some reason + // instead we use libloading to load the symbol ourselves + pub type JniGetCreatedJavaVms = unsafe extern "system" fn( + vmBuf: *mut *mut jni::sys::JavaVM, + bufLen: jsize, + nVMs: *mut jsize, + ) -> jint; + pub const JNI_GET_JAVA_VMS_NAME: &[u8] = b"JNI_GetCreatedJavaVMs"; + + let lib = libloading::os::unix::Library::this(); + // let lib = unsafe { libloading::os::unix::Library::new("libart.so") } + // .map_err(|e| TpmError::InitializationError(format!("could not find libart.so: {e}")))?; + + let get_created_java_vms: JniGetCreatedJavaVms = unsafe { + *lib.get(JNI_GET_JAVA_VMS_NAME).map_err(|e| { + TpmError::InitializationError(format!("function JNI_GET_JAVA_VMS_NAME not loaded: {e}")) + })? + }; + + // now that we have the function, we can call it + let mut buffer = [std::ptr::null_mut::(); 1]; + let buffer_ptr = buffer.as_mut_ptr(); + let mut found_vms = 0; + let found_vm_ptr = &mut found_vms as *mut i32; + let res = unsafe { get_created_java_vms(buffer_ptr, 1, found_vm_ptr) }; + + if res != jni::sys::JNI_OK { + return Err(TpmError::InitializationError( + "Unable to get existing JVMs".to_owned(), + )); + } + + if found_vms == 0 { + return Err(TpmError::InitializationError( + "No running JVMs found".to_owned(), + )); + } + + let jvm = unsafe { + JavaVM::from_raw(buffer[0]).map_err(|e| TpmError::InitializationError(e.to_string()))? + }; + jvm.attach_current_thread() + .map_err(|e| TpmError::InitializationError(e.to_string()))?; + Ok(jvm) +} diff --git a/src/tpm/core/error.rs b/src/tpm/core/error.rs index ee6bdffb..a706ad4f 100644 --- a/src/tpm/core/error.rs +++ b/src/tpm/core/error.rs @@ -19,6 +19,8 @@ pub enum TpmError { InitializationError(String), /// Error indicating that an attempted operation is unsupported, containing a description. UnsupportedOperation(String), + /// Error indicating that an internal error occured, possibly caused by ffi bindings + InternalError(Box), } impl fmt::Display for TpmError { @@ -39,10 +41,18 @@ impl TpmError { TpmError::Win(err) => format!("Windows error: {}", err), TpmError::InitializationError(msg) => format!("Initialization error: {}", msg), TpmError::UnsupportedOperation(msg) => format!("Unsupported operation: {}", msg), + TpmError::InternalError(e) => format!("Internal error: {}", e), } } } +/// A trait to allow ergonomic conversions to TpmError +pub trait ToTpmError { + /// Wrap any error in TpmError::InternalError + /// the wrapped error can be accessed througth the error trait + fn err_internal(self) -> Result; +} + /// Enables `TpmError` to be treated as a trait object for any error (`dyn std::error::Error`). /// /// This implementation allows for compatibility with Rust's standard error handling mechanisms, diff --git a/src/tpm/core/instance.rs b/src/tpm/core/instance.rs index 69bc6dea..dc23d892 100644 --- a/src/tpm/core/instance.rs +++ b/src/tpm/core/instance.rs @@ -37,6 +37,8 @@ pub enum TpmType { #[derive(Eq, Hash, PartialEq, Clone, Debug)] #[cfg(feature = "android")] pub enum AndroidTpmType { + /// Android Provider using the Android Keystore API + Keystore, /// Represents the Samsung Knox security platform with TPM functionalities. Knox, } @@ -124,9 +126,14 @@ impl TpmInstance { } #[cfg(feature = "android")] TpmType::Android(tpm_type) => match tpm_type { + AndroidTpmType::Knox => Arc::new(Mutex::new( crate::tpm::android::knox::KnoxProvider::new(), )), + AndroidTpmType::Keystore => Arc::new(Mutex::new( + crate::tpm::android::AndroidProvider::new(key_id), + )), + }, TpmType::None => todo!(), } From 7556bc2bff34d7f28c7fee5ff723b82cca87db86 Mon Sep 17 00:00:00 2001 From: noah-pe <3001838@stud.hs-mannheim.de> Date: Tue, 4 Jun 2024 17:22:59 +0200 Subject: [PATCH 063/121] merge conflict fixed, only 1 robusta_jni --- Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 8f131d6f..7eeeb7db 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,7 +36,6 @@ win = ["tpm", "windows"] yubi = ["hsm", "yubikey"] [dependencies] -robusta_jni = "0.2" jni = "0.20.0" anyhow = "*" async-std = "*" From 655a0e7f2815a20852bf05bc30b096f711379c2e Mon Sep 17 00:00:00 2001 From: MasterChief06 <164910544+MasterChief06@users.noreply.github.com> Date: Wed, 5 Jun 2024 09:27:57 +0200 Subject: [PATCH 064/121] Licence Doc.md added MIT Licence --- Documentation/Doc.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/Documentation/Doc.md b/Documentation/Doc.md index 38797c56..6db62e79 100644 --- a/Documentation/Doc.md +++ b/Documentation/Doc.md @@ -248,6 +248,27 @@ Example: ## Open Source Project ### License +MIT License + +Copyright (c) 2024 Vulcan's Limes + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. ### Issue Guide From ae9463c3d016548b40bf4898d530ceee4d40789f Mon Sep 17 00:00:00 2001 From: MasterChief06 <164910544+MasterChief06@users.noreply.github.com> Date: Wed, 5 Jun 2024 09:33:37 +0200 Subject: [PATCH 065/121] Update Doc.md MIT Licence Link added --- Documentation/Doc.md | 21 +-------------------- 1 file changed, 1 insertion(+), 20 deletions(-) diff --git a/Documentation/Doc.md b/Documentation/Doc.md index 6db62e79..045ba702 100644 --- a/Documentation/Doc.md +++ b/Documentation/Doc.md @@ -249,26 +249,7 @@ Example: ### License MIT License - -Copyright (c) 2024 Vulcan's Limes - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +[LICENSE](https://github.com/cep-sose2024/rust-crypto-knox/blob/4989f3eb36552537e00fe58cbd29fa95312a6892/LICENSE) ### Issue Guide From e4408bc9fe751fa9d94386ecb5bb828c927d5658 Mon Sep 17 00:00:00 2001 From: MasterChief06 <164910544+MasterChief06@users.noreply.github.com> Date: Wed, 5 Jun 2024 10:09:03 +0200 Subject: [PATCH 066/121] Update Componentdiagram Doc.md added componentdiagram describtion --- Documentation/Doc.md | 46 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/Documentation/Doc.md b/Documentation/Doc.md index 045ba702..b86e5d3d 100644 --- a/Documentation/Doc.md +++ b/Documentation/Doc.md @@ -56,6 +56,52 @@ The Repository contains a Wrapper that is used to perform cryptographic operatio ![component diagram](images/component_diagram.jpg) ### Explanation +## Summary + +This project offers a comprehensive solution for cryptographic operations in Android and Rust, ensuring security and efficiency. The `CryptoManager`, `RustDef`, and `interface.rs` modules work together to ensure cryptographic keys are securely generated, stored, and used, while providing seamless integration between Java and Rust. + +## Components +### 1. Component Diagram + +The component diagram illustrates the relationships and interactions between the different classes and modules of the project. Here are the main components: + +- **CryptoManager**: Responsible for key generation, storage, encryption, and decryption in the Android environment. +- **RustDef**: Acts as a bridge between Java and Rust, ensuring cryptographic functions implemented in Rust can be called from Java. +- **interface.rs**: Contains the Rust implementation of cryptographic functions and exposes them to the Java environment. + +### 2. CryptoManager.java + +The `CryptoManager` class handles cryptographic operations within the Android environment. It provides functions for generating and storing symmetric and asymmetric keys in the Android Keystore, as well as for encrypting and decrypting data. Additionally, it enables signing and verifying data using keys stored in the Keystore. + +**Key Functions:** +- `genKey(String key_id, String keyGenInfo)`: Generates a new symmetric key and stores it in the Android Keystore. +- `encryptData(byte[] data)`: Encrypts the given data. +- `decryptData(byte[] encryptedData)`: Decrypts the given encrypted data. +- `generateKeyPair(String key_id, String keyGenInfo)`: Generates a new asymmetric key pair and stores it in the Android Keystore. +- `signData(byte[] data)`: Signs the given data. +- `verifySignature(byte[] data, byte[] signedBytes)`: Verifies the signature of the given data. + +### 3. RustDef.java + +The `RustDef` class defines the interface to the cryptographic functions implemented in Rust. It loads the native library and declares the native methods implemented in Rust. + +**Key Functions:** +- `getName()`: Retrieves the name of the project. +- `encryptMessage(String message)`: Encrypts a message using the Rust library. +- `decryptMessage(String encryptedMessage)`: Decrypts a message using the Rust library. + +### 4. interface.rs + +This module contains the implementation of cryptographic functions in Rust and exposes them to the Java environment. The functions in `interface.rs` are designed to perform cryptographic operations efficiently and securely, providing an interface for use in Android applications. + +**Key Functions:** +- `get_name() -> &'static str`: Returns the name of the project. +- `encrypt_message(message: &str) -> Result>`: Encrypts a message. +- `decrypt_message(encrypted_message: &str) -> Result>`: Decrypts a message. + +## Usage + +To integrate this project into your own, ensure all dependencies are correctly set up, including the Rust libraries and Android Keystore configuration. Follow the method calls provided in the classes and modules to utilize the cryptographic functions in your application. ### Abstraction Layer From 46078c38ae441b999a8b601f8b657256bc548c8f Mon Sep 17 00:00:00 2001 From: MasterChief06 <164910544+MasterChief06@users.noreply.github.com> Date: Wed, 5 Jun 2024 10:20:52 +0200 Subject: [PATCH 067/121] Update Componentdia desc. Doc.md Update Componentdiagram descr. cleanUp --- Documentation/Doc.md | 40 +++------------------------------------- 1 file changed, 3 insertions(+), 37 deletions(-) diff --git a/Documentation/Doc.md b/Documentation/Doc.md index b86e5d3d..99121089 100644 --- a/Documentation/Doc.md +++ b/Documentation/Doc.md @@ -55,56 +55,22 @@ The Repository contains a Wrapper that is used to perform cryptographic operatio ![component diagram](images/component_diagram.jpg) -### Explanation -## Summary - -This project offers a comprehensive solution for cryptographic operations in Android and Rust, ensuring security and efficiency. The `CryptoManager`, `RustDef`, and `interface.rs` modules work together to ensure cryptographic keys are securely generated, stored, and used, while providing seamless integration between Java and Rust. ## Components -### 1. Component Diagram The component diagram illustrates the relationships and interactions between the different classes and modules of the project. Here are the main components: -- **CryptoManager**: Responsible for key generation, storage, encryption, and decryption in the Android environment. -- **RustDef**: Acts as a bridge between Java and Rust, ensuring cryptographic functions implemented in Rust can be called from Java. -- **interface.rs**: Contains the Rust implementation of cryptographic functions and exposes them to the Java environment. - -### 2. CryptoManager.java - +- **CryptoManager**: The `CryptoManager` class handles cryptographic operations within the Android environment. It provides functions for generating and storing symmetric and asymmetric keys in the Android Keystore, as well as for encrypting and decrypting data. Additionally, it enables signing and verifying data using keys stored in the Keystore. -**Key Functions:** -- `genKey(String key_id, String keyGenInfo)`: Generates a new symmetric key and stores it in the Android Keystore. -- `encryptData(byte[] data)`: Encrypts the given data. -- `decryptData(byte[] encryptedData)`: Decrypts the given encrypted data. -- `generateKeyPair(String key_id, String keyGenInfo)`: Generates a new asymmetric key pair and stores it in the Android Keystore. -- `signData(byte[] data)`: Signs the given data. -- `verifySignature(byte[] data, byte[] signedBytes)`: Verifies the signature of the given data. - -### 3. RustDef.java +- **RustDef**: The `RustDef` class defines the interface to the cryptographic functions implemented in Rust. It loads the native library and declares the native methods implemented in Rust. -**Key Functions:** -- `getName()`: Retrieves the name of the project. -- `encryptMessage(String message)`: Encrypts a message using the Rust library. -- `decryptMessage(String encryptedMessage)`: Decrypts a message using the Rust library. - -### 4. interface.rs +- **interface.rs**: This module contains the implementation of cryptographic functions in Rust and exposes them to the Java environment. The functions in `interface.rs` are designed to perform cryptographic operations efficiently and securely, providing an interface for use in Android applications. -**Key Functions:** -- `get_name() -> &'static str`: Returns the name of the project. -- `encrypt_message(message: &str) -> Result>`: Encrypts a message. -- `decrypt_message(encrypted_message: &str) -> Result>`: Decrypts a message. - -## Usage - -To integrate this project into your own, ensure all dependencies are correctly set up, including the Rust libraries and Android Keystore configuration. Follow the method calls provided in the classes and modules to utilize the cryptographic functions in your application. - -### Abstraction Layer - ### Libraries - #### JNI From d4bd25c65ca3f6f4d8b903bb7dd28acbc9e44410 Mon Sep 17 00:00:00 2001 From: noah-pe <164938052+noah-pe@users.noreply.github.com> Date: Wed, 5 Jun 2024 10:39:24 +0200 Subject: [PATCH 068/121] added "comprehensive overview" to doc --- Documentation/Doc.md | 58 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 55 insertions(+), 3 deletions(-) diff --git a/Documentation/Doc.md b/Documentation/Doc.md index 99121089..df237468 100644 --- a/Documentation/Doc.md +++ b/Documentation/Doc.md @@ -10,7 +10,7 @@ - [Libraries](#libraries) 3. [Installation Guide](#installation-guide) - [Required Software](#required-software) -4. [Implementations](#implementations) +4. [Comprehensive Overview](#comprehensive-overview) - [Supported Devices](#supported-devices) - [Devices We Tested On](#devices-we-tested-on) - [Performance](#performance) @@ -169,19 +169,71 @@ The easiest way to do that is by specifying the library path when building like With that, you should have everything complete and compiled the project from scratch. -## Implementations +## Comprehensive Overview ### Supported Devices +This wrapper should work for all Android devices equiped with a SE, but the focus for our group is specifically on smartphones using Samsung Knox Vault. Therefore, testing will only be done using a Samsung smartphone, and in case there are incompatibilities between device manufacturers, Samsung will take priority. An up-to-date list of devices equipped with Knox Vault can be found [here](https://www.samsungknox.com/en/knox-platform/supported-devices) after selecting Knox Vault in the filter options. As of April 2024, the following devices are equipped with Knox Vault: -### Devices We Tested On +Smartphones +- Samsung Galaxy A 35 / A55 +- Samsung Galaxy S 22 / S23 / S 24 + - the plus / ultra versions as well +- Samsung Galaxy Z Flip 3 / 4 / 5 +- Samsung Galaxy Z Fold 3 / 4 / 5 +- Samsung Galaxy X Cover 7 (Enterprise Edition) + +Tablets +- Galaxy Tab S 8 / S 9 + - the plus, ultra, FE and 5G versions as well +- Galaxy Tab Active 5 (Enterprise Edition) + - the 5G version as well + +We have received a Samsung S22 from j&s-soft and have used this device for testing purposes througout the project. ### Performance +After an extensive performance test, we can conclude that data encryption using AES-256-CBC on the Knox Vault Chip occurs at a rate of approximately 0.11 MB/s. The test showed a variance of 0.58 s². ### Feature List +Our project supports the following features: + +- **Saving keys in the strongbox**: All generated keys are stored securely within the strongbox. +- **Encrypt and Decrypt Data**: Utilizing symmetric encryption, our system ensures data confidentiality through encryption and decryption mechanisms. +- **Sign and Verify Data**: We provide functionality for both symmetric and asymmetric signing and verification, ensuring data integrity and authenticity. + +In the following chapter, you will find detailed information on the supported algorithms. ### Supported Algorithms +We have provided a list of the supported Algorithms of our project: + +| Algorithm Type | Details | +|-------------------|----------------------------------------------| +| **RSA** | RSA;512;SHA-256;PKCS1 | +| | RSA;1024;SHA-256;PKCS1 | +| | RSA;2048;SHA-256;PKCS1 | +| | RSA;3072;SHA-256;PKCS1 | +| | RSA;4096;SHA-256;PKCS1 | +| | RSA;8192;SHA-256;PKCS1 | +| **ECC** | EC;secp256r1;SHA-256 | +| | EC;secp384r1;SHA-256 | +| | EC;secp521r1;SHA-256 | +| **3DES** | DESede;168;CBC;PKCS7Padding | +| **AES** | AES;128;GCM;NoPadding | +| | AES;128;CBC;PKCS7Padding | +| | AES;128;CTR;NoPadding | +| | AES;192;GCM;NoPadding | +| | AES;192;CBC;PKCS7Padding | +| | AES;192;CTR;NoPadding | +| | AES;256;GCM;NoPadding | +| | AES;256;CBC;PKCS7Padding | +| | AES;256;CTR;NoPadding | + ### Out of Scope +In order to finish this project in the given time, we decided to +mark the following as out of scope: +- tablets +- attestation +- asmmetric encryption / decryption ## Implementation From b39167ea255d403635fcb3e290a26ea80b0607eb Mon Sep 17 00:00:00 2001 From: ErikFribus Date: Thu, 6 Jun 2024 08:11:54 +0200 Subject: [PATCH 069/121] Updated RustDef.java & CryptoManager.java: Updated code documentation and code. --- src/tpm/android/knox/java/CryptoManager.java | 9 +++++---- src/tpm/android/knox/java/RustDef.java | 9 +++------ 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/tpm/android/knox/java/CryptoManager.java b/src/tpm/android/knox/java/CryptoManager.java index dece854b..5a4cde73 100644 --- a/src/tpm/android/knox/java/CryptoManager.java +++ b/src/tpm/android/knox/java/CryptoManager.java @@ -1,6 +1,5 @@ package tpm.android.knox.java; -import android.os.Build; import android.security.keystore.KeyGenParameterSpec; import android.security.keystore.KeyInfo; import android.security.keystore.KeyProperties; @@ -11,21 +10,18 @@ import java.security.InvalidKeyException; import java.security.Key; import java.security.KeyFactory; -import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.PrivateKey; -import java.security.PublicKey; import java.security.Signature; import java.security.SignatureException; import java.security.UnrecoverableKeyException; import java.security.cert.CertificateException; import java.security.spec.ECGenParameterSpec; import java.security.spec.InvalidKeySpecException; -import java.util.Arrays; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; @@ -37,6 +33,11 @@ import javax.crypto.spec.GCMParameterSpec; import javax.crypto.spec.IvParameterSpec; +/** + * This class provides all the methods we need for communication with the keystore and cryptographic systems. + * It loads the keystore, generates and loads keys, encrypts and decrypts and signs and verifies. + * + */ public class CryptoManager { private static final String ANDROID_KEY_STORE = "AndroidKeyStore"; private final KeyStore keyStore; diff --git a/src/tpm/android/knox/java/RustDef.java b/src/tpm/android/knox/java/RustDef.java index abfcb2d5..a952776b 100644 --- a/src/tpm/android/knox/java/RustDef.java +++ b/src/tpm/android/knox/java/RustDef.java @@ -1,4 +1,4 @@ -package com.example.vulcans_limes; +package tpm.android.knox.java; import java.io.IOException; @@ -66,10 +66,7 @@ class RustDef { */ static native ArrayList special(ArrayList input1, int input2); - /** - * Proof of concept method - shows callback from Rust to a java method - * ONLY USE FOR TESTING - */ + static native String callRust(); static native byte[] demoEncrypt(byte[] data); @@ -82,7 +79,7 @@ class RustDef { static native byte[] demoSign(byte[] data); - static native boolean demoVerify(byte[] data); + static native boolean demoVerify(byte[] data, byte[] signed_data); static native void demoLoad(String key_id); From d585dfea9906c0948ebee7cd8f5bc579f05a5905 Mon Sep 17 00:00:00 2001 From: ErikFribus Date: Thu, 6 Jun 2024 08:13:02 +0200 Subject: [PATCH 070/121] Generated and added JavaDoc to Documents folder --- Documentation/JavaDoc/allclasses-index.html | 67 + Documentation/JavaDoc/allpackages-index.html | 63 + Documentation/JavaDoc/copy.svg | 33 + Documentation/JavaDoc/element-list | 1 + Documentation/JavaDoc/help-doc.html | 177 +++ Documentation/JavaDoc/index-all.html | 119 ++ Documentation/JavaDoc/index.html | 26 + .../JavaDoc/legal/ASSEMBLY_EXCEPTION | 27 + Documentation/JavaDoc/legal/jquery.md | 72 + Documentation/JavaDoc/legal/jqueryUI.md | 49 + Documentation/JavaDoc/link.svg | 31 + Documentation/JavaDoc/member-search-index.js | 1 + Documentation/JavaDoc/module-search-index.js | 1 + Documentation/JavaDoc/overview-tree.html | 70 + Documentation/JavaDoc/package-search-index.js | 1 + Documentation/JavaDoc/resources/glass.png | Bin 0 -> 499 bytes Documentation/JavaDoc/resources/x.png | Bin 0 -> 394 bytes .../JavaDoc/script-dir/jquery-3.6.1.min.js | 2 + .../JavaDoc/script-dir/jquery-ui.min.css | 6 + .../JavaDoc/script-dir/jquery-ui.min.js | 6 + Documentation/JavaDoc/script.js | 253 ++++ Documentation/JavaDoc/search-page.js | 284 ++++ Documentation/JavaDoc/search.html | 71 + Documentation/JavaDoc/search.js | 458 ++++++ Documentation/JavaDoc/stylesheet.css | 1272 +++++++++++++++++ Documentation/JavaDoc/tag-search-index.js | 1 + .../tpm/android/knox/java/CryptoManager.html | 476 ++++++ .../android/knox/java/package-summary.html | 92 ++ .../tpm/android/knox/java/package-tree.html | 66 + Documentation/JavaDoc/type-search-index.js | 1 + 30 files changed, 3726 insertions(+) create mode 100644 Documentation/JavaDoc/allclasses-index.html create mode 100644 Documentation/JavaDoc/allpackages-index.html create mode 100644 Documentation/JavaDoc/copy.svg create mode 100644 Documentation/JavaDoc/element-list create mode 100644 Documentation/JavaDoc/help-doc.html create mode 100644 Documentation/JavaDoc/index-all.html create mode 100644 Documentation/JavaDoc/index.html create mode 100644 Documentation/JavaDoc/legal/ASSEMBLY_EXCEPTION create mode 100644 Documentation/JavaDoc/legal/jquery.md create mode 100644 Documentation/JavaDoc/legal/jqueryUI.md create mode 100644 Documentation/JavaDoc/link.svg create mode 100644 Documentation/JavaDoc/member-search-index.js create mode 100644 Documentation/JavaDoc/module-search-index.js create mode 100644 Documentation/JavaDoc/overview-tree.html create mode 100644 Documentation/JavaDoc/package-search-index.js create mode 100644 Documentation/JavaDoc/resources/glass.png create mode 100644 Documentation/JavaDoc/resources/x.png create mode 100644 Documentation/JavaDoc/script-dir/jquery-3.6.1.min.js create mode 100644 Documentation/JavaDoc/script-dir/jquery-ui.min.css create mode 100644 Documentation/JavaDoc/script-dir/jquery-ui.min.js create mode 100644 Documentation/JavaDoc/script.js create mode 100644 Documentation/JavaDoc/search-page.js create mode 100644 Documentation/JavaDoc/search.html create mode 100644 Documentation/JavaDoc/search.js create mode 100644 Documentation/JavaDoc/stylesheet.css create mode 100644 Documentation/JavaDoc/tag-search-index.js create mode 100644 Documentation/JavaDoc/tpm/android/knox/java/CryptoManager.html create mode 100644 Documentation/JavaDoc/tpm/android/knox/java/package-summary.html create mode 100644 Documentation/JavaDoc/tpm/android/knox/java/package-tree.html create mode 100644 Documentation/JavaDoc/type-search-index.js diff --git a/Documentation/JavaDoc/allclasses-index.html b/Documentation/JavaDoc/allclasses-index.html new file mode 100644 index 00000000..b85d5aa1 --- /dev/null +++ b/Documentation/JavaDoc/allclasses-index.html @@ -0,0 +1,67 @@ + + + + +All Classes and Interfaces + + + + + + + + + + + + + +

JavaScript is disabled on your browser.
+ +
+ +
+
+
+

All Classes and Interfaces

+
+
+
Classes
+
+
Class
+
Description
+ +
+
This class provides all the methods we need for communication with the keystore and cryptographic systems.
+
+
+
+
+
+
+ + diff --git a/Documentation/JavaDoc/allpackages-index.html b/Documentation/JavaDoc/allpackages-index.html new file mode 100644 index 00000000..26dfb9b6 --- /dev/null +++ b/Documentation/JavaDoc/allpackages-index.html @@ -0,0 +1,63 @@ + + + + +All Packages + + + + + + + + + + + + + + +
+ +
+
+
+

All Packages

+
+
Package Summary
+
+
Package
+
Description
+ +
 
+
+
+
+
+ + diff --git a/Documentation/JavaDoc/copy.svg b/Documentation/JavaDoc/copy.svg new file mode 100644 index 00000000..7c46ab15 --- /dev/null +++ b/Documentation/JavaDoc/copy.svg @@ -0,0 +1,33 @@ + + + + + + + + diff --git a/Documentation/JavaDoc/element-list b/Documentation/JavaDoc/element-list new file mode 100644 index 00000000..f14328b9 --- /dev/null +++ b/Documentation/JavaDoc/element-list @@ -0,0 +1 @@ +tpm.android.knox.java diff --git a/Documentation/JavaDoc/help-doc.html b/Documentation/JavaDoc/help-doc.html new file mode 100644 index 00000000..56d0bd9c --- /dev/null +++ b/Documentation/JavaDoc/help-doc.html @@ -0,0 +1,177 @@ + + + + +API Help + + + + + + + + + + + + + + +
+ +
+
+

JavaDoc Help

+ +
+
+

Navigation

+Starting from the Overview page, you can browse the documentation using the links in each page, and in the navigation bar at the top of each page. The Index and Search box allow you to navigate to specific declarations and summary pages, including: All Packages, All Classes and Interfaces + +
+
+
+

Kinds of Pages

+The following sections describe the different kinds of pages in this collection. +
+

Package

+

Each package has a page that contains a list of its classes and interfaces, with a summary for each. These pages may contain the following categories:

+
    +
  • Interfaces
  • +
  • Classes
  • +
  • Enum Classes
  • +
  • Exception Classes
  • +
  • Annotation Interfaces
  • +
+
+
+

Class or Interface

+

Each class, interface, nested class and nested interface has its own separate page. Each of these pages has three sections consisting of a declaration and description, member summary tables, and detailed member descriptions. Entries in each of these sections are omitted if they are empty or not applicable.

+
    +
  • Class Inheritance Diagram
  • +
  • Direct Subclasses
  • +
  • All Known Subinterfaces
  • +
  • All Known Implementing Classes
  • +
  • Class or Interface Declaration
  • +
  • Class or Interface Description
  • +
+
+
    +
  • Nested Class Summary
  • +
  • Enum Constant Summary
  • +
  • Field Summary
  • +
  • Property Summary
  • +
  • Constructor Summary
  • +
  • Method Summary
  • +
  • Required Element Summary
  • +
  • Optional Element Summary
  • +
+
+
    +
  • Enum Constant Details
  • +
  • Field Details
  • +
  • Property Details
  • +
  • Constructor Details
  • +
  • Method Details
  • +
  • Element Details
  • +
+

Note: Annotation interfaces have required and optional elements, but not methods. Only enum classes have enum constants. The components of a record class are displayed as part of the declaration of the record class. Properties are a feature of JavaFX.

+

The summary entries are alphabetical, while the detailed descriptions are in the order they appear in the source code. This preserves the logical groupings established by the programmer.

+
+
+

Other Files

+

Packages and modules may contain pages with additional information related to the declarations nearby.

+
+
+

Tree (Class Hierarchy)

+

There is a Class Hierarchy page for all packages, plus a hierarchy for each package. Each hierarchy page contains a list of classes and a list of interfaces. Classes are organized by inheritance structure starting with java.lang.Object. Interfaces do not inherit from java.lang.Object.

+
    +
  • When viewing the Overview page, clicking on TREE displays the hierarchy for all packages.
  • +
  • When viewing a particular package, class or interface page, clicking on TREE displays the hierarchy for only that package.
  • +
+
+
+

All Packages

+

The All Packages page contains an alphabetic index of all packages contained in the documentation.

+
+
+

All Classes and Interfaces

+

The All Classes and Interfaces page contains an alphabetic index of all classes and interfaces contained in the documentation, including annotation interfaces, enum classes, and record classes.

+
+
+

Index

+

The Index contains an alphabetic index of all classes, interfaces, constructors, methods, and fields in the documentation, as well as summary pages such as All Packages, All Classes and Interfaces.

+
+
+
+This help file applies to API documentation generated by the standard doclet.
+
+
+ + diff --git a/Documentation/JavaDoc/index-all.html b/Documentation/JavaDoc/index-all.html new file mode 100644 index 00000000..01c7fc62 --- /dev/null +++ b/Documentation/JavaDoc/index-all.html @@ -0,0 +1,119 @@ + + + + +Index + + + + + + + + + + + + + + +
+ +
+
+
+

Index

+
+C D E G L S T V 
All Classes and Interfaces|All Packages +

C

+
+
CryptoManager - Class in tpm.android.knox.java
+
+
This class provides all the methods we need for communication with the keystore and cryptographic systems.
+
+
CryptoManager() - Constructor for class tpm.android.knox.java.CryptoManager
+
+
Constructs a new instance of CryptoManager with the default Android KeyStore.
+
+
+

D

+
+
decryptData(byte[]) - Method in class tpm.android.knox.java.CryptoManager
+
+
Decrypts the given encrypted data using a symmetric key stored in the Android KeyStore.
+
+
+

E

+
+
encryptData(byte[]) - Method in class tpm.android.knox.java.CryptoManager
+
+
Encrypts the given data using a symmetric key stored in the Android KeyStore.
+
+
+

G

+
+
generateKeyPair(String, String) - Method in class tpm.android.knox.java.CryptoManager
+
+
Generates a new asymmetric key pair and saves it into the Android KeyStore.
+
+
genKey(String, String) - Method in class tpm.android.knox.java.CryptoManager
+
+
Generates a new symmetric key and saves it into the Android KeyStore.
+
+
+

L

+
+
loadKey(String) - Method in class tpm.android.knox.java.CryptoManager
+
+
Sets the `KEY_NAME` to the provided key identifier.
+
+
+

S

+
+
signData(byte[]) - Method in class tpm.android.knox.java.CryptoManager
+
+
Signs the given data using a private key stored in the Android KeyStore.
+
+
+

T

+
+
tpm.android.knox.java - package tpm.android.knox.java
+
 
+
+

V

+
+
verifySignature(byte[], byte[]) - Method in class tpm.android.knox.java.CryptoManager
+
+
Verifies the given data against a signature produced by a private key stored in the Android KeyStore.
+
+
+C D E G L S T V 
All Classes and Interfaces|All Packages
+
+
+ + diff --git a/Documentation/JavaDoc/index.html b/Documentation/JavaDoc/index.html new file mode 100644 index 00000000..39d85190 --- /dev/null +++ b/Documentation/JavaDoc/index.html @@ -0,0 +1,26 @@ + + + + +Generated Documentation (Untitled) + + + + + + + + + + + +
+ +

tpm/android/knox/java/package-summary.html

+
+ + diff --git a/Documentation/JavaDoc/legal/ASSEMBLY_EXCEPTION b/Documentation/JavaDoc/legal/ASSEMBLY_EXCEPTION new file mode 100644 index 00000000..42966666 --- /dev/null +++ b/Documentation/JavaDoc/legal/ASSEMBLY_EXCEPTION @@ -0,0 +1,27 @@ + +OPENJDK ASSEMBLY EXCEPTION + +The OpenJDK source code made available by Oracle America, Inc. (Oracle) at +openjdk.org ("OpenJDK Code") is distributed under the terms of the GNU +General Public License version 2 +only ("GPL2"), with the following clarification and special exception. + + Linking this OpenJDK Code statically or dynamically with other code + is making a combined work based on this library. Thus, the terms + and conditions of GPL2 cover the whole combination. + + As a special exception, Oracle gives you permission to link this + OpenJDK Code with certain code licensed by Oracle as indicated at + https://openjdk.org/legal/exception-modules-2007-05-08.html + ("Designated Exception Modules") to produce an executable, + regardless of the license terms of the Designated Exception Modules, + and to copy and distribute the resulting executable under GPL2, + provided that the Designated Exception Modules continue to be + governed by the licenses under which they were offered by Oracle. + +As such, it allows licensees and sublicensees of Oracle's GPL2 OpenJDK Code +to build an executable that includes those portions of necessary code that +Oracle could not provide under GPL2 (or that Oracle has provided under GPL2 +with the Classpath exception). If you modify or add to the OpenJDK code, +that new GPL2 code may still be combined with Designated Exception Modules +if the new code is made subject to this exception by its copyright holder. diff --git a/Documentation/JavaDoc/legal/jquery.md b/Documentation/JavaDoc/legal/jquery.md new file mode 100644 index 00000000..d468b318 --- /dev/null +++ b/Documentation/JavaDoc/legal/jquery.md @@ -0,0 +1,72 @@ +## jQuery v3.6.1 + +### jQuery License +``` +jQuery v 3.6.1 +Copyright OpenJS Foundation and other contributors, https://openjsf.org/ + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +****************************************** + +The jQuery JavaScript Library v3.6.1 also includes Sizzle.js + +Sizzle.js includes the following license: + +Copyright JS Foundation and other contributors, https://js.foundation/ + +This software consists of voluntary contributions made by many +individuals. For exact contribution history, see the revision history +available at https://github.com/jquery/sizzle + +The following license applies to all parts of this software except as +documented below: + +==== + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +==== + +All files located in the node_modules and external directories are +externally maintained libraries used by this software which have their +own licenses; we recommend you read them, as their terms may differ from +the terms above. + +********************* + +``` diff --git a/Documentation/JavaDoc/legal/jqueryUI.md b/Documentation/JavaDoc/legal/jqueryUI.md new file mode 100644 index 00000000..8bda9d7a --- /dev/null +++ b/Documentation/JavaDoc/legal/jqueryUI.md @@ -0,0 +1,49 @@ +## jQuery UI v1.13.2 + +### jQuery UI License +``` +Copyright jQuery Foundation and other contributors, https://jquery.org/ + +This software consists of voluntary contributions made by many +individuals. For exact contribution history, see the revision history +available at https://github.com/jquery/jquery-ui + +The following license applies to all parts of this software except as +documented below: + +==== + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +==== + +Copyright and related rights for sample code are waived via CC0. Sample +code is defined as all source code contained within the demos directory. + +CC0: http://creativecommons.org/publicdomain/zero/1.0/ + +==== + +All files located in the node_modules and external directories are +externally maintained libraries used by this software which have their +own licenses; we recommend you read them, as their terms may differ from +the terms above. + +``` diff --git a/Documentation/JavaDoc/link.svg b/Documentation/JavaDoc/link.svg new file mode 100644 index 00000000..7ccc5ed0 --- /dev/null +++ b/Documentation/JavaDoc/link.svg @@ -0,0 +1,31 @@ + + + + + + + + diff --git a/Documentation/JavaDoc/member-search-index.js b/Documentation/JavaDoc/member-search-index.js new file mode 100644 index 00000000..3944b902 --- /dev/null +++ b/Documentation/JavaDoc/member-search-index.js @@ -0,0 +1 @@ +memberSearchIndex = [{"p":"tpm.android.knox.java","c":"CryptoManager","l":"CryptoManager()","u":"%3Cinit%3E()"},{"p":"tpm.android.knox.java","c":"CryptoManager","l":"decryptData(byte[])"},{"p":"tpm.android.knox.java","c":"CryptoManager","l":"encryptData(byte[])"},{"p":"tpm.android.knox.java","c":"CryptoManager","l":"generateKeyPair(String, String)","u":"generateKeyPair(java.lang.String,java.lang.String)"},{"p":"tpm.android.knox.java","c":"CryptoManager","l":"genKey(String, String)","u":"genKey(java.lang.String,java.lang.String)"},{"p":"tpm.android.knox.java","c":"CryptoManager","l":"loadKey(String)","u":"loadKey(java.lang.String)"},{"p":"tpm.android.knox.java","c":"CryptoManager","l":"signData(byte[])"},{"p":"tpm.android.knox.java","c":"CryptoManager","l":"verifySignature(byte[], byte[])","u":"verifySignature(byte[],byte[])"}];updateSearchResults(); \ No newline at end of file diff --git a/Documentation/JavaDoc/module-search-index.js b/Documentation/JavaDoc/module-search-index.js new file mode 100644 index 00000000..0d59754f --- /dev/null +++ b/Documentation/JavaDoc/module-search-index.js @@ -0,0 +1 @@ +moduleSearchIndex = [];updateSearchResults(); \ No newline at end of file diff --git a/Documentation/JavaDoc/overview-tree.html b/Documentation/JavaDoc/overview-tree.html new file mode 100644 index 00000000..9a3654d4 --- /dev/null +++ b/Documentation/JavaDoc/overview-tree.html @@ -0,0 +1,70 @@ + + + + +Class Hierarchy + + + + + + + + + + + + + + +
+ +
+
+
+

Hierarchy For All Packages

+
+Package Hierarchies: + +
+

Class Hierarchy

+ +
+
+
+
+ + diff --git a/Documentation/JavaDoc/package-search-index.js b/Documentation/JavaDoc/package-search-index.js new file mode 100644 index 00000000..61a4b15a --- /dev/null +++ b/Documentation/JavaDoc/package-search-index.js @@ -0,0 +1 @@ +packageSearchIndex = [{"l":"All Packages","u":"allpackages-index.html"},{"l":"tpm.android.knox.java"}];updateSearchResults(); \ No newline at end of file diff --git a/Documentation/JavaDoc/resources/glass.png b/Documentation/JavaDoc/resources/glass.png new file mode 100644 index 0000000000000000000000000000000000000000..a7f591f467a1c0c949bbc510156a0c1afb860a6e GIT binary patch literal 499 zcmVJoRsvExf%rEN>jUL}qZ_~k#FbE+Q;{`;0FZwVNX2n-^JoI; zP;4#$8DIy*Yk-P>VN(DUKmPse7mx+ExD4O|;?E5D0Z5($mjO3`*anwQU^s{ZDK#Lz zj>~{qyaIx5K!t%=G&2IJNzg!ChRpyLkO7}Ry!QaotAHAMpbB3AF(}|_f!G-oI|uK6 z`id_dumai5K%C3Y$;tKS_iqMPHg<*|-@e`liWLAggVM!zAP#@l;=c>S03;{#04Z~5 zN_+ss=Yg6*hTr59mzMwZ@+l~q!+?ft!fF66AXT#wWavHt30bZWFCK%!BNk}LN?0Hg z1VF_nfs`Lm^DjYZ1(1uD0u4CSIr)XAaqW6IT{!St5~1{i=i}zAy76p%_|w8rh@@c0Axr!ns=D-X+|*sY6!@wacG9%)Qn*O zl0sa739kT-&_?#oVxXF6tOnqTD)cZ}2vi$`ZU8RLAlo8=_z#*P3xI~i!lEh+Pdu-L zx{d*wgjtXbnGX_Yf@Tc7Q3YhLhPvc8noGJs2DA~1DySiA&6V{5JzFt ojAY1KXm~va;tU{v7C?Xj0BHw!K;2aXV*mgE07*qoM6N<$f;4TDA^-pY literal 0 HcmV?d00001 diff --git a/Documentation/JavaDoc/script-dir/jquery-3.6.1.min.js b/Documentation/JavaDoc/script-dir/jquery-3.6.1.min.js new file mode 100644 index 00000000..2c69bc90 --- /dev/null +++ b/Documentation/JavaDoc/script-dir/jquery-3.6.1.min.js @@ -0,0 +1,2 @@ +/*! jQuery v3.6.1 | (c) OpenJS Foundation and other contributors | jquery.org/license */ +!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],r=Object.getPrototypeOf,s=t.slice,g=t.flat?function(e){return t.flat.call(e)}:function(e){return t.concat.apply([],e)},u=t.push,i=t.indexOf,n={},o=n.toString,y=n.hasOwnProperty,a=y.toString,l=a.call(Object),v={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType&&"function"!=typeof e.item},x=function(e){return null!=e&&e===e.window},E=C.document,c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.6.1",S=function(e,t){return new S.fn.init(e,t)};function p(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp(F),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(p.childNodes),p.childNodes),t[p.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&v(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!N[t+" "]&&(!y||!y.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&(U.test(t)||z.test(t))){(f=ee.test(t)&&ve(e.parentNode)||e)===e&&d.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=S)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+xe(l[o]);c=l.join(",")}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){N(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return g(t.replace(B,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[S]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ye(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ve(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e&&e.namespaceURI,n=e&&(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:p;return r!=C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),p!=C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.scope=ce(function(e){return a.appendChild(e).appendChild(C.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=S,!C.getElementsByName||!C.getElementsByName(S).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],y=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&y.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||y.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+S+"-]").length||y.push("~="),(t=C.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||y.push("\\["+M+"*name"+M+"*="+M+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||y.push(":checked"),e.querySelectorAll("a#"+S+"+*").length||y.push(".#.+[+~]"),e.querySelectorAll("\\\f"),y.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&y.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&y.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&y.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),y.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",F)}),y=y.length&&new RegExp(y.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),v=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},j=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e==C||e.ownerDocument==p&&v(p,e)?-1:t==C||t.ownerDocument==p&&v(p,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==C?-1:t==C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]==p?-1:s[r]==p?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(T(e),d.matchesSelector&&E&&!N[t+" "]&&(!s||!s.test(t))&&(!y||!y.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,n,r){return m(n)?S.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?S.grep(e,function(e){return e===n!==r}):"string"!=typeof n?S.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(S.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||D,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:q.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof S?t[0]:t,S.merge(this,S.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),N.test(r[1])&&S.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(S):S.makeArray(e,this)}).prototype=S.fn,D=S(E);var L=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function O(e,t){while((e=e[t])&&1!==e.nodeType);return e}S.fn.extend({has:function(e){var t=S(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i;ce=E.createDocumentFragment().appendChild(E.createElement("div")),(fe=E.createElement("input")).setAttribute("type","radio"),fe.setAttribute("checked","checked"),fe.setAttribute("name","t"),ce.appendChild(fe),v.checkClone=ce.cloneNode(!0).cloneNode(!0).lastChild.checked,ce.innerHTML="",v.noCloneChecked=!!ce.cloneNode(!0).lastChild.defaultValue,ce.innerHTML="",v.option=!!ce.lastChild;var ge={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function ye(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?S.merge([e],n):n}function ve(e,t){for(var n=0,r=e.length;n",""]);var me=/<|&#?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function je(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&S(e).children("tbody")[0]||e}function De(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function qe(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Le(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var Ut,Xt=[],Vt=/(=)\?(?=&|$)|\?\?/;S.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Xt.pop()||S.expando+"_"+Ct.guid++;return this[e]=!0,e}}),S.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Vt.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Vt.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Vt,"$1"+r):!1!==e.jsonp&&(e.url+=(Et.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||S.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?S(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,Xt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),v.createHTMLDocument=((Ut=E.implementation.createHTMLDocument("").body).innerHTML="
",2===Ut.childNodes.length),S.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(v.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=N.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&S(o).remove(),S.merge([],i.childNodes)));var r,i,o},S.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(S.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},S.expr.pseudos.animated=function(t){return S.grep(S.timers,function(e){return t===e.elem}).length},S.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=S.css(e,"position"),c=S(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=S.css(e,"top"),u=S.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,S.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},S.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){S.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===S.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===S.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=S(e).offset()).top+=S.css(e,"borderTopWidth",!0),i.left+=S.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-S.css(r,"marginTop",!0),left:t.left-i.left-S.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===S.css(e,"position"))e=e.offsetParent;return e||re})}}),S.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;S.fn[t]=function(e){return B(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),S.each(["top","left"],function(e,n){S.cssHooks[n]=_e(v.pixelPosition,function(e,t){if(t)return t=Be(e,n),Pe.test(t)?S(e).position()[n]+"px":t})}),S.each({Height:"height",Width:"width"},function(a,s){S.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){S.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return B(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?S.css(e,t,i):S.style(e,t,n,i)},s,n?e:void 0,n)}})}),S.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){S.fn[t]=function(e){return this.on(t,e)}}),S.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),S.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){S.fn[n]=function(e,t){return 0",options:{classes:{},disabled:!1,create:null},_createWidget:function(t,e){e=x(e||this.defaultElement||this)[0],this.element=x(e),this.uuid=i++,this.eventNamespace="."+this.widgetName+this.uuid,this.bindings=x(),this.hoverable=x(),this.focusable=x(),this.classesElementLookup={},e!==this&&(x.data(e,this.widgetFullName,this),this._on(!0,this.element,{remove:function(t){t.target===e&&this.destroy()}}),this.document=x(e.style?e.ownerDocument:e.document||e),this.window=x(this.document[0].defaultView||this.document[0].parentWindow)),this.options=x.widget.extend({},this.options,this._getCreateOptions(),t),this._create(),this.options.disabled&&this._setOptionDisabled(this.options.disabled),this._trigger("create",null,this._getCreateEventData()),this._init()},_getCreateOptions:function(){return{}},_getCreateEventData:x.noop,_create:x.noop,_init:x.noop,destroy:function(){var i=this;this._destroy(),x.each(this.classesElementLookup,function(t,e){i._removeClass(e,t)}),this.element.off(this.eventNamespace).removeData(this.widgetFullName),this.widget().off(this.eventNamespace).removeAttr("aria-disabled"),this.bindings.off(this.eventNamespace)},_destroy:x.noop,widget:function(){return this.element},option:function(t,e){var i,s,n,o=t;if(0===arguments.length)return x.widget.extend({},this.options);if("string"==typeof t)if(o={},t=(i=t.split(".")).shift(),i.length){for(s=o[t]=x.widget.extend({},this.options[t]),n=0;n
"),i=e.children()[0];return x("body").append(e),t=i.offsetWidth,e.css("overflow","scroll"),t===(i=i.offsetWidth)&&(i=e[0].clientWidth),e.remove(),s=t-i},getScrollInfo:function(t){var e=t.isWindow||t.isDocument?"":t.element.css("overflow-x"),i=t.isWindow||t.isDocument?"":t.element.css("overflow-y"),e="scroll"===e||"auto"===e&&t.widthC(E(s),E(n))?o.important="horizontal":o.important="vertical",c.using.call(this,t,o)}),l.offset(x.extend(u,{using:t}))})},x.ui.position={fit:{left:function(t,e){var i=e.within,s=i.isWindow?i.scrollLeft:i.offset.left,n=i.width,o=t.left-e.collisionPosition.marginLeft,l=s-o,a=o+e.collisionWidth-n-s;e.collisionWidth>n?0n?0",delay:300,options:{icons:{submenu:"ui-icon-caret-1-e"},items:"> *",menus:"ul",position:{my:"left top",at:"right top"},role:"menu",blur:null,focus:null,select:null},_create:function(){this.activeMenu=this.element,this.mouseHandled=!1,this.lastMousePosition={x:null,y:null},this.element.uniqueId().attr({role:this.options.role,tabIndex:0}),this._addClass("ui-menu","ui-widget ui-widget-content"),this._on({"mousedown .ui-menu-item":function(t){t.preventDefault(),this._activateItem(t)},"click .ui-menu-item":function(t){var e=x(t.target),i=x(x.ui.safeActiveElement(this.document[0]));!this.mouseHandled&&e.not(".ui-state-disabled").length&&(this.select(t),t.isPropagationStopped()||(this.mouseHandled=!0),e.has(".ui-menu").length?this.expand(t):!this.element.is(":focus")&&i.closest(".ui-menu").length&&(this.element.trigger("focus",[!0]),this.active&&1===this.active.parents(".ui-menu").length&&clearTimeout(this.timer)))},"mouseenter .ui-menu-item":"_activateItem","mousemove .ui-menu-item":"_activateItem",mouseleave:"collapseAll","mouseleave .ui-menu":"collapseAll",focus:function(t,e){var i=this.active||this._menuItems().first();e||this.focus(t,i)},blur:function(t){this._delay(function(){x.contains(this.element[0],x.ui.safeActiveElement(this.document[0]))||this.collapseAll(t)})},keydown:"_keydown"}),this.refresh(),this._on(this.document,{click:function(t){this._closeOnDocumentClick(t)&&this.collapseAll(t,!0),this.mouseHandled=!1}})},_activateItem:function(t){var e,i;this.previousFilter||t.clientX===this.lastMousePosition.x&&t.clientY===this.lastMousePosition.y||(this.lastMousePosition={x:t.clientX,y:t.clientY},e=x(t.target).closest(".ui-menu-item"),i=x(t.currentTarget),e[0]===i[0]&&(i.is(".ui-state-active")||(this._removeClass(i.siblings().children(".ui-state-active"),null,"ui-state-active"),this.focus(t,i))))},_destroy:function(){var t=this.element.find(".ui-menu-item").removeAttr("role aria-disabled").children(".ui-menu-item-wrapper").removeUniqueId().removeAttr("tabIndex role aria-haspopup");this.element.removeAttr("aria-activedescendant").find(".ui-menu").addBack().removeAttr("role aria-labelledby aria-expanded aria-hidden aria-disabled tabIndex").removeUniqueId().show(),t.children().each(function(){var t=x(this);t.data("ui-menu-submenu-caret")&&t.remove()})},_keydown:function(t){var e,i,s,n=!0;switch(t.keyCode){case x.ui.keyCode.PAGE_UP:this.previousPage(t);break;case x.ui.keyCode.PAGE_DOWN:this.nextPage(t);break;case x.ui.keyCode.HOME:this._move("first","first",t);break;case x.ui.keyCode.END:this._move("last","last",t);break;case x.ui.keyCode.UP:this.previous(t);break;case x.ui.keyCode.DOWN:this.next(t);break;case x.ui.keyCode.LEFT:this.collapse(t);break;case x.ui.keyCode.RIGHT:this.active&&!this.active.is(".ui-state-disabled")&&this.expand(t);break;case x.ui.keyCode.ENTER:case x.ui.keyCode.SPACE:this._activate(t);break;case x.ui.keyCode.ESCAPE:this.collapse(t);break;default:e=this.previousFilter||"",s=n=!1,i=96<=t.keyCode&&t.keyCode<=105?(t.keyCode-96).toString():String.fromCharCode(t.keyCode),clearTimeout(this.filterTimer),i===e?s=!0:i=e+i,e=this._filterMenuItems(i),(e=s&&-1!==e.index(this.active.next())?this.active.nextAll(".ui-menu-item"):e).length||(i=String.fromCharCode(t.keyCode),e=this._filterMenuItems(i)),e.length?(this.focus(t,e),this.previousFilter=i,this.filterTimer=this._delay(function(){delete this.previousFilter},1e3)):delete this.previousFilter}n&&t.preventDefault()},_activate:function(t){this.active&&!this.active.is(".ui-state-disabled")&&(this.active.children("[aria-haspopup='true']").length?this.expand(t):this.select(t))},refresh:function(){var t,e,s=this,n=this.options.icons.submenu,i=this.element.find(this.options.menus);this._toggleClass("ui-menu-icons",null,!!this.element.find(".ui-icon").length),e=i.filter(":not(.ui-menu)").hide().attr({role:this.options.role,"aria-hidden":"true","aria-expanded":"false"}).each(function(){var t=x(this),e=t.prev(),i=x("").data("ui-menu-submenu-caret",!0);s._addClass(i,"ui-menu-icon","ui-icon "+n),e.attr("aria-haspopup","true").prepend(i),t.attr("aria-labelledby",e.attr("id"))}),this._addClass(e,"ui-menu","ui-widget ui-widget-content ui-front"),(t=i.add(this.element).find(this.options.items)).not(".ui-menu-item").each(function(){var t=x(this);s._isDivider(t)&&s._addClass(t,"ui-menu-divider","ui-widget-content")}),i=(e=t.not(".ui-menu-item, .ui-menu-divider")).children().not(".ui-menu").uniqueId().attr({tabIndex:-1,role:this._itemRole()}),this._addClass(e,"ui-menu-item")._addClass(i,"ui-menu-item-wrapper"),t.filter(".ui-state-disabled").attr("aria-disabled","true"),this.active&&!x.contains(this.element[0],this.active[0])&&this.blur()},_itemRole:function(){return{menu:"menuitem",listbox:"option"}[this.options.role]},_setOption:function(t,e){var i;"icons"===t&&(i=this.element.find(".ui-menu-icon"),this._removeClass(i,null,this.options.icons.submenu)._addClass(i,null,e.submenu)),this._super(t,e)},_setOptionDisabled:function(t){this._super(t),this.element.attr("aria-disabled",String(t)),this._toggleClass(null,"ui-state-disabled",!!t)},focus:function(t,e){var i;this.blur(t,t&&"focus"===t.type),this._scrollIntoView(e),this.active=e.first(),i=this.active.children(".ui-menu-item-wrapper"),this._addClass(i,null,"ui-state-active"),this.options.role&&this.element.attr("aria-activedescendant",i.attr("id")),i=this.active.parent().closest(".ui-menu-item").children(".ui-menu-item-wrapper"),this._addClass(i,null,"ui-state-active"),t&&"keydown"===t.type?this._close():this.timer=this._delay(function(){this._close()},this.delay),(i=e.children(".ui-menu")).length&&t&&/^mouse/.test(t.type)&&this._startOpening(i),this.activeMenu=e.parent(),this._trigger("focus",t,{item:e})},_scrollIntoView:function(t){var e,i,s;this._hasScroll()&&(i=parseFloat(x.css(this.activeMenu[0],"borderTopWidth"))||0,s=parseFloat(x.css(this.activeMenu[0],"paddingTop"))||0,e=t.offset().top-this.activeMenu.offset().top-i-s,i=this.activeMenu.scrollTop(),s=this.activeMenu.height(),t=t.outerHeight(),e<0?this.activeMenu.scrollTop(i+e):s",options:{appendTo:null,autoFocus:!1,delay:300,minLength:1,position:{my:"left top",at:"left bottom",collision:"none"},source:null,change:null,close:null,focus:null,open:null,response:null,search:null,select:null},requestIndex:0,pending:0,liveRegionTimer:null,_create:function(){var i,s,n,t=this.element[0].nodeName.toLowerCase(),e="textarea"===t,t="input"===t;this.isMultiLine=e||!t&&this._isContentEditable(this.element),this.valueMethod=this.element[e||t?"val":"text"],this.isNewMenu=!0,this._addClass("ui-autocomplete-input"),this.element.attr("autocomplete","off"),this._on(this.element,{keydown:function(t){if(this.element.prop("readOnly"))s=n=i=!0;else{s=n=i=!1;var e=x.ui.keyCode;switch(t.keyCode){case e.PAGE_UP:i=!0,this._move("previousPage",t);break;case e.PAGE_DOWN:i=!0,this._move("nextPage",t);break;case e.UP:i=!0,this._keyEvent("previous",t);break;case e.DOWN:i=!0,this._keyEvent("next",t);break;case e.ENTER:this.menu.active&&(i=!0,t.preventDefault(),this.menu.select(t));break;case e.TAB:this.menu.active&&this.menu.select(t);break;case e.ESCAPE:this.menu.element.is(":visible")&&(this.isMultiLine||this._value(this.term),this.close(t),t.preventDefault());break;default:s=!0,this._searchTimeout(t)}}},keypress:function(t){if(i)return i=!1,void(this.isMultiLine&&!this.menu.element.is(":visible")||t.preventDefault());if(!s){var e=x.ui.keyCode;switch(t.keyCode){case e.PAGE_UP:this._move("previousPage",t);break;case e.PAGE_DOWN:this._move("nextPage",t);break;case e.UP:this._keyEvent("previous",t);break;case e.DOWN:this._keyEvent("next",t)}}},input:function(t){if(n)return n=!1,void t.preventDefault();this._searchTimeout(t)},focus:function(){this.selectedItem=null,this.previous=this._value()},blur:function(t){clearTimeout(this.searching),this.close(t),this._change(t)}}),this._initSource(),this.menu=x("