about kelvin, a terminal password manager i'm building
Tags: rust, security, linux
Published: 2024-06-09 19:03:16

### overview
kelvin is a password manager for the linux terminal, it generates passwords, and it could be used as a vault to save and secure passwords. kelvin creates your vault locally. vault is encrypted and made a hidden directory.

in building kelvin, i depended on a skeleton of three structs, admin, deck and deck_data.

```
pub struct Admin {
    pub username: String,
    pub password: String,
}

pub struct Deck {
    pub domain: String,
    pub plaintext: String,
}

pub struct DeckData {
    pub domain: String,
    pub ciphertext: Vec<u8>,
    pub admin_data: Admin,
    pub rsa_public_key: String,
    pub rsa_private_key: String,
}
```

### admin.rs
the admin struct has the username and passwords fields. implementations on admin struct mainly deal with creating an administrator, validating administator and serialization and deserialization.

### implemetations on admin struct

`new()`:

- This is a constructor method for creating an Admin instance.
- It takes a name (username) and a password as arguments.
- It initializes the Admin struct with the provided username and password.

`hash_password()`:

- This method hashes the admin's password for security.
- It uses the bcrypt crate to hash the password with a default cost.
- The hashed password replaces the original password in the struct.

`verify_password()`:

- This method verifies a password against the hashed password stored in the Admin struct.
- It compares the provided password with the stored hashed password.
- Returns true if the provided password matches the stored hashed password, otherwise false.

`save_to_json()`:

- Serializes the Admin struct to JSON and saves it to a file.
- It converts the struct to a JSON string using serde_json.
- Constructs a filepath based on the admin's username and a predefined constant VAULT_PATH.
- Writes the JSON string representation of the admin data to the file.
- Calls `encrypt_directory()` to encrypt the directory containing the file.

`read_data_from_json()`:

- Reads admin data from a JSON file and deserializes it into an Admin struct.
- Constructs a filepath based on the admin's username and `VAULT_PATH`.
- Calls `decrypt_directory()` to decrypt the directory containing the file.
- Reads the contents of the file into a string.
- Deserializes the JSON string into an Admin struct.
- Returns the deserialized Admin struct.

`prompt_auth()`:

- Prompts for authentication by comparing provided username and password with stored admin credentials.
- Reads admin data from JSON using `read_data_from_json()`.
- Compares the provided username with the stored username and verifies the provided password.
- Returns true if both username and password match, otherwise false.

### deck.rs
deck struct manages the creation of a deck, a single data that has a domain and it's password(plaintext).

### implementation`s on deck struct
`new()`:

- Constructor method for creating a new Deck instance.
- Takes a domain and plaintext as arguments.
- Initializes the Deck struct with the provided domain and plaintext.

`encrypt()`:

- Encrypts the plaintext data of the deck using RSA encryption.
- Calls `get_keys()` to obtain RSA keys and a random number generator.
- Converts the plaintext to bytes.
- Encrypts the plaintext using RSA public key encryption (PKCS#1 v1.5 padding).
- Returns a tuple containing the encrypted data and the RSA keys.

`read_data_from_json():`

- Reads deck data from a JSON file and deserializes it into a DeckData struct (not defined in this snippet).
- Constructs a filepath based on the deck's domain and a predefined constant `VAULT_PATH`.
- Calls `decrypt_directory()` to decrypt the directory containing the file.
- Reads the contents of the file into a string.
- Deserializes the JSON string into a `Vec<DeckData>`.
- Returns the first DeckData from the vector or an error if no data is found.

### deckdata.rs

deckdata struct deals serializing of the deck to a json file and saving the deck.

### implementations on deckdata struct
`new()`:

- This is a constructor method for `DeckData`.
- It takes arguments to initialize the fields of `DeckData`, including Admin data, domain, ciphertext, RSA public key, and RSA private key.
- It converts RSA public and private keys to PKCS#1 PEM format and initializes the struct.

- `serialize_struct()`:
- Serializes the struct to a JSON string.
- Uses the serde_json crate to convert the struct into a JSON string.
- Returns the JSON string representation of the struct.

`save_to_json()`:

- Serializes the struct to JSON and saves it to a file.
- Constructs a filepath based on the domain and a predefined constant `VAULT_PATH`.
- Writes the JSON string representation of the struct to the file.
- Calls `encrypt_directory()` (defined in data.rs) to encrypt the directory.
- Returns a Result indicating success or failure.

`read_data_from_json()`:

- Reads JSON data from a file, deserializes it into a `DeckData` struct.
- Constructs a filepath based on the domain and `VAULT_PATH`.
- Calls `decrypt_directory() `(defined in data.rs) to decrypt the directory.
- Reads the contents of the file into a string.
- Deserializes the JSON string into a `Vec<DeckData>`.
- Returns the first `DeckData` from the vector or an error if no data is found.

`decrypt()`:

- Decrypts the ciphertext using RSA private key.
- Parses the RSA public and private keys from their PEM representations.
- Decrypts the ciphertext using RSA with PKCS#1 v1.5 padding.
- Returns the decrypted data.

### helping scripts 
aside the main skeleton, other scripts contain helping functions that help make kelvin work as a whole.

### `data.rs`

`check_file_exists(username: &str, directory_path: &str) -> bool`:

- Checks if a file with the given username exists in the directory_path.
- Iterates through the directory to find the file.
- Returns true if the file exists, otherwise false.

`read_user_data(username: &str, directory_path: &str) -> Option<Admin>`:

- Reads user data from a file specified by username and directory_path.
- If the file exists, reads its content, deserializes it into an Admin struct, and returns it.
- Returns None if the file doesn't exist or if there's an error in reading/deserializing.

`read_deck_data(domain: &str) -> Option<deckdata::DeckData>`:

- Reads deck data from a JSON file specified by domain and `VAULT_PATH`.
- If the file exists, reads its content, deserializes it into a DeckData struct, and returns it.
- Returns None if the file doesn't exist or if there's an error in reading/deserializing.

`encrypt_directory() -> std::io::Result<()>`:

- Encrypts the directory containing encrypted data.
- Archives the directory into a .tar.gz file.
- Encrypts the .tar.gz file using gpg.
- Deletes the original directory and the .tar.gz file.
- Returns a Result indicating success or failure.

`decrypt_directory() -> std::io::Result<()>`:

- Decrypts the directory containing encrypted data.
- Decrypts the .tar.gz.gpg file using gpg.
- Extracts the decrypted .tar.gz file.
- Deletes the encrypted .tar.gz.gpg file.
- Returns a Result indicating success or failure.

### `password.rs`

`generate_password(length: usize) -> String`:

- Generates a password from printable ASCII characters, including symbols, digits, uppercase, and lowercase letters.
- Randomly selects characters from the ASCII character set to create the password of the specified length.
- Shuffles the characters within the password to enhance randomness and security.
- Returns the generated password as a string.

### `prompt.rs`

`prompt_deck()`:

- Prompts the user to enter a domain and its password.
- Reads input from the user for domain and password.
- Returns a tuple containing the entered domain and password.

`prompt_deck_open_sesame()`:

- Prompts the user to enter a domain.
- Reads input from the user for the domain.
- Returns the entered domain.

`prompt_logins()`:

- Prompts the user to enter an admin username and password.
- Reads input from the user for the admin username and password.
- Returns a tuple containing the entered admin username and password.

`initialize_vault()`:

- Checks if the vault directory exists, and if not, creates it.
- Initializes the vault directory for storing encrypted data.
- Returns a result indicating success or failure.

`clip(text: &str)`:

- Sets the system clipboard content to the provided text.
- Uses the clipboard crate to interact with the system clipboard.
- Pauses execution for 2 seconds to ensure the clipboard content is set before returning.

### kelvin as a whole
in `main.rs` is where kelvin works as a whole. clap is used as the argument parser where kelvin demands commands to channel it's operations.

commands include;
- `generate`
- `create-admin`
- `deck` whick is used to add or create a deck
- `reset`
- `open-sesame` which is used to get a password to a domain in the vault
- `help`

### conclusion
this project appears to be laying the groundwork for a terminal-based password manager, or "vault," with a focus on secure storage and retrieval of user and domain credentials. working features include adding a deck, retrieving passwords, generating passwords and interacting with the system clipboard.

in the future, the project aims to evolve into a fully functional terminal vault with a user-friendly terminal interface. It will likely include features such as:

- Secure storage of user credentials and domain passwords.
- User authentication to access stored credentials.
- Terminal-based user interface for easy interaction.
- Daemonization to allow the vault to run persistently in the background.
- Command-line interface for starting, stopping, and managing the vault daemon.

overall, the project aims to provide a convenient and secure solution for managing passwords and sensitive data from the terminal, offering both ease of use and robust security features.

contribute to kelvin on [github](github.com/db-keli/kelvin)
text me on discord to join contributors [here](discordapp.com/users/1083741166492733500)