diff --git a/agent/Makefile.am b/agent/Makefile.am index ce0b59218..a2f8a9a8c 100644 --- a/agent/Makefile.am +++ b/agent/Makefile.am @@ -26,6 +26,8 @@ LDFLAGS = @LDFLAGS@ gpg_agent_SOURCES = \ gpg-agent.c agent.h \ command.c \ + query.c \ + trans.c \ findkey.c \ pksign.c \ pkdecrypt.c diff --git a/agent/agent.h b/agent/agent.h index e3ba2f262..3cf340ccb 100644 --- a/agent/agent.h +++ b/agent/agent.h @@ -34,6 +34,8 @@ struct { int quiet; /* be as quiet as possible */ int dry_run; /* don't change any persistent data */ const char *homedir; /* configuration directory name */ + const char *pinentry_program; + } opt; @@ -66,9 +68,22 @@ struct server_control_s { }; typedef struct server_control_s *CTRL; + +struct pin_entry_info_s { + int min_digits; /* min. number of digits required or 0 for freeform entry */ + int max_digits; /* max. number of allowed digits allowed*/ + int max_tries; + int failed_tries; + size_t max_length; /* allocated length of the buffer */ + char pin[1]; +}; + + /*-- gpg-agent.c --*/ void agent_exit (int rc); +/*-- trans.c --*/ +const char *trans (const char *text); /*-- command.c --*/ void start_command_handler (void); @@ -76,6 +91,8 @@ void start_command_handler (void); /*-- findkey.c --*/ GCRY_SEXP agent_key_from_file (const unsigned char *grip); +/*-- query.c --*/ +int agent_askpin (const char *desc_text, struct pin_entry_info_s *pininfo); /*-- pksign.c --*/ int agent_pksign (CTRL ctrl, FILE *outfp); diff --git a/agent/command.c b/agent/command.c index 4e3da80a4..33e61f69c 100644 --- a/agent/command.c +++ b/agent/command.c @@ -79,6 +79,11 @@ rc_to_assuan_status (int rc) case GNUPG_No_Secret_Key: rc = ASSUAN_No_Secret_Key; break; case GNUPG_Invalid_Data: rc = ASSUAN_Invalid_Data; break; + case GNUPG_Bad_PIN: + case GNUPG_Bad_Passphrase: + rc = ASSUAN_No_Secret_Key; + break; + case GNUPG_Read_Error: case GNUPG_Write_Error: case GNUPG_IO_Error: @@ -295,13 +300,11 @@ start_command_handler (void) ctrl.server_local->assuan_ctx = ctx; ctrl.server_local->message_fd = -1; - log_info ("Assuan started\n"); for (;;) { rc = assuan_accept (ctx); if (rc == -1) { - log_info ("Assuan terminated\n"); break; } else if (rc) diff --git a/agent/findkey.c b/agent/findkey.c index 9b0eb157b..84af44f43 100644 --- a/agent/findkey.c +++ b/agent/findkey.c @@ -30,6 +30,33 @@ #include "agent.h" +static int +unprotect (GCRY_SEXP s_skey) +{ + struct pin_entry_info_s *pi; + int rc; + + /* fixme: check whether the key needs unprotection */ + + /* fixme: allocate the pin in secure memory */ + pi = xtrycalloc (1, sizeof (*pi) + 100); + pi->max_length = 100; + pi->min_digits = 4; + pi->max_digits = 8; + pi->max_tries = 3; + + rc = agent_askpin (NULL, pi); + /* fixme: actually unprotect the key and ask again until we get a valid + PIN - agent_askpin takes care of counting failed tries */ + + xfree (pi); + return rc; +} + + + + + /* Return the secret key as an S-Exp after locating it using the grip. Returns NULL if key is not available. */ GCRY_SEXP agent_key_from_file (const unsigned char *grip) @@ -86,6 +113,17 @@ agent_key_from_file (const unsigned char *grip) return NULL; } + rc = unprotect (s_skey); + if (rc) + { + gcry_sexp_release (s_skey); + log_error ("failed to unprotect the secret key: %s\n", + gcry_strerror (rc)); + return NULL; + } + return s_skey; } + + diff --git a/agent/gpg-agent.c b/agent/gpg-agent.c index 28f20dab0..2e65080ce 100644 --- a/agent/gpg-agent.c +++ b/agent/gpg-agent.c @@ -64,6 +64,9 @@ enum cmd_and_opt_values oFlush, oLogFile, oServer, + + oPinentryProgram, + aTest }; @@ -72,7 +75,6 @@ static ARGPARSE_OPTS opts[] = { { 301, NULL, 0, N_("@Options:\n ") }, - /* FIXME: add option --server */ { oServer, "server", 0, N_("run in server mode") }, { oVerbose, "verbose", 0, N_("verbose") }, { oQuiet, "quiet", 0, N_("be somewhat more quiet") }, @@ -87,6 +89,10 @@ static ARGPARSE_OPTS opts[] = { { oLogFile, "log-file" ,2, N_("use a log file for the server")}, { oShutdown, "shutdown" ,0, N_("shutdown the agent")}, { oFlush , "flush" ,0, N_("flush the cache")}, + + { oPinentryProgram, "pinentry-program", 2 , "Path of PIN Entry program" }, + + {0} }; @@ -384,6 +390,8 @@ main (int argc, char **argv ) case oSh: csh_style = 0; break; case oServer: server_mode = 1; break; + case oPinentryProgram: opt.pinentry_program = pargs.r.ret_str; break; + default : pargs.err = configfp? 1:2; break; } } diff --git a/agent/query.c b/agent/query.c new file mode 100644 index 000000000..27dc6dba9 --- /dev/null +++ b/agent/query.c @@ -0,0 +1,219 @@ +/* query.c - fork of the pinentry to query stuff from the user + * Copyright (C) 2001 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "agent.h" +#include "../assuan/assuan.h" + +#ifdef _POSIX_OPEN_MAX +#define MAX_OPEN_FDS _POSIX_OPEN_MAX +#else +#define MAX_OPEN_FDS 20 +#endif + +#define LINELENGTH 1002 /* 1000 + [CR,]LF */ + +static ASSUAN_CONTEXT entry_ctx = NULL; + +/* data to be passed to our callbacks */ +struct entry_parm_s { + int lines; + size_t size; + char *buffer; +}; + + + + +/* Fork off the pin entry if this has not already been done */ +static int +start_pinentry (void) +{ + int rc; + const char *pgmname; + ASSUAN_CONTEXT ctx; + const char *argv[3]; + + if (entry_ctx) + return 0; /* No need to serialize things becuase the agent is + expected to tun as a single-thread (or may be in + future using libpth) */ + + + log_debug ("no running PIN Entry - starting it\n"); + + if (fflush (NULL)) + { + log_error ("error flushing pending output: %s\n", strerror (errno)); + return seterr (Write_Error); + } + + /* FIXME: change the default location of the program */ + if (!opt.pinentry_program || !*opt.pinentry_program) + opt.pinentry_program = "../../pinentry/kpinentry/kpinentry"; + if ( !(pgmname = strrchr (opt.pinentry_program, '/'))) + pgmname = opt.pinentry_program; + else + pgmname++; + + argv[0] = pgmname; + argv[1] = NULL; + + /* connect to the pinentry and perform initial handshaking */ + rc = assuan_pipe_connect (&ctx, opt.pinentry_program, (char**)argv); + if (rc) + { + log_error ("can't connect to the PIN entry module: %s\n", + assuan_strerror (rc)); + return seterr (No_PIN_Entry); + } + entry_ctx = ctx; + + log_debug ("connection to PIN entry established\n"); + + if (DBG_COMMAND) + { + log_debug ("waiting for debugger [hit RETURN when ready] .....\n"); + getchar (); + log_debug ("... okay\n"); + } + + return 0; +} + + +static AssuanError +getpin_cb (void *opaque, const void *buffer, size_t length) +{ + struct entry_parm_s *parm = opaque; + + /* we expect the pin to fit on one line */ + if (parm->lines || length >= parm->size) + return ASSUAN_Too_Much_Data; + + /* fixme: we should make sure that the assuan buffer is allocated in + secure memory or read the response byte by byte */ + memcpy (parm->buffer, buffer, length); + parm->buffer[length] = 0; + parm->lines++; + return 0; +} + + +static int +all_digitsp( const char *s) +{ + for (; *s && *s >= '0' && *s <= '9'; s++) + ; + return !*s; +} + + + +/* Call the Entry and ask for the PIN. We do chekc for a valid PIN + number here and repeat it as long as we have invalid formed + numbers. */ +int +agent_askpin (const char *desc_text, + struct pin_entry_info_s *pininfo) +{ + int rc; + char line[LINELENGTH]; + struct entry_parm_s parm; + const char *errtext = NULL; + + if (!pininfo || pininfo->max_length < 1) + return seterr (Invalid_Value); + if (!desc_text) + desc_text = trans ("Please enter you PIN, so that the secret key " + "can be unlocked for this session"); + + rc = start_pinentry (); + if (rc) + return rc; + + snprintf (line, DIM(line)-1, "SETDESC %s", desc_text); + line[DIM(line)-1] = 0; + rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL); + if (rc) + return map_assuan_err (rc); + + rc = assuan_transact (entry_ctx, + pininfo->min_digits? "SETPROMPT PIN:" + : "SETPROMPT Passphrase:", + NULL, NULL, NULL, NULL); + if (rc) + return map_assuan_err (rc); + + for (;pininfo->failed_tries < pininfo->max_tries; pininfo->failed_tries++) + { + memset (&parm, 0, sizeof parm); + parm.size = pininfo->max_length; + parm.buffer = pininfo->pin; + + if (errtext) + { + /* fixme: should we show the try count? It must be translated */ + snprintf (line, DIM(line)-1, "SETERROR %s (try %d of %d)", + errtext, pininfo->failed_tries+1, pininfo->max_tries); + line[DIM(line)-1] = 0; + rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL); + if (rc) + return map_assuan_err (rc); + errtext = NULL; + } + + rc = assuan_transact (entry_ctx, "GETPIN", getpin_cb, &parm, NULL, NULL); + if (rc == ASSUAN_Too_Much_Data) + errtext = pininfo->min_digits? trans ("PIN too long") + : trans ("Passphrase too long"); + else if (rc) + return map_assuan_err (rc); + if (!errtext && !pininfo->min_digits) + return 0; /* okay, got a passphrase */ + if (!errtext && !all_digitsp (pininfo->pin)) + errtext = trans ("Invalid characters in PIN"); + if (!errtext && pininfo->max_digits + && strlen (pininfo->pin) > pininfo->max_digits) + errtext = trans ("PIN too long"); + if (!errtext + && strlen (pininfo->pin) < pininfo->min_digits) + errtext = trans ("PIN too short"); + + if (!errtext) + return 0; /* okay, got a PIN */ + } + + return pininfo->min_digits? GNUPG_Bad_PIN : GNUPG_Bad_Passphrase; +} + + + + + diff --git a/agent/trans.c b/agent/trans.c new file mode 100644 index 000000000..7fa5e3d6b --- /dev/null +++ b/agent/trans.c @@ -0,0 +1,42 @@ +/* trans.c - translatable strings + * Copyright (C) 2001 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +/* To avoid any problems with the gettext implementation (there used + to be some vulnerabilities in the last years and the use of + external files is a minor security problem in itself), we use our + own simple translation stuff */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "agent.h" + +const char * +trans (const char *text) +{ + return text; +} diff --git a/common/errors.h b/common/errors.h index a21b492af..d64e3905e 100644 --- a/common/errors.h +++ b/common/errors.h @@ -59,6 +59,10 @@ enum { GNUPG_Invalid_Session_Key = 30, GNUPG_Invalid_Sexp = 31, GNUPG_Unsupported_Algorithm = 32, + GNUPG_No_PIN_Entry = 33, + GNUPG_PIN_Entry_Error = 34, + GNUPG_Bad_PIN = 35, + GNUPG_Bad_Passphrase = 36, }; /* Status codes - fixme: should go into another file */