Categories
J&S-Soft Overview
J&S-Soft GmbH, based in Heidelberg, is an international consulting and software development company specializing in technologies such as SAP HANA, SAP Business Technology Platform (BTP), S/4HANA, as well as SAP UI5 and SAP Fiori. In addition to these services, J&S-Soft GmbH develops its own software solutions, including the open-source framework enmeshed. This framework enables simple, intelligent, and secure GDPR-compliant data transfers, even extending to private users.
Since its founding, the company has established numerous partnerships, leading to continuous revenue growth. Notably, in 2023, J&S-Soft GmbH generated 40% of its revenue from products related to the enmeshed framework. Continued strong revenue growth is expected for 2024. To maintain this success, the further development of the enmeshed framework is of great importance.
CEP SoSe24 Project Overview
Objectives
With the planned project “CEP SoSe24,” J&S-Soft GmbH aims to achieve the following objectives:
- Significant Advancement of the Enmeshed Framework
- Enhance and expand the existing capabilities of the enmeshed framework to support more robust and scalable applications.
- Secure Generation and Management of Multiple Keys for Various Cryptographic Applications
- Develop and implement mechanisms to securely generate and manage numerous keys essential for different cryptographic procedures.
- Increase Compatibility through the Integration of Various Hardware Security Modules
- Improve the compatibility of the enmeshed framework by integrating different hardware security modules (HSMs) to enhance security and performance.
- Evaluation of Previous Developments and Incorporation of New Ideas into the Enmeshed Framework
- Assess the current developments and integrate new concepts and innovations to continuously improve the enmeshed framework.
Project-Specific Conditions
The following chapters describe the conditions specific to the project.
Organizational Conditions
The development team at J&S-Soft GmbH employs various encryption methods within the enmeshed framework for secure communication and data exchange. In this context, a multitude of keys is generated and managed. A significant challenge is to generate these keys securely and protect them from unauthorized access. To adequately address this risk, the process will be secured using different Hardware Security Modules (HSMs) in the future.
Project Work Packages
The project is divided into three work packages:
AP-1: Conceptualization of Secure Key Management Using Hardware Security Modules
Develop a comprehensive concept for secure key management utilizing HSMs to enhance security and efficiency.
AP-2: Implementation Based on the Developed Concept
Execute the implementation of the secure key management system according to the conceptual framework established in AP-1.
AP-3: Creation of a Risk Assessment for the Developed Implementation
Conduct a thorough risk assessment of the implemented key management system to identify potential vulnerabilities and ensure robust security measures are in place.
Crypto Layer
The Crypto Layer is a comprehensive and flexible cryptographic library designed to provide a unified interface for various cryptographic operations and algorithms. It offers a wide range of functionalities, including encryption, decryption, signing, signature verification, and hashing, while supporting both symmetric and asymmetric cryptography.
Features
-
Encryption Algorithms: Supports a variety of encryption algorithms, including:
- Asymmetric Encryption: RSA, ECC (Elliptic Curve Cryptography) with various curve types (P-256, P-384, P-521, secp256k1, Brainpool curves, Curve25519, Curve448, FRP256v1)
- Symmetric Block Ciphers: AES (with multiple modes like GCM, CCM, ECB, CBC, CFB, OFB, CTR), Triple DES (two-key and three-key configurations), DES, RC2, Camellia
- Stream Ciphers: RC4, ChaCha20
-
Hashing Algorithms: Supports a wide range of hashing algorithms, including:
- SHA-1, SHA-2 (SHA-224, SHA-256, SHA-384, SHA-512, SHA-512/224, SHA-512/256)
- SHA-3 (SHA3-224, SHA3-256, SHA3-384, SHA3-512)
- MD2, MD4, MD5, RIPEMD-160
-
Key Management: Provides a unified interface for creating, loading, and managing cryptographic keys, supporting various key usages and algorithms.
-
Cross-Platform Support: Designed to work seamlessly across multiple platforms, including Linux and Windows, with platform-specific implementations for key handling and security module integration.
-
Security Module Integration: Integrates with Hardware Security Modules (HSMs) and Trusted Platform Modules (TPMs) for secure key storage and cryptographic operations, ensuring enhanced security and compliance with industry standards.
-
Extensibility: The modular design of the Crypto Layer allows for easy extension and integration of additional cryptographic algorithms and security modules in the future.
Usage
The Crypto Layer provides a comprehensive set of interfaces and enums for working with cryptographic operations and algorithms. Here’s a brief overview of the main components:
Encryption Algorithms
The encryption
module defines enums for various encryption algorithms, including:
AsymmetricEncryption
: Represents asymmetric encryption algorithms like RSA and ECC.BlockCiphers
: Represents symmetric block cipher algorithms like AES, Triple DES, DES, RC2, and Camellia.StreamCiphers
: Represents stream cipher algorithms like RC4 and ChaCha20.
Hashing Algorithms
The hashes
module defines the Hash
enum, which represents various hashing algorithms like SHA-1, SHA-2, SHA-3, MD2, MD4, MD5, and RIPEMD-160.
Key Management
The key_handle
module provides the KeyHandle
trait, which defines a common interface for cryptographic key operations like signing, decryption, encryption, and signature verification. The GenericKeyHandle
enum represents a platform-agnostic key handle that can be used on both Linux and Windows platforms.
Security Module Integration
The module_provider
module defines the Provider
trait, which encapsulates operations related to cryptographic processing and key management. This trait is designed to be implemented by security modules, ensuring a unified approach to interacting with different types of security modules.
The factory
module provides the SecModules
struct, which serves as a namespace for managing and accessing security module instances. It includes methods for retrieving or creating instances of security modules based on their type (HSM or TPM).
Error Handling
The error
module defines the SecurityModuleError
enum, which represents various types of errors that can occur within a security module, including errors originating from HSMs, TPMs, or during cryptographic operations like signing, decryption, encryption, and signature verification.
Usage Examples
Here are some usage examples based on the Windows TPM handler implementation:
Creating a TPM Provider
use crypto_layer::factory::{SecModules, SecurityModule};
use crypto_layer::tpm::core::instance::TpmType;
let key_id = "my_key_id".to_string();
let tpm_provider = SecModules::get_instances(key_id, SecurityModule::Tpm(TpmType::default()))
.expect("Failed to create TPM provider");
Initializing the TPM Module
use crypto_layer::common::error::SecurityModuleError;
match tpm_provider.lock().unwrap().initialize_module() {
Ok(()) => println!("TPM module initialized successfully"),
Err(e) => println!("Failed to initialize TPM module: {:?}", e),
}
Creating a Key
use crypto_layer::common::crypto::algorithms::{
encryption::{AsymmetricEncryption, BlockCiphers},
hashes::Hash,
};
use crypto_layer::common::KeyUsage;
let key_algorithm = AsymmetricEncryption::Ecc(EccSchemeAlgorithm::EcDsa(EccCurves::P256));
let sym_algorithm = Some(BlockCiphers::Aes(SymmetricMode::Cbc, KeyBits::Bits256));
let hash = Some(Hash::Sha2(Sha2Bits::Sha256));
let key_usages = vec![KeyUsage::SignEncrypt, KeyUsage::Decrypt];
match tpm_provider.lock().unwrap().create_key(
"my_key_id",
key_algorithm,
sym_algorithm,
hash,
key_usages,
) {
Ok(()) => println!("Key created successfully"),
Err(e) => println!("Failed to create key: {:?}", e),
}
Signing Data
let data = b"Hello, world!";
match tpm_provider.lock().unwrap().sign_data(data) {
Ok(signature) => println!("Signature: {:?}", signature),
Err(e) => println!("Failed to sign data: {:?}", e),
}
Verifying Signature
let data = b"Hello, world!";
let signature = // ... obtained signature ...
match tpm_provider.lock().unwrap().verify_signature(data, &signature) {
Ok(valid) => {
if valid {
println!("Signature is valid");
} else {
println!("Signature is invalid");
}
}
Err(e) => println!("Failed to verify signature: {:?}", e),
}
These examples demonstrate how to use the Windows TPM handler implementation to perform various cryptographic operations using the Crypto Layer.
Installation
The Crypto Layer is distributed as a Rust crate and can be included in your project by adding the following line to your Cargo.toml
file:
[dependencies]
crypto-layer = "0.1.0"
Contributing
Contributions to the Crypto Layer are welcome! If you find any issues or have suggestions for improvements, please open an issue or submit a pull request on the project’s GitHub repository.
License
The Crypto Layer is released under the MIT License.
YubiKey Provider Documentation
Overview
This module provides cryptographic operations using a YubiKey. It implements the Provider
trait and interacts with the YubiKey device for key management and cryptographic operations.
Table of Contents
Cryptographic Operations Test Documentation
This document provides a comprehensive overview of the test cases designed for cryptographic operations within the system. These tests focus on signing, verifying, encrypting, and decrypting data using RSA and ECC keys. The goal is to ensure the integrity and reliability of these operations, which are critical for the security of the system’s data and communications.
Overview
The purpose of this module is to thoroughly test cryptographic operations, specifically focusing on:
- Signing and verifying data with RSA and ECC keys.
- Encrypting and decrypting data using RSA keys.
Test Cases
Signing and Verifying Data
test_sign_and_verify_rsa_1024
- Description: Tests signing and verifying data using a 1024-bit RSA key.
- Steps:
- Initialize
YubiKeyProvider
with key ID and configuration. - Initialize the HSM module on the YubiKey device.
- Create a 1024-bit RSA key pair.
- Sign predefined data.
- Verify the signature of the signed data.
- Initialize
test_sign_and_verify_rsa_2048
- Description: Tests signing and verifying data using a 2048-bit RSA key.
- Steps: Follow the same steps as
test_sign_and_verify_rsa_1024
, but with a 2048-bit key configuration.
test_sign_and_verify_ecc_256
- Description: Tests signing and verifying data using a 256-bit ECC key.
- Steps: Follow the same steps as
test_sign_and_verify_rsa_1024
, but with a 256-bit ECC key configuration.
test_sign_and_verify_ecc_384
- Description: Tests signing and verifying data using a 384-bit ECC key.
- Steps: Follow the same steps as
test_sign_and_verify_rsa_1024
, but with a 384-bit ECC key configuration.
Encrypting and Decrypting Data
test_encrypt_and_decrypt_rsa_1024
- Description: Tests encrypting and decrypting data using a 1024-bit RSA key.
- Steps:
- Initialize
YubiKeyProvider
with key ID and configuration. - Initialize the HSM module on the YubiKey device.
- Create a 1024-bit RSA key pair.
- Encrypt predefined data.
- Decrypt the encrypted data and verify it matches the original data.
- Initialize
test_encrypt_and_decrypt_rsa_2048
- Description: Tests encrypting and decrypting data using a 2048-bit RSA key.
- Steps: Follow the same steps as
test_encrypt_and_decrypt_rsa_1024
, but with a 2048-bit key configuration.
Test Procedures
Each test case follows these general steps:
- Initialization: Set up the
YubiKeyProvider
with necessary parameters. - Module Initialization: Initialize the HSM module on the YubiKey device.
- Key Creation: Generate a key pair according to the test requirements.
- Operation: Perform the cryptographic operation (sign, verify, encrypt, decrypt).
- Verification: Ensure the operation’s outcome matches the expected result.
Test Parameters
- RSA Keys: Tests cover 1024-bit and 2048-bit key sizes.
- ECC Keys: Tests cover 256-bit and 384-bit key sizes.
Test Assumptions
- A YubiKey device is connected and configured correctly.
- The device is accessible via the system’s USB interface.
Expected Behavior
- Successful key creation, signing, verification, encryption, and decryption without errors.
- Failures in these operations are considered test failures and should be reported.
Dependencies
```rust 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::En ; use yubikey::MgmKey; Constants SLOTS rust
const SLOTS: [RetiredSlotId; 20] = [ RetiredSlotId::R1, RetiredSlotId::R2, // … remaining slots RetiredSlotId::R20, ]; SLOTSU32 rust
const SLOTSU32: [u32; 20] = [ 0x005f_c10d, // … remaining slots 0x005f_c120, ];
Provider Implementation create_key Creates a new cryptographic key identified by the provider-given key_id.#Example Below is an example of how to use the YubiKeyProvider to sign data. 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, 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.
pub struct YubiKeyProvider {
yubikey: Option<Mutex
impl KeyHandle for YubiKeyProvider {
/// Signs data using the cryptographic key on a YubiKey.
#[instrument]
fn sign_data(&self, data: &[u8]) -> Result<Vec
// 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<Zeroizing<Vec<u8>>, yubikey::Error>;
let mut vec_data: Vec<u8> = 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(),
))),
}
} }
Arguments key_id: A string slice that uniquely identifies the key. config: A boxed ProviderConfig containing configuration details for key generation. Returns A Result that, on success, contains Ok(()). On failure, it returns a SecurityModuleError.
Example rust
#[instrument]
fn create_key(
&mut self,
key_id: &str,
config: Box
Arguments key_id: A string slice that uniquely identifies the key. config: A boxed ProviderConfig containing configuration details. Returns A Result that, on success, contains Ok(()). On failure, it returns a SecurityModuleError.
Example rust
#[instrument]
fn load_key(&mut self, key_id: &str, config: Box
Arguments key_algorithm: The asymmetric encryption algorithm to be used for the key. Returns A Result that, on success, contains Ok(()). On failure, it returns a Yubikey based Error.
Example rust
#[instrument] fn initialize_module(&mut self) -> Result<(), SecurityModuleError> { // Implementation here } Helper Functions save_key_object Saves the key object to the YubiKey device.
Arguments yubikey: Reference to the YubiKey device. key_id: A string slice that uniquely identifies the key. slot_id: An address where an object will be stored. pkey: The public key which is intended to be stored. algo: Algorithm identifier. Returns A Result that, on success, contains Ok(()). On failure, it returns a yubikey::Error.
Example rust
fn save_key_object( yubikey: &mut YubiKey, key_id: &str, slot_id: u32, pkey: &str, algo: &str, ) -> Result<(), yubikey::Error> { // Implementation here } parse_slot_data Parses the data from a YubiKey slot.
Arguments data: Reference to the data array. Returns A Result that, on success, contains Ok((key_name, slot, public_key, algo)). On failure, it returns a SecurityModuleError.
Example rust
fn parse_slot_data(data: &[u8]) -> Result<(String, String, String, String), SecurityModuleError> { // Implementation here } get_free_slot Gets a free slot for storing a key object.
Arguments yubikey: Reference to the YubiKey device. Returns A Result that, on success, contains Ok(RetiredSlotId). On failure, it returns a SecurityModuleError.
Example rust
fn get_free_slot(yubikey: &mut YubiKey) -> Result<RetiredSlotId, SecurityModuleError> { // Implementation here } get_reference_u32slot Converts a RetiredSlotId to its corresponding u32 value.
Arguments slot: The RetiredSlotId to be converted. Returns The corresponding u32 value of the RetiredSlotId.
Example rust
fn get_reference_u32slot(slot: RetiredSlotId) -> u32 { // Implementation here } list_all_slots Lists all slots with their corresponding key information.
Arguments
yubikey: Reference to the YubiKey device.
Returns
A Result that, on success, contains Ok(Vec
Example rust
fn list_all_slots(yubikey: &mut YubiKey) -> Result<Vec
YubiKey Provider Documentation
Dependencies
```rust 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::En ; use yubikey::MgmKey; Constants SLOTS rust
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, ];
SLOTSU32
rust
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, ];
Provider Implementation
create_key Creates a new cryptographic key identified by the provider-given key_id.
Arguments key_id: A string slice that uniquely identifies the key. config: A boxed ProviderConfig containing configuration details for key generation. Returns A Result that, on success, contains Ok(()). On failure, it returns a SecurityModuleError.
Example rust
[instrument]
fn create_key(
&mut self,
key_id: &str,
config: Box
load_key
Loads an existing cryptographic key identified by key_id.
Arguments key_id: A string slice that uniquely identifies the key. config: A boxed ProviderConfig containing configuration details. Returns A Result that, on success, contains Ok(()). On failure, it returns a SecurityModuleError.
Example rust
#[instrument]
fn load_key(&mut self, key_id: &str, config: Box
initialize_module
Initializes the YubiKey module and returns a handle for cryptographic operations.
Arguments key_algorithm: The asymmetric encryption algorithm to be used for the key. Returns A Result that, on success, contains Ok(()). On failure, it returns a Yubikey based Error.
Example rust
#[instrument] fn initialize_module(&mut self) -> Result<(), SecurityModuleError> { // Implementation here } Helper Functions save_key_object Saves the key object to the YubiKey device.
Arguments
yubikey: Reference to the YubiKey device. key_id: A string slice that uniquely identifies the key. slot_id: An address where an object will be stored. pkey: The public key which is intended to be stored. algo: Algorithm identifier. Returns A Result that, on success, contains Ok(()). On failure, it returns a yubikey::Error.
Example rust
fn save_key_object( yubikey: &mut YubiKey, key_id: &str, slot_id: u32, pkey: &str, algo: &str, ) -> Result<(), yubikey::Error> { // Implementation here } parse_slot_data Parses the data from a YubiKey slot.
Arguments data: Reference to the data array. Returns A Result that, on success, contains Ok((key_name, slot, public_key, algo)). On failure, it returns a SecurityModuleError.
Example rust
fn parse_slot_data(data: &[u8]) -> Result<(String, String, String, String), SecurityModuleError> { // Implementation here } get_free_slot Gets a free slot for storing a key object.
Arguments yubikey: Reference to the YubiKey device. Returns A Result that, on success, contains Ok(RetiredSlotId). On failure, it returns a SecurityModuleError.
Example rust
fn get_free_slot(yubikey: &mut YubiKey) -> Result<RetiredSlotId, SecurityModuleError> { // Implementation here } get_reference_u32slot Converts a RetiredSlotId to its corresponding u32 value.
Arguments slot: The RetiredSlotId to be converted. Returns The corresponding u32 value of the RetiredSlotId.
Example rust
fn get_reference_u32slot(slot: RetiredSlotId) -> u32 { // Implementation here } list_all_slots Lists all slots with their corresponding key information.
Arguments
yubikey: Reference to the YubiKey device.
Returns
A Result that, on success, contains Ok(Vec
Example rust
fn list_all_slots(yubikey: &mut YubiKey) -> Result<Vec
Key Storage Security:
HSM (Hardware Security Module): Keys are stored within the cryptographic boundary of the HSM and are only used for cryptographic operations. Applications like OpenPGP on a YubiKey do not have direct access to private keys; they can only request the cryptographic processor to perform an operation using one of the protected keys (e.g., “please sign this hash using the private key with key handle N”). In summary, both TLS and OpenPGP can be used to implement different layers of security, with TLS securing the connection and OpenPGP protecting the data itself.