Add support for pinentry protocol

This feature is now used by Emacs so that Emacs doesn't need to handle
the passphrase itself.
pull/10/head
Christopher Wellons 2017-12-23 21:46:51 -05:00
parent e9a50022c0
commit a407afcdaf
6 changed files with 142 additions and 9 deletions

View File

@ -258,6 +258,14 @@ time with an optional argument to `--agent`.
Whether or not to enable the agent by default. This can be explicitly Whether or not to enable the agent by default. This can be explicitly
overridden at run time with `--agent` and `--no-agent`. overridden at run time with `--agent` and `--no-agent`.
#### `ENCHIVE_PINENTRY_DEFAULT`
The default program to use for `pinentry`.
#### `ENCHIVE_PINENTRY_DEFAULT_ENABLED`
Whether or not to use `pinentry` by default when reading passphrases.
#### `ENCHIVE_KEY_DERIVE_ITERATIONS` #### `ENCHIVE_KEY_DERIVE_ITERATIONS`
Power-of-two exponent for protection key derivation. Can be configured Power-of-two exponent for protection key derivation. Can be configured

View File

@ -35,6 +35,14 @@
# define ENCHIVE_AGENT_DEFAULT_ENABLED 0 # define ENCHIVE_AGENT_DEFAULT_ENABLED 0
#endif #endif
#ifndef ENCHIVE_PINENTRY_DEFAULT
# define ENCHIVE_PINENTRY_DEFAULT pinentry
#endif
#ifndef ENCHIVE_PINENTRY_DEFAULT_ENABLED
# define ENCHIVE_PINENTRY_DEFAULT_ENABLED 0
#endif
#ifndef ENCHIVE_PASSPHRASE_MAX #ifndef ENCHIVE_PASSPHRASE_MAX
# define ENCHIVE_PASSPHRASE_MAX 1024 # define ENCHIVE_PASSPHRASE_MAX 1024
#endif #endif

View File

@ -4,12 +4,8 @@
;;; Commentary: ;;; Commentary:
;; Load this file, then M-x `enchive-mode' to enable automatic ;; Load this file, then M-x `enchive-mode' (global minor mode) to
;; encryption and decryption of Enchive files. ;; enable automatic encryption and decryption of Enchive files.
;; The agent *must* be started before any encrypted files are opened
;; because this mode doesn't (yet) know how to prompt for a
;; passphrase.
;;; Code: ;;; Code:
@ -28,7 +24,8 @@
(let ((file-name-handler-alist ())) (let ((file-name-handler-alist ()))
(cond ((eq operation 'insert-file-contents) (cond ((eq operation 'insert-file-contents)
(let ((file (car args))) (let ((file (car args)))
(unless (= 0 (call-process "enchive" file t "--agent" "extract")) (unless (= 0 (call-process "enchive" file t nil
"--pinentry" "extract"))
(error "Enchive subprocess failed")) (error "Enchive subprocess failed"))
(setf buffer-file-name file) (setf buffer-file-name file)
(list file (buffer-size)))) (list file (buffer-size))))

View File

@ -40,7 +40,7 @@ Like GnuPG, you can safely encrypt files on systems that you don't trust with yo
Files are secured with ChaCha20, Curve25519, and HMAC-SHA256. Files are secured with ChaCha20, Curve25519, and HMAC-SHA256.
.SH OPTIONS .SH OPTIONS
.TP .TP
\fB\-a\fR \fIseconds\fR, \fB\-\-agent\fR[=\fIseconds\fR] \fB\-a\fR\fIseconds\fR, \fB\-\-agent\fR[=\fIseconds\fR]
Runs the key agent for awhile after successfully reading the passphrase. Runs the key agent for awhile after successfully reading the passphrase.
The agent will remain resident in memory until a period of inactivity passes. The agent will remain resident in memory until a period of inactivity passes.
Default is 900 seconds (15 minutes). Default is 900 seconds (15 minutes).
@ -48,6 +48,10 @@ Default is 900 seconds (15 minutes).
\fB\-A\fB, \fB\-\-no\-agent\fR \fB\-A\fB, \fB\-\-no\-agent\fR
Do not start the key agent (default). Do not start the key agent (default).
.TP .TP
\fB\-e\fR\fIprogram\fR, \fB\-\-pinentry\fR[=\fIprogram\fR]
Read passphrases using the system's pinentry program. By default
Enchive uses the program named "pinentry".
.TP
\fB\-p, \-\-pubkey\fR \fIfile\fR \fB\-p, \-\-pubkey\fR \fIfile\fR
Specifies the public key file to use for encryption. Specifies the public key file to use for encryption.
.TP .TP

View File

@ -24,6 +24,12 @@ static const char *docs_usage[] = {
" (default)", " (default)",
# endif # endif
#endif #endif
" -e, --pinentry[=program] use pinentry to read passphrases"
#if ENCHIVE_PINENTRY_DEFAULT_ENABLED
" (default)",
#else
"",
#endif
" --version display version information", " --version display version information",
" --help display this usage information", " --help display this usage information",
"", "",

View File

@ -22,6 +22,12 @@ static int global_agent_timeout = ENCHIVE_AGENT_TIMEOUT;
static int global_agent_timeout = 0; static int global_agent_timeout = 0;
#endif #endif
#if ENCHIVE_PINENTRY_DEFAULT_ENABLED
static char *pinentry_path = STR(ENCHIVE_PINENTRY_DEFAULT);
#else
static char *pinentry_path = 0;
#endif
static const char enchive_suffix[] = ".enchive"; static const char enchive_suffix[] = ".enchive";
static struct { static struct {
@ -432,10 +438,107 @@ get_passphrase_dumb(char *buf, size_t len, char *prompt)
#include <unistd.h> #include <unistd.h>
#include <termios.h> #include <termios.h>
static void
pinentry_decode(char *buf, size_t blen, const char *str)
{
size_t i, j;
for (i = 0, j = 0; str[i] && str[i] != '\n' && j < blen - 1; i++) {
int c = str[i];
if (c == '%') {
static const char *hex = "0123456789ABCDEF";
char *nibh, *nibl;
if (!str[i + 1] || !str[i + 2])
fatal("invalid data from pinentry");
nibh = memchr(hex, str[i + 1], 16);
nibl = memchr(hex, str[i + 2], 16);
if (!nibh || !nibl)
fatal("invalid data from pinentry");
buf[j++] = (nibh - hex) * 16 + (nibl - hex);
i += 2;
} else {
buf[j++] = c;
}
}
buf[j] = 0;
}
static void
invoke_pinentry(char *buf, size_t len, char *prompt)
{
int pin[2];
int pout[2];
pid_t pid;
if (pipe(pin) != 0)
fatal("could not start pinentry -- %s", strerror(errno));
if (pipe(pout) != 0)
fatal("could not start pinentry -- %s", strerror(errno));
pid = fork();
if (pid == -1)
fatal("pinentry fork() failed -- %s", strerror(errno));
if (pid) {
FILE *pfi, *pfo;
char line[ENCHIVE_PASSPHRASE_MAX * 3 + 32];
close(pin[0]);
close(pout[1]);
if (!(pfi = fdopen(pin[1], "w")))
fatal("fdopen() input -- %s", strerror(errno));
if (!(pfo = fdopen(pout[0], "r")))
fatal("fdopen() output -- %s", strerror(errno));
if (!fgets(line, sizeof(line), pfo))
/* Likely caused by exec() failure, so exit quietly. */
exit(EXIT_FAILURE);
if (strncmp(line, "OK", 2) != 0)
fatal("pinentry startup failure");
if (fprintf(pfi, "SETPROMPT %s\n", prompt) < 0 || fflush(pfi) < 0)
fatal("pinentry write() -- %s", strerror(errno));
if (!fgets(line, sizeof(line), pfo))
fatal("pinentry read() -- %s", strerror(errno));
if (strncmp(line, "OK", 2) != 0)
fatal("pinentry protocol failure");
if (fprintf(pfi, "GETPIN\n") < 0 || fflush(pfi) < 0)
fatal("pinentry write() -- %s", strerror(errno));
if (!fgets(line, sizeof(line), pfo))
fatal("pinentry read() -- %s", strerror(errno));
if (strncmp(line, "ERR ", 4) == 0)
fatal("passphrase entry canceled");
else if (strncmp(line, "OK", 2) == 0)
buf[0] = 0;
else if (strncmp(line, "D ", 2) == 0)
pinentry_decode(buf, len, line + 2);
else
fatal("pinentry protocol failure");
} else {
close(pin[1]);
close(pout[0]);
dup2(pin[0], STDIN_FILENO);
dup2(pout[1], STDOUT_FILENO);
if (execlp(pinentry_path, pinentry_path, (char *)0))
fatal("exec(\"%s\") failed -- %s",
pinentry_path, strerror(errno));
}
}
static void static void
get_passphrase(char *buf, size_t len, char *prompt) get_passphrase(char *buf, size_t len, char *prompt)
{ {
int tty = open("/dev/tty", O_RDWR); int tty;
if (pinentry_path) {
invoke_pinentry(buf, len, prompt);
return;
}
tty = open("/dev/tty", O_RDWR);
if (tty == -1) { if (tty == -1) {
get_passphrase_dumb(buf, len, prompt); get_passphrase_dumb(buf, len, prompt);
} else { } else {
@ -1401,6 +1504,7 @@ main(int argc, char **argv)
{"agent", 'a', OPTPARSE_OPTIONAL}, {"agent", 'a', OPTPARSE_OPTIONAL},
{"no-agent", 'A', OPTPARSE_NONE}, {"no-agent", 'A', OPTPARSE_NONE},
#endif #endif
{"pinentry", 'e', OPTPARSE_OPTIONAL},
{"pubkey", 'p', OPTPARSE_REQUIRED}, {"pubkey", 'p', OPTPARSE_REQUIRED},
{"seckey", 's', OPTPARSE_REQUIRED}, {"seckey", 's', OPTPARSE_REQUIRED},
{"version", 'V', OPTPARSE_NONE}, {"version", 'V', OPTPARSE_NONE},
@ -1433,6 +1537,12 @@ main(int argc, char **argv)
global_agent_timeout = 0; global_agent_timeout = 0;
break; break;
#endif #endif
case 'e':
if (options->optarg)
pinentry_path = options->optarg;
else
pinentry_path = STR(ENCHIVE_PINENTRY_DEFAULT);
break;
case 'p': case 'p':
global_pubkey = options->optarg; global_pubkey = options->optarg;
break; break;