diff --git a/agent/call-scd.c b/agent/call-scd.c index 6438693af..c5b95f433 100644 --- a/agent/call-scd.c +++ b/agent/call-scd.c @@ -1,1335 +1,1340 @@ /* call-scd.c - fork of the scdaemon to do SC operations * Copyright (C) 2001, 2002, 2005, 2007, 2010, * 2011 Free Software Foundation, Inc. * Copyright (C) 2013 Werner Koch * * 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 3 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, see . */ #include #include #include #include #include #include #include #include #ifdef HAVE_SIGNAL_H # include #endif #include #include #ifndef HAVE_W32_SYSTEM #include #endif #include #include "agent.h" #include #include "../common/strlist.h" #ifdef _POSIX_OPEN_MAX #define MAX_OPEN_FDS _POSIX_OPEN_MAX #else #define MAX_OPEN_FDS 20 #endif /* Definition of module local data of the CTRL structure. */ struct scd_local_s { /* We keep a list of all allocated context with an anchor at SCD_LOCAL_LIST (see below). */ struct scd_local_s *next_local; /* We need to get back to the ctrl object actually referencing this structure. This is really an awkward way of enumerating the local contexts. A much cleaner way would be to keep a global list of ctrl objects to enumerate them. */ ctrl_t ctrl_backlink; assuan_context_t ctx; /* NULL or session context for the SCdaemon used with this connection. */ int locked; /* This flag is used to assert proper use of start_scd and unlock_scd. */ }; /* Callback parameter for learn card */ struct learn_parm_s { void (*kpinfo_cb)(void*, const char *); void *kpinfo_cb_arg; void (*certinfo_cb)(void*, const char *); void *certinfo_cb_arg; void (*sinfo_cb)(void*, const char *, size_t, const char *); void *sinfo_cb_arg; }; /* Callback parameter used by inq_getpin and inq_writekey_parms. */ struct inq_needpin_parm_s { assuan_context_t ctx; int (*getpin_cb)(void *, const char *, const char *, char*, size_t); void *getpin_cb_arg; const char *getpin_cb_desc; assuan_context_t passthru; /* If not NULL, pass unknown inquiries up to the caller. */ /* The next fields are used by inq_writekey_parm. */ const unsigned char *keydata; size_t keydatalen; }; /* To keep track of all active SCD contexts, we keep a linked list anchored at this variable. */ static struct scd_local_s *scd_local_list; /* A Mutex used inside the start_scd function. */ static npth_mutex_t start_scd_lock; /* A malloced string with the name of the socket to be used for additional connections. May be NULL if not provided by SCdaemon. */ static char *socket_name; /* The context of the primary connection. This is also used as a flag to indicate whether the scdaemon has been started. */ static assuan_context_t primary_scd_ctx; /* To allow reuse of the primary connection, the following flag is set to true if the primary context has been reset and is not in use by any connection. */ static int primary_scd_ctx_reusable; /* Local prototypes. */ /* This function must be called once to initialize this module. This has to be done before a second thread is spawned. We can't do the static initialization because NPth emulation code might not be able to do a static init; in particular, it is not possible for W32. */ void initialize_module_call_scd (void) { static int initialized; int err; if (!initialized) { err = npth_mutex_init (&start_scd_lock, NULL); if (err) log_fatal ("error initializing mutex: %s\n", strerror (err)); initialized = 1; } } /* This function may be called to print information pertaining to the current state of this module to the log. */ void agent_scd_dump_state (void) { log_info ("agent_scd_dump_state: primary_scd_ctx=%p pid=%ld reusable=%d\n", primary_scd_ctx, (long)assuan_get_pid (primary_scd_ctx), primary_scd_ctx_reusable); if (socket_name) log_info ("agent_scd_dump_state: socket='%s'\n", socket_name); } /* The unlock_scd function shall be called after having accessed the SCD. It is currently not very useful but gives an opportunity to keep track of connections currently calling SCD. Note that the "lock" operation is done by the start_scd() function which must be called and error checked before any SCD operation. CTRL is the usual connection context and RC the error code to be passed trhough the function. */ static int unlock_scd (ctrl_t ctrl, int rc) { if (ctrl->scd_local->locked != 1) { log_error ("unlock_scd: invalid lock count (%d)\n", ctrl->scd_local->locked); if (!rc) rc = gpg_error (GPG_ERR_INTERNAL); } ctrl->scd_local->locked = 0; return rc; } /* To make sure we leave no secrets in our image after forking of the scdaemon, we use this callback. */ static void atfork_cb (void *opaque, int where) { (void)opaque; if (!where) gcry_control (GCRYCTL_TERM_SECMEM); } /* Fork off the SCdaemon if this has not already been done. Lock the daemon and make sure that a proper context has been setup in CTRL. This function might also lock the daemon, which means that the caller must call unlock_scd after this function has returned success and the actual Assuan transaction been done. */ static int start_scd (ctrl_t ctrl) { gpg_error_t err = 0; const char *pgmname; assuan_context_t ctx = NULL; const char *argv[5]; assuan_fd_t no_close_list[3]; int i; int rc; char *abs_homedir = NULL; if (opt.disable_scdaemon) return gpg_error (GPG_ERR_NOT_SUPPORTED); /* If this is the first call for this session, setup the local data structure. */ if (!ctrl->scd_local) { ctrl->scd_local = xtrycalloc (1, sizeof *ctrl->scd_local); if (!ctrl->scd_local) return gpg_error_from_syserror (); ctrl->scd_local->ctrl_backlink = ctrl; ctrl->scd_local->next_local = scd_local_list; scd_local_list = ctrl->scd_local; } /* Assert that the lock count is as expected. */ if (ctrl->scd_local->locked) { log_error ("start_scd: invalid lock count (%d)\n", ctrl->scd_local->locked); return gpg_error (GPG_ERR_INTERNAL); } ctrl->scd_local->locked++; if (ctrl->scd_local->ctx) return 0; /* Okay, the context is fine. We used to test for an alive context here and do an disconnect. Now that we have a ticker function to check for it, it is easier not to check here but to let the connection run on an error instead. */ /* We need to protect the following code. */ rc = npth_mutex_lock (&start_scd_lock); if (rc) { log_error ("failed to acquire the start_scd lock: %s\n", strerror (rc)); return gpg_error (GPG_ERR_INTERNAL); } /* Check whether the pipe server has already been started and in this case either reuse a lingering pipe connection or establish a new socket based one. */ if (primary_scd_ctx && primary_scd_ctx_reusable) { ctx = primary_scd_ctx; primary_scd_ctx_reusable = 0; if (opt.verbose) log_info ("new connection to SCdaemon established (reusing)\n"); goto leave; } rc = assuan_new (&ctx); if (rc) { log_error ("can't allocate assuan context: %s\n", gpg_strerror (rc)); err = rc; goto leave; } if (socket_name) { rc = assuan_socket_connect (ctx, socket_name, 0, 0); if (rc) { log_error ("can't connect to socket '%s': %s\n", socket_name, gpg_strerror (rc)); err = gpg_error (GPG_ERR_NO_SCDAEMON); goto leave; } if (opt.verbose) log_info ("new connection to SCdaemon established\n"); goto leave; } if (primary_scd_ctx) { log_info ("SCdaemon is running but won't accept further connections\n"); err = gpg_error (GPG_ERR_NO_SCDAEMON); goto leave; } /* Nope, it has not been started. Fire it up now. */ if (opt.verbose) log_info ("no running SCdaemon - starting it\n"); if (fflush (NULL)) { #ifndef HAVE_W32_SYSTEM err = gpg_error_from_syserror (); #endif log_error ("error flushing pending output: %s\n", strerror (errno)); /* At least Windows XP fails here with EBADF. According to docs and Wine an fflush(NULL) is the same as _flushall. However the Wime implementation does not flush stdin,stdout and stderr - see above. Lets try to ignore the error. */ #ifndef HAVE_W32_SYSTEM goto leave; #endif } if (!opt.scdaemon_program || !*opt.scdaemon_program) opt.scdaemon_program = gnupg_module_name (GNUPG_MODULE_NAME_SCDAEMON); if ( !(pgmname = strrchr (opt.scdaemon_program, '/'))) pgmname = opt.scdaemon_program; else pgmname++; argv[0] = pgmname; argv[1] = "--multi-server"; if (gnupg_default_homedir_p ()) argv[2] = NULL; else { abs_homedir = make_absfilename_try (gnupg_homedir (), NULL); if (!abs_homedir) { log_error ("error building filename: %s\n", gpg_strerror (gpg_error_from_syserror ())); goto leave; } argv[2] = "--homedir"; argv[3] = abs_homedir; argv[4] = NULL; } i=0; if (!opt.running_detached) { if (log_get_fd () != -1) no_close_list[i++] = assuan_fd_from_posix_fd (log_get_fd ()); no_close_list[i++] = assuan_fd_from_posix_fd (fileno (stderr)); } no_close_list[i] = ASSUAN_INVALID_FD; /* Connect to the scdaemon and perform initial handshaking. Use detached flag so that under Windows SCDAEMON does not show up a new window. */ rc = assuan_pipe_connect (ctx, opt.scdaemon_program, argv, no_close_list, atfork_cb, NULL, ASSUAN_PIPE_CONNECT_DETACHED); if (rc) { log_error ("can't connect to the SCdaemon: %s\n", gpg_strerror (rc)); err = gpg_error (GPG_ERR_NO_SCDAEMON); goto leave; } if (opt.verbose) log_debug ("first connection to SCdaemon established\n"); /* Get the name of the additional socket opened by scdaemon. */ { membuf_t data; unsigned char *databuf; size_t datalen; xfree (socket_name); socket_name = NULL; init_membuf (&data, 256); assuan_transact (ctx, "GETINFO socket_name", put_membuf_cb, &data, NULL, NULL, NULL, NULL); databuf = get_membuf (&data, &datalen); if (databuf && datalen) { socket_name = xtrymalloc (datalen + 1); if (!socket_name) log_error ("warning: can't store socket name: %s\n", strerror (errno)); else { memcpy (socket_name, databuf, datalen); socket_name[datalen] = 0; if (DBG_IPC) log_debug ("additional connections at '%s'\n", socket_name); } } xfree (databuf); } /* Tell the scdaemon we want him to send us an event signal. We don't support this for W32CE. */ #ifndef HAVE_W32CE_SYSTEM if (opt.sigusr2_enabled) { char buf[100]; #ifdef HAVE_W32_SYSTEM snprintf (buf, sizeof buf, "OPTION event-signal=%p", get_agent_scd_notify_event ()); #else snprintf (buf, sizeof buf, "OPTION event-signal=%d", SIGUSR2); #endif assuan_transact (ctx, buf, NULL, NULL, NULL, NULL, NULL, NULL); } #endif /*HAVE_W32CE_SYSTEM*/ primary_scd_ctx = ctx; primary_scd_ctx_reusable = 0; leave: xfree (abs_homedir); if (err) { unlock_scd (ctrl, err); if (ctx) assuan_release (ctx); } else { ctrl->scd_local->ctx = ctx; } rc = npth_mutex_unlock (&start_scd_lock); if (rc) log_error ("failed to release the start_scd lock: %s\n", strerror (rc)); return err; } /* Check whether the SCdaemon is active. This is a fast check without any locking and might give a wrong result if another thread is about to start the daemon or the daemon is about to be stopped.. */ int agent_scd_check_running (void) { return !!primary_scd_ctx; } /* Check whether the Scdaemon is still alive and clean it up if not. */ void agent_scd_check_aliveness (void) { pid_t pid; #ifdef HAVE_W32_SYSTEM DWORD rc; #else int rc; #endif struct timespec abstime; int err; if (!primary_scd_ctx) return; /* No scdaemon running. */ /* This is not a critical function so we use a short timeout while acquiring the lock. */ npth_clock_gettime (&abstime); abstime.tv_sec += 1; err = npth_mutex_timedlock (&start_scd_lock, &abstime); if (err) { if (err == ETIMEDOUT) { if (opt.verbose > 1) log_info ("failed to acquire the start_scd lock while" " doing an aliveness check: %s\n", strerror (err)); } else log_error ("failed to acquire the start_scd lock while" " doing an aliveness check: %s\n", strerror (err)); return; } if (primary_scd_ctx) { pid = assuan_get_pid (primary_scd_ctx); #ifdef HAVE_W32_SYSTEM /* If we have a PID we disconnect if either GetExitProcessCode fails or if ir returns the exit code of the scdaemon. 259 is the error code for STILL_ALIVE. */ if (pid != (pid_t)(void*)(-1) && pid && (!GetExitCodeProcess ((HANDLE)pid, &rc) || rc != 259)) #else if (pid != (pid_t)(-1) && pid && ((rc=waitpid (pid, NULL, WNOHANG))==-1 || (rc == pid)) ) #endif { /* Okay, scdaemon died. Disconnect the primary connection now but take care that it won't do another wait. Also cleanup all other connections and release their resources. The next use will start a new daemon then. Due to the use of the START_SCD_LOCAL we are sure that none of these context are actually in use. */ struct scd_local_s *sl; assuan_set_flag (primary_scd_ctx, ASSUAN_NO_WAITPID, 1); assuan_release (primary_scd_ctx); for (sl=scd_local_list; sl; sl = sl->next_local) { if (sl->ctx) { if (sl->ctx != primary_scd_ctx) assuan_release (sl->ctx); sl->ctx = NULL; } } primary_scd_ctx = NULL; primary_scd_ctx_reusable = 0; xfree (socket_name); socket_name = NULL; } } err = npth_mutex_unlock (&start_scd_lock); if (err) log_error ("failed to release the start_scd lock while" " doing the aliveness check: %s\n", strerror (err)); } /* Reset the SCD if it has been used. Actually it is not a reset but a cleanup of resources used by the current connection. */ int agent_reset_scd (ctrl_t ctrl) { if (ctrl->scd_local) { if (ctrl->scd_local->ctx) { /* We can't disconnect the primary context because libassuan does a waitpid on it and thus the system would hang. Instead we send a reset and keep that connection for reuse. */ if (ctrl->scd_local->ctx == primary_scd_ctx) { /* Send a RESTART to the SCD. This is required for the primary connection as a kind of virtual EOF; we don't have another way to tell it that the next command should be viewed as if a new connection has been made. For the non-primary connections this is not needed as we simply close the socket. We don't check for an error here because the RESTART may fail for example if the scdaemon has already been terminated. Anyway, we need to set the reusable flag to make sure that the aliveness check can clean it up. */ assuan_transact (primary_scd_ctx, "RESTART", NULL, NULL, NULL, NULL, NULL, NULL); primary_scd_ctx_reusable = 1; } else assuan_release (ctrl->scd_local->ctx); ctrl->scd_local->ctx = NULL; } /* Remove the local context from our list and release it. */ if (!scd_local_list) BUG (); else if (scd_local_list == ctrl->scd_local) scd_local_list = ctrl->scd_local->next_local; else { struct scd_local_s *sl; for (sl=scd_local_list; sl->next_local; sl = sl->next_local) if (sl->next_local == ctrl->scd_local) break; if (!sl->next_local) BUG (); sl->next_local = ctrl->scd_local->next_local; } xfree (ctrl->scd_local); ctrl->scd_local = NULL; } return 0; } static gpg_error_t learn_status_cb (void *opaque, const char *line) { struct learn_parm_s *parm = opaque; const char *keyword = line; int keywordlen; for (keywordlen=0; *line && !spacep (line); line++, keywordlen++) ; while (spacep (line)) line++; if (keywordlen == 8 && !memcmp (keyword, "CERTINFO", keywordlen)) { parm->certinfo_cb (parm->certinfo_cb_arg, line); } else if (keywordlen == 11 && !memcmp (keyword, "KEYPAIRINFO", keywordlen)) { parm->kpinfo_cb (parm->kpinfo_cb_arg, line); } else if (keywordlen && *line) { parm->sinfo_cb (parm->sinfo_cb_arg, keyword, keywordlen, line); } return 0; } /* Perform the LEARN command and return a list of all private keys stored on the card. */ int agent_card_learn (ctrl_t ctrl, void (*kpinfo_cb)(void*, const char *), void *kpinfo_cb_arg, void (*certinfo_cb)(void*, const char *), void *certinfo_cb_arg, void (*sinfo_cb)(void*, const char *, size_t, const char *), void *sinfo_cb_arg) { int rc; struct learn_parm_s parm; rc = start_scd (ctrl); if (rc) return rc; memset (&parm, 0, sizeof parm); parm.kpinfo_cb = kpinfo_cb; parm.kpinfo_cb_arg = kpinfo_cb_arg; parm.certinfo_cb = certinfo_cb; parm.certinfo_cb_arg = certinfo_cb_arg; parm.sinfo_cb = sinfo_cb; parm.sinfo_cb_arg = sinfo_cb_arg; rc = assuan_transact (ctrl->scd_local->ctx, "LEARN --force", NULL, NULL, NULL, NULL, learn_status_cb, &parm); if (rc) return unlock_scd (ctrl, rc); return unlock_scd (ctrl, 0); } static gpg_error_t get_serialno_cb (void *opaque, const char *line) { char **serialno = opaque; const char *keyword = line; const char *s; int keywordlen, n; for (keywordlen=0; *line && !spacep (line); line++, keywordlen++) ; while (spacep (line)) line++; if (keywordlen == 8 && !memcmp (keyword, "SERIALNO", keywordlen)) { if (*serialno) return gpg_error (GPG_ERR_CONFLICT); /* Unexpected status line. */ for (n=0,s=line; hexdigitp (s); s++, n++) ; if (!n || (n&1)|| !(spacep (s) || !*s) ) return gpg_error (GPG_ERR_ASS_PARAMETER); *serialno = xtrymalloc (n+1); if (!*serialno) return out_of_core (); memcpy (*serialno, line, n); (*serialno)[n] = 0; } return 0; } /* Return the serial number of the card or an appropriate error. The serial number is returned as a hexstring. */ int agent_card_serialno (ctrl_t ctrl, char **r_serialno, const char *demand) { int rc; char *serialno = NULL; char line[ASSUAN_LINELENGTH]; rc = start_scd (ctrl); if (rc) return rc; if (!demand) strcpy (line, "SERIALNO"); else snprintf (line, DIM(line), "SERIALNO --demand=%s", demand); rc = assuan_transact (ctrl->scd_local->ctx, line, NULL, NULL, NULL, NULL, get_serialno_cb, &serialno); if (rc) { xfree (serialno); return unlock_scd (ctrl, rc); } *r_serialno = serialno; return unlock_scd (ctrl, 0); } /* Handle the NEEDPIN inquiry. */ static gpg_error_t inq_needpin (void *opaque, const char *line) { struct inq_needpin_parm_s *parm = opaque; const char *s; char *pin; size_t pinlen; int rc; if ((s = has_leading_keyword (line, "NEEDPIN"))) { line = s; pinlen = 90; pin = gcry_malloc_secure (pinlen); if (!pin) return out_of_core (); rc = parm->getpin_cb (parm->getpin_cb_arg, parm->getpin_cb_desc, line, pin, pinlen); if (!rc) - rc = assuan_send_data (parm->ctx, pin, pinlen); + { + assuan_begin_confidential (parm->ctx); + rc = assuan_send_data (parm->ctx, pin, pinlen); + assuan_end_confidential (parm->ctx); + } + wipememory (pin, pinlen); xfree (pin); } else if ((s = has_leading_keyword (line, "POPUPPINPADPROMPT"))) { rc = parm->getpin_cb (parm->getpin_cb_arg, parm->getpin_cb_desc, s, NULL, 1); } else if ((s = has_leading_keyword (line, "DISMISSPINPADPROMPT"))) { rc = parm->getpin_cb (parm->getpin_cb_arg, parm->getpin_cb_desc, "", NULL, 0); } else if (parm->passthru) { unsigned char *value; size_t valuelen; int rest; int needrest = !strncmp (line, "KEYDATA", 8); /* Pass the inquiry up to our caller. We limit the maximum amount to an arbitrary value. As we know that the KEYDATA enquiry is pretty sensitive we disable logging then */ if ((rest = (needrest && !assuan_get_flag (parm->passthru, ASSUAN_CONFIDENTIAL)))) assuan_begin_confidential (parm->passthru); rc = assuan_inquire (parm->passthru, line, &value, &valuelen, 8096); if (rest) assuan_end_confidential (parm->passthru); if (!rc) { if ((rest = (needrest && !assuan_get_flag (parm->ctx, ASSUAN_CONFIDENTIAL)))) assuan_begin_confidential (parm->ctx); rc = assuan_send_data (parm->ctx, value, valuelen); if (rest) assuan_end_confidential (parm->ctx); xfree (value); } else log_error ("error forwarding inquiry '%s': %s\n", line, gpg_strerror (rc)); } else { log_error ("unsupported inquiry '%s'\n", line); rc = gpg_error (GPG_ERR_ASS_UNKNOWN_INQUIRE); } return rc; } /* Helper returning a command option to describe the used hash algorithm. See scd/command.c:cmd_pksign. */ static const char * hash_algo_option (int algo) { switch (algo) { case GCRY_MD_MD5 : return "--hash=md5"; case GCRY_MD_RMD160: return "--hash=rmd160"; case GCRY_MD_SHA1 : return "--hash=sha1"; case GCRY_MD_SHA224: return "--hash=sha224"; case GCRY_MD_SHA256: return "--hash=sha256"; case GCRY_MD_SHA384: return "--hash=sha384"; case GCRY_MD_SHA512: return "--hash=sha512"; default: return ""; } } /* Create a signature using the current card. MDALGO is either 0 or * gives the digest algorithm. DESC_TEXT is an additional parameter * passed to GETPIN_CB. */ int agent_card_pksign (ctrl_t ctrl, const char *keyid, int (*getpin_cb)(void *, const char *, const char *, char*, size_t), void *getpin_cb_arg, const char *desc_text, int mdalgo, const unsigned char *indata, size_t indatalen, unsigned char **r_buf, size_t *r_buflen) { int rc; char line[ASSUAN_LINELENGTH]; membuf_t data; struct inq_needpin_parm_s inqparm; *r_buf = NULL; rc = start_scd (ctrl); if (rc) return rc; if (indatalen*2 + 50 > DIM(line)) return unlock_scd (ctrl, gpg_error (GPG_ERR_GENERAL)); bin2hex (indata, indatalen, stpcpy (line, "SETDATA ")); rc = assuan_transact (ctrl->scd_local->ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (rc) return unlock_scd (ctrl, rc); init_membuf (&data, 1024); inqparm.ctx = ctrl->scd_local->ctx; inqparm.getpin_cb = getpin_cb; inqparm.getpin_cb_arg = getpin_cb_arg; inqparm.getpin_cb_desc = desc_text; inqparm.passthru = 0; inqparm.keydata = NULL; inqparm.keydatalen = 0; if (ctrl->use_auth_call) snprintf (line, sizeof line, "PKAUTH %s", keyid); else snprintf (line, sizeof line, "PKSIGN %s %s", hash_algo_option (mdalgo), keyid); rc = assuan_transact (ctrl->scd_local->ctx, line, put_membuf_cb, &data, inq_needpin, &inqparm, NULL, NULL); if (rc) { size_t len; xfree (get_membuf (&data, &len)); return unlock_scd (ctrl, rc); } *r_buf = get_membuf (&data, r_buflen); return unlock_scd (ctrl, 0); } /* Check whether there is any padding info from scdaemon. */ static gpg_error_t padding_info_cb (void *opaque, const char *line) { int *r_padding = opaque; const char *s; if ((s=has_leading_keyword (line, "PADDING"))) { *r_padding = atoi (s); } return 0; } /* Decipher INDATA using the current card. Note that the returned * value is not an s-expression but the raw data as returned by * scdaemon. The padding information is stored at R_PADDING with -1 * for not known. DESC_TEXT is an additional parameter passed to * GETPIN_CB. */ int agent_card_pkdecrypt (ctrl_t ctrl, const char *keyid, int (*getpin_cb)(void *, const char *, const char *, char*, size_t), void *getpin_cb_arg, const char *desc_text, const unsigned char *indata, size_t indatalen, char **r_buf, size_t *r_buflen, int *r_padding) { int rc, i; char *p, line[ASSUAN_LINELENGTH]; membuf_t data; struct inq_needpin_parm_s inqparm; size_t len; *r_buf = NULL; *r_padding = -1; /* Unknown. */ rc = start_scd (ctrl); if (rc) return rc; /* FIXME: use secure memory where appropriate */ for (len = 0; len < indatalen;) { p = stpcpy (line, "SETDATA "); if (len) p = stpcpy (p, "--append "); for (i=0; len < indatalen && (i*2 < DIM(line)-50); i++, len++) { sprintf (p, "%02X", indata[len]); p += 2; } rc = assuan_transact (ctrl->scd_local->ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (rc) return unlock_scd (ctrl, rc); } init_membuf (&data, 1024); inqparm.ctx = ctrl->scd_local->ctx; inqparm.getpin_cb = getpin_cb; inqparm.getpin_cb_arg = getpin_cb_arg; inqparm.getpin_cb_desc = desc_text; inqparm.passthru = 0; inqparm.keydata = NULL; inqparm.keydatalen = 0; snprintf (line, DIM(line), "PKDECRYPT %s", keyid); rc = assuan_transact (ctrl->scd_local->ctx, line, put_membuf_cb, &data, inq_needpin, &inqparm, padding_info_cb, r_padding); if (rc) { xfree (get_membuf (&data, &len)); return unlock_scd (ctrl, rc); } *r_buf = get_membuf (&data, r_buflen); if (!*r_buf) return unlock_scd (ctrl, gpg_error (GPG_ERR_ENOMEM)); return unlock_scd (ctrl, 0); } /* Read a certificate with ID into R_BUF and R_BUFLEN. */ int agent_card_readcert (ctrl_t ctrl, const char *id, char **r_buf, size_t *r_buflen) { int rc; char line[ASSUAN_LINELENGTH]; membuf_t data; size_t len; *r_buf = NULL; rc = start_scd (ctrl); if (rc) return rc; init_membuf (&data, 1024); snprintf (line, DIM(line), "READCERT %s", id); rc = assuan_transact (ctrl->scd_local->ctx, line, put_membuf_cb, &data, NULL, NULL, NULL, NULL); if (rc) { xfree (get_membuf (&data, &len)); return unlock_scd (ctrl, rc); } *r_buf = get_membuf (&data, r_buflen); if (!*r_buf) return unlock_scd (ctrl, gpg_error (GPG_ERR_ENOMEM)); return unlock_scd (ctrl, 0); } /* Read a key with ID and return it in an allocate buffer pointed to by r_BUF as a valid S-expression. */ int agent_card_readkey (ctrl_t ctrl, const char *id, unsigned char **r_buf) { int rc; char line[ASSUAN_LINELENGTH]; membuf_t data; size_t len, buflen; *r_buf = NULL; rc = start_scd (ctrl); if (rc) return rc; init_membuf (&data, 1024); snprintf (line, DIM(line), "READKEY %s", id); rc = assuan_transact (ctrl->scd_local->ctx, line, put_membuf_cb, &data, NULL, NULL, NULL, NULL); if (rc) { xfree (get_membuf (&data, &len)); return unlock_scd (ctrl, rc); } *r_buf = get_membuf (&data, &buflen); if (!*r_buf) return unlock_scd (ctrl, gpg_error (GPG_ERR_ENOMEM)); if (!gcry_sexp_canon_len (*r_buf, buflen, NULL, NULL)) { xfree (*r_buf); *r_buf = NULL; return unlock_scd (ctrl, gpg_error (GPG_ERR_INV_VALUE)); } return unlock_scd (ctrl, 0); } /* Handle a KEYDATA inquiry. Note, we only send the data, assuan_transact takes care of flushing and writing the end */ static gpg_error_t inq_writekey_parms (void *opaque, const char *line) { struct inq_needpin_parm_s *parm = opaque; if (has_leading_keyword (line, "KEYDATA")) return assuan_send_data (parm->ctx, parm->keydata, parm->keydatalen); else return inq_needpin (opaque, line); } int agent_card_writekey (ctrl_t ctrl, int force, const char *serialno, const char *id, const char *keydata, size_t keydatalen, int (*getpin_cb)(void *, const char *, const char *, char*, size_t), void *getpin_cb_arg) { int rc; char line[ASSUAN_LINELENGTH]; struct inq_needpin_parm_s parms; (void)serialno; rc = start_scd (ctrl); if (rc) return rc; snprintf (line, DIM(line), "WRITEKEY %s%s", force ? "--force " : "", id); parms.ctx = ctrl->scd_local->ctx; parms.getpin_cb = getpin_cb; parms.getpin_cb_arg = getpin_cb_arg; parms.getpin_cb_desc= NULL; parms.passthru = 0; parms.keydata = keydata; parms.keydatalen = keydatalen; rc = assuan_transact (ctrl->scd_local->ctx, line, NULL, NULL, inq_writekey_parms, &parms, NULL, NULL); return unlock_scd (ctrl, rc); } /* Type used with the card_getattr_cb. */ struct card_getattr_parm_s { const char *keyword; /* Keyword to look for. */ size_t keywordlen; /* strlen of KEYWORD. */ char *data; /* Malloced and unescaped data. */ int error; /* ERRNO value or 0 on success. */ }; /* Callback function for agent_card_getattr. */ static gpg_error_t card_getattr_cb (void *opaque, const char *line) { struct card_getattr_parm_s *parm = opaque; const char *keyword = line; int keywordlen; if (parm->data) return 0; /* We want only the first occurrence. */ for (keywordlen=0; *line && !spacep (line); line++, keywordlen++) ; while (spacep (line)) line++; if (keywordlen == parm->keywordlen && !memcmp (keyword, parm->keyword, keywordlen)) { parm->data = percent_plus_unescape ((const unsigned char*)line, 0xff); if (!parm->data) parm->error = errno; } return 0; } /* Call the agent to retrieve a single line data object. On success the object is malloced and stored at RESULT; it is guaranteed that NULL is never stored in this case. On error an error code is returned and NULL stored at RESULT. */ gpg_error_t agent_card_getattr (ctrl_t ctrl, const char *name, char **result) { int err; struct card_getattr_parm_s parm; char line[ASSUAN_LINELENGTH]; *result = NULL; if (!*name) return gpg_error (GPG_ERR_INV_VALUE); memset (&parm, 0, sizeof parm); parm.keyword = name; parm.keywordlen = strlen (name); /* We assume that NAME does not need escaping. */ if (8 + strlen (name) > DIM(line)-1) return gpg_error (GPG_ERR_TOO_LARGE); stpcpy (stpcpy (line, "GETATTR "), name); err = start_scd (ctrl); if (err) return err; err = assuan_transact (ctrl->scd_local->ctx, line, NULL, NULL, NULL, NULL, card_getattr_cb, &parm); if (!err && parm.error) err = gpg_error_from_errno (parm.error); if (!err && !parm.data) err = gpg_error (GPG_ERR_NO_DATA); if (!err) *result = parm.data; else xfree (parm.data); return unlock_scd (ctrl, err); } struct card_cardlist_parm_s { int error; strlist_t list; }; /* Callback function for agent_card_cardlist. */ static gpg_error_t card_cardlist_cb (void *opaque, const char *line) { struct card_cardlist_parm_s *parm = opaque; const char *keyword = line; int keywordlen; for (keywordlen=0; *line && !spacep (line); line++, keywordlen++) ; while (spacep (line)) line++; if (keywordlen == 8 && !memcmp (keyword, "SERIALNO", keywordlen)) { const char *s; int n; for (n=0,s=line; hexdigitp (s); s++, n++) ; if (!n || (n&1) || *s) parm->error = gpg_error (GPG_ERR_ASS_PARAMETER); else add_to_strlist (&parm->list, line); } return 0; } /* Call the scdaemon to retrieve list of available cards. On success the allocated strlist is stored at RESULT. On error an error code is returned and NULL stored at RESULT. */ gpg_error_t agent_card_cardlist (ctrl_t ctrl, strlist_t *result) { int err; struct card_cardlist_parm_s parm; char line[ASSUAN_LINELENGTH]; *result = NULL; memset (&parm, 0, sizeof parm); strcpy (line, "GETINFO card_list"); err = start_scd (ctrl); if (err) return err; err = assuan_transact (ctrl->scd_local->ctx, line, NULL, NULL, NULL, NULL, card_cardlist_cb, &parm); if (!err && parm.error) err = parm.error; if (!err) *result = parm.list; else free_strlist (parm.list); return unlock_scd (ctrl, err); } static gpg_error_t pass_status_thru (void *opaque, const char *line) { assuan_context_t ctx = opaque; char keyword[200]; int i; if (line[0] == '#' && (!line[1] || spacep (line+1))) { /* We are called in convey comments mode. Now, if we see a comment marker as keyword we forward the line verbatim to the the caller. This way the comment lines from scdaemon won't appear as status lines with keyword '#'. */ assuan_write_line (ctx, line); } else { for (i=0; *line && !spacep (line) && i < DIM(keyword)-1; line++, i++) keyword[i] = *line; keyword[i] = 0; /* Truncate any remaining keyword stuff. */ for (; *line && !spacep (line); line++) ; while (spacep (line)) line++; assuan_write_status (ctx, keyword, line); } return 0; } static gpg_error_t pass_data_thru (void *opaque, const void *buffer, size_t length) { assuan_context_t ctx = opaque; assuan_send_data (ctx, buffer, length); return 0; } /* Send the line CMDLINE with command for the SCDdaemon to it and send all status messages back. This command is used as a general quoting mechanism to pass everything verbatim to SCDAEMON. The PIN inquiry is handled inside gpg-agent. */ int agent_card_scd (ctrl_t ctrl, const char *cmdline, int (*getpin_cb)(void *, const char *, const char *, char*, size_t), void *getpin_cb_arg, void *assuan_context) { int rc; struct inq_needpin_parm_s inqparm; int saveflag; rc = start_scd (ctrl); if (rc) return rc; inqparm.ctx = ctrl->scd_local->ctx; inqparm.getpin_cb = getpin_cb; inqparm.getpin_cb_arg = getpin_cb_arg; inqparm.getpin_cb_desc = NULL; inqparm.passthru = assuan_context; inqparm.keydata = NULL; inqparm.keydatalen = 0; saveflag = assuan_get_flag (ctrl->scd_local->ctx, ASSUAN_CONVEY_COMMENTS); assuan_set_flag (ctrl->scd_local->ctx, ASSUAN_CONVEY_COMMENTS, 1); rc = assuan_transact (ctrl->scd_local->ctx, cmdline, pass_data_thru, assuan_context, inq_needpin, &inqparm, pass_status_thru, assuan_context); assuan_set_flag (ctrl->scd_local->ctx, ASSUAN_CONVEY_COMMENTS, saveflag); if (rc) { return unlock_scd (ctrl, rc); } return unlock_scd (ctrl, 0); } void agent_card_killscd (void) { if (primary_scd_ctx == NULL) return; assuan_transact (primary_scd_ctx, "KILLSCD", NULL, NULL, NULL, NULL, NULL, NULL); } diff --git a/agent/command.c b/agent/command.c index e22e2fa0b..5da5dbcc0 100644 --- a/agent/command.c +++ b/agent/command.c @@ -1,3628 +1,3635 @@ /* command.c - gpg-agent command handler * Copyright (C) 2001-2011 Free Software Foundation, Inc. * Copyright (C) 2001-2013 Werner Koch * Copyright (C) 2015 g10 Code GmbH. * * 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 3 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, see . */ /* FIXME: we should not use the default assuan buffering but setup some buffering in secure mempory to protect session keys etc. */ #include #include #include #include #include #include #include #include #include #include #include #include "agent.h" #include #include "../common/i18n.h" #include "cvt-openpgp.h" #include "../common/ssh-utils.h" #include "../common/asshelp.h" #include "../common/server-help.h" /* Maximum allowed size of the inquired ciphertext. */ #define MAXLEN_CIPHERTEXT 4096 /* Maximum allowed size of the key parameters. */ #define MAXLEN_KEYPARAM 1024 /* Maximum allowed size of key data as used in inquiries (bytes). */ #define MAXLEN_KEYDATA 8192 /* The size of the import/export KEK key (in bytes). */ #define KEYWRAP_KEYSIZE (128/8) /* A shortcut to call assuan_set_error using an gpg_err_code_t and a text string. */ #define set_error(e,t) assuan_set_error (ctx, gpg_error (e), (t)) /* Check that the maximum digest length we support has at least the length of the keygrip. */ #if MAX_DIGEST_LEN < 20 #error MAX_DIGEST_LEN shorter than keygrip #endif /* Data used to associate an Assuan context with local server data. This is this modules local part of the server_control_s struct. */ struct server_local_s { /* Our Assuan context. */ assuan_context_t assuan_ctx; /* If this flag is true, the passphrase cache is used for signing operations. It defaults to true but may be set on a per connection base. The global option opt.ignore_cache_for_signing takes precedence over this flag. */ unsigned int use_cache_for_signing : 1; /* Flag to suppress I/O logging during a command. */ unsigned int pause_io_logging : 1; /* Flag indicating that the connection is from ourselves. */ unsigned int connect_from_self : 1; /* Helper flag for io_monitor to allow suppressing of our own * greeting in some cases. See io_monitor for details. */ unsigned int greeting_seen : 1; /* If this flag is set to true the agent will be terminated after the end of the current session. */ unsigned int stopme : 1; /* Flag indicating whether pinentry notifications shall be done. */ unsigned int allow_pinentry_notify : 1; /* An allocated description for the next key operation. This is used if a pinnetry needs to be popped up. */ char *keydesc; /* Malloced KEK (Key-Encryption-Key) for the import_key command. */ void *import_key; /* Malloced KEK for the export_key command. */ void *export_key; /* Client is aware of the error code GPG_ERR_FULLY_CANCELED. */ int allow_fully_canceled; /* Last CACHE_NONCE sent as status (malloced). */ char *last_cache_nonce; /* Last PASSWD_NONCE sent as status (malloced). */ char *last_passwd_nonce; }; /* An entry for the getval/putval commands. */ struct putval_item_s { struct putval_item_s *next; size_t off; /* Offset to the value into DATA. */ size_t len; /* Length of the value. */ char d[1]; /* Key | Nul | value. */ }; /* A list of key value pairs fpr the getval/putval commands. */ static struct putval_item_s *putval_list; /* To help polling clients, we keep track of the number of certain events. This structure keeps those counters. The counters are integers and there should be no problem if they are overflowing as callers need to check only whether a counter changed. The actual values are not meaningful. */ struct { /* Incremented if any of the other counters below changed. */ unsigned int any; /* Incremented if a key is added or removed from the internal privat key database. */ unsigned int key; /* Incremented if a change of the card readers stati has been detected. */ unsigned int card; } eventcounter; /* Local prototypes. */ static int command_has_option (const char *cmd, const char *cmdopt); /* Release the memory buffer MB but first wipe out the used memory. */ static void clear_outbuf (membuf_t *mb) { void *p; size_t n; p = get_membuf (mb, &n); if (p) { wipememory (p, n); xfree (p); } } /* Write the content of memory buffer MB as assuan data to CTX and wipe the buffer out afterwards. */ static gpg_error_t write_and_clear_outbuf (assuan_context_t ctx, membuf_t *mb) { gpg_error_t ae; void *p; size_t n; p = get_membuf (mb, &n); if (!p) return out_of_core (); ae = assuan_send_data (ctx, p, n); memset (p, 0, n); xfree (p); return ae; } /* Clear the nonces used to enable the passphrase cache for certain multi-command command sequences. */ static void clear_nonce_cache (ctrl_t ctrl) { if (ctrl->server_local->last_cache_nonce) { agent_put_cache (ctrl, ctrl->server_local->last_cache_nonce, CACHE_MODE_NONCE, NULL, 0); xfree (ctrl->server_local->last_cache_nonce); ctrl->server_local->last_cache_nonce = NULL; } if (ctrl->server_local->last_passwd_nonce) { agent_put_cache (ctrl, ctrl->server_local->last_passwd_nonce, CACHE_MODE_NONCE, NULL, 0); xfree (ctrl->server_local->last_passwd_nonce); ctrl->server_local->last_passwd_nonce = NULL; } } /* This function is called by Libassuan whenever the client sends a reset. It has been registered similar to the other Assuan commands. */ static gpg_error_t reset_notify (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); (void) line; memset (ctrl->keygrip, 0, 20); ctrl->have_keygrip = 0; ctrl->digest.valuelen = 0; xfree (ctrl->server_local->keydesc); ctrl->server_local->keydesc = NULL; clear_nonce_cache (ctrl); return 0; } /* Replace all '+' by a blank in the string S. */ static void plus_to_blank (char *s) { for (; *s; s++) { if (*s == '+') *s = ' '; } } /* Parse a hex string. Return an Assuan error code or 0 on success and the length of the parsed string in LEN. */ static int parse_hexstring (assuan_context_t ctx, const char *string, size_t *len) { const char *p; size_t n; /* parse the hash value */ for (p=string, n=0; hexdigitp (p); p++, n++) ; if (*p != ' ' && *p != '\t' && *p) return set_error (GPG_ERR_ASS_PARAMETER, "invalid hexstring"); if ((n&1)) return set_error (GPG_ERR_ASS_PARAMETER, "odd number of digits"); *len = n; return 0; } /* Parse the keygrip in STRING into the provided buffer BUF. BUF must provide space for 20 bytes. BUF is not changed if the function returns an error. */ static int parse_keygrip (assuan_context_t ctx, const char *string, unsigned char *buf) { int rc; size_t n = 0; rc = parse_hexstring (ctx, string, &n); if (rc) return rc; n /= 2; if (n != 20) return set_error (GPG_ERR_ASS_PARAMETER, "invalid length of keygrip"); if (hex2bin (string, buf, 20) < 0) return set_error (GPG_ERR_BUG, "hex2bin"); return 0; } /* Write an Assuan status line. KEYWORD is the first item on the * status line. The following arguments are all separated by a space * in the output. The last argument must be a NULL. Linefeeds and * carriage returns characters (which are not allowed in an Assuan * status line) are silently quoted in C-style. */ gpg_error_t agent_write_status (ctrl_t ctrl, const char *keyword, ...) { gpg_error_t err; va_list arg_ptr; assuan_context_t ctx = ctrl->server_local->assuan_ctx; va_start (arg_ptr, keyword); err = vprint_assuan_status_strings (ctx, keyword, arg_ptr); va_end (arg_ptr); return err; } /* This function is similar to print_assuan_status but takes a CTRL arg instead of an assuan context as first argument. */ gpg_error_t agent_print_status (ctrl_t ctrl, const char *keyword, const char *format, ...) { gpg_error_t err; va_list arg_ptr; assuan_context_t ctx = ctrl->server_local->assuan_ctx; va_start (arg_ptr, format); err = vprint_assuan_status (ctx, keyword, format, arg_ptr); va_end (arg_ptr); return err; } /* Helper to notify the client about a launched Pinentry. Because that might disturb some older clients, this is only done if enabled via an option. Returns an gpg error code. */ gpg_error_t agent_inq_pinentry_launched (ctrl_t ctrl, unsigned long pid, const char *extra) { char line[256]; if (!ctrl || !ctrl->server_local || !ctrl->server_local->allow_pinentry_notify) return 0; snprintf (line, DIM(line), "PINENTRY_LAUNCHED %lu%s%s", pid, extra?" ":"", extra? extra:""); return assuan_inquire (ctrl->server_local->assuan_ctx, line, NULL, NULL, 0); } /* An agent progress callback for Libgcrypt. This has been registered * to be called via the progress dispatcher mechanism from * gpg-agent.c */ static void progress_cb (ctrl_t ctrl, const char *what, int printchar, int current, int total) { if (!ctrl || !ctrl->server_local || !ctrl->server_local->assuan_ctx) ; else if (printchar == '\n' && what && !strcmp (what, "primegen")) agent_print_status (ctrl, "PROGRESS", "%.20s X 100 100", what); else agent_print_status (ctrl, "PROGRESS", "%.20s %c %d %d", what, printchar=='\n'?'X':printchar, current, total); } /* Helper to print a message while leaving a command. Note that this * function does not call assuan_set_error; the caller may do this * prior to calling us. */ static gpg_error_t leave_cmd (assuan_context_t ctx, gpg_error_t err) { if (err) { const char *name = assuan_get_command_name (ctx); if (!name) name = "?"; /* Not all users of gpg-agent know about the fully canceled error code; map it back if needed. */ if (gpg_err_code (err) == GPG_ERR_FULLY_CANCELED) { ctrl_t ctrl = assuan_get_pointer (ctx); if (!ctrl->server_local->allow_fully_canceled) err = gpg_err_make (gpg_err_source (err), GPG_ERR_CANCELED); } /* Most code from common/ does not know the error source, thus we fix this here. */ if (gpg_err_source (err) == GPG_ERR_SOURCE_UNKNOWN) err = gpg_err_make (GPG_ERR_SOURCE_DEFAULT, gpg_err_code (err)); if (gpg_err_source (err) == GPG_ERR_SOURCE_DEFAULT) log_error ("command '%s' failed: %s\n", name, gpg_strerror (err)); else log_error ("command '%s' failed: %s <%s>\n", name, gpg_strerror (err), gpg_strsource (err)); } return err; } static const char hlp_geteventcounter[] = "GETEVENTCOUNTER\n" "\n" "Return a status line named EVENTCOUNTER with the current values\n" "of all event counters. The values are decimal numbers in the range\n" "0 to UINT_MAX and wrapping around to 0. The actual values should\n" "not be relied upon, they shall only be used to detect a change.\n" "\n" "The currently defined counters are:\n" "\n" "ANY - Incremented with any change of any of the other counters.\n" "KEY - Incremented for added or removed private keys.\n" "CARD - Incremented for changes of the card readers stati."; static gpg_error_t cmd_geteventcounter (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); (void)line; if (ctrl->restricted) return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); return agent_print_status (ctrl, "EVENTCOUNTER", "%u %u %u", eventcounter.any, eventcounter.key, eventcounter.card); } /* This function should be called once for all key removals or additions. This function is assured not to do any context switches. */ void bump_key_eventcounter (void) { eventcounter.key++; eventcounter.any++; } /* This function should be called for all card reader status changes. This function is assured not to do any context switches. */ void bump_card_eventcounter (void) { eventcounter.card++; eventcounter.any++; } static const char hlp_istrusted[] = "ISTRUSTED \n" "\n" "Return OK when we have an entry with this fingerprint in our\n" "trustlist"; static gpg_error_t cmd_istrusted (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc, n, i; char *p; char fpr[41]; /* Parse the fingerprint value. */ for (p=line,n=0; hexdigitp (p); p++, n++) ; if (*p || !(n == 40 || n == 32)) return set_error (GPG_ERR_ASS_PARAMETER, "invalid fingerprint"); i = 0; if (n==32) { strcpy (fpr, "00000000"); i += 8; } for (p=line; i < 40; p++, i++) fpr[i] = *p >= 'a'? (*p & 0xdf): *p; fpr[i] = 0; rc = agent_istrusted (ctrl, fpr, NULL); if (!rc || gpg_err_code (rc) == GPG_ERR_NOT_TRUSTED) return rc; else if (rc == -1 || gpg_err_code (rc) == GPG_ERR_EOF ) return gpg_error (GPG_ERR_NOT_TRUSTED); else return leave_cmd (ctx, rc); } static const char hlp_listtrusted[] = "LISTTRUSTED\n" "\n" "List all entries from the trustlist."; static gpg_error_t cmd_listtrusted (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc; (void)line; if (ctrl->restricted) return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); rc = agent_listtrusted (ctx); return leave_cmd (ctx, rc); } static const char hlp_martrusted[] = "MARKTRUSTED \n" "\n" "Store a new key in into the trustlist."; static gpg_error_t cmd_marktrusted (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc, n, i; char *p; char fpr[41]; int flag; if (ctrl->restricted) return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); /* parse the fingerprint value */ for (p=line,n=0; hexdigitp (p); p++, n++) ; if (!spacep (p) || !(n == 40 || n == 32)) return set_error (GPG_ERR_ASS_PARAMETER, "invalid fingerprint"); i = 0; if (n==32) { strcpy (fpr, "00000000"); i += 8; } for (p=line; i < 40; p++, i++) fpr[i] = *p >= 'a'? (*p & 0xdf): *p; fpr[i] = 0; while (spacep (p)) p++; flag = *p++; if ( (flag != 'S' && flag != 'P') || !spacep (p) ) return set_error (GPG_ERR_ASS_PARAMETER, "invalid flag - must be P or S"); while (spacep (p)) p++; rc = agent_marktrusted (ctrl, p, fpr, flag); return leave_cmd (ctx, rc); } static const char hlp_havekey[] = "HAVEKEY \n" "\n" "Return success if at least one of the secret keys with the given\n" "keygrips is available."; static gpg_error_t cmd_havekey (assuan_context_t ctx, char *line) { gpg_error_t err; unsigned char buf[20]; do { err = parse_keygrip (ctx, line, buf); if (err) return err; if (!agent_key_available (buf)) return 0; /* Found. */ while (*line && *line != ' ' && *line != '\t') line++; while (*line == ' ' || *line == '\t') line++; } while (*line); /* No leave_cmd() here because errors are expected and would clutter the log. */ return gpg_error (GPG_ERR_NO_SECKEY); } static const char hlp_sigkey[] = "SIGKEY \n" "SETKEY \n" "\n" "Set the key used for a sign or decrypt operation."; static gpg_error_t cmd_sigkey (assuan_context_t ctx, char *line) { int rc; ctrl_t ctrl = assuan_get_pointer (ctx); rc = parse_keygrip (ctx, line, ctrl->keygrip); if (rc) return rc; ctrl->have_keygrip = 1; return 0; } static const char hlp_setkeydesc[] = "SETKEYDESC plus_percent_escaped_string\n" "\n" "Set a description to be used for the next PKSIGN, PKDECRYPT, IMPORT_KEY\n" "or EXPORT_KEY operation if this operation requires a passphrase. If\n" "this command is not used a default text will be used. Note, that\n" "this description implictly selects the label used for the entry\n" "box; if the string contains the string PIN (which in general will\n" "not be translated), \"PIN\" is used, otherwise the translation of\n" "\"passphrase\" is used. The description string should not contain\n" "blanks unless they are percent or '+' escaped.\n" "\n" "The description is only valid for the next PKSIGN, PKDECRYPT,\n" "IMPORT_KEY, EXPORT_KEY, or DELETE_KEY operation."; static gpg_error_t cmd_setkeydesc (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); char *desc, *p; for (p=line; *p == ' '; p++) ; desc = p; p = strchr (desc, ' '); if (p) *p = 0; /* We ignore any garbage; we might late use it for other args. */ if (!*desc) return set_error (GPG_ERR_ASS_PARAMETER, "no description given"); /* Note, that we only need to replace the + characters and should leave the other escaping in place because the escaped string is send verbatim to the pinentry which does the unescaping (but not the + replacing) */ plus_to_blank (desc); xfree (ctrl->server_local->keydesc); if (ctrl->restricted) { ctrl->server_local->keydesc = strconcat ((ctrl->restricted == 2 ? _("Note: Request from the web browser.") : _("Note: Request from a remote site.") ), "%0A%0A", desc, NULL); } else ctrl->server_local->keydesc = xtrystrdup (desc); if (!ctrl->server_local->keydesc) return out_of_core (); return 0; } static const char hlp_sethash[] = "SETHASH (--hash=)|() \n" "\n" "The client can use this command to tell the server about the data\n" "(which usually is a hash) to be signed."; static gpg_error_t cmd_sethash (assuan_context_t ctx, char *line) { int rc; size_t n; char *p; ctrl_t ctrl = assuan_get_pointer (ctx); unsigned char *buf; char *endp; int algo; /* Parse the alternative hash options which may be used instead of the algo number. */ if (has_option_name (line, "--hash")) { if (has_option (line, "--hash=sha1")) algo = GCRY_MD_SHA1; else if (has_option (line, "--hash=sha224")) algo = GCRY_MD_SHA224; else if (has_option (line, "--hash=sha256")) algo = GCRY_MD_SHA256; else if (has_option (line, "--hash=sha384")) algo = GCRY_MD_SHA384; else if (has_option (line, "--hash=sha512")) algo = GCRY_MD_SHA512; else if (has_option (line, "--hash=rmd160")) algo = GCRY_MD_RMD160; else if (has_option (line, "--hash=md5")) algo = GCRY_MD_MD5; else if (has_option (line, "--hash=tls-md5sha1")) algo = MD_USER_TLS_MD5SHA1; else return set_error (GPG_ERR_ASS_PARAMETER, "invalid hash algorithm"); } else algo = 0; line = skip_options (line); if (!algo) { /* No hash option has been given: require an algo number instead */ algo = (int)strtoul (line, &endp, 10); for (line = endp; *line == ' ' || *line == '\t'; line++) ; if (!algo || gcry_md_test_algo (algo)) return set_error (GPG_ERR_UNSUPPORTED_ALGORITHM, NULL); } ctrl->digest.algo = algo; ctrl->digest.raw_value = 0; /* Parse the hash value. */ n = 0; rc = parse_hexstring (ctx, line, &n); if (rc) return rc; n /= 2; if (algo == MD_USER_TLS_MD5SHA1 && n == 36) ; else if (n != 16 && n != 20 && n != 24 && n != 28 && n != 32 && n != 48 && n != 64) return set_error (GPG_ERR_ASS_PARAMETER, "unsupported length of hash"); if (n > MAX_DIGEST_LEN) return set_error (GPG_ERR_ASS_PARAMETER, "hash value to long"); buf = ctrl->digest.value; ctrl->digest.valuelen = n; for (p=line, n=0; n < ctrl->digest.valuelen; p += 2, n++) buf[n] = xtoi_2 (p); for (; n < ctrl->digest.valuelen; n++) buf[n] = 0; return 0; } static const char hlp_pksign[] = "PKSIGN [] []\n" "\n" "Perform the actual sign operation. Neither input nor output are\n" "sensitive to eavesdropping."; static gpg_error_t cmd_pksign (assuan_context_t ctx, char *line) { gpg_error_t err; cache_mode_t cache_mode = CACHE_MODE_NORMAL; ctrl_t ctrl = assuan_get_pointer (ctx); membuf_t outbuf; char *cache_nonce = NULL; char *p; line = skip_options (line); for (p=line; *p && *p != ' ' && *p != '\t'; p++) ; *p = '\0'; if (*line) cache_nonce = xtrystrdup (line); if (opt.ignore_cache_for_signing) cache_mode = CACHE_MODE_IGNORE; else if (!ctrl->server_local->use_cache_for_signing) cache_mode = CACHE_MODE_IGNORE; init_membuf (&outbuf, 512); err = agent_pksign (ctrl, cache_nonce, ctrl->server_local->keydesc, &outbuf, cache_mode); if (err) clear_outbuf (&outbuf); else err = write_and_clear_outbuf (ctx, &outbuf); xfree (cache_nonce); xfree (ctrl->server_local->keydesc); ctrl->server_local->keydesc = NULL; return leave_cmd (ctx, err); } static const char hlp_pkdecrypt[] = "PKDECRYPT []\n" "\n" "Perform the actual decrypt operation. Input is not\n" "sensitive to eavesdropping."; static gpg_error_t cmd_pkdecrypt (assuan_context_t ctx, char *line) { int rc; ctrl_t ctrl = assuan_get_pointer (ctx); unsigned char *value; size_t valuelen; membuf_t outbuf; int padding; (void)line; /* First inquire the data to decrypt */ rc = print_assuan_status (ctx, "INQUIRE_MAXLEN", "%u", MAXLEN_CIPHERTEXT); if (!rc) rc = assuan_inquire (ctx, "CIPHERTEXT", &value, &valuelen, MAXLEN_CIPHERTEXT); if (rc) return rc; init_membuf (&outbuf, 512); rc = agent_pkdecrypt (ctrl, ctrl->server_local->keydesc, value, valuelen, &outbuf, &padding); xfree (value); if (rc) clear_outbuf (&outbuf); else { if (padding != -1) rc = print_assuan_status (ctx, "PADDING", "%d", padding); else rc = 0; if (!rc) rc = write_and_clear_outbuf (ctx, &outbuf); } xfree (ctrl->server_local->keydesc); ctrl->server_local->keydesc = NULL; return leave_cmd (ctx, rc); } static const char hlp_genkey[] = "GENKEY [--no-protection] [--preset] [--timestamp=]\n" " [--inq-passwd] [--passwd-nonce=] []\n" "\n" "Generate a new key, store the secret part and return the public\n" "part. Here is an example transaction:\n" "\n" " C: GENKEY\n" " S: INQUIRE KEYPARAM\n" " C: D (genkey (rsa (nbits 2048)))\n" " C: END\n" " S: D (public-key\n" " S: D (rsa (n 326487324683264) (e 10001)))\n" " S: OK key created\n" "\n" "If the --preset option is used the passphrase for the generated\n" "key will be added to the cache. If --inq-passwd is used an inquire\n" "with the keyword NEWPASSWD is used to request the passphrase for the\n" "new key. If a --passwd-nonce is used, the corresponding cached\n" "passphrase is used to protect the new key. If --timestamp is given\n" "its value is recorded as the key's creation time; the value is\n" "expected in ISO format (e.g. \"20030316T120000\")."; static gpg_error_t cmd_genkey (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc; int no_protection; unsigned char *value = NULL; size_t valuelen; unsigned char *newpasswd = NULL; membuf_t outbuf; char *cache_nonce = NULL; char *passwd_nonce = NULL; int opt_preset; int opt_inq_passwd; size_t n; char *p, *pend; const char *s; time_t opt_timestamp; int c; if (ctrl->restricted) return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); no_protection = has_option (line, "--no-protection"); opt_preset = has_option (line, "--preset"); opt_inq_passwd = has_option (line, "--inq-passwd"); passwd_nonce = option_value (line, "--passwd-nonce"); if (passwd_nonce) { for (pend = passwd_nonce; *pend && !spacep (pend); pend++) ; c = *pend; *pend = '\0'; passwd_nonce = xtrystrdup (passwd_nonce); *pend = c; if (!passwd_nonce) { rc = gpg_error_from_syserror (); goto leave; } } if ((s=has_option_name (line, "--timestamp"))) { if (*s != '=') { rc = set_error (GPG_ERR_ASS_PARAMETER, "missing value for option"); goto leave; } opt_timestamp = isotime2epoch (s+1); if (opt_timestamp < 1) { rc = set_error (GPG_ERR_ASS_PARAMETER, "invalid time value"); goto leave; } } else opt_timestamp = 0; line = skip_options (line); for (p=line; *p && *p != ' ' && *p != '\t'; p++) ; *p = '\0'; if (*line) cache_nonce = xtrystrdup (line); /* First inquire the parameters */ rc = print_assuan_status (ctx, "INQUIRE_MAXLEN", "%u", MAXLEN_KEYPARAM); if (!rc) rc = assuan_inquire (ctx, "KEYPARAM", &value, &valuelen, MAXLEN_KEYPARAM); if (rc) return rc; init_membuf (&outbuf, 512); /* If requested, ask for the password to be used for the key. If this is not used the regular Pinentry mechanism is used. */ if (opt_inq_passwd && !no_protection) { /* (N is used as a dummy) */ assuan_begin_confidential (ctx); rc = assuan_inquire (ctx, "NEWPASSWD", &newpasswd, &n, 256); assuan_end_confidential (ctx); if (rc) goto leave; if (!*newpasswd) { /* Empty password given - switch to no-protection mode. */ xfree (newpasswd); newpasswd = NULL; no_protection = 1; } } else if (passwd_nonce) newpasswd = agent_get_cache (ctrl, passwd_nonce, CACHE_MODE_NONCE); rc = agent_genkey (ctrl, cache_nonce, opt_timestamp, (char*)value, valuelen, no_protection, newpasswd, opt_preset, &outbuf); leave: if (newpasswd) { /* Assuan_inquire does not allow us to read into secure memory thus we need to wipe it ourself. */ wipememory (newpasswd, strlen (newpasswd)); xfree (newpasswd); } xfree (value); if (rc) clear_outbuf (&outbuf); else rc = write_and_clear_outbuf (ctx, &outbuf); xfree (cache_nonce); xfree (passwd_nonce); return leave_cmd (ctx, rc); } static const char hlp_readkey[] = "READKEY [--no-data] \n" " --card \n" "\n" "Return the public key for the given keygrip or keyid.\n" "With --card, private key file with card information will be created."; static gpg_error_t cmd_readkey (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc; unsigned char grip[20]; gcry_sexp_t s_pkey = NULL; unsigned char *pkbuf = NULL; char *serialno = NULL; char *keyidbuf = NULL; size_t pkbuflen; int opt_card, opt_no_data; if (ctrl->restricted) return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); opt_no_data = has_option (line, "--no-data"); opt_card = has_option (line, "--card"); line = skip_options (line); if (opt_card) { const char *keyid = line; rc = agent_card_getattr (ctrl, "SERIALNO", &serialno); if (rc) { log_error (_("error getting serial number of card: %s\n"), gpg_strerror (rc)); goto leave; } /* Hack to create the shadow key for the OpenPGP standard keys. */ if ((!strcmp (keyid, "$SIGNKEYID") || !strcmp (keyid, "$ENCRKEYID")) && !agent_card_getattr (ctrl, keyid, &keyidbuf)) keyid = keyidbuf; rc = agent_card_readkey (ctrl, keyid, &pkbuf); if (rc) goto leave; pkbuflen = gcry_sexp_canon_len (pkbuf, 0, NULL, NULL); rc = gcry_sexp_sscan (&s_pkey, NULL, (char*)pkbuf, pkbuflen); if (rc) goto leave; if (!gcry_pk_get_keygrip (s_pkey, grip)) { rc = gcry_pk_testkey (s_pkey); if (rc == 0) rc = gpg_error (GPG_ERR_INTERNAL); goto leave; } if (agent_key_available (grip)) { /* (Shadow)-key is not available in our key storage. */ rc = agent_write_shadow_key (grip, serialno, keyid, pkbuf, 0); if (rc) goto leave; } rc = opt_no_data? 0 : assuan_send_data (ctx, pkbuf, pkbuflen); } else { rc = parse_keygrip (ctx, line, grip); if (rc) goto leave; rc = agent_public_key_from_file (ctrl, grip, &s_pkey); if (!rc) { pkbuflen = gcry_sexp_sprint (s_pkey, GCRYSEXP_FMT_CANON, NULL, 0); log_assert (pkbuflen); pkbuf = xtrymalloc (pkbuflen); if (!pkbuf) rc = gpg_error_from_syserror (); else { pkbuflen = gcry_sexp_sprint (s_pkey, GCRYSEXP_FMT_CANON, pkbuf, pkbuflen); rc = opt_no_data? 0 : assuan_send_data (ctx, pkbuf, pkbuflen); } } } leave: xfree (keyidbuf); xfree (serialno); xfree (pkbuf); gcry_sexp_release (s_pkey); return leave_cmd (ctx, rc); } static const char hlp_keyinfo[] = "KEYINFO [--[ssh-]list] [--data] [--ssh-fpr[=algo]] [--with-ssh] \n" "\n" "Return information about the key specified by the KEYGRIP. If the\n" "key is not available GPG_ERR_NOT_FOUND is returned. If the option\n" "--list is given the keygrip is ignored and information about all\n" "available keys are returned. If --ssh-list is given information\n" "about all keys listed in the sshcontrol are returned. With --with-ssh\n" "information from sshcontrol is always added to the info. Unless --data\n" "is given, the information is returned as a status line using the format:\n" "\n" " KEYINFO \n" "\n" "KEYGRIP is the keygrip.\n" "\n" "TYPE is describes the type of the key:\n" " 'D' - Regular key stored on disk,\n" " 'T' - Key is stored on a smartcard (token),\n" " 'X' - Unknown type,\n" " '-' - Key is missing.\n" "\n" "SERIALNO is an ASCII string with the serial number of the\n" " smartcard. If the serial number is not known a single\n" " dash '-' is used instead.\n" "\n" "IDSTR is the IDSTR used to distinguish keys on a smartcard. If it\n" " is not known a dash is used instead.\n" "\n" "CACHED is 1 if the passphrase for the key was found in the key cache.\n" " If not, a '-' is used instead.\n" "\n" "PROTECTION describes the key protection type:\n" " 'P' - The key is protected with a passphrase,\n" " 'C' - The key is not protected,\n" " '-' - Unknown protection.\n" "\n" "FPR returns the formatted ssh-style fingerprint of the key. It is only\n" " printed if the option --ssh-fpr has been used. If ALGO is not given\n" " to that option the default ssh fingerprint algo is used. Without the\n" " option a '-' is printed.\n" "\n" "TTL is the TTL in seconds for that key or '-' if n/a.\n" "\n" "FLAGS is a word consisting of one-letter flags:\n" " 'D' - The key has been disabled,\n" " 'S' - The key is listed in sshcontrol (requires --with-ssh),\n" " 'c' - Use of the key needs to be confirmed,\n" " '-' - No flags given.\n" "\n" "More information may be added in the future."; static gpg_error_t do_one_keyinfo (ctrl_t ctrl, const unsigned char *grip, assuan_context_t ctx, int data, int with_ssh_fpr, int in_ssh, int ttl, int disabled, int confirm) { gpg_error_t err; char hexgrip[40+1]; char *fpr = NULL; int keytype; unsigned char *shadow_info = NULL; char *serialno = NULL; char *idstr = NULL; const char *keytypestr; const char *cached; const char *protectionstr; char *pw; int missing_key = 0; char ttlbuf[20]; char flagsbuf[5]; err = agent_key_info_from_file (ctrl, grip, &keytype, &shadow_info); if (err) { if (in_ssh && gpg_err_code (err) == GPG_ERR_NOT_FOUND) missing_key = 1; else goto leave; } /* Reformat the grip so that we use uppercase as good style. */ bin2hex (grip, 20, hexgrip); if (ttl > 0) snprintf (ttlbuf, sizeof ttlbuf, "%d", ttl); else strcpy (ttlbuf, "-"); *flagsbuf = 0; if (disabled) strcat (flagsbuf, "D"); if (in_ssh) strcat (flagsbuf, "S"); if (confirm) strcat (flagsbuf, "c"); if (!*flagsbuf) strcpy (flagsbuf, "-"); if (missing_key) { protectionstr = "-"; keytypestr = "-"; } else { switch (keytype) { case PRIVATE_KEY_CLEAR: case PRIVATE_KEY_OPENPGP_NONE: protectionstr = "C"; keytypestr = "D"; break; case PRIVATE_KEY_PROTECTED: protectionstr = "P"; keytypestr = "D"; break; case PRIVATE_KEY_SHADOWED: protectionstr = "-"; keytypestr = "T"; break; default: protectionstr = "-"; keytypestr = "X"; break; } } /* Compute the ssh fingerprint if requested. */ if (with_ssh_fpr) { gcry_sexp_t key; if (!agent_raw_key_from_file (ctrl, grip, &key)) { ssh_get_fingerprint_string (key, with_ssh_fpr, &fpr); gcry_sexp_release (key); } } /* Here we have a little race by doing the cache check separately from the retrieval function. Given that the cache flag is only a hint, it should not really matter. */ pw = agent_get_cache (ctrl, hexgrip, CACHE_MODE_NORMAL); cached = pw ? "1" : "-"; xfree (pw); if (shadow_info) { err = parse_shadow_info (shadow_info, &serialno, &idstr, NULL); if (err) goto leave; } if (!data) err = agent_write_status (ctrl, "KEYINFO", hexgrip, keytypestr, serialno? serialno : "-", idstr? idstr : "-", cached, protectionstr, fpr? fpr : "-", ttlbuf, flagsbuf, NULL); else { char *string; string = xtryasprintf ("%s %s %s %s %s %s %s %s %s\n", hexgrip, keytypestr, serialno? serialno : "-", idstr? idstr : "-", cached, protectionstr, fpr? fpr : "-", ttlbuf, flagsbuf); if (!string) err = gpg_error_from_syserror (); else err = assuan_send_data (ctx, string, strlen(string)); xfree (string); } leave: xfree (fpr); xfree (shadow_info); xfree (serialno); xfree (idstr); return err; } /* Entry into the command KEYINFO. This function handles the * command option processing. For details see hlp_keyinfo above. */ static gpg_error_t cmd_keyinfo (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int err; unsigned char grip[20]; gnupg_dir_t dir = NULL; int list_mode; int opt_data, opt_ssh_fpr, opt_with_ssh; ssh_control_file_t cf = NULL; char hexgrip[41]; int disabled, ttl, confirm, is_ssh; if (ctrl->restricted) return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); if (has_option (line, "--ssh-list")) list_mode = 2; else list_mode = has_option (line, "--list"); opt_data = has_option (line, "--data"); if (has_option_name (line, "--ssh-fpr")) { if (has_option (line, "--ssh-fpr=md5")) opt_ssh_fpr = GCRY_MD_MD5; else if (has_option (line, "--ssh-fpr=sha1")) opt_ssh_fpr = GCRY_MD_SHA1; else if (has_option (line, "--ssh-fpr=sha256")) opt_ssh_fpr = GCRY_MD_SHA256; else opt_ssh_fpr = opt.ssh_fingerprint_digest; } else opt_ssh_fpr = 0; opt_with_ssh = has_option (line, "--with-ssh"); line = skip_options (line); if (opt_with_ssh || list_mode == 2) cf = ssh_open_control_file (); if (list_mode == 2) { if (cf) { while (!ssh_read_control_file (cf, hexgrip, &disabled, &ttl, &confirm)) { if (hex2bin (hexgrip, grip, 20) < 0 ) continue; /* Bad hex string. */ err = do_one_keyinfo (ctrl, grip, ctx, opt_data, opt_ssh_fpr, 1, ttl, disabled, confirm); if (err) goto leave; } } err = 0; } else if (list_mode) { char *dirname; gnupg_dirent_t dir_entry; dirname = make_filename_try (gnupg_homedir (), GNUPG_PRIVATE_KEYS_DIR, NULL); if (!dirname) { err = gpg_error_from_syserror (); goto leave; } dir = gnupg_opendir (dirname); if (!dir) { err = gpg_error_from_syserror (); xfree (dirname); goto leave; } xfree (dirname); while ( (dir_entry = gnupg_readdir (dir)) ) { if (strlen (dir_entry->d_name) != 44 || strcmp (dir_entry->d_name + 40, ".key")) continue; strncpy (hexgrip, dir_entry->d_name, 40); hexgrip[40] = 0; if ( hex2bin (hexgrip, grip, 20) < 0 ) continue; /* Bad hex string. */ disabled = ttl = confirm = is_ssh = 0; if (opt_with_ssh) { err = ssh_search_control_file (cf, hexgrip, &disabled, &ttl, &confirm); if (!err) is_ssh = 1; else if (gpg_err_code (err) != GPG_ERR_NOT_FOUND) goto leave; } err = do_one_keyinfo (ctrl, grip, ctx, opt_data, opt_ssh_fpr, is_ssh, ttl, disabled, confirm); if (err) goto leave; } err = 0; } else { err = parse_keygrip (ctx, line, grip); if (err) goto leave; disabled = ttl = confirm = is_ssh = 0; if (opt_with_ssh) { err = ssh_search_control_file (cf, line, &disabled, &ttl, &confirm); if (!err) is_ssh = 1; else if (gpg_err_code (err) != GPG_ERR_NOT_FOUND) goto leave; } err = do_one_keyinfo (ctrl, grip, ctx, opt_data, opt_ssh_fpr, is_ssh, ttl, disabled, confirm); } leave: ssh_close_control_file (cf); gnupg_closedir (dir); if (err && gpg_err_code (err) != GPG_ERR_NOT_FOUND) leave_cmd (ctx, err); return err; } /* Helper for cmd_get_passphrase. */ static int send_back_passphrase (assuan_context_t ctx, int via_data, const char *pw) { size_t n; int rc; assuan_begin_confidential (ctx); n = strlen (pw); if (via_data) rc = assuan_send_data (ctx, pw, n); else { char *p = xtrymalloc_secure (n*2+1); if (!p) rc = gpg_error_from_syserror (); else { bin2hex (pw, n, p); rc = assuan_set_okay_line (ctx, p); xfree (p); } } return rc; } /* Callback function to compare the first entered PIN with the one currently being entered. */ static gpg_error_t reenter_passphrase_cmp_cb (struct pin_entry_info_s *pi) { const char *pin1 = pi->check_cb_arg; if (!strcmp (pin1, pi->pin)) return 0; /* okay */ return gpg_error (GPG_ERR_BAD_PASSPHRASE); } static const char hlp_get_passphrase[] = "GET_PASSPHRASE [--data] [--check] [--no-ask] [--repeat[=N]]\n" " [--qualitybar] [--newsymkey] \n" " [ ]\n" "\n" "This function is usually used to ask for a passphrase to be used\n" "for conventional encryption, but may also be used by programs which\n" "need specal handling of passphrases. This command uses a syntax\n" "which helps clients to use the agent with minimum effort. The\n" "agent either returns with an error or with a OK followed by the hex\n" "encoded passphrase. Note that the length of the strings is\n" "implicitly limited by the maximum length of a command.\n" "\n" "If the option \"--data\" is used the passphrase is returned by usual\n" "data lines and not on the okay line.\n" "\n" "If the option \"--check\" is used the passphrase constraints checks as\n" "implemented by gpg-agent are applied. A check is not done if the\n" "passphrase has been found in the cache.\n" "\n" "If the option \"--no-ask\" is used and the passphrase is not in the\n" "cache the user will not be asked to enter a passphrase but the error\n" "code GPG_ERR_NO_DATA is returned. \n" "\n" "If the option\"--newsymkey\" is used the agent asks for a new passphrase\n" "to be used in symmetric-only encryption. This must not be empty.\n" "\n" "If the option \"--qualitybar\" is used a visual indication of the\n" "entered passphrase quality is shown. (Unless no minimum passphrase\n" "length has been configured.)"; static gpg_error_t cmd_get_passphrase (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc; char *pw; char *response = NULL; char *response2 = NULL; char *cacheid = NULL; /* May point into LINE. */ char *desc = NULL; /* Ditto */ char *prompt = NULL; /* Ditto */ char *errtext = NULL; /* Ditto */ const char *desc2 = _("Please re-enter this passphrase"); char *p; int opt_data, opt_check, opt_no_ask, opt_qualbar, opt_newsymkey; int opt_repeat = 0; char *entry_errtext = NULL; struct pin_entry_info_s *pi = NULL; struct pin_entry_info_s *pi2 = NULL; int is_generated; if (ctrl->restricted) return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); opt_data = has_option (line, "--data"); opt_check = has_option (line, "--check"); opt_no_ask = has_option (line, "--no-ask"); if (has_option_name (line, "--repeat")) { p = option_value (line, "--repeat"); if (p) opt_repeat = atoi (p); else opt_repeat = 1; } opt_qualbar = has_option (line, "--qualitybar"); opt_newsymkey = has_option (line, "--newsymkey"); line = skip_options (line); cacheid = line; p = strchr (cacheid, ' '); if (p) { *p++ = 0; while (*p == ' ') p++; errtext = p; p = strchr (errtext, ' '); if (p) { *p++ = 0; while (*p == ' ') p++; prompt = p; p = strchr (prompt, ' '); if (p) { *p++ = 0; while (*p == ' ') p++; desc = p; p = strchr (desc, ' '); if (p) *p = 0; /* Ignore trailing garbage. */ } } } if (!*cacheid || strlen (cacheid) > 50) return set_error (GPG_ERR_ASS_PARAMETER, "invalid length of cacheID"); if (!desc) return set_error (GPG_ERR_ASS_PARAMETER, "no description given"); if (!strcmp (cacheid, "X")) cacheid = NULL; if (!strcmp (errtext, "X")) errtext = NULL; if (!strcmp (prompt, "X")) prompt = NULL; if (!strcmp (desc, "X")) desc = NULL; pw = cacheid ? agent_get_cache (ctrl, cacheid, CACHE_MODE_USER) : NULL; if (pw) { rc = send_back_passphrase (ctx, opt_data, pw); xfree (pw); goto leave; } else if (opt_no_ask) { rc = gpg_error (GPG_ERR_NO_DATA); goto leave; } /* Note, that we only need to replace the + characters and should * leave the other escaping in place because the escaped string is * send verbatim to the pinentry which does the unescaping (but not * the + replacing) */ if (errtext) plus_to_blank (errtext); if (prompt) plus_to_blank (prompt); if (desc) plus_to_blank (desc); /* If opt_repeat is 2 or higher we can't use our pin_entry_info_s * based method but fallback to the old simple method. It is * anyway questionable whether this extra repeat count makes any * real sense. */ if (opt_newsymkey && opt_repeat < 2) { /* We do not want to break any existing usage of this command * and thus we introduced the option --newsymkey to make this * command more useful to query the passphrase for symmetric * encryption. */ pi = gcry_calloc_secure (1, sizeof (*pi) + MAX_PASSPHRASE_LEN + 1); if (!pi) { rc = gpg_error_from_syserror (); goto leave; } pi2 = gcry_calloc_secure (1, sizeof (*pi2) + MAX_PASSPHRASE_LEN + 1); if (!pi2) { rc = gpg_error_from_syserror (); goto leave; } pi->max_length = MAX_PASSPHRASE_LEN + 1; pi->max_tries = 3; pi->with_qualitybar = opt_qualbar; pi->with_repeat = opt_repeat; pi->constraints_flags = (CHECK_CONSTRAINTS_NOT_EMPTY | CHECK_CONSTRAINTS_NEW_SYMKEY); pi2->max_length = MAX_PASSPHRASE_LEN + 1; pi2->max_tries = 3; pi2->check_cb = reenter_passphrase_cmp_cb; pi2->check_cb_arg = pi->pin; for (;;) /* (degenerated for-loop) */ { xfree (response); response = NULL; rc = agent_get_passphrase (ctrl, &response, desc, prompt, entry_errtext? entry_errtext:errtext, opt_qualbar, cacheid, CACHE_MODE_USER, pi); if (rc) goto leave; xfree (entry_errtext); entry_errtext = NULL; is_generated = !!(pi->status & PINENTRY_STATUS_PASSWORD_GENERATED); /* We don't allow an empty passpharse in this mode. */ if (!is_generated && check_passphrase_constraints (ctrl, pi->pin, pi->constraints_flags, &entry_errtext)) { pi->failed_tries = 0; pi2->failed_tries = 0; continue; } if (*pi->pin && !pi->repeat_okay && ctrl->pinentry_mode != PINENTRY_MODE_LOOPBACK && opt_repeat) { /* The passphrase is empty and the pinentry did not * already run the repetition check, do it here. This * is only called when using an old and simple pinentry. * It is neither called in loopback mode because the * caller does any passphrase repetition by herself nor if * no repetition was requested. */ xfree (response); response = NULL; rc = agent_get_passphrase (ctrl, &response, L_("Please re-enter this passphrase"), prompt, entry_errtext? entry_errtext:errtext, opt_qualbar, cacheid, CACHE_MODE_USER, pi2); if (gpg_err_code (rc) == GPG_ERR_BAD_PASSPHRASE) { /* The re-entered passphrase one did not match and * the user did not hit cancel. */ entry_errtext = xtrystrdup (L_("does not match - try again")); if (!entry_errtext) { rc = gpg_error_from_syserror (); goto leave; } continue; } } break; } if (!rc && *pi->pin) { /* Return the passphrase. */ if (cacheid) agent_put_cache (ctrl, cacheid, CACHE_MODE_USER, pi->pin, 0); rc = send_back_passphrase (ctx, opt_data, pi->pin); } } else { next_try: xfree (response); response = NULL; rc = agent_get_passphrase (ctrl, &response, desc, prompt, entry_errtext? entry_errtext:errtext, opt_qualbar, cacheid, CACHE_MODE_USER, NULL); xfree (entry_errtext); entry_errtext = NULL; is_generated = 0; if (!rc) { int i; if (opt_check && !is_generated && check_passphrase_constraints (ctrl, response, (opt_newsymkey? CHECK_CONSTRAINTS_NEW_SYMKEY:0), &entry_errtext)) { goto next_try; } for (i = 0; i < opt_repeat; i++) { if (ctrl->pinentry_mode == PINENTRY_MODE_LOOPBACK) break; xfree (response2); response2 = NULL; rc = agent_get_passphrase (ctrl, &response2, desc2, prompt, errtext, 0, cacheid, CACHE_MODE_USER, NULL); if (rc) break; if (strcmp (response2, response)) { entry_errtext = try_percent_escape (_("does not match - try again"), NULL); if (!entry_errtext) { rc = gpg_error_from_syserror (); break; } goto next_try; } } if (!rc) { if (cacheid) agent_put_cache (ctrl, cacheid, CACHE_MODE_USER, response, 0); rc = send_back_passphrase (ctx, opt_data, response); } } } leave: xfree (response); xfree (response2); xfree (entry_errtext); xfree (pi2); xfree (pi); return leave_cmd (ctx, rc); } static const char hlp_clear_passphrase[] = "CLEAR_PASSPHRASE [--mode=normal] \n" "\n" "may be used to invalidate the cache entry for a passphrase. The\n" "function returns with OK even when there is no cached passphrase.\n" "The --mode=normal option is used to clear an entry for a cacheid\n" "added by the agent. The --mode=ssh option is used for a cacheid\n" "added for ssh.\n"; static gpg_error_t cmd_clear_passphrase (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); char *cacheid = NULL; char *p; cache_mode_t cache_mode = CACHE_MODE_USER; if (ctrl->restricted) return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); if (has_option (line, "--mode=normal")) cache_mode = CACHE_MODE_NORMAL; else if (has_option (line, "--mode=ssh")) cache_mode = CACHE_MODE_SSH; line = skip_options (line); /* parse the stuff */ for (p=line; *p == ' '; p++) ; cacheid = p; p = strchr (cacheid, ' '); if (p) *p = 0; /* ignore garbage */ if (!*cacheid || strlen (cacheid) > 50) return set_error (GPG_ERR_ASS_PARAMETER, "invalid length of cacheID"); agent_put_cache (ctrl, cacheid, cache_mode, NULL, 0); agent_clear_passphrase (ctrl, cacheid, cache_mode); return 0; } static const char hlp_get_confirmation[] = "GET_CONFIRMATION \n" "\n" "This command may be used to ask for a simple confirmation.\n" "DESCRIPTION is displayed along with a Okay and Cancel button. This\n" "command uses a syntax which helps clients to use the agent with\n" "minimum effort. The agent either returns with an error or with a\n" "OK. Note, that the length of DESCRIPTION is implicitly limited by\n" "the maximum length of a command. DESCRIPTION should not contain\n" "any spaces, those must be encoded either percent escaped or simply\n" "as '+'."; static gpg_error_t cmd_get_confirmation (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc; char *desc = NULL; char *p; if (ctrl->restricted) return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); /* parse the stuff */ for (p=line; *p == ' '; p++) ; desc = p; p = strchr (desc, ' '); if (p) *p = 0; /* We ignore any garbage -may be later used for other args. */ if (!*desc) return set_error (GPG_ERR_ASS_PARAMETER, "no description given"); if (!strcmp (desc, "X")) desc = NULL; /* Note, that we only need to replace the + characters and should leave the other escaping in place because the escaped string is send verbatim to the pinentry which does the unescaping (but not the + replacing) */ if (desc) plus_to_blank (desc); rc = agent_get_confirmation (ctrl, desc, NULL, NULL, 0); return leave_cmd (ctx, rc); } static const char hlp_learn[] = "LEARN [--send] [--sendinfo] [--force]\n" "\n" "Learn something about the currently inserted smartcard. With\n" "--sendinfo information about the card is returned; with --send\n" "the available certificates are returned as D lines; with --force\n" "private key storage will be updated by the result."; static gpg_error_t cmd_learn (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err; int send, sendinfo, force; send = has_option (line, "--send"); sendinfo = send? 1 : has_option (line, "--sendinfo"); force = has_option (line, "--force"); if (ctrl->restricted) return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); err = agent_handle_learn (ctrl, send, sendinfo? ctx : NULL, force); return leave_cmd (ctx, err); } static const char hlp_passwd[] = "PASSWD [--cache-nonce=] [--passwd-nonce=] [--preset]\n" " [--verify] \n" "\n" "Change the passphrase/PIN for the key identified by keygrip in LINE. If\n" "--preset is used then the new passphrase will be added to the cache.\n" "If --verify is used the command asks for the passphrase and verifies\n" "that the passphrase valid.\n"; static gpg_error_t cmd_passwd (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err; int c; char *cache_nonce = NULL; char *passwd_nonce = NULL; unsigned char grip[20]; gcry_sexp_t s_skey = NULL; unsigned char *shadow_info = NULL; char *passphrase = NULL; char *pend; int opt_preset, opt_verify; if (ctrl->restricted) return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); opt_preset = has_option (line, "--preset"); cache_nonce = option_value (line, "--cache-nonce"); opt_verify = has_option (line, "--verify"); if (cache_nonce) { for (pend = cache_nonce; *pend && !spacep (pend); pend++) ; c = *pend; *pend = '\0'; cache_nonce = xtrystrdup (cache_nonce); *pend = c; if (!cache_nonce) { err = gpg_error_from_syserror (); goto leave; } } passwd_nonce = option_value (line, "--passwd-nonce"); if (passwd_nonce) { for (pend = passwd_nonce; *pend && !spacep (pend); pend++) ; c = *pend; *pend = '\0'; passwd_nonce = xtrystrdup (passwd_nonce); *pend = c; if (!passwd_nonce) { err = gpg_error_from_syserror (); goto leave; } } line = skip_options (line); err = parse_keygrip (ctx, line, grip); if (err) goto leave; ctrl->in_passwd++; err = agent_key_from_file (ctrl, opt_verify? NULL : cache_nonce, ctrl->server_local->keydesc, grip, &shadow_info, CACHE_MODE_IGNORE, NULL, &s_skey, &passphrase); if (err) ; else if (shadow_info) { log_error ("changing a smartcard PIN is not yet supported\n"); err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); } else if (opt_verify) { /* All done. */ if (passphrase) { if (!passwd_nonce) { char buf[12]; gcry_create_nonce (buf, 12); passwd_nonce = bin2hex (buf, 12, NULL); } if (passwd_nonce && !agent_put_cache (ctrl, passwd_nonce, CACHE_MODE_NONCE, passphrase, CACHE_TTL_NONCE)) { assuan_write_status (ctx, "PASSWD_NONCE", passwd_nonce); xfree (ctrl->server_local->last_passwd_nonce); ctrl->server_local->last_passwd_nonce = passwd_nonce; passwd_nonce = NULL; } } } else { char *newpass = NULL; if (passwd_nonce) newpass = agent_get_cache (ctrl, passwd_nonce, CACHE_MODE_NONCE); err = agent_protect_and_store (ctrl, s_skey, &newpass); if (!err && passphrase) { /* A passphrase existed on the old key and the change was successful. Return a nonce for that old passphrase to let the caller try to unprotect the other subkeys with the same key. */ if (!cache_nonce) { char buf[12]; gcry_create_nonce (buf, 12); cache_nonce = bin2hex (buf, 12, NULL); } if (cache_nonce && !agent_put_cache (ctrl, cache_nonce, CACHE_MODE_NONCE, passphrase, CACHE_TTL_NONCE)) { assuan_write_status (ctx, "CACHE_NONCE", cache_nonce); xfree (ctrl->server_local->last_cache_nonce); ctrl->server_local->last_cache_nonce = cache_nonce; cache_nonce = NULL; } if (newpass) { /* If we have a new passphrase (which might be empty) we store it under a passwd nonce so that the caller may send that nonce again to use it for another key. */ if (!passwd_nonce) { char buf[12]; gcry_create_nonce (buf, 12); passwd_nonce = bin2hex (buf, 12, NULL); } if (passwd_nonce && !agent_put_cache (ctrl, passwd_nonce, CACHE_MODE_NONCE, newpass, CACHE_TTL_NONCE)) { assuan_write_status (ctx, "PASSWD_NONCE", passwd_nonce); xfree (ctrl->server_local->last_passwd_nonce); ctrl->server_local->last_passwd_nonce = passwd_nonce; passwd_nonce = NULL; } } } if (!err && opt_preset) { char hexgrip[40+1]; bin2hex(grip, 20, hexgrip); err = agent_put_cache (ctrl, hexgrip, CACHE_MODE_ANY, newpass, ctrl->cache_ttl_opt_preset); } xfree (newpass); } ctrl->in_passwd--; xfree (ctrl->server_local->keydesc); ctrl->server_local->keydesc = NULL; leave: xfree (passphrase); gcry_sexp_release (s_skey); xfree (shadow_info); xfree (cache_nonce); xfree (passwd_nonce); return leave_cmd (ctx, err); } static const char hlp_preset_passphrase[] = "PRESET_PASSPHRASE [--inquire] []\n" "\n" "Set the cached passphrase/PIN for the key identified by the keygrip\n" "to passwd for the given time, where -1 means infinite and 0 means\n" "the default (currently only a timeout of -1 is allowed, which means\n" "to never expire it). If passwd is not provided, ask for it via the\n" "pinentry module unless --inquire is passed in which case the passphrase\n" "is retrieved from the client via a server inquire.\n"; static gpg_error_t cmd_preset_passphrase (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc; char *grip_clear = NULL; unsigned char *passphrase = NULL; int ttl; size_t len; int opt_inquire; if (ctrl->restricted) return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); if (!opt.allow_preset_passphrase) return set_error (GPG_ERR_NOT_SUPPORTED, "no --allow-preset-passphrase"); opt_inquire = has_option (line, "--inquire"); line = skip_options (line); grip_clear = line; while (*line && (*line != ' ' && *line != '\t')) line++; if (!*line) return gpg_error (GPG_ERR_MISSING_VALUE); *line = '\0'; line++; while (*line && (*line == ' ' || *line == '\t')) line++; /* Currently, only infinite timeouts are allowed. */ ttl = -1; if (line[0] != '-' || line[1] != '1') return gpg_error (GPG_ERR_NOT_IMPLEMENTED); line++; line++; while (!(*line != ' ' && *line != '\t')) line++; /* Syntax check the hexstring. */ len = 0; rc = parse_hexstring (ctx, line, &len); if (rc) return rc; line[len] = '\0'; /* If there is a passphrase, use it. Currently, a passphrase is required. */ if (*line) { if (opt_inquire) { rc = set_error (GPG_ERR_ASS_PARAMETER, "both --inquire and passphrase specified"); goto leave; } /* Do in-place conversion. */ passphrase = line; if (!hex2str (passphrase, passphrase, strlen (passphrase)+1, NULL)) rc = set_error (GPG_ERR_ASS_PARAMETER, "invalid hexstring"); } else if (opt_inquire) { /* Note that the passphrase will be truncated at any null byte and the * limit is 480 characters. */ size_t maxlen = 480; rc = print_assuan_status (ctx, "INQUIRE_MAXLEN", "%zu", maxlen); if (!rc) - rc = assuan_inquire (ctx, "PASSPHRASE", &passphrase, &len, maxlen); + { + assuan_begin_confidential (ctx); + rc = assuan_inquire (ctx, "PASSPHRASE", &passphrase, &len, maxlen); + assuan_end_confidential (ctx); + } } else rc = set_error (GPG_ERR_NOT_IMPLEMENTED, "passphrase is required"); if (!rc) { rc = agent_put_cache (ctrl, grip_clear, CACHE_MODE_ANY, passphrase, ttl); if (opt_inquire) - xfree (passphrase); + { + wipememory (passphrase, len); + xfree (passphrase); + } } leave: return leave_cmd (ctx, rc); } static const char hlp_scd[] = "SCD \n" " \n" "This is a general quote command to redirect everything to the\n" "SCdaemon."; static gpg_error_t cmd_scd (assuan_context_t ctx, char *line) { int rc; #ifdef BUILD_WITH_SCDAEMON ctrl_t ctrl = assuan_get_pointer (ctx); if (ctrl->restricted) return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); rc = divert_generic_cmd (ctrl, line, ctx); #else (void)ctx; (void)line; rc = gpg_error (GPG_ERR_NOT_SUPPORTED); #endif return rc; } static const char hlp_keywrap_key[] = "KEYWRAP_KEY [--clear] \n" "\n" "Return a key to wrap another key. For now the key is returned\n" "verbatim and thus makes not much sense because an eavesdropper on\n" "the gpg-agent connection will see the key as well as the wrapped key.\n" "However, this function may either be equipped with a public key\n" "mechanism or not used at all if the key is a pre-shared key. In any\n" "case wrapping the import and export of keys is a requirement for\n" "certain cryptographic validations and thus useful. The key persists\n" "until a RESET command but may be cleared using the option --clear.\n" "\n" "Supported modes are:\n" " --import - Return a key to import a key into gpg-agent\n" " --export - Return a key to export a key from gpg-agent"; static gpg_error_t cmd_keywrap_key (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err = 0; int clearopt = has_option (line, "--clear"); if (ctrl->restricted) return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); assuan_begin_confidential (ctx); if (has_option (line, "--import")) { xfree (ctrl->server_local->import_key); if (clearopt) ctrl->server_local->import_key = NULL; else if (!(ctrl->server_local->import_key = gcry_random_bytes (KEYWRAP_KEYSIZE, GCRY_STRONG_RANDOM))) err = gpg_error_from_syserror (); else err = assuan_send_data (ctx, ctrl->server_local->import_key, KEYWRAP_KEYSIZE); } else if (has_option (line, "--export")) { xfree (ctrl->server_local->export_key); if (clearopt) ctrl->server_local->export_key = NULL; else if (!(ctrl->server_local->export_key = gcry_random_bytes (KEYWRAP_KEYSIZE, GCRY_STRONG_RANDOM))) err = gpg_error_from_syserror (); else err = assuan_send_data (ctx, ctrl->server_local->export_key, KEYWRAP_KEYSIZE); } else err = set_error (GPG_ERR_ASS_PARAMETER, "unknown value for MODE"); assuan_end_confidential (ctx); return leave_cmd (ctx, err); } static const char hlp_import_key[] = "IMPORT_KEY [--unattended] [--force] [--timestamp=]\n" " []\n" "\n" "Import a secret key into the key store. The key is expected to be\n" "encrypted using the current session's key wrapping key (cf. command\n" "KEYWRAP_KEY) using the AESWRAP-128 algorithm. This function takes\n" "no arguments but uses the inquiry \"KEYDATA\" to ask for the actual\n" "key data. The unwrapped key must be a canonical S-expression. The\n" "option --unattended tries to import the key as-is without any\n" "re-encryption. An existing key can be overwritten with --force.\n" "If --timestamp is given its value is recorded as the key's creation\n" "time; the value is expected in ISO format (e.g. \"20030316T120000\")."; static gpg_error_t cmd_import_key (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err; int opt_unattended; time_t opt_timestamp; int force; unsigned char *wrappedkey = NULL; size_t wrappedkeylen; gcry_cipher_hd_t cipherhd = NULL; unsigned char *key = NULL; size_t keylen, realkeylen; char *passphrase = NULL; unsigned char *finalkey = NULL; size_t finalkeylen; unsigned char grip[20]; gcry_sexp_t openpgp_sexp = NULL; char *cache_nonce = NULL; char *p; const char *s; if (ctrl->restricted) return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); if (!ctrl->server_local->import_key) { err = gpg_error (GPG_ERR_MISSING_KEY); goto leave; } opt_unattended = has_option (line, "--unattended"); force = has_option (line, "--force"); if ((s=has_option_name (line, "--timestamp"))) { if (*s != '=') { err = set_error (GPG_ERR_ASS_PARAMETER, "missing value for option"); goto leave; } opt_timestamp = isotime2epoch (s+1); if (opt_timestamp < 1) { err = set_error (GPG_ERR_ASS_PARAMETER, "invalid time value"); goto leave; } } else opt_timestamp = 0; line = skip_options (line); for (p=line; *p && *p != ' ' && *p != '\t'; p++) ; *p = '\0'; if (*line) cache_nonce = xtrystrdup (line); assuan_begin_confidential (ctx); err = assuan_inquire (ctx, "KEYDATA", &wrappedkey, &wrappedkeylen, MAXLEN_KEYDATA); assuan_end_confidential (ctx); if (err) goto leave; if (wrappedkeylen < 24) { err = gpg_error (GPG_ERR_INV_LENGTH); goto leave; } keylen = wrappedkeylen - 8; key = xtrymalloc_secure (keylen); if (!key) { err = gpg_error_from_syserror (); goto leave; } err = gcry_cipher_open (&cipherhd, GCRY_CIPHER_AES128, GCRY_CIPHER_MODE_AESWRAP, 0); if (err) goto leave; err = gcry_cipher_setkey (cipherhd, ctrl->server_local->import_key, KEYWRAP_KEYSIZE); if (err) goto leave; err = gcry_cipher_decrypt (cipherhd, key, keylen, wrappedkey, wrappedkeylen); if (err) goto leave; gcry_cipher_close (cipherhd); cipherhd = NULL; xfree (wrappedkey); wrappedkey = NULL; realkeylen = gcry_sexp_canon_len (key, keylen, NULL, &err); if (!realkeylen) goto leave; /* Invalid canonical encoded S-expression. */ err = keygrip_from_canon_sexp (key, realkeylen, grip); if (err) { /* This might be due to an unsupported S-expression format. Check whether this is openpgp-private-key and trigger that import code. */ if (!gcry_sexp_sscan (&openpgp_sexp, NULL, key, realkeylen)) { const char *tag; size_t taglen; tag = gcry_sexp_nth_data (openpgp_sexp, 0, &taglen); if (tag && taglen == 19 && !memcmp (tag, "openpgp-private-key", 19)) ; else { gcry_sexp_release (openpgp_sexp); openpgp_sexp = NULL; } } if (!openpgp_sexp) goto leave; /* Note that ERR is still set. */ } if (openpgp_sexp) { /* In most cases the key is encrypted and thus the conversion function from the OpenPGP format to our internal format will ask for a passphrase. That passphrase will be returned and used to protect the key using the same code as for regular key import. */ xfree (key); key = NULL; err = convert_from_openpgp (ctrl, openpgp_sexp, force, grip, ctrl->server_local->keydesc, cache_nonce, &key, opt_unattended? NULL : &passphrase); if (err) goto leave; realkeylen = gcry_sexp_canon_len (key, 0, NULL, &err); if (!realkeylen) goto leave; /* Invalid canonical encoded S-expression. */ if (passphrase) { assert (!opt_unattended); if (!cache_nonce) { char buf[12]; gcry_create_nonce (buf, 12); cache_nonce = bin2hex (buf, 12, NULL); } if (cache_nonce && !agent_put_cache (ctrl, cache_nonce, CACHE_MODE_NONCE, passphrase, CACHE_TTL_NONCE)) assuan_write_status (ctx, "CACHE_NONCE", cache_nonce); } } else if (opt_unattended) { err = set_error (GPG_ERR_ASS_PARAMETER, "\"--unattended\" may only be used with OpenPGP keys"); goto leave; } else { if (!force && !agent_key_available (grip)) err = gpg_error (GPG_ERR_EEXIST); else { char *prompt = xtryasprintf (_("Please enter the passphrase to protect the " "imported object within the %s system."), GNUPG_NAME); if (!prompt) err = gpg_error_from_syserror (); else err = agent_ask_new_passphrase (ctrl, prompt, &passphrase); xfree (prompt); } if (err) goto leave; } if (passphrase) { err = agent_protect (key, passphrase, &finalkey, &finalkeylen, ctrl->s2k_count, -1); if (!err) err = agent_write_private_key (grip, finalkey, finalkeylen, force, opt_timestamp); } else err = agent_write_private_key (grip, key, realkeylen, force, opt_timestamp); leave: gcry_sexp_release (openpgp_sexp); xfree (finalkey); xfree (passphrase); xfree (key); gcry_cipher_close (cipherhd); xfree (wrappedkey); xfree (cache_nonce); xfree (ctrl->server_local->keydesc); ctrl->server_local->keydesc = NULL; return leave_cmd (ctx, err); } static const char hlp_export_key[] = "EXPORT_KEY [--cache-nonce=] [--openpgp] \n" "\n" "Export a secret key from the key store. The key will be encrypted\n" "using the current session's key wrapping key (cf. command KEYWRAP_KEY)\n" "using the AESWRAP-128 algorithm. The caller needs to retrieve that key\n" "prior to using this command. The function takes the keygrip as argument.\n" "\n" "If --openpgp is used, the secret key material will be exported in RFC 4880\n" "compatible passphrase-protected form. Without --openpgp, the secret key\n" "material will be exported in the clear (after prompting the user to unlock\n" "it, if needed).\n"; static gpg_error_t cmd_export_key (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err; unsigned char grip[20]; gcry_sexp_t s_skey = NULL; unsigned char *key = NULL; size_t keylen; gcry_cipher_hd_t cipherhd = NULL; unsigned char *wrappedkey = NULL; size_t wrappedkeylen; int openpgp; char *cache_nonce; char *passphrase = NULL; unsigned char *shadow_info = NULL; char *pend; int c; if (ctrl->restricted) return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); openpgp = has_option (line, "--openpgp"); cache_nonce = option_value (line, "--cache-nonce"); if (cache_nonce) { for (pend = cache_nonce; *pend && !spacep (pend); pend++) ; c = *pend; *pend = '\0'; cache_nonce = xtrystrdup (cache_nonce); *pend = c; if (!cache_nonce) { err = gpg_error_from_syserror (); goto leave; } } line = skip_options (line); if (!ctrl->server_local->export_key) { err = set_error (GPG_ERR_MISSING_KEY, "did you run KEYWRAP_KEY ?"); goto leave; } err = parse_keygrip (ctx, line, grip); if (err) goto leave; if (agent_key_available (grip)) { err = gpg_error (GPG_ERR_NO_SECKEY); goto leave; } /* Get the key from the file. With the openpgp flag we also ask for the passphrase so that we can use it to re-encrypt it. */ err = agent_key_from_file (ctrl, cache_nonce, ctrl->server_local->keydesc, grip, &shadow_info, CACHE_MODE_IGNORE, NULL, &s_skey, openpgp ? &passphrase : NULL); if (err) goto leave; if (shadow_info) { /* Key is on a smartcard. */ err = gpg_error (GPG_ERR_UNUSABLE_SECKEY); goto leave; } if (openpgp) { /* The openpgp option changes the key format into the OpenPGP key transfer format. The result is already a padded canonical S-expression. */ if (!passphrase) { err = agent_ask_new_passphrase (ctrl, _("This key (or subkey) is not protected with a passphrase." " Please enter a new passphrase to export it."), &passphrase); if (err) goto leave; } err = convert_to_openpgp (ctrl, s_skey, passphrase, &key, &keylen); if (!err && passphrase) { if (!cache_nonce) { char buf[12]; gcry_create_nonce (buf, 12); cache_nonce = bin2hex (buf, 12, NULL); } if (cache_nonce && !agent_put_cache (ctrl, cache_nonce, CACHE_MODE_NONCE, passphrase, CACHE_TTL_NONCE)) { assuan_write_status (ctx, "CACHE_NONCE", cache_nonce); xfree (ctrl->server_local->last_cache_nonce); ctrl->server_local->last_cache_nonce = cache_nonce; cache_nonce = NULL; } } } else { /* Convert into a canonical S-expression and wrap that. */ err = make_canon_sexp_pad (s_skey, 1, &key, &keylen); } if (err) goto leave; gcry_sexp_release (s_skey); s_skey = NULL; err = gcry_cipher_open (&cipherhd, GCRY_CIPHER_AES128, GCRY_CIPHER_MODE_AESWRAP, 0); if (err) goto leave; err = gcry_cipher_setkey (cipherhd, ctrl->server_local->export_key, KEYWRAP_KEYSIZE); if (err) goto leave; wrappedkeylen = keylen + 8; wrappedkey = xtrymalloc (wrappedkeylen); if (!wrappedkey) { err = gpg_error_from_syserror (); goto leave; } err = gcry_cipher_encrypt (cipherhd, wrappedkey, wrappedkeylen, key, keylen); if (err) goto leave; xfree (key); key = NULL; gcry_cipher_close (cipherhd); cipherhd = NULL; assuan_begin_confidential (ctx); err = assuan_send_data (ctx, wrappedkey, wrappedkeylen); assuan_end_confidential (ctx); leave: xfree (cache_nonce); xfree (passphrase); xfree (wrappedkey); gcry_cipher_close (cipherhd); xfree (key); gcry_sexp_release (s_skey); xfree (ctrl->server_local->keydesc); ctrl->server_local->keydesc = NULL; xfree (shadow_info); return leave_cmd (ctx, err); } static const char hlp_delete_key[] = "DELETE_KEY [--force|--stub-only] \n" "\n" "Delete a secret key from the key store. If --force is used\n" "and a loopback pinentry is allowed, the agent will not ask\n" "the user for confirmation. If --stub-only is used the key will\n" "only be deleted if it is a reference to a token."; static gpg_error_t cmd_delete_key (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err; int force, stub_only; unsigned char grip[20]; if (ctrl->restricted) return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); force = has_option (line, "--force"); stub_only = has_option (line, "--stub-only"); line = skip_options (line); /* If the use of a loopback pinentry has been disabled, we assume * that a silent deletion of keys shall also not be allowed. */ if (!opt.allow_loopback_pinentry) force = 0; err = parse_keygrip (ctx, line, grip); if (err) goto leave; err = agent_delete_key (ctrl, ctrl->server_local->keydesc, grip, force, stub_only); if (err) goto leave; leave: xfree (ctrl->server_local->keydesc); ctrl->server_local->keydesc = NULL; return leave_cmd (ctx, err); } #if SIZEOF_TIME_T > SIZEOF_UNSIGNED_LONG #define KEYTOCARD_TIMESTAMP_FORMAT "(10:created-at10:%010llu))" #else #define KEYTOCARD_TIMESTAMP_FORMAT "(10:created-at10:%010lu))" #endif static const char hlp_keytocard[] = "KEYTOCARD [--force] \n" "\n"; static gpg_error_t cmd_keytocard (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int force; gpg_error_t err = 0; unsigned char grip[20]; gcry_sexp_t s_skey = NULL; unsigned char *keydata; size_t keydatalen; const char *serialno, *timestamp_str, *id; unsigned char *shadow_info = NULL; time_t timestamp; if (ctrl->restricted) return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); force = has_option (line, "--force"); line = skip_options (line); err = parse_keygrip (ctx, line, grip); if (err) goto leave; if (agent_key_available (grip)) { err =gpg_error (GPG_ERR_NO_SECKEY); goto leave; } /* Fixme: Replace the parsing code by split_fields(). */ line += 40; while (*line && (*line == ' ' || *line == '\t')) line++; serialno = line; while (*line && (*line != ' ' && *line != '\t')) line++; if (!*line) { err = gpg_error (GPG_ERR_MISSING_VALUE); goto leave; } *line = '\0'; line++; while (*line && (*line == ' ' || *line == '\t')) line++; id = line; while (*line && (*line != ' ' && *line != '\t')) line++; if (!*line) { err = gpg_error (GPG_ERR_MISSING_VALUE); goto leave; } *line = '\0'; line++; while (*line && (*line == ' ' || *line == '\t')) line++; timestamp_str = line; while (*line && (*line != ' ' && *line != '\t')) line++; if (*line) *line = '\0'; if ((timestamp = isotime2epoch (timestamp_str)) == (time_t)(-1)) { err = gpg_error (GPG_ERR_INV_TIME); goto leave; } err = agent_key_from_file (ctrl, NULL, ctrl->server_local->keydesc, grip, &shadow_info, CACHE_MODE_IGNORE, NULL, &s_skey, NULL); if (err) { xfree (shadow_info); goto leave; } if (shadow_info) { /* Key is on a smartcard already. */ xfree (shadow_info); gcry_sexp_release (s_skey); err = gpg_error (GPG_ERR_UNUSABLE_SECKEY); goto leave; } keydatalen = gcry_sexp_sprint (s_skey, GCRYSEXP_FMT_CANON, NULL, 0); keydata = xtrymalloc_secure (keydatalen + 30); if (keydata == NULL) { err = gpg_error_from_syserror (); gcry_sexp_release (s_skey); goto leave; } gcry_sexp_sprint (s_skey, GCRYSEXP_FMT_CANON, keydata, keydatalen); gcry_sexp_release (s_skey); keydatalen--; /* Decrement for last '\0'. */ /* Add timestamp "created-at" in the private key */ snprintf (keydata+keydatalen-1, 30, KEYTOCARD_TIMESTAMP_FORMAT, timestamp); keydatalen += 10 + 19 - 1; err = divert_writekey (ctrl, force, serialno, id, keydata, keydatalen); xfree (keydata); leave: return leave_cmd (ctx, err); } static const char hlp_getval[] = "GETVAL \n" "\n" "Return the value for KEY from the special environment as created by\n" "PUTVAL."; static gpg_error_t cmd_getval (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc = 0; char *key = NULL; char *p; struct putval_item_s *vl; if (ctrl->restricted) return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); for (p=line; *p == ' '; p++) ; key = p; p = strchr (key, ' '); if (p) { *p++ = 0; for (; *p == ' '; p++) ; if (*p) return set_error (GPG_ERR_ASS_PARAMETER, "too many arguments"); } if (!*key) return set_error (GPG_ERR_ASS_PARAMETER, "no key given"); for (vl=putval_list; vl; vl = vl->next) if ( !strcmp (vl->d, key) ) break; if (vl) /* Got an entry. */ rc = assuan_send_data (ctx, vl->d+vl->off, vl->len); else return gpg_error (GPG_ERR_NO_DATA); return leave_cmd (ctx, rc); } static const char hlp_putval[] = "PUTVAL []\n" "\n" "The gpg-agent maintains a kind of environment which may be used to\n" "store key/value pairs in it, so that they can be retrieved later.\n" "This may be used by helper daemons to daemonize themself on\n" "invocation and register them with gpg-agent. Callers of the\n" "daemon's service may now first try connect to get the information\n" "for that service from gpg-agent through the GETVAL command and then\n" "try to connect to that daemon. Only if that fails they may start\n" "an own instance of the service daemon. \n" "\n" "KEY is an arbitrary symbol with the same syntax rules as keys\n" "for shell environment variables. PERCENT_ESCAPED_VALUE is the\n" "corresponding value; they should be similar to the values of\n" "envronment variables but gpg-agent does not enforce any\n" "restrictions. If that value is not given any value under that KEY\n" "is removed from this special environment."; static gpg_error_t cmd_putval (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc = 0; char *key = NULL; char *value = NULL; size_t valuelen = 0; char *p; struct putval_item_s *vl, *vlprev; if (ctrl->restricted) return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); for (p=line; *p == ' '; p++) ; key = p; p = strchr (key, ' '); if (p) { *p++ = 0; for (; *p == ' '; p++) ; if (*p) { value = p; p = strchr (value, ' '); if (p) *p = 0; valuelen = percent_plus_unescape_inplace (value, 0); } } if (!*key) return set_error (GPG_ERR_ASS_PARAMETER, "no key given"); for (vl=putval_list,vlprev=NULL; vl; vlprev=vl, vl = vl->next) if ( !strcmp (vl->d, key) ) break; if (vl) /* Delete old entry. */ { if (vlprev) vlprev->next = vl->next; else putval_list = vl->next; xfree (vl); } if (valuelen) /* Add entry. */ { vl = xtrymalloc (sizeof *vl + strlen (key) + valuelen); if (!vl) rc = gpg_error_from_syserror (); else { vl->len = valuelen; vl->off = strlen (key) + 1; strcpy (vl->d, key); memcpy (vl->d + vl->off, value, valuelen); vl->next = putval_list; putval_list = vl; } } return leave_cmd (ctx, rc); } static const char hlp_updatestartuptty[] = "UPDATESTARTUPTTY\n" "\n" "Set startup TTY and X11 DISPLAY variables to the values of this\n" "session. This command is useful to pull future pinentries to\n" "another screen. It is only required because there is no way in the\n" "ssh-agent protocol to convey this information."; static gpg_error_t cmd_updatestartuptty (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err = 0; session_env_t se; char *lc_ctype = NULL; char *lc_messages = NULL; int iterator; const char *name; (void)line; if (ctrl->restricted) return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); se = session_env_new (); if (!se) err = gpg_error_from_syserror (); iterator = 0; while (!err && (name = session_env_list_stdenvnames (&iterator, NULL))) { const char *value = session_env_getenv (ctrl->session_env, name); if (value) err = session_env_setenv (se, name, value); } if (!err && ctrl->lc_ctype) if (!(lc_ctype = xtrystrdup (ctrl->lc_ctype))) err = gpg_error_from_syserror (); if (!err && ctrl->lc_messages) if (!(lc_messages = xtrystrdup (ctrl->lc_messages))) err = gpg_error_from_syserror (); if (err) { session_env_release (se); xfree (lc_ctype); xfree (lc_messages); } else { session_env_release (opt.startup_env); opt.startup_env = se; xfree (opt.startup_lc_ctype); opt.startup_lc_ctype = lc_ctype; xfree (opt.startup_lc_messages); opt.startup_lc_messages = lc_messages; } return err; } static const char hlp_killagent[] = "KILLAGENT\n" "\n" "Stop the agent."; static gpg_error_t cmd_killagent (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); (void)line; if (ctrl->restricted) return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); ctrl->server_local->stopme = 1; assuan_set_flag (ctx, ASSUAN_FORCE_CLOSE, 1); return 0; } static const char hlp_reloadagent[] = "RELOADAGENT\n" "\n" "This command is an alternative to SIGHUP\n" "to reload the configuration."; static gpg_error_t cmd_reloadagent (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); (void)line; if (ctrl->restricted) return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); agent_sighup_action (); return 0; } static const char hlp_getinfo[] = "GETINFO \n" "\n" "Multipurpose function to return a variety of information.\n" "Supported values for WHAT are:\n" "\n" " version - Return the version of the program.\n" " pid - Return the process id of the server.\n" " socket_name - Return the name of the socket.\n" " ssh_socket_name - Return the name of the ssh socket.\n" " scd_running - Return OK if the SCdaemon is already running.\n" " s2k_time - Return the time in milliseconds required for S2K.\n" " s2k_count - Return the standard S2K count.\n" " s2k_count_cal - Return the calibrated S2K count.\n" " std_env_names - List the names of the standard environment.\n" " std_session_env - List the standard session environment.\n" " std_startup_env - List the standard startup environment.\n" " getenv NAME - Return value of envvar NAME.\n" " connections - Return number of active connections.\n" " jent_active - Returns OK if Libgcrypt's JENT is active.\n" " restricted - Returns OK if the connection is in restricted mode.\n" " cmd_has_option CMD OPT\n" " - Returns OK if command CMD has option OPT.\n"; static gpg_error_t cmd_getinfo (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc = 0; if (!strcmp (line, "version")) { const char *s = VERSION; rc = assuan_send_data (ctx, s, strlen (s)); } else if (!strncmp (line, "cmd_has_option", 14) && (line[14] == ' ' || line[14] == '\t' || !line[14])) { char *cmd, *cmdopt; line += 14; while (*line == ' ' || *line == '\t') line++; if (!*line) rc = gpg_error (GPG_ERR_MISSING_VALUE); else { cmd = line; while (*line && (*line != ' ' && *line != '\t')) line++; if (!*line) rc = gpg_error (GPG_ERR_MISSING_VALUE); else { *line++ = 0; while (*line == ' ' || *line == '\t') line++; if (!*line) rc = gpg_error (GPG_ERR_MISSING_VALUE); else { cmdopt = line; if (!command_has_option (cmd, cmdopt)) rc = gpg_error (GPG_ERR_FALSE); } } } } else if (!strcmp (line, "s2k_count")) { char numbuf[50]; snprintf (numbuf, sizeof numbuf, "%lu", get_standard_s2k_count ()); rc = assuan_send_data (ctx, numbuf, strlen (numbuf)); } else if (!strcmp (line, "restricted")) { rc = ctrl->restricted? 0 : gpg_error (GPG_ERR_FALSE); } else if (ctrl->restricted) { rc = gpg_error (GPG_ERR_FORBIDDEN); } /* All sub-commands below are not allowed in restricted mode. */ else if (!strcmp (line, "pid")) { char numbuf[50]; snprintf (numbuf, sizeof numbuf, "%lu", (unsigned long)getpid ()); rc = assuan_send_data (ctx, numbuf, strlen (numbuf)); } else if (!strcmp (line, "socket_name")) { const char *s = get_agent_socket_name (); if (s) rc = assuan_send_data (ctx, s, strlen (s)); else rc = gpg_error (GPG_ERR_NO_DATA); } else if (!strcmp (line, "ssh_socket_name")) { const char *s = get_agent_ssh_socket_name (); if (s) rc = assuan_send_data (ctx, s, strlen (s)); else rc = gpg_error (GPG_ERR_NO_DATA); } else if (!strcmp (line, "scd_running")) { rc = agent_scd_check_running ()? 0 : gpg_error (GPG_ERR_FALSE); } else if (!strcmp (line, "std_env_names")) { int iterator; const char *name; iterator = 0; while ((name = session_env_list_stdenvnames (&iterator, NULL))) { rc = assuan_send_data (ctx, name, strlen (name)+1); if (!rc) rc = assuan_send_data (ctx, NULL, 0); if (rc) break; } } else if (!strcmp (line, "std_session_env") || !strcmp (line, "std_startup_env")) { int iterator; const char *name, *value; char *string; iterator = 0; while ((name = session_env_list_stdenvnames (&iterator, NULL))) { value = session_env_getenv_or_default (line[5] == 't'? opt.startup_env:ctrl->session_env, name, NULL); if (value) { string = xtryasprintf ("%s=%s", name, value); if (!string) rc = gpg_error_from_syserror (); else { rc = assuan_send_data (ctx, string, strlen (string)+1); if (!rc) rc = assuan_send_data (ctx, NULL, 0); } if (rc) break; } } } else if (!strncmp (line, "getenv", 6) && (line[6] == ' ' || line[6] == '\t' || !line[6])) { line += 6; while (*line == ' ' || *line == '\t') line++; if (!*line) rc = gpg_error (GPG_ERR_MISSING_VALUE); else { const char *s = getenv (line); if (!s) rc = set_error (GPG_ERR_NOT_FOUND, "No such envvar"); else rc = assuan_send_data (ctx, s, strlen (s)); } } else if (!strcmp (line, "connections")) { char numbuf[20]; snprintf (numbuf, sizeof numbuf, "%d", get_agent_active_connection_count ()); rc = assuan_send_data (ctx, numbuf, strlen (numbuf)); } else if (!strcmp (line, "jent_active")) { char *buf; char *fields[5]; buf = gcry_get_config (0, "rng-type"); if (buf && split_fields_colon (buf, fields, DIM (fields)) >= 5 && atoi (fields[4]) > 0) rc = 0; else rc = gpg_error (GPG_ERR_FALSE); gcry_free (buf); } else if (!strcmp (line, "s2k_count_cal")) { char numbuf[50]; snprintf (numbuf, sizeof numbuf, "%lu", get_calibrated_s2k_count ()); rc = assuan_send_data (ctx, numbuf, strlen (numbuf)); } else if (!strcmp (line, "s2k_time")) { char numbuf[50]; snprintf (numbuf, sizeof numbuf, "%lu", get_standard_s2k_time ()); rc = assuan_send_data (ctx, numbuf, strlen (numbuf)); } else rc = set_error (GPG_ERR_ASS_PARAMETER, "unknown value for WHAT"); return rc; } /* This function is called by Libassuan to parse the OPTION command. It has been registered similar to the other Assuan commands. */ static gpg_error_t option_handler (assuan_context_t ctx, const char *key, const char *value) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err = 0; if (!strcmp (key, "agent-awareness")) { /* The value is a version string telling us of which agent version the caller is aware of. */ ctrl->server_local->allow_fully_canceled = gnupg_compare_version (value, "2.1.0"); } else if (ctrl->restricted) { err = gpg_error (GPG_ERR_FORBIDDEN); } /* All options below are not allowed in restricted mode. */ else if (!strcmp (key, "putenv")) { /* Change the session's environment to be used for the Pinentry. Valid values are: Delete envvar NAME = Set envvar NAME to the empty string = Set envvar NAME to VALUE */ err = session_env_putenv (ctrl->session_env, value); } else if (!strcmp (key, "display")) { err = session_env_setenv (ctrl->session_env, "DISPLAY", value); } else if (!strcmp (key, "ttyname")) { if (!opt.keep_tty) err = session_env_setenv (ctrl->session_env, "GPG_TTY", value); } else if (!strcmp (key, "ttytype")) { if (!opt.keep_tty) err = session_env_setenv (ctrl->session_env, "TERM", value); } else if (!strcmp (key, "lc-ctype")) { if (ctrl->lc_ctype) xfree (ctrl->lc_ctype); ctrl->lc_ctype = xtrystrdup (value); if (!ctrl->lc_ctype) return out_of_core (); } else if (!strcmp (key, "lc-messages")) { if (ctrl->lc_messages) xfree (ctrl->lc_messages); ctrl->lc_messages = xtrystrdup (value); if (!ctrl->lc_messages) return out_of_core (); } else if (!strcmp (key, "xauthority")) { err = session_env_setenv (ctrl->session_env, "XAUTHORITY", value); } else if (!strcmp (key, "pinentry-user-data")) { err = session_env_setenv (ctrl->session_env, "PINENTRY_USER_DATA", value); } else if (!strcmp (key, "use-cache-for-signing")) ctrl->server_local->use_cache_for_signing = *value? !!atoi (value) : 0; else if (!strcmp (key, "allow-pinentry-notify")) ctrl->server_local->allow_pinentry_notify = 1; else if (!strcmp (key, "pinentry-mode")) { int tmp = parse_pinentry_mode (value); if (tmp == -1) err = gpg_error (GPG_ERR_INV_VALUE); else if (tmp == PINENTRY_MODE_LOOPBACK && !opt.allow_loopback_pinentry) err = gpg_error (GPG_ERR_NOT_SUPPORTED); else ctrl->pinentry_mode = tmp; } else if (!strcmp (key, "cache-ttl-opt-preset")) { ctrl->cache_ttl_opt_preset = *value? atoi (value) : 0; } else if (!strcmp (key, "s2k-count")) { ctrl->s2k_count = *value? strtoul(value, NULL, 10) : 0; if (ctrl->s2k_count && ctrl->s2k_count < 65536) { ctrl->s2k_count = 0; } } else if (!strcmp (key, "pretend-request-origin")) { log_assert (!ctrl->restricted); switch (parse_request_origin (value)) { case REQUEST_ORIGIN_LOCAL: ctrl->restricted = 0; break; case REQUEST_ORIGIN_REMOTE: ctrl->restricted = 1; break; case REQUEST_ORIGIN_BROWSER: ctrl->restricted = 2; break; default: err = gpg_error (GPG_ERR_INV_VALUE); /* Better pretend to be remote in case of a bad value. */ ctrl->restricted = 1; break; } } else err = gpg_error (GPG_ERR_UNKNOWN_OPTION); return err; } /* Called by libassuan after all commands. ERR is the error from the last assuan operation and not the one returned from the command. */ static void post_cmd_notify (assuan_context_t ctx, gpg_error_t err) { ctrl_t ctrl = assuan_get_pointer (ctx); (void)err; /* Switch off any I/O monitor controlled logging pausing. */ ctrl->server_local->pause_io_logging = 0; } /* This function is called by libassuan for all I/O. We use it here to disable logging for the GETEVENTCOUNTER commands. This is so that the debug output won't get cluttered by this primitive command. */ static unsigned int io_monitor (assuan_context_t ctx, void *hook, int direction, const char *line, size_t linelen) { ctrl_t ctrl = assuan_get_pointer (ctx); (void) hook; /* We want to suppress all Assuan log messages for connections from * self. However, assuan_get_pid works only after * assuan_accept. Now, assuan_accept already logs a line ending with * the process id. We use this hack here to get the peers pid so * that we can compare it to our pid. We should add an assuan * function to return the pid for a file descriptor and use that to * detect connections to self. */ if (ctx && !ctrl->server_local->greeting_seen && direction == ASSUAN_IO_TO_PEER) { ctrl->server_local->greeting_seen = 1; if (linelen > 32 && !strncmp (line, "OK Pleased to meet you, process ", 32) && strtoul (line+32, NULL, 10) == getpid ()) return ASSUAN_IO_MONITOR_NOLOG; } /* Do not log self-connections. This makes the log cleaner because * we won't see the check-our-own-socket calls. */ if (ctx && ctrl->server_local->connect_from_self) return ASSUAN_IO_MONITOR_NOLOG; /* Note that we only check for the uppercase name. This allows the user to see the logging for debugging if using a non-upercase command name. */ if (ctx && direction == ASSUAN_IO_FROM_PEER && linelen >= 15 && !strncmp (line, "GETEVENTCOUNTER", 15) && (linelen == 15 || spacep (line+15))) { ctrl->server_local->pause_io_logging = 1; } return ctrl->server_local->pause_io_logging? ASSUAN_IO_MONITOR_NOLOG : 0; } /* Return true if the command CMD implements the option OPT. */ static int command_has_option (const char *cmd, const char *cmdopt) { if (!strcmp (cmd, "GET_PASSPHRASE")) { if (!strcmp (cmdopt, "repeat")) return 1; if (!strcmp (cmdopt, "newsymkey")) return 1; } return 0; } /* Tell Libassuan about our commands. Also register the other Assuan handlers. */ static int register_commands (assuan_context_t ctx) { static struct { const char *name; assuan_handler_t handler; const char * const help; } table[] = { { "GETEVENTCOUNTER",cmd_geteventcounter, hlp_geteventcounter }, { "ISTRUSTED", cmd_istrusted, hlp_istrusted }, { "HAVEKEY", cmd_havekey, hlp_havekey }, { "KEYINFO", cmd_keyinfo, hlp_keyinfo }, { "SIGKEY", cmd_sigkey, hlp_sigkey }, { "SETKEY", cmd_sigkey, hlp_sigkey }, { "SETKEYDESC", cmd_setkeydesc,hlp_setkeydesc }, { "SETHASH", cmd_sethash, hlp_sethash }, { "PKSIGN", cmd_pksign, hlp_pksign }, { "PKDECRYPT", cmd_pkdecrypt, hlp_pkdecrypt }, { "GENKEY", cmd_genkey, hlp_genkey }, { "READKEY", cmd_readkey, hlp_readkey }, { "GET_PASSPHRASE", cmd_get_passphrase, hlp_get_passphrase }, { "PRESET_PASSPHRASE", cmd_preset_passphrase, hlp_preset_passphrase }, { "CLEAR_PASSPHRASE", cmd_clear_passphrase, hlp_clear_passphrase }, { "GET_CONFIRMATION", cmd_get_confirmation, hlp_get_confirmation }, { "LISTTRUSTED", cmd_listtrusted, hlp_listtrusted }, { "MARKTRUSTED", cmd_marktrusted, hlp_martrusted }, { "LEARN", cmd_learn, hlp_learn }, { "PASSWD", cmd_passwd, hlp_passwd }, { "INPUT", NULL }, { "OUTPUT", NULL }, { "SCD", cmd_scd, hlp_scd }, { "KEYWRAP_KEY", cmd_keywrap_key, hlp_keywrap_key }, { "IMPORT_KEY", cmd_import_key, hlp_import_key }, { "EXPORT_KEY", cmd_export_key, hlp_export_key }, { "DELETE_KEY", cmd_delete_key, hlp_delete_key }, { "GETVAL", cmd_getval, hlp_getval }, { "PUTVAL", cmd_putval, hlp_putval }, { "UPDATESTARTUPTTY", cmd_updatestartuptty, hlp_updatestartuptty }, { "KILLAGENT", cmd_killagent, hlp_killagent }, { "RELOADAGENT", cmd_reloadagent,hlp_reloadagent }, { "GETINFO", cmd_getinfo, hlp_getinfo }, { "KEYTOCARD", cmd_keytocard, hlp_keytocard }, { NULL } }; int i, rc; for (i=0; table[i].name; i++) { rc = assuan_register_command (ctx, table[i].name, table[i].handler, table[i].help); if (rc) return rc; } assuan_register_post_cmd_notify (ctx, post_cmd_notify); assuan_register_reset_notify (ctx, reset_notify); assuan_register_option_handler (ctx, option_handler); return 0; } /* Startup the server. If LISTEN_FD and FD is given as -1, this is a simple piper server, otherwise it is a regular server. CTRL is the control structure for this connection; it has only the basic initialization. */ void start_command_handler (ctrl_t ctrl, gnupg_fd_t listen_fd, gnupg_fd_t fd) { int rc; assuan_context_t ctx = NULL; if (ctrl->restricted) { if (agent_copy_startup_env (ctrl)) return; } rc = assuan_new (&ctx); if (rc) { log_error ("failed to allocate assuan context: %s\n", gpg_strerror (rc)); agent_exit (2); } if (listen_fd == GNUPG_INVALID_FD && fd == GNUPG_INVALID_FD) { assuan_fd_t filedes[2]; filedes[0] = assuan_fdopen (0); filedes[1] = assuan_fdopen (1); rc = assuan_init_pipe_server (ctx, filedes); } else if (listen_fd != GNUPG_INVALID_FD) { rc = assuan_init_socket_server (ctx, listen_fd, 0); /* FIXME: Need to call assuan_sock_set_nonce for Windows. But this branch is currently not used. */ } else { rc = assuan_init_socket_server (ctx, fd, ASSUAN_SOCKET_SERVER_ACCEPTED); } if (rc) { log_error ("failed to initialize the server: %s\n", gpg_strerror(rc)); agent_exit (2); } rc = register_commands (ctx); if (rc) { log_error ("failed to register commands with Assuan: %s\n", gpg_strerror(rc)); agent_exit (2); } assuan_set_pointer (ctx, ctrl); ctrl->server_local = xcalloc (1, sizeof *ctrl->server_local); ctrl->server_local->assuan_ctx = ctx; ctrl->server_local->use_cache_for_signing = 1; ctrl->digest.raw_value = 0; assuan_set_io_monitor (ctx, io_monitor, NULL); agent_set_progress_cb (progress_cb, ctrl); for (;;) { pid_t client_pid; rc = assuan_accept (ctx); if (gpg_err_code (rc) == GPG_ERR_EOF || rc == -1) { break; } else if (rc) { log_info ("Assuan accept problem: %s\n", gpg_strerror (rc)); break; } client_pid = assuan_get_pid (ctx); ctrl->server_local->connect_from_self = (client_pid == getpid ()); if (client_pid != ASSUAN_INVALID_PID) ctrl->client_pid = (unsigned long)client_pid; else ctrl->client_pid = 0; rc = assuan_process (ctx); if (rc) { log_info ("Assuan processing failed: %s\n", gpg_strerror (rc)); continue; } } /* Reset the nonce caches. */ clear_nonce_cache (ctrl); /* Reset the SCD if needed. */ agent_reset_scd (ctrl); /* Reset the pinentry (in case of popup messages). */ agent_reset_query (ctrl); /* Cleanup. */ assuan_release (ctx); xfree (ctrl->server_local->keydesc); xfree (ctrl->server_local->import_key); xfree (ctrl->server_local->export_key); if (ctrl->server_local->stopme) agent_exit (0); xfree (ctrl->server_local); ctrl->server_local = NULL; } /* Helper for the pinentry loopback mode. It merely passes the parameters on to the client. */ gpg_error_t pinentry_loopback(ctrl_t ctrl, const char *keyword, unsigned char **buffer, size_t *size, size_t max_length) { gpg_error_t rc; assuan_context_t ctx = ctrl->server_local->assuan_ctx; rc = print_assuan_status (ctx, "INQUIRE_MAXLEN", "%zu", max_length); if (rc) return rc; assuan_begin_confidential (ctx); rc = assuan_inquire (ctx, keyword, buffer, size, max_length); assuan_end_confidential (ctx); return rc; } diff --git a/scd/command.c b/scd/command.c index fab65860b..925fd7597 100644 --- a/scd/command.c +++ b/scd/command.c @@ -1,2128 +1,2130 @@ /* command.c - SCdaemon command handler * Copyright (C) 2001, 2002, 2003, 2004, 2005, * 2007, 2008, 2009, 2011 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 3 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, see . */ #include #include #include #include #include #include #include #include #ifdef USE_NPTH # include #endif #include "scdaemon.h" #include #include #include "iso7816.h" #include "apdu.h" /* Required for apdu_*_reader (). */ #include "atr.h" #include "../common/asshelp.h" #include "../common/server-help.h" /* Maximum length allowed as a PIN; used for INQUIRE NEEDPIN */ #define MAXLEN_PIN 100 /* Maximum allowed size of key data as used in inquiries. */ #define MAXLEN_KEYDATA 4096 /* Maximum allowed total data size for SETDATA. */ #define MAXLEN_SETDATA 4096 /* Maximum allowed size of certificate data as used in inquiries. */ #define MAXLEN_CERTDATA 16384 #define set_error(e,t) assuan_set_error (ctx, gpg_error (e), (t)) #define IS_LOCKED(c) (locked_session && locked_session != (c)->server_local) /* Data used to associate an Assuan context with local server data. This object describes the local properties of one session. */ struct server_local_s { /* We keep a list of all active sessions with the anchor at SESSION_LIST (see below). This field is used for linking. */ struct server_local_s *next_session; /* This object is usually assigned to a CTRL object (which is globally visible). While enumerating all sessions we sometimes need to access data of the CTRL object; thus we keep a backpointer here. */ ctrl_t ctrl_backlink; /* The Assuan context used by this session/server. */ assuan_context_t assuan_ctx; #ifdef HAVE_W32_SYSTEM void *event_signal; /* Or NULL if not used. */ #else int event_signal; /* Or 0 if not used. */ #endif /* True if the card has been removed and a reset is required to continue operation. */ int card_removed; /* If set to true we will be terminate ourself at the end of the this session. */ int stopme; }; /* To keep track of all running sessions, we link all active server contexts and the anchor in this variable. */ static struct server_local_s *session_list; /* If a session has been locked we store a link to its server object in this variable. */ static struct server_local_s *locked_session; /* Convert the STRING into a newly allocated buffer while translating the hex numbers. Stops at the first invalid character. Blanks and colons are allowed to separate the hex digits. Returns NULL on error or a newly malloced buffer and its length in LENGTH. */ static unsigned char * hex_to_buffer (const char *string, size_t *r_length) { unsigned char *buffer; const char *s; size_t n; buffer = xtrymalloc (strlen (string)+1); if (!buffer) return NULL; for (s=string, n=0; *s; s++) { if (spacep (s) || *s == ':') continue; if (hexdigitp (s) && hexdigitp (s+1)) { buffer[n++] = xtoi_2 (s); s++; } else break; } *r_length = n; return buffer; } /* Reset the card and free the application context. With SEND_RESET set to true actually send a RESET to the reader; this is the normal way of calling the function. If KEEP_LOCK is set and the session is locked that lock wil not be released. */ static void do_reset (ctrl_t ctrl, int send_reset, int keep_lock) { app_t app = ctrl->app_ctx; if (app) app_reset (app, ctrl, IS_LOCKED (ctrl)? 0: send_reset); /* If we hold a lock, unlock now. */ if (!keep_lock && locked_session && ctrl->server_local == locked_session) { locked_session = NULL; log_info ("implicitly unlocking due to RESET\n"); } } static gpg_error_t reset_notify (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); do_reset (ctrl, 1, has_option (line, "--keep-lock")); return 0; } static gpg_error_t option_handler (assuan_context_t ctx, const char *key, const char *value) { ctrl_t ctrl = assuan_get_pointer (ctx); if (!strcmp (key, "event-signal")) { /* A value of 0 is allowed to reset the event signal. */ #ifdef HAVE_W32_SYSTEM if (!*value) return gpg_error (GPG_ERR_ASS_PARAMETER); #ifdef _WIN64 ctrl->server_local->event_signal = (void *)strtoull (value, NULL, 16); #else ctrl->server_local->event_signal = (void *)strtoul (value, NULL, 16); #endif #else int i = *value? atoi (value) : -1; if (i < 0) return gpg_error (GPG_ERR_ASS_PARAMETER); ctrl->server_local->event_signal = i; #endif } return 0; } /* If the card has not yet been opened, do it. */ static gpg_error_t open_card (ctrl_t ctrl) { /* If we ever got a card not present error code, return that. Only the SERIALNO command and a reset are able to clear from that state. */ if (ctrl->server_local->card_removed) return gpg_error (GPG_ERR_CARD_REMOVED); if ( IS_LOCKED (ctrl) ) return gpg_error (GPG_ERR_LOCKED); if (ctrl->app_ctx) return 0; return select_application (ctrl, NULL, &ctrl->app_ctx, 0, NULL, 0); } /* Explicitly open a card for a specific use of APPTYPE or SERIALNO. */ static gpg_error_t open_card_with_request (ctrl_t ctrl, const char *apptype, const char *serialno) { gpg_error_t err; unsigned char *serialno_bin = NULL; size_t serialno_bin_len = 0; app_t app = ctrl->app_ctx; /* If we are already initialized for one specific application we need to check that the client didn't requested a specific application different from the one in use before we continue. */ if (apptype && ctrl->app_ctx) return check_application_conflict (apptype, ctrl->app_ctx); /* Re-scan USB devices. Release APP, before the scan. */ ctrl->app_ctx = NULL; release_application (app, 0); if (serialno) serialno_bin = hex_to_buffer (serialno, &serialno_bin_len); err = select_application (ctrl, apptype, &ctrl->app_ctx, 1, serialno_bin, serialno_bin_len); xfree (serialno_bin); return err; } static const char hlp_serialno[] = "SERIALNO [--demand=] []\n" "\n" "Return the serial number of the card using a status response. This\n" "function should be used to check for the presence of a card.\n" "\n" "If --demand is given, an application on the card with SERIALNO is\n" "selected and an error is returned if no such card available.\n" "\n" "If APPTYPE is given, an application of that type is selected and an\n" "error is returned if the application is not supported or available.\n" "The default is to auto-select the application using a hardwired\n" "preference system. Note, that a future extension to this function\n" "may enable specifying a list and order of applications to try.\n" "\n" "This function is special in that it can be used to reset the card.\n" "Most other functions will return an error when a card change has\n" "been detected and the use of this function is therefore required.\n" "\n" "Background: We want to keep the client clear of handling card\n" "changes between operations; i.e. the client can assume that all\n" "operations are done on the same card unless he calls this function."; static gpg_error_t cmd_serialno (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); struct server_local_s *sl; int rc = 0; char *serial; const char *demand; if ( IS_LOCKED (ctrl) ) return gpg_error (GPG_ERR_LOCKED); if ((demand = has_option_name (line, "--demand"))) { if (*demand != '=') return set_error (GPG_ERR_ASS_PARAMETER, "missing value for option"); line = (char *)++demand; for (; *line && !spacep (line); line++) ; if (*line) *line++ = 0; } else demand = NULL; line = skip_options (line); /* Clear the remove flag so that the open_card is able to reread it. */ if (ctrl->server_local->card_removed) ctrl->server_local->card_removed = 0; if ((rc = open_card_with_request (ctrl, *line? line:NULL, demand))) { ctrl->server_local->card_removed = 1; return rc; } /* Success, clear the card_removed flag for all sessions. */ for (sl=session_list; sl; sl = sl->next_session) { ctrl_t c = sl->ctrl_backlink; if (c != ctrl) c->server_local->card_removed = 0; } serial = app_get_serialno (ctrl->app_ctx); if (!serial) return gpg_error (GPG_ERR_INV_VALUE); rc = assuan_write_status (ctx, "SERIALNO", serial); xfree (serial); return rc; } static const char hlp_learn[] = "LEARN [--force] [--keypairinfo]\n" "\n" "Learn all useful information of the currently inserted card. When\n" "used without the force options, the command might do an INQUIRE\n" "like this:\n" "\n" " INQUIRE KNOWNCARDP \n" "\n" "The client should just send an \"END\" if the processing should go on\n" "or a \"CANCEL\" to force the function to terminate with a Cancel\n" "error message.\n" "\n" "With the option --keypairinfo only KEYPARIINFO status lines are\n" "returned.\n" "\n" "The response of this command is a list of status lines formatted as\n" "this:\n" "\n" " S APPTYPE \n" "\n" "This returns the type of the application, currently the strings:\n" "\n" " P15 = PKCS-15 structure used\n" " DINSIG = DIN SIG\n" " OPENPGP = OpenPGP card\n" " PIV = PIV card\n" " NKS = NetKey card\n" "\n" "are implemented. These strings are aliases for the AID\n" "\n" " S KEYPAIRINFO \n" "\n" "If there is no certificate yet stored on the card a single 'X' is\n" "returned as the keygrip. In addition to the keypair info, information\n" "about all certificates stored on the card is also returned:\n" "\n" " S CERTINFO \n" "\n" "Where CERTTYPE is a number indicating the type of certificate:\n" " 0 := Unknown\n" " 100 := Regular X.509 cert\n" " 101 := Trusted X.509 cert\n" " 102 := Useful X.509 cert\n" " 110 := Root CA cert in a special format (e.g. DINSIG)\n" " 111 := Root CA cert as standard X509 cert.\n" "\n" "For certain cards, more information will be returned:\n" "\n" " S KEY-FPR \n" "\n" "For OpenPGP cards this returns the stored fingerprints of the\n" "keys. This can be used check whether a key is available on the\n" "card. NO may be 1, 2 or 3.\n" "\n" " S CA-FPR \n" "\n" "Similar to above, these are the fingerprints of keys assumed to be\n" "ultimately trusted.\n" "\n" " S DISP-NAME \n" "\n" "The name of the card holder as stored on the card; percent\n" "escaping takes place, spaces are encoded as '+'\n" "\n" " S PUBKEY-URL \n" "\n" "The URL to be used for locating the entire public key.\n" " \n" "Note, that this function may even be used on a locked card."; static gpg_error_t cmd_learn (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc = 0; int only_keypairinfo = has_option (line, "--keypairinfo"); if ((rc = open_card (ctrl))) return rc; /* Unless the force option is used we try a shortcut by identifying the card using a serial number and inquiring the client with that. The client may choose to cancel the operation if he already knows about this card */ if (!only_keypairinfo) { const char *reader; char *serial; app_t app = ctrl->app_ctx; if (!app) return gpg_error (GPG_ERR_CARD_NOT_PRESENT); reader = apdu_get_reader_name (app->slot); if (!reader) return out_of_core (); send_status_direct (ctrl, "READER", reader); /* No need to free the string of READER. */ serial = app_get_serialno (ctrl->app_ctx); if (!serial) return gpg_error (GPG_ERR_INV_VALUE); rc = assuan_write_status (ctx, "SERIALNO", serial); if (rc < 0) { xfree (serial); return out_of_core (); } if (!has_option (line, "--force")) { char *command; rc = gpgrt_asprintf (&command, "KNOWNCARDP %s", serial); if (rc < 0) { xfree (serial); return out_of_core (); } rc = assuan_inquire (ctx, command, NULL, NULL, 0); xfree (command); if (rc) { if (gpg_err_code (rc) != GPG_ERR_ASS_CANCELED) log_error ("inquire KNOWNCARDP failed: %s\n", gpg_strerror (rc)); xfree (serial); return rc; } /* Not canceled, so we have to proceed. */ } xfree (serial); } /* Let the application print out its collection of useful status information. */ if (!rc) rc = app_write_learn_status (ctrl->app_ctx, ctrl, only_keypairinfo); return rc; } static const char hlp_readcert[] = "READCERT |\n" "\n" "Note, that this function may even be used on a locked card."; static gpg_error_t cmd_readcert (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc; unsigned char *cert; size_t ncert; if ((rc = open_card (ctrl))) return rc; line = xstrdup (line); /* Need a copy of the line. */ rc = app_readcert (ctrl->app_ctx, ctrl, line, &cert, &ncert); if (rc) log_error ("app_readcert failed: %s\n", gpg_strerror (rc)); xfree (line); line = NULL; if (!rc) { rc = assuan_send_data (ctx, cert, ncert); xfree (cert); if (rc) return rc; } return rc; } static const char hlp_readkey[] = "READKEY [--advanced] [--info[-only]] \n" "\n" "Return the public key for the given cert or key ID as a standard\n" "S-expression. With --advanced the S-expression is returned in\n" "advanced format. With --info a KEYPAIRINFO status line is also\n" "emitted; with --info-only the regular output is suppressed."; static gpg_error_t cmd_readkey (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc; int advanced = 0; int opt_info = 0; int opt_nokey = 0; unsigned char *cert = NULL; size_t ncert; unsigned char *pk; size_t pklen; int direct_readkey = 0; if ((rc = open_card (ctrl))) return rc; if (has_option (line, "--advanced")) advanced = 1; if (has_option (line, "--info")) opt_info = 1; if (has_option (line, "--info-only")) opt_info = opt_nokey = 1; line = skip_options (line); line = xstrdup (line); /* Need a copy of the line. */ /* If the application supports the READKEY function we use that. Otherwise we use the old way by extracting it from the certificate. */ rc = app_readkey (ctrl->app_ctx, ctrl, advanced, line, &pk, &pklen); if (!rc) direct_readkey = 1; /* Yeah, got that key - send it back. */ else if (gpg_err_code (rc) == GPG_ERR_UNSUPPORTED_OPERATION || gpg_err_code (rc) == GPG_ERR_NOT_FOUND) { /* Fall back to certificate reading. */ rc = app_readcert (ctrl->app_ctx, ctrl, line, &cert, &ncert); if (rc) log_error ("app_readcert failed: %s\n", gpg_strerror (rc)); else { rc = app_help_pubkey_from_cert (cert, ncert, &pk, &pklen); if (rc) log_error ("failed to parse the certificate: %s\n", gpg_strerror (rc)); } } else log_error ("app_readkey failed: %s\n", gpg_strerror (rc)); if (!rc && pk && pklen && opt_info && !direct_readkey) { char keygripstr[KEYGRIP_LEN*2+1]; char *algostr; rc = app_help_get_keygrip_string_pk (pk, pklen, keygripstr, NULL, NULL, &algostr); if (rc) { log_error ("app_help_get_keygrip_string failed: %s\n", gpg_strerror (rc)); goto leave; } /* FIXME: Using LINE is not correct because it might be an * OID and has not been canonicalized (i.e. uppercased). */ send_status_info (ctrl, "KEYPAIRINFO", keygripstr, strlen (keygripstr), line, strlen (line), "-", (size_t)1, "-", (size_t)1, algostr, strlen (algostr), NULL, (size_t)0); xfree (algostr); } if (!rc && pk && pklen && !opt_nokey) rc = assuan_send_data (ctx, pk, pklen); leave: xfree (cert); xfree (pk); xfree (line); return rc; } static const char hlp_setdata[] = "SETDATA [--append] \n" "\n" "The client should use this command to tell us the data he want to sign.\n" "With the option --append, the data is appended to the data set by a\n" "previous SETDATA command."; static gpg_error_t cmd_setdata (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int append; int n, i, off; char *p; unsigned char *buf; append = (ctrl->in_data.value && has_option (line, "--append")); line = skip_options (line); if (locked_session && locked_session != ctrl->server_local) return gpg_error (GPG_ERR_LOCKED); /* Parse the hexstring. */ for (p=line,n=0; hexdigitp (p); p++, n++) ; if (*p) return set_error (GPG_ERR_ASS_PARAMETER, "invalid hexstring"); if (!n) return set_error (GPG_ERR_ASS_PARAMETER, "no data given"); if ((n&1)) return set_error (GPG_ERR_ASS_PARAMETER, "odd number of digits"); n /= 2; if (append) { if (ctrl->in_data.valuelen + n > MAXLEN_SETDATA) return set_error (GPG_ERR_TOO_LARGE, "limit on total size of data reached"); buf = xtrymalloc (ctrl->in_data.valuelen + n); } else buf = xtrymalloc (n); if (!buf) return out_of_core (); if (append) { memcpy (buf, ctrl->in_data.value, ctrl->in_data.valuelen); off = ctrl->in_data.valuelen; } else off = 0; for (p=line, i=0; i < n; p += 2, i++) buf[off+i] = xtoi_2 (p); xfree (ctrl->in_data.value); ctrl->in_data.value = buf; ctrl->in_data.valuelen = off+n; return 0; } static gpg_error_t pin_cb (void *opaque, const char *info, char **retstr) { assuan_context_t ctx = opaque; char *command; int rc; unsigned char *value; size_t valuelen; if (!retstr) { /* We prompt for pinpad entry. To make sure that the popup has been show we use an inquire and not just a status message. We ignore any value returned. */ if (info) { log_debug ("prompting for pinpad entry '%s'\n", info); rc = gpgrt_asprintf (&command, "POPUPPINPADPROMPT %s", info); if (rc < 0) return gpg_error (gpg_err_code_from_errno (errno)); rc = assuan_inquire (ctx, command, &value, &valuelen, MAXLEN_PIN); xfree (command); } else { log_debug ("dismiss pinpad entry prompt\n"); rc = assuan_inquire (ctx, "DISMISSPINPADPROMPT", &value, &valuelen, MAXLEN_PIN); } if (!rc) xfree (value); return rc; } *retstr = NULL; log_debug ("asking for PIN '%s'\n", info); rc = gpgrt_asprintf (&command, "NEEDPIN %s", info); if (rc < 0) return gpg_error (gpg_err_code_from_errno (errno)); /* Fixme: Write an inquire function which returns the result in secure memory and check all further handling of the PIN. */ + assuan_begin_confidential (ctx); rc = assuan_inquire (ctx, command, &value, &valuelen, MAXLEN_PIN); + assuan_end_confidential (ctx); xfree (command); if (rc) return rc; if (!valuelen || value[valuelen-1]) { /* We require that the returned value is an UTF-8 string */ xfree (value); return gpg_error (GPG_ERR_INV_RESPONSE); } *retstr = (char*)value; return 0; } static const char hlp_pksign[] = "PKSIGN [--hash=[rmd160|sha{1,224,256,384,512}|md5]] \n" "\n" "The --hash option is optional; the default is SHA1."; static gpg_error_t cmd_pksign (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc; unsigned char *outdata; size_t outdatalen; char *keyidstr; int hash_algo; if (has_option (line, "--hash=rmd160")) hash_algo = GCRY_MD_RMD160; else if (has_option (line, "--hash=sha1")) hash_algo = GCRY_MD_SHA1; else if (has_option (line, "--hash=sha224")) hash_algo = GCRY_MD_SHA224; else if (has_option (line, "--hash=sha256")) hash_algo = GCRY_MD_SHA256; else if (has_option (line, "--hash=sha384")) hash_algo = GCRY_MD_SHA384; else if (has_option (line, "--hash=sha512")) hash_algo = GCRY_MD_SHA512; else if (has_option (line, "--hash=md5")) hash_algo = GCRY_MD_MD5; else if (!strstr (line, "--")) hash_algo = GCRY_MD_SHA1; else return set_error (GPG_ERR_ASS_PARAMETER, "invalid hash algorithm"); line = skip_options (line); if ((rc = open_card (ctrl))) return rc; /* We have to use a copy of the key ID because the function may use the pin_cb which in turn uses the assuan line buffer and thus overwriting the original line with the keyid */ keyidstr = xtrystrdup (line); if (!keyidstr) return out_of_core (); rc = app_sign (ctrl->app_ctx, ctrl, keyidstr, hash_algo, pin_cb, ctx, ctrl->in_data.value, ctrl->in_data.valuelen, &outdata, &outdatalen); xfree (keyidstr); if (rc) { log_error ("app_sign failed: %s\n", gpg_strerror (rc)); } else { rc = assuan_send_data (ctx, outdata, outdatalen); xfree (outdata); if (rc) return rc; /* that is already an assuan error code */ } return rc; } static const char hlp_pkauth[] = "PKAUTH "; static gpg_error_t cmd_pkauth (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc; unsigned char *outdata; size_t outdatalen; char *keyidstr; if ((rc = open_card (ctrl))) return rc; if (!ctrl->app_ctx) return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); /* We have to use a copy of the key ID because the function may use the pin_cb which in turn uses the assuan line buffer and thus overwriting the original line with the keyid */ keyidstr = xtrystrdup (line); if (!keyidstr) return out_of_core (); rc = app_auth (ctrl->app_ctx, ctrl, keyidstr, pin_cb, ctx, ctrl->in_data.value, ctrl->in_data.valuelen, &outdata, &outdatalen); xfree (keyidstr); if (rc) { log_error ("app_auth failed: %s\n", gpg_strerror (rc)); } else { rc = assuan_send_data (ctx, outdata, outdatalen); xfree (outdata); if (rc) return rc; /* that is already an assuan error code */ } return rc; } static const char hlp_pkdecrypt[] = "PKDECRYPT "; static gpg_error_t cmd_pkdecrypt (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc; unsigned char *outdata; size_t outdatalen; char *keyidstr; unsigned int infoflags; if ((rc = open_card (ctrl))) return rc; keyidstr = xtrystrdup (line); if (!keyidstr) return out_of_core (); rc = app_decipher (ctrl->app_ctx, ctrl, keyidstr, pin_cb, ctx, ctrl->in_data.value, ctrl->in_data.valuelen, &outdata, &outdatalen, &infoflags); xfree (keyidstr); if (rc) { log_error ("app_decipher failed: %s\n", gpg_strerror (rc)); } else { /* If the card driver told us that there is no padding, send a status line. If there is a padding it is assumed that the caller knows what padding is used. It would have been better to always send that information but for backward compatibility we can't do that. */ if ((infoflags & APP_DECIPHER_INFO_NOPAD)) send_status_direct (ctrl, "PADDING", "0"); rc = assuan_send_data (ctx, outdata, outdatalen); xfree (outdata); if (rc) return rc; /* that is already an assuan error code */ } return rc; } static const char hlp_getattr[] = "GETATTR \n" "\n" "This command is used to retrieve data from a smartcard. The\n" "allowed names depend on the currently selected smartcard\n" "application. NAME must be percent and '+' escaped. The value is\n" "returned through status message, see the LEARN command for details.\n" "\n" "However, the current implementation assumes that Name is not escaped;\n" "this works as long as no one uses arbitrary escaping. \n" "\n" "Note, that this function may even be used on a locked card."; static gpg_error_t cmd_getattr (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc; const char *keyword; if ((rc = open_card (ctrl))) return rc; keyword = line; for (; *line && !spacep (line); line++) ; if (*line) *line++ = 0; /* (We ignore any garbage for now.) */ /* FIXME: Applications should not return sensitive data if the card is locked. */ rc = app_getattr (ctrl->app_ctx, ctrl, keyword); return rc; } static const char hlp_setattr[] = "SETATTR \n" "\n" "This command is used to store data on a smartcard. The allowed\n" "names and values are depend on the currently selected smartcard\n" "application. NAME and VALUE must be percent and '+' escaped.\n" "\n" "However, the current implementation assumes that NAME is not\n" "escaped; this works as long as no one uses arbitrary escaping.\n" "\n" "A PIN will be requested for most NAMEs. See the corresponding\n" "setattr function of the actually used application (app-*.c) for\n" "details."; static gpg_error_t cmd_setattr (assuan_context_t ctx, char *orig_line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc; char *keyword; int keywordlen; size_t nbytes; char *line, *linebuf; if ((rc = open_card (ctrl))) return rc; /* We need to use a copy of LINE, because PIN_CB uses the same context and thus reuses the Assuan provided LINE. */ line = linebuf = xtrystrdup (orig_line); if (!line) return out_of_core (); keyword = line; for (keywordlen=0; *line && !spacep (line); line++, keywordlen++) ; if (*line) *line++ = 0; while (spacep (line)) line++; nbytes = percent_plus_unescape_inplace (line, 0); rc = app_setattr (ctrl->app_ctx, ctrl, keyword, pin_cb, ctx, (const unsigned char*)line, nbytes); xfree (linebuf); return rc; } static const char hlp_writecert[] = "WRITECERT \n" "\n" "This command is used to store a certifciate on a smartcard. The\n" "allowed certids depend on the currently selected smartcard\n" "application. The actual certifciate is requested using the inquiry\n" "\"CERTDATA\" and needs to be provided in its raw (e.g. DER) form.\n" "\n" "In almost all cases a PIN will be requested. See the related\n" "writecert function of the actually used application (app-*.c) for\n" "details."; static gpg_error_t cmd_writecert (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc; char *certid; unsigned char *certdata; size_t certdatalen; line = skip_options (line); if (!*line) return set_error (GPG_ERR_ASS_PARAMETER, "no certid given"); certid = line; while (*line && !spacep (line)) line++; *line = 0; if ((rc = open_card (ctrl))) return rc; if (!ctrl->app_ctx) return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); certid = xtrystrdup (certid); if (!certid) return out_of_core (); /* Now get the actual keydata. */ rc = assuan_inquire (ctx, "CERTDATA", &certdata, &certdatalen, MAXLEN_CERTDATA); if (rc) { xfree (certid); return rc; } /* Write the certificate to the card. */ rc = app_writecert (ctrl->app_ctx, ctrl, certid, pin_cb, ctx, certdata, certdatalen); xfree (certid); xfree (certdata); return rc; } static const char hlp_writekey[] = "WRITEKEY [--force] \n" "\n" "This command is used to store a secret key on a smartcard. The\n" "allowed keyids depend on the currently selected smartcard\n" "application. The actual keydata is requested using the inquiry\n" "\"KEYDATA\" and need to be provided without any protection. With\n" "--force set an existing key under this KEYID will get overwritten.\n" "The keydata is expected to be the usual canonical encoded\n" "S-expression.\n" "\n" "A PIN will be requested for most NAMEs. See the corresponding\n" "writekey function of the actually used application (app-*.c) for\n" "details."; static gpg_error_t cmd_writekey (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc; char *keyid; int force = has_option (line, "--force"); unsigned char *keydata; size_t keydatalen; line = skip_options (line); if (!*line) return set_error (GPG_ERR_ASS_PARAMETER, "no keyid given"); keyid = line; while (*line && !spacep (line)) line++; *line = 0; if ((rc = open_card (ctrl))) return rc; if (!ctrl->app_ctx) return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); keyid = xtrystrdup (keyid); if (!keyid) return out_of_core (); /* Now get the actual keydata. */ assuan_begin_confidential (ctx); rc = assuan_inquire (ctx, "KEYDATA", &keydata, &keydatalen, MAXLEN_KEYDATA); assuan_end_confidential (ctx); if (rc) { xfree (keyid); return rc; } /* Write the key to the card. */ rc = app_writekey (ctrl->app_ctx, ctrl, keyid, force? 1:0, pin_cb, ctx, keydata, keydatalen); xfree (keyid); xfree (keydata); return rc; } static const char hlp_genkey[] = "GENKEY [--force] [--timestamp=] \n" "\n" "Generate a key on-card identified by NO, which is application\n" "specific. Return values are application specific. For OpenPGP\n" "cards 3 status lines are returned:\n" "\n" " S KEY-FPR \n" " S KEY-CREATED-AT \n" " S KEY-DATA [-|p|n] \n" "\n" " 'p' and 'n' are the names of the RSA parameters; '-' is used to\n" " indicate that HEXDATA is the first chunk of a parameter given\n" " by the next KEY-DATA.\n" "\n" "--force is required to overwrite an already existing key. The\n" "KEY-CREATED-AT is required for further processing because it is\n" "part of the hashed key material for the fingerprint.\n" "\n" "If --timestamp is given an OpenPGP key will be created using this\n" "value. The value needs to be in ISO Format; e.g.\n" "\"--timestamp=20030316T120000\" and after 1970-01-01 00:00:00.\n" "\n" "The public part of the key can also later be retrieved using the\n" "READKEY command."; static gpg_error_t cmd_genkey (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc; char *save_line; int force; const char *s; time_t timestamp; force = has_option (line, "--force"); if ((s=has_option_name (line, "--timestamp"))) { if (*s != '=') return set_error (GPG_ERR_ASS_PARAMETER, "missing value for option"); timestamp = isotime2epoch (s+1); if (timestamp < 1) return set_error (GPG_ERR_ASS_PARAMETER, "invalid time value"); } else timestamp = 0; line = skip_options (line); if (!*line) { rc = set_error (GPG_ERR_ASS_PARAMETER, "no key number given"); goto leave; } save_line = line; while (*line && !spacep (line)) line++; *line = 0; if ((rc = open_card (ctrl))) goto leave; if (!ctrl->app_ctx) { rc = gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); goto leave; } { char *tmp = xtrystrdup (save_line); if (!tmp) return gpg_error_from_syserror (); rc = app_genkey (ctrl->app_ctx, ctrl, tmp, NULL, force? APP_GENKEY_FLAG_FORCE : 0, timestamp, pin_cb, ctx); xfree (tmp); } leave: return rc; } static const char hlp_random[] = "RANDOM \n" "\n" "Get NBYTES of random from the card and send them back as data.\n" "This usually involves EEPROM write on the card and thus excessive\n" "use of this command may destroy the card.\n" "\n" "Note, that this function may be even be used on a locked card."; static gpg_error_t cmd_random (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc; size_t nbytes; unsigned char *buffer; if (!*line) return set_error (GPG_ERR_ASS_PARAMETER, "number of requested bytes missing"); nbytes = strtoul (line, NULL, 0); if ((rc = open_card (ctrl))) return rc; if (!ctrl->app_ctx) return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); buffer = xtrymalloc (nbytes); if (!buffer) return out_of_core (); rc = app_get_challenge (ctrl->app_ctx, ctrl, nbytes, buffer); if (!rc) { rc = assuan_send_data (ctx, buffer, nbytes); xfree (buffer); return rc; /* that is already an assuan error code */ } xfree (buffer); return rc; } static const char hlp_passwd[] = "PASSWD [--reset] [--nullpin] [--clear] \n" "\n" "Change the PIN or, if --reset is given, reset the retry counter of\n" "the card holder verification vector CHVNO. The option --nullpin is\n" "used for TCOS cards to set the initial PIN. The option --clear clears\n" "the security status associated with the PIN so that the PIN needs to\n" "be presented again. The format of CHVNO depends on the card application."; static gpg_error_t cmd_passwd (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc; char *chvnostr; unsigned int flags = 0; if (has_option (line, "--reset")) flags |= APP_CHANGE_FLAG_RESET; if (has_option (line, "--nullpin")) flags |= APP_CHANGE_FLAG_NULLPIN; if (has_option (line, "--clear")) flags |= APP_CHANGE_FLAG_CLEAR; line = skip_options (line); if (!*line) return set_error (GPG_ERR_ASS_PARAMETER, "no CHV number given"); chvnostr = line; while (*line && !spacep (line)) line++; *line = 0; /* Do not allow other flags aside of --clear. */ if ((flags & APP_CHANGE_FLAG_CLEAR) && (flags & ~APP_CHANGE_FLAG_CLEAR)) return set_error (GPG_ERR_UNSUPPORTED_OPERATION, "--clear used with other options"); if ((rc = open_card (ctrl))) return rc; if (!ctrl->app_ctx) return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); chvnostr = xtrystrdup (chvnostr); if (!chvnostr) return out_of_core (); rc = app_change_pin (ctrl->app_ctx, ctrl, chvnostr, flags, pin_cb, ctx); if (rc) log_error ("command passwd failed: %s\n", gpg_strerror (rc)); xfree (chvnostr); return rc; } static const char hlp_checkpin[] = "CHECKPIN \n" "\n" "Perform a VERIFY operation without doing anything else. This may\n" "be used to initialize a the PIN cache earlier to long lasting\n" "operations. Its use is highly application dependent.\n" "\n" "For OpenPGP:\n" "\n" " Perform a simple verify operation for CHV1 and CHV2, so that\n" " further operations won't ask for CHV2 and it is possible to do a\n" " cheap check on the PIN: If there is something wrong with the PIN\n" " entry system, only the regular CHV will get blocked and not the\n" " dangerous CHV3. IDSTR is the usual card's serial number in hex\n" " notation; an optional fingerprint part will get ignored. There\n" " is however a special mode if the IDSTR is suffixed with the\n" " literal string \"[CHV3]\": In this case the Admin PIN is checked\n" " if and only if the retry counter is still at 3.\n" "\n" "For Netkey:\n" "\n" " Any of the valid PIN Ids may be used. These are the strings:\n" "\n" " PW1.CH - Global password 1\n" " PW2.CH - Global password 2\n" " PW1.CH.SIG - SigG password 1\n" " PW2.CH.SIG - SigG password 2\n" "\n" " For a definitive list, see the implementation in app-nks.c.\n" " Note that we call a PW2.* PIN a \"PUK\" despite that since TCOS\n" " 3.0 they are technically alternative PINs used to mutally\n" " unblock each other."; static gpg_error_t cmd_checkpin (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc; char *idstr; if ((rc = open_card (ctrl))) return rc; if (!ctrl->app_ctx) return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); /* We have to use a copy of the key ID because the function may use the pin_cb which in turn uses the assuan line buffer and thus overwriting the original line with the keyid. */ idstr = xtrystrdup (line); if (!idstr) return out_of_core (); rc = app_check_pin (ctrl->app_ctx, ctrl, idstr, pin_cb, ctx); xfree (idstr); if (rc) log_error ("app_check_pin failed: %s\n", gpg_strerror (rc)); return rc; } static const char hlp_lock[] = "LOCK [--wait]\n" "\n" "Grant exclusive card access to this session. Note that there is\n" "no lock counter used and a second lock from the same session will\n" "be ignored. A single unlock (or RESET) unlocks the session.\n" "Return GPG_ERR_LOCKED if another session has locked the reader.\n" "\n" "If the option --wait is given the command will wait until a\n" "lock has been released."; static gpg_error_t cmd_lock (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc = 0; retry: if (locked_session) { if (locked_session != ctrl->server_local) rc = gpg_error (GPG_ERR_LOCKED); } else locked_session = ctrl->server_local; #ifdef USE_NPTH if (rc && has_option (line, "--wait")) { rc = 0; npth_sleep (1); /* Better implement an event mechanism. However, for card operations this should be sufficient. */ /* Send a progress so that we can detect a connection loss. */ rc = send_status_printf (ctrl, "PROGRESS", "scd_locked . 0 0"); if (!rc) goto retry; } #endif /*USE_NPTH*/ if (rc) log_error ("cmd_lock failed: %s\n", gpg_strerror (rc)); return rc; } static const char hlp_unlock[] = "UNLOCK\n" "\n" "Release exclusive card access."; static gpg_error_t cmd_unlock (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc = 0; (void)line; if (locked_session) { if (locked_session != ctrl->server_local) rc = gpg_error (GPG_ERR_LOCKED); else locked_session = NULL; } else rc = gpg_error (GPG_ERR_NOT_LOCKED); if (rc) log_error ("cmd_unlock failed: %s\n", gpg_strerror (rc)); return rc; } /* Ease reading of Assuan data ;ines by sending a physical line after * each LF. */ static gpg_error_t pretty_assuan_send_data (assuan_context_t ctx, const void *buffer_arg, size_t size) { const char *buffer = buffer_arg; const char *p; size_t n, nbytes; gpg_error_t err; nbytes = size; do { p = memchr (buffer, '\n', nbytes); n = p ? (p - buffer) + 1 : nbytes; err = assuan_send_data (ctx, buffer, n); if (err) { /* We also set ERRNO in case this function is used by a * custom estream I/O handler. */ gpg_err_set_errno (EIO); goto leave; } buffer += n; nbytes -= n; if (nbytes && (err=assuan_send_data (ctx, NULL, 0))) /* Flush line. */ { gpg_err_set_errno (EIO); goto leave; } } while (nbytes); leave: return err; } static const char hlp_getinfo[] = "GETINFO \n" "\n" "Multi purpose command to return certain information. \n" "Supported values of WHAT are:\n" "\n" " version - Return the version of the program.\n" " pid - Return the process id of the server.\n" " socket_name - Return the name of the socket.\n" " connections - Return number of active connections.\n" " status - Return the status of the current reader (in the future,\n" " may also return the status of all readers). The status\n" " is a list of one-character flags. The following flags\n" " are currently defined:\n" " 'u' Usable card present.\n" " 'r' Card removed. A reset is necessary.\n" " These flags are exclusive.\n" " reader_list - Return a list of detected card readers.\n" " deny_admin - Returns OK if admin commands are not allowed or\n" " GPG_ERR_GENERAL if admin commands are allowed.\n" " app_list - Return a list of supported applications. One\n" " application per line, fields delimited by colons,\n" " first field is the name.\n" " card_list - Return a list of serial numbers of active cards,\n" " using a status response."; static gpg_error_t cmd_getinfo (assuan_context_t ctx, char *line) { int rc = 0; if (!strcmp (line, "version")) { const char *s = VERSION; rc = assuan_send_data (ctx, s, strlen (s)); } else if (!strcmp (line, "pid")) { char numbuf[50]; snprintf (numbuf, sizeof numbuf, "%lu", (unsigned long)getpid ()); rc = assuan_send_data (ctx, numbuf, strlen (numbuf)); } else if (!strcmp (line, "socket_name")) { const char *s = scd_get_socket_name (); if (s) rc = assuan_send_data (ctx, s, strlen (s)); else rc = gpg_error (GPG_ERR_NO_DATA); } else if (!strcmp (line, "connections")) { char numbuf[20]; snprintf (numbuf, sizeof numbuf, "%d", get_active_connection_count ()); rc = assuan_send_data (ctx, numbuf, strlen (numbuf)); } else if (!strcmp (line, "status")) { ctrl_t ctrl = assuan_get_pointer (ctx); char flag; if (open_card (ctrl)) flag = 'r'; else flag = 'u'; rc = assuan_send_data (ctx, &flag, 1); } else if (!strcmp (line, "reader_list")) { char *s = apdu_get_reader_list (); if (s) rc = pretty_assuan_send_data (ctx, s, strlen (s)); else rc = gpg_error (GPG_ERR_NO_DATA); xfree (s); } else if (!strcmp (line, "deny_admin")) rc = opt.allow_admin? gpg_error (GPG_ERR_GENERAL) : 0; else if (!strcmp (line, "app_list")) { char *s = get_supported_applications (); if (s) rc = assuan_send_data (ctx, s, strlen (s)); else rc = 0; xfree (s); } else if (!strcmp (line, "card_list")) { ctrl_t ctrl = assuan_get_pointer (ctx); app_send_card_list (ctrl); } else rc = set_error (GPG_ERR_ASS_PARAMETER, "unknown value for WHAT"); return rc; } static const char hlp_restart[] = "RESTART\n" "\n" "Restart the current connection; this is a kind of warm reset. It\n" "deletes the context used by this connection but does not send a\n" "RESET to the card. Thus the card itself won't get reset. \n" "\n" "This is used by gpg-agent to reuse a primary pipe connection and\n" "may be used by clients to backup from a conflict in the serial\n" "command; i.e. to select another application."; static gpg_error_t cmd_restart (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); app_t app = ctrl->app_ctx; (void)line; if (app) { ctrl->app_ctx = NULL; release_application (app, 0); } if (locked_session && ctrl->server_local == locked_session) { locked_session = NULL; log_info ("implicitly unlocking due to RESTART\n"); } return 0; } static const char hlp_disconnect[] = "DISCONNECT\n" "\n" "Disconnect the card if the backend supports a disconnect operation."; static gpg_error_t cmd_disconnect (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); (void)line; if (!ctrl->app_ctx) return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); apdu_disconnect (ctrl->app_ctx->slot); return 0; } static const char hlp_apdu[] = "APDU [--[dump-]atr] [--more] [--exlen[=N]] [hexstring]\n" "\n" "Send an APDU to the current reader. This command bypasses the high\n" "level functions and sends the data directly to the card. HEXSTRING\n" "is expected to be a proper APDU. If HEXSTRING is not given no\n" "commands are set to the card but the command will implictly check\n" "whether the card is ready for use. \n" "\n" "Using the option \"--atr\" returns the ATR of the card as a status\n" "message before any data like this:\n" " S CARD-ATR 3BFA1300FF813180450031C173C00100009000B1\n" "\n" "Using the option --more handles the card status word MORE_DATA\n" "(61xx) and concatenates all responses to one block.\n" "\n" "Using the option \"--exlen\" the returned APDU may use extended\n" "length up to N bytes. If N is not given a default value is used\n" "(currently 4096)."; static gpg_error_t cmd_apdu (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); app_t app; int rc; unsigned char *apdu; size_t apdulen; int with_atr; int handle_more; const char *s; size_t exlen; if (has_option (line, "--dump-atr")) with_atr = 2; else with_atr = has_option (line, "--atr"); handle_more = has_option (line, "--more"); if ((s=has_option_name (line, "--exlen"))) { if (*s == '=') exlen = strtoul (s+1, NULL, 0); else exlen = 4096; } else exlen = 0; line = skip_options (line); if ((rc = open_card (ctrl))) return rc; app = ctrl->app_ctx; if (!app) return gpg_error (GPG_ERR_CARD_NOT_PRESENT); if (with_atr) { unsigned char *atr; size_t atrlen; char hexbuf[400]; atr = apdu_get_atr (app->slot, &atrlen); if (!atr || atrlen > sizeof hexbuf - 2 ) { rc = gpg_error (GPG_ERR_INV_CARD); goto leave; } if (with_atr == 2) { char *string, *p, *pend; string = atr_dump (atr, atrlen); if (string) { for (rc=0, p=string; !rc && (pend = strchr (p, '\n')); p = pend+1) { rc = assuan_send_data (ctx, p, pend - p + 1); if (!rc) rc = assuan_send_data (ctx, NULL, 0); } if (!rc && *p) rc = assuan_send_data (ctx, p, strlen (p)); es_free (string); if (rc) goto leave; } } else { bin2hex (atr, atrlen, hexbuf); send_status_info (ctrl, "CARD-ATR", hexbuf, strlen (hexbuf), NULL, 0); } xfree (atr); } apdu = hex_to_buffer (line, &apdulen); if (!apdu) { rc = gpg_error_from_syserror (); goto leave; } if (apdulen) { unsigned char *result = NULL; size_t resultlen; rc = apdu_send_direct (app->slot, exlen, apdu, apdulen, handle_more, NULL, &result, &resultlen); if (rc) log_error ("apdu_send_direct failed: %s\n", gpg_strerror (rc)); else { rc = assuan_send_data (ctx, result, resultlen); xfree (result); } } xfree (apdu); leave: return rc; } static const char hlp_killscd[] = "KILLSCD\n" "\n" "Commit suicide."; static gpg_error_t cmd_killscd (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); (void)line; ctrl->server_local->stopme = 1; assuan_set_flag (ctx, ASSUAN_FORCE_CLOSE, 1); return 0; } /* Tell the assuan library about our commands */ static int register_commands (assuan_context_t ctx) { static struct { const char *name; assuan_handler_t handler; const char * const help; } table[] = { { "SERIALNO", cmd_serialno, hlp_serialno }, { "LEARN", cmd_learn, hlp_learn }, { "READCERT", cmd_readcert, hlp_readcert }, { "READKEY", cmd_readkey, hlp_readkey }, { "SETDATA", cmd_setdata, hlp_setdata }, { "PKSIGN", cmd_pksign, hlp_pksign }, { "PKAUTH", cmd_pkauth, hlp_pkauth }, { "PKDECRYPT", cmd_pkdecrypt,hlp_pkdecrypt }, { "INPUT", NULL }, { "OUTPUT", NULL }, { "GETATTR", cmd_getattr, hlp_getattr }, { "SETATTR", cmd_setattr, hlp_setattr }, { "WRITECERT", cmd_writecert,hlp_writecert }, { "WRITEKEY", cmd_writekey, hlp_writekey }, { "GENKEY", cmd_genkey, hlp_genkey }, { "RANDOM", cmd_random, hlp_random }, { "PASSWD", cmd_passwd, hlp_passwd }, { "CHECKPIN", cmd_checkpin, hlp_checkpin }, { "LOCK", cmd_lock, hlp_lock }, { "UNLOCK", cmd_unlock, hlp_unlock }, { "GETINFO", cmd_getinfo, hlp_getinfo }, { "RESTART", cmd_restart, hlp_restart }, { "DISCONNECT", cmd_disconnect,hlp_disconnect }, { "APDU", cmd_apdu, hlp_apdu }, { "KILLSCD", cmd_killscd, hlp_killscd }, { NULL } }; int i, rc; for (i=0; table[i].name; i++) { rc = assuan_register_command (ctx, table[i].name, table[i].handler, table[i].help); if (rc) return rc; } assuan_set_hello_line (ctx, "GNU Privacy Guard's Smartcard server ready"); assuan_register_reset_notify (ctx, reset_notify); assuan_register_option_handler (ctx, option_handler); return 0; } /* Startup the server. If FD is given as -1 this is simple pipe server, otherwise it is a regular server. Returns true if there are no more active asessions. */ int scd_command_handler (ctrl_t ctrl, int fd) { int rc; assuan_context_t ctx = NULL; int stopme; rc = assuan_new (&ctx); if (rc) { log_error ("failed to allocate assuan context: %s\n", gpg_strerror (rc)); scd_exit (2); } if (fd == -1) { assuan_fd_t filedes[2]; filedes[0] = assuan_fdopen (0); filedes[1] = assuan_fdopen (1); rc = assuan_init_pipe_server (ctx, filedes); } else { rc = assuan_init_socket_server (ctx, INT2FD(fd), ASSUAN_SOCKET_SERVER_ACCEPTED); } if (rc) { log_error ("failed to initialize the server: %s\n", gpg_strerror(rc)); scd_exit (2); } rc = register_commands (ctx); if (rc) { log_error ("failed to register commands with Assuan: %s\n", gpg_strerror(rc)); scd_exit (2); } assuan_set_pointer (ctx, ctrl); /* Allocate and initialize the server object. Put it into the list of active sessions. */ ctrl->server_local = xcalloc (1, sizeof *ctrl->server_local); ctrl->server_local->next_session = session_list; session_list = ctrl->server_local; ctrl->server_local->ctrl_backlink = ctrl; ctrl->server_local->assuan_ctx = ctx; /* Command processing loop. */ for (;;) { rc = assuan_accept (ctx); if (rc == -1) { break; } else if (rc) { log_info ("Assuan accept problem: %s\n", gpg_strerror (rc)); break; } rc = assuan_process (ctx); if (rc) { log_info ("Assuan processing failed: %s\n", gpg_strerror (rc)); continue; } } /* Cleanup. We don't send an explicit reset to the card. */ do_reset (ctrl, 0, 0); /* Release the server object. */ if (session_list == ctrl->server_local) session_list = ctrl->server_local->next_session; else { struct server_local_s *sl; for (sl=session_list; sl->next_session; sl = sl->next_session) if (sl->next_session == ctrl->server_local) break; if (!sl->next_session) BUG (); sl->next_session = ctrl->server_local->next_session; } stopme = ctrl->server_local->stopme; xfree (ctrl->server_local); ctrl->server_local = NULL; /* Release the Assuan context. */ assuan_release (ctx); if (stopme) scd_exit (0); /* If there are no more sessions return true. */ return !session_list; } /* Send a keyinfo string. If DATA is true the string is emitted as a * data line, else as a status line. */ void send_keyinfo (ctrl_t ctrl, int data, const char *keygrip_str, const char *serialno, const char *idstr) { char *string; assuan_context_t ctx = ctrl->server_local->assuan_ctx; string = xtryasprintf ("%s T %s %s%s", keygrip_str, serialno? serialno : "-", idstr? idstr : "-", data? "\n" : ""); if (!string) return; if (!data) assuan_write_status (ctx, "KEYINFO", string); else assuan_send_data (ctx, string, strlen (string)); xfree (string); return; } /* Send a line with status information via assuan and escape all given buffers. The variable elements are pairs of (char *, size_t), terminated with a (NULL, 0). */ void send_status_info (ctrl_t ctrl, const char *keyword, ...) { va_list arg_ptr; const unsigned char *value; size_t valuelen; char buf[950], *p; size_t n; assuan_context_t ctx = ctrl->server_local->assuan_ctx; va_start (arg_ptr, keyword); p = buf; n = 0; while ( (value = va_arg (arg_ptr, const unsigned char *)) && n < DIM (buf)-2 ) { valuelen = va_arg (arg_ptr, size_t); if (!valuelen) continue; /* empty buffer */ if (n) { *p++ = ' '; n++; } for ( ; valuelen && n < DIM (buf)-2; n++, valuelen--, value++) { if (*value == '+' || *value == '\"' || *value == '%' || *value < ' ') { sprintf (p, "%%%02X", *value); p += 3; n += 2; } else if (*value == ' ') *p++ = '+'; else *p++ = *value; } } *p = 0; assuan_write_status (ctx, keyword, buf); va_end (arg_ptr); } /* Send a ready formatted status line via assuan. */ gpg_error_t send_status_direct (ctrl_t ctrl, const char *keyword, const char *args) { assuan_context_t ctx = ctrl->server_local->assuan_ctx; if (strchr (args, '\n')) { log_error ("error: LF detected in status line - not sending\n"); return gpg_error (GPG_ERR_INTERNAL); } return assuan_write_status (ctx, keyword, args); } /* This status functions expects a printf style format string. No * filtering of the data is done instead the orintf formatted data is * send using assuan_send_status. */ gpg_error_t send_status_printf (ctrl_t ctrl, const char *keyword, const char *format, ...) { gpg_error_t err; va_list arg_ptr; assuan_context_t ctx; if (!ctrl || !ctrl->server_local || !(ctx = ctrl->server_local->assuan_ctx)) return 0; va_start (arg_ptr, format); err = vprint_assuan_status (ctx, keyword, format, arg_ptr); va_end (arg_ptr); return err; } void popup_prompt (void *opaque, int on) { ctrl_t ctrl = opaque; if (ctrl) { assuan_context_t ctx = ctrl->server_local->assuan_ctx; if (ctx) { const char *cmd; gpg_error_t err; unsigned char *value; size_t valuelen; if (on) cmd = "POPUPPINPADPROMPT --ack"; else cmd = "DISMISSPINPADPROMPT"; err = assuan_inquire (ctx, cmd, &value, &valuelen, 100); if (!err) xfree (value); } } } /* Helper to send the clients a status change notification. */ void send_client_notifications (app_t app, int removal) { struct { pid_t pid; #ifdef HAVE_W32_SYSTEM HANDLE handle; #else int signo; #endif } killed[50]; int killidx = 0; int kidx; struct server_local_s *sl; for (sl=session_list; sl; sl = sl->next_session) if (sl->ctrl_backlink && sl->ctrl_backlink->app_ctx == app) { pid_t pid; #ifdef HAVE_W32_SYSTEM HANDLE handle; #else int signo; #endif if (removal) { sl->ctrl_backlink->app_ctx = NULL; sl->card_removed = 1; release_application (app, 1); } if (!sl->event_signal || !sl->assuan_ctx) continue; pid = assuan_get_pid (sl->assuan_ctx); #ifdef HAVE_W32_SYSTEM handle = sl->event_signal; for (kidx=0; kidx < killidx; kidx++) if (killed[kidx].pid == pid && killed[kidx].handle == handle) break; if (kidx < killidx) log_info ("event %p (%p) already triggered for client %d\n", sl->event_signal, handle, (int)pid); else { log_info ("triggering event %p (%p) for client %d\n", sl->event_signal, handle, (int)pid); if (!SetEvent (handle)) log_error ("SetEvent(%p) failed: %s\n", sl->event_signal, w32_strerror (-1)); if (killidx < DIM (killed)) { killed[killidx].pid = pid; killed[killidx].handle = handle; killidx++; } } #else /*!HAVE_W32_SYSTEM*/ signo = sl->event_signal; if (pid != (pid_t)(-1) && pid && signo > 0) { for (kidx=0; kidx < killidx; kidx++) if (killed[kidx].pid == pid && killed[kidx].signo == signo) break; if (kidx < killidx) log_info ("signal %d already sent to client %d\n", signo, (int)pid); else { log_info ("sending signal %d to client %d\n", signo, (int)pid); kill (pid, signo); if (killidx < DIM (killed)) { killed[killidx].pid = pid; killed[killidx].signo = signo; killidx++; } } } #endif /*!HAVE_W32_SYSTEM*/ } }