mirror of
https://github.com/octoleo/Purse.git
synced 2024-12-28 20:12:38 +00:00
Merge pull request #7 from drduh/wip-26mar24
More config options, trap exits, stdout support
This commit is contained in:
commit
8f09cf87e3
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,3 +1,3 @@
|
|||||||
purse.*.tar
|
purse.*.tar
|
||||||
purse.index
|
purse.index*
|
||||||
safe/
|
safe/
|
||||||
|
61
README.md
61
README.md
@ -1,43 +1,35 @@
|
|||||||
# Purse
|
|
||||||
|
|
||||||
Purse is a fork of [drduh/pwd.sh](https://github.com/drduh/pwd.sh).
|
Purse is a fork of [drduh/pwd.sh](https://github.com/drduh/pwd.sh).
|
||||||
|
|
||||||
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.
|
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 (GnuPG) and safely handle passwords (never saving plaintext to disk, only using shell built-ins), Purse eliminates the need to remember 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.
|
||||||
|
|
||||||
# Release notes
|
# Install
|
||||||
|
|
||||||
See [Releases](https://github.com/drduh/Purse/releases)
|
This script requires a GnuPG identity - see [drduh/YubiKey-Guide](https://github.com/drduh/YubiKey-Guide) to set one up.
|
||||||
|
|
||||||
# Use
|
For the latest version, clone the repository or download the script directly:
|
||||||
|
|
||||||
This script requires a GnuPG identity - see [drduh/YubiKey-Guide](https://github.com/drduh/YubiKey-Guide) to set one up. Multiple identities stored on several YubiKeys are recommended for improved durability and reliability.
|
|
||||||
|
|
||||||
Clone the repository:
|
|
||||||
|
|
||||||
```console
|
```console
|
||||||
git clone https://github.com/drduh/Purse
|
git clone https://github.com/drduh/Purse
|
||||||
```
|
|
||||||
|
|
||||||
Or download the script directly:
|
|
||||||
|
|
||||||
```console
|
|
||||||
wget https://github.com/drduh/Purse/blob/master/purse.sh
|
wget https://github.com/drduh/Purse/blob/master/purse.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Versioned [Releases](https://github.com/drduh/Purse/releases) are also available.
|
||||||
|
|
||||||
|
# Use
|
||||||
|
|
||||||
Run the script interactively using `./purse.sh` or symlink to a directory in `PATH`:
|
Run the script interactively using `./purse.sh` or symlink to a directory in `PATH`:
|
||||||
|
|
||||||
* Type `w` to write a password
|
- `w` to write a password
|
||||||
* Type `r` to read a password
|
- `r` to read a password
|
||||||
* Type `l` to list passwords
|
- `l` to list passwords
|
||||||
* Type `b` to create an archive for backup
|
- `b` to create an archive for backup
|
||||||
* Type `h` to print the help text
|
- `h` to print the help text
|
||||||
|
|
||||||
Options can also be passed on the command line.
|
Options can also be passed on the command line.
|
||||||
|
|
||||||
Example usage:
|
|
||||||
|
|
||||||
Create a 20-character password for `userName`:
|
Create a 20-character password for `userName`:
|
||||||
|
|
||||||
```console
|
```console
|
||||||
@ -50,7 +42,7 @@ Read password for `userName`:
|
|||||||
./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 specific 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
|
```console
|
||||||
./purse.sh l
|
./purse.sh l
|
||||||
@ -70,8 +62,27 @@ Restore an archive from backup:
|
|||||||
tar xvf purse*tar
|
tar xvf purse*tar
|
||||||
```
|
```
|
||||||
|
|
||||||
**Note** For additional privacy, the recipient key ID is **not** included in metadata (`throw-keyids` option).
|
# Configure
|
||||||
|
|
||||||
The password index file can also be encrypted by changing the `encrypt_index` variable to `true` in the script, although two touches will be required for two separate decryption operations.
|
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:
|
||||||
|
|
||||||
See [config/gpg.conf](https://github.com/drduh/config/blob/master/gpg.conf) for additional configuration options.
|
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
|
||||||
|
|
||||||
|
**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.
|
||||||
|
276
purse.sh
276
purse.sh
@ -1,54 +1,70 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# https://github.com/drduh/Purse/blob/master/purse.sh
|
# https://github.com/drduh/Purse/blob/master/purse.sh
|
||||||
|
#set -x # uncomment to debug
|
||||||
set -o errtrace
|
set -o errtrace
|
||||||
set -o nounset
|
set -o nounset
|
||||||
set -o pipefail
|
set -o pipefail
|
||||||
#set -x # uncomment to debug
|
|
||||||
|
|
||||||
umask 077
|
umask 077
|
||||||
|
export LC_ALL="C"
|
||||||
cb_timeout=10 # seconds to keep password on clipboard
|
|
||||||
daily_backup="false" # if true, create daily archive on write
|
|
||||||
encrypt_index="false" # if true, requires 2 touches to decrypt
|
|
||||||
pass_copy="false" # if true, keep password on clipboard before write
|
|
||||||
pass_len=14 # default password length
|
|
||||||
pass_chars="[:alnum:]!@#$%^&*();:+="
|
|
||||||
|
|
||||||
gpgconf="${HOME}/.gnupg/gpg.conf"
|
|
||||||
backuptar="${PURSE_BACKUP:=purse.$(hostname).$(date +%F).tar}"
|
|
||||||
safeix="${PURSE_INDEX:=purse.index}"
|
|
||||||
safedir="${PURSE_SAFE:=safe}"
|
|
||||||
|
|
||||||
now="$(date +%s)"
|
now="$(date +%s)"
|
||||||
|
today="$(date +%F)"
|
||||||
copy="$(command -v xclip || command -v pbcopy)"
|
copy="$(command -v xclip || command -v pbcopy)"
|
||||||
gpg="$(command -v gpg || command -v gpg2)"
|
gpg="$(command -v gpg || command -v gpg2)"
|
||||||
script="$(basename "${BASH_SOURCE}")"
|
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 () {
|
fail () {
|
||||||
# Print an error message and exit.
|
# Print an error in red and exit.
|
||||||
|
|
||||||
tput setaf 1 ; printf "\nError: %s\n" "${1}" ; tput sgr0
|
tput setaf 1 ; printf "\nERROR: %s\n" "${1}" ; tput sgr0
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
warn () {
|
||||||
|
# Print a warning in yellow.
|
||||||
|
|
||||||
|
tput setaf 3 ; printf "\nWARNING: %s\n" "${1}" ; tput sgr0
|
||||||
|
}
|
||||||
|
|
||||||
get_pass () {
|
get_pass () {
|
||||||
# Prompt for a password.
|
# Prompt for a password.
|
||||||
|
|
||||||
password=""
|
prompt=" ${1}"
|
||||||
prompt="${1}"
|
printf "\n"
|
||||||
|
|
||||||
while IFS= read -p "${prompt}" -r -s -n 1 char ; do
|
while IFS= read -p "${prompt}" -r -s -n 1 char ; do
|
||||||
if [[ ${char} == $'\0' ]] ; then
|
if [[ ${char} == $'\0' ]] ; then break
|
||||||
break
|
|
||||||
elif [[ ${char} == $'\177' ]] ; then
|
elif [[ ${char} == $'\177' ]] ; then
|
||||||
if [[ -z "${password}" ]] ; then
|
if [[ -z "${password}" ]] ; then prompt=""
|
||||||
prompt=""
|
|
||||||
else
|
else
|
||||||
prompt=$'\b \b'
|
prompt=$'\b \b'
|
||||||
password="${password%?}"
|
password="${password%?}"
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
prompt="*"
|
prompt="${pass_echo}"
|
||||||
password+="${char}"
|
password+="${char}"
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
@ -64,39 +80,35 @@ decrypt () {
|
|||||||
encrypt () {
|
encrypt () {
|
||||||
# Encrypt to a group of hidden recipients.
|
# Encrypt to a group of hidden recipients.
|
||||||
|
|
||||||
${gpg} --encrypt --armor --batch --yes --throw-keyids \
|
${gpg} --encrypt --armor --batch --yes \
|
||||||
--hidden-recipient "purse_keygroup" \
|
--hidden-recipient "purse_keygroup" \
|
||||||
--output "${1}" "${2}"
|
--throw-keyids --comment "${comment}" \
|
||||||
|
--output "${1}" "${2}" 2>/dev/null
|
||||||
}
|
}
|
||||||
|
|
||||||
read_pass () {
|
read_pass () {
|
||||||
# Read a password from safe.
|
# 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
|
while [[ -z "${username}" ]] ; do
|
||||||
if [[ -z "${2+x}" ]] ; then read -r -p "
|
if [[ -z "${2+x}" ]] ; then read -r -p "
|
||||||
Username: " username
|
Username: " username
|
||||||
else username="${2}" ; fi
|
else username="${2}" ; fi
|
||||||
done
|
done
|
||||||
|
|
||||||
if [[ "${encrypt_index}" = "true" ]] ; then
|
if [[ -n "${encrypt_index}" ]] ; then prompt_key "index"
|
||||||
prompt_key "index"
|
spath=$(decrypt "${safe_ix}" | \
|
||||||
|
|
||||||
spath=$(decrypt "${safeix}" | \
|
|
||||||
grep -F "${username}" | tail -1 | cut -d ":" -f2) || \
|
grep -F "${username}" | tail -1 | cut -d ":" -f2) || \
|
||||||
fail "Decryption failed"
|
fail "Secret not available"
|
||||||
else
|
else spath=$(grep -F "${username}" "${safe_ix}" | \
|
||||||
spath=$(grep -F "${username}" "${safeix}" | \
|
tail -1 | cut -d ":" -f2)
|
||||||
tail -1 | cut -d ":" -f2)
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
prompt_key "password"
|
prompt_key "password"
|
||||||
|
|
||||||
if [[ -s "${spath}" ]] ; then
|
if [[ -s "${spath}" ]] ; then
|
||||||
clip <(decrypt "${spath}" | head -1) || \
|
clip <(decrypt "${spath}" | head -1) || \
|
||||||
fail "Decryption failed"
|
fail "Failed to decrypt ${spath}"
|
||||||
else fail "Secret not available"
|
else fail "Secret not available"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
@ -104,116 +116,114 @@ read_pass () {
|
|||||||
prompt_key () {
|
prompt_key () {
|
||||||
# Print a message if safe file exists.
|
# 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}"
|
printf "\n Touch key to access %s ...\n" "${1}"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
gen_pass () {
|
gen_pass () {
|
||||||
# Generate a password using GPG.
|
# Generate a password from urandom.
|
||||||
|
|
||||||
if [[ -z "${3+x}" ]] ; then read -r -p "
|
if [[ -z "${3+x}" ]] ; then read -r -p "
|
||||||
|
|
||||||
Password length (default: ${pass_len}): " length
|
Password length (default: ${pass_len}): " length
|
||||||
else length="${3}" ; fi
|
else length="${3}" ; fi
|
||||||
|
|
||||||
if [[ ${length} =~ ^[0-9]+$ ]] ; then pass_len=${length} ; fi
|
if [[ "${length}" =~ ^[0-9]+$ ]] ; then
|
||||||
|
pass_len="${length}"
|
||||||
|
fi
|
||||||
|
|
||||||
LC_LANG=C tr -dc "${pass_chars}" < /dev/urandom | \
|
tr -dc "${pass_chars}" < /dev/urandom | \
|
||||||
fold -w "${pass_len}" | head -1
|
fold -w "${pass_len}" | head -1
|
||||||
}
|
}
|
||||||
|
|
||||||
write_pass () {
|
write_pass () {
|
||||||
# Write a password and update the index.
|
# Write a password and update the index.
|
||||||
|
|
||||||
if [[ "${pass_copy}" = "true" ]] ; then
|
spath="${safe_dir}/$(tr -dc "[:lower:]" < /dev/urandom | \
|
||||||
|
fold -w10 | head -1)"
|
||||||
|
|
||||||
|
if [[ -n "${pass_copy}" ]] ; then
|
||||||
clip <(printf '%s' "${userpass}")
|
clip <(printf '%s' "${userpass}")
|
||||||
fi
|
fi
|
||||||
|
|
||||||
fpath="$(LC_LANG=C tr -dc '[:lower:]' < /dev/urandom | fold -w10 | head -1)"
|
|
||||||
spath="${safedir}/${fpath}"
|
|
||||||
printf '%s\n' "${userpass}" | \
|
printf '%s\n' "${userpass}" | \
|
||||||
encrypt "${spath}" - || \
|
encrypt "${spath}" - || \
|
||||||
fail "Failed to put ${spath}"
|
fail "Failed saving ${spath}"
|
||||||
userpass=""
|
|
||||||
|
|
||||||
if [[ "${encrypt_index}" = "true" ]] ; then
|
if [[ -n "${encrypt_index}" ]] ; then
|
||||||
prompt_key "index"
|
prompt_key "index"
|
||||||
|
|
||||||
( if [[ -f "${safeix}" ]] ; then
|
( if [[ -f "${safe_ix}" ]] ; then
|
||||||
decrypt "${safeix}" || return ; fi
|
decrypt "${safe_ix}" || return ; fi
|
||||||
printf "%s@%s:%s\n" "${username}" "${now}" "${spath}") | \
|
printf "%s@%s:%s\n" "${username}" "${now}" "${spath}") | \
|
||||||
encrypt "${safeix}.${now}" - || \
|
encrypt "${safe_ix}.${now}" - && \
|
||||||
fail "Failed to put ${safeix}.${now}"
|
mv "${safe_ix}.${now}" "${safe_ix}" || \
|
||||||
mv "${safeix}.${now}" "${safeix}"
|
fail "Failed saving ${safe_ix}.${now}"
|
||||||
else
|
else
|
||||||
printf "%s@%s:%s\n" "${username}" "${now}" "${spath}" >> "${safeix}"
|
printf "%s@%s:%s\n" \
|
||||||
|
"${username}" "${now}" "${spath}" >> "${safe_ix}"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
list_entry () {
|
list_entry () {
|
||||||
# Decrypt the index to list entries.
|
# 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
|
if [[ -n "${encrypt_index}" ]] ; then prompt_key "index"
|
||||||
prompt_key "index"
|
decrypt "${safe_ix}" || fail "${safe_ix} not available"
|
||||||
decrypt "${safeix}" || fail "Decryption failed"
|
else printf "\n" ; cat "${safe_ix}"
|
||||||
else
|
|
||||||
printf "\n"
|
|
||||||
cat "${safeix}"
|
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
backup () {
|
backup () {
|
||||||
# Archive index, safe and configuration.
|
# Archive index, safe and configuration.
|
||||||
|
|
||||||
if [[ -f "${safeix}" && -d "${safedir}" ]] ; then
|
if [[ ! -f ${safe_backup} ]] ; then
|
||||||
cp "${gpgconf}" "gpg.conf.${now}"
|
if [[ -f "${safe_ix}" && -d "${safe_dir}" ]] ; then
|
||||||
tar --create --file "${backuptar}" \
|
cp "${gpg_conf}" "gpg.conf.${today}"
|
||||||
"${safeix}" "${safedir}" "gpg.conf.${now}" "${script}"
|
tar cf "${safe_backup}" "${safe_dir}" "${safe_ix}" \
|
||||||
rm "gpg.conf.${now}"
|
"${BASH_SOURCE}" "gpg.conf.${today}" && \
|
||||||
else fail "Nothing to archive" ; fi
|
printf "\nArchived %s\n" "${safe_backup}"
|
||||||
|
rm -f "gpg.conf.${today}"
|
||||||
printf "\nArchived %s\n" "${backuptar}"
|
else fail "Nothing to archive" ; fi
|
||||||
|
else warn "${safe_backup} exists, skipping archive" ; fi
|
||||||
}
|
}
|
||||||
|
|
||||||
clip () {
|
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"
|
printf "\n"
|
||||||
shift
|
while [[ "${clip_timeout}" -gt 0 ]] ; do
|
||||||
while [ $cb_timeout -gt 0 ] ; do
|
printf "\r\033[K Password on %s! Clearing in %.d" \
|
||||||
printf "\r\033[KPassword on clipboard! Clearing in %.d" $((cb_timeout--))
|
"${clip_dest}" "$((clip_timeout--))" ; sleep 1
|
||||||
sleep 1
|
|
||||||
done
|
done
|
||||||
|
printf "\r\033[K Clearing password from %s ..." "${clip_dest}"
|
||||||
|
|
||||||
printf "\n"
|
if [[ "${clip_dest}" = "screen" ]] ; then clear
|
||||||
printf "" | ${copy}
|
else printf "\n" ; printf "" | "${copy}" ; fi
|
||||||
}
|
}
|
||||||
|
|
||||||
setup_keygroup() {
|
setup_keygroup() {
|
||||||
# Configure GPG keygroup.
|
# Configure one or more recipients.
|
||||||
|
|
||||||
purse_keygroup="group purse_keygroup ="
|
purse_keygroup="group purse_keygroup ="
|
||||||
keyid=""
|
keyid=""
|
||||||
recommend="$(${gpg} -K | grep "sec#" | \
|
recommend="$(${gpg} -K | grep "sec#" | \
|
||||||
awk -F "/" '{print $2}' | cut -c-18 | tr "\n" " ")"
|
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
|
while [[ -z "${keyid}" ]] ; do read -r -p "
|
||||||
|
|
||||||
Enter backup key IDs first, preferred key IDs last.
|
|
||||||
" "${recommend}"
|
|
||||||
|
|
||||||
while [[ -z "${keyid}" ]] ; do
|
|
||||||
read -r -p "
|
|
||||||
Key ID or Enter to continue: " keyid
|
Key ID or Enter to continue: " keyid
|
||||||
if [[ -z "${keyid}" ]] ; then
|
if [[ -z "${keyid}" ]] ; then
|
||||||
printf "%s\n" "$purse_keygroup" >> "${gpgconf}"
|
printf "%s\n" "${purse_keygroup}" >> "${gpg_conf}"
|
||||||
break
|
break
|
||||||
fi
|
fi
|
||||||
purse_keygroup="${purse_keygroup} ${keyid}"
|
purse_keygroup="${purse_keygroup} ${keyid}"
|
||||||
@ -224,18 +234,18 @@ setup_keygroup() {
|
|||||||
new_entry () {
|
new_entry () {
|
||||||
# Prompt for username and password.
|
# Prompt for username and password.
|
||||||
|
|
||||||
username=""
|
|
||||||
while [[ -z "${username}" ]] ; do
|
while [[ -z "${username}" ]] ; do
|
||||||
if [[ -z "${2+x}" ]] ; then read -r -p "
|
if [[ -z "${2+x}" ]] ; then read -r -p "
|
||||||
Username: " username
|
Username: " username
|
||||||
else username="${2}" ; fi
|
else username="${2}" ; fi
|
||||||
done
|
done
|
||||||
|
|
||||||
if [[ -z "${3+x}" ]] ; then get_pass "
|
if [[ -z "${3+x}" ]] ; then
|
||||||
Password for \"${username}\" (Enter to generate): "
|
get_pass "Password for \"${username}\" (Enter to generate): "
|
||||||
userpass="${password}"
|
userpass="${password}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
printf "\n"
|
||||||
if [[ -z "${password}" ]] ; then
|
if [[ -z "${password}" ]] ; then
|
||||||
userpass=$(gen_pass "$@")
|
userpass=$(gen_pass "$@")
|
||||||
fi
|
fi
|
||||||
@ -245,82 +255,62 @@ print_help () {
|
|||||||
# Print help text.
|
# Print help text.
|
||||||
|
|
||||||
printf """
|
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 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
|
||||||
Purse can be used interactively or by passing one of the following options:
|
|
||||||
|
|
||||||
* 'w' to write a password
|
* 'w' to write a password
|
||||||
* 'r' to read a password
|
* 'r' to read a password
|
||||||
* 'l' to list passwords
|
* 'l' to list passwords
|
||||||
* 'b' to create an archive for backup
|
* 'b' to create an archive for backup\n
|
||||||
|
Options can also be passed on the command line.\n
|
||||||
Example usage:
|
* Create a 20-character password for userName:
|
||||||
|
./purse.sh w userName 20\n
|
||||||
* Generate a 30 character password for 'userName':
|
* Read password for userName:
|
||||||
./purse.sh w userName 30
|
./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:
|
||||||
* Copy the password for 'userName' to clipboard:
|
./purse.sh l
|
||||||
./purse.sh r userName
|
./purse.sh r userName@1574723625\n
|
||||||
|
* Create an archive for backup:
|
||||||
* List stored passwords and copy a specific version:
|
./purse.sh b\n
|
||||||
./purse.sh l
|
* Restore an archive from backup:
|
||||||
./purse.sh r userName@1574723625
|
tar xvf purse*tar\n"""
|
||||||
|
|
||||||
* Create an archive for backup:
|
|
||||||
./purse.sh b
|
|
||||||
|
|
||||||
* Restore an archive from backup:
|
|
||||||
tar xvf purse*tar"""
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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 [[ -z ${copy} && ! -x ${copy} ]] ; then fail "Clipboard is not available" ; fi
|
if [[ ! -f "${gpg_conf}" ]] ; then fail "GnuPG config is not available" ; fi
|
||||||
|
|
||||||
if [[ ! -f ${gpgconf} ]] ; then fail "GnuPG config 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
|
if [[ -z "${copy}" || ! -x "${copy}" ]] ; then
|
||||||
chmod -R 0700 "${safedir}" 2>/dev/null
|
warn "Clipboard not available, passwords will print to screen/stdout!"
|
||||||
|
clip_dest="screen"
|
||||||
|
fi
|
||||||
|
|
||||||
|
username=""
|
||||||
password=""
|
password=""
|
||||||
action=""
|
action=""
|
||||||
|
|
||||||
if [[ -n "${1+x}" ]] ; then action="${1}" ; fi
|
if [[ -n "${1+x}" ]] ; then action="${1}" ; fi
|
||||||
|
|
||||||
while [[ -z "${action}" ]] ; do
|
while [[ -z "${action}" ]] ; do read -r -n 1 -p "
|
||||||
read -r -n 1 -p "
|
|
||||||
Read or Write (or Help for more options): " action
|
Read or Write (or Help for more options): " action
|
||||||
printf "\n"
|
printf "\n"
|
||||||
done
|
done
|
||||||
|
|
||||||
if [[ "${action}" =~ ^([hH])$ ]] ; then
|
if [[ "${action}" =~ ^([rR])$ ]] ; then read_pass "$@"
|
||||||
print_help
|
|
||||||
|
|
||||||
elif [[ "${action}" =~ ^([bB])$ ]] ; then
|
|
||||||
backup
|
|
||||||
|
|
||||||
elif [[ "${action}" =~ ^([lL])$ ]] ; then
|
|
||||||
list_entry
|
|
||||||
|
|
||||||
elif [[ "${action}" =~ ^([wW])$ ]] ; then
|
elif [[ "${action}" =~ ^([wW])$ ]] ; then
|
||||||
purse_keygroup=$(grep "group purse_keygroup" "${gpgconf}")
|
purse_keygroup="$(grep "group purse_keygroup" "${gpg_conf}")"
|
||||||
if [[ -z "${purse_keygroup}" ]] ; then
|
if [[ -z "${purse_keygroup}" ]] ; then
|
||||||
setup_keygroup
|
setup_keygroup
|
||||||
fi
|
fi
|
||||||
printf "\n %s\n" "${purse_keygroup}"
|
printf "\n %s\n" "${purse_keygroup}"
|
||||||
|
|
||||||
new_entry "$@"
|
new_entry "$@"
|
||||||
write_pass
|
write_pass
|
||||||
|
if [[ -n "${daily_backup}" ]] ; then backup ; fi
|
||||||
if [[ "${daily_backup}" = "true" ]] ; then
|
elif [[ "${action}" =~ ^([lL])$ ]] ; then list_entry
|
||||||
if [[ ! -f ${backuptar} ]] ; then
|
elif [[ "${action}" =~ ^([bB])$ ]] ; then backup
|
||||||
backup
|
else print_help ; fi
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
else read_pass "$@" ; fi
|
|
||||||
|
|
||||||
chmod -R 0400 "${safeix}" "${safedir}" 2>/dev/null
|
|
||||||
|
|
||||||
tput setaf 2 ; printf "\nDone\n" ; tput sgr0
|
tput setaf 2 ; printf "\nDone\n" ; tput sgr0
|
||||||
|
Loading…
Reference in New Issue
Block a user