I need an MWE of use GnuPG pinentry with Assuan protocol in C project, i.e. Assuan context proper initialization, connection, etc.
I've tried to read docs, but both pinentry and Assuan documentation are not very helpful on this subject, especially for a beginner.
Well, I've read gpg-agent
sources and have finally found some clues. The following code is working well for pinentry-qt5
, though it definitely need some security tuning.
#include <assuan.h>
#include <gcrypt.h> // secure memory allocation for the passphrase
#include <string.h>
// This wrapper is for Assuan commands that return OK/<error> and nothing more
void pinentry_nooutput_cmd (assuan_context_t ctx_p, const char *cmd) {
char *line;
size_t linelen;
gpg_error_t err;
err = assuan_write_line (ctx_p, cmd);
if (err) {
fprintf (stderr, "assuan send error: %s\n", gpg_strerror (err));
exit (1);
}
err = assuan_read_line (ctx_p, &line, &linelen);
if (err) {
fprintf (stderr, "assuan receive error: %s\n", gpg_strerror (err));
exit (1);
}
if (strcmp (line, "OK")) {
fprintf (stderr, "pinentry error: %s\n", line);
exit (1);
}
}
// This function is used to get a passphrase from the user and store it in secmem
// 'repeat' is 1 for a new passphrase, and 0 otherwise
char *pinentry_get_passphrase (const char *pinentry_exec, int repeat) {
assuan_context_t ctx_p;
gpg_error_t err;
// pinentry command line options. --no-global-grab is used for debug
const char *argv[] = { "--no-global-grab", NULL };
// file descriptors that should stay open. Apparently none is needed
assuan_fd_t fd_child_list[] = { ASSUAN_INVALID_FD };
// context allocation
err = assuan_new (&ctx_p);
if (err) if (err) {
fprintf (stderr, "cannot allocate assuan context: %s\n", gpg_strerror (err));
exit (1);
}
// connect to pinentry.
// here can be a callback for pinentry memory cleanup, but it is not necessary and can be NULL
err = assuan_pipe_connect (ctx_p, pinentry_exec, argv, fd_child_list, NULL, NULL, 0);
if (err) {
fprintf (stderr, "cannot connect to pinentry: %s\n", gpg_strerror (err));
exit (1);
}
// example pinentry tuning according to its docs
pinentry_nooutput_cmd (ctx_p, "SETTIMEOUT 120");
pinentry_nooutput_cmd (ctx_p, "SETTITLE Password");
pinentry_nooutput_cmd (ctx_p, "SETDESC Please enter password:");
// set repeat if needed
if (repeat)
pinentry_nooutput_cmd (ctx_p, "SETREPEAT");
// get passphrase
err = assuan_write_line (ctx_p, "GETPIN");
if (err) {
fprintf (stderr, "assuan send error: %s\n", gpg_strerror (err));
exit (1);
}
char *line;
size_t linelen;
err = assuan_read_line (ctx_p, &line, &linelen);
if (err) {
fprintf (stderr, "assuan receive error: %s\n", gpg_strerror (err));
exit (1);
}
// if repeat, check the answer and read one more line
if (repeat) {
if (strcmp (line, "S PIN_REPEATED")) {
fprintf (stderr, "pinentry error: %s\n", line);
exit (1);
}
err = assuan_read_line (ctx_p, &line, &linelen);
if (err) {
fprintf (stderr, "assuan receive error: %s\n", gpg_strerror (err));
exit (1);
}
}
// check for empty password if needed
if (!strcmp (line, "OK")) {
fputs ("error: empty password\n", stderr);
exit (1);
}
// read the passphrase (answer is "D <passphrase>")
if (*line != 'D') {
fprintf (stderr, "pinentry error: %s\n", line);
exit (1);
}
// move pointer
line += 2;
// get password length
size_t length = strlen (line);
// alloc secmem
char *password = gcry_malloc_secure (length + 1);
memmove (password, line, length);
// add null termination
*(line + length) = '\0';
// release the context
assuan_release (ctx_p);
return password;
}
int main () {
char *newpass = pinentry_get_passphrase ("/usr/bin/pinentry", 1);
fprintf (stdout, "New pass: %s\n", newpass);
gcry_free (newpass);
char *pass = pinentry_get_passphrase ("/usr/bin/pinentry", 0);
fprintf (stdout, "Pass: %s\n", pass);
gcry_free (pass);
return 0;
}
Compile:
gcc -x c -std=c17 \
-o pinentry_test \
`pkg-config --cflags libassuan gpg-error libgcrypt` \
pinentry_test.c \
`pkg-config --libs libassuan gpg-error libgcrypt`;