diff --git a/src/utils/crypto/crypto.md b/src/utils/crypto/crypto.md new file mode 100644 index 0000000..6c624b6 --- /dev/null +++ b/src/utils/crypto/crypto.md @@ -0,0 +1,203 @@ +# Crypto Utility Module (`crypto.rs`) + +This document provides detailed documentation for the `CryptoUtils` module, a Rust implementation for essential cryptographic operations. The module offers functionalities for symmetric encryption/decryption and asymmetric key pair management. + +## Table of Contents + +1. [Overview](#overview) +2. [Dependencies](#dependencies) +3. [Error Handling](#error-handling) +4. [Core Structures](#core-structures) + - [`KeyPair`](#keypair) +5. [Static Properties](#static-properties) + - [`KEY`](#key) + - [`IV`](#iv) +6. [API Functions](#api-functions) + - [Symmetric Encryption](#symmetric-encryption) + - [`encrypt`](#encrypt) + - [`decrypt`](#decrypt) + - [Asymmetric Key Management](#asymmetric-key-management) + - [`generate_key_pair`](#generate_key_pair) + - [`save_keys_to_files`](#save_keys_to_files) + - [`load_keys_from_files`](#load_keys_from_files) + - [`init_keys`](#init_keys) +7. [Usage Examples](#usage-examples) + +--- + +### Overview + +The `CryptoUtils` module provides a set of static methods to perform common cryptographic tasks. It is designed to be a centralized utility for handling both symmetric (AES-256-CBC) and asymmetric (RSA-4096) cryptography. + +### Dependencies + +This module requires the following dependencies to be added to your `Cargo.toml`: + +```toml +[dependencies] +openssl = "0.10" +sha2 = "0.10" +hex = "0.4" +once_cell = "1.19" # For lazy static initialization +``` + +### Error Handling + +The module defines a custom `CryptoError` enum to handle various failure scenarios, providing clear and specific error information. + +- `OpenSsl`: Wraps errors from the `openssl` crate. +- `Io`: For file system I/O errors (e.g., reading/writing keys). +- `Hex`: For errors during hex encoding/decoding. +- `Utf8`: For errors converting byte slices to UTF-8 strings. +- `Custom`: For other specific, custom error messages. + +### Core Structures + +#### `KeyPair` + +A public struct that holds a pair of RSA keys. + +- `private_key: String`: The PEM-encoded private key. +- `public_key: String`: The PEM-encoded public key. + +### Static Properties + +#### `KEY` + +A statically initialized 32-byte array used as the secret key for AES-256-CBC encryption and decryption. It is derived by applying SHA-256 to a hardcoded salt phrase, ensuring a consistent key across the application. + +#### `IV` + +A 16-byte initialization vector used for the AES-256-CBC algorithm. + +### API Functions + +All functions are implemented as static methods on the `CryptoUtils` struct. + +#### Symmetric Encryption + +##### `encrypt` + +`pub fn encrypt(secret: &str) -> Result` + +Encrypts a string slice using AES-256-CBC. + +- **Parameters**: + - `secret`: The plaintext string to encrypt. +- **Returns**: A `Result` containing the hex-encoded ciphertext string or a `CryptoError`. + +##### `decrypt` + +`pub fn decrypt(encrypted_secret: &str) -> Result` + +Decrypts a hex-encoded ciphertext string using AES-256-CBC. + +- **Parameters**: + - `encrypted_secret`: The hex-encoded ciphertext. +- **Returns**: A `Result` containing the decrypted plaintext string or a `CryptoError`. + +#### Asymmetric Key Management + +##### `generate_key_pair` + +`pub fn generate_key_pair() -> Result` + +Generates a new 4096-bit RSA key pair. + +- **Returns**: A `Result` containing a `KeyPair` struct with the new PEM-encoded keys or a `CryptoError`. + +##### `save_keys_to_files` + +`pub fn save_keys_to_files(keys: &KeyPair, directory: &Path) -> Result<(), CryptoError>` + +Saves a `KeyPair` to the specified directory in two files: `private.pem` and `public.pem`. + +- **Parameters**: + - `keys`: A reference to the `KeyPair` to save. + - `directory`: The path to the directory where the keys will be saved. + +##### `load_keys_from_files` + +`pub fn load_keys_from_files(directory: &Path) -> Result` + +Loads an RSA key pair from `private.pem` and `public.pem` files in a given directory. + +- **Parameters**: + - `directory`: The path to the directory containing the key files. +- **Returns**: A `Result` containing the loaded `KeyPair` or a `CryptoError`. + +##### `init_keys` + +`pub fn init_keys() -> Result` + +A convenience function that initializes the RSA key pair for the application. It first checks if the keys exist in the default `./keys` directory. + +- If the keys exist, it loads them. +- If they do not exist, it generates a new pair and saves them to the `./keys` directory. +- **Returns**: A `Result` containing the initialized `KeyPair` or a `CryptoError`. + +### Usage Examples + +#### Example 1: AES Encryption and Decryption + +```rust +use your_project::utils::crypto::crypto::CryptoUtils; + +fn main() { + let secret_message = "This is a highly confidential message."; + + // Encrypt the message + match CryptoUtils::encrypt(secret_message) { + Ok(encrypted) => { + println!("Original: {}", secret_message); + println!("Encrypted: {}", encrypted); + + // Decrypt the message + match CryptoUtils::decrypt(&encrypted) { + Ok(decrypted) => { + println!("Decrypted: {}", decrypted); + assert_eq!(secret_message, decrypted); + }, + Err(e) => eprintln!("Decryption failed: {}", e), + } + }, + Err(e) => eprintln!("Encryption failed: {}", e), + } +} +``` + +#### Example 2: RSA Key Pair Initialization and Management + +This example demonstrates how to ensure RSA keys are available for the application. + +```rust +use your_project::utils::crypto::crypto::CryptoUtils; +use std::fs; +use std::path::Path; + +fn main() { + // Clean up previous keys for demonstration purposes + if Path::new("keys").exists() { + fs::remove_dir_all("keys").unwrap(); + } + + println!("Attempting to initialize keys..."); + + // Use init_keys to either generate or load keys + match CryptoUtils::init_keys() { + Ok(keys) => { + println!("Keys initialized successfully."); + println!("Public Key (first 50 chars): {}...", &keys.public_key[..50]); + + // Calling it again should now load the existing keys + println!("\nCalling init_keys again..."); + let loaded_keys = CryptoUtils::init_keys().unwrap(); + assert_eq!(keys.public_key, loaded_keys.public_key); + println!("Keys loaded successfully from files."); + }, + Err(e) => { + eprintln!("Failed to initialize keys: {}", e); + } + } +} +``` diff --git a/src/utils/jwt/jwt.md b/src/utils/jwt/jwt.md new file mode 100644 index 0000000..b96cdfb --- /dev/null +++ b/src/utils/jwt/jwt.md @@ -0,0 +1,186 @@ +# JWT Utility Module Documentation + +## Overview + +The `JWTUtils` module provides a comprehensive suite of tools for creating, signing, verifying, and managing JSON Web Tokens (JWTs) in Rust. It is designed to work seamlessly with the `CryptoUtils` module for RSA key management. This implementation handles JWTs manually using the `openssl` crate for cryptographic operations, avoiding external JWT-specific libraries. + +The module supports standard claims like `exp` (expiration time), `iat` (issued at), and `iss` (issuer), and allows for custom payloads. + +## Dependencies + +To use this module, ensure the following dependencies are included in your `Cargo.toml` file: + +```toml +[dependencies] +openssl = "0.10" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +chrono = "0.4" +base64 = "0.21" +``` + +## Core Components + +### `JWTError` Enum + +A custom error type that consolidates all potential failures within the module, including issues from `openssl`, `serde_json`, `base64`, and invalid token logic. + +- `OpenSsl(openssl::error::ErrorStack)`: An error from the OpenSSL library. +- `SerdeJson(serde_json::Error)`: An error during JSON serialization or deserialization. +- `Base64(base64::DecodeError)`: An error during Base64 decoding. +- `Crypto(String)`: An error related to cryptographic key loading from `CryptoUtils`. +- `InvalidTokenFormat(String)`: The token string is malformed (e.g., wrong number of segments). +- `Validation(String)`: The token failed a validation check (e.g., signature invalid, expired). +- `Custom(String)`: A generic error for other specific issues. + +### `JWTOptions` Struct + +Defines the configurable options for creating a JWT. + +- `algorithm: String`: The signing algorithm (defaults to `"RS256"`). +- `expires_in: i64`: The token's lifetime in seconds. Defaults to `3600` (1 hour) or the value of the `JWT_EXPIRES_IN` environment variable. +- `issuer: String`: The issuer of the token. Defaults to an empty string or the value of the `JWT_ISSUER` environment variable. + +### `JWTUtils` Struct + +The main struct for handling JWT operations. An instance of `JWTUtils` is typically created to generate a new token. + +- `payload: Value`: The custom payload for the JWT, represented as a `serde_json::Value`. +- `private_key: String`: The PEM-encoded RSA private key for signing. +- `public_key: String`: The PEM-encoded RSA public key for verification. +- `options: JWTOptions`: The configuration options for the token. + +## Instantiation + +### `new(payload: Value, private_key: Option, public_key: Option, options: Option) -> Result` + +Creates a new `JWTUtils` instance. + +- **payload**: The custom data to include in the token. +- **private_key / public_key**: Optional RSA keys. If not provided, the constructor will attempt to load them from the default `keys/` directory using `CryptoUtils::load_keys_from_files()`. +- **options**: Optional `JWTOptions`. If not provided, default values will be used. + +## Instance Methods + +### `create_token(&self) -> Result` + +Generates and signs a complete JWT string. The process involves: + +1. Creating the JWT header (`{"alg": "RS256", "typ": "JWT"}`). +2. Processing the payload by adding standard claims (`iat`, `exp`, `iss`). +3. Base64URL-encoding the header and payload. +4. Creating a signature by signing the encoded header and payload with the RSA private key. +5. Combining the three parts into the final `header.payload.signature` format. + +### `verify(&self, token: &str) -> bool` + +Verifies the signature of a given JWT using the public key stored in the `JWTUtils` instance. + +- **Returns**: `true` if the signature is valid, `false` otherwise. +- **Note**: This method **only** checks the signature. It does not validate the expiration time or other claims. For comprehensive validation, use `decode_and_verify`. + +## Static Methods + +### `decode(token: &str) -> Result<(Value, Value), JWTError>` + +Decodes a JWT string into its header and payload components without verifying the signature. + +- **Returns**: A tuple `(header, payload)` where both elements are of type `serde_json::Value`. +- **Use Case**: Useful for inspecting token data when the signature's validity is not a concern (e.g., for logging or preliminary checks). + +### `is_expired(token: &str) -> bool` + +Checks if a token has expired. It decodes the token and compares the `exp` claim to the current UTC time. + +- **Returns**: `true` if the token is expired, has no `exp` claim, or is malformed. `false` otherwise. + +### `decode_and_verify(token: &str) -> Result<(Value, Value), JWTError>` + +A comprehensive function that performs all necessary validations on a token: + +1. Verifies the token's signature using the public key loaded from the default `keys/` directory. +2. Checks if the token has expired. +3. If both checks pass, it decodes and returns the header and payload. + +- **Returns**: `Ok((header, payload))` on success, or a `JWTError` if validation fails for any reason. + +### `refresh_token(old_token: &str) -> Result` + +Generates a new token based on the payload of an old token. The `iat` and `exp` claims are stripped from the original payload and replaced with new ones. + +- **Note**: This function loads the RSA keys from the `keys/` directory to sign the new token. + +### `validate_claims(token: &str, required_claims: &[&str]) -> bool` + +Checks for the presence of specific keys in the token's payload. + +- **required_claims**: A slice of strings representing the keys that must be present. +- **Returns**: `true` if all required claims exist, `false` otherwise. + +## Usage Examples + +### Example 1: Creating a JWT + +```rust +use serde_json::json; +use your_project::utils::jwt::JWTUtils; + +fn create_new_token() { + // The custom data for the token + let payload = json!({ + "user_id": "12345", + "roles": ["admin", "user"] + }); + + // Keys can be provided directly or loaded automatically from the 'keys/' directory + // If None, the constructor will try to load them from files. + let jwt_instance = JWTUtils::new(payload, None, None, None).unwrap(); + + match jwt_instance.create_token() { + Ok(token) => println!("Generated Token: {}", token), + Err(e) => eprintln!("Error creating token: {}", e), + } +} +``` + +### Example 2: Verifying and Decoding a JWT + +```rust +use your_project::utils::jwt::JWTUtils; + +fn validate_and_read_token(token: &str) { + match JWTUtils::decode_and_verify(token) { + Ok((header, payload)) => { + println!("Token is valid!"); + println!("Header: {:?}", header); + println!("Payload: {:?}", payload); + }, + Err(e) => { + eprintln!("Token validation failed: {}", e); + } + } + + // You can also check for specific claims + if JWTUtils::validate_claims(token, &["user_id", "roles"]) { + println!("All required claims are present."); + } +} +``` + +### Example 3: Refreshing a Token + +```rust +use your_project::utils::jwt::JWTUtils; + +fn refresh_existing_token(old_token: &str) { + match JWTUtils::refresh_token(old_token) { + Ok(new_token) => { + println!("Token refreshed successfully!"); + println!("New Token: {}", new_token); + }, + Err(e) => { + eprintln!("Failed to refresh token: {}", e); + } + } +} +```