This adds a new optional build executable called bitcoin-wallet-unlock
. This program invokes Pinentry (part of GnuPG) to securely read the user’s wallet passphrase. This is intended to be used by users of bitcion-cli
, as bitcoin-qt
already reads wallet passphrases securely.
Details
Normally parameters to Bitcoin RPC methods are passed as program arguments to bitcoin-cli
. This is insecure for walletpassphrase
for two reasons:
- The passphrase will be leaked to other processes via the command name, e.g. another user running
top
orps
might see the passphrase in the clear. - In all likelihood the command invocation will end up in the user’s shell history.
A commonly cited workaround is to use bitcoin-cli -stdin walletpassphrase
. In this mode the command arguments are provided to bitcoin-cli
over stdin, and this allows the user to enter a passphrase (and timeout) without the issues described above. However, it is still not perfect, as bitcoin-cli
keeps stdin in ICANON
mode. This means that if you literally type bitcoin-cli -stdin walletpassphrase
and enter a passphrase and timeout value, the passphrase you type will be echoed on the terminal in the clear. Another person shoulder-surfing will be able to see the cleartext passphrase, as can any process doing a screen capture.
Pinentry solves this problem and a few others. When reading from the TTY, Pinentry disables echoing to the console, which means that shoulder-surfers can’t see the passphrase. Pinentry has some other nifty features. For example, if you invoke pinentry from a terminal emulator in a graphical session in most cases it can automatically upgrade to a graphical input method, e.g. using GTK. This is what input looks like by default for me in GNOME when running bitcoin-wallet-unlock
from a terminal: https://monad.io/bwemqwkg.png
This implementation of bitcoin-wallet-unlock
allows using -p
to force a particular pinentry backend, and -t
to force TTY input. Extra arguments passed to bitcoin-wallet-unlock
will be forwarded to the underlying pinentry program; there’s an example of this in the comment for GetPinentryPassphrase()
. Communication with Pinentry is done using a limited subset of the assuan protocol, which is described here.
These are the caveats I know of with this PR:
- Since the HTTP client code in
bitcoin-cli
isn’t well factored, I am just execingbitcoin-cli
, rather than actually making the RPC request from thebitcoin-wallet-unlock
program. This feels a little bit hacky, but minimizes code churn. - The default behavior is to use a relative path for
bitcoin-cli
andpinentry
when callingexecvp()
. This could potentially cause a malicious program to be invoked if an attacker can put bad things in the users PATH. This can be mitigated by invokingbitcoin-wallet-unlock
with absolute paths, e.g.bitcoin-wallet-unlock -c /usr/bin/bitcoin-cli -p /usr/bin/pinentry
. - Graphical pinentry methods can delegate to a desktop password manager (e.g. with
gnome-keyring-daemon
on GNOME) when pinentry is linked with libsecret. That is a cool feautre, but I don’t enable this because I don’t think it’s secure (it’s trivial to dump the plaintext passwords with these once you’re logged in as a user). - This code is not portable to Windows as it uses
pipe()
andexec()
(although I suspect most Windows users are usingbitcoin-qt
).