A New Password Manager
2020-04-12There are a lot of different password managers out there, a lot of good ones, but in one way or another, none of them quite hit the mark for me. The closest thing I found was Pass, but even that didn't quite fit the bill. It did inspire me to create my own though...
Introducing SecureStore, a Pass-like password manager. Written in Bash flavoured shell and using GPG to encrypt the contents of the store. Looking at it from a high level, it's very similar to Pass, but there are some notable differences once you look at it a little deeper.
This post isn't an introduction, it assumes you're generally familiar with the available functionality, so take a look at the GitHub README.md for the installation guide and an overview of how to use it. This post focuses on architecture, general design decisions and extendability.
Secure Foundations
SecureStore's architecture takes a layered approach, with each
layer building on functionality from the last. The foundational
layer, named securestore
, takes care of basic
functionality, providing helper methods and managing the GPG encryption. As with
all layers, it can be used without any of the layers above it, so in this
instance we can use it to create a simple encrypted store for any of your files.
The interface is designed around sub-commands, similar to git
. Each
sub-command has a corresponding function, so there's function within
securestore
called init
(that aliases ss_init
), add
(that aliases ss_add
), remove
(that aliases ss_remove
) and so on. As
mentioned, these functions alias prefixed versions of themselves in order to
allow higher layers of the software to override functionality provided by lower
layers without loosing access to said functionality.
The way sub-commands are created and how functionality is inherited is heavily based off of one of my previous posts, The Original Task Runner, so for a deeper understanding of what's going on there, you might want to pause here and have a quick read.
Version Controlled
Next up is the vcstore
layer. This builds on the foundations
created by the securestore
layer and adds version control. We can access this
functionality by running vcstore <sub-command>
.
Let's take a look at one of the sub-commands, add
:
vcs_add() {
ss_add "$@"
vcs_git_add "$1"
vcs_git_commit "Added '$1'"
}
add() { vcs_add "$@"; }
As you can see, add
is aliased to vcs_add
so that in higher layers, add
can be overridden. This provides a consistent and clean interface
to the user, but still allows access to the vcs_add
functionality if add
is
overwritten. In fact, we're overriding the add
sub-command from the
securestore
layer here, but still making use of it's functionality by calling
ss_add
. Using this pattern, we can re-use existing functionality in lower
layers and then add our version control code on top.
Once again, vcstore
can be used without any higher layers, so if you're after
an encrypted, version controlled note store, this would do the job nicely.
The Password Manager
The part you've all been waiting for, the password manager! Named pass
, this
layer adds password manager functionality to the previous two
layers. All the usual sub-commands are still available to us, most haven't
changed from the vcstore
layer, so we'll just look at the extra functionality
that's provided here.
This time, when you initialise a new store, it's automatically created in
$HOME/pass
instead of the current directory, this means that no matter where
you are on your file system, you can run pass <sub-command>
and it'll always
execute in the correct context.
In my opinion, the really cool thing about this layer is that every entry is a valid Bash script. Let me give you an example:
Username=""
Password="{PASSWORD}"
URL=""
AutoType() {
type "$Username"
key Tab
type "$Password"
key Return
}
Making every entry a valid Bash script allows us to do some really powerful
things. Let's take a look at the get-property
sub-command:
#: $PROG get-property <entry_name> <property_name>
#: Print the value of the <property_name> stored in <entry_name> to stdout
pass_get_property() {
ss_get "$1" > "$TMP_FILE" # Get the file from the store
source "$TMP_FILE" # Load the file into the current context
[ -z "$2" ] && ss_error "You must specify a property to retrieve"
echo "${!2}"
}
get_property() { pass_get_property "$@"; }
As you can see, we actually source
the entry within the function, this loads
all of the variables and functions in our entry into the current scope. After
that, the sky's the limit! You can use the get-property
, type-property
,
copy-property
, open-property
and the run-function
sub-commands to access
and run anything in an entry.
You can even refer to properties in other entries by doing something like this:
Password="$(pass get-property Other/Entry Password)"
What Next?
To get you started, in the git repository there are some example
integrations demonstrating how you could use
dmenu as a UI. Or how to run the correct AutoType
function depending on the currently focused window.
The rest is up to you, build new layers, extend the functionality, integrate it into your workflow!