diff --git a/Cargo.lock b/Cargo.lock index f11fe232..163eb16c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -606,6 +606,12 @@ dependencies = [ "walkdir", ] +[[package]] +name = "hamming" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65043da274378d68241eb9a8f8f8aa54e349136f7b8e12f63e3ef44043cc30e1" + [[package]] name = "hashbrown" version = "0.9.1" @@ -1210,6 +1216,52 @@ version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" +[[package]] +name = "primal" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bf9e0aea92a2e2a931fafb1b5d4c6e438197bbca4943a9984b3327fb8e3b5d0" +dependencies = [ + "primal-check", + "primal-estimate", + "primal-sieve", +] + +[[package]] +name = "primal-bit" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2837edc85364907810b0ec880f1010dab1a9cc6e75bacb3655c7fd22e125ec1b" +dependencies = [ + "hamming", +] + +[[package]] +name = "primal-check" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01419cee72c1a1ca944554e23d83e483e1bccf378753344e881de28b5487511d" +dependencies = [ + "num-integer", +] + +[[package]] +name = "primal-estimate" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12e07b550a7cbfcaa567bcd28042919548016ad615b5731633fa5239992d853f" + +[[package]] +name = "primal-sieve" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd0113d948c8f955a7ae96520023fe1e730eadbf26e67c4452f801a485e9d357" +dependencies = [ + "primal-bit", + "primal-estimate", + "smallvec", +] + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -1924,9 +1976,9 @@ dependencies = [ [[package]] name = "tss-esapi" -version = "6.1.0" +version = "7.0.0-alpha.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23bb26bea47d9a884f613730d2f68c9aeaef9a6a51ce8277d111042dbb8c5975" +checksum = "d65600c381941a18c1bfbc3f0f65976e34ef5d22b9b6c1d3d256df28c152601b" dependencies = [ "bitfield", "enumflags2", @@ -1935,6 +1987,7 @@ dependencies = [ "mbox", "num-derive", "num-traits", + "primal", "regex", "serde", "tss-esapi-sys", diff --git a/Cargo.toml b/Cargo.toml index 63b5107f..721a3df7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,7 +29,7 @@ log = { version = "0.4.14", features = ["serde"] } cryptoki = { version = "0.2.0", optional = true, features = ["psa-crypto-conversions"] } picky-asn1-der = { version = "<=0.2.4", optional = true } picky-asn1 = { version = ">=0.3.1, <=0.3.1", optional = true } -tss-esapi = { version = "6.1.0", optional = true } +tss-esapi = { version = "7.0.0-alpha.1", optional = true } bincode = "1.3.1" structopt = "0.3.21" derivative = "2.2.0" diff --git a/ci.sh b/ci.sh index 8fab7c4c..21564d1b 100755 --- a/ci.sh +++ b/ci.sh @@ -366,4 +366,27 @@ else echo "Execute stress tests" RUST_BACKTRACE=1 cargo test $TEST_FEATURES --manifest-path ./e2e_tests/Cargo.toml stress_test fi + + # For the TPM provider we check that keys can still be used after a TPM Reset + if [ "$PROVIDER_NAME" = "tpm" ]; then + # We first create the keys + RUST_BACKTRACE=1 cargo test $TEST_FEATURES --manifest-path ./e2e_tests/Cargo.toml before_tpm_reset + stop_service + + # In order to reset the TPM, we need to restart the TPM server and send a Startup(CLEAR) + pkill tpm_server + sleep 1 + + tpm_server & + TPM_SRV_PID=$! + sleep 5 + + tpm2_startup -c -T mssim + + # We then spin up the service again and check that the keys can still be used + RUST_LOG=error RUST_BACKTRACE=1 cargo run --release $FEATURES -- --config $CONFIG_PATH & + wait_for_service + + RUST_BACKTRACE=1 cargo test $TEST_FEATURES --manifest-path ./e2e_tests/Cargo.toml after_tpm_reset + fi fi diff --git a/e2e_tests/tests/per_provider/mod.rs b/e2e_tests/tests/per_provider/mod.rs index f6b7e8fa..b90d36b5 100644 --- a/e2e_tests/tests/per_provider/mod.rs +++ b/e2e_tests/tests/per_provider/mod.rs @@ -3,3 +3,5 @@ mod key_mappings; mod normal_tests; mod stress_test; +#[cfg(feature = "tpm-provider")] +mod tpm_reset; diff --git a/e2e_tests/tests/per_provider/normal_tests/asym_sign_verify.rs b/e2e_tests/tests/per_provider/normal_tests/asym_sign_verify.rs index e4ae1bc8..3d00cc1b 100644 --- a/e2e_tests/tests/per_provider/normal_tests/asym_sign_verify.rs +++ b/e2e_tests/tests/per_provider/normal_tests/asym_sign_verify.rs @@ -90,7 +90,7 @@ fn asym_verify_fail_ecc_sha256() -> Result<()> { #[test] fn only_verify_from_internet() -> Result<()> { let mut client = TestClient::new(); - let key_name = String::from("only_verify"); + let key_name = String::from("only_verify_from_internet"); if !client.is_operation_supported(Opcode::PsaImportKey) { return Ok(()); } diff --git a/e2e_tests/tests/per_provider/normal_tests/import_key.rs b/e2e_tests/tests/per_provider/normal_tests/import_key.rs index 8f0989a2..0247b2d1 100644 --- a/e2e_tests/tests/per_provider/normal_tests/import_key.rs +++ b/e2e_tests/tests/per_provider/normal_tests/import_key.rs @@ -365,43 +365,43 @@ fn failed_imported_key_should_be_removed() -> Result<()> { Ok(()) } -#[cfg(feature = "tpm-provider")] -#[test] -fn import_key_pair() { - let mut client = TestClient::new(); - let key_name = String::from("failed_imported_key_should_be_removed"); - - client - .import_key( - key_name, - Attributes { - lifetime: Lifetime::Persistent, - key_type: Type::RsaKeyPair, - bits: 1024, - policy: Policy { - usage_flags: UsageFlags { - export: false, - copy: false, - cache: false, - encrypt: false, - decrypt: false, - sign_message: true, - sign_hash: true, - verify_message: true, - verify_hash: true, - derive: false, - }, - permitted_algorithms: Algorithm::AsymmetricSignature( - AsymmetricSignature::RsaPkcs1v15Sign { - hash_alg: Hash::Sha256.into(), - }, - ), - }, - }, - KEY_PAIR_DATA.to_vec(), - ) - .unwrap(); -} +// #[cfg(feature = "tpm-provider")] +// #[test] +// fn import_key_pair() { +// let mut client = TestClient::new(); +// let key_name = String::from("failed_imported_key_should_be_removed"); + +// client +// .import_key( +// key_name, +// Attributes { +// lifetime: Lifetime::Persistent, +// key_type: Type::RsaKeyPair, +// bits: 1024, +// policy: Policy { +// usage_flags: UsageFlags { +// export: false, +// copy: false, +// cache: false, +// encrypt: false, +// decrypt: false, +// sign_message: true, +// sign_hash: true, +// verify_message: true, +// verify_hash: true, +// derive: false, +// }, +// permitted_algorithms: Algorithm::AsymmetricSignature( +// AsymmetricSignature::RsaPkcs1v15Sign { +// hash_alg: Hash::Sha256.into(), +// }, +// ), +// }, +// }, +// KEY_PAIR_DATA.to_vec(), +// ) +// .unwrap(); +// } #[cfg(any(feature = "mbed-crypto-provider", feature = "cryptoauthlib-provider"))] #[test] diff --git a/e2e_tests/tests/per_provider/tpm_reset.rs b/e2e_tests/tests/per_provider/tpm_reset.rs new file mode 100644 index 00000000..afd48c20 --- /dev/null +++ b/e2e_tests/tests/per_provider/tpm_reset.rs @@ -0,0 +1,43 @@ +// Copyright 2021 Contributors to the Parsec project. +// SPDX-License-Identifier: Apache-2.0 + +// These tests track a potential regression where the TPM provider +// was unable to handle stored keys after a TPM reset. +// +// `before_tpm_reset` creates keys that should be usable post-TPM-reset, +// in `after_tpm_reset`. +// +// See: https://github.com/parallaxsecond/parsec/issues/504 +use e2e_tests::TestClient; + +const RSA_KEY_NAME: &str = "tpm-reset-rsa"; +const ECC_KEY_NAME: &str = "tpm-reset-ecc"; + +#[test] +fn before_tpm_reset() { + let mut client = TestClient::new(); + client.do_not_destroy_keys(); + + let rsa_key_name = String::from(RSA_KEY_NAME); + let ecc_key_name = String::from(ECC_KEY_NAME); + + client.generate_rsa_sign_key(rsa_key_name.clone()).unwrap(); + client + .generate_ecc_key_pair_secpr1_ecdsa_sha256(ecc_key_name.clone()) + .unwrap(); +} + +#[test] +fn after_tpm_reset() { + let mut client = TestClient::new(); + + let rsa_key_name = String::from(RSA_KEY_NAME); + let ecc_key_name = String::from(ECC_KEY_NAME); + + let _ = client + .sign_with_rsa_sha256(rsa_key_name, vec![0xff; 32]) + .unwrap(); + let _ = client + .sign_with_ecdsa_sha256(ecc_key_name, vec![0xff; 32]) + .unwrap(); +} diff --git a/src/key_info_managers/mod.rs b/src/key_info_managers/mod.rs index 3471d9d1..04984a10 100644 --- a/src/key_info_managers/mod.rs +++ b/src/key_info_managers/mod.rs @@ -261,6 +261,39 @@ impl KeyInfoManagerClient { } } + /// Replace the key info saved for a given triple + /// + /// # Errors + /// + /// If the key triple doesn't exist in the KIM, PsaErrorDoesNotExist is returned. For + /// any other error occurring in the KIM, KeyInfoManagerError is returned. + pub fn replace_key_info( + &self, + key_triple: KeyTriple, + key_id: &T, + attributes: Attributes, + ) -> parsec_interface::requests::Result<()> { + let mut key_info_manager_impl = self + .key_info_manager_impl + .write() + .expect("Key Info Manager lock poisoned"); + let key_info = KeyInfo { + id: bincode::serialize(key_id)?, + attributes, + }; + + match key_info_manager_impl.insert(key_triple.clone(), key_info) { + Ok(None) => { + let _ = key_info_manager_impl + .remove(&key_triple) + .map_err(to_response_status)?; + Err(ResponseStatus::PsaErrorDoesNotExist) + } + Ok(Some(_)) => Ok(()), + Err(string) => Err(to_response_status(string)), + } + } + /// Returns a Vec of ApplicationName of clients having keys in the provider. /// /// # Errors diff --git a/src/providers/tpm/asym_encryption.rs b/src/providers/tpm/asym_encryption.rs index bb0fbec8..5163d91e 100644 --- a/src/providers/tpm/asym_encryption.rs +++ b/src/providers/tpm/asym_encryption.rs @@ -1,9 +1,6 @@ // Copyright 2020 Contributors to the Parsec project. // SPDX-License-Identifier: Apache-2.0 -use super::{ - utils::{self, PasswordContext}, - Provider, -}; +use super::{utils, Provider}; use crate::authenticators::ApplicationName; use crate::key_info_managers::KeyTriple; use parsec_interface::operations::{psa_asymmetric_decrypt, psa_asymmetric_encrypt}; @@ -19,21 +16,22 @@ impl Provider { ) -> Result { let key_triple = KeyTriple::new(app_name, ProviderId::Tpm, op.key_name.clone()); + let password_context = self.get_key_ctx(&key_triple)?; + let key_attributes = self.key_info_store.get_key_attributes(&key_triple)?; + let mut esapi_context = self .esapi_context .lock() .expect("ESAPI Context lock poisoned"); - let password_context: PasswordContext = self.key_info_store.get_key_id(&key_triple)?; - let key_attributes = self.key_info_store.get_key_attributes(&key_triple)?; - op.validate(key_attributes)?; match esapi_context.rsa_encrypt( - password_context.context, + password_context.key_material().clone(), + utils::parsec_to_tpm_params(key_attributes)?, Some( password_context - .auth_value + .auth_value() .try_into() .map_err(utils::to_response_status)?, ), @@ -42,7 +40,6 @@ impl Provider { .clone() .try_into() .map_err(utils::to_response_status)?, - utils::convert_asym_scheme_to_tpm(op.alg.into())?, match op.salt { Some(salt) => Some( salt.deref() @@ -71,21 +68,22 @@ impl Provider { ) -> Result { let key_triple = KeyTriple::new(app_name, ProviderId::Tpm, op.key_name.clone()); + let password_context = self.get_key_ctx(&key_triple)?; + let key_attributes = self.key_info_store.get_key_attributes(&key_triple)?; + let mut esapi_context = self .esapi_context .lock() .expect("ESAPI Context lock poisoned"); - let password_context: PasswordContext = self.key_info_store.get_key_id(&key_triple)?; - let key_attributes = self.key_info_store.get_key_attributes(&key_triple)?; - op.validate(key_attributes)?; match esapi_context.rsa_decrypt( - password_context.context, + password_context.key_material().clone(), + utils::parsec_to_tpm_params(key_attributes)?, Some( password_context - .auth_value + .auth_value() .try_into() .map_err(utils::to_response_status)?, ), @@ -94,7 +92,6 @@ impl Provider { .clone() .try_into() .map_err(utils::to_response_status)?, - utils::convert_asym_scheme_to_tpm(op.alg.into())?, match op.salt { Some(salt) => Some( salt.deref() diff --git a/src/providers/tpm/asym_sign.rs b/src/providers/tpm/asym_sign.rs index 821ea666..32653d3b 100644 --- a/src/providers/tpm/asym_sign.rs +++ b/src/providers/tpm/asym_sign.rs @@ -1,9 +1,6 @@ // Copyright 2020 Contributors to the Parsec project. // SPDX-License-Identifier: Apache-2.0 -use super::{ - utils::{self, PasswordContext}, - Provider, -}; +use super::{utils, Provider}; use crate::authenticators::ApplicationName; use crate::key_info_managers::KeyTriple; use log::error; @@ -21,14 +18,14 @@ impl Provider { ) -> Result { let key_triple = KeyTriple::new(app_name, ProviderId::Tpm, op.key_name.clone()); + let password_context = self.get_key_ctx(&key_triple)?; + let key_attributes = self.key_info_store.get_key_attributes(&key_triple)?; + let mut esapi_context = self .esapi_context .lock() .expect("ESAPI Context lock poisoned"); - let password_context: PasswordContext = self.key_info_store.get_key_id(&key_triple)?; - let key_attributes = self.key_info_store.get_key_attributes(&key_triple)?; - match op.alg { AsymmetricSignature::RsaPkcs1v15Sign { .. } => (), AsymmetricSignature::Ecdsa { .. } => (), @@ -49,9 +46,10 @@ impl Provider { let signature = esapi_context .sign( - password_context.context, + password_context.key_material().clone(), + utils::parsec_to_tpm_params(key_attributes)?, Some( - Auth::try_from(password_context.auth_value) + Auth::try_from(password_context.auth_value()) .map_err(utils::to_response_status)?, ), Digest::try_from((*op.hash).clone()).map_err(utils::to_response_status)?, @@ -64,7 +62,7 @@ impl Provider { })?; Ok(psa_sign_hash::Result { - signature: utils::signature_data_to_bytes(signature.signature, key_attributes)?.into(), + signature: utils::signature_data_to_bytes(signature, key_attributes)?.into(), }) } @@ -75,14 +73,14 @@ impl Provider { ) -> Result { let key_triple = KeyTriple::new(app_name, ProviderId::Tpm, op.key_name.clone()); + let password_context = self.get_key_ctx(&key_triple)?; + let key_attributes = self.key_info_store.get_key_attributes(&key_triple)?; + let mut esapi_context = self .esapi_context .lock() .expect("ESAPI Context lock poisoned"); - let password_context: PasswordContext = self.key_info_store.get_key_id(&key_triple)?; - let key_attributes = self.key_info_store.get_key_attributes(&key_triple)?; - match op.alg { AsymmetricSignature::RsaPkcs1v15Sign { .. } => (), AsymmetricSignature::Ecdsa { .. } => (), @@ -105,7 +103,8 @@ impl Provider { let _ = esapi_context .verify_signature( - password_context.context, + password_context.key_material().clone(), + utils::parsec_to_tpm_params(key_attributes)?, Digest::try_from((*op.hash).clone()).map_err(utils::to_response_status)?, signature, ) diff --git a/src/providers/tpm/key_management.rs b/src/providers/tpm/key_management.rs index 374762b5..aa2fddf3 100644 --- a/src/providers/tpm/key_management.rs +++ b/src/providers/tpm/key_management.rs @@ -1,8 +1,10 @@ // Copyright 2020 Contributors to the Parsec project. // SPDX-License-Identifier: Apache-2.0 use super::utils; +use super::utils::validate_public_key; +#[allow(deprecated)] +use super::utils::LegacyPasswordContext; use super::utils::PasswordContext; -use super::utils::{validate_private_key, validate_public_key, PUBLIC_EXPONENT}; use super::Provider; use crate::authenticators::ApplicationName; use crate::key_info_managers::KeyTriple; @@ -14,12 +16,60 @@ use parsec_interface::operations::{ }; use parsec_interface::requests::{ProviderId, ResponseStatus, Result}; use parsec_interface::secrecy::ExposeSecret; -use picky_asn1_x509::{RSAPrivateKey, RSAPublicKey}; -use tss_esapi::abstraction::transient::RsaExponent; +use picky_asn1_x509::RSAPublicKey; +use std::convert::TryInto; const AUTH_VAL_LEN: usize = 32; impl Provider { + #[allow(deprecated)] + pub(super) fn get_key_ctx(&self, key_triple: &KeyTriple) -> Result { + // Try to deserialize into the new format + self.key_info_store + .get_key_id::(key_triple) + .or_else(|e| { + // If it failed, check if it was a deserialization error + if let ResponseStatus::InvalidEncoding = e { + // Try to deserialize into legacy format + let legacy_ctx = self + .key_info_store + .get_key_id::(key_triple)?; + + // Try to migrate the key context to the new format + let mut esapi_context = self + .esapi_context + .lock() + .expect("ESAPI Context lock poisoned"); + let password_ctx = PasswordContext::new( + esapi_context + .migrate_key_from_ctx( + legacy_ctx.context, + Some( + legacy_ctx + .auth_value + .clone() + .try_into() + .map_err(utils::to_response_status)?, + ), + ) + .map_err(utils::to_response_status)?, + legacy_ctx.auth_value, + ); + + // Grab key attributes and replace legacy entry with new one + let attributes = self.key_info_store.get_key_attributes(key_triple)?; + let _ = self.key_info_store.replace_key_info( + key_triple.clone(), + &password_ctx, + attributes, + )?; + Ok(password_ctx) + } else { + Err(e) + } + }) + } + pub(super) fn psa_generate_key_internal( &self, app_name: ApplicationName, @@ -41,7 +91,7 @@ impl Provider { .lock() .expect("ESAPI Context lock poisoned"); - let (key_context, auth_value) = esapi_context + let (key_material, auth_value) = esapi_context .create_key(utils::parsec_to_tpm_params(attributes)?, AUTH_VAL_LEN) .map_err(|e| { format_error!("Error creating a RSA signing key", e); @@ -52,10 +102,7 @@ impl Provider { self.key_info_store.insert_key_info( key_triple, - &PasswordContext { - context: key_context, - auth_value: auth_value.value().to_vec(), - }, + &PasswordContext::new(key_material, auth_value.value().to_vec()), attributes, )?; @@ -69,7 +116,6 @@ impl Provider { ) -> Result { match op.attributes.key_type { Type::RsaPublicKey => self.psa_import_key_internal_rsa_public(app_name, op), - Type::RsaKeyPair => self.psa_import_key_internal_rsa_keypair(app_name, op), _ => { error!( "The TPM provider does not support importing for the {:?} key type.", @@ -114,7 +160,7 @@ impl Provider { validate_public_key(&public_key, &attributes)?; let key_data = public_key.modulus.as_unsigned_bytes_be(); - let pub_key_context = esapi_context + let key_material = esapi_context .load_external_rsa_public_key(key_data) .map_err(|e| { format_error!("Error creating a RSA signing key", e); @@ -123,73 +169,7 @@ impl Provider { self.key_info_store.insert_key_info( key_triple, - &PasswordContext { - context: pub_key_context, - auth_value: Vec::new(), - }, - attributes, - )?; - - Ok(psa_import_key::Result {}) - } - - pub(super) fn psa_import_key_internal_rsa_keypair( - &self, - app_name: ApplicationName, - op: psa_import_key::Operation, - ) -> Result { - // Currently only the RSA PKCS1 v1.5 signature scheme is supported - // by the tss-esapi crate. - if op.attributes.policy.permitted_algorithms - != Algorithm::AsymmetricSignature(AsymmetricSignature::RsaPkcs1v15Sign { - hash_alg: SignHash::Specific(Hash::Sha256), - }) - { - return Err(ResponseStatus::PsaErrorNotSupported); - } - let key_name = op.key_name; - let attributes = op.attributes; - let key_triple = KeyTriple::new(app_name, ProviderId::Tpm, key_name); - let key_data = op.data; - - self.key_info_store.does_not_exist(&key_triple)?; - let mut esapi_context = self - .esapi_context - .lock() - .expect("ESAPI Context lock poisoned"); - - let private_key: RSAPrivateKey = picky_asn1_der::from_bytes(key_data.expose_secret()) - .map_err(|err| { - format_error!("Could not deserialise key elements", err); - ResponseStatus::PsaErrorInvalidArgument - })?; - - // Derive the public key from the keypair. - let public_key = RSAPublicKey { - modulus: private_key.modulus.clone(), - public_exponent: private_key.public_exponent.clone(), - }; - - // Validate the public and the private key. - validate_public_key(&public_key, &attributes)?; - validate_private_key(&private_key, &attributes)?; - - let key_prime = private_key.prime_1.as_unsigned_bytes_be(); - let public_modulus = private_key.modulus.as_unsigned_bytes_be(); - - let keypair_context = esapi_context - .load_external_rsa(key_prime, public_modulus, RsaExponent::new(PUBLIC_EXPONENT)) - .map_err(|e| { - format_error!("Error creating a RSA signing key", e); - utils::to_response_status(e) - })?; - - self.key_info_store.insert_key_info( - key_triple, - &PasswordContext { - context: keypair_context, - auth_value: Vec::new(), - }, + &PasswordContext::new(key_material, Vec::new()), attributes, )?; @@ -204,23 +184,15 @@ impl Provider { let key_name = op.key_name; let key_triple = KeyTriple::new(app_name, ProviderId::Tpm, key_name); - let mut esapi_context = self - .esapi_context - .lock() - .expect("ESAPI Context lock poisoned"); - - let password_context: PasswordContext = self.key_info_store.get_key_id(&key_triple)?; + let password_context = self.get_key_ctx(&key_triple)?; let key_attributes = self.key_info_store.get_key_attributes(&key_triple)?; - let pub_key_data = esapi_context - .read_public_key(password_context.context) - .map_err(|e| { - format_error!("Error reading a public key", e); - utils::to_response_status(e) - })?; - Ok(psa_export_public_key::Result { - data: utils::pub_key_to_bytes(pub_key_data, key_attributes)?.into(), + data: utils::pub_key_to_bytes( + password_context.key_material().public().clone(), + key_attributes, + )? + .into(), }) } diff --git a/src/providers/tpm/mod.rs b/src/providers/tpm/mod.rs index dedd61c3..129477f3 100644 --- a/src/providers/tpm/mod.rs +++ b/src/providers/tpm/mod.rs @@ -19,9 +19,9 @@ use std::collections::HashSet; use std::io::ErrorKind; use std::str::FromStr; use std::sync::Mutex; -use tss_esapi::abstraction::cipher::Cipher; use tss_esapi::interface_types::algorithm::HashingAlgorithm; use tss_esapi::interface_types::resource_handles::Hierarchy; +use tss_esapi::structures::{SymmetricCipherParameters, SymmetricDefinitionObject}; use tss_esapi::Tcti; use uuid::Uuid; use zeroize::Zeroize; @@ -268,9 +268,12 @@ impl ProviderBuilder { /// /// The method is unsafe because it relies on creating a TSS Context which could cause /// undefined behaviour if multiple such contexts are opened concurrently. - unsafe fn find_default_context_cipher(&self) -> std::io::Result { + unsafe fn find_default_context_cipher(&self) -> std::io::Result { info!("Checking for ciphers supported by the TPM."); - let ciphers = [Cipher::aes_256_cfb(), Cipher::aes_128_cfb()]; + let ciphers = [ + SymmetricDefinitionObject::AES_256_CFB, + SymmetricDefinitionObject::AES_128_CFB, + ]; let mut ctx = tss_esapi::Context::new( Tcti::from_str(self.tcti.as_ref().ok_or_else(|| { std::io::Error::new(ErrorKind::InvalidData, "TCTI configuration missing") @@ -285,7 +288,9 @@ impl ProviderBuilder { })?; for cipher in ciphers.iter() { if ctx - .test_parms(tss_esapi::utils::PublicParmsUnion::SymDetail(*cipher)) + .test_parms(tss_esapi::structures::PublicParameters::SymCipher( + SymmetricCipherParameters::new(*cipher), + )) .is_ok() { return Ok(*cipher); diff --git a/src/providers/tpm/utils.rs b/src/providers/tpm/utils.rs index 4db3ea22..be7114da 100644 --- a/src/providers/tpm/utils.rs +++ b/src/providers/tpm/utils.rs @@ -1,24 +1,28 @@ // Copyright 2020 Contributors to the Parsec project. // SPDX-License-Identifier: Apache-2.0 +#![allow(deprecated)] + use log::error; use parsec_interface::operations::psa_algorithm::*; use parsec_interface::operations::psa_key_attributes::*; use parsec_interface::requests::{ResponseStatus, Result}; use picky_asn1::wrapper::IntegerAsn1; -use picky_asn1_x509::{RSAPrivateKey, RSAPublicKey}; +use picky_asn1_x509::RSAPublicKey; use serde::{Deserialize, Serialize}; -use std::convert::TryInto; -use tss_esapi::abstraction::transient::KeyParams; +use std::convert::{TryFrom, TryInto}; +use tss_esapi::abstraction::transient::{KeyMaterial, KeyParams}; use tss_esapi::constants::response_code::Tss2ResponseCodeKind; -use tss_esapi::interface_types::algorithm::HashingAlgorithm; -use tss_esapi::interface_types::ecc::EccCurve; -use tss_esapi::utils::{ - AsymSchemeUnion, PublicKey, Signature, SignatureData, TpmsContext, RSA_KEY_SIZES, +use tss_esapi::interface_types::{ + algorithm::HashingAlgorithm, ecc::EccCurve, key_bits::RsaKeyBits, +}; +use tss_esapi::structures::{ + EccScheme, EccSignature, HashScheme, RsaExponent, RsaScheme, RsaSignature, Signature, }; +use tss_esapi::tss2_esys::TPMS_CONTEXT; +use tss_esapi::utils::{PublicKey, TpmsContext}; use tss_esapi::Error; use zeroize::{Zeroize, Zeroizing}; -pub const PUBLIC_EXPONENT: u32 = 0x10001; const PUBLIC_EXPONENT_BYTES: [u8; 3] = [0x01, 0x00, 0x01]; /// Convert the TSS library specific error values to ResponseStatus values that are returned on @@ -93,6 +97,39 @@ pub fn to_response_status(error: Error) -> ResponseStatus { // The PasswordContext is what is stored by the Key Info Manager. #[derive(Serialize, Deserialize, Zeroize)] pub struct PasswordContext { + /// This value is kept for legacy purposes, to aid in the migration process + context: TpmsContext, + /// This value is confidential and needs to be zeroized by its new owner. + auth_value: Vec, + /// Public and private parts of the key + key_material: KeyMaterial, +} + +impl PasswordContext { + /// Create a new [PasswordContext] + pub fn new(key_material: KeyMaterial, auth_value: Vec) -> Self { + PasswordContext { + context: TPMS_CONTEXT::default().try_into().unwrap(), // the default value is guaranteed to work + auth_value, + key_material, + } + } + + /// Get a slice of bytes representing the authentication value of the key + pub fn auth_value(&self) -> &[u8] { + &self.auth_value + } + + /// Get reference to the [KeyMaterial] of the key + pub fn key_material(&self) -> &KeyMaterial { + &self.key_material + } +} + +// LegacyPasswordContext that stored key contexts only. +#[deprecated] +#[derive(Serialize, Deserialize, Zeroize)] +pub struct LegacyPasswordContext { pub context: TpmsContext, /// This value is confidential and needs to be zeroized by its new owner. pub auth_value: Vec, @@ -100,20 +137,41 @@ pub struct PasswordContext { pub fn parsec_to_tpm_params(attributes: Attributes) -> Result { match attributes.key_type { - Type::RsaKeyPair => { - let size = match attributes.bits { - x @ 1024 | x @ 2048 | x @ 3072 | x @ 4096 => x.try_into().unwrap(), // will not fail on the matched values - _ => return Err(ResponseStatus::PsaErrorInvalidArgument), - }; + Type::RsaKeyPair | Type::RsaPublicKey => { + let size_u16 = u16::try_from(attributes.bits).map_err(|_| { + error!( + "Requested RSA key size is not supported ({})", + attributes.bits + ); + ResponseStatus::PsaErrorInvalidArgument + })?; + let size = RsaKeyBits::try_from(size_u16).map_err(|_| { + error!("Requested RSA key size is not supported ({})", size_u16); + ResponseStatus::PsaErrorInvalidArgument + })?; match attributes.policy.permitted_algorithms { - Algorithm::AsymmetricSignature(alg) if alg.is_rsa_alg() => Ok(KeyParams::RsaSign { + Algorithm::AsymmetricSignature(alg) if alg.is_rsa_alg() => Ok(KeyParams::Rsa { size, - scheme: convert_asym_scheme_to_tpm(attributes.policy.permitted_algorithms)?, - pub_exponent: 0, + scheme: match alg { + AsymmetricSignature::RsaPkcs1v15Sign { + hash_alg: SignHash::Specific(hash), + } => RsaScheme::RsaSsa(HashScheme::new(convert_hash_to_tpm(hash)?)), + AsymmetricSignature::RsaPss { + hash_alg: SignHash::Specific(hash), + } => RsaScheme::RsaPss(HashScheme::new(convert_hash_to_tpm(hash)?)), + _ => return Err(ResponseStatus::PsaErrorNotSupported), + }, + pub_exponent: RsaExponent::create(0).unwrap(), }), - Algorithm::AsymmetricEncryption(_) => Ok(KeyParams::RsaEncrypt { + Algorithm::AsymmetricEncryption(alg) => Ok(KeyParams::Rsa { size, - pub_exponent: 0, + scheme: match alg { + AsymmetricEncryption::RsaPkcs1v15Crypt => RsaScheme::RsaEs, + AsymmetricEncryption::RsaOaep { hash_alg } => { + RsaScheme::Oaep(HashScheme::new(convert_hash_to_tpm(hash_alg)?)) + } + }, + pub_exponent: RsaExponent::create(0).unwrap(), }), alg => { error!( @@ -124,32 +182,29 @@ pub fn parsec_to_tpm_params(attributes: Attributes) -> Result { } } } - Type::EccKeyPair { .. } => Ok(KeyParams::Ecc { - scheme: convert_asym_scheme_to_tpm(attributes.policy.permitted_algorithms)?, + Type::EccKeyPair { .. } | Type::EccPublicKey { .. } => Ok(KeyParams::Ecc { + scheme: match attributes.policy.permitted_algorithms { + Algorithm::AsymmetricSignature(AsymmetricSignature::Ecdsa { + hash_alg: SignHash::Specific(hash), + }) => EccScheme::EcDsa(HashScheme::new(convert_hash_to_tpm(hash)?)), + Algorithm::AsymmetricSignature(AsymmetricSignature::EcdsaAny) + | Algorithm::AsymmetricSignature(AsymmetricSignature::DeterministicEcdsa { + .. + }) => return Err(ResponseStatus::PsaErrorNotSupported), + _ => { + error!( + "Wrong algorithm provided for ECC key: {:?}", + attributes.policy.permitted_algorithms + ); + return Err(ResponseStatus::PsaErrorInvalidArgument); + } + }, curve: convert_curve_to_tpm(attributes)?, }), _ => Err(ResponseStatus::PsaErrorNotSupported), } } -pub fn convert_asym_scheme_to_tpm(algorithm: Algorithm) -> Result { - match algorithm { - Algorithm::AsymmetricSignature(AsymmetricSignature::RsaPkcs1v15Sign { - hash_alg: SignHash::Specific(hash_alg), - }) => Ok(AsymSchemeUnion::RSASSA(convert_hash_to_tpm(hash_alg)?)), - Algorithm::AsymmetricSignature(AsymmetricSignature::Ecdsa { - hash_alg: SignHash::Specific(hash_alg), - }) => Ok(AsymSchemeUnion::ECDSA(convert_hash_to_tpm(hash_alg)?)), - Algorithm::AsymmetricEncryption(AsymmetricEncryption::RsaPkcs1v15Crypt) => { - Ok(AsymSchemeUnion::RSAES) - } - Algorithm::AsymmetricEncryption(AsymmetricEncryption::RsaOaep { hash_alg }) => { - Ok(AsymSchemeUnion::RSAOAEP(convert_hash_to_tpm(hash_alg)?)) - } - _ => Err(ResponseStatus::PsaErrorNotSupported), - } -} - #[allow(deprecated)] fn convert_hash_to_tpm(hash: Hash) -> Result { match hash { @@ -218,20 +273,24 @@ fn elliptic_curve_point_to_octet_string(mut x: Vec, mut y: Vec) -> Vec Result> { +pub fn signature_data_to_bytes(data: Signature, key_attributes: Attributes) -> Result> { match data { - SignatureData::RsaSignature(signature) => Ok(signature), - SignatureData::EcdsaSignature { mut r, mut s } => { + Signature::RsaSsa(rsa_signature) | Signature::RsaPss(rsa_signature) => { + Ok(rsa_signature.signature().value().to_vec()) + } + Signature::EcDsa(ecc_signature) => { // ECDSA signature data is represented the concatenation of the two result values, r and s, // in big endian format, as described here: // https://parallaxsecond.github.io/parsec-book/parsec_client/operations/psa_algorithm.html#asymmetricsignature-algorithm let p_byte_size = key_attributes.bits / 8; // should not fail for valid keys - if r.len() != p_byte_size || s.len() != p_byte_size { + if ecc_signature.signature_r().value().len() != p_byte_size + || ecc_signature.signature_s().value().len() != p_byte_size + { if crate::utils::GlobalConfig::log_error_details() { error!( "Received ECC signature with invalid size: r - {} bytes; s - {} bytes", - r.len(), - s.len() + ecc_signature.signature_r().value().len(), + ecc_signature.signature_s().value().len() ); } else { error!("Received ECC signature with invalid size."); @@ -240,10 +299,14 @@ pub fn signature_data_to_bytes(data: SignatureData, key_attributes: Attributes) } let mut signature = vec![]; - signature.append(&mut r); - signature.append(&mut s); + signature.append(&mut ecc_signature.signature_r().value().to_vec()); + signature.append(&mut ecc_signature.signature_s().value().to_vec()); Ok(signature) } + _ => { + error!("Unsupported signature type received from TPM"); + Err(ResponseStatus::PsaErrorGenericError) + } } } @@ -252,9 +315,55 @@ pub fn parsec_to_tpm_signature( key_attributes: Attributes, signature_alg: AsymmetricSignature, ) -> Result { - Ok(Signature { - scheme: convert_asym_scheme_to_tpm(Algorithm::AsymmetricSignature(signature_alg))?, - signature: bytes_to_signature_data(data, key_attributes)?, + // Ok(Signature { + // scheme: convert_asym_scheme_to_tpm(Algorithm::AsymmetricSignature(signature_alg))?, + // signature: bytes_to_signature_data(data, key_attributes)?, + // }) + Ok(match signature_alg { + AsymmetricSignature::RsaPkcs1v15Sign { + hash_alg: SignHash::Specific(hash), + } => Signature::RsaSsa( + RsaSignature::create( + convert_hash_to_tpm(hash)?, + data.to_vec().try_into().map_err(to_response_status)?, + ) + .map_err(to_response_status)?, + ), + AsymmetricSignature::RsaPss { + hash_alg: SignHash::Specific(hash), + } => Signature::RsaPss( + RsaSignature::create( + convert_hash_to_tpm(hash)?, + data.to_vec().try_into().map_err(to_response_status)?, + ) + .map_err(to_response_status)?, + ), + AsymmetricSignature::Ecdsa { + hash_alg: SignHash::Specific(hash), + } => { + // ECDSA signature data is represented the concatenation of the two result values, r and s, + // in big endian format, as described here: + // https://parallaxsecond.github.io/parsec-book/parsec_client/operations/psa_algorithm.html#asymmetricsignature-algorithm + let p_size = key_attributes.bits / 8; + if data.len() != p_size * 2 { + return Err(ResponseStatus::PsaErrorInvalidArgument); + } + + let mut r = data.to_vec(); + let s = r.split_off(p_size); + Signature::EcDsa( + EccSignature::create( + convert_hash_to_tpm(hash)?, + r.try_into().map_err(to_response_status)?, + s.try_into().map_err(to_response_status)?, + ) + .map_err(to_response_status)?, + ) + } + _ => { + error!("Signature type not supported: {:?}", signature_alg); + return Err(ResponseStatus::PsaErrorNotSupported); + } }) } @@ -291,18 +400,15 @@ pub fn validate_public_key(public_key: &RSAPublicKey, attributes: &Attributes) - return Err(ResponseStatus::PsaErrorInvalidArgument); } - let valid_key_sizes_vec = RSA_KEY_SIZES.to_vec(); - if !valid_key_sizes_vec.contains(&((len * 8) as u16)) { + if RsaKeyBits::try_from((len * 8) as u16).is_err() { if crate::utils::GlobalConfig::log_error_details() { error!( - "The TPM provider only supports RSA public keys of size {:?} bits ({} bits given).", - valid_key_sizes_vec, + "The TPM provider only supports RSA public keys of size 1024, 2048, 3072 and 4096 bits ({} bits given).", len * 8, ); } else { error!( - "The TPM provider only supports RSA public keys of size {:?} bits", - valid_key_sizes_vec, + "The TPM provider only supports RSA public keys of size 1024, 2048, 3072 and 4096 bits" ); } return Err(ResponseStatus::PsaErrorNotSupported); @@ -310,66 +416,3 @@ pub fn validate_public_key(public_key: &RSAPublicKey, attributes: &Attributes) - Ok(()) } - -/// Validates an RSAPrivateKey against the attributes we expect. Returns ok on success, otherwise -/// returns an error. -pub fn validate_private_key(private_key: &RSAPrivateKey, attributes: &Attributes) -> Result<()> { - // NOTE: potentially incomplete, but any errors that aren't caught here should be caught - // further down the stack (i.e. in the tss crate). - - // The public exponent must be exactly 0x10001 -- that is the only value supported by the TPM - // provider. Reject everything else. - let given_public_exponent = private_key.public_exponent.as_unsigned_bytes_be(); - if given_public_exponent != PUBLIC_EXPONENT_BYTES { - if crate::utils::GlobalConfig::log_error_details() { - error!( - "Unexpected public exponent in private key (expected: {:?}, got: {:?}).", - PUBLIC_EXPONENT_BYTES, given_public_exponent - ); - } else { - error!("Unexpected public exponent in private key."); - } - return Err(ResponseStatus::PsaErrorInvalidArgument); - } - - // The key prime's length in bits should be exactly half of the size of the size of the key's - // public modulus. - let key_prime = private_key.prime_1.as_unsigned_bytes_be(); - let key_prime_len_bits = key_prime.len() * 8; - if key_prime_len_bits != attributes.bits / 2 { - if crate::utils::GlobalConfig::log_error_details() { - error!( - "The key prime is not of the expected size (expected {}, got {}).", - attributes.bits / 2, - key_prime_len_bits, - ); - } else { - error!("The key prime is not of the expected size.",); - } - return Err(ResponseStatus::PsaErrorInvalidArgument); - } - Ok(()) -} - -fn bytes_to_signature_data( - data: Zeroizing>, - key_attributes: Attributes, -) -> Result { - match key_attributes.key_type { - Type::RsaKeyPair | Type::RsaPublicKey => Ok(SignatureData::RsaSignature(data.to_vec())), - Type::EccKeyPair { .. } | Type::EccPublicKey { .. } => { - // ECDSA signature data is represented the concatenation of the two result values, r and s, - // in big endian format, as described here: - // https://parallaxsecond.github.io/parsec-book/parsec_client/operations/psa_algorithm.html#asymmetricsignature-algorithm - let p_size = key_attributes.bits / 8; - if data.len() != p_size * 2 { - return Err(ResponseStatus::PsaErrorInvalidArgument); - } - - let mut r = data.to_vec(); - let s = r.split_off(p_size); - Ok(SignatureData::EcdsaSignature { r, s }) - } - _ => Err(ResponseStatus::PsaErrorNotSupported), - } -}