Compare commits

...

20 Commits

Author SHA1 Message Date
drduh 8f09cf87e3
Merge pull request #7 from drduh/wip-26mar24
More config options, trap exits, stdout support
2024-03-27 17:12:09 +00:00
drduh abcb88a032 grammar 2024-03-27 10:11:45 -07:00
drduh c8ede9797a Encrypted index config option 2024-03-26 16:56:11 -07:00
drduh 1b990c96a6 Add chars and echo config options 2024-03-26 16:36:25 -07:00
drduh e6c3828504 Fix clip functionality 2024-03-26 15:57:33 -07:00
drduh 404d5402d1 Style touch-ups 2024-03-26 14:52:56 -07:00
drduh f429a52625 Safer archive backups 2024-03-26 14:32:02 -07:00
drduh 02b910b326 Add configuration options to README 2024-03-26 14:23:36 -07:00
drduh 80d501cba8 Update to pwd.sh parity 2024-03-26 14:18:57 -07:00
drduh 2c02ec96a3
Create FUNDING.yml 2024-03-10 22:38:49 +00:00
drduh 15bb2a9dc8
Merge pull request #6 from drduh/wip-10mar24
Version 3 beta
2024-03-10 22:23:51 +00:00
drduh 0ca2711773
Merge branch 'master' into wip-10mar24 2024-03-10 22:20:12 +00:00
drduh 84a98927c4 more formatting 2024-03-10 15:13:41 -07:00
drduh 1370d1ee13 Clean up readme 2024-03-10 15:09:41 -07:00
drduh 5cc6c81ed0 Version 3 beta 2024-03-10 14:59:33 -07:00
drduh 7e53a6b510
Merge pull request #5 from drduh/document-tr-bug
Document (fixed) tr bug
2022-12-26 15:46:04 -08:00
drduh 9df8f4c58c Document (fixed) tr bug 2022-12-26 15:45:39 -08:00
drduh 6976cff500
Merge pull request #3 from DrearyLisper/master
Add LANG=C to get tr working.
2022-08-21 11:17:15 -07:00
Dreary Lisper acca8834a4
Add LANG=C to get tr working. 2022-01-04 18:45:43 +00:00
drduh 7074a8bf4c Include script in backup archive 2020-05-25 14:58:56 -07:00
5 changed files with 208 additions and 218 deletions

1
.github/FUNDING.yml vendored Normal file
View File

@ -0,0 +1 @@
github: [drduh]

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
purse.*.tar
purse.index*
safe/

View File

@ -1,6 +1,6 @@
The MIT License (MIT)
Copyright (c) 2018-2020 drduh
Copyright (c) 2018 drduh
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

114
README.md
View File

@ -1,112 +1,88 @@
# Purse
Purse is a fork of [drduh/pwd.sh](https://github.com/drduh/pwd.sh).
Both programs are Bash shell scripts which use [GPG](https://www.gnupg.org/) to manage passwords and other secrets in encrypted text files. Purse uses asymmetric (public-key) authentication, while pwd.sh uses symmetric (password-based) authentication.
Both programs are Bash shell scripts which use [GnuPG](https://www.gnupg.org/) to manage passwords and other secrets in encrypted text files. Purse is based on asymmetric (public-key) authentication, while pwd.sh is based on symmetric (password-based) authentication.
While both scripts use a trusted crypto implementation (GPG) and safely handle passwords (never saving plaintext to disk), Purse eliminates the need to remember and use a master password - just plug in a YubiKey, enter the PIN, then touch it to decrypt a password to clipboard.
While both scripts use a trusted crypto implementation (GnuPG) and safely handle passwords (never saving plaintext to disk, only using shell built-ins), Purse eliminates the need to remember a main passphrase - just plug in a YubiKey, enter the PIN, then touch it to decrypt a password to clipboard.
By using Purse with YubiKey, the risk of master password theft or keylogging is eliminated - only physical possession of the Yubikey AND knowledge of the PIN can unlock the encrypted index and password files.
# Install
# Release notes
This script requires a GnuPG identity - see [drduh/YubiKey-Guide](https://github.com/drduh/YubiKey-Guide) to set one up.
## Version 2b1 (2020)
For the latest version, clone the repository or download the script directly:
Minor update to the second release. Currently in beta testing. Compatible on Linux, OpenBSD, macOS.
```console
git clone https://github.com/drduh/Purse
Changelist:
wget https://github.com/drduh/Purse/blob/master/purse.sh
```
* Purse now uses a GPG keygroup to encrypt secrets to multiple recipients for improved reliability. The program will prompt for key IDs to define the keygroup; a single key ID can still be used.
* Encrypted index is now optional and off by default, allowing a single touch to encrypt and decrypt secrets instead of two.
* GPG configuration file is now included in Purse backup archives.
## Version 2b (2019)
The second release of purse.sh features several security and reliability improvements, and is an optional upgrade. Currently in beta testing. Compatible on Linux, OpenBSD, macOS.
Known issues:
* Read actions now require two Yubikey touches, if touch to decrypt is enabled - once for the index and twice for the encrypted password file.
Changelist:
* Passwords are now encrypted as individual files, rather than all encrypted as a single flat file.
* Individual password filenames are random, mapped to usernames in an encrypted index file.
* Index and password files are now "immutable" using chmod while purse.sh is not running.
* Read passwords are now copied to clipboard and cleared after a timeout, instead of printed to stdout.
* Use printf instead of echo for improved portability.
* New option: list passwords in the index.
* New option: create tar archive for backup.
* Removed option: delete password; the index is now a permanent ledger.
* Removed option: read all passwords; no use case for having a single command.
* Removed option: suppress generated password output; should be read from safe to verify save.
## Version 1 (2018)
The original release which has been available for general use and review since June 2018 (forked from pwd.sh which dates to 2015). There are no known bugs nor security vulnerabilities identified in this stable version of purse.sh. Compatible on Linux, OpenBSD, macOS.
Versioned [Releases](https://github.com/drduh/Purse/releases) are also available.
# Use
This script requires a GPG identity - see [drduh/YubiKey-Guide](https://github.com/drduh/YubiKey-Guide) to set one up. Multiple identities stored on several YubiKeys are recommended for reliability.
Run the script interactively using `./purse.sh` or symlink to a directory in `PATH`:
```console
$ git clone https://github.com/drduh/Purse
```
(Version 2b and older) Set your GPG key ID with `export PURSE_KEYID=0xFF3E7D88647EBCDB` or by editing `purse.sh`.
`cd purse.sh` and run the script interactively using `./purse.sh` or symlink to a directory in `PATH`:
* Type `w` to write a password
* Type `r` to read a password
* Type `l` to list passwords
* Type `b` to create an archive for backup
* Type `h` to print the help text
- `w` to write a password
- `r` to read a password
- `l` to list passwords
- `b` to create an archive for backup
- `h` to print the help text
Options can also be passed on the command line.
Example usage:
Create a 30-character password for `userName`:
Create a 20-character password for `userName`:
```console
$ ./purse.sh w userName 30
./purse.sh w userName 20
```
Read password for `userName`:
```console
$ ./purse.sh r userName
./purse.sh r userName
```
Passwords are stored with a timestamp for revision control. The most recent version is copied to clipboard on read. To list all passwords or read a previous version of a password:
Passwords are stored with an epoch timestamp for revision control. The most recent version is copied to clipboard on read. To list all passwords or read a specific version of a password:
```console
$ ./purse.sh l
./purse.sh l
$ ./purse.sh r userName@1574723600
./purse.sh r userName@1574723600
```
Create an archive for backup:
```console
$ ./purse.sh b
./purse.sh b
```
Restore an archive from backup:
```console
$ tar xvf purse*tar
tar xvf purse*tar
```
The backup contains only encrypted passwords and can be publicly shared for use on trusted computers. For additional privacy, the recipient key ID is **not** included in GPG metadata (`throw-keyids` option). The password index file can also be encrypted by changing the `encrypt_index` variable to `true` in the script.
# Configure
See [drduh/config/gpg.conf](https://github.com/drduh/config/blob/master/gpg.conf) for additional GPG configuration options.
Several customizable options and features are also available, and can be configured with environment variables, for example in the [shell rc](https://github.com/drduh/config/blob/master/zshrc) file:
# Similar software
Variable | Description | Default | Values
-|-|-|-
`PURSE_TIME` | seconds to clear password from clipboard/screen | `10` | any valid integer
`PURSE_LEN` | default generated password length | `14` | any valid integer
`PURSE_COPY` | copy password to clipboard before write | unset (disabled) | `1` or `true` to enable
`PURSE_DAILY` | create daily backup archive on write | unset (disabled) | `1` or `true` to enable
`PURSE_ENCIX` | encrypt index for additional privacy; 2 YubiKey touches will be required for separate decryption operations | unset (disabled) | `1` or `true` to enable
`PURSE_COMMENT` | **unencrypted** comment to include in index and safe files | unset | any valid string
`PURSE_CHARS` | character set for passwords | `[:alnum:]!?@#$%^&*();:+=` | any valid characters
`PURSE_DEST` | password output destination, will set to `screen` without clipboard | `clipboard` | `clipboard` or `screen`
`PURSE_ECHO` | character used to echo password input | `*` | any valid character
`PURSE_SAFE` | safe directory name | `safe` | any valid string
`PURSE_INDEX` | index file name | `purse.index` | any valid string
`PURSE_BACKUP` | backup archive file name | `purse.$hostname.$today.tar` | any valid string
* [drduh/pwd.sh](https://github.com/drduh/pwd.sh)
* [zx2c4/password-store](https://github.com/zx2c4/password-store)
* [caodonnell/passman.sh: a pwd.sh fork](https://github.com/caodonnell/passman.sh)
* [bndw/pick: command-line password manager for macOS and Linux](https://github.com/bndw/pick)
* [anders/pwgen: generate passwords using OS X Security framework](https://github.com/anders/pwgen)
**Note** For additional privacy, the recipient key ID is **not** included in metadata (GnuPG `throw-keyids` option).
See [config/gpg.conf](https://github.com/drduh/config/blob/master/gpg.conf) for additional GnuPG options.

306
purse.sh
View File

@ -1,49 +1,70 @@
#!/usr/bin/env bash
# https://github.com/drduh/Purse/blob/master/purse.sh
#set -x # uncomment to debug
set -o errtrace
set -o nounset
set -o pipefail
#set -x # uncomment to debug
umask 077
export LC_ALL="C"
encrypt_index="false"
now=$(date +%s)
now="$(date +%s)"
today="$(date +%F)"
copy="$(command -v xclip || command -v pbcopy)"
gpg="$(command -v gpg || command -v gpg2)"
gpgconf="${HOME}/.gnupg/gpg.conf"
backuptar="${PURSE_BACKUP:=purse.$(hostname).$(date +%F).tar}"
safeix="${PURSE_INDEX:=purse.index}"
safedir="${PURSE_SAFE:=safe}"
timeout=10
gpg_conf="${GNUPGHOME}/gpg.conf"
clip_dest="${PURSE_DEST:=clipboard}" # set to 'screen' to print to stdout
clip_timeout="${PURSE_TIME:=10}" # seconds to clear clipboard/screen
comment="${PURSE_COMMENT:=}" # *unencrypted* comment in files
daily_backup="${PURSE_DAILY:=}" # daily backup archive on write
encrypt_index="${PURSE_ENCIX:=}" # also keep index encrypted
pass_copy="${PURSE_COPY:=}" # copy password before write
pass_echo="${PURSE_ECHO:=*}" # show "*" when typing passwords
pass_len="${PURSE_LEN:=14}" # default password length
safe_dir="${PURSE_SAFE:=safe}" # safe directory name
safe_ix="${PURSE_INDEX:=purse.index}" # index file name
safe_backup="${PURSE_BACKUP:=purse.$(hostname).${today}.tar}"
pass_chars="${PURSE_CHARS:='[:alnum:]!?@#$%^&*();:+='}"
trap cleanup EXIT INT TERM
cleanup () {
# "Lock" files on trapped exits.
ret=$?
chmod -R 0000 \
"${safe_dir}" "${safe_ix}" 2>/dev/null
exit ${ret}
}
fail () {
# Print an error message and exit.
# Print an error in red and exit.
tput setaf 1 1 1 ; printf "\nError: %s\n" "${1}" ; tput sgr0
tput setaf 1 ; printf "\nERROR: %s\n" "${1}" ; tput sgr0
exit 1
}
warn () {
# Print a warning in yellow.
tput setaf 3 ; printf "\nWARNING: %s\n" "${1}" ; tput sgr0
}
get_pass () {
# Prompt for a password.
password=""
prompt="${1}"
prompt=" ${1}"
printf "\n"
while IFS= read -p "${prompt}" -r -s -n 1 char ; do
if [[ ${char} == $'\0' ]] ; then
break
if [[ ${char} == $'\0' ]] ; then break
elif [[ ${char} == $'\177' ]] ; then
if [[ -z "${password}" ]] ; then
prompt=""
if [[ -z "${password}" ]] ; then prompt=""
else
prompt=$'\b \b'
password="${password%?}"
fi
else
prompt="*"
prompt="${pass_echo}"
password+="${char}"
fi
done
@ -52,156 +73,157 @@ get_pass () {
decrypt () {
# Decrypt with GPG.
cat "${1}" | ${gpg} --armor --batch --decrypt 2>/dev/null
cat "${1}" | \
${gpg} --armor --batch --decrypt 2>/dev/null
}
encrypt () {
# Encrypt to a group of hidden recipients.
${gpg} --encrypt --armor --batch --yes --throw-keyids \
${gpg} --encrypt --armor --batch --yes \
--hidden-recipient "purse_keygroup" \
--output "${1}" "${2}"
--throw-keyids --comment "${comment}" \
--output "${1}" "${2}" 2>/dev/null
}
read_pass () {
# Read a password from safe.
if [[ ! -s ${safeix} ]] ; then fail "${safeix} not found" ; fi
if [[ ! -s "${safe_ix}" ]] ; then fail "${safe_ix} not found" ; fi
username=""
while [[ -z "${username}" ]] ; do
if [[ -z "${2+x}" ]] ; then read -r -p "
Username: " username
else username="${2}" ; fi
done
if [[ "${encrypt_index}" = "true" ]] ; then
prompt_key "index"
spath=$(decrypt "${safeix}" | \
grep -F "${username}" | tail -n1 | cut -d ":" -f2) || \
fail "Decryption failed"
else
spath=$(grep -F "${username}" "${safeix}" | \
tail -n1 | cut -d ":" -f2)
if [[ -n "${encrypt_index}" ]] ; then prompt_key "index"
spath=$(decrypt "${safe_ix}" | \
grep -F "${username}" | tail -1 | cut -d ":" -f2) || \
fail "Secret not available"
else spath=$(grep -F "${username}" "${safe_ix}" | \
tail -1 | cut -d ":" -f2)
fi
prompt_key "password"
clip <(decrypt "${spath}" | head -n1) || \
fail "Decryption failed"
if [[ -s "${spath}" ]] ; then
clip <(decrypt "${spath}" | head -1) || \
fail "Failed to decrypt ${spath}"
else fail "Secret not available"
fi
}
prompt_key () {
# Print a message if safe file exists.
if [[ -f "${safeix}" ]] ; then
if [[ -f "${safe_ix}" ]] ; then
printf "\n Touch key to access %s ...\n" "${1}"
fi
}
gen_pass () {
# Generate a password using GPG.
len=20
max=80
# Generate a password from urandom.
if [[ -z "${3+x}" ]] ; then read -r -p "
Password length (default: ${len}, max: ${max}): " length
Password length (default: ${pass_len}): " length
else length="${3}" ; fi
if [[ ${length} =~ ^[0-9]+$ ]] ; then len=${length} ; fi
if [[ "${length}" =~ ^[0-9]+$ ]] ; then
pass_len="${length}"
fi
# base64: 4 characters for every 3 bytes
${gpg} --armor --gen-random 0 "$((max * 3 / 4))" | cut -c -"${len}"
tr -dc "${pass_chars}" < /dev/urandom | \
fold -w "${pass_len}" | head -1
}
write_pass () {
# Write a password and update index file.
# Write a password and update the index.
fpath=$(tr -dc "[:lower:]" < /dev/urandom | fold -w8 | head -n1)
spath=${safedir}/${fpath}
printf '%s\n' "${userpass}" | \
encrypt "${spath}" - || \
fail "Failed to put ${spath}"
spath="${safe_dir}/$(tr -dc "[:lower:]" < /dev/urandom | \
fold -w10 | head -1)"
if [[ "${encrypt_index}" = "true" ]] ; then
prompt_key "index"
( if [[ -f "${safeix}" ]] ; then
decrypt "${safeix}" || return ; fi
printf "%s@%s:%s\n" "${username}" "${now}" "${spath}") | \
encrypt "${safeix}.${now}" - || \
fail "Failed to put ${safeix}.${now}"
mv "${safeix}.${now}" "${safeix}"
else
printf "%s@%s:%s\n" "${username}" "${now}" "${spath}" >> "${safeix}"
if [[ -n "${pass_copy}" ]] ; then
clip <(printf '%s' "${userpass}")
fi
printf '%s\n' "${userpass}" | \
encrypt "${spath}" - || \
fail "Failed saving ${spath}"
if [[ -n "${encrypt_index}" ]] ; then
prompt_key "index"
( if [[ -f "${safe_ix}" ]] ; then
decrypt "${safe_ix}" || return ; fi
printf "%s@%s:%s\n" "${username}" "${now}" "${spath}") | \
encrypt "${safe_ix}.${now}" - && \
mv "${safe_ix}.${now}" "${safe_ix}" || \
fail "Failed saving ${safe_ix}.${now}"
else
printf "%s@%s:%s\n" \
"${username}" "${now}" "${spath}" >> "${safe_ix}"
fi
}
list_entry () {
# Decrypt the index to list entries.
if [[ ! -s ${safeix} ]] ; then fail "${safeix} not found" ; fi
if [[ ! -s "${safe_ix}" ]] ; then fail "${safe_ix} not found" ; fi
if [[ "${encrypt_index}" = "true" ]] ; then
prompt_key "index"
decrypt "${safeix}" || fail "Decryption failed"
else
cat "${safeix}"
if [[ -n "${encrypt_index}" ]] ; then prompt_key "index"
decrypt "${safe_ix}" || fail "${safe_ix} not available"
else printf "\n" ; cat "${safe_ix}"
fi
}
backup () {
# Archive encrypted index and safe directory.
# Archive index, safe and configuration.
if [[ -f "${safeix}" ]] ; then
cp "${gpgconf}" "gpg.conf.${now}"
tar cfv "${backuptar}" "${safeix}" "${safedir}" "gpg.conf.${now}"
rm "gpg.conf.${now}"
else fail "Nothing to archive" ; fi
printf "\nArchived %s \n" "${backuptar}"
if [[ ! -f ${safe_backup} ]] ; then
if [[ -f "${safe_ix}" && -d "${safe_dir}" ]] ; then
cp "${gpg_conf}" "gpg.conf.${today}"
tar cf "${safe_backup}" "${safe_dir}" "${safe_ix}" \
"${BASH_SOURCE}" "gpg.conf.${today}" && \
printf "\nArchived %s\n" "${safe_backup}"
rm -f "gpg.conf.${today}"
else fail "Nothing to archive" ; fi
else warn "${safe_backup} exists, skipping archive" ; fi
}
clip () {
# Use clipboard and clear after timeout.
# Use clipboard or stdout and clear after timeout.
${copy} < "${1}"
if [[ "${clip_dest}" = "screen" ]] ; then
printf '\n%s\n' "$(cat ${1})"
else "${copy}" < "${1}" ; fi
printf "\n"
shift
while [ $timeout -gt 0 ] ; do
printf "\r\033[KPassword on clipboard! Clearing in %.d" $((timeout--))
sleep 1
while [[ "${clip_timeout}" -gt 0 ]] ; do
printf "\r\033[K Password on %s! Clearing in %.d" \
"${clip_dest}" "$((clip_timeout--))" ; sleep 1
done
printf "\r\033[K Clearing password from %s ..." "${clip_dest}"
printf "" | ${copy}
if [[ "${clip_dest}" = "screen" ]] ; then clear
else printf "\n" ; printf "" | "${copy}" ; fi
}
setup_keygroup() {
# Configure GPG keygroup setting.
# Configure one or more recipients.
purse_keygroup="group purse_keygroup ="
keyid=""
recommend="$(${gpg} -K | grep "sec#" | \
awk -F "/" '{print $2}' | cut -c-18 | tr "\n" " ")"
printf "\n Setting up GPG key group ...
printf "\n Setting up keygroup ...\n
Found recommended key IDs: %s\n
Enter one or more key IDs, preferred one last\n" "${recommend}"
Found key IDs: %s
Enter backup key IDs first, preferred key IDs last.
" "${recommend}"
while [[ -z "${keyid}" ]] ; do
read -r -p "
while [[ -z "${keyid}" ]] ; do read -r -p "
Key ID or Enter to continue: " keyid
if [[ -z "${keyid}" ]] ; then
printf "%s\n" "$purse_keygroup" >> "${gpgconf}"
printf "%s\n" "${purse_keygroup}" >> "${gpg_conf}"
break
fi
purse_keygroup="${purse_keygroup} ${keyid}"
@ -210,97 +232,85 @@ setup_keygroup() {
}
new_entry () {
# Prompt for new username and/or password.
# Prompt for username and password.
username=""
while [[ -z "${username}" ]] ; do
if [[ -z "${2+x}" ]] ; then read -r -p "
Username: " username
else username="${2}" ; fi
done
if [[ -z "${3+x}" ]] ; then get_pass "
Password for \"${username}\" (Enter to generate): "
if [[ -z "${3+x}" ]] ; then
get_pass "Password for \"${username}\" (Enter to generate): "
userpass="${password}"
fi
if [[ -z "${password}" ]] ; then userpass=$(gen_pass "$@") ; fi
printf "\n"
if [[ -z "${password}" ]] ; then
userpass=$(gen_pass "$@")
fi
}
print_help () {
# Print help text.
printf """
Purse is a Bash shell script to manage passwords with GnuPG asymmetric encryption. It is designed and recommended to be used with Yubikey as the secret key storage.
Purse can be used interactively or by passing one of the following options:
Purse is a Bash shell script to manage passwords with GnuPG asymmetric encryption. It is designed and recommended to be used with YubiKey as the secret key storage.\n
Purse can be used interactively or by passing one of the following options:\n
* 'w' to write a password
* 'r' to read a password
* 'l' to list passwords
* 'b' to create an archive for backup
Example usage:
* Generate a 30 character password for 'userName':
./purse.sh w userName 30
* Copy the password for 'userName' to clipboard:
./purse.sh r userName
* List stored passwords and copy a previous version:
./purse.sh l
./purse.sh r userName@1574723625
* Create an archive for backup:
./purse.sh b
* Restore an archive from backup:
tar xvf purse*tar"""
* 'b' to create an archive for backup\n
Options can also be passed on the command line.\n
* Create a 20-character password for userName:
./purse.sh w userName 20\n
* Read password for userName:
./purse.sh r userName\n
* Passwords are stored with an epoch timestamp for revision control. The most recent version is copied to clipboard on read. To list all passwords or read a specific version of a password:
./purse.sh l
./purse.sh r userName@1574723625\n
* Create an archive for backup:
./purse.sh b\n
* Restore an archive from backup:
tar xvf purse*tar\n"""
}
if [[ -z ${gpg} && ! -x ${gpg} ]] ; then fail "GnuPG is not available" ; fi
if [[ -z "${gpg}" || ! -x "${gpg}" ]] ; then fail "GnuPG is not available" ; fi
if [[ ! -f ${gpgconf} ]] ; then fail "GnuPG config is not available" ; fi
if [[ ! -f "${gpg_conf}" ]] ; then fail "GnuPG config is not available" ; fi
if [[ -z ${copy} && ! -x ${copy} ]] ; then fail "Clipboard is not available" ; fi
if [[ ! -d "${safe_dir}" ]] ; then mkdir -p "${safe_dir}" ; fi
if [[ ! -d ${safedir} ]] ; then mkdir -p ${safedir} ; fi
chmod -R 0700 "${safe_dir}" "${safe_ix}" 2>/dev/null
chmod -R 0600 ${safeix} 2>/dev/null
chmod -R 0700 ${safedir} 2>/dev/null
if [[ -z "${copy}" || ! -x "${copy}" ]] ; then
warn "Clipboard not available, passwords will print to screen/stdout!"
clip_dest="screen"
fi
username=""
password=""
action=""
if [[ -n "${1+x}" ]] ; then action="${1}" ; fi
while [[ -z "${action}" ]] ; do
read -n 1 -p "
while [[ -z "${action}" ]] ; do read -r -n 1 -p "
Read or Write (or Help for more options): " action
printf "\n"
done
if [[ "${action}" =~ ^([hH])$ ]] ; then
print_help
elif [[ "${action}" =~ ^([bB])$ ]] ; then
backup
elif [[ "${action}" =~ ^([lL])$ ]] ; then
list_entry
if [[ "${action}" =~ ^([rR])$ ]] ; then read_pass "$@"
elif [[ "${action}" =~ ^([wW])$ ]] ; then
purse_keygroup=$(grep "group purse_keygroup" "${gpgconf}")
purse_keygroup="$(grep "group purse_keygroup" "${gpg_conf}")"
if [[ -z "${purse_keygroup}" ]] ; then
setup_keygroup
fi
printf "\n %s\n" "${purse_keygroup}"
new_entry "$@"
write_pass
if [[ -n "${daily_backup}" ]] ; then backup ; fi
elif [[ "${action}" =~ ^([lL])$ ]] ; then list_entry
elif [[ "${action}" =~ ^([bB])$ ]] ; then backup
else print_help ; fi
else read_pass "$@" ; fi
chmod -R 0400 ${safeix} ${safedir} 2>/dev/null
tput setaf 2 2 2 ; printf "\nDone\n" ; tput sgr0
tput setaf 2 ; printf "\nDone\n" ; tput sgr0