Skip to content

Commit 18e6024

Browse files
authored
Merge pull request #40 from drduh/wip-10mar24
Version 3 beta
2 parents ae4fe07 + a313775 commit 18e6024

File tree

4 files changed

+72
-78
lines changed

4 files changed

+72
-78
lines changed

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
pwd.*.tar
2+
pwd.index
3+
safe/

LICENSE.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
The MIT License (MIT)
22

3-
Copyright (c) 2015-2020 drduh
3+
Copyright (c) 2015 drduh
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal

README.md

+19-45
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,29 @@
11
pwd.sh is a Bash shell script to manage passwords and other secrets.
22

3-
It uses GnuPG to symmetrically (i.e., using a master password) encrypt and decrypt plain text files.
3+
It uses GnuPG to symmetrically (i.e., using a master password) encrypt and decrypt plaintext files.
44

55
[drduh/Purse](https://github.com/drduh/Purse) is a fork which uses public key authentication instead of a master password and can integrate with YubiKey.
66

77
# Release notes
88

9-
## Version 2 (2020)
9+
See [Releases](https://github.com/drduh/pwd.sh/releases)
1010

11-
The second release of pwd.sh features many security and reliability improvements, and is a recommended upgrade. Compatible on Linux, OpenBSD, macOS.
12-
13-
Known Issues:
14-
15-
* Newer versions of macOS error with `tr: Illegal byte sequence` - see [issue #36](https://github.com/drduh/pwd.sh/issues/36)
11+
# Use
1612

17-
Changelist:
13+
Clone the repository:
1814

19-
* Passwords are now encrypted as individual files, rather than all encrypted as a single flat file.
20-
* Individual password filenames are random, mapped to usernames in an encrypted index file.
21-
* Index and password files are now "immutable" using chmod while pwd.sh is not running.
22-
* Read passwords are now copied to clipboard and cleared after a timeout, instead of printed to stdout.
23-
* Use printf instead of echo for improved portability.
24-
* New option: list passwords in the index.
25-
* New option: create tar archive for backup.
26-
* Removed option: delete password; the index is now a permanent ledger.
27-
* Removed option: read all passwords; no use case for having a single command.
28-
* Removed option: suppress generated password output; should be read from safe to verify save.
15+
```console
16+
git clone https://github.com/drduh/pwd.sh
2917

30-
## Version 1 (2015)
18+
```
3119

32-
The original release which has been available for general use and review since July 2015. There are no known bugs nor security vulnerabilities identified in this stable version of pwd.sh. Compatible on Linux, OpenBSD, macOS.
33-
34-
# Use
20+
Or download the script directly:
3521

3622
```console
37-
$ git clone https://github.com/drduh/pwd.sh
23+
wget https://raw.githubusercontent.com/drduh/pwd.sh/master/pwd.sh
3824
```
3925

40-
`cd pwd.sh` and run the script interactively using `./pwd.sh` or symlink to a directory in `PATH`:
26+
Run the script interactively using `./pwd.sh` or symlink to a directory in `PATH`:
4127

4228
* Type `w` to write a password
4329
* Type `r` to read a password
@@ -47,48 +33,36 @@ $ git clone https://github.com/drduh/pwd.sh
4733

4834
Options can also be passed on the command line.
4935

50-
Example usage:
51-
52-
Create a 30-character password for `userName`:
36+
Create a 20-character password for `userName`:
5337

5438
```console
55-
$ ./pwd.sh w userName 30
39+
./pwd.sh w userName 20
5640
```
5741

5842
Read password for `userName`:
5943

6044
```console
61-
$ ./pwd.sh r userName
45+
./pwd.sh r userName
6246
```
6347

64-
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:
48+
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:
6549

6650
```console
67-
$ ./pwd.sh l
51+
./pwd.sh l
6852

69-
$ ./pwd.sh r userName@1574723600
53+
./pwd.sh r userName@1574723600
7054
```
7155

7256
Create an archive for backup:
7357

7458
```console
75-
$ ./pwd.sh b
59+
./pwd.sh b
7660
```
7761

7862
Restore an archive from backup:
7963

8064
```console
81-
$ tar xvf pwd*tar
65+
tar xvf pwd*tar
8266
```
8367

84-
The backup contains only encrypted files and can be publicly shared for use on trusted computers.
85-
86-
See [drduh/config/gpg.conf](https://github.com/drduh/config/blob/master/gpg.conf) for additional GPG configuration options.
87-
88-
# Similar software
89-
90-
* [drduh/Purse](https://github.com/drduh/Purse)
91-
* [zx2c4/password-store](https://github.com/zx2c4/password-store)
92-
* [caodonnell/passman.sh: a pwd.sh fork](https://github.com/caodonnell/passman.sh)
93-
* [bndw/pick: command-line password manager for macOS and Linux](https://github.com/bndw/pick)
94-
* [anders/pwgen: generate passwords using OS X Security framework](https://github.com/anders/pwgen)
68+
See [config/gpg.conf](https://github.com/drduh/config/blob/master/gpg.conf) for additional configuration options.

pwd.sh

+49-32
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,32 @@
11
#!/usr/bin/env bash
2-
# https://github.com/drduh/pwd.sh
3-
2+
# https://github.com/drduh/pwd.sh/blob/master/pwd.sh
43
set -o errtrace
54
set -o nounset
65
set -o pipefail
7-
8-
#set -x # uncomment to debug
6+
#set -x # uncomment to debug
97

108
umask 077
119

12-
now=$(date +%s)
13-
copy="$(command -v xclip || command -v pbcopy)"
14-
gpg="$(command -v gpg || command -v gpg2)"
10+
cb_timeout=10 # seconds to keep password on clipboard
11+
daily_backup="false" # if true, create daily archive on write
12+
pass_copy="false" # if true, keep password on clipboard before write
13+
pass_len=14 # default password length
14+
pass_chars="[:alnum:]!@#$%^&*();:+="
15+
1516
gpgconf="${HOME}/.gnupg/gpg.conf"
1617
backuptar="${PWDSH_BACKUP:=pwd.$(hostname).$(date +%F).tar}"
1718
safeix="${PWDSH_INDEX:=pwd.index}"
1819
safedir="${PWDSH_SAFE:=safe}"
19-
script="$(basename $BASH_SOURCE)"
20-
timeout=10
20+
21+
now="$(date +%s)"
22+
copy="$(command -v xclip || command -v pbcopy)"
23+
gpg="$(command -v gpg || command -v gpg2)"
24+
script="$(basename "${BASH_SOURCE}")"
2125

2226
fail () {
2327
# Print an error message and exit.
2428

25-
tput setaf 1 1 1 ; printf "\nError: %s\n" "${1}" ; tput sgr0
29+
tput setaf 1 ; printf "\nError: %s\n" "${1}" ; tput sgr0
2630
exit 1
2731
}
2832

@@ -52,7 +56,8 @@ get_pass () {
5256
decrypt () {
5357
# Decrypt with GPG.
5458

55-
printf "%s\n" "${1}" | ${gpg} --armor --batch --no-symkey-cache \
59+
printf "%s\n" "${1}" | \
60+
${gpg} --armor --batch --no-symkey-cache \
5661
--decrypt --passphrase-fd 0 "${2}" 2>/dev/null
5762
}
5863

@@ -91,17 +96,14 @@ read_pass () {
9196
gen_pass () {
9297
# Generate a password using GPG.
9398

94-
len=20
95-
max=80
96-
9799
if [[ -z "${3+x}" ]] ; then read -r -p "
98-
Password length (default: ${len}, max: ${max}): " length
100+
Password length (default: ${pass_len}): " length
99101
else length="${3}" ; fi
100102

101-
if [[ ${length} =~ ^[0-9]+$ ]] ; then len=${length} ; fi
103+
if [[ ${length} =~ ^[0-9]+$ ]] ; then pass_len=${length} ; fi
102104

103-
# base64: 4 characters for every 3 bytes
104-
${gpg} --armor --gen-random 0 "$((max * 3 / 4))" | cut -c -"${len}"
105+
LC_LANG=C tr -dc "${pass_chars}" < /dev/urandom | \
106+
fold -w "${pass_len}" | head -1
105107
}
106108

107109
write_pass () {
@@ -112,11 +114,16 @@ write_pass () {
112114
Password to unlock ${safeix}: " ; done
113115
printf "\n"
114116

115-
fpath=$(LC_LANG=C tr -dc "[:lower:]" < /dev/urandom | fold -w8 | head -n1)
116-
spath=${safedir}/${fpath}
117+
if [[ "${pass_copy}" = "true" ]] ; then
118+
clip <(printf '%s' "${userpass}")
119+
fi
120+
121+
fpath="$(LC_LANG=C tr -dc '[:lower:]' < /dev/urandom | fold -w10 | head -1)"
122+
spath="${safedir}/${fpath}"
117123
printf '%s\n' "${userpass}" | \
118124
encrypt "${password}" "${spath}" - || \
119125
fail "Failed to put ${spath}"
126+
userpass=""
120127

121128
( if [[ -f "${safeix}" ]] ; then
122129
decrypt "${password}" "${safeix}" || return ; fi
@@ -136,20 +143,21 @@ list_entry () {
136143
Password to unlock ${safeix}: " ; done
137144
printf "\n\n"
138145

139-
decrypt ${password} "${safeix}" || fail "Decryption failed"
146+
decrypt "${password}" "${safeix}" || \
147+
fail "Decryption failed"
140148
}
141149

142150
backup () {
143-
# Archive encrypted index and safe directory.
151+
# Archive index, safe and configuration.
144152

145-
if [[ -f "${safeix}" && -d "${safedir}" ]] ; then \
153+
if [[ -f "${safeix}" && -d "${safedir}" ]] ; then
146154
cp "${gpgconf}" "gpg.conf.${now}"
147-
tar cfv "${backuptar}" \
155+
tar --create --file "${backuptar}" \
148156
"${safeix}" "${safedir}" "gpg.conf.${now}" "${script}"
149157
rm "gpg.conf.${now}"
150158
else fail "Nothing to archive" ; fi
151159

152-
printf "\nArchived %s\n" "${backuptar}" ; \
160+
printf "\nArchived %s\n" "${backuptar}"
153161
}
154162

155163
clip () {
@@ -159,16 +167,17 @@ clip () {
159167

160168
printf "\n"
161169
shift
162-
while [ $timeout -gt 0 ] ; do
163-
printf "\r\033[KPassword on clipboard! Clearing in %.d" $((timeout--))
170+
while [ $cb_timeout -gt 0 ] ; do
171+
printf "\r\033[KPassword on clipboard! Clearing in %.d" $((cb_timeout--))
164172
sleep 1
165173
done
166174

175+
printf "\n"
167176
printf "" | ${copy}
168177
}
169178

170179
new_entry () {
171-
# Prompt for new username and/or password.
180+
# Prompt for username and password.
172181

173182
username=""
174183
while [[ -z "${username}" ]] ; do
@@ -183,7 +192,9 @@ new_entry () {
183192
fi
184193

185194
printf "\n"
186-
if [[ -z "${password}" ]] ; then userpass=$(gen_pass "$@") ; fi
195+
if [[ -z "${password}" ]] ; then
196+
userpass=$(gen_pass "$@")
197+
fi
187198
}
188199

189200
print_help () {
@@ -207,7 +218,7 @@ print_help () {
207218
* Copy the password for 'userName' to clipboard:
208219
./pwd.sh r userName
209220
210-
* List stored passwords and copy a previous version:
221+
* List stored passwords and copy a specific version:
211222
./pwd.sh l
212223
./pwd.sh r userName@1574723625
213224
@@ -234,7 +245,7 @@ action=""
234245
if [[ -n "${1+x}" ]] ; then action="${1}" ; fi
235246

236247
while [[ -z "${action}" ]] ; do
237-
read -n 1 -p "
248+
read -r -n 1 -p "
238249
Read or Write (or Help for more options): " action
239250
printf "\n"
240251
done
@@ -252,8 +263,14 @@ elif [[ "${action}" =~ ^([wW])$ ]] ; then
252263
new_entry "$@"
253264
write_pass
254265

266+
if [[ "${daily_backup}" = "true" ]] ; then
267+
if [[ ! -f ${backuptar} ]] ; then
268+
backup
269+
fi
270+
fi
271+
255272
else read_pass "$@" ; fi
256273

257274
chmod -R 0400 "${safeix}" "${safedir}" 2>/dev/null
258275

259-
tput setaf 2 2 2 ; printf "\nDone\n" ; tput sgr0
276+
tput setaf 2 ; printf "\nDone\n" ; tput sgr0

0 commit comments

Comments
 (0)