DEV Community

Cover image for about kelvin, a terminal password manager i'm building
kekeli🦀
kekeli🦀

Posted on

about kelvin, a terminal password manager i'm building

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,
}
Enter fullscreen mode Exit fullscreen mode

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
text me on discord to join contributors here

Top comments (0)