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
toporpsmight 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-cliisn’t well factored, I am just execingbitcoin-cli, rather than actually making the RPC request from thebitcoin-wallet-unlockprogram. This feels a little bit hacky, but minimizes code churn. - The default behavior is to use a relative path for
bitcoin-cliandpinentrywhen 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-unlockwith 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-daemonon 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).