filosottile / passage Goto Github PK
View Code? Open in Web Editor NEWA fork of password-store (https://www.passwordstore.org) that uses age (https://age-encryption.org) as backend.
License: Other
A fork of password-store (https://www.passwordstore.org) that uses age (https://age-encryption.org) as backend.
License: Other
passage ======= passage is a fork of password-store (https://www.passwordstore.org) that uses age (https://age-encryption.org) as a backend instead of GnuPG. Differences from pass --------------------- The password store is at $HOME/.passage/store by default. For decryption, the age identities at $HOME/.passage/identities are used with the -i age CLI option. For encryption, the nearest .age-recipients file (that is, the one in the same directory as the secret, or in the closest parent) is used with the -R age CLI option. If no .age-recipients files are found, the identities file is used with the -i option. Extensions are searched at $HOME/.passage/extensions. password-store extensions that wish to be compatible with passage can switch on the PASSAGE variable. The init command is not currently available, and moving or copying a secret always re-encrypts it. Example: simple set up ---------------------- In this setup, the key is simply saved on disk, which can be useful if the password store is synced to a location less trusted than the local disk. age-keygen >> $HOME/.passage/identities Example: set up with a password-protected key --------------------------------------------- This setup allows using the identity file password as the primary password to unlock the store. KEY="$(age-keygen)" echo "$KEY" | age -p -a >> $HOME/.passage/identities echo "$KEY" | age-keygen -y >> $HOME/.passage/store/.age-recipients Example: set up with age-plugin-yubikey --------------------------------------- This setup requires age v1.1.0, or rage (https://github.com/str4d/rage), and the PIV plugin age-plugin-yubikey (https://github.com/str4d/age-plugin-yubikey). It's recommended to add more YubiKeys and/or age keys to the .age-recipients file as recovery options, in case this YubiKey is lost. age-plugin-yubikey # run interactive setup age-plugin-yubikey --identity >> $HOME/.passage/identities age-plugin-yubikey --list >> $HOME/.passage/store/.age-recipients Integrating with fzf -------------------- The following script can be invoked with any (or no) passage flags, and spawns a fuzzy search dialog using fzf (https://github.com/junegunn/fzf) for selecting the secret. #! /usr/bin/env bash set -eou pipefail PREFIX="${PASSAGE_DIR:-$HOME/.passage/store}" FZF_DEFAULT_OPTS="" name="$(find "$PREFIX" -type f -name '*.age' | \ sed -e "s|$PREFIX/||" -e 's|\.age$||' | \ fzf --height 40% --reverse --no-multi)" passage "${@}" "$name" Migrating from pass ------------------- #! /usr/bin/env bash set -eou pipefail cd "${PASSWORD_STORE_DIR:-$HOME/.password-store}" while read -r -d "" passfile; do name="${passfile#./}"; name="${name%.gpg}" [[ -f "${PASSAGE_DIR:-$HOME/.passage/store}/$name.age" ]] && continue pass "$name" | passage insert -m "$name" || { passage rm "$name"; break; } done < <(find . -path '*/.git' -prune -o -iname '*.gpg' -print0) Environment variables --------------------- PASSAGE_DIR Password store location PASSAGE_IDENTITIES_FILE Identities file location PASSAGE_AGE age binary (tested with age and rage) PASSAGE_RECIPIENTS_FILE Override recipients for encryption operations Passed to age with -R PASSAGE_RECIPIENTS Override recipients for encryption operations Space separated, each passed to age with -r All other environment variables from password-store are respected, such as PASSWORD_STORE_CLIP_TIME and PASSWORD_STORE_GENERATED_LENGTH.
Now that age-plugin-yubikey
supports multiple keys, what's the best way to add a new yubikey (for example, a backup) to the passage store? I added my new key to the existing identities and .age-recipients and then copied:
passage cp path/to/secret new/path/to/secret
It's a bit cumbersome and sort of messes up the paths in the store. Is this the only option, or is there a better one I haven't considered?
Type passage edit tmp
, then press ctrl+C
during the password prompt. Now, no characters show in the terminal when typing further commands.
I created a password with only one identity in my .passage/identities file. Then later I added a new identity to the same file. Both identities (and recipients in .passage/store/.age-recipients) were setup using age-plugin-yubikey.
Then I used passage cp to reencrypt the secret using the new age-recipients file. When I tried to decrypt the secret, age-plugin-yubikey prompted me for the yubikey that was first in the identifies file. I didn't see how to have it use the new yubikey who's recipient key was used to encrypt the secret.
Does passage + age-plugin-yubikey support multiple yubikeys? Apologies if this is more of a age-plugin-yubikey question.
cc: @str4d
It would be great to have the option to easily rekey all secrets if there are changes to the recipients.
agenix
which is a mechanism to deploy secrets to hosts that uses age has this option, which is very convenient.
In pass
, the gpg-agent
manages the secret associated with the GPG key. This may be the PIN to access a YubiKey, or the passphrase to a password-protected GPG identity file.
On the other hand, passage
prompts for the password to the age
private key every time it is needed. This program should either hand-off the secret-gathering to another program (like an age-plugin-...
or ssh-agent
or something) or it should support the use of a chosen pinentry
program.
Hello,
Many thanks for your work on age
and passage
. I recently switched to both from gpg
and pass
. Key management is much easier with age! :)
Instead of putting .passage/store/
into a git repository, I track the whole .passage
dir (identities are stored on a yubikey). The git subcommand does not recognize that the passage dir is a repository.
I narrowed it down to line 33 in password-store.sh:
[[ $(git -C "$INNER_GIT_DIR" rev-parse --is-inside-work-tree 2>/dev/null) == true ]] || INNER_GIT_DIR=""
This test somehow fails (and resets the variable to ""
) even though executing this test manually results in the correct true
result. I do not understand why this test does not detect correctly that the .passage
dir is a git repository in the script but executing it manually gives the correct result.
The GIT_CEILING_DIRECTORIES
seems to be correctly set to the home directory.
Can somebody help me out here?
Thanks for your help!
Because the repo does not yet have any tagged commits, the current instructions lead to an error though the rest of the steps continue.
fatal: No names found, cannot describe anything. # this is the error from git describe --tags
install: mkdir /usr/local/Cellar/passage
install: mkdir /usr/local/Cellar/passage//lib
install: mkdir /usr/local/Cellar/passage//lib/passage
install: src/platform/darwin.sh -> /usr/local/Cellar/passage//lib/passage/platform.sh
install: mkdir /usr/local/Cellar/passage//lib/passage/extensions
install: mkdir /usr/local/Cellar/passage//bin
install: src/.passage -> /usr/local/Cellar/passage//bin/passage
brew link passage
doesn't work after that, presumably because brew
expects installations to be in a tagged subfolder.
$ brew link passage
Linking /usr/local/Cellar/passage/lib... 0 symlinks created.
Running the commands with a manual tag works fine. E.g., make install PREFIX="$(brew --cellar)/passage/0.0.1"
No need to do anything since I'm assuming that this is still relatively alpha and you're not ready to tag anything yet. But I figured I would put this here for anyone else who has this problem.
When copying/moving to a new path passage will reencrypt.
Would be nice to expose a reencrypt in place subcommand that could be used to rotate the keys used for encryption (eg: in the case they are leaked.
In the feature request #25 I suggested that it might be useful to store the recipient file encrypted on a shared storage as well, if you want to share the passwords with others and the shared storage is not trusted.
But every write operation would require decryption.
To avoid this the following idea might be useful:
A copy of the plaintext recipient file is stored locally (trusted storage) and the hash value of the encrypted recipient file. For each write operation the locally stored hash value is compared with the hash value of the encrypted recipient file stored on the shared storage. If they match, the locally stored plaintext recipient file can be used (this results in less costs than a decryption). If not, decryption must be performed.
Sorry, but another little gotcha: the README's instructions to migrate from pass are zsh specific.
In particular, name="${${passfile#./}%.gpg}"
doesn't work on bash. You need to do
that in two steps for bash.
Maybe just add that those instructions are an example for zsh users and that bash users
should edit accordingly?
For what it's worth, I used the following for bash:
set -o pipefail
cd "${PASSWORD_STORE_DIR:-$HOME/.password-store}" || exit
while read -r -d "" passfile
do
name="${passfile#./}"
name="${name%.gpg}"
[[ -f "${PASSAGE_DIR:-$HOME/.passage/store}/$name.age" ]] && continue
pass "$name" | passage insert -m "$name" || { passage rm "$name"; break; }
done < <(find . -path '*/.git' -prune -o -iname '*.gpg' -print0)
sudo make install
Password:
install: chmod 755 /usr/share/zsh/site-functions: Operation not permitted
install: /usr/share/zsh/site-functions/_passage: Operation not permitted
make: *** [install-common] Error 71
This is very hypothetical, and only based on me reading the source code since I had a bit of trouble installing it.
But pass is vulnerable to file rename attacks, if an attacker have control over the encrypted files then he can copy one file into another, and cause the teams password for service A to be leaked to service B.
See CVE-2020-28086 ( https://blog.hackeriet.no/filename-rename-in-pass/ ) for my writeup on the issue.
It looks like there isn't any protection in passage for this type of attack either.
Sorry if I was wrong about this, just close this issue then :)
And this isn't really responsible disclosure, but i reckon that since this is so new there isn't any user impact to take into account.
It seems that only the $PASSAGE_DIR
folder is checked for the .age-recipients
file. That, or I am misunderstanding the documentation:
For encryption, the nearest .age-recipients file (that is, the one in the same
directory as the secret, or in the closest parent) is used with the -R age CLI
option.
For the current version (set_age_recipients()
):
local current="$PREFIX/$1"
# should prevent traversal above $PREFIX
# vvvvvvvvvvvvvvvvvvvvv
while [[ $current != "$PREFIX" && ! -f $current/.age-recipients ]]; do
current="${current%/*}"
done
current="$current/.age-recipients"
Where PREFIX="${PASSAGE_DIR:-$HOME/.passage/store}"
, seems to prevent passage from traversing upwards. Removing the first condition leads the program to act as I would expect. There may be security implications to allowing an infinite upwards traversal in search for recipients, so the fix may be a documentation edit.
Additionally, there is no warning when the identity key fallback is used; this may lead to an unfortunate UX where additional recipients are not used to encrypt, and a "recovery" key would fail to recover the data.
Thank you for your work.
Steps to reproduce:
[capitol@batia ~]$ cd /tmp/
[capitol@batia tmp]$ mkdir d
[capitol@batia tmp]$ cd d
[capitol@batia d]$ git clone [email protected]:FiloSottile/passage.git
Cloning into 'passage'...
remote: Enumerating objects: 2553, done.
remote: Counting objects: 100% (2553/2553), done.
remote: Compressing objects: 100% (1038/1038), done.
remote: Total 2553 (delta 1471), reused 2550 (delta 1468), pack-reused 0
Receiving objects: 100% (2553/2553), 395.23 KiB | 1.20 MiB/s, done.
Resolving deltas: 100% (1471/1471), done.
[capitol@batia d]$ cd passage/
[capitol@batia passage]$ mkdir /tmp/passage
[capitol@batia passage]$ LANG=C PREFIX=/tmp/passage make install
sed: can't read src/passage.sh: No such file or directory
make: *** [Makefile:52: install] Error 2
Running make install
errors with:
install: cannot stat 'src/completion/pass.fish-completion': No such file or directory
src/completion/pass.fish-completion
was deleted in a previous commit. If dropping fish support was intentional, I think references to the completion need to be removed from the Makefile.
$ make install PREFIX="$(brew --cellar)/passage/dev"
install: mkdir /opt/homebrew/Cellar/passage
install: mkdir /opt/homebrew/Cellar/passage/dev
install: mkdir /opt/homebrew/Cellar/passage/dev/lib
install: mkdir /opt/homebrew/Cellar/passage/dev/lib/passage
install: src/platform/darwin.sh -> /opt/homebrew/Cellar/passage/dev/lib/passage/platform.sh
install: mkdir /opt/homebrew/Cellar/passage/dev/lib/passage/extensions
install: mkdir /opt/homebrew/Cellar/passage/dev/bin
install: src/.passage -> /opt/homebrew/Cellar/passage/dev/bin/passage
Would the project be amenable to being XDG compatible instead of defaulting to ~/.passage
? That is, first check to see if the XDG_CONFIG_HOME
environment variable is defined, and if so, put .passage/
under there. In the typical configuration, this would be ~/.config/passage
.
This simplifies my backups, as I can copy ~/.config/
without having to special case dot folders under $HOME.
I am trying to use multiple yubikeys. To set up a key on a new machine I do the following:
age-plugin-yubikey --identity > $HOME/.passage/identities
git clone password-repo ~$HOME/.passage/store/
pass Example
This obviously fails to work because I did not re-encrypt the repository, but init
is not implemented. Do I just have to copy each file again so it is re-encrypted with both keys?
An attacker should not be able to edit the .age-recipient file unnoticed (e.g. add a new recipient unnoticed). Where and how should the .age-recipient file be stored most securely? Should it be signed (e.g. with minisign) if you store it at a location less trusted than the local disk?
Before adding any passwords:
❯ passage ls
/opt/homebrew/bin/passage: line 290: [[: 0p: value too great for base (error token is "0p")
Error: password store is empty.
After generating the first password:
❯ passage ls
/opt/homebrew/bin/passage: line 290: [[: 0p: value too great for base (error token is "0p")
Passage
And trying to fetch it:
❯ passage show test
/opt/homebrew/bin/passage: line 290: [[: 0p: value too great for base (error token is "0p")
/opt/homebrew/bin/passage: line 297: [[: 0p: value too great for base (error token is "0p")
Clip location '' is not a number.
Occurs in zsh and bash on macOS.
Would y'all mind if I submitted a Homebrew formula for passage
to homebrew/core
? I've written a basic formula here:
class Passage < Formula
desc "Fork of password-store that uses age instead of GnuPG"
homepage "https://github.com/FiloSottile/passage"
url "https://github.com/FiloSottile/passage/archive/1.7.4a1.tar.gz"
version "1.7.4a1"
sha256 "0705ff409d4a6160ade347e63be623170da023ec199116dac83b406a18f7e0d7"
license "GPL-2.0-or-later"
head "https://github.com/FiloSottile/passage.git", branch: "main"
depends_on "age"
depends_on "gnu-getopt"
depends_on "qrencode"
depends_on "tree"
def install
system "make", "PREFIX=#{prefix}", "WITH_ALLCOMP=yes", "BASHCOMPDIR=#{bash_completion}",
"ZSHCOMPDIR=#{zsh_completion}", "FISHCOMPDIR=#{fish_completion}", "install"
inreplace "#{bin}/passage",
/^SYSTEM_EXTENSION_DIR=.*$/,
"SYSTEM_EXTENSION_DIR=\"#{HOMEBREW_PREFIX}/lib/passage/extensions\""
end
test do
(testpath/".passage").mkdir
system Formula["age"].opt_bin/"age-keygen", "-o", ".passage/identities"
system bin/"passage", "generate", "foo.bar"
assert_predicate testpath/".passage/store/foo.bar.age", :exist?
end
end
👋
It's possible to work around this but it does make things more inconvenient.
To reproduce:
.passage/identities
and .passage/store/.age-recipients
with identities found on two different Yubikeys$ passage generate test
Unplug one of the two YubiKeys, then
$ rm .passage/store/.age-recipients
$ passage mv test test_new
What happens now is that for some reason, passage
is asking for the PIN for the Yubikey present and for the second Yubikey to be inserted at the same time, so it's very unclear what to enter. I've tried 2
to skip and typing in the PIN, but neither worked for me ...
This might lead to users inadvertently exhausting their PIN entry attempts ...
EDIT: Probably that is due to the PIN being needed for the decryption, and the missing YubiKey to get the public key?
The tag 1.7.4a0
tries to install fish completion but the completion file for it is not in the repo. It was added later. Could you create a new release with this fixed?
btw thx for doing this. I wanted to port pass to age myself. :D
The pass
utility caches the password to the gpg key for a while just like sudo
which allows for quick subsequent decryptions without entering the password. Is there something similar which can be implemented with passage
?
Is there any interest in adding OTP support in passage? I would be interested in knowing how you @FiloSottile suggest to deal with that, I used the pass-otp extension.
I've setup three Yubikeys with Age and configured passage to use them by running once for each yubikey:
➜ age-plugin-yubikey --identity >> $HOME/.passage/identities
➜ age-plugin-yubikey --list >> $HOME/.passage/store/.age-recipients
I then generated a test password and am only able to decrypt it with the first yubikey I added to the .age-recipients
file:
➜ passage test/pass
Please insert YubiKey with serial 19234119: (y/n)
To share passwords with others, it makes sense to store the data on a shared storage. The shared storage might not be very trusted (e.g. Google Drive).
One problem is to share the recipient file securely with others.
Would it make sense to encrypt the recipient file also for all recipients with the recipient keys of the recipient file and put it on the shared storage? If so, would it make sense to add in passage a command e.g. "encrypt_recipient_file" or an env variable e.g. "ENCRYPT_RECIPIENT_FILE=true"?
When editing a password, passage
prompts for the password once to decrypt the file. But then it asks again when saving the file. It should not need to prompt the second time.
$ passage edit tmp
Enter passphrase for identity file "/home/username/.passage/identities":
Enter passphrase for identity file "/home/username/.passage/identities":
Password unchanged.
passage expects the yubikey to be inserted:
$ passage insert foo -m
Enter contents of foo and press Ctrl+D when finished:
Please insert YubiKey with serial 12345678 (press [1] for "YubiKey is plugged in" or [2] for "Skip this YubiKey")
Pressing 2:
age: error: failed to wrap key for recipient #1: yubikey plugin: Could not open YubiKey with serial 12345678
age: report unexpected or unhelpful errors at https://filippo.io/age/report
Password encryption aborted.
I was expecting to be able to encrypt to a yubikey that isn't present.
Expected behavior:
No matter if the standard path to stores and identities as well as env vars like PASSAGE_DIR and/or PASSAGE_IDENTITIES_FILE are empty the exit status code for passage --version
will be 0.
Actual behavior:
passage returns with exit status 1 if the standard identities path and PASSAGE_IDENTITIES_FILE are empty.
Because passage returns without error if standard store and PASSAGE_DIR are empty, I would think that also with missing identities passage should exit without an error.
On macOS:
$ passage
/opt/homebrew/bin/passage: line 281: /opt/homebrew/opt/gnu-getopt/bin/getopt: No such file or directory
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.