docs: Add documentation for Crypto and JWT modules
This commit adds detailed documentation for the and modules. The new markdown files ( and ) provide a comprehensive overview of each module's functionality, including: - Dependencies and setup. - Error handling strategies. - Detailed descriptions of structs and functions. - Practical usage examples for key operations.
This commit is contained in:
203
src/utils/crypto/crypto.md
Normal file
203
src/utils/crypto/crypto.md
Normal file
@@ -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<String, CryptoError>`
|
||||||
|
|
||||||
|
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<String, CryptoError>`
|
||||||
|
|
||||||
|
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<KeyPair, CryptoError>`
|
||||||
|
|
||||||
|
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<KeyPair, CryptoError>`
|
||||||
|
|
||||||
|
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<KeyPair, CryptoError>`
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
186
src/utils/jwt/jwt.md
Normal file
186
src/utils/jwt/jwt.md
Normal file
@@ -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<String>, public_key: Option<String>, options: Option<JWTOptions>) -> Result<Self, JWTError>`
|
||||||
|
|
||||||
|
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<String, JWTError>`
|
||||||
|
|
||||||
|
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<String, JWTError>`
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
Reference in New Issue
Block a user