diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 00000000..b58b603f --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,5 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 00000000..16ece649 --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,36 @@ + + + + \ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 00000000..79ee123c --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 00000000..44126d1b --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/tech_shield_rust_crypto.iml b/.idea/tech_shield_rust_crypto.iml new file mode 100644 index 00000000..cf84ae4a --- /dev/null +++ b/.idea/tech_shield_rust_crypto.iml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 00000000..35eb1ddf --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 00000000..10efcb2f --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,16 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "lldb", + "request": "launch", + "name": "Debug", + "program": "${workspaceFolder}/", + "args": [], + "cwd": "${workspaceFolder}" + } + ] +} \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 49004566..a90f3fe8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -137,7 +137,7 @@ dependencies = [ "futures-io", "futures-lite 2.3.0", "parking", - "polling 3.7.1", + "polling 3.7.2", "rustix 0.38.34", "slab", "tracing", @@ -405,7 +405,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" dependencies = [ "generic-array", - "rand_core 0.6.4", + "rand_core", "subtle", "zeroize", ] @@ -430,25 +430,31 @@ dependencies = [ "base64 0.22.1", "ed25519-dalek", "futures", + "hex", "libloading", - "nitrokey", + "md-5", "once_cell", "openssl", "rand", "reqwest", + "ring", "robusta_jni", + "rsa", "serde", "serde_json", + "sha2", "sodiumoxide", "test-case", "tokio", "tracing", "tracing-android", "tracing-appender", + "tracing-attributes", "tracing-subscriber", "tss-esapi", "windows", "x25519-dalek", + "x509-cert", "yubikey", ] @@ -642,7 +648,7 @@ dependencies = [ "hkdf", "pem-rfc7468", "pkcs8", - "rand_core 0.6.4", + "rand_core", "sec1", "subtle", "zeroize", @@ -741,7 +747,7 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" dependencies = [ - "rand_core 0.6.4", + "rand_core", "subtle", ] @@ -915,17 +921,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "getrandom" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" -dependencies = [ - "cfg-if", - "libc", - "wasi 0.9.0+wasi-snapshot-preview1", -] - [[package]] name = "getrandom" version = "0.2.15" @@ -934,7 +929,7 @@ checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi", ] [[package]] @@ -962,7 +957,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" dependencies = [ "ff", - "rand_core 0.6.4", + "rand_core", "subtle", ] @@ -997,6 +992,18 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +[[package]] +name = "hermit-abi" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + [[package]] name = "hkdf" version = "0.12.4" @@ -1287,7 +1294,7 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" dependencies = [ - "hermit-abi", + "hermit-abi 0.3.9", "libc", "windows-sys 0.48.0", ] @@ -1348,7 +1355,7 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" dependencies = [ - "spin", + "spin 0.5.2", ] [[package]] @@ -1432,11 +1439,21 @@ dependencies = [ "stable_deref_trait", ] +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest", +] + [[package]] name = "memchr" -version = "2.7.2" +version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "mime" @@ -1466,7 +1483,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" dependencies = [ "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi", "windows-sys 0.48.0", ] @@ -1487,27 +1504,6 @@ dependencies = [ "tempfile", ] -[[package]] -name = "nitrokey" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddeb2d19d5499ab4740c0131562e8c4b2c13f8954677be4318c1efc944911531" -dependencies = [ - "lazy_static", - "libc", - "nitrokey-sys", - "rand_core 0.5.1", -] - -[[package]] -name = "nitrokey-sys" -version = "3.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d88466a33516e986e87aeb072307356605bb9ac5b13cd95647ee53a6c5d09641" -dependencies = [ - "cc", -] - [[package]] name = "nom" version = "7.1.3" @@ -1599,7 +1595,7 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ - "hermit-abi", + "hermit-abi 0.3.9", "libc", ] @@ -1909,13 +1905,13 @@ dependencies = [ [[package]] name = "polling" -version = "3.7.1" +version = "3.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e6a007746f34ed64099e88783b0ae369eaa3da6392868ba262e2af9b8fbaea1" +checksum = "a3ed00ed3fbf728b5816498ecd316d1716eecaced9c0c8d2c5a6740ca214985b" dependencies = [ "cfg-if", "concurrent-queue", - "hermit-abi", + "hermit-abi 0.4.0", "pin-project-lite", "rustix 0.38.34", "tracing", @@ -1992,7 +1988,7 @@ checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha", - "rand_core 0.6.4", + "rand_core", ] [[package]] @@ -2002,16 +1998,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core 0.6.4", -] - -[[package]] -name = "rand_core" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" -dependencies = [ - "getrandom 0.1.16", + "rand_core", ] [[package]] @@ -2020,14 +2007,14 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.15", + "getrandom", ] [[package]] name = "redox_syscall" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e" +checksum = "c82cf8cff14456045f55ec4241383baeff27af886adb72ffb2162f99911de0fd" dependencies = [ "bitflags 2.5.0", ] @@ -2113,6 +2100,21 @@ dependencies = [ "subtle", ] +[[package]] +name = "ring" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +dependencies = [ + "cc", + "cfg-if", + "getrandom", + "libc", + "spin 0.9.8", + "untrusted", + "windows-sys 0.52.0", +] + [[package]] name = "robusta-codegen" version = "0.2.2" @@ -2153,7 +2155,7 @@ dependencies = [ "num-traits", "pkcs1", "pkcs8", - "rand_core 0.6.4", + "rand_core", "sha2", "signature 2.2.0", "spki", @@ -2406,7 +2408,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ "digest", - "rand_core 0.6.4", + "rand_core", ] [[package]] @@ -2462,6 +2464,12 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + [[package]] name = "spki" version = "0.7.3" @@ -2785,7 +2793,6 @@ version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ - "log", "pin-project-lite", "tracing-attributes", "tracing-core", @@ -2874,7 +2881,7 @@ checksum = "a9ba6594ded739cb539f8ffcd3713f6c21d4525c47314bbc6de15c0cd251aedf" dependencies = [ "bitfield", "enumflags2", - "getrandom 0.2.15", + "getrandom", "hostname-validator", "log", "mbox", @@ -2911,6 +2918,12 @@ version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "url" version = "2.5.1" @@ -2940,7 +2953,7 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0" dependencies = [ - "getrandom 0.2.15", + "getrandom", ] [[package]] @@ -2992,12 +3005,6 @@ dependencies = [ "try-lock", ] -[[package]] -name = "wasi" -version = "0.9.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" - [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -3113,9 +3120,9 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows" -version = "0.57.0" +version = "0.56.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12342cb4d8e3b046f3d80effd474a7a02447231330ef77d71daa6fbc40681143" +checksum = "1de69df01bdf1ead2f4ac895dc77c9351aefff65b2f3db429a343f9cbf05e132" dependencies = [ "windows-core", "windows-targets 0.52.5", @@ -3123,9 +3130,9 @@ dependencies = [ [[package]] name = "windows-core" -version = "0.57.0" +version = "0.56.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2ed2439a290666cd67ecce2b0ffaad89c2a56b976b736e6ece670297897832d" +checksum = "4698e52ed2d08f8658ab0c39512a7c00ee5fe2688c65f8c0a4f06750d729f2a6" dependencies = [ "windows-implement", "windows-interface", @@ -3135,9 +3142,9 @@ dependencies = [ [[package]] name = "windows-implement" -version = "0.57.0" +version = "0.56.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7" +checksum = "f6fc35f58ecd95a9b71c4f2329b911016e6bec66b3f2e6a4aad86bd2e99e2f9b" dependencies = [ "proc-macro2", "quote", @@ -3146,9 +3153,9 @@ dependencies = [ [[package]] name = "windows-interface" -version = "0.57.0" +version = "0.56.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7" +checksum = "08990546bf4edef8f431fa6326e032865f27138718c587dc21bc0265bbcb57cc" dependencies = [ "proc-macro2", "quote", @@ -3332,7 +3339,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" dependencies = [ "curve25519-dalek", - "rand_core 0.6.4", + "rand_core", "serde", "zeroize", ] @@ -3396,7 +3403,7 @@ dependencies = [ "p384", "pbkdf2", "pcsc", - "rand_core 0.6.4", + "rand_core", "rsa", "secrecy", "sha1", diff --git a/Cargo.toml b/Cargo.toml index 65a9c7b2..b26ebb4b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,35 +32,46 @@ hcvault = [] core = [] linux = ["tpm", "tss-esapi"] macos = [] -nitro = ["hsm", "nitrokey"] std = [] tpm = [] win = ["tpm", "windows"] yubi = ["hsm", "yubikey"] [dependencies] +yubikey = { version = "0.8.0", optional = true, features = ["untested"] } +sha2 = "0.10.8" +ring = "0.17.8" +tracing-attributes = "0.1.15" anyhow = "*" async-std = "*" futures = "*" -nitrokey = { version = "0.9.0", optional = true } once_cell = "1.14.0" -windows = { version = "0.57.0", features = ["Win32_Security_Tpm", "Security_Cryptography_Core", "Win32_Security_Cryptography"], optional = true } +windows = { version = "0.56.0", features = [ + "Win32_Security_Tpm", + "Security_Cryptography_Core", + "Win32_Security_Cryptography", +], optional = true } tss-esapi = { version = "7.5.0", optional = true } serde = { version = "*", features = ["derive"] } serde_json = "1.0.109" -tracing = { version = "0.1.40", features = ["std", "log"] } +tracing = { version = "0.1.40", features = ["attributes"] } tracing-subscriber = "0.3.18" tracing-appender = "0.2.3" -yubikey = { version = "0.8.0", optional = true } +x509-cert = "0.2.5" +base64 = "0.22.1" +hex = "0.4.3" +rsa = "0.9.6" +md-5 = "0.10.6" +openssl = "0.10.64" reqwest = { version = "0.12.4", features = ["json"] } tokio = { version = "1", features = ["full"] } -openssl = "0.10.64" -base64 = "0.22.1" ed25519-dalek = "2.1.1" arrayref = "0.3.7" sodiumoxide = "0.2.7" rand = "0.8.5" -x25519-dalek = { version = "2.0.1", features = ["static_secrets"], optional = true } +x25519-dalek = { version = "2.0.1", features = [ + "static_secrets", +], optional = true } robusta_jni = { version = "0.2", optional = true } libloading = { version = "0.8.3", optional = true } tracing-android = { version = "0.2.0", optional = true } diff --git a/src/common/factory.rs b/src/common/factory.rs index 8d119ef3..aeadb77d 100644 --- a/src/common/factory.rs +++ b/src/common/factory.rs @@ -17,7 +17,7 @@ type SecurityModuleInstances = Lazy>; /// /// This enum categorizes security modules into HSM (Hardware Security Module) and /// TPM (Trusted Platform Module), allowing for a unified interface when working with different types of security modules. -#[repr(C)] +//#[repr(C)] #[derive(Eq, Hash, PartialEq, Clone, Debug)] pub enum SecurityModule { #[cfg(feature = "hsm")] diff --git a/src/common/traits/key_handle.rs b/src/common/traits/key_handle.rs index c39d3164..04ed7655 100644 --- a/src/common/traits/key_handle.rs +++ b/src/common/traits/key_handle.rs @@ -21,6 +21,8 @@ pub enum GenericKeyHandle { Linux(TssKeyHandle), #[cfg(feature = "win")] Windows(NCRYPT_KEY_HANDLE), + #[cfg(feature = "yubikey")] + YubiKey(KeyHandle), } /// Defines a common interface for cryptographic key operations. diff --git a/src/hsm/mod.rs b/src/hsm/mod.rs index f56ca7c0..2672d21b 100644 --- a/src/hsm/mod.rs +++ b/src/hsm/mod.rs @@ -1,3 +1,92 @@ +/// # High-Level Overview +/// +/// This module provides configuration structures for hardware security module (HSM) providers. +/// It includes the `HsmProviderConfig` structure, which encapsulates information about the +/// cryptographic algorithms and key usages supported by the HSM provider. +/// +/// ## Module Structure +/// +/// The module consists of the following components: +/// +/// - `core`: Contains core functionality for HSM providers. +/// - `nitrokey`: Provides support for Nitrokey HSM devices. +/// - `yubikey`: Offers support for YubiKey HSM devices (conditionally compiled with the `yubi` feature). +/// +/// ## `HsmProviderConfig` Structure +/// +/// The `HsmProviderConfig` struct defines the configuration parameters for an HSM provider. It contains: +/// +/// - `key_algorithm`: Specifies the asymmetric encryption algorithm supported by the HSM. +/// - `key_usage`: Specifies the key usages supported by the HSM. +/// +/// ## Usage +/// +/// To use this module, follow these steps: +/// +/// 1. Import the required modules: `crypto::{algorithms::encryption::AsymmetricEncryption, KeyUsage}`. +/// 2. Define a new configuration using `HsmProviderConfig::new`. +/// 3. Pass the configuration to the HSM provider for initialization. +/// +/// ## Example +/// +/// ```rust +/// use crate::common::crypto::{algorithms::encryption::AsymmetricEncryption, KeyUsage}; +/// use crate::common::traits::module_provider_config::ProviderConfig; +/// +/// // Import the HSM provider configuration module +/// use crate::hsm::HsmProviderConfig; +/// +/// // Define the HSM configuration with RSA encryption and key usage for signing and encryption +/// let config = HsmProviderConfig::new( +/// AsymmetricEncryption::Rsa(KeyBits::Bits2048), +/// vec![KeyUsage::SignEncrypt], +/// ); +/// +/// // Pass the configuration to the HSM provider for initialization +/// let provider = initialize_hsm_provider(config); +/// ``` +use crate::common::crypto::algorithms::encryption::AsymmetricEncryption; +use crate::common::traits::module_provider_config::ProviderConfig; + +/// The core functionality for hardware security module (HSM) providers. pub mod core; + +/// Provides support for Nitrokey HSM devices. pub mod nitrokey; + +use std::any::Any; + +/// Provides support for YubiKey HSM devices (conditionally compiled with the `yubi` feature). +#[cfg(feature = "yubi")] pub mod yubikey; + +/// Configuration parameters for an HSM provider. +#[derive(Debug)] +pub struct HsmProviderConfig { + /// The asymmetric encryption algorithm supported by the HSM. + pub(super) key_algorithm: AsymmetricEncryption, +} + +impl ProviderConfig for HsmProviderConfig { + /// Returns a reference to the dynamic `Any` trait object. + fn as_any(&self) -> &dyn std::any::Any { + self + } +} + +impl HsmProviderConfig { + /// Creates a new instance of `HsmProviderConfig`. + /// + /// # Arguments + /// + /// - `key_algorithm`: The asymmetric encryption algorithm supported by the HSM. + /// - `key_usage`: The key usages supported by the HSM. + /// + /// # Returns + /// + /// A boxed trait object representing the HSM provider configuration. + #[allow(clippy::new_ret_no_self)] + pub fn new(key_algorithm: AsymmetricEncryption) -> Box { + Box::new(Self { key_algorithm }) + } +} diff --git a/src/hsm/yubikey/key_handle.rs b/src/hsm/yubikey/key_handle.rs new file mode 100644 index 00000000..cf393b85 --- /dev/null +++ b/src/hsm/yubikey/key_handle.rs @@ -0,0 +1,350 @@ +use super::YubiKeyProvider; +use crate::{ + common::{ + crypto::algorithms::{ + encryption::{AsymmetricEncryption, EccCurves, EccSchemeAlgorithm}, + KeyBits, + }, + error::SecurityModuleError, + traits::key_handle::KeyHandle, + }, + hsm::core::error::HsmError, +}; + +use ::yubikey::piv; +use ::yubikey::{ + piv::{AlgorithmId, SlotId}, + MgmKey, +}; +use base64::{engine::general_purpose, Engine}; +use openssl::{ + ec::EcKey, + hash::MessageDigest, + pkey::PKey, + rsa::{Padding, Rsa}, + sign::Verifier, +}; +use rsa::sha2::Digest; +use sha2::Sha256; +use tracing::instrument; +use x509_cert::der::zeroize::Zeroizing; + +const BYTES_1024: usize = 128; +const BYTES_2048: usize = 256; +/// Provides cryptographic operations for asymmetric keys on a YubiKey, +/// such as signing, encryption, decryption, and signature verification. + +/// Signs data using the cryptographic key on a YubiKey. +/// +/// This method hashes the input data using SHA-256 and then signs the hash. +/// +/// # Arguments +/// +/// * `data` - The data to be signed. +/// +/// # Returns +/// +/// A `Result` containing the signature as a `Vec` on success, or a `yubikey::Error` on failure. +/// + +impl KeyHandle for YubiKeyProvider { + #[instrument] + fn sign_data(&self, data: &[u8]) -> Result, SecurityModuleError> { + let yubikey = self.yubikey.as_ref().unwrap(); + let mut yubikey = yubikey.lock().unwrap(); + let data = data.to_vec(); + let key_algo = self.key_algo.unwrap(); + + // Input gets hashed with SHA-256 + let mut hasher = Sha256::new(); + hasher.update(data); + let data = hasher.finalize(); + let mut data: &[u8] = &data; + + //TODO After PIN input implementation in App, insert code for re-authentication + let verify = yubikey.verify_pin(self.pin.as_ref()); + if !verify.is_ok() { + return Err(SecurityModuleError::Hsm(HsmError::DeviceSpecific( + "PIN verification failed".to_string(), + ))); + } + let auth = yubikey.authenticate(MgmKey::new(self.management_key.unwrap()).unwrap()); + if !auth.is_ok() { + return Err(SecurityModuleError::Hsm(HsmError::DeviceSpecific( + "Authentication failed".to_string(), + ))); + } + + let signature: Result>, yubikey::Error>; + let mut vec_data: Vec = create_digest_info(data).unwrap(); + let algorithm_id: AlgorithmId; + + match key_algo { + AsymmetricEncryption::Rsa(KeyBits::Bits1024) => { + algorithm_id = AlgorithmId::Rsa1024; + vec_data = apply_pkcs1v15_padding(&vec_data, BYTES_1024); + data = &vec_data.as_slice(); + } + AsymmetricEncryption::Rsa(KeyBits::Bits2048) => { + algorithm_id = AlgorithmId::Rsa2048; + vec_data = apply_pkcs1v15_padding(&vec_data, BYTES_2048); + data = vec_data.as_slice(); + } + + AsymmetricEncryption::Ecc(EccSchemeAlgorithm::EcDsa(EccCurves::P256)) => { + algorithm_id = AlgorithmId::EccP256; + } + AsymmetricEncryption::Ecc(EccSchemeAlgorithm::EcDsa(EccCurves::P384)) => { + algorithm_id = AlgorithmId::EccP384; + } + _ => { + return Err(SecurityModuleError::Hsm(HsmError::DeviceSpecific( + "Key Algorithm not supported".to_string(), + ))); + } + } + signature = piv::sign_data( + &mut yubikey, + data, + algorithm_id, + SlotId::Retired(self.slot_id.unwrap()), + ); + match signature { + Ok(buffer) => { + let signature = general_purpose::STANDARD.encode(&buffer); + let signature = general_purpose::STANDARD + .decode(signature) + .expect("Failed to decode signature"); + Ok(signature) + } + Err(err) => Err(SecurityModuleError::Hsm(HsmError::DeviceSpecific( + err.to_string(), + ))), + } + } + + /// Decrypts data encrypted with the corresponding public key on a YubiKey. + /// Only works with PKCS#1 v1.5 padding. + /// Utilizes the YubiKey API for decryption. + /// + /// # Arguments + /// + /// * `encrypted_data` - The data to be decrypted. + /// + /// # Returns + /// + /// A `Result` containing the decrypted data as a `Vec` on success, or a `yubikey::Error` on failure. + #[instrument] + fn decrypt_data(&self, encrypted_data: &[u8]) -> Result, SecurityModuleError> { + let yubikey = self.yubikey.as_ref().unwrap(); + let mut yubikey = yubikey.lock().unwrap(); + + let decrypted: Result>, &str>; + let key_algo = self.key_algo.unwrap(); + + match key_algo { + AsymmetricEncryption::Rsa(KeyBits::Bits1024) => { + decrypted = piv::decrypt_data( + &mut yubikey, + encrypted_data, + piv::AlgorithmId::Rsa1024, + piv::SlotId::Retired(self.slot_id.unwrap()), + ) + .map_err(|_| "Failed to decrypt data"); + } + AsymmetricEncryption::Rsa(KeyBits::Bits2048) => { + decrypted = piv::decrypt_data( + &mut yubikey, + encrypted_data, + piv::AlgorithmId::Rsa2048, + piv::SlotId::Retired(self.slot_id.unwrap()), + ) + .map_err(|_| "Failed to decrypt data"); + } + // The Yubikey do not support decryption with ECC, see: + // https://docs.yubico.com/yesdk/users-manual/application-piv/apdu/auth-decrypt.html + /* + AsymmetricEncryption::Ecc(EccSchemeAlgorithm::EcDsa(EccCurves::P256)) => {} + AsymmetricEncryption::Ecc(EccSchemeAlgorithm::EcDsa(EccCurves::P384)) => {} + */ + _ => { + return Err(SecurityModuleError::Hsm(HsmError::DeviceSpecific( + "Key Algorithm not supported".to_string(), + ))); + } + } + fn remove_pkcs1_padding(buffer: &[u8]) -> Result, &'static str> { + let mut pos = 2; // Start nach dem ersten Padding-Byte `0x02` + if buffer[0] != 0 { + return Err("Invalid padding"); + } + // Überspringe alle non-zero Bytes + while pos < buffer.len() && buffer[pos] != 0 { + pos += 1; + } + if pos >= buffer.len() { + return Err("No data after padding"); + } + // Das erste `0x00` Byte überspringen, um die tatsächlichen Daten zu erhalten + Ok(buffer[pos + 1..].to_vec()) + } + match decrypted { + Ok(buffer) => match remove_pkcs1_padding(&buffer) { + Ok(data) => { + return Ok(data); + } + Err(err) => { + return Err(SecurityModuleError::Hsm( + crate::hsm::core::error::HsmError::DeviceSpecific(err.to_string()), + )); + } + }, + Err(err) => { + return Err(SecurityModuleError::Hsm( + crate::hsm::core::error::HsmError::DeviceSpecific(err.to_string()), + )); + } + } + } + + /// Encrypts data with the cryptographic key on a YubiKey. + /// + /// Uses the YubiKey API for encryption. + /// + /// # Arguments + /// + /// * `data` - The data to be encrypted. + /// + /// # Returns + /// + /// A `Result` containing the encrypted data as a `Vec` on success, or a `yubikey::Error` on failure. + /// Möglicher Fehler: Müssen Daten vor dem returnen noch in Base64 umgewandelt werden? + #[instrument] + fn encrypt_data(&self, data: &[u8]) -> Result, SecurityModuleError> { + match self.key_algo.unwrap() { + AsymmetricEncryption::Rsa(KeyBits::Bits1024) + | AsymmetricEncryption::Rsa(KeyBits::Bits2048) => { + let rsa = Rsa::public_key_from_pem(self.pkey.trim().as_bytes()) + .map_err(|_| "failed to create RSA from public key PEM"); + let mut encrypted_data = vec![0; rsa.clone().unwrap().size() as usize]; + let _ = rsa + .map_err(|_| "") + .unwrap() + .public_encrypt(data, &mut encrypted_data, Padding::PKCS1) + .map_err(|_| "failed to encrypt data"); + Ok(encrypted_data) + } + _ => { + return Err(SecurityModuleError::Hsm(HsmError::DeviceSpecific( + "Key Algorithm not supported".to_string(), + ))); + } + } + } + + /// Verifies a signature against the provided data using the YubiKey. + /// + /// This method hashes the input data using SHA-256 and then verifies the signature. + /// + /// # 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 { + match self.key_algo.unwrap() { + AsymmetricEncryption::Rsa(KeyBits::Bits1024) + | AsymmetricEncryption::Rsa(KeyBits::Bits2048) => { + let rsa = Rsa::public_key_from_pem(self.pkey.trim().as_bytes()) + .expect("failed to create RSA from public key PEM"); + let key_pkey = PKey::from_rsa(rsa).unwrap(); + + let mut verifier = Verifier::new(MessageDigest::sha256(), &key_pkey) + .expect("failed to create verifier"); + verifier + .update(data) + .map_err(|_| "failed to update verifier") + .unwrap(); + if verifier + .verify(signature) + .expect("failed to verify signature") + { + return Result::Ok(true); + } else { + return Err(SecurityModuleError::Hsm(HsmError::DeviceSpecific( + "Signature verification failed".to_string(), + ))); + } + } + + AsymmetricEncryption::Ecc(EccSchemeAlgorithm::EcDsa(EccCurves::P256)) + | AsymmetricEncryption::Ecc(EccSchemeAlgorithm::EcDsa(EccCurves::P384)) => { + let ecc = EcKey::public_key_from_pem(self.pkey.trim().as_bytes()) + .expect("failed to create ECC from public key PEM"); + let ecc = PKey::from_ec_key(ecc).expect("failed to create PKey from ECC"); + + let mut verifier = Verifier::new(MessageDigest::sha256(), &ecc) + .expect("failed to create verifier"); + verifier + .update(data) + .map_err(|_| "failed to update verifier") + .unwrap(); + if verifier + .verify(signature) + .expect("failed to verify signature") + { + return Result::Ok(true); + } else { + return Err(SecurityModuleError::Hsm(HsmError::DeviceSpecific( + "Signature verification failed".to_string(), + ))); + } + } + _ => { + return Err(SecurityModuleError::Hsm(HsmError::DeviceSpecific( + "Key Algorithm not supported".to_string(), + ))); + } + } + } +} + +#[instrument] +fn create_digest_info(digest: &[u8]) -> Result, Box> { + let oid_sha256: [u8; 9] = [0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01]; // OID für SHA-256 + let mut digest_info = vec![]; + + // ASN.1 SEQUENCE Header + digest_info.extend_from_slice(&[ + 0x30, 0x31, // SEQUENCE, Länge 49 + 0x30, 0x0d, // SEQUENCE, Länge 13 + 0x06, 0x09, // OID Header + ]); + digest_info.extend_from_slice(&oid_sha256); // OID SHA-256 + digest_info.extend_from_slice(&[ + 0x05, 0x00, // NULL + 0x04, 0x20, // OCTET STRING, Länge 32 + ]); + digest_info.extend_from_slice(digest); // SHA-256 Hash-Wert + + Ok(digest_info) +} + +#[instrument] +fn apply_pkcs1v15_padding(data: &[u8], block_size: usize) -> Vec { + let padding_length = block_size - data.len() - 3; + let mut padded_data = Vec::with_capacity(block_size); + padded_data.push(0x00); + padded_data.push(0x01); + for _ in 0..padding_length { + padded_data.push(0xFF); + } + padded_data.push(0x00); + padded_data.extend_from_slice(data); + padded_data +} diff --git a/src/hsm/yubikey/mod.rs b/src/hsm/yubikey/mod.rs index 8b137891..b0980e91 100644 --- a/src/hsm/yubikey/mod.rs +++ b/src/hsm/yubikey/mod.rs @@ -1 +1,49 @@ +use crate::common::crypto::algorithms::encryption::AsymmetricEncryption; +use ::yubikey::{piv::RetiredSlotId, YubiKey}; +use std::sync::{Arc, Mutex}; +use tracing::instrument; +pub mod key_handle; +pub mod provider; + +/// A YubiKey-based cryptographic provider for managing cryptographic keys and performing +/// cryptographic operations. +/// +/// This provider leverages the YubiKey API to interact with a YubiKey device for operations +/// like signing, encryption, and decryption. It provides a secure and hardware-backed solution +/// for managing cryptographic keys and performing cryptographic operations. + +// #[derive(cloe, Debug)]??? +#[derive(Debug)] +pub struct YubiKeyProvider { + /// A unique identifier for the cryptographic key managed by this provider. + pub(super) pkey: String, + pub(super) slot_id: Option, + pub(super) key_algo: Option, + pub(super) yubikey: Option>>, + pin: String, + management_key: Option<[u8; 24]>, +} + +impl YubiKeyProvider { + /// Constructs a new `YubiKeyProvider`. + /// + /// # Arguments + /// + /// * `key_id` - A string identifier for the cryptographic key to be managed by this provider. + /// + /// # Returns + /// + /// A new instance of `YubiKeyProvider` with the specified `key_id`. + #[instrument] + pub fn new(key_id: String) -> Self { + Self { + pkey: String::new(), + slot_id: None, + key_algo: None, + yubikey: None, + pin: String::new(), + management_key: None, + } + } +} diff --git a/src/hsm/yubikey/provider.rs b/src/hsm/yubikey/provider.rs new file mode 100644 index 00000000..f3a62d29 --- /dev/null +++ b/src/hsm/yubikey/provider.rs @@ -0,0 +1,629 @@ +use super::YubiKeyProvider; +use crate::common::{ + crypto::algorithms::{ + encryption::{AsymmetricEncryption, EccCurves, EccSchemeAlgorithm}, + KeyBits, + }, + error::SecurityModuleError, + traits::module_provider::Provider, +}; +use crate::hsm::{core::error::HsmError, HsmProviderConfig}; +use ::yubikey::{ + piv::{self, AlgorithmId, RetiredSlotId, SlotId}, + Error, YubiKey, +}; +use base64::{engine::general_purpose, Engine}; +use std::any::Any; +use std::sync::{Arc, Mutex}; +use tracing::instrument; +use x509_cert::der::Encode; +use yubikey::MgmKey; + +const SLOTS: [RetiredSlotId; 20] = [ + RetiredSlotId::R1, + RetiredSlotId::R2, + RetiredSlotId::R3, + RetiredSlotId::R4, + RetiredSlotId::R5, + RetiredSlotId::R6, + RetiredSlotId::R7, + RetiredSlotId::R8, + RetiredSlotId::R9, + RetiredSlotId::R10, + RetiredSlotId::R11, + RetiredSlotId::R12, + RetiredSlotId::R13, + RetiredSlotId::R14, + RetiredSlotId::R15, + RetiredSlotId::R16, + RetiredSlotId::R17, + RetiredSlotId::R18, + RetiredSlotId::R19, + RetiredSlotId::R20, +]; + +/// IDs/addresses for read/write objects operations; +/// see https://developers.yubico.com/yubico-piv-tool/Actions/read_write_objects.html +const SLOTSU32: [u32; 20] = [ + 0x005f_c10d, + 0x005f_c10e, + 0x005f_c10f, + 0x005f_c110, + 0x005f_c111, + 0x005f_c112, + 0x005f_c113, + 0x005f_c114, + 0x005f_c115, + 0x005f_c116, + 0x005f_c117, + 0x005f_c118, + 0x005f_c119, + 0x005f_c11a, + 0x005f_c11b, + 0x005f_c11c, + 0x005f_c11d, + 0x005f_c11e, + 0x005f_c11f, + 0x005f_c120, +]; + +/// Implements the `Provider` trait, providing cryptographic operations utilizing a YubiKey. +/// +/// This implementation interacts with a YubiKey device for key management and cryptographic +/// operations. +impl Provider for YubiKeyProvider { + /// Creates a new cryptographic key identified by the provider given 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 + /// stored in the YubiKey. + /// + /// # Arguments + /// + /// * `key_id` - A string slice that uniquely identifies the key for later usage. + /// * `config` - A boxed `ProviderConfig` containing configuration details for key-generating + /// + /// # Returns + /// + /// The generated Public Key will be stored in the Yubikey as Object with futher information + /// A `Result` that, on success, contains `Ok()`. + /// On failure, it returns a `yubikey::Error`. + /// + /// # Errors + /// Stick throws Error, if all Slots are used. We have coded a method to get all stored keys, + /// so that the user can see which slots are used. + /// We also coded a method, which can remove any stored key from the Yubikey. + + #[instrument] + fn create_key( + &mut self, + key_id: &str, + config: Box, + ) -> Result<(), SecurityModuleError> { + if let Some(hsm_config) = config.downcast_ref::() { + self.key_algo = Some(hsm_config.key_algorithm); + let key_algo = self.key_algo.clone(); + let key_algorithm; + match key_algo { + Some(algo) => key_algorithm = algo, + None => { + return Err(SecurityModuleError::InitializationError( + "No key algorithm found".to_string(), + )); + } + } + + let slot: u32; + let slot_id; + let algorithm: AlgorithmId; + + if !(self.load_key(key_id, config).is_ok()) { + let mut yubikey = self.yubikey.as_ref().unwrap().lock().unwrap(); + let _ = yubikey.verify_pin(self.pin.as_ref()); + let _ = yubikey.authenticate(MgmKey::new(self.management_key.unwrap()).unwrap()); + match get_free_slot(&mut yubikey) { + Ok(free) => { + slot_id = free; + } + Err(err) => { + return Err(SecurityModuleError::InitializationError(err.to_string())); + } + } + } else { + slot_id = self.slot_id.unwrap(); + } + + fn generate_key( + mut yubikey: &mut YubiKey, + algorithm: AlgorithmId, + slot_id: RetiredSlotId, + ) -> Result<(RetiredSlotId, String), SecurityModuleError> { + let pkey: String; + + let gen_key = piv::generate( + &mut yubikey, + SlotId::Retired(slot_id), + algorithm, + yubikey::PinPolicy::Default, + yubikey::TouchPolicy::Default, + ); + match gen_key { + Ok(_) => { + let gen_key = gen_key.as_ref().unwrap().to_der().unwrap(); + let gen_key = general_purpose::STANDARD.encode(&gen_key); + let gen_key = format!( + "-----BEGIN PUBLIC KEY-----\n{}\n-----END PUBLIC KEY-----", + gen_key.trim() + ); + pkey = gen_key; + } + Err(err) => { + return Err(SecurityModuleError::Hsm(HsmError::DeviceSpecific( + err.to_string(), + ))) + } + } + Ok((slot_id, pkey)) + } + + match key_algorithm { + AsymmetricEncryption::Rsa(curve) => match curve { + KeyBits::Bits1024 => algorithm = AlgorithmId::Rsa1024, + KeyBits::Bits2048 => algorithm = AlgorithmId::Rsa2048, + _ => { + return Err(SecurityModuleError::Hsm(HsmError::DeviceSpecific( + "Key Algorithm not supported".to_string(), + ))); + } + }, + AsymmetricEncryption::Ecc(EccSchemeAlgorithm::EcDsa(curve)) => match curve { + EccCurves::P256 => algorithm = AlgorithmId::EccP256, + EccCurves::P384 => algorithm = AlgorithmId::EccP384, + _ => { + return Err(SecurityModuleError::Hsm(HsmError::DeviceSpecific( + "Key Algorithm not supported".to_string(), + ))); + } + }, + _ => { + return Err(SecurityModuleError::Hsm(HsmError::DeviceSpecific( + "Key Algorithm not supported".to_string(), + ))); + } + } + + let algo: &str; + match key_algorithm { + AsymmetricEncryption::Rsa(KeyBits::Bits1024) => algo = "Rsa1024", + AsymmetricEncryption::Rsa(KeyBits::Bits2048) => algo = "Rsa2048", + AsymmetricEncryption::Ecc(EccSchemeAlgorithm::EcDsa(EccCurves::P256)) => { + algo = "EccP256" + } + AsymmetricEncryption::Ecc(EccSchemeAlgorithm::EcDsa(EccCurves::P384)) => { + algo = "EccP384" + } + _ => { + return Err(SecurityModuleError::Hsm(HsmError::DeviceSpecific( + "Key Algorithm not supported".to_string(), + ))); + } + } + + let mut yubikey = self.yubikey.as_ref().unwrap().lock().unwrap(); + let (slot_id, pkey) = generate_key(&mut yubikey, algorithm, slot_id).unwrap(); + self.slot_id = Some(slot_id); + self.pkey = pkey; + + let pkey = self.pkey.clone(); + slot = get_reference_u32slot(self.slot_id.unwrap()); + + let _ = yubikey.verify_pin(self.pin.as_ref()); + let _ = yubikey.authenticate(MgmKey::new(self.management_key.unwrap()).unwrap()); + + match save_key_object(&mut yubikey, key_id, slot, &pkey, algo) { + Ok(_) => Ok(()), + Err(err) => { + return Err(SecurityModuleError::Hsm(HsmError::DeviceSpecific( + err.to_string(), + ))) + } + } + } else { + Err(SecurityModuleError::Hsm(HsmError::DeviceSpecific( + "Failed to get the Configurations".to_string(), + ))) + } + } + + /// Loads an existing cryptographic key identified by `key_id`. + /// + /// This method attempts to load a persisted cryptographic key by its identifier from the YubiKey. + /// If successful, it returns a handle to the key for further + /// cryptographic operations. + /// + /// # Arguments + /// + /// * `key_id` - A string slice that uniquely identifies the key to be loaded. + /// * `config` - A boxed `ProviderConfig` containing configuration details 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, config: Box) -> Result<(), SecurityModuleError> { + if let Some(hsm_config) = config.downcast_ref::() { + self.key_algo = Some(hsm_config.key_algorithm); + let mut yubikey = self.yubikey.as_ref().unwrap().lock().unwrap(); + let mut found = false; + for i in 10..20 { + let _ = yubikey.verify_pin(self.pin.as_ref()); + let _ = yubikey.authenticate(MgmKey::new(self.management_key.unwrap()).unwrap()); + let data = yubikey.fetch_object(SLOTSU32[i]); + let mut output: Vec = Vec::new(); + match data { + Ok(data) => { + output = data.to_vec(); + } + Err(_) => {} + } + + let data = output; + match parse_slot_data(&data) { + Ok((key_name, _, public_key, algo)) => { + let _keyalgo; + match self.key_algo { + Some(AsymmetricEncryption::Rsa(KeyBits::Bits1024)) => { + _keyalgo = "Rsa1024" + } + Some(AsymmetricEncryption::Rsa(KeyBits::Bits2048)) => { + _keyalgo = "Rsa2048" + } + Some(AsymmetricEncryption::Ecc(EccSchemeAlgorithm::EcDsa( + EccCurves::P256, + ))) => _keyalgo = "EccP256", + Some(AsymmetricEncryption::Ecc(EccSchemeAlgorithm::EcDsa( + EccCurves::P384, + ))) => _keyalgo = "EccP384", + _ => { + return Err(SecurityModuleError::Hsm(HsmError::DeviceSpecific( + "Algorithm not supported".to_string(), + ))) + } + } + if key_name == key_id.to_string() && _keyalgo == algo { + self.slot_id = Some(SLOTS[i - 10]); + self.pkey = public_key; + found = true; + break; + } + } + Err(_) => { + continue; + } + } + } + + if !found { + return Err(SecurityModuleError::Hsm(HsmError::DeviceSpecific( + "Key not found".to_string(), + ))); + } else { + Ok(()) + } + } else { + Err(SecurityModuleError::Hsm(HsmError::DeviceSpecific( + "Failed to get the Configurations".to_string(), + ))) + } + } + + /// Initializes the YubiKey module and returns a handle for cryptographic operations. + /// + /// This method initializes the YubiKey device and sets up the necessary environment + /// for cryptographic operations. + /// + /// # Arguments + /// + /// * `key_algorithm` - The asymmetric encryption algorithm to be used for the key. + /// * `hash` - An optional hash algorithm to be used with the key. + /// + /// # Returns + /// + /// A `Result` that, on success, contains `Ok(())`, indicating that the module was initialized successfully. + /// On failure, it returns a Yubikey based `Error`. + #[instrument] + fn initialize_module(&mut self) -> Result<(), SecurityModuleError> { + let yubi = YubiKey::open().map_err(|_| Error::NotFound); + let mut yubikey: YubiKey; + match yubi { + Ok(yubi) => { + yubikey = yubi; + } + Err(err) => { + return Err(SecurityModuleError::Hsm(HsmError::DeviceSpecific( + err.to_string(), + ))); + } + } + // Hier muesste die Pin Eingabe und die Managementkey Eingabe implementiert werden. Ist aktuell hardcoded. + self.pin = "123456".to_string(); + self.management_key = Some(*MgmKey::default().as_ref()); + + let verify = yubikey.verify_pin(self.pin.as_ref()); + match verify { + Ok(_) => { + self.yubikey = Some(Arc::new(Mutex::new(yubikey))); + + Ok(()) + } + Err(err) => { + return Err(SecurityModuleError::Hsm(HsmError::DeviceSpecific( + err.to_string(), + ))); + } + } + } +} + +/// Saves the key object to the YubiKey device. +/// +/// This method saves a object to the YubiKey device. The object is stored in a slot and represents +/// information about the key, such as the key name, slot, key and public key. This information +/// belongs to a private key which is stored in a other Slot. +/// +/// # Arguments +/// * 'key_id' - A string slice that uniquely identifies the key for later usage. +/// * 'slot_id' - An address where an object will be stored must be given. +/// * 'pkey' - The public key which is intended to be stored. +/// +/// # Returns +/// +/// The saved Object will be stored in the Yubikey on a free Retired slot as Object with futher information +/// A `Result` that, on success, contains `Ok()`. +/// On failure, it returns a `yubikey::Error`. +fn save_key_object( + yubikey: &mut YubiKey, + key_id: &str, + slot_id: u32, + pkey: &str, + algo: &str, +) -> Result<(), yubikey::Error> { + let key_name = key_id; + let slot = slot_id.to_string(); + let public_key = pkey; + + let total_length = key_name.len() + 1 + slot.len() + 1 + public_key.len() + 1 + algo.len(); + let mut data = vec![0u8; total_length]; + let data_slice: &mut [u8] = &mut data; + + let mut offset = 0; + data_slice[offset..offset + key_name.len()].copy_from_slice(key_name.as_bytes()); + offset += key_name.len(); + data_slice[offset] = 0; + offset += 1; + + data_slice[offset..offset + slot.len()].copy_from_slice(slot.as_bytes()); + offset += slot.len(); + data_slice[offset] = 0; + offset += 1; + + data_slice[offset..offset + public_key.len()].copy_from_slice(public_key.as_bytes()); + offset += public_key.len(); + data_slice[offset] = 0; + offset += 1; + + data_slice[offset..offset + algo.len()].copy_from_slice(algo.as_bytes()); + + let saved = yubikey.save_object(slot_id, data_slice); + match saved { + Ok(()) => Ok(()), + Err(err) => Err(err), + } +} + +/// parses the u8 Data to different Key-Information Strings +/// +/// 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 the YubiKey. +/// +/// # Arguments +/// +///* 'data' - This array reference contains important information, it provides: the key name, the slot where it is stored and the public key itself +/// +/// # Returns +/// +/// A `Result` that, on success, contains `Ok(key_name, slot, public_key)` where the individual information is given. +/// On failure, it returns a `Utf8Error`. +fn parse_slot_data(data: &[u8]) -> Result<(String, String, String, String), SecurityModuleError> { + let parts: Vec<&[u8]> = data.split(|&x| x == 0).collect(); + if !(parts.len() < 4 + || parts[0].is_empty() + || parts[1].is_empty() + || parts[2].is_empty() + || parts[3].is_empty()) + { + let key_name = std::str::from_utf8(parts[0]).unwrap(); + let slot = std::str::from_utf8(parts[1]).unwrap(); + let public_key = std::str::from_utf8(parts[2]).unwrap(); + let algo = std::str::from_utf8(parts[3]).unwrap(); + + Ok(( + key_name.to_string(), + slot.to_string(), + public_key.to_string(), + algo.to_string(), + )) + } else { + return Err(SecurityModuleError::Hsm(HsmError::DeviceSpecific( + "Failed to parse the slot data".to_string(), + ))); + } +} + +/// Gets a free slot for storing a key object. +/// +/// This method goes through the available slots on the YubiKey and returns the first free slot +/// +/// # Arguments +/// The method takes a Yubikey device as an input +/// +/// # Returns +/// +/// A `Result` that, on failure, returns the first free slot. +/// On Success, it returns that no more free slots are available. +fn get_free_slot(yubikey: &mut YubiKey) -> Result { + let mut end = false; + let mut slot_id: RetiredSlotId = SLOTS[0]; + let mut counter = 0; + for i in 10..20 { + let data = yubikey.fetch_object(SLOTSU32[i]); + let mut output: Vec = Vec::new(); + match data { + Ok(data) => { + output = data.to_vec(); + } + Err(_) => {} + } + let data = output; + let parsed = parse_slot_data(&data); + if !parsed.is_ok() { + slot_id = SLOTS[i - 10]; + end = true; + } + + if end { + break; + } + counter += 1; + } + if counter <= 9 { + Ok(slot_id) + } else { + let _ = list_all_slots(yubikey); + + return Err(SecurityModuleError::Hsm(HsmError::DeviceSpecific( + "No more free slots available".to_string(), + ))); + } +} + +/// Converts a `RetiredSlotId` to its corresponding u32 value. +/// +/// This method converts a `RetiredSlotId` to its corresponding u32 value, +/// which is required for slot operations on the YubiKey device. +/// +/// # Arguments +/// +/// * `slot` - The `RetiredSlotId` to be converted. +/// +/// # Returns +/// +/// The corresponding u32 value of the `RetiredSlotId`. +fn get_reference_u32slot(slot: RetiredSlotId) -> u32 { + let mut output: u32 = SLOTSU32[0]; + for i in 0..20 { + if SLOTS[i] == slot { + output = SLOTSU32[i + 10]; + break; + } else { + continue; + } + } + output +} + +fn list_all_slots(yubikey: &mut YubiKey) -> Result, SecurityModuleError> { + let mut output: Vec = Vec::new(); + for i in 10..20 { + let data = yubikey.fetch_object(SLOTSU32[i]); + let mut temp_vec: Vec = Vec::new(); + match data { + Ok(data) => { + temp_vec = data.to_vec(); + } + Err(_) => {} + } + let data = temp_vec; + match parse_slot_data(&data) { + Ok((key_name, slot, pkey, algo)) => { + let output_string = format!( + "Key Name: {}, Slot: {}, Public-Key: {}, Key-Algorithm: {}\n", + key_name, slot, pkey, algo + ); + output.push(output_string); + } + Err(_) => {} + } + } + Ok(output) +} + +/* +/// Clears a slot on the YubiKey device. +/// # Arguments +/// * `yubikey` - The YubiKey device to clear the slot on. +/// * `slot` - The slot to clear. If `None`, all slots are cleared. +/// Needs improvement, as it is problematic to iterate effectively over all slots. + + +fn clear_slot(yubikey: &mut YubiKey, slot: Option) { + match slot { + Some(address) => { + remv(yubikey, address); + } + None => { + //for address in SLOTSU32 { + //remv(yubikey, address); + } + } +} + +/// Removes an object from the YubiKey device. +/// # Arguments +/// * `yubikey` - The YubiKey device to remove the object from. +/// * `address` - The address of the object to remove. + +fn remv(yubikey: &mut YubiKey, address: u32) { + let mut empty_vec: Vec = Vec::new(); + let empty_slice: &mut [u8] = &mut empty_vec[..]; + yubikey.save_object(address, empty_slice); +} +*/ +// Halbfertiger Code, kann benutzt werden wenn PIN-Abfrage in App implementiert wird +/* +#[instrument] +fn initialize_module( + &mut self, + key_algorithm: AsymmetricEncryption, + sym_algorithm: Option, + hash: Option, + input: &str, +) -> Result { + // Opens a connection to the yubikey device + loop { + let yubikey = YubiKey::open(); + if yubikey.is_ok() { + let verify = device.verify_pin(input); + if verify.is_ok() { + //successful login + return device; + } else { + let count = device.get_pin_retries().unwrap(); + // TODO: Implement PUK handling + if count == 0 { + return yubiKey::Error::PinLocked; + /* let puk; + let pin_neu; + let change_puk = device.unblock_pin(puk.as_ref(), pin_neu.as_ref()); + if change_puk.is_ok() { + return device; + */ + } + return yubikey::Errror::WrongPin; + } + } + } +} +*/ diff --git a/src/lib.rs b/src/lib.rs index 898d5a89..91d81ea8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,7 @@ pub mod common; #[cfg(feature = "ffi")] pub mod ffi; -#[cfg(feature = "hsm")] +//#[cfg(feature = "hsm")] pub mod hsm; #[cfg(test)] mod tests; diff --git a/src/tests/common/traits/mod.rs b/src/tests/common/traits/mod.rs index fd907b57..0ff29815 100644 --- a/src/tests/common/traits/mod.rs +++ b/src/tests/common/traits/mod.rs @@ -1,68 +1,196 @@ -use tracing::Level; -use tracing_appender::rolling; -use tracing_subscriber::FmtSubscriber; - -use crate::{ +#[cfg(feature = "hsm")] +use crate::hsm::core::instance::HsmType; +#[cfg(feature = "tpm")] +use crate::tpm::core::instance::TpmType; +/*use crate::{ common::{ + crypto::{ + algorithms::{ + encryption::{ + AsymmetricEncryption, BlockCiphers, EccCurves, EccSchemeAlgorithm, + SymmetricMode, + }, + hashes::{Hash, Sha2Bits}, + KeyBits, + }, + KeyUsage, + }, factory::SecurityModule, - traits::{log_config::LogConfig, module_provider::Provider}, }, - tpm::core::instance::TpmType, - SecModules, + tests::common::traits::setup_security_module, }; -use std::sync::{Arc, Mutex}; - -pub mod key_handle; -pub mod module_provider; - -#[derive(Debug, Clone, Copy)] -struct Logger {} - -impl Logger { - fn new_boxed() -> Box { - Box::new(Self {}) - } -} - -impl LogConfig for Logger { - fn setup_logging(&self) { - let file_appender = rolling::daily("./logs", "output.log"); - let (non_blocking, _guard) = tracing_appender::non_blocking(file_appender); - let subscriber = FmtSubscriber::builder() - .with_max_level(Level::TRACE) - .with_writer(non_blocking) - .finish(); - tracing::subscriber::set_global_default(subscriber) - .expect("setting default subscriber failed"); - } -} - -fn setup_security_module(module: SecurityModule) -> Arc> { - let log = Logger::new_boxed(); - match module { - #[cfg(feature = "hsm")] - SecurityModule::Hsm(_hsm_type) => { - unimplemented!() - } - #[cfg(feature = "tpm")] - SecurityModule::Tpm(tpm_type) => match tpm_type { - TpmType::Linux => SecModules::get_instance( - "test_key".to_owned(), - SecurityModule::Tpm(TpmType::Linux), - Some(log), - ) - .unwrap(), - TpmType::Windows => SecModules::get_instance( - "test_key".to_owned(), - SecurityModule::Tpm(TpmType::Windows), - Some(log), - ) - .unwrap(), - TpmType::None => unimplemented!(), - TpmType::Android(_) => unimplemented!(), - }, - #[cfg(feature = "nks")] - SecurityModule::Nks => unimplemented!(), - // _ => unimplemented!(), - } -} +use test_case::test_matrix;*/ +// +// // #[test_matrix( +// // [SecurityModule::Tpm(TpmType::Linux), +// // SecurityModule::Tpm(TpmType::Windows), +// // SecurityModule::Hsm(HsmType::NitroKey)] +// // )] +// #[test_matrix( +// [SecurityModule::Nks] +// )] +// fn test_sign_and_verify_rsa(module: SecurityModule) { +// let provider = setup_security_module(module); +// +// let config = TpmConfig::new( +// AsymmetricEncryption::Rsa(KeyBits::Bits4096), +// BlockCiphers::Aes(SymmetricMode::Gcm, KeyBits::Bits512), +// Hash::Sha2(Sha2Bits::Sha256), +// vec![KeyUsage::SignEncrypt, KeyUsage::ClientAuth], +// ); +// +// provider +// .lock() +// .unwrap() +// .initialize_module() +// .expect("Failed to initialize module"); +// provider +// .lock() +// .unwrap() +// .create_key("test_rsa_key", config) +// .expect("Failed to create RSA key"); +// +// let data = b"Hello, World!"; +// let signature = provider +// .lock() +// .unwrap() +// .sign_data(data) +// .expect("Failed to sign data"); +// +// assert!(provider +// .lock() +// .unwrap() +// .verify_signature(data, &signature) +// .unwrap()); +// } +// +// // #[test_matrix( +// // [SecurityModule::Tpm(TpmType::Linux), +// // SecurityModule::Tpm(TpmType::Windows), +// // SecurityModule::Hsm(HsmType::NitroKey)] +// // )] +// #[test_matrix( +// [SecurityModule::Nks] +// )] +// fn test_sign_and_verify_ecdsa(module: SecurityModule) { +// let provider = setup_security_module(module); +// +// let config = TpmConfig::new( +// AsymmetricEncryption::Ecc(EccSchemeAlgorithm::EcDsa(EccCurves::Curve25519)), +// BlockCiphers::Aes(SymmetricMode::Gcm, KeyBits::Bits512), +// Hash::Sha2(Sha2Bits::Sha256), +// vec![KeyUsage::SignEncrypt, KeyUsage::ClientAuth], +// ); +// +// provider +// .lock() +// .unwrap() +// .initialize_module() +// .expect("Failed to initialize module"); +// provider +// .lock() +// .unwrap() +// .create_key("test_ecdsa_key", config) +// .expect("Failed to create ECDSA key"); +// +// let data = b"Hello, World!"; +// let signature = provider +// .lock() +// .unwrap() +// .sign_data(data) +// .expect("Failed to sign data"); +// +// assert!(provider +// .lock() +// .unwrap() +// .verify_signature(data, &signature) +// .unwrap()); +// } +// +// // #[test_matrix( +// // [SecurityModule::Tpm(TpmType::Linux), +// // SecurityModule::Tpm(TpmType::Windows), +// // SecurityModule::Hsm(HsmType::NitroKey)] +// // )] +// #[test_matrix( +// [SecurityModule::Nks] +// )] +// fn test_encrypt_and_decrypt_rsa(module: SecurityModule) { +// let provider = setup_security_module(module); +// +// let config = TpmConfig::new( +// AsymmetricEncryption::Rsa(KeyBits::Bits4096), +// BlockCiphers::Aes(SymmetricMode::Gcm, KeyBits::Bits512), +// Hash::Sha2(Sha2Bits::Sha256), +// vec![KeyUsage::Decrypt, KeyUsage::SignEncrypt], +// ); +// +// provider +// .lock() +// .unwrap() +// .initialize_module() +// .expect("Failed to initialize module"); +// provider +// .lock() +// .unwrap() +// .create_key("test_rsa_key", config) +// .expect("Failed to create RSA key"); +// +// let data = b"Hello, World!"; +// let encrypted_data = provider +// .lock() +// .unwrap() +// .encrypt_data(data) +// .expect("Failed to encrypt data"); +// let decrypted_data = provider +// .lock() +// .unwrap() +// .decrypt_data(&encrypted_data) +// .expect("Failed to decrypt data"); +// +// assert_eq!(data, decrypted_data.as_slice()); +// } +// +// // #[test_matrix( +// // [SecurityModule::Tpm(TpmType::Linux), +// // SecurityModule::Tpm(TpmType::Windows), +// // SecurityModule::Hsm(HsmType::NitroKey)] +// // )] +// #[test_matrix( +// [SecurityModule::Nks] +// )] +// fn test_encrypt_and_decrypt_ecdh(module: SecurityModule) { +// let provider = setup_security_module(module); +// +// let config = TpmConfig::new( +// AsymmetricEncryption::Ecc(EccSchemeAlgorithm::EcDh(EccCurves::Curve25519)), +// BlockCiphers::Aes(SymmetricMode::Gcm, KeyBits::Bits512), +// Hash::Sha2(Sha2Bits::Sha256), +// vec![KeyUsage::SignEncrypt, KeyUsage::Decrypt], +// ); +// +// provider +// .lock() +// .unwrap() +// .initialize_module() +// .expect("Failed to initialize module"); +// provider +// .lock() +// .unwrap() +// .create_key("test_ecdh_key", config) +// .expect("Failed to create ECDH key"); +// +// let data = b"Hello, World!"; +// let encrypted_data = provider +// .lock() +// .unwrap() +// .encrypt_data(data) +// .expect("Failed to encrypt data"); +// let decrypted_data = provider +// .lock() +// .unwrap() +// .decrypt_data(&encrypted_data) +// .expect("Failed to decrypt data"); +// +// assert_eq!(data, decrypted_data.as_slice()); +// } \ No newline at end of file diff --git a/src/tests/hsm/mod.rs b/src/tests/hsm/mod.rs index 8b137891..e0f99172 100644 --- a/src/tests/hsm/mod.rs +++ b/src/tests/hsm/mod.rs @@ -1 +1 @@ - +pub mod yubikey; diff --git a/src/tests/hsm/yubikey/key_handle_tests.rs b/src/tests/hsm/yubikey/key_handle_tests.rs new file mode 100644 index 00000000..5335e784 --- /dev/null +++ b/src/tests/hsm/yubikey/key_handle_tests.rs @@ -0,0 +1,261 @@ +/// # Test Cases for Cryptographic Operations +/// +/// The purpose of this module is to provide comprehensive tests for cryptographic operations +/// within the system, specifically focusing on signing and verifying data using RSA and ECC keys. +/// These tests ensure the integrity and reliability of cryptographic operations, which are crucial +/// for maintaining the security of the system's data and communications. +/// +/// ## Test Cases +/// +/// - `test_sign_and_verify_rsa_1024`: Tests signing and verifying data with a 1024-bit RSA key. +/// - `test_sign_and_verify_rsa_2048`: Tests signing and verifying data with a 2048-bit RSA key. +/// - `test_sign_and_verify_ecc_256`: Tests signing and verifying data with a 256-bit ECC key. +/// - `test_sign_and_verify_ecc_384`: Tests signing and verifying data with a 384-bit ECC key. +/// +/// ## Test Procedures +/// +/// Each test case follows a similar procedure: +/// +/// 1. **Initialization**: Initializes the `YubiKeyProvider` with the necessary parameters, such as +/// the key ID and configuration. +/// 2. **Module Initialization**: Initializes the HSM module on the YubiKey device. +/// 3. **Key Creation**: Creates a new key pair based on the test case requirements. +/// 4. **Signing**: Signs a predefined data string. +/// 5. **Verification**: Verifies the signature of the signed data. +/// +/// ## Test Parameters +/// +/// Each test case specifies the key type, size, and usage: +/// +/// - **RSA Keys**: Test cases for RSA keys specify either 1024-bit or 2048-bit key sizes +/// with key usage for signing and encryption. +/// - **ECC Keys**: Test cases for ECC keys specify either 256-bit or 384-bit key sizes +/// with key usage for signing and encryption. +/// +/// ## Test Assumptions +/// +/// These test cases assume that a YubiKey device is connected and properly configured for +/// cryptographic operations. They also assume that the YubiKey device is accessible via +/// the system's USB interface. +/// +/// ## Expected Behavior +/// +/// The expected behavior for each test case is successful key creation, signing, and verification +/// without encountering any errors. Any failures during these operations are considered test +/// failures and will be reported accordingly. +/// +/// Please use **cargo test --features yubi -- --test-threads=1** for successful testing due to parallelization issues. +#[cfg(test)] +#[allow(unused_imports)] +use crate::common::{ + crypto::{ + algorithms::{ + encryption::{AsymmetricEncryption, BlockCiphers, EccCurves, EccSchemeAlgorithm}, + hashes::Hash, + KeyBits, + }, + KeyUsage, + }, + traits::{key_handle::KeyHandle, module_provider::Provider}, +}; + +// Import YubiKeyProvider and HsmProviderConfig for HSM operations +use crate::hsm::{yubikey::YubiKeyProvider, HsmProviderConfig}; +// The following tests cover different cryptographic scenarios, ensuring the robustness and +// compatibility of the system across various configurations and key sizes. + +//Test for signing and verifying RSA data with 1024-bit key +#[cfg(feature = "yubi")] +#[test] +fn test_sign_and_verify_rsa_1024() { + // Initialization of YubiKeyProvider and configuration of HsmProviderConfig + // omitted for brevity; please refer to the individual test implementations + let mut provider = YubiKeyProvider::new("test_sv_1024".to_string()); + + let config = HsmProviderConfig::new(AsymmetricEncryption::Rsa(KeyBits::Bits1024)); + + provider + .initialize_module() + .expect("Failed to initialize module"); + provider + .create_key("test_rsa_key_1024", config) + .expect("Failed to create RSA key"); + + let data = b"Hello, World!"; + let signature = provider.sign_data(data).expect("Failed to sign data"); + + assert!(provider.verify_signature(data, &signature).unwrap()); +} + +// Test for signing and verifying RSA data with a 2048-bit key +#[cfg(feature = "yubi")] +#[test] +fn test_sign_and_verify_rsa_2048() { + let mut provider = YubiKeyProvider::new("test_sv_2048".to_string()); + + let config = HsmProviderConfig::new(AsymmetricEncryption::Rsa(KeyBits::Bits2048)); + + provider + .initialize_module() + .expect("Failed to initialize module"); + provider + .create_key("test_rsa_key_2048", config) + .expect("Failed to create RSA key"); + + let data = b"Hello, World!"; + let signature = provider.sign_data(data).expect("Failed to sign data"); + + assert!(provider.verify_signature(data, &signature).unwrap()); +} + +// Test for signing and verifying ECC data with a 256-bit key +#[cfg(feature = "yubi")] +#[test] +fn test_sign_and_verify_ecc_256() { + let mut provider = YubiKeyProvider::new("test_ecc_256".to_string()); + + let config = HsmProviderConfig::new(AsymmetricEncryption::Ecc(EccSchemeAlgorithm::EcDsa( + EccCurves::P256, + ))); + + provider + .initialize_module() + .expect("Failed to initialize module"); + provider + .create_key("test_ecc_key_256", config) + .expect("Failed to create ECC key"); + + let data = b"Hello, World!"; + let signature = provider.sign_data(data).expect("Failed to sign data"); + + assert!(provider.verify_signature(data, &signature).unwrap()); +} + +// Test for signing and verifying ECC data with a 384-bit key +#[cfg(feature = "yubi")] +#[test] +fn test_sign_and_verify_ecc_384() { + let mut provider = YubiKeyProvider::new("test_ecc_384".to_string()); + + let config = HsmProviderConfig::new(AsymmetricEncryption::Ecc(EccSchemeAlgorithm::EcDsa( + EccCurves::P384, + ))); + + provider + .initialize_module() + .expect("Failed to initialize module"); + provider + .create_key("test_ecc_key_384", config) + .expect("Failed to create ECC key"); + + let data = b"Hello, World!"; + let signature = provider.sign_data(data).expect("Failed to sign data"); + + assert!(provider.verify_signature(data, &signature).unwrap()); +} + +#[cfg(feature = "yubi")] +#[test] +fn test_encrypt_and_decrypt_rsa_1024() { + let mut provider = YubiKeyProvider::new("test_enc_dec_1024".to_string()); + let config = HsmProviderConfig::new(AsymmetricEncryption::Rsa(KeyBits::Bits1024)); + + provider + .initialize_module() + .expect("Failed to initialize module"); + provider + .create_key("test_rsa_key_1024", config) + .expect("Failed to create RSA key"); + + let data = b"Hello, World!"; + let encrypted_data = provider.encrypt_data(data).expect("Failed to encrypt data"); + let decrypted_data = provider + .decrypt_data(&encrypted_data) + .expect("Failed to decrypt data"); + + assert_eq!(data, decrypted_data.as_slice()); +} + +#[cfg(feature = "yubi")] +#[test] +fn test_encrypt_and_decrypt_rsa_2048() { + let mut provider = YubiKeyProvider::new("test_enc_dec_2048".to_string()); + let config = HsmProviderConfig::new(AsymmetricEncryption::Rsa(KeyBits::Bits2048)); + + provider + .initialize_module() + .expect("Failed to initialize module"); + provider + .create_key("test_rsa_key_2048", config) + .expect("Failed to create RSA key"); + + let data = b"Hello, World!"; + let encrypted_data = provider.encrypt_data(data).expect("Failed to encrypt data"); + let decrypted_data = provider + .decrypt_data(&encrypted_data) + .expect("Failed to decrypt data"); + + assert_eq!(data, decrypted_data.as_slice()); +} + +/* +/// The following tests, `test_encrypt_and_decrypt_rsa` and `test_encrypt_and_decrypt_ecdh`, +/// are currently commented out as they are placeholders for future implementations +/// involving encryption and decryption operations. These tests will further expand the +/// test coverage to ensure the complete functionality of the cryptographic operations +/// provided by the system. + +#[test] +fn test_encrypt_and_decrypt_rsa() { + let mut provider = TpmProvider::new("test_rsa_key".to_string()); + + let config = TpmConfig::new( + AsymmetricEncryption::Rsa(KeyBits::Bits4096), + BlockCiphers::Aes(SymmetricMode::Gcm, KeyBits::Bits512), + Hash::Sha2(Sha2Bits::Sha256), + vec![KeyUsage::SignEncrypt, KeyUsage::Decrypt], + ); + + provider + .initialize_module() + .expect("Failed to initialize module"); + provider + .create_key("test_rsa_key", config) + .expect("Failed to create RSA key"); + + let data = b"Hello, World!"; + let encrypted_data = provider.encrypt_data(data).expect("Failed to encrypt data"); + let decrypted_data = provider + .decrypt_data(&encrypted_data) + .expect("Failed to decrypt data"); + + assert_eq!(data, decrypted_data.as_slice()); +} + +#[test] +fn test_encrypt_and_decrypt_ecdh() { + let mut provider = TpmProvider::new("test_ecdh_key".to_string()); + + let config = TpmConfig::new( + AsymmetricEncryption::Ecc(EccSchemeAlgorithm::EcDh(EccCurves::Curve25519)), + BlockCiphers::Aes(SymmetricMode::Gcm, KeyBits::Bits512), + Hash::Sha2(Sha2Bits::Sha256), + vec![KeyUsage::SignEncrypt, KeyUsage::Decrypt], + ); + + provider + .initialize_module() + .expect("Failed to initialize module"); + provider + .create_key("test_ecdh_key", config) + .expect("Failed to create ECDH key"); + + let data = b"Hello, World!"; + let encrypted_data = provider.encrypt_data(data).expect("Failed to encrypt data"); + let decrypted_data = provider + .decrypt_data(&encrypted_data) + .expect("Failed to decrypt data"); + + assert_eq!(data, decrypted_data.as_slice()); +} +*/ diff --git a/src/tests/hsm/yubikey/mod.rs b/src/tests/hsm/yubikey/mod.rs new file mode 100644 index 00000000..9d9955fb --- /dev/null +++ b/src/tests/hsm/yubikey/mod.rs @@ -0,0 +1,2 @@ +mod key_handle_tests; +mod provider_handle_tests; \ No newline at end of file diff --git a/src/tests/hsm/yubikey/provider_handle_tests.rs b/src/tests/hsm/yubikey/provider_handle_tests.rs new file mode 100644 index 00000000..6ded5846 --- /dev/null +++ b/src/tests/hsm/yubikey/provider_handle_tests.rs @@ -0,0 +1,232 @@ +/// # Test Cases for YubiKeyProvider +/// +/// The `YubiKeyProvider` module facilitates the creation and loading of cryptographic keys +/// on a YubiKey device. This module contains several test cases to ensure the functionality +/// of key creation and loading for different key types and sizes. +/// +/// ## Test Cases +/// +/// - `test_create_rsa_key_1024`: Tests the creation of a 1024-bit RSA key pair on the YubiKey device. +/// - `test_create_rsa_key_2048`: Tests the creation of a 2048-bit RSA key pair on the YubiKey device. +/// - `test_create_ecc_key_256`: Tests the creation of a 256-bit ECC key pair on the YubiKey device. +/// - `test_create_ecc_key_384`: Tests the creation of a 384-bit ECC key pair on the YubiKey device. +/// - `test_load_rsa_key`: Tests the loading of an RSA key pair from the YubiKey device. +/// - `test_load_ecc_key`: Tests the loading of an ECC key pair from the YubiKey device. +/// +/// ## Test Procedures +/// +/// Each test case follows a similar procedure: +/// +/// 1. **Initialization**: Initializes the `YubiKeyProvider` with the necessary parameters, such as +/// the key ID and configuration. +/// 2. **Module Initialization**: Initializes the HSM module on the YubiKey device. +/// 3. **Key Creation/Loading**: Either creates a new key pair or loads an existing key pair +/// based on the test case requirements. +/// +/// ## Test Parameters +/// +/// Each test case specifies the key type, size, and usage: +/// +/// - **RSA Keys**: Test cases for RSA keys specify either 1024-bit or 2048-bit key sizes +/// with key usage for signing and encryption. +/// - **ECC Keys**: Test cases for ECC keys specify either 256-bit or 384-bit key sizes +/// with key usage for signing and encryption. +/// +/// ## Test Assumptions +/// +/// These test cases assume that a YubiKey device is connected and properly configured for +/// cryptographic operations. They also assume that the YubiKey device is accessible via +/// the system's USB interface. +/// +/// ## Expected Behavior +/// +/// The expected behavior for each test case is successful key creation or loading without +/// encountering any errors. Any failures during key creation or loading are considered test +/// failures and will be reported accordingly. +/// +/// Please use **cargo test --features yubi -- --test-threads=1** for successful testing due to parallelization issues +#[allow(unused_imports)] +use crate::common::{ + crypto::{ + algorithms::{ + encryption::{AsymmetricEncryption, BlockCiphers, EccCurves, EccSchemeAlgorithm}, + hashes::Hash, + KeyBits, + }, + KeyUsage, + }, + traits::module_provider::Provider, +}; +use crate::hsm::yubikey::YubiKeyProvider; +use crate::hsm::HsmProviderConfig; + +// Tests for creating 1024-bit RSA keys +#[cfg(feature = "yubi")] +#[test] +fn test_create_rsa_key_1024() { + let key_id = "test_rsa_key_1024"; + let mut provider = YubiKeyProvider::new(key_id.to_string()); + let config = HsmProviderConfig::new(AsymmetricEncryption::Rsa(KeyBits::Bits1024)); + + //initialize HSM-module + provider + .initialize_module() + .expect("Failed to initialize module"); + + //generate RSA-keypair + provider + .create_key(key_id, config) + .expect("Failed to create RSA key"); +} + +// Tests for creating 2048-bit RSA keys +#[cfg(feature = "yubi")] +#[test] +fn test_create_rsa_key_2048() { + let key_id = "test_rsa_key_2048"; + let mut provider: YubiKeyProvider = YubiKeyProvider::new(key_id.to_string()); + + let config = HsmProviderConfig::new(AsymmetricEncryption::Rsa(KeyBits::Bits2048)); + + // initialize HSM-module + provider + .initialize_module() + .expect("Failed to initialize module"); + + // generate RSA-keypair + provider + .create_key(key_id, config) + .expect("Failed to create RSA key"); +} + +//Tests for creating 256-bit ECC keys +#[cfg(feature = "yubi")] +#[test] +fn test_create_ecc_key_256() { + let key_id = "test_ecc_key_256"; + + let mut provider = YubiKeyProvider::new(key_id.to_string()); + + let config = HsmProviderConfig::new(AsymmetricEncryption::Ecc(EccSchemeAlgorithm::EcDsa( + EccCurves::P256, + ))); + + // initialize HSM-module + provider + .initialize_module() + .expect("Failed to initialize module"); + + // generate ECC-keypair + provider + .create_key(key_id, config) + .expect("Failed to create ECC key"); +} + +// Tests for creating 384-bit ECC keys +#[cfg(feature = "yubi")] +#[test] +fn test_create_ecc_key_384() { + let key_id = "test_ecc_key_384"; + + let mut provider = YubiKeyProvider::new(key_id.to_string()); + + let config = HsmProviderConfig::new(AsymmetricEncryption::Ecc(EccSchemeAlgorithm::EcDsa( + EccCurves::P384, + ))); + + // initialize HSM-module + provider + .initialize_module() + .expect("Failed to initialize module"); + + // generate ECC-keypair + provider + .create_key(key_id, config) + .expect("Failed to create ECC key"); +} + +// Test for loading RSA keys +#[cfg(feature = "yubi")] +#[test] +fn test_load_rsa_key_1024() { + let key_id = "test_rsa_key_1024"; + let mut provider = YubiKeyProvider::new(key_id.to_string()); + + let config = HsmProviderConfig::new(AsymmetricEncryption::Rsa(KeyBits::Bits1024)); + + // initialize HSM-module + provider + .initialize_module() + .expect("Failed to initialize module"); + + // load RSA-key + provider + .load_key(&key_id, config) + .expect("Failed to load RSA key"); +} + +// Test for loading RSA keys +#[cfg(feature = "yubi")] +#[test] +fn test_load_rsa_key_2048() { + let key_id = "test_rsa_key_2048"; + let mut provider = YubiKeyProvider::new(key_id.to_string()); + + let config = HsmProviderConfig::new(AsymmetricEncryption::Rsa(KeyBits::Bits1024)); + + // initialize HSM-module + provider + .initialize_module() + .expect("Failed to initialize module"); + + // load RSA-key + provider + .load_key(&key_id, config) + .expect("Failed to load RSA key"); +} + +// Test to load an ECC key +#[cfg(feature = "yubi")] +#[test] +fn test_load_ecc_key_256() { + let key_id = "test_ecc_key_256"; + + let mut provider = YubiKeyProvider::new(key_id.to_string()); + + let config = HsmProviderConfig::new(AsymmetricEncryption::Ecc(EccSchemeAlgorithm::EcDsa( + EccCurves::P256, + ))); + + // initialize HSM-module + provider + .initialize_module() + .expect("Failed to initialize module"); + + // load ECC-key + provider + .load_key(key_id, config) + .expect("Failed to load ECC key"); +} + +// Test to load an ECC key +#[cfg(feature = "yubi")] +#[test] +fn test_load_ecc_key_384() { + let key_id = "test_ecc_key_384"; + + let mut provider = YubiKeyProvider::new(key_id.to_string()); + + let config = HsmProviderConfig::new(AsymmetricEncryption::Ecc(EccSchemeAlgorithm::EcDsa( + EccCurves::P256, + ))); + + // initialize HSM-module + provider + .initialize_module() + .expect("Failed to initialize module"); + + // load ECC-key + provider + .load_key(key_id, config) + .expect("Failed to load ECC key"); +}