diff --git a/agent/call-pinentry.c b/agent/call-pinentry.c index a35a3fbff..6524cb1b6 100644 --- a/agent/call-pinentry.c +++ b/agent/call-pinentry.c @@ -1,1580 +1,1580 @@ /* call-pinentry.c - Spawn the pinentry to query stuff from the user * Copyright (C) 2001, 2002, 2004, 2007, 2008, * 2010 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 #include #ifndef HAVE_W32_SYSTEM # include # include # include # include #endif #include #include "agent.h" #include #include "../common/sysutils.h" #include "../common/i18n.h" #ifdef _POSIX_OPEN_MAX #define MAX_OPEN_FDS _POSIX_OPEN_MAX #else #define MAX_OPEN_FDS 20 #endif /* Because access to the pinentry must be serialized (it is and shall be a global mutually exclusive dialog) we better timeout pending requests after some time. 1 minute seem to be a reasonable time. */ #define LOCK_TIMEOUT (1*60) /* The assuan context of the current pinentry. */ static assuan_context_t entry_ctx; /* A list of features of the current pinentry. */ static struct { /* The Pinentry support RS+US tabbing. This means that a RS (0x1e) * starts a new tabbing block in which a US (0x1f) followed by a * colon marks a colon. A pinentry can use this to pretty print * name value pairs. */ unsigned int tabbing:1; } entry_features; /* The control variable of the connection owning the current pinentry. This is only valid if ENTRY_CTX is not NULL. Note, that we care only about the value of the pointer and that it should never be dereferenced. */ static ctrl_t entry_owner; /* A mutex used to serialize access to the pinentry. */ static npth_mutex_t entry_lock; /* The thread ID of the popup working thread. */ static npth_t popup_tid; /* A flag used in communication between the popup working thread and its stop function. */ static int popup_finished; /* Data to be passed to our callbacks, */ struct entry_parm_s { int lines; size_t size; unsigned char *buffer; }; /* 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 Pth emulation code might not be able to do a static init; in particular, it is not possible for W32. */ void initialize_module_call_pinentry (void) { static int initialized; if (!initialized) { if (npth_mutex_init (&entry_lock, NULL)) initialized = 1; } } /* This function may be called to print infromation pertaining to the current state of this module to the log. */ void agent_query_dump_state (void) { log_info ("agent_query_dump_state: entry_ctx=%p pid=%ld popup_tid=%p\n", entry_ctx, (long)assuan_get_pid (entry_ctx), (void*)popup_tid); } /* Called to make sure that a popup window owned by the current connection gets closed. */ void agent_reset_query (ctrl_t ctrl) { if (entry_ctx && popup_tid && entry_owner == ctrl) { agent_popup_message_stop (ctrl); } } /* Unlock the pinentry so that another thread can start one and disconnect that pinentry - we do this after the unlock so that a stalled pinentry does not block other threads. Fixme: We should have a timeout in Assuan for the disconnect operation. */ static gpg_error_t unlock_pinentry (gpg_error_t rc) { assuan_context_t ctx = entry_ctx; int err; if (rc) { if (DBG_IPC) log_debug ("error calling pinentry: %s <%s>\n", gpg_strerror (rc), gpg_strsource (rc)); /* Change the source of the error to pinentry so that the final consumer of the error code knows that the problem is with pinentry. For backward compatibility we do not do that for some common error codes. */ switch (gpg_err_code (rc)) { case GPG_ERR_NO_PIN_ENTRY: case GPG_ERR_CANCELED: case GPG_ERR_FULLY_CANCELED: case GPG_ERR_ASS_UNKNOWN_INQUIRE: case GPG_ERR_ASS_TOO_MUCH_DATA: case GPG_ERR_NO_PASSPHRASE: case GPG_ERR_BAD_PASSPHRASE: case GPG_ERR_BAD_PIN: break; default: rc = gpg_err_make (GPG_ERR_SOURCE_PINENTRY, gpg_err_code (rc)); break; } } entry_ctx = NULL; err = npth_mutex_unlock (&entry_lock); if (err) { log_error ("failed to release the entry lock: %s\n", strerror (err)); if (!rc) rc = gpg_error_from_errno (err); } assuan_release (ctx); return rc; } /* To make sure we leave no secrets in our image after forking of the pinentry, we use this callback. */ static void atfork_cb (void *opaque, int where) { ctrl_t ctrl = opaque; if (!where) { int iterator = 0; const char *name, *assname, *value; gcry_control (GCRYCTL_TERM_SECMEM); while ((name = session_env_list_stdenvnames (&iterator, &assname))) { /* For all new envvars (!ASSNAME) and the two medium old ones which do have an assuan name but are conveyed using environment variables, update the environment of the forked process. */ if (!assname || !strcmp (name, "XAUTHORITY") || !strcmp (name, "PINENTRY_USER_DATA")) { value = session_env_getenv (ctrl->session_env, name); if (value) gnupg_setenv (name, value, 1); } } } } /* Status line callback for the FEATURES status. */ static gpg_error_t getinfo_features_cb (void *opaque, const char *line) { const char *args; char **tokens; int i; (void)opaque; if ((args = has_leading_keyword (line, "FEATURES"))) { tokens = strtokenize (args, " "); if (!tokens) return gpg_error_from_syserror (); for (i=0; tokens[i]; i++) if (!strcmp (tokens[i], "tabbing")) entry_features.tabbing = 1; xfree (tokens); } return 0; } static gpg_error_t getinfo_pid_cb (void *opaque, const void *buffer, size_t length) { unsigned long *pid = opaque; char pidbuf[50]; /* There is only the pid in the server's response. */ if (length >= sizeof pidbuf) length = sizeof pidbuf -1; if (length) { strncpy (pidbuf, buffer, length); pidbuf[length] = 0; *pid = strtoul (pidbuf, NULL, 10); } return 0; } /* Fork off the pin entry if this has not already been done. Note, that this function must always be used to acquire the lock for the pinentry - we will serialize _all_ pinentry calls. */ static gpg_error_t start_pinentry (ctrl_t ctrl) { int rc = 0; const char *full_pgmname; const char *pgmname; assuan_context_t ctx; const char *argv[5]; assuan_fd_t no_close_list[3]; int i; const char *tmpstr; unsigned long pinentry_pid; const char *value; struct timespec abstime; char *flavor_version; int err; npth_clock_gettime (&abstime); abstime.tv_sec += LOCK_TIMEOUT; err = npth_mutex_timedlock (&entry_lock, &abstime); if (err) { if (err == ETIMEDOUT) rc = gpg_error (GPG_ERR_TIMEOUT); else rc = gpg_error_from_errno (rc); log_error (_("failed to acquire the pinentry lock: %s\n"), gpg_strerror (rc)); return rc; } entry_owner = ctrl; if (entry_ctx) return 0; if (opt.verbose) log_info ("starting a new PIN Entry\n"); #ifdef HAVE_W32_SYSTEM fflush (stdout); fflush (stderr); #endif if (fflush (NULL)) { #ifndef HAVE_W32_SYSTEM gpg_error_t tmperr = gpg_error (gpg_err_code_from_errno (errno)); #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 Wine implementaion does not flush stdin,stdout and stderr - see above. Let's try to ignore the error. */ #ifndef HAVE_W32_SYSTEM return unlock_pinentry (tmperr); #endif } full_pgmname = opt.pinentry_program; if (!full_pgmname || !*full_pgmname) full_pgmname = gnupg_module_name (GNUPG_MODULE_NAME_PINENTRY); if ( !(pgmname = strrchr (full_pgmname, '/'))) pgmname = full_pgmname; else pgmname++; /* OS X needs the entire file name in argv[0], so that it can locate the resource bundle. For other systems we stick to the usual convention of supplying only the name of the program. */ #ifdef __APPLE__ argv[0] = full_pgmname; #else /*!__APPLE__*/ argv[0] = pgmname; #endif /*__APPLE__*/ if (!opt.keep_display && (value = session_env_getenv (ctrl->session_env, "DISPLAY"))) { argv[1] = "--display"; argv[2] = value; argv[3] = NULL; } else argv[1] = 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; rc = assuan_new (&ctx); if (rc) { log_error ("can't allocate assuan context: %s\n", gpg_strerror (rc)); return rc; } /* We don't want to log the pinentry communication to make the logs easier to read. We might want to add a new debug option to enable pinentry logging. */ #ifdef ASSUAN_NO_LOGGING assuan_set_flag (ctx, ASSUAN_NO_LOGGING, !opt.debug_pinentry); #endif /* Connect to the pinentry and perform initial handshaking. Note that atfork is used to change the environment for pinentry. We start the server in detached mode to suppress the console window under Windows. */ rc = assuan_pipe_connect (ctx, full_pgmname, argv, no_close_list, atfork_cb, ctrl, ASSUAN_PIPE_CONNECT_DETACHED); if (rc) { log_error ("can't connect to the PIN entry module '%s': %s\n", full_pgmname, gpg_strerror (rc)); assuan_release (ctx); return unlock_pinentry (gpg_error (GPG_ERR_NO_PIN_ENTRY)); } entry_ctx = ctx; if (DBG_IPC) log_debug ("connection to PIN entry established\n"); value = session_env_getenv (ctrl->session_env, "PINENTRY_USER_DATA"); if (value != NULL) { char *optstr; if (asprintf (&optstr, "OPTION pinentry-user-data=%s", value) < 0 ) return unlock_pinentry (out_of_core ()); rc = assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL, NULL); xfree (optstr); if (rc && gpg_err_code (rc) != GPG_ERR_UNKNOWN_OPTION) return unlock_pinentry (rc); } rc = assuan_transact (entry_ctx, opt.no_grab? "OPTION no-grab":"OPTION grab", NULL, NULL, NULL, NULL, NULL, NULL); if (rc) return unlock_pinentry (rc); value = session_env_getenv (ctrl->session_env, "GPG_TTY"); if (value) { char *optstr; if (asprintf (&optstr, "OPTION ttyname=%s", value) < 0 ) return unlock_pinentry (out_of_core ()); rc = assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL, NULL); xfree (optstr); if (rc) return unlock_pinentry (rc); } value = session_env_getenv (ctrl->session_env, "TERM"); if (value) { char *optstr; if (asprintf (&optstr, "OPTION ttytype=%s", value) < 0 ) return unlock_pinentry (out_of_core ()); rc = assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL, NULL); xfree (optstr); if (rc) return unlock_pinentry (rc); } if (ctrl->lc_ctype) { char *optstr; if (asprintf (&optstr, "OPTION lc-ctype=%s", ctrl->lc_ctype) < 0 ) return unlock_pinentry (out_of_core ()); rc = assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL, NULL); xfree (optstr); if (rc) return unlock_pinentry (rc); } if (ctrl->lc_messages) { char *optstr; if (asprintf (&optstr, "OPTION lc-messages=%s", ctrl->lc_messages) < 0 ) return unlock_pinentry (out_of_core ()); rc = assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL, NULL); xfree (optstr); if (rc) return unlock_pinentry (rc); } if (opt.allow_external_cache) { /* Indicate to the pinentry that it may read from an external cache. It is essential that the pinentry respect this. If the cached password is not up to date and retry == 1, then, using a version of GPG Agent that doesn't support this, won't issue another pin request and the user won't get a chance to correct the password. */ rc = assuan_transact (entry_ctx, "OPTION allow-external-password-cache", NULL, NULL, NULL, NULL, NULL, NULL); if (rc && gpg_err_code (rc) != GPG_ERR_UNKNOWN_OPTION) return unlock_pinentry (rc); } if (opt.allow_emacs_pinentry) { /* Indicate to the pinentry that it may read passphrase through Emacs minibuffer, if possible. */ rc = assuan_transact (entry_ctx, "OPTION allow-emacs-prompt", NULL, NULL, NULL, NULL, NULL, NULL); if (rc && gpg_err_code (rc) != GPG_ERR_UNKNOWN_OPTION) return unlock_pinentry (rc); } { /* Provide a few default strings for use by the pinentries. This may help a pinentry to avoid implementing localization code. */ static struct { const char *key, *value; int what; } tbl[] = { /* TRANSLATORS: These are labels for buttons etc used in Pinentries. An underscore indicates that the next letter should be used as an accelerator. Double the underscore for a literal one. The actual to be translated text starts after the second vertical bar. Note that gpg-agent has been set to utf-8 so that the strings are in the expected encoding. */ { "ok", N_("|pinentry-label|_OK") }, { "cancel", N_("|pinentry-label|_Cancel") }, { "yes", N_("|pinentry-label|_Yes") }, { "no", N_("|pinentry-label|_No") }, { "prompt", N_("|pinentry-label|PIN:") }, { "pwmngr", N_("|pinentry-label|_Save in password manager"), 1 }, { "cf-visi",N_("Do you really want to make your " "passphrase visible on the screen?") }, { "tt-visi",N_("|pinentry-tt|Make passphrase visible") }, { "tt-hide",N_("|pinentry-tt|Hide passphrase") }, { NULL, NULL} }; char *optstr; int idx; const char *s, *s2; for (idx=0; tbl[idx].key; idx++) { if (!opt.allow_external_cache && tbl[idx].what == 1) continue; /* No need for it. */ s = L_(tbl[idx].value); if (*s == '|' && (s2=strchr (s+1,'|'))) s = s2+1; if (asprintf (&optstr, "OPTION default-%s=%s", tbl[idx].key, s) < 0 ) return unlock_pinentry (out_of_core ()); assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL, NULL); xfree (optstr); } } /* Tell the pinentry that we would prefer that the given character is used as the invisible character by the entry widget. */ if (opt.pinentry_invisible_char) { char *optstr; if ((optstr = xtryasprintf ("OPTION invisible-char=%s", opt.pinentry_invisible_char))) { assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL, NULL); /* We ignore errors because this is just a fancy thing and older pinentries do not support this feature. */ xfree (optstr); } } if (opt.pinentry_timeout) { char *optstr; if ((optstr = xtryasprintf ("SETTIMEOUT %lu", opt.pinentry_timeout))) { assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL, NULL); /* We ignore errors because this is just a fancy thing. */ xfree (optstr); } } /* Tell the pinentry the name of a file it shall touch after having messed with the tty. This is optional and only supported by newer pinentries and thus we do no error checking. */ tmpstr = opt.pinentry_touch_file; if (tmpstr && !strcmp (tmpstr, "/dev/null")) tmpstr = NULL; else if (!tmpstr) tmpstr = get_agent_socket_name (); if (tmpstr) { char *optstr; if (asprintf (&optstr, "OPTION touch-file=%s", tmpstr ) < 0 ) ; else { assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL, NULL); xfree (optstr); } } /* Tell Pinentry about our client. */ if (ctrl->client_pid) { char *optstr; const char *nodename = ""; #ifndef HAVE_W32_SYSTEM struct utsname utsbuf; if (!uname (&utsbuf)) nodename = utsbuf.nodename; #endif /*!HAVE_W32_SYSTEM*/ if ((optstr = xtryasprintf ("OPTION owner=%lu %s", ctrl->client_pid, nodename))) { assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL, NULL); /* We ignore errors because this is just a fancy thing and older pinentries do not support this feature. */ xfree (optstr); } } /* Ask the pinentry for its version and flavor and store that as a * string in MB. This information is useful for helping users to * figure out Pinentry problems. Noet that "flavor" may also return * a status line with the features; we use a dedicated handler for * that. */ { membuf_t mb; init_membuf (&mb, 256); if (assuan_transact (entry_ctx, "GETINFO flavor", put_membuf_cb, &mb, NULL, NULL, getinfo_features_cb, NULL)) put_membuf_str (&mb, "unknown"); put_membuf_str (&mb, " "); if (assuan_transact (entry_ctx, "GETINFO version", put_membuf_cb, &mb, NULL, NULL, NULL, NULL)) put_membuf_str (&mb, "unknown"); put_membuf_str (&mb, " "); if (assuan_transact (entry_ctx, "GETINFO ttyinfo", put_membuf_cb, &mb, NULL, NULL, NULL, NULL)) put_membuf_str (&mb, "? ? ?"); put_membuf (&mb, "", 1); flavor_version = get_membuf (&mb, NULL); } /* Now ask the Pinentry for its PID. If the Pinentry is new enough it will send the pid back and we will use an inquire to notify our client. The client may answer the inquiry either with END or with CAN to cancel the pinentry. */ rc = assuan_transact (entry_ctx, "GETINFO pid", getinfo_pid_cb, &pinentry_pid, NULL, NULL, NULL, NULL); if (rc) { log_info ("You may want to update to a newer pinentry\n"); rc = 0; } else if (!rc && (pid_t)pinentry_pid == (pid_t)(-1)) log_error ("pinentry did not return a PID\n"); else { rc = agent_inq_pinentry_launched (ctrl, pinentry_pid, flavor_version); if (gpg_err_code (rc) == GPG_ERR_CANCELED || gpg_err_code (rc) == GPG_ERR_FULLY_CANCELED) return unlock_pinentry (gpg_err_make (GPG_ERR_SOURCE_DEFAULT, gpg_err_code (rc))); rc = 0; } xfree (flavor_version); - return 0; + return rc; } /* Returns True if the pinentry is currently active. If WAITSECONDS is greater than zero the function will wait for this many seconds before returning. */ int pinentry_active_p (ctrl_t ctrl, int waitseconds) { int err; (void)ctrl; if (waitseconds > 0) { struct timespec abstime; int rc; npth_clock_gettime (&abstime); abstime.tv_sec += waitseconds; err = npth_mutex_timedlock (&entry_lock, &abstime); if (err) { if (err == ETIMEDOUT) rc = gpg_error (GPG_ERR_TIMEOUT); else rc = gpg_error (GPG_ERR_INTERNAL); return rc; } } else { err = npth_mutex_trylock (&entry_lock); if (err) return gpg_error (GPG_ERR_LOCKED); } err = npth_mutex_unlock (&entry_lock); if (err) log_error ("failed to release the entry lock at %d: %s\n", __LINE__, strerror (errno)); return 0; } static gpg_error_t getpin_cb (void *opaque, const void *buffer, size_t length) { struct entry_parm_s *parm = opaque; if (!buffer) return 0; /* we expect the pin to fit on one line */ if (parm->lines || length >= parm->size) return gpg_error (GPG_ERR_ASS_TOO_MUCH_DATA); /* fixme: we should make sure that the assuan buffer is allocated in secure memory or read the response byte by byte */ memcpy (parm->buffer, buffer, length); parm->buffer[length] = 0; parm->lines++; return 0; } static int all_digitsp( const char *s) { for (; *s && *s >= '0' && *s <= '9'; s++) ; return !*s; } /* Return a new malloced string by unescaping the string S. Escaping is percent escaping and '+'/space mapping. A binary Nul will silently be replaced by a 0xFF. Function returns NULL to indicate an out of memory status. Parsing stops at the end of the string or a white space character. */ static char * unescape_passphrase_string (const unsigned char *s) { char *buffer, *d; buffer = d = xtrymalloc_secure (strlen ((const char*)s)+1); if (!buffer) return NULL; while (*s && !spacep (s)) { if (*s == '%' && s[1] && s[2]) { s++; *d = xtoi_2 (s); if (!*d) *d = '\xff'; d++; s += 2; } else if (*s == '+') { *d++ = ' '; s++; } else *d++ = *s++; } *d = 0; return buffer; } /* Estimate the quality of the passphrase PW and return a value in the range 0..100. */ static int estimate_passphrase_quality (const char *pw) { int goodlength = opt.min_passphrase_len + opt.min_passphrase_len/3; int length; const char *s; if (goodlength < 1) return 0; for (length = 0, s = pw; *s; s++) if (!spacep (s)) length ++; if (length > goodlength) return 100; return ((length*10) / goodlength)*10; } /* Handle the QUALITY inquiry. */ static gpg_error_t inq_quality (void *opaque, const char *line) { assuan_context_t ctx = opaque; const char *s; char *pin; int rc; int percent; char numbuf[20]; if ((s = has_leading_keyword (line, "QUALITY"))) { pin = unescape_passphrase_string (s); if (!pin) rc = gpg_error_from_syserror (); else { percent = estimate_passphrase_quality (pin); if (check_passphrase_constraints (NULL, pin, NULL)) percent = -percent; snprintf (numbuf, sizeof numbuf, "%d", percent); rc = assuan_send_data (ctx, numbuf, strlen (numbuf)); xfree (pin); } } else { log_error ("unsupported inquiry '%s' from pinentry\n", line); rc = gpg_error (GPG_ERR_ASS_UNKNOWN_INQUIRE); } return rc; } /* Helper for agent_askpin and agent_get_passphrase. */ static gpg_error_t setup_qualitybar (ctrl_t ctrl) { int rc; char line[ASSUAN_LINELENGTH]; char *tmpstr, *tmpstr2; const char *tooltip; (void)ctrl; /* TRANSLATORS: This string is displayed by Pinentry as the label for the quality bar. */ tmpstr = try_percent_escape (L_("Quality:"), "\t\r\n\f\v"); snprintf (line, DIM(line), "SETQUALITYBAR %s", tmpstr? tmpstr:""); xfree (tmpstr); rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (rc == 103 /*(Old assuan error code)*/ || gpg_err_code (rc) == GPG_ERR_ASS_UNKNOWN_CMD) ; /* Ignore Unknown Command from old Pinentry versions. */ else if (rc) return rc; tmpstr2 = gnupg_get_help_string ("pinentry.qualitybar.tooltip", 0); if (tmpstr2) tooltip = tmpstr2; else { /* TRANSLATORS: This string is a tooltip, shown by pinentry when hovering over the quality bar. Please use an appropriate string to describe what this is about. The length of the tooltip is limited to about 900 characters. If you do not translate this entry, a default english text (see source) will be used. */ tooltip = L_("pinentry.qualitybar.tooltip"); if (!strcmp ("pinentry.qualitybar.tooltip", tooltip)) tooltip = ("The quality of the text entered above.\n" "Please ask your administrator for " "details about the criteria."); } tmpstr = try_percent_escape (tooltip, "\t\r\n\f\v"); xfree (tmpstr2); snprintf (line, DIM(line), "SETQUALITYBAR_TT %s", tmpstr? tmpstr:""); xfree (tmpstr); rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (rc == 103 /*(Old assuan error code)*/ || gpg_err_code (rc) == GPG_ERR_ASS_UNKNOWN_CMD) ; /* Ignore Unknown Command from old pinentry versions. */ else if (rc) return rc; return 0; } enum { PINENTRY_STATUS_CLOSE_BUTTON = 1 << 0, PINENTRY_STATUS_PIN_REPEATED = 1 << 8, PINENTRY_STATUS_PASSWORD_FROM_CACHE = 1 << 9 }; /* Check the button_info line for a close action. Also check for the PIN_REPEATED flag. */ static gpg_error_t pinentry_status_cb (void *opaque, const char *line) { unsigned int *flag = opaque; const char *args; if ((args = has_leading_keyword (line, "BUTTON_INFO"))) { if (!strcmp (args, "close")) *flag |= PINENTRY_STATUS_CLOSE_BUTTON; } else if (has_leading_keyword (line, "PIN_REPEATED")) { *flag |= PINENTRY_STATUS_PIN_REPEATED; } else if (has_leading_keyword (line, "PASSWORD_FROM_CACHE")) { *flag |= PINENTRY_STATUS_PASSWORD_FROM_CACHE; } return 0; } /* Build a SETDESC command line. This is a dedicated funcion so that * it can remove control characters which are not supported by the * current Pinentry. */ static void build_cmd_setdesc (char *line, size_t linelen, const char *desc) { char *src, *dst; snprintf (line, linelen, "SETDESC %s", desc); if (!entry_features.tabbing) { /* Remove RS and US. */ for (src=dst=line; *src; src++) if (!strchr ("\x1e\x1f", *src)) *dst++ = *src; *dst = 0; } } /* Call the Entry and ask for the PIN. We do check for a valid PIN number here and repeat it as long as we have invalid formed numbers. KEYINFO and CACHE_MODE are used to tell pinentry something about the key. */ gpg_error_t agent_askpin (ctrl_t ctrl, const char *desc_text, const char *prompt_text, const char *initial_errtext, struct pin_entry_info_s *pininfo, const char *keyinfo, cache_mode_t cache_mode) { gpg_error_t rc; char line[ASSUAN_LINELENGTH]; struct entry_parm_s parm; const char *errtext = NULL; int is_pin = 0; int saveflag; unsigned int pinentry_status; if (opt.batch) return 0; /* fixme: we should return BAD PIN */ if (ctrl->pinentry_mode != PINENTRY_MODE_ASK) { if (ctrl->pinentry_mode == PINENTRY_MODE_CANCEL) return gpg_error (GPG_ERR_CANCELED); if (ctrl->pinentry_mode == PINENTRY_MODE_LOOPBACK) { unsigned char *passphrase; size_t size; *pininfo->pin = 0; /* Reset the PIN. */ rc = pinentry_loopback(ctrl, "PASSPHRASE", &passphrase, &size, pininfo->max_length - 1); if (rc) return rc; memcpy(&pininfo->pin, passphrase, size); xfree(passphrase); pininfo->pin[size] = 0; if (pininfo->check_cb) { /* More checks by utilizing the optional callback. */ pininfo->cb_errtext = NULL; rc = pininfo->check_cb (pininfo); } return rc; } return gpg_error(GPG_ERR_NO_PIN_ENTRY); } if (!pininfo || pininfo->max_length < 1) return gpg_error (GPG_ERR_INV_VALUE); if (!desc_text && pininfo->min_digits) desc_text = L_("Please enter your PIN, so that the secret key " "can be unlocked for this session"); else if (!desc_text) desc_text = L_("Please enter your passphrase, so that the secret key " "can be unlocked for this session"); if (prompt_text) is_pin = !!strstr (prompt_text, "PIN"); else is_pin = desc_text && strstr (desc_text, "PIN"); rc = start_pinentry (ctrl); if (rc) return rc; /* If we have a KEYINFO string and are normal, user, or ssh cache mode, we tell that the Pinentry so it may use it for own caching purposes. Most pinentries won't have this implemented and thus we do not error out in this case. */ if (keyinfo && (cache_mode == CACHE_MODE_NORMAL || cache_mode == CACHE_MODE_USER || cache_mode == CACHE_MODE_SSH)) snprintf (line, DIM(line), "SETKEYINFO %c/%s", cache_mode == CACHE_MODE_USER? 'u' : cache_mode == CACHE_MODE_SSH? 's' : 'n', keyinfo); else snprintf (line, DIM(line), "SETKEYINFO --clear"); rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (rc && gpg_err_code (rc) != GPG_ERR_ASS_UNKNOWN_CMD) return unlock_pinentry (rc); build_cmd_setdesc (line, DIM(line), desc_text); rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (rc) return unlock_pinentry (rc); snprintf (line, DIM(line), "SETPROMPT %s", prompt_text? prompt_text : is_pin? L_("PIN:") : L_("Passphrase:")); rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (rc) return unlock_pinentry (rc); /* If a passphrase quality indicator has been requested and a minimum passphrase length has not been disabled, send the command to the pinentry. */ if (pininfo->with_qualitybar && opt.min_passphrase_len ) { rc = setup_qualitybar (ctrl); if (rc) return unlock_pinentry (rc); } if (initial_errtext) { snprintf (line, DIM(line), "SETERROR %s", initial_errtext); rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (rc) return unlock_pinentry (rc); } if (pininfo->with_repeat) { snprintf (line, DIM(line), "SETREPEATERROR %s", L_("does not match - try again")); rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (rc) pininfo->with_repeat = 0; /* Pinentry does not support it. */ } pininfo->repeat_okay = 0; for (;pininfo->failed_tries < pininfo->max_tries; pininfo->failed_tries++) { memset (&parm, 0, sizeof parm); parm.size = pininfo->max_length; *pininfo->pin = 0; /* Reset the PIN. */ parm.buffer = (unsigned char*)pininfo->pin; if (errtext) { /* TRANSLATORS: The string is appended to an error message in the pinentry. The %s is the actual error message, the two %d give the current and maximum number of tries. */ snprintf (line, DIM(line), L_("SETERROR %s (try %d of %d)"), errtext, pininfo->failed_tries+1, pininfo->max_tries); rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (rc) return unlock_pinentry (rc); errtext = NULL; } if (pininfo->with_repeat) { snprintf (line, DIM(line), "SETREPEAT %s", L_("Repeat:")); rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (rc) return unlock_pinentry (rc); } saveflag = assuan_get_flag (entry_ctx, ASSUAN_CONFIDENTIAL); assuan_begin_confidential (entry_ctx); pinentry_status = 0; rc = assuan_transact (entry_ctx, "GETPIN", getpin_cb, &parm, inq_quality, entry_ctx, pinentry_status_cb, &pinentry_status); assuan_set_flag (entry_ctx, ASSUAN_CONFIDENTIAL, saveflag); /* Most pinentries out in the wild return the old Assuan error code for canceled which gets translated to an assuan Cancel error and not to the code for a user cancel. Fix this here. */ if (rc && gpg_err_source (rc) && gpg_err_code (rc) == GPG_ERR_ASS_CANCELED) rc = gpg_err_make (gpg_err_source (rc), GPG_ERR_CANCELED); /* Change error code in case the window close button was clicked to cancel the operation. */ if ((pinentry_status & PINENTRY_STATUS_CLOSE_BUTTON) && gpg_err_code (rc) == GPG_ERR_CANCELED) rc = gpg_err_make (gpg_err_source (rc), GPG_ERR_FULLY_CANCELED); if (gpg_err_code (rc) == GPG_ERR_ASS_TOO_MUCH_DATA) errtext = is_pin? L_("PIN too long") : L_("Passphrase too long"); else if (rc) return unlock_pinentry (rc); if (!errtext && pininfo->min_digits) { /* do some basic checks on the entered PIN. */ if (!all_digitsp (pininfo->pin)) errtext = L_("Invalid characters in PIN"); else if (pininfo->max_digits && strlen (pininfo->pin) > pininfo->max_digits) errtext = L_("PIN too long"); else if (strlen (pininfo->pin) < pininfo->min_digits) errtext = L_("PIN too short"); } if (!errtext && pininfo->check_cb) { /* More checks by utilizing the optional callback. */ pininfo->cb_errtext = NULL; rc = pininfo->check_cb (pininfo); if (gpg_err_code (rc) == GPG_ERR_BAD_PASSPHRASE && pininfo->cb_errtext) errtext = pininfo->cb_errtext; else if (gpg_err_code (rc) == GPG_ERR_BAD_PASSPHRASE || gpg_err_code (rc) == GPG_ERR_BAD_PIN) errtext = (is_pin? L_("Bad PIN") : L_("Bad Passphrase")); else if (rc) return unlock_pinentry (rc); } if (!errtext) { if (pininfo->with_repeat && (pinentry_status & PINENTRY_STATUS_PIN_REPEATED)) pininfo->repeat_okay = 1; return unlock_pinentry (0); /* okay, got a PIN or passphrase */ } if ((pinentry_status & PINENTRY_STATUS_PASSWORD_FROM_CACHE)) /* The password was read from the cache. Don't count this against the retry count. */ pininfo->failed_tries --; } return unlock_pinentry (gpg_error (pininfo->min_digits? GPG_ERR_BAD_PIN : GPG_ERR_BAD_PASSPHRASE)); } /* Ask for the passphrase using the supplied arguments. The returned passphrase needs to be freed by the caller. */ int agent_get_passphrase (ctrl_t ctrl, char **retpass, const char *desc, const char *prompt, const char *errtext, int with_qualitybar, const char *keyinfo, cache_mode_t cache_mode) { int rc; char line[ASSUAN_LINELENGTH]; struct entry_parm_s parm; int saveflag; unsigned int pinentry_status; *retpass = NULL; if (opt.batch) return gpg_error (GPG_ERR_BAD_PASSPHRASE); if (ctrl->pinentry_mode != PINENTRY_MODE_ASK) { if (ctrl->pinentry_mode == PINENTRY_MODE_CANCEL) return gpg_error (GPG_ERR_CANCELED); if (ctrl->pinentry_mode == PINENTRY_MODE_LOOPBACK) { size_t size; size_t len = ASSUAN_LINELENGTH/2; return pinentry_loopback (ctrl, "PASSPHRASE", (unsigned char **)retpass, &size, len); } return gpg_error (GPG_ERR_NO_PIN_ENTRY); } rc = start_pinentry (ctrl); if (rc) return rc; if (!prompt) prompt = desc && strstr (desc, "PIN")? L_("PIN:"): L_("Passphrase:"); /* If we have a KEYINFO string and are normal, user, or ssh cache mode, we tell that the Pinentry so it may use it for own caching purposes. Most pinentries won't have this implemented and thus we do not error out in this case. */ if (keyinfo && (cache_mode == CACHE_MODE_NORMAL || cache_mode == CACHE_MODE_USER || cache_mode == CACHE_MODE_SSH)) snprintf (line, DIM(line), "SETKEYINFO %c/%s", cache_mode == CACHE_MODE_USER? 'u' : cache_mode == CACHE_MODE_SSH? 's' : 'n', keyinfo); else snprintf (line, DIM(line), "SETKEYINFO --clear"); rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (rc && gpg_err_code (rc) != GPG_ERR_ASS_UNKNOWN_CMD) return unlock_pinentry (rc); if (desc) build_cmd_setdesc (line, DIM(line), desc); else snprintf (line, DIM(line), "RESET"); rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (rc) return unlock_pinentry (rc); snprintf (line, DIM(line), "SETPROMPT %s", prompt); rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (rc) return unlock_pinentry (rc); if (with_qualitybar && opt.min_passphrase_len) { rc = setup_qualitybar (ctrl); if (rc) return unlock_pinentry (rc); } if (errtext) { snprintf (line, DIM(line), "SETERROR %s", errtext); rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (rc) return unlock_pinentry (rc); } memset (&parm, 0, sizeof parm); parm.size = ASSUAN_LINELENGTH/2 - 5; parm.buffer = gcry_malloc_secure (parm.size+10); if (!parm.buffer) return unlock_pinentry (out_of_core ()); saveflag = assuan_get_flag (entry_ctx, ASSUAN_CONFIDENTIAL); assuan_begin_confidential (entry_ctx); pinentry_status = 0; rc = assuan_transact (entry_ctx, "GETPIN", getpin_cb, &parm, inq_quality, entry_ctx, pinentry_status_cb, &pinentry_status); assuan_set_flag (entry_ctx, ASSUAN_CONFIDENTIAL, saveflag); /* Most pinentries out in the wild return the old Assuan error code for canceled which gets translated to an assuan Cancel error and not to the code for a user cancel. Fix this here. */ if (rc && gpg_err_source (rc) && gpg_err_code (rc) == GPG_ERR_ASS_CANCELED) rc = gpg_err_make (gpg_err_source (rc), GPG_ERR_CANCELED); /* Change error code in case the window close button was clicked to cancel the operation. */ if ((pinentry_status & PINENTRY_STATUS_CLOSE_BUTTON) && gpg_err_code (rc) == GPG_ERR_CANCELED) rc = gpg_err_make (gpg_err_source (rc), GPG_ERR_FULLY_CANCELED); if (rc) xfree (parm.buffer); else *retpass = parm.buffer; return unlock_pinentry (rc); } /* Pop up the PIN-entry, display the text and the prompt and ask the user to confirm this. We return 0 for success, ie. the user confirmed it, GPG_ERR_NOT_CONFIRMED for what the text says or an other error. If WITH_CANCEL it true an extra cancel button is displayed to allow the user to easily return a GPG_ERR_CANCELED. if the Pinentry does not support this, the user can still cancel by closing the Pinentry window. */ int agent_get_confirmation (ctrl_t ctrl, const char *desc, const char *ok, const char *notok, int with_cancel) { int rc; char line[ASSUAN_LINELENGTH]; if (ctrl->pinentry_mode != PINENTRY_MODE_ASK) { if (ctrl->pinentry_mode == PINENTRY_MODE_CANCEL) return gpg_error (GPG_ERR_CANCELED); return gpg_error (GPG_ERR_NO_PIN_ENTRY); } rc = start_pinentry (ctrl); if (rc) return rc; if (desc) build_cmd_setdesc (line, DIM(line), desc); else snprintf (line, DIM(line), "RESET"); rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); /* Most pinentries out in the wild return the old Assuan error code for canceled which gets translated to an assuan Cancel error and not to the code for a user cancel. Fix this here. */ if (rc && gpg_err_source (rc) && gpg_err_code (rc) == GPG_ERR_ASS_CANCELED) rc = gpg_err_make (gpg_err_source (rc), GPG_ERR_CANCELED); if (rc) return unlock_pinentry (rc); if (ok) { snprintf (line, DIM(line), "SETOK %s", ok); rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (rc) return unlock_pinentry (rc); } if (notok) { /* Try to use the newer NOTOK feature if a cancel button is requested. If no cancel button is requested we keep on using the standard cancel. */ if (with_cancel) { snprintf (line, DIM(line), "SETNOTOK %s", notok); rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); } else rc = GPG_ERR_ASS_UNKNOWN_CMD; if (gpg_err_code (rc) == GPG_ERR_ASS_UNKNOWN_CMD) { snprintf (line, DIM(line), "SETCANCEL %s", notok); rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); } if (rc) return unlock_pinentry (rc); } rc = assuan_transact (entry_ctx, "CONFIRM", NULL, NULL, NULL, NULL, NULL, NULL); if (rc && gpg_err_source (rc) && gpg_err_code (rc) == GPG_ERR_ASS_CANCELED) rc = gpg_err_make (gpg_err_source (rc), GPG_ERR_CANCELED); return unlock_pinentry (rc); } /* Pop up the PINentry, display the text DESC and a button with the text OK_BTN (which may be NULL to use the default of "OK") and wait for the user to hit this button. The return value is not relevant. */ int agent_show_message (ctrl_t ctrl, const char *desc, const char *ok_btn) { int rc; char line[ASSUAN_LINELENGTH]; if (ctrl->pinentry_mode != PINENTRY_MODE_ASK) return gpg_error (GPG_ERR_CANCELED); rc = start_pinentry (ctrl); if (rc) return rc; if (desc) build_cmd_setdesc (line, DIM(line), desc); else snprintf (line, DIM(line), "RESET"); rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); /* Most pinentries out in the wild return the old Assuan error code for canceled which gets translated to an assuan Cancel error and not to the code for a user cancel. Fix this here. */ if (rc && gpg_err_source (rc) && gpg_err_code (rc) == GPG_ERR_ASS_CANCELED) rc = gpg_err_make (gpg_err_source (rc), GPG_ERR_CANCELED); if (rc) return unlock_pinentry (rc); if (ok_btn) { snprintf (line, DIM(line), "SETOK %s", ok_btn); rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (rc) return unlock_pinentry (rc); } rc = assuan_transact (entry_ctx, "CONFIRM --one-button", NULL, NULL, NULL, NULL, NULL, NULL); if (rc && gpg_err_source (rc) && gpg_err_code (rc) == GPG_ERR_ASS_CANCELED) rc = gpg_err_make (gpg_err_source (rc), GPG_ERR_CANCELED); return unlock_pinentry (rc); } /* The thread running the popup message. */ static void * popup_message_thread (void *arg) { (void)arg; /* We use the --one-button hack instead of the MESSAGE command to allow the use of old Pinentries. Those old Pinentries will then show an additional Cancel button but that is mostly a visual annoyance. */ assuan_transact (entry_ctx, "CONFIRM --one-button", NULL, NULL, NULL, NULL, NULL, NULL); popup_finished = 1; return NULL; } /* Pop up a message window similar to the confirm one but keep it open until agent_popup_message_stop has been called. It is crucial for the caller to make sure that the stop function gets called as soon as the message is not anymore required because the message is system modal and all other attempts to use the pinentry will fail (after a timeout). */ int agent_popup_message_start (ctrl_t ctrl, const char *desc, const char *ok_btn) { int rc; char line[ASSUAN_LINELENGTH]; npth_attr_t tattr; int err; if (ctrl->pinentry_mode != PINENTRY_MODE_ASK) return gpg_error (GPG_ERR_CANCELED); rc = start_pinentry (ctrl); if (rc) return rc; if (desc) build_cmd_setdesc (line, DIM(line), desc); else snprintf (line, DIM(line), "RESET"); rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (rc) return unlock_pinentry (rc); if (ok_btn) { snprintf (line, DIM(line), "SETOK %s", ok_btn); rc = assuan_transact (entry_ctx, line, NULL,NULL,NULL,NULL,NULL,NULL); if (rc) return unlock_pinentry (rc); } err = npth_attr_init (&tattr); if (err) return unlock_pinentry (gpg_error_from_errno (err)); npth_attr_setdetachstate (&tattr, NPTH_CREATE_JOINABLE); popup_finished = 0; err = npth_create (&popup_tid, &tattr, popup_message_thread, NULL); npth_attr_destroy (&tattr); if (err) { rc = gpg_error_from_errno (err); log_error ("error spawning popup message handler: %s\n", strerror (err) ); return unlock_pinentry (rc); } npth_setname_np (popup_tid, "popup-message"); return 0; } /* Close a popup window. */ void agent_popup_message_stop (ctrl_t ctrl) { int rc; pid_t pid; (void)ctrl; if (!popup_tid || !entry_ctx) { log_debug ("agent_popup_message_stop called with no active popup\n"); return; } pid = assuan_get_pid (entry_ctx); if (pid == (pid_t)(-1)) ; /* No pid available can't send a kill. */ else if (popup_finished) ; /* Already finished and ready for joining. */ #ifdef HAVE_W32_SYSTEM /* Older versions of assuan set PID to 0 on Windows to indicate an invalid value. */ else if (pid != (pid_t) INVALID_HANDLE_VALUE && pid != 0) { HANDLE process = (HANDLE) pid; /* Arbitrary error code. */ TerminateProcess (process, 1); } #else else if (pid && ((rc=waitpid (pid, NULL, WNOHANG))==-1 || (rc == pid)) ) { /* The daemon already died. No need to send a kill. However because we already waited for the process, we need to tell assuan that it should not wait again (done by unlock_pinentry). */ if (rc == pid) assuan_set_flag (entry_ctx, ASSUAN_NO_WAITPID, 1); } else if (pid > 0) kill (pid, SIGINT); #endif /* Now wait for the thread to terminate. */ rc = npth_join (popup_tid, NULL); if (rc) log_debug ("agent_popup_message_stop: pth_join failed: %s\n", strerror (rc)); /* Thread IDs are opaque, but we try our best here by resetting it to the same content that a static global variable has. */ memset (&popup_tid, '\0', sizeof (popup_tid)); entry_owner = NULL; /* Now we can close the connection. */ unlock_pinentry (0); } int agent_clear_passphrase (ctrl_t ctrl, const char *keyinfo, cache_mode_t cache_mode) { int rc; char line[ASSUAN_LINELENGTH]; if (! (keyinfo && (cache_mode == CACHE_MODE_NORMAL || cache_mode == CACHE_MODE_USER || cache_mode == CACHE_MODE_SSH))) return gpg_error (GPG_ERR_NOT_SUPPORTED); rc = start_pinentry (ctrl); if (rc) return rc; snprintf (line, DIM(line), "CLEARPASSPHRASE %c/%s", cache_mode == CACHE_MODE_USER? 'u' : cache_mode == CACHE_MODE_SSH? 's' : 'n', keyinfo); rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); return unlock_pinentry (rc); } diff --git a/agent/command-ssh.c b/agent/command-ssh.c index eeb14988e..5c7cf1f26 100644 --- a/agent/command-ssh.c +++ b/agent/command-ssh.c @@ -1,3818 +1,3817 @@ /* command-ssh.c - gpg-agent's implementation of the ssh-agent protocol. * Copyright (C) 2004-2006, 2009, 2012 Free Software Foundation, Inc. * Copyright (C) 2004-2006, 2009, 2012-2014 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 . */ /* Only v2 of the ssh-agent protocol is implemented. Relevant RFCs are: RFC-4250 - Protocol Assigned Numbers RFC-4251 - Protocol Architecture RFC-4252 - Authentication Protocol RFC-4253 - Transport Layer Protocol RFC-5656 - ECC support The protocol for the agent is defined in OpenSSH's PROTOCL.agent file. */ #include #include #include #include #include #include #include #include #ifndef HAVE_W32_SYSTEM #include #include #endif /*!HAVE_W32_SYSTEM*/ #ifdef HAVE_UCRED_H #include #endif #include "agent.h" #include "../common/i18n.h" #include "../common/util.h" #include "../common/ssh-utils.h" /* Request types. */ #define SSH_REQUEST_REQUEST_IDENTITIES 11 #define SSH_REQUEST_SIGN_REQUEST 13 #define SSH_REQUEST_ADD_IDENTITY 17 #define SSH_REQUEST_REMOVE_IDENTITY 18 #define SSH_REQUEST_REMOVE_ALL_IDENTITIES 19 #define SSH_REQUEST_LOCK 22 #define SSH_REQUEST_UNLOCK 23 #define SSH_REQUEST_ADD_ID_CONSTRAINED 25 /* Options. */ #define SSH_OPT_CONSTRAIN_LIFETIME 1 #define SSH_OPT_CONSTRAIN_CONFIRM 2 /* Response types. */ #define SSH_RESPONSE_SUCCESS 6 #define SSH_RESPONSE_FAILURE 5 #define SSH_RESPONSE_IDENTITIES_ANSWER 12 #define SSH_RESPONSE_SIGN_RESPONSE 14 /* Other constants. */ #define SSH_DSA_SIGNATURE_PADDING 20 #define SSH_DSA_SIGNATURE_ELEMS 2 #define SPEC_FLAG_USE_PKCS1V2 (1 << 0) #define SPEC_FLAG_IS_ECDSA (1 << 1) #define SPEC_FLAG_IS_EdDSA (1 << 2) /*(lowercase 'd' on purpose.)*/ #define SPEC_FLAG_WITH_CERT (1 << 7) /* The name of the control file. */ #define SSH_CONTROL_FILE_NAME "sshcontrol" /* The blurb we put into the header of a newly created control file. */ static const char sshcontrolblurb[] = "# List of allowed ssh keys. Only keys present in this file are used\n" "# in the SSH protocol. The ssh-add tool may add new entries to this\n" "# file to enable them; you may also add them manually. Comment\n" "# lines, like this one, as well as empty lines are ignored. Lines do\n" "# have a certain length limit but this is not serious limitation as\n" "# the format of the entries is fixed and checked by gpg-agent. A\n" "# non-comment line starts with optional white spaces, followed by the\n" "# keygrip of the key given as 40 hex digits, optionally followed by a\n" "# caching TTL in seconds, and another optional field for arbitrary\n" "# flags. Prepend the keygrip with an '!' mark to disable it.\n" "\n"; /* Macros. */ /* Return a new uint32 with b0 being the most significant byte and b3 being the least significant byte. */ #define uint32_construct(b0, b1, b2, b3) \ ((b0 << 24) | (b1 << 16) | (b2 << 8) | b3) /* * Basic types. */ /* Type for a request handler. */ typedef gpg_error_t (*ssh_request_handler_t) (ctrl_t ctrl, estream_t request, estream_t response); struct ssh_key_type_spec; typedef struct ssh_key_type_spec ssh_key_type_spec_t; /* Type, which is used for associating request handlers with the appropriate request IDs. */ typedef struct ssh_request_spec { unsigned char type; ssh_request_handler_t handler; const char *identifier; unsigned int secret_input; } ssh_request_spec_t; /* Type for "key modifier functions", which are necessary since OpenSSH and GnuPG treat key material slightly different. A key modifier is called right after a new key identity has been received in order to "sanitize" the material. */ typedef gpg_error_t (*ssh_key_modifier_t) (const char *elems, gcry_mpi_t *mpis); /* The encoding of a generated signature is dependent on the algorithm; therefore algorithm specific signature encoding functions are necessary. */ typedef gpg_error_t (*ssh_signature_encoder_t) (ssh_key_type_spec_t *spec, estream_t signature_blob, gcry_sexp_t sig); /* Type, which is used for boundling all the algorithm specific information together in a single object. */ struct ssh_key_type_spec { /* Algorithm identifier as used by OpenSSH. */ const char *ssh_identifier; /* Human readable name of the algorithm. */ const char *name; /* Algorithm identifier as used by GnuPG. */ int algo; /* List of MPI names for secret keys; order matches the one of the agent protocol. */ const char *elems_key_secret; /* List of MPI names for public keys; order matches the one of the agent protocol. */ const char *elems_key_public; /* List of MPI names for signature data. */ const char *elems_signature; /* List of MPI names for secret keys; order matches the one, which is required by gpg-agent's key access layer. */ const char *elems_sexp_order; /* Key modifier function. Key modifier functions are necessary in order to fix any inconsistencies between the representation of keys on the SSH and on the GnuPG side. */ ssh_key_modifier_t key_modifier; /* Signature encoder function. Signature encoder functions are necessary since the encoding of signatures depends on the used algorithm. */ ssh_signature_encoder_t signature_encoder; /* The name of the ECC curve or NULL. */ const char *curve_name; /* The hash algorithm to be used with this key. 0 for using the default. */ int hash_algo; /* Misc flags. */ unsigned int flags; }; /* Definition of an object to access the sshcontrol file. */ struct ssh_control_file_s { char *fname; /* Name of the file. */ FILE *fp; /* This is never NULL. */ int lnr; /* The current line number. */ struct { int valid; /* True if the data of this structure is valid. */ int disabled; /* The item is disabled. */ int ttl; /* The TTL of the item. */ int confirm; /* The confirm flag is set. */ char hexgrip[40+1]; /* The hexgrip of the item (uppercase). */ } item; }; /* Prototypes. */ static gpg_error_t ssh_handler_request_identities (ctrl_t ctrl, estream_t request, estream_t response); static gpg_error_t ssh_handler_sign_request (ctrl_t ctrl, estream_t request, estream_t response); static gpg_error_t ssh_handler_add_identity (ctrl_t ctrl, estream_t request, estream_t response); static gpg_error_t ssh_handler_remove_identity (ctrl_t ctrl, estream_t request, estream_t response); static gpg_error_t ssh_handler_remove_all_identities (ctrl_t ctrl, estream_t request, estream_t response); static gpg_error_t ssh_handler_lock (ctrl_t ctrl, estream_t request, estream_t response); static gpg_error_t ssh_handler_unlock (ctrl_t ctrl, estream_t request, estream_t response); static gpg_error_t ssh_key_modifier_rsa (const char *elems, gcry_mpi_t *mpis); static gpg_error_t ssh_signature_encoder_rsa (ssh_key_type_spec_t *spec, estream_t signature_blob, gcry_sexp_t signature); static gpg_error_t ssh_signature_encoder_dsa (ssh_key_type_spec_t *spec, estream_t signature_blob, gcry_sexp_t signature); static gpg_error_t ssh_signature_encoder_ecdsa (ssh_key_type_spec_t *spec, estream_t signature_blob, gcry_sexp_t signature); static gpg_error_t ssh_signature_encoder_eddsa (ssh_key_type_spec_t *spec, estream_t signature_blob, gcry_sexp_t signature); static gpg_error_t ssh_key_extract_comment (gcry_sexp_t key, char **comment); /* Global variables. */ /* Associating request types with the corresponding request handlers. */ static ssh_request_spec_t request_specs[] = { #define REQUEST_SPEC_DEFINE(id, name, secret_input) \ { SSH_REQUEST_##id, ssh_handler_##name, #name, secret_input } REQUEST_SPEC_DEFINE (REQUEST_IDENTITIES, request_identities, 1), REQUEST_SPEC_DEFINE (SIGN_REQUEST, sign_request, 0), REQUEST_SPEC_DEFINE (ADD_IDENTITY, add_identity, 1), REQUEST_SPEC_DEFINE (ADD_ID_CONSTRAINED, add_identity, 1), REQUEST_SPEC_DEFINE (REMOVE_IDENTITY, remove_identity, 0), REQUEST_SPEC_DEFINE (REMOVE_ALL_IDENTITIES, remove_all_identities, 0), REQUEST_SPEC_DEFINE (LOCK, lock, 0), REQUEST_SPEC_DEFINE (UNLOCK, unlock, 0) #undef REQUEST_SPEC_DEFINE }; /* Table holding key type specifications. */ static ssh_key_type_spec_t ssh_key_types[] = { { "ssh-ed25519", "Ed25519", GCRY_PK_EDDSA, "qd", "q", "rs", "qd", NULL, ssh_signature_encoder_eddsa, "Ed25519", 0, SPEC_FLAG_IS_EdDSA }, { "ssh-rsa", "RSA", GCRY_PK_RSA, "nedupq", "en", "s", "nedpqu", ssh_key_modifier_rsa, ssh_signature_encoder_rsa, NULL, 0, SPEC_FLAG_USE_PKCS1V2 }, { "ssh-dss", "DSA", GCRY_PK_DSA, "pqgyx", "pqgy", "rs", "pqgyx", NULL, ssh_signature_encoder_dsa, NULL, 0, 0 }, { "ecdsa-sha2-nistp256", "ECDSA", GCRY_PK_ECC, "qd", "q", "rs", "qd", NULL, ssh_signature_encoder_ecdsa, "nistp256", GCRY_MD_SHA256, SPEC_FLAG_IS_ECDSA }, { "ecdsa-sha2-nistp384", "ECDSA", GCRY_PK_ECC, "qd", "q", "rs", "qd", NULL, ssh_signature_encoder_ecdsa, "nistp384", GCRY_MD_SHA384, SPEC_FLAG_IS_ECDSA }, { "ecdsa-sha2-nistp521", "ECDSA", GCRY_PK_ECC, "qd", "q", "rs", "qd", NULL, ssh_signature_encoder_ecdsa, "nistp521", GCRY_MD_SHA512, SPEC_FLAG_IS_ECDSA }, { "ssh-ed25519-cert-v01@openssh.com", "Ed25519", GCRY_PK_EDDSA, "qd", "q", "rs", "qd", NULL, ssh_signature_encoder_eddsa, "Ed25519", 0, SPEC_FLAG_IS_EdDSA | SPEC_FLAG_WITH_CERT }, { "ssh-rsa-cert-v01@openssh.com", "RSA", GCRY_PK_RSA, "nedupq", "en", "s", "nedpqu", ssh_key_modifier_rsa, ssh_signature_encoder_rsa, NULL, 0, SPEC_FLAG_USE_PKCS1V2 | SPEC_FLAG_WITH_CERT }, { "ssh-dss-cert-v01@openssh.com", "DSA", GCRY_PK_DSA, "pqgyx", "pqgy", "rs", "pqgyx", NULL, ssh_signature_encoder_dsa, NULL, 0, SPEC_FLAG_WITH_CERT | SPEC_FLAG_WITH_CERT }, { "ecdsa-sha2-nistp256-cert-v01@openssh.com", "ECDSA", GCRY_PK_ECC, "qd", "q", "rs", "qd", NULL, ssh_signature_encoder_ecdsa, "nistp256", GCRY_MD_SHA256, SPEC_FLAG_IS_ECDSA | SPEC_FLAG_WITH_CERT }, { "ecdsa-sha2-nistp384-cert-v01@openssh.com", "ECDSA", GCRY_PK_ECC, "qd", "q", "rs", "qd", NULL, ssh_signature_encoder_ecdsa, "nistp384", GCRY_MD_SHA384, SPEC_FLAG_IS_ECDSA | SPEC_FLAG_WITH_CERT }, { "ecdsa-sha2-nistp521-cert-v01@openssh.com", "ECDSA", GCRY_PK_ECC, "qd", "q", "rs", "qd", NULL, ssh_signature_encoder_ecdsa, "nistp521", GCRY_MD_SHA512, SPEC_FLAG_IS_ECDSA | SPEC_FLAG_WITH_CERT } }; /* General utility functions. */ /* A secure realloc, i.e. it makes sure to allocate secure memory if A is NULL. This is required because the standard gcry_realloc does not know whether to allocate secure or normal if NULL is passed as existing buffer. */ static void * realloc_secure (void *a, size_t n) { void *p; if (a) p = gcry_realloc (a, n); else p = gcry_malloc_secure (n); return p; } /* Lookup the ssh-identifier for the ECC curve CURVE_NAME. Returns NULL if not found. */ static const char * ssh_identifier_from_curve_name (const char *curve_name) { int i; for (i = 0; i < DIM (ssh_key_types); i++) if (ssh_key_types[i].curve_name && !strcmp (ssh_key_types[i].curve_name, curve_name)) return ssh_key_types[i].ssh_identifier; return NULL; } /* Primitive I/O functions. */ /* Read a byte from STREAM, store it in B. */ static gpg_error_t stream_read_byte (estream_t stream, unsigned char *b) { gpg_error_t err; int ret; ret = es_fgetc (stream); if (ret == EOF) { if (es_ferror (stream)) err = gpg_error_from_syserror (); else err = gpg_error (GPG_ERR_EOF); *b = 0; } else { *b = ret & 0xFF; err = 0; } return err; } /* Write the byte contained in B to STREAM. */ static gpg_error_t stream_write_byte (estream_t stream, unsigned char b) { gpg_error_t err; int ret; ret = es_fputc (b, stream); if (ret == EOF) err = gpg_error_from_syserror (); else err = 0; return err; } /* Read a uint32 from STREAM, store it in UINT32. */ static gpg_error_t stream_read_uint32 (estream_t stream, u32 *uint32) { unsigned char buffer[4]; size_t bytes_read; gpg_error_t err; int ret; ret = es_read (stream, buffer, sizeof (buffer), &bytes_read); if (ret) err = gpg_error_from_syserror (); else { if (bytes_read != sizeof (buffer)) err = gpg_error (GPG_ERR_EOF); else { u32 n; n = uint32_construct (buffer[0], buffer[1], buffer[2], buffer[3]); *uint32 = n; err = 0; } } return err; } /* Write the uint32 contained in UINT32 to STREAM. */ static gpg_error_t stream_write_uint32 (estream_t stream, u32 uint32) { unsigned char buffer[4]; gpg_error_t err; int ret; buffer[0] = uint32 >> 24; buffer[1] = uint32 >> 16; buffer[2] = uint32 >> 8; buffer[3] = uint32 >> 0; ret = es_write (stream, buffer, sizeof (buffer), NULL); if (ret) err = gpg_error_from_syserror (); else err = 0; return err; } /* Read SIZE bytes from STREAM into BUFFER. */ static gpg_error_t stream_read_data (estream_t stream, unsigned char *buffer, size_t size) { gpg_error_t err; size_t bytes_read; int ret; ret = es_read (stream, buffer, size, &bytes_read); if (ret) err = gpg_error_from_syserror (); else { if (bytes_read != size) err = gpg_error (GPG_ERR_EOF); else err = 0; } return err; } /* Skip over SIZE bytes from STREAM. */ static gpg_error_t stream_read_skip (estream_t stream, size_t size) { char buffer[128]; size_t bytes_to_read, bytes_read; int ret; do { bytes_to_read = size; if (bytes_to_read > sizeof buffer) bytes_to_read = sizeof buffer; ret = es_read (stream, buffer, bytes_to_read, &bytes_read); if (ret) return gpg_error_from_syserror (); else if (bytes_read != bytes_to_read) return gpg_error (GPG_ERR_EOF); else size -= bytes_to_read; } while (size); return 0; } /* Write SIZE bytes from BUFFER to STREAM. */ static gpg_error_t stream_write_data (estream_t stream, const unsigned char *buffer, size_t size) { gpg_error_t err; int ret; ret = es_write (stream, buffer, size, NULL); if (ret) err = gpg_error_from_syserror (); else err = 0; return err; } /* Read a binary string from STREAM into STRING, store size of string in STRING_SIZE. Append a hidden nul so that the result may directly be used as a C string. Depending on SECURE use secure memory for STRING. If STRING is NULL do only a dummy read. */ static gpg_error_t stream_read_string (estream_t stream, unsigned int secure, unsigned char **string, u32 *string_size) { gpg_error_t err; unsigned char *buffer = NULL; u32 length = 0; if (string_size) *string_size = 0; /* Read string length. */ err = stream_read_uint32 (stream, &length); if (err) goto out; if (string) { /* Allocate space. */ if (secure) buffer = xtrymalloc_secure (length + 1); else buffer = xtrymalloc (length + 1); if (! buffer) { err = gpg_error_from_syserror (); goto out; } /* Read data. */ err = stream_read_data (stream, buffer, length); if (err) goto out; /* Finalize string object. */ buffer[length] = 0; *string = buffer; } else /* Dummy read requested. */ { err = stream_read_skip (stream, length); if (err) goto out; } if (string_size) *string_size = length; out: if (err) xfree (buffer); return err; } /* Read a binary string from STREAM and store it as an opaque MPI at R_MPI, adding 0x40 (this is the prefix for EdDSA key in OpenPGP). Depending on SECURE use secure memory. If the string is too large for key material return an error. */ static gpg_error_t stream_read_blob (estream_t stream, unsigned int secure, gcry_mpi_t *r_mpi) { gpg_error_t err; unsigned char *buffer = NULL; u32 length = 0; *r_mpi = NULL; /* Read string length. */ err = stream_read_uint32 (stream, &length); if (err) goto leave; /* To avoid excessive use of secure memory we check that an MPI is not too large. */ if (length > (4096/8) + 8) { log_error (_("ssh keys greater than %d bits are not supported\n"), 4096); err = GPG_ERR_TOO_LARGE; goto leave; } /* Allocate space. */ if (secure) buffer = xtrymalloc_secure (length+1); else buffer = xtrymalloc (length+1); if (!buffer) { err = gpg_error_from_syserror (); goto leave; } /* Read data. */ err = stream_read_data (stream, buffer + 1, length); if (err) goto leave; buffer[0] = 0x40; *r_mpi = gcry_mpi_set_opaque (NULL, buffer, 8*(length+1)); buffer = NULL; leave: xfree (buffer); return err; } /* Read a C-string from STREAM, store copy in STRING. */ static gpg_error_t stream_read_cstring (estream_t stream, char **string) { return stream_read_string (stream, 0, (unsigned char **)string, NULL); } /* Write a binary string from STRING of size STRING_N to STREAM. */ static gpg_error_t stream_write_string (estream_t stream, const unsigned char *string, u32 string_n) { gpg_error_t err; err = stream_write_uint32 (stream, string_n); if (err) goto out; err = stream_write_data (stream, string, string_n); out: return err; } /* Write a C-string from STRING to STREAM. */ static gpg_error_t stream_write_cstring (estream_t stream, const char *string) { gpg_error_t err; err = stream_write_string (stream, (const unsigned char *) string, strlen (string)); return err; } /* Read an MPI from STREAM, store it in MPINT. Depending on SECURE use secure memory. */ static gpg_error_t stream_read_mpi (estream_t stream, unsigned int secure, gcry_mpi_t *mpint) { unsigned char *mpi_data; u32 mpi_data_size; gpg_error_t err; gcry_mpi_t mpi; mpi_data = NULL; err = stream_read_string (stream, secure, &mpi_data, &mpi_data_size); if (err) goto out; /* To avoid excessive use of secure memory we check that an MPI is not too large. */ if (mpi_data_size > 520) { log_error (_("ssh keys greater than %d bits are not supported\n"), 4096); err = GPG_ERR_TOO_LARGE; goto out; } err = gcry_mpi_scan (&mpi, GCRYMPI_FMT_STD, mpi_data, mpi_data_size, NULL); if (err) goto out; *mpint = mpi; out: xfree (mpi_data); return err; } /* Write the MPI contained in MPINT to STREAM. */ static gpg_error_t stream_write_mpi (estream_t stream, gcry_mpi_t mpint) { unsigned char *mpi_buffer; size_t mpi_buffer_n; gpg_error_t err; mpi_buffer = NULL; err = gcry_mpi_aprint (GCRYMPI_FMT_STD, &mpi_buffer, &mpi_buffer_n, mpint); if (err) goto out; err = stream_write_string (stream, mpi_buffer, mpi_buffer_n); out: xfree (mpi_buffer); return err; } /* Copy data from SRC to DST until EOF is reached. */ static gpg_error_t stream_copy (estream_t dst, estream_t src) { char buffer[BUFSIZ]; size_t bytes_read; gpg_error_t err; int ret; err = 0; while (1) { ret = es_read (src, buffer, sizeof (buffer), &bytes_read); if (ret || (! bytes_read)) { if (ret) err = gpg_error_from_syserror (); break; } ret = es_write (dst, buffer, bytes_read, NULL); if (ret) { err = gpg_error_from_syserror (); break; } } return err; } /* Open the ssh control file and create it if not available. With APPEND passed as true the file will be opened in append mode, otherwise in read only mode. On success 0 is returned and a new control file object stored at R_CF. On error an error code is returned and NULL is stored at R_CF. */ static gpg_error_t open_control_file (ssh_control_file_t *r_cf, int append) { gpg_error_t err; ssh_control_file_t cf; cf = xtrycalloc (1, sizeof *cf); if (!cf) { err = gpg_error_from_syserror (); goto leave; } /* Note: As soon as we start to use non blocking functions here (i.e. where Pth might switch threads) we need to employ a mutex. */ cf->fname = make_filename_try (gnupg_homedir (), SSH_CONTROL_FILE_NAME, NULL); if (!cf->fname) { err = gpg_error_from_syserror (); goto leave; } /* FIXME: With "a+" we are not able to check whether this will be created and thus the blurb needs to be written first. */ cf->fp = fopen (cf->fname, append? "a+":"r"); if (!cf->fp && errno == ENOENT) { estream_t stream = es_fopen (cf->fname, "wx,mode=-rw-r"); if (!stream) { err = gpg_error_from_syserror (); log_error (_("can't create '%s': %s\n"), cf->fname, gpg_strerror (err)); goto leave; } es_fputs (sshcontrolblurb, stream); es_fclose (stream); cf->fp = fopen (cf->fname, append? "a+":"r"); } if (!cf->fp) { err = gpg_error_from_syserror (); log_error (_("can't open '%s': %s\n"), cf->fname, gpg_strerror (err)); goto leave; } err = 0; leave: if (err && cf) { if (cf->fp) fclose (cf->fp); xfree (cf->fname); xfree (cf); } else *r_cf = cf; return err; } static void rewind_control_file (ssh_control_file_t cf) { fseek (cf->fp, 0, SEEK_SET); cf->lnr = 0; clearerr (cf->fp); } static void close_control_file (ssh_control_file_t cf) { if (!cf) return; fclose (cf->fp); xfree (cf->fname); xfree (cf); } /* Read the next line from the control file and store the data in CF. Returns 0 on success, GPG_ERR_EOF on EOF, or other error codes. */ static gpg_error_t read_control_file_item (ssh_control_file_t cf) { int c, i, n; char *p, *pend, line[256]; long ttl = 0; cf->item.valid = 0; clearerr (cf->fp); do { if (!fgets (line, DIM(line)-1, cf->fp) ) { if (feof (cf->fp)) return gpg_error (GPG_ERR_EOF); return gpg_error_from_syserror (); } cf->lnr++; if (!*line || line[strlen(line)-1] != '\n') { /* Eat until end of line */ while ( (c=getc (cf->fp)) != EOF && c != '\n') ; return gpg_error (*line? GPG_ERR_LINE_TOO_LONG : GPG_ERR_INCOMPLETE_LINE); } /* Allow for empty lines and spaces */ for (p=line; spacep (p); p++) ; } while (!*p || *p == '\n' || *p == '#'); cf->item.disabled = 0; if (*p == '!') { cf->item.disabled = 1; for (p++; spacep (p); p++) ; } for (i=0; hexdigitp (p) && i < 40; p++, i++) cf->item.hexgrip[i] = (*p >= 'a'? (*p & 0xdf): *p); cf->item.hexgrip[i] = 0; if (i != 40 || !(spacep (p) || *p == '\n')) { log_error ("%s:%d: invalid formatted line\n", cf->fname, cf->lnr); return gpg_error (GPG_ERR_BAD_DATA); } ttl = strtol (p, &pend, 10); p = pend; if (!(spacep (p) || *p == '\n') || (int)ttl < -1) { log_error ("%s:%d: invalid TTL value; assuming 0\n", cf->fname, cf->lnr); cf->item.ttl = 0; } cf->item.ttl = ttl; /* Now check for key-value pairs of the form NAME[=VALUE]. */ cf->item.confirm = 0; while (*p) { for (; spacep (p) && *p != '\n'; p++) ; if (!*p || *p == '\n') break; n = strcspn (p, "= \t\n"); if (p[n] == '=') { log_error ("%s:%d: assigning a value to a flag is not yet supported; " "flag ignored\n", cf->fname, cf->lnr); p++; } else if (n == 7 && !memcmp (p, "confirm", 7)) { cf->item.confirm = 1; } else log_error ("%s:%d: invalid flag '%.*s'; ignored\n", cf->fname, cf->lnr, n, p); p += n; } /* log_debug ("%s:%d: grip=%s ttl=%d%s%s\n", */ /* cf->fname, cf->lnr, */ /* cf->item.hexgrip, cf->item.ttl, */ /* cf->item.disabled? " disabled":"", */ /* cf->item.confirm? " confirm":""); */ cf->item.valid = 1; return 0; /* Okay: valid entry found. */ } /* Search the control file CF from the beginning until a matching HEXGRIP is found; return success in this case and store true at DISABLED if the found key has been disabled. If R_TTL is not NULL a specified TTL for that key is stored there. If R_CONFIRM is not NULL it is set to 1 if the key has the confirm flag set. */ static gpg_error_t search_control_file (ssh_control_file_t cf, const char *hexgrip, int *r_disabled, int *r_ttl, int *r_confirm) { gpg_error_t err; assert (strlen (hexgrip) == 40 ); if (r_disabled) *r_disabled = 0; if (r_ttl) *r_ttl = 0; if (r_confirm) *r_confirm = 0; rewind_control_file (cf); while (!(err=read_control_file_item (cf))) { if (!cf->item.valid) continue; /* Should not happen. */ if (!strcmp (hexgrip, cf->item.hexgrip)) break; } if (!err) { if (r_disabled) *r_disabled = cf->item.disabled; if (r_ttl) *r_ttl = cf->item.ttl; if (r_confirm) *r_confirm = cf->item.confirm; } return err; } /* Add an entry to the control file to mark the key with the keygrip HEXGRIP as usable for SSH; i.e. it will be returned when ssh asks for it. FMTFPR is the fingerprint string. This function is in general used to add a key received through the ssh-add function. We can assume that the user wants to allow ssh using this key. */ static gpg_error_t add_control_entry (ctrl_t ctrl, ssh_key_type_spec_t *spec, const char *hexgrip, const char *fmtfpr, int ttl, int confirm) { gpg_error_t err; ssh_control_file_t cf; int disabled; (void)ctrl; err = open_control_file (&cf, 1); if (err) return err; err = search_control_file (cf, hexgrip, &disabled, NULL, NULL); if (err && gpg_err_code(err) == GPG_ERR_EOF) { struct tm *tp; time_t atime = time (NULL); /* Not yet in the file - add it. Because the file has been opened in append mode, we simply need to write to it. */ tp = localtime (&atime); fprintf (cf->fp, ("# %s key added on: %04d-%02d-%02d %02d:%02d:%02d\n" "# MD5 Fingerprint: %s\n" "%s %d%s\n"), spec->name, 1900+tp->tm_year, tp->tm_mon+1, tp->tm_mday, tp->tm_hour, tp->tm_min, tp->tm_sec, fmtfpr, hexgrip, ttl, confirm? " confirm":""); } close_control_file (cf); return 0; } /* Scan the sshcontrol file and return the TTL. */ static int ttl_from_sshcontrol (const char *hexgrip) { ssh_control_file_t cf; int disabled, ttl; if (!hexgrip || strlen (hexgrip) != 40) return 0; /* Wrong input: Use global default. */ if (open_control_file (&cf, 0)) return 0; /* Error: Use the global default TTL. */ if (search_control_file (cf, hexgrip, &disabled, &ttl, NULL) || disabled) ttl = 0; /* Use the global default if not found or disabled. */ close_control_file (cf); return ttl; } /* Scan the sshcontrol file and return the confirm flag. */ static int confirm_flag_from_sshcontrol (const char *hexgrip) { ssh_control_file_t cf; int disabled, confirm; if (!hexgrip || strlen (hexgrip) != 40) return 1; /* Wrong input: Better ask for confirmation. */ if (open_control_file (&cf, 0)) return 1; /* Error: Better ask for confirmation. */ if (search_control_file (cf, hexgrip, &disabled, NULL, &confirm) || disabled) confirm = 0; /* If not found or disabled, there is no reason to ask for confirmation. */ close_control_file (cf); return confirm; } /* Open the ssh control file for reading. This is a public version of open_control_file. The caller must use ssh_close_control_file to release the returned handle. */ ssh_control_file_t ssh_open_control_file (void) { ssh_control_file_t cf; /* Then look at all the registered and non-disabled keys. */ if (open_control_file (&cf, 0)) return NULL; return cf; } /* Close an ssh control file handle. This is the public version of close_control_file. CF may be NULL. */ void ssh_close_control_file (ssh_control_file_t cf) { close_control_file (cf); } /* Read the next item from the ssh control file. The function returns 0 if a item was read, GPG_ERR_EOF on eof or another error value. R_HEXGRIP shall either be null or a BUFFER of at least 41 byte. R_DISABLED, R_TTLm and R_CONFIRM return flags from the control file; they are only set on success. */ gpg_error_t ssh_read_control_file (ssh_control_file_t cf, char *r_hexgrip, int *r_disabled, int *r_ttl, int *r_confirm) { gpg_error_t err; do err = read_control_file_item (cf); while (!err && !cf->item.valid); if (!err) { if (r_hexgrip) strcpy (r_hexgrip, cf->item.hexgrip); if (r_disabled) *r_disabled = cf->item.disabled; if (r_ttl) *r_ttl = cf->item.ttl; if (r_confirm) *r_confirm = cf->item.confirm; } return err; } /* Search for a key with HEXGRIP in sshcontrol and return all info. */ gpg_error_t ssh_search_control_file (ssh_control_file_t cf, const char *hexgrip, int *r_disabled, int *r_ttl, int *r_confirm) { gpg_error_t err; int i; const char *s; char uphexgrip[41]; /* We need to make sure that HEXGRIP is all uppercase. The easiest way to do this and also check its length is by copying to a second buffer. */ for (i=0, s=hexgrip; i < 40 && *s; s++, i++) uphexgrip[i] = *s >= 'a'? (*s & 0xdf): *s; uphexgrip[i] = 0; if (i != 40) err = gpg_error (GPG_ERR_INV_LENGTH); else err = search_control_file (cf, uphexgrip, r_disabled, r_ttl, r_confirm); if (gpg_err_code (err) == GPG_ERR_EOF) err = gpg_error (GPG_ERR_NOT_FOUND); return err; } /* MPI lists. */ /* Free the list of MPIs MPI_LIST. */ static void mpint_list_free (gcry_mpi_t *mpi_list) { if (mpi_list) { unsigned int i; for (i = 0; mpi_list[i]; i++) gcry_mpi_release (mpi_list[i]); xfree (mpi_list); } } /* Receive key material MPIs from STREAM according to KEY_SPEC; depending on SECRET expect a public key or secret key. CERT is the certificate blob used if KEY_SPEC indicates the certificate format; it needs to be positioned to the end of the nonce. The newly allocated list of MPIs is stored in MPI_LIST. Returns usual error code. */ static gpg_error_t ssh_receive_mpint_list (estream_t stream, int secret, ssh_key_type_spec_t *spec, estream_t cert, gcry_mpi_t **mpi_list) { const char *elems_public; unsigned int elems_n; const char *elems; int elem_is_secret; gcry_mpi_t *mpis = NULL; gpg_error_t err = 0; unsigned int i; if (secret) elems = spec->elems_key_secret; else elems = spec->elems_key_public; elems_n = strlen (elems); elems_public = spec->elems_key_public; /* Check that either both, CERT and the WITH_CERT flag, are given or none of them. */ if (!(!!(spec->flags & SPEC_FLAG_WITH_CERT) ^ !cert)) { err = gpg_error (GPG_ERR_INV_CERT_OBJ); goto out; } mpis = xtrycalloc (elems_n + 1, sizeof *mpis ); if (!mpis) { err = gpg_error_from_syserror (); goto out; } elem_is_secret = 0; for (i = 0; i < elems_n; i++) { if (secret) elem_is_secret = !strchr (elems_public, elems[i]); if (cert && !elem_is_secret) err = stream_read_mpi (cert, elem_is_secret, &mpis[i]); else err = stream_read_mpi (stream, elem_is_secret, &mpis[i]); if (err) goto out; } *mpi_list = mpis; mpis = NULL; out: if (err) mpint_list_free (mpis); return err; } /* Key modifier function for RSA. */ static gpg_error_t ssh_key_modifier_rsa (const char *elems, gcry_mpi_t *mpis) { gcry_mpi_t p; gcry_mpi_t q; gcry_mpi_t u; if (strcmp (elems, "nedupq")) /* Modifying only necessary for secret keys. */ goto out; u = mpis[3]; p = mpis[4]; q = mpis[5]; if (gcry_mpi_cmp (p, q) > 0) { /* P shall be smaller then Q! Swap primes. iqmp becomes u. */ gcry_mpi_t tmp; tmp = mpis[4]; mpis[4] = mpis[5]; mpis[5] = tmp; } else /* U needs to be recomputed. */ gcry_mpi_invm (u, p, q); out: return 0; } /* Signature encoder function for RSA. */ static gpg_error_t ssh_signature_encoder_rsa (ssh_key_type_spec_t *spec, estream_t signature_blob, gcry_sexp_t s_signature) { gpg_error_t err = 0; gcry_sexp_t valuelist = NULL; gcry_sexp_t sublist = NULL; gcry_mpi_t sig_value = NULL; gcry_mpi_t *mpis = NULL; const char *elems; size_t elems_n; int i; unsigned char *data; size_t data_n; gcry_mpi_t s; valuelist = gcry_sexp_nth (s_signature, 1); if (!valuelist) { err = gpg_error (GPG_ERR_INV_SEXP); goto out; } elems = spec->elems_signature; elems_n = strlen (elems); mpis = xtrycalloc (elems_n + 1, sizeof *mpis); if (!mpis) { err = gpg_error_from_syserror (); goto out; } for (i = 0; i < elems_n; i++) { sublist = gcry_sexp_find_token (valuelist, spec->elems_signature + i, 1); if (!sublist) { err = gpg_error (GPG_ERR_INV_SEXP); break; } sig_value = gcry_sexp_nth_mpi (sublist, 1, GCRYMPI_FMT_USG); if (!sig_value) { err = gpg_error (GPG_ERR_INTERNAL); /* FIXME? */ break; } gcry_sexp_release (sublist); sublist = NULL; mpis[i] = sig_value; } if (err) goto out; /* RSA specific */ s = mpis[0]; err = gcry_mpi_aprint (GCRYMPI_FMT_USG, &data, &data_n, s); if (err) goto out; err = stream_write_string (signature_blob, data, data_n); xfree (data); out: gcry_sexp_release (valuelist); gcry_sexp_release (sublist); mpint_list_free (mpis); return err; } /* Signature encoder function for DSA. */ static gpg_error_t ssh_signature_encoder_dsa (ssh_key_type_spec_t *spec, estream_t signature_blob, gcry_sexp_t s_signature) { gpg_error_t err = 0; gcry_sexp_t valuelist = NULL; gcry_sexp_t sublist = NULL; gcry_mpi_t sig_value = NULL; gcry_mpi_t *mpis = NULL; const char *elems; size_t elems_n; int i; unsigned char buffer[SSH_DSA_SIGNATURE_PADDING * SSH_DSA_SIGNATURE_ELEMS]; unsigned char *data = NULL; size_t data_n; valuelist = gcry_sexp_nth (s_signature, 1); if (!valuelist) { err = gpg_error (GPG_ERR_INV_SEXP); goto out; } elems = spec->elems_signature; elems_n = strlen (elems); mpis = xtrycalloc (elems_n + 1, sizeof *mpis); if (!mpis) { err = gpg_error_from_syserror (); goto out; } for (i = 0; i < elems_n; i++) { sublist = gcry_sexp_find_token (valuelist, spec->elems_signature + i, 1); if (!sublist) { err = gpg_error (GPG_ERR_INV_SEXP); break; } sig_value = gcry_sexp_nth_mpi (sublist, 1, GCRYMPI_FMT_USG); if (!sig_value) { err = gpg_error (GPG_ERR_INTERNAL); /* FIXME? */ break; } gcry_sexp_release (sublist); sublist = NULL; mpis[i] = sig_value; } if (err) goto out; /* DSA specific code. */ /* FIXME: Why this complicated code? Why collecting boths mpis in a buffer instead of writing them out one after the other? */ for (i = 0; i < 2; i++) { err = gcry_mpi_aprint (GCRYMPI_FMT_USG, &data, &data_n, mpis[i]); if (err) break; if (data_n > SSH_DSA_SIGNATURE_PADDING) { err = gpg_error (GPG_ERR_INTERNAL); /* FIXME? */ break; } memset (buffer + (i * SSH_DSA_SIGNATURE_PADDING), 0, SSH_DSA_SIGNATURE_PADDING - data_n); memcpy (buffer + (i * SSH_DSA_SIGNATURE_PADDING) + (SSH_DSA_SIGNATURE_PADDING - data_n), data, data_n); xfree (data); data = NULL; } if (err) goto out; err = stream_write_string (signature_blob, buffer, sizeof (buffer)); out: xfree (data); gcry_sexp_release (valuelist); gcry_sexp_release (sublist); mpint_list_free (mpis); return err; } /* Signature encoder function for ECDSA. */ static gpg_error_t ssh_signature_encoder_ecdsa (ssh_key_type_spec_t *spec, estream_t stream, gcry_sexp_t s_signature) { gpg_error_t err = 0; gcry_sexp_t valuelist = NULL; gcry_sexp_t sublist = NULL; gcry_mpi_t sig_value = NULL; gcry_mpi_t *mpis = NULL; const char *elems; size_t elems_n; int i; unsigned char *data[2] = {NULL, NULL}; size_t data_n[2]; size_t innerlen; valuelist = gcry_sexp_nth (s_signature, 1); if (!valuelist) { err = gpg_error (GPG_ERR_INV_SEXP); goto out; } elems = spec->elems_signature; elems_n = strlen (elems); mpis = xtrycalloc (elems_n + 1, sizeof *mpis); if (!mpis) { err = gpg_error_from_syserror (); goto out; } for (i = 0; i < elems_n; i++) { sublist = gcry_sexp_find_token (valuelist, spec->elems_signature + i, 1); if (!sublist) { err = gpg_error (GPG_ERR_INV_SEXP); break; } sig_value = gcry_sexp_nth_mpi (sublist, 1, GCRYMPI_FMT_USG); if (!sig_value) { err = gpg_error (GPG_ERR_INTERNAL); /* FIXME? */ break; } gcry_sexp_release (sublist); sublist = NULL; mpis[i] = sig_value; } if (err) goto out; /* ECDSA specific */ innerlen = 0; for (i = 0; i < DIM(data); i++) { err = gcry_mpi_aprint (GCRYMPI_FMT_STD, &data[i], &data_n[i], mpis[i]); if (err) goto out; innerlen += 4 + data_n[i]; } err = stream_write_uint32 (stream, innerlen); if (err) goto out; for (i = 0; i < DIM(data); i++) { err = stream_write_string (stream, data[i], data_n[i]); if (err) goto out; } out: for (i = 0; i < DIM(data); i++) xfree (data[i]); gcry_sexp_release (valuelist); gcry_sexp_release (sublist); mpint_list_free (mpis); return err; } /* Signature encoder function for EdDSA. */ static gpg_error_t ssh_signature_encoder_eddsa (ssh_key_type_spec_t *spec, estream_t stream, gcry_sexp_t s_signature) { gpg_error_t err = 0; gcry_sexp_t valuelist = NULL; gcry_sexp_t sublist = NULL; const char *elems; size_t elems_n; int i; unsigned char *data[2] = {NULL, NULL}; size_t data_n[2]; size_t totallen = 0; valuelist = gcry_sexp_nth (s_signature, 1); if (!valuelist) { err = gpg_error (GPG_ERR_INV_SEXP); goto out; } elems = spec->elems_signature; elems_n = strlen (elems); if (elems_n != DIM(data)) { err = gpg_error (GPG_ERR_INV_SEXP); goto out; } for (i = 0; i < DIM(data); i++) { sublist = gcry_sexp_find_token (valuelist, spec->elems_signature + i, 1); if (!sublist) { err = gpg_error (GPG_ERR_INV_SEXP); break; } data[i] = gcry_sexp_nth_buffer (sublist, 1, &data_n[i]); if (!data[i]) { err = gpg_error (GPG_ERR_INTERNAL); /* FIXME? */ break; } totallen += data_n[i]; gcry_sexp_release (sublist); sublist = NULL; } if (err) goto out; err = stream_write_uint32 (stream, totallen); if (err) goto out; for (i = 0; i < DIM(data); i++) { err = stream_write_data (stream, data[i], data_n[i]); if (err) goto out; } out: for (i = 0; i < DIM(data); i++) xfree (data[i]); gcry_sexp_release (valuelist); gcry_sexp_release (sublist); return err; } /* S-Expressions. */ /* This function constructs a new S-Expression for the key identified by the KEY_SPEC, SECRET, CURVE_NAME, MPIS, and COMMENT, which is to be stored at R_SEXP. Returns an error code. */ static gpg_error_t sexp_key_construct (gcry_sexp_t *r_sexp, ssh_key_type_spec_t key_spec, int secret, const char *curve_name, gcry_mpi_t *mpis, const char *comment) { gpg_error_t err; gcry_sexp_t sexp_new = NULL; void *formatbuf = NULL; void **arg_list = NULL; estream_t format = NULL; char *algo_name = NULL; if ((key_spec.flags & SPEC_FLAG_IS_EdDSA)) { /* It is much easier and more readable to use a separate code path for EdDSA. */ if (!curve_name) err = gpg_error (GPG_ERR_INV_CURVE); else if (!mpis[0] || !gcry_mpi_get_flag (mpis[0], GCRYMPI_FLAG_OPAQUE)) err = gpg_error (GPG_ERR_BAD_PUBKEY); else if (secret && (!mpis[1] || !gcry_mpi_get_flag (mpis[1], GCRYMPI_FLAG_OPAQUE))) err = gpg_error (GPG_ERR_BAD_SECKEY); else if (secret) err = gcry_sexp_build (&sexp_new, NULL, "(private-key(ecc(curve %s)" "(flags eddsa)(q %m)(d %m))" "(comment%s))", curve_name, mpis[0], mpis[1], comment? comment:""); else err = gcry_sexp_build (&sexp_new, NULL, "(public-key(ecc(curve %s)" "(flags eddsa)(q %m))" "(comment%s))", curve_name, mpis[0], comment? comment:""); } else { const char *key_identifier[] = { "public-key", "private-key" }; int arg_idx; const char *elems; size_t elems_n; unsigned int i, j; if (secret) elems = key_spec.elems_sexp_order; else elems = key_spec.elems_key_public; elems_n = strlen (elems); format = es_fopenmem (0, "a+b"); if (!format) { err = gpg_error_from_syserror (); goto out; } /* Key identifier, algorithm identifier, mpis, comment, and a NULL as a safeguard. */ arg_list = xtrymalloc (sizeof (*arg_list) * (2 + 1 + elems_n + 1 + 1)); if (!arg_list) { err = gpg_error_from_syserror (); goto out; } arg_idx = 0; es_fputs ("(%s(%s", format); arg_list[arg_idx++] = &key_identifier[secret]; algo_name = xtrystrdup (gcry_pk_algo_name (key_spec.algo)); if (!algo_name) { err = gpg_error_from_syserror (); goto out; } strlwr (algo_name); arg_list[arg_idx++] = &algo_name; if (curve_name) { es_fputs ("(curve%s)", format); arg_list[arg_idx++] = &curve_name; } for (i = 0; i < elems_n; i++) { es_fprintf (format, "(%c%%m)", elems[i]); if (secret) { for (j = 0; j < elems_n; j++) if (key_spec.elems_key_secret[j] == elems[i]) break; } else j = i; arg_list[arg_idx++] = &mpis[j]; } es_fputs (")(comment%s))", format); arg_list[arg_idx++] = &comment; arg_list[arg_idx] = NULL; es_putc (0, format); if (es_ferror (format)) { err = gpg_error_from_syserror (); goto out; } if (es_fclose_snatch (format, &formatbuf, NULL)) { err = gpg_error_from_syserror (); goto out; } format = NULL; err = gcry_sexp_build_array (&sexp_new, NULL, formatbuf, arg_list); } if (!err) *r_sexp = sexp_new; out: es_fclose (format); xfree (arg_list); xfree (formatbuf); xfree (algo_name); return err; } /* This function extracts the key from the s-expression SEXP according to KEY_SPEC and stores it in ssh format at (R_BLOB, R_BLOBLEN). If WITH_SECRET is true, the secret key parts are also extracted if possible. Returns 0 on success or an error code. Note that data stored at R_BLOB must be freed using es_free! */ static gpg_error_t ssh_key_to_blob (gcry_sexp_t sexp, int with_secret, ssh_key_type_spec_t key_spec, void **r_blob, size_t *r_blob_size) { gpg_error_t err = 0; gcry_sexp_t value_list = NULL; gcry_sexp_t value_pair = NULL; char *curve_name = NULL; estream_t stream = NULL; void *blob = NULL; size_t blob_size; const char *elems, *p_elems; const char *data; size_t datalen; *r_blob = NULL; *r_blob_size = 0; stream = es_fopenmem (0, "r+b"); if (!stream) { err = gpg_error_from_syserror (); goto out; } /* Get the type of the key extpression. */ data = gcry_sexp_nth_data (sexp, 0, &datalen); if (!data) { err = gpg_error (GPG_ERR_INV_SEXP); goto out; } if ((datalen == 10 && !strncmp (data, "public-key", 10)) || (datalen == 21 && !strncmp (data, "protected-private-key", 21)) || (datalen == 20 && !strncmp (data, "shadowed-private-key", 20))) elems = key_spec.elems_key_public; else if (datalen == 11 && !strncmp (data, "private-key", 11)) elems = with_secret? key_spec.elems_key_secret : key_spec.elems_key_public; else { err = gpg_error (GPG_ERR_INV_SEXP); goto out; } /* Get key value list. */ value_list = gcry_sexp_cadr (sexp); if (!value_list) { err = gpg_error (GPG_ERR_INV_SEXP); goto out; } /* Write the ssh algorithm identifier. */ if ((key_spec.flags & SPEC_FLAG_IS_ECDSA)) { /* Parse the "curve" parameter. We currently expect the curve name for ECC and not the parameters of the curve. This can easily be changed but then we need to find the curve name from the parameters using gcry_pk_get_curve. */ const char *mapped; const char *sshname; gcry_sexp_release (value_pair); value_pair = gcry_sexp_find_token (value_list, "curve", 5); if (!value_pair) { err = gpg_error (GPG_ERR_INV_CURVE); goto out; } curve_name = gcry_sexp_nth_string (value_pair, 1); if (!curve_name) { err = gpg_error (GPG_ERR_INV_CURVE); /* (Or out of core.) */ goto out; } /* Fixme: The mapping should be done by using gcry_pk_get_curve et al to iterate over all name aliases. */ if (!strcmp (curve_name, "NIST P-256")) mapped = "nistp256"; else if (!strcmp (curve_name, "NIST P-384")) mapped = "nistp384"; else if (!strcmp (curve_name, "NIST P-521")) mapped = "nistp521"; else mapped = NULL; if (mapped) { xfree (curve_name); curve_name = xtrystrdup (mapped); if (!curve_name) { err = gpg_error_from_syserror (); goto out; } } sshname = ssh_identifier_from_curve_name (curve_name); if (!sshname) { err = gpg_error (GPG_ERR_UNKNOWN_CURVE); goto out; } err = stream_write_cstring (stream, sshname); if (err) goto out; err = stream_write_cstring (stream, curve_name); if (err) goto out; } else { /* Note: This is also used for EdDSA. */ err = stream_write_cstring (stream, key_spec.ssh_identifier); if (err) goto out; } /* Write the parameters. */ for (p_elems = elems; *p_elems; p_elems++) { gcry_sexp_release (value_pair); value_pair = gcry_sexp_find_token (value_list, p_elems, 1); if (!value_pair) { err = gpg_error (GPG_ERR_INV_SEXP); goto out; } if ((key_spec.flags & SPEC_FLAG_IS_EdDSA)) { data = gcry_sexp_nth_data (value_pair, 1, &datalen); if (!data) { err = gpg_error (GPG_ERR_INV_SEXP); goto out; } if (*p_elems == 'q' && datalen) { /* Remove the prefix 0x40. */ data++; datalen--; } err = stream_write_string (stream, data, datalen); if (err) goto out; } else { gcry_mpi_t mpi; /* Note that we need to use STD format; i.e. prepend a 0x00 to indicate a positive number if the high bit is set. */ mpi = gcry_sexp_nth_mpi (value_pair, 1, GCRYMPI_FMT_STD); if (!mpi) { err = gpg_error (GPG_ERR_INV_SEXP); goto out; } err = stream_write_mpi (stream, mpi); gcry_mpi_release (mpi); if (err) goto out; } } if (es_fclose_snatch (stream, &blob, &blob_size)) { err = gpg_error_from_syserror (); goto out; } stream = NULL; *r_blob = blob; blob = NULL; *r_blob_size = blob_size; out: gcry_sexp_release (value_list); gcry_sexp_release (value_pair); xfree (curve_name); es_fclose (stream); es_free (blob); return err; } /* Key I/O. */ /* Search for a key specification entry. If SSH_NAME is not NULL, search for an entry whose "ssh_name" is equal to SSH_NAME; otherwise, search for an entry whose algorithm is equal to ALGO. Store found entry in SPEC on success, return error otherwise. */ static gpg_error_t ssh_key_type_lookup (const char *ssh_name, int algo, ssh_key_type_spec_t *spec) { gpg_error_t err; unsigned int i; for (i = 0; i < DIM (ssh_key_types); i++) if ((ssh_name && (! strcmp (ssh_name, ssh_key_types[i].ssh_identifier))) || algo == ssh_key_types[i].algo) break; if (i == DIM (ssh_key_types)) err = gpg_error (GPG_ERR_NOT_FOUND); else { *spec = ssh_key_types[i]; err = 0; } return err; } /* Receive a key from STREAM, according to the key specification given as KEY_SPEC. Depending on SECRET, receive a secret or a public key. If READ_COMMENT is true, receive a comment string as well. Constructs a new S-Expression from received data and stores it in KEY_NEW. Returns zero on success or an error code. */ static gpg_error_t ssh_receive_key (estream_t stream, gcry_sexp_t *key_new, int secret, int read_comment, ssh_key_type_spec_t *key_spec) { gpg_error_t err; char *key_type = NULL; char *comment = NULL; estream_t cert = NULL; gcry_sexp_t key = NULL; ssh_key_type_spec_t spec; gcry_mpi_t *mpi_list = NULL; const char *elems; char *curve_name = NULL; err = stream_read_cstring (stream, &key_type); if (err) goto out; err = ssh_key_type_lookup (key_type, 0, &spec); if (err) goto out; if ((spec.flags & SPEC_FLAG_WITH_CERT)) { /* This is an OpenSSH certificate+private key. The certificate is an SSH string and which we store in an estream object. */ unsigned char *buffer; u32 buflen; char *cert_key_type; err = stream_read_string (stream, 0, &buffer, &buflen); if (err) goto out; cert = es_fopenmem_init (0, "rb", buffer, buflen); xfree (buffer); if (!cert) { err = gpg_error_from_syserror (); goto out; } /* Check that the key type matches. */ err = stream_read_cstring (cert, &cert_key_type); if (err) goto out; if (strcmp (cert_key_type, key_type) ) { xfree (cert_key_type); log_error ("key types in received ssh certificate do not match\n"); err = gpg_error (GPG_ERR_INV_CERT_OBJ); goto out; } xfree (cert_key_type); /* Skip the nonce. */ err = stream_read_string (cert, 0, NULL, NULL); if (err) goto out; } if ((spec.flags & SPEC_FLAG_IS_EdDSA)) { /* The format of an EdDSA key is: * string key_type ("ssh-ed25519") * string public_key * string private_key * * Note that the private key is the concatenation of the private * key with the public key. Thus theres are 64 bytes; however * we only want the real 32 byte private key - Libgcrypt expects * this. */ mpi_list = xtrycalloc (3, sizeof *mpi_list); if (!mpi_list) { err = gpg_error_from_syserror (); goto out; } err = stream_read_blob (cert? cert : stream, 0, &mpi_list[0]); if (err) goto out; if (secret) { u32 len = 0; unsigned char *buffer; /* Read string length. */ err = stream_read_uint32 (stream, &len); if (err) goto out; if (len != 32 && len != 64) { err = gpg_error (GPG_ERR_BAD_SECKEY); goto out; } buffer = xtrymalloc_secure (32); if (!buffer) { err = gpg_error_from_syserror (); goto out; } err = stream_read_data (stream, buffer, 32); if (err) { xfree (buffer); goto out; } mpi_list[1] = gcry_mpi_set_opaque (NULL, buffer, 8*32); buffer = NULL; if (len == 64) { err = stream_read_skip (stream, 32); if (err) goto out; } } } else if ((spec.flags & SPEC_FLAG_IS_ECDSA)) { /* The format of an ECDSA key is: * string key_type ("ecdsa-sha2-nistp256" | * "ecdsa-sha2-nistp384" | * "ecdsa-sha2-nistp521" ) * string ecdsa_curve_name * string ecdsa_public_key * mpint ecdsa_private * * Note that we use the mpint reader instead of the string * reader for ecsa_public_key. For the certificate variante * ecdsa_curve_name+ecdsa_public_key are replaced by the * certificate. */ unsigned char *buffer; const char *mapped; err = stream_read_string (cert? cert : stream, 0, &buffer, NULL); if (err) goto out; curve_name = buffer; /* Fixme: Check that curve_name matches the keytype. */ /* Because Libgcrypt < 1.6 has no support for the "nistpNNN" curve names, we need to translate them here to Libgcrypt's native names. */ if (!strcmp (curve_name, "nistp256")) mapped = "NIST P-256"; else if (!strcmp (curve_name, "nistp384")) mapped = "NIST P-384"; else if (!strcmp (curve_name, "nistp521")) mapped = "NIST P-521"; else mapped = NULL; if (mapped) { xfree (curve_name); curve_name = xtrystrdup (mapped); if (!curve_name) { err = gpg_error_from_syserror (); goto out; } } err = ssh_receive_mpint_list (stream, secret, &spec, cert, &mpi_list); if (err) goto out; } else { err = ssh_receive_mpint_list (stream, secret, &spec, cert, &mpi_list); if (err) goto out; } if (read_comment) { err = stream_read_cstring (stream, &comment); if (err) goto out; } if (secret) elems = spec.elems_key_secret; else elems = spec.elems_key_public; if (spec.key_modifier) { err = (*spec.key_modifier) (elems, mpi_list); if (err) goto out; } if ((spec.flags & SPEC_FLAG_IS_EdDSA)) { if (secret) { err = gcry_sexp_build (&key, NULL, "(private-key(ecc(curve \"Ed25519\")" "(flags eddsa)(q %m)(d %m))" "(comment%s))", mpi_list[0], mpi_list[1], comment? comment:""); } else { err = gcry_sexp_build (&key, NULL, "(public-key(ecc(curve \"Ed25519\")" "(flags eddsa)(q %m))" "(comment%s))", mpi_list[0], comment? comment:""); } } else { err = sexp_key_construct (&key, spec, secret, curve_name, mpi_list, comment? comment:""); if (err) goto out; } if (key_spec) *key_spec = spec; *key_new = key; out: es_fclose (cert); mpint_list_free (mpi_list); xfree (curve_name); xfree (key_type); xfree (comment); return err; } /* Write the public key from KEY to STREAM in SSH key format. If OVERRIDE_COMMENT is not NULL, it will be used instead of the comment stored in the key. */ static gpg_error_t ssh_send_key_public (estream_t stream, gcry_sexp_t key, const char *override_comment) { ssh_key_type_spec_t spec; int algo; char *comment = NULL; void *blob = NULL; size_t bloblen; gpg_error_t err = 0; algo = get_pk_algo_from_key (key); if (algo == 0) goto out; err = ssh_key_type_lookup (NULL, algo, &spec); if (err) goto out; err = ssh_key_to_blob (key, 0, spec, &blob, &bloblen); if (err) goto out; err = stream_write_string (stream, blob, bloblen); if (err) goto out; if (override_comment) err = stream_write_cstring (stream, override_comment); else { err = ssh_key_extract_comment (key, &comment); if (err) err = stream_write_cstring (stream, "(none)"); else err = stream_write_cstring (stream, comment); } if (err) goto out; out: xfree (comment); es_free (blob); return err; } /* Read a public key out of BLOB/BLOB_SIZE according to the key specification given as KEY_SPEC, storing the new key in KEY_PUBLIC. Returns zero on success or an error code. */ static gpg_error_t ssh_read_key_public_from_blob (unsigned char *blob, size_t blob_size, gcry_sexp_t *key_public, ssh_key_type_spec_t *key_spec) { gpg_error_t err; estream_t blob_stream; blob_stream = es_fopenmem (0, "r+b"); if (!blob_stream) { err = gpg_error_from_syserror (); goto out; } err = stream_write_data (blob_stream, blob, blob_size); if (err) goto out; err = es_fseek (blob_stream, 0, SEEK_SET); if (err) goto out; err = ssh_receive_key (blob_stream, key_public, 0, 0, key_spec); out: es_fclose (blob_stream); return err; } /* This function calculates the key grip for the key contained in the S-Expression KEY and writes it to BUFFER, which must be large enough to hold it. Returns usual error code. */ static gpg_error_t ssh_key_grip (gcry_sexp_t key, unsigned char *buffer) { if (!gcry_pk_get_keygrip (key, buffer)) { gpg_error_t err = gcry_pk_testkey (key); return err? err : gpg_error (GPG_ERR_INTERNAL); } return 0; } static gpg_error_t card_key_list (ctrl_t ctrl, char **r_serialno, strlist_t *result) { gpg_error_t err; *r_serialno = NULL; *result = NULL; err = agent_card_serialno (ctrl, r_serialno, NULL); if (err) { if (gpg_err_code (err) != GPG_ERR_ENODEV && opt.verbose) log_info (_("error getting serial number of card: %s\n"), gpg_strerror (err)); /* Nothing available. */ return 0; } err = agent_card_cardlist (ctrl, result); if (err) { xfree (*r_serialno); *r_serialno = NULL; } return err; } /* Check whether a smartcard is available and whether it has a usable key. Store a copy of that key at R_PK and return 0. If no key is available store NULL at R_PK and return an error code. If CARDSN is not NULL, a string with the serial number of the card will be a malloced and stored there. */ static gpg_error_t card_key_available (ctrl_t ctrl, gcry_sexp_t *r_pk, char **cardsn) { gpg_error_t err; char *authkeyid; char *serialno = NULL; unsigned char *pkbuf; size_t pkbuflen; gcry_sexp_t s_pk; unsigned char grip[20]; *r_pk = NULL; if (cardsn) *cardsn = NULL; /* First see whether a card is available and whether the application is supported. */ err = agent_card_getattr (ctrl, "$AUTHKEYID", &authkeyid); if ( gpg_err_code (err) == GPG_ERR_CARD_REMOVED ) { /* Ask for the serial number to reset the card. */ err = agent_card_serialno (ctrl, &serialno, NULL); if (err) { if (opt.verbose) log_info (_("error getting serial number of card: %s\n"), gpg_strerror (err)); return err; } log_info (_("detected card with S/N: %s\n"), serialno); err = agent_card_getattr (ctrl, "$AUTHKEYID", &authkeyid); } if (err) { log_error (_("no authentication key for ssh on card: %s\n"), gpg_strerror (err)); xfree (serialno); return err; } /* Get the S/N if we don't have it yet. Use the fast getattr method. */ if (!serialno && (err = agent_card_getattr (ctrl, "SERIALNO", &serialno)) ) { log_error (_("error getting serial number of card: %s\n"), gpg_strerror (err)); xfree (authkeyid); return err; } /* Read the public key. */ err = agent_card_readkey (ctrl, authkeyid, &pkbuf); if (err) { if (opt.verbose) log_info (_("no suitable card key found: %s\n"), gpg_strerror (err)); xfree (serialno); xfree (authkeyid); return err; } pkbuflen = gcry_sexp_canon_len (pkbuf, 0, NULL, NULL); err = gcry_sexp_sscan (&s_pk, NULL, (char*)pkbuf, pkbuflen); if (err) { log_error ("failed to build S-Exp from received card key: %s\n", gpg_strerror (err)); xfree (pkbuf); xfree (serialno); xfree (authkeyid); return err; } err = ssh_key_grip (s_pk, grip); if (err) { log_debug ("error computing keygrip from received card key: %s\n", gcry_strerror (err)); xfree (pkbuf); gcry_sexp_release (s_pk); xfree (serialno); xfree (authkeyid); return err; } if ( agent_key_available (grip) ) { /* (Shadow)-key is not available in our key storage. */ err = agent_write_shadow_key (grip, serialno, authkeyid, pkbuf, 0); if (err) { xfree (pkbuf); gcry_sexp_release (s_pk); xfree (serialno); xfree (authkeyid); return err; } } if (cardsn) { char *dispsn; /* If the card handler is able to return a short serialnumber, use that one, else use the complete serialno. */ if (!agent_card_getattr (ctrl, "$DISPSERIALNO", &dispsn)) { *cardsn = xtryasprintf ("cardno:%s", dispsn); xfree (dispsn); } else *cardsn = xtryasprintf ("cardno:%s", serialno); if (!*cardsn) { err = gpg_error_from_syserror (); xfree (pkbuf); gcry_sexp_release (s_pk); xfree (serialno); xfree (authkeyid); return err; } } xfree (pkbuf); xfree (serialno); xfree (authkeyid); *r_pk = s_pk; return 0; } /* Request handler. Each handler is provided with a CTRL context, a REQUEST object and a RESPONSE object. The actual request is to be read from REQUEST, the response needs to be written to RESPONSE. */ /* Handler for the "request_identities" command. */ static gpg_error_t ssh_handler_request_identities (ctrl_t ctrl, estream_t request, estream_t response) { u32 key_counter; estream_t key_blobs; gcry_sexp_t key_public; gpg_error_t err; int ret; ssh_control_file_t cf = NULL; gpg_error_t ret_err; (void)request; /* Prepare buffer stream. */ key_public = NULL; key_counter = 0; - err = 0; key_blobs = es_fopenmem (0, "r+b"); if (! key_blobs) { err = gpg_error_from_syserror (); goto out; } /* First check whether a key is currently available in the card reader - this should be allowed even without being listed in sshcontrol. */ if (!opt.disable_scdaemon) { char *serialno; strlist_t card_list, sl; err = card_key_list (ctrl, &serialno, &card_list); if (err) { if (opt.verbose) log_info (_("error getting list of cards: %s\n"), gpg_strerror (err)); goto scd_out; } for (sl = card_list; sl; sl = sl->next) { char *serialno0; char *cardsn; err = agent_card_serialno (ctrl, &serialno0, sl->d); if (err) { if (opt.verbose) log_info (_("error getting serial number of card: %s\n"), gpg_strerror (err)); continue; } xfree (serialno0); if (card_key_available (ctrl, &key_public, &cardsn)) continue; err = ssh_send_key_public (key_blobs, key_public, cardsn); gcry_sexp_release (key_public); key_public = NULL; xfree (cardsn); if (err) { xfree (serialno); free_strlist (card_list); goto out; } key_counter++; } xfree (serialno); free_strlist (card_list); } scd_out: /* Then look at all the registered and non-disabled keys. */ err = open_control_file (&cf, 0); if (err) goto out; while (!read_control_file_item (cf)) { unsigned char grip[20]; if (!cf->item.valid) continue; /* Should not happen. */ if (cf->item.disabled) continue; assert (strlen (cf->item.hexgrip) == 40); hex2bin (cf->item.hexgrip, grip, sizeof (grip)); err = agent_public_key_from_file (ctrl, grip, &key_public); if (err) { log_error ("%s:%d: key '%s' skipped: %s\n", cf->fname, cf->lnr, cf->item.hexgrip, gpg_strerror (err)); continue; } err = ssh_send_key_public (key_blobs, key_public, NULL); if (err) goto out; gcry_sexp_release (key_public); key_public = NULL; key_counter++; } err = 0; ret = es_fseek (key_blobs, 0, SEEK_SET); if (ret) { err = gpg_error_from_syserror (); goto out; } out: /* Send response. */ gcry_sexp_release (key_public); if (!err) { ret_err = stream_write_byte (response, SSH_RESPONSE_IDENTITIES_ANSWER); if (!ret_err) ret_err = stream_write_uint32 (response, key_counter); if (!ret_err) ret_err = stream_copy (response, key_blobs); } else { ret_err = stream_write_byte (response, SSH_RESPONSE_FAILURE); } es_fclose (key_blobs); close_control_file (cf); return ret_err; } /* This function hashes the data contained in DATA of size DATA_N according to the message digest algorithm specified by MD_ALGORITHM and writes the message digest to HASH, which needs to large enough for the digest. */ static gpg_error_t data_hash (unsigned char *data, size_t data_n, int md_algorithm, unsigned char *hash) { gcry_md_hash_buffer (md_algorithm, hash, data, data_n); return 0; } /* This function signs the data described by CTRL. If HASH is not NULL, (HASH,HASHLEN) overrides the hash stored in CTRL. This is to allow the use of signature algorithms that implement the hashing internally (e.g. Ed25519). On success the created signature is stored in ssh format at R_SIG and it's size at R_SIGLEN; the caller must use es_free to releaase this memory. */ static gpg_error_t data_sign (ctrl_t ctrl, ssh_key_type_spec_t *spec, const void *hash, size_t hashlen, unsigned char **r_sig, size_t *r_siglen) { gpg_error_t err; gcry_sexp_t signature_sexp = NULL; estream_t stream = NULL; void *blob = NULL; size_t bloblen; char hexgrip[40+1]; *r_sig = NULL; *r_siglen = 0; /* Quick check to see whether we have a valid keygrip and convert it to hex. */ if (!ctrl->have_keygrip) { err = gpg_error (GPG_ERR_NO_SECKEY); goto out; } bin2hex (ctrl->keygrip, 20, hexgrip); /* Ask for confirmation if needed. */ if (confirm_flag_from_sshcontrol (hexgrip)) { gcry_sexp_t key; char *fpr, *prompt; char *comment = NULL; err = agent_raw_key_from_file (ctrl, ctrl->keygrip, &key); if (err) goto out; err = ssh_get_fingerprint_string (key, &fpr); if (!err) { gcry_sexp_t tmpsxp = gcry_sexp_find_token (key, "comment", 0); if (tmpsxp) comment = gcry_sexp_nth_string (tmpsxp, 1); gcry_sexp_release (tmpsxp); } gcry_sexp_release (key); if (err) goto out; prompt = xtryasprintf (L_("An ssh process requested the use of key%%0A" " %s%%0A" " (%s)%%0A" "Do you want to allow this?"), fpr, comment? comment:""); xfree (fpr); gcry_free (comment); err = agent_get_confirmation (ctrl, prompt, L_("Allow"), L_("Deny"), 0); xfree (prompt); if (err) goto out; } /* Create signature. */ ctrl->use_auth_call = 1; err = agent_pksign_do (ctrl, NULL, L_("Please enter the passphrase " "for the ssh key%%0A %F%%0A (%c)"), &signature_sexp, CACHE_MODE_SSH, ttl_from_sshcontrol, hash, hashlen); ctrl->use_auth_call = 0; if (err) goto out; stream = es_fopenmem (0, "r+b"); if (!stream) { err = gpg_error_from_syserror (); goto out; } err = stream_write_cstring (stream, spec->ssh_identifier); if (err) goto out; err = spec->signature_encoder (spec, stream, signature_sexp); if (err) goto out; err = es_fclose_snatch (stream, &blob, &bloblen); if (err) goto out; stream = NULL; *r_sig = blob; blob = NULL; *r_siglen = bloblen; out: xfree (blob); es_fclose (stream); gcry_sexp_release (signature_sexp); return err; } /* Handler for the "sign_request" command. */ static gpg_error_t ssh_handler_sign_request (ctrl_t ctrl, estream_t request, estream_t response) { gcry_sexp_t key = NULL; ssh_key_type_spec_t spec; unsigned char hash[MAX_DIGEST_LEN]; unsigned int hash_n; unsigned char key_grip[20]; unsigned char *key_blob = NULL; u32 key_blob_size; unsigned char *data = NULL; unsigned char *sig = NULL; size_t sig_n; u32 data_size; u32 flags; gpg_error_t err; gpg_error_t ret_err; int hash_algo; /* Receive key. */ err = stream_read_string (request, 0, &key_blob, &key_blob_size); if (err) goto out; err = ssh_read_key_public_from_blob (key_blob, key_blob_size, &key, &spec); if (err) goto out; /* Receive data to sign. */ err = stream_read_string (request, 0, &data, &data_size); if (err) goto out; /* FIXME? */ err = stream_read_uint32 (request, &flags); if (err) goto out; hash_algo = spec.hash_algo; if (!hash_algo) hash_algo = GCRY_MD_SHA1; /* Use the default. */ ctrl->digest.algo = hash_algo; if ((spec.flags & SPEC_FLAG_USE_PKCS1V2)) ctrl->digest.raw_value = 0; else ctrl->digest.raw_value = 1; /* Calculate key grip. */ err = ssh_key_grip (key, key_grip); if (err) goto out; ctrl->have_keygrip = 1; memcpy (ctrl->keygrip, key_grip, 20); /* Hash data unless we use EdDSA. */ if ((spec.flags & SPEC_FLAG_IS_EdDSA)) { ctrl->digest.valuelen = 0; } else { hash_n = gcry_md_get_algo_dlen (hash_algo); if (!hash_n) { err = gpg_error (GPG_ERR_INTERNAL); goto out; } err = data_hash (data, data_size, hash_algo, hash); if (err) goto out; memcpy (ctrl->digest.value, hash, hash_n); ctrl->digest.valuelen = hash_n; } /* Sign data. */ if ((spec.flags & SPEC_FLAG_IS_EdDSA)) err = data_sign (ctrl, &spec, data, data_size, &sig, &sig_n); else err = data_sign (ctrl, &spec, NULL, 0, &sig, &sig_n); out: /* Done. */ if (!err) { ret_err = stream_write_byte (response, SSH_RESPONSE_SIGN_RESPONSE); if (ret_err) goto leave; ret_err = stream_write_string (response, sig, sig_n); if (ret_err) goto leave; } else { log_error ("ssh sign request failed: %s <%s>\n", gpg_strerror (err), gpg_strsource (err)); ret_err = stream_write_byte (response, SSH_RESPONSE_FAILURE); if (ret_err) goto leave; } leave: gcry_sexp_release (key); xfree (key_blob); xfree (data); es_free (sig); return ret_err; } /* This function extracts the comment contained in the key s-expression KEY and stores a copy in COMMENT. Returns usual error code. */ static gpg_error_t ssh_key_extract_comment (gcry_sexp_t key, char **r_comment) { gcry_sexp_t comment_list; *r_comment = NULL; comment_list = gcry_sexp_find_token (key, "comment", 0); if (!comment_list) return gpg_error (GPG_ERR_INV_SEXP); *r_comment = gcry_sexp_nth_string (comment_list, 1); gcry_sexp_release (comment_list); if (!*r_comment) return gpg_error (GPG_ERR_INV_SEXP); return 0; } /* This function converts the key contained in the S-Expression KEY into a buffer, which is protected by the passphrase PASSPHRASE. Returns usual error code. */ static gpg_error_t ssh_key_to_protected_buffer (gcry_sexp_t key, const char *passphrase, unsigned char **buffer, size_t *buffer_n) { unsigned char *buffer_new; unsigned int buffer_new_n; gpg_error_t err; err = 0; buffer_new_n = gcry_sexp_sprint (key, GCRYSEXP_FMT_CANON, NULL, 0); buffer_new = xtrymalloc_secure (buffer_new_n); if (! buffer_new) { err = gpg_error_from_syserror (); goto out; } gcry_sexp_sprint (key, GCRYSEXP_FMT_CANON, buffer_new, buffer_new_n); /* FIXME: guarantee? */ err = agent_protect (buffer_new, passphrase, buffer, buffer_n, 0, -1); out: xfree (buffer_new); return err; } /* Callback function to compare the first entered PIN with the one currently being entered. */ static gpg_error_t reenter_compare_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); } /* Store the ssh KEY into our local key storage and protect it after asking for a passphrase. Cache that passphrase. TTL is the maximum caching time for that key. If the key already exists in our key storage, don't do anything. When entering a key also add an entry to the sshcontrol file. */ static gpg_error_t ssh_identity_register (ctrl_t ctrl, ssh_key_type_spec_t *spec, gcry_sexp_t key, int ttl, int confirm) { gpg_error_t err; unsigned char key_grip_raw[20]; char key_grip[41]; unsigned char *buffer = NULL; size_t buffer_n; char *description = NULL; const char *description2 = L_("Please re-enter this passphrase"); char *comment = NULL; char *key_fpr = NULL; const char *initial_errtext = NULL; struct pin_entry_info_s *pi = NULL; struct pin_entry_info_s *pi2 = NULL; err = ssh_key_grip (key, key_grip_raw); if (err) goto out; bin2hex (key_grip_raw, 20, key_grip); err = ssh_get_fingerprint_string (key, &key_fpr); if (err) goto out; /* Check whether the key is already in our key storage. Don't do anything then besides (re-)adding it to sshcontrol. */ if ( !agent_key_available (key_grip_raw) ) goto key_exists; /* Yes, key is available. */ err = ssh_key_extract_comment (key, &comment); if (err) goto out; if ( asprintf (&description, L_("Please enter a passphrase to protect" " the received secret key%%0A" " %s%%0A" " %s%%0A" "within gpg-agent's key storage"), key_fpr, comment ? comment : "") < 0) { err = gpg_error_from_syserror (); goto out; } pi = gcry_calloc_secure (1, sizeof (*pi) + MAX_PASSPHRASE_LEN + 1); if (!pi) { err = gpg_error_from_syserror (); goto out; } pi2 = gcry_calloc_secure (1, sizeof (*pi2) + MAX_PASSPHRASE_LEN + 1); if (!pi2) { err = gpg_error_from_syserror (); goto out; } pi->max_length = MAX_PASSPHRASE_LEN + 1; pi->max_tries = 1; pi->with_repeat = 1; pi2->max_length = MAX_PASSPHRASE_LEN + 1; pi2->max_tries = 1; pi2->check_cb = reenter_compare_cb; pi2->check_cb_arg = pi->pin; next_try: err = agent_askpin (ctrl, description, NULL, initial_errtext, pi, NULL, 0); initial_errtext = NULL; if (err) goto out; /* Unless the passphrase is empty or the pinentry told us that it already did the repetition check, ask to confirm it. */ if (*pi->pin && !pi->repeat_okay) { err = agent_askpin (ctrl, description2, NULL, NULL, pi2, NULL, 0); if (gpg_err_code (err) == GPG_ERR_BAD_PASSPHRASE) { /* The re-entered one did not match and the user did not hit cancel. */ initial_errtext = L_("does not match - try again"); goto next_try; } } err = ssh_key_to_protected_buffer (key, pi->pin, &buffer, &buffer_n); if (err) goto out; /* Store this key to our key storage. */ err = agent_write_private_key (key_grip_raw, buffer, buffer_n, 0); if (err) goto out; /* Cache this passphrase. */ err = agent_put_cache (key_grip, CACHE_MODE_SSH, pi->pin, ttl); if (err) goto out; key_exists: /* And add an entry to the sshcontrol file. */ err = add_control_entry (ctrl, spec, key_grip, key_fpr, ttl, confirm); out: if (pi2 && pi2->max_length) wipememory (pi2->pin, pi2->max_length); xfree (pi2); if (pi && pi->max_length) wipememory (pi->pin, pi->max_length); xfree (pi); xfree (buffer); xfree (comment); xfree (key_fpr); xfree (description); return err; } /* This function removes the key contained in the S-Expression KEY from the local key storage, in case it exists there. Returns usual error code. FIXME: this function is a stub. */ static gpg_error_t ssh_identity_drop (gcry_sexp_t key) { unsigned char key_grip[21] = { 0 }; gpg_error_t err; err = ssh_key_grip (key, key_grip); if (err) goto out; key_grip[sizeof (key_grip) - 1] = 0; /* FIXME: What to do here - forgetting the passphrase or deleting the key from key cache? */ out: return err; } /* Handler for the "add_identity" command. */ static gpg_error_t ssh_handler_add_identity (ctrl_t ctrl, estream_t request, estream_t response) { gpg_error_t ret_err; ssh_key_type_spec_t spec; gpg_error_t err; gcry_sexp_t key; unsigned char b; int confirm; int ttl; confirm = 0; key = NULL; ttl = 0; /* FIXME? */ err = ssh_receive_key (request, &key, 1, 1, &spec); if (err) goto out; while (1) { err = stream_read_byte (request, &b); if (gpg_err_code (err) == GPG_ERR_EOF) { err = 0; break; } switch (b) { case SSH_OPT_CONSTRAIN_LIFETIME: { u32 n = 0; err = stream_read_uint32 (request, &n); if (! err) ttl = n; break; } case SSH_OPT_CONSTRAIN_CONFIRM: { confirm = 1; break; } default: /* FIXME: log/bad? */ break; } } if (err) goto out; err = ssh_identity_register (ctrl, &spec, key, ttl, confirm); out: gcry_sexp_release (key); if (! err) ret_err = stream_write_byte (response, SSH_RESPONSE_SUCCESS); else ret_err = stream_write_byte (response, SSH_RESPONSE_FAILURE); return ret_err; } /* Handler for the "remove_identity" command. */ static gpg_error_t ssh_handler_remove_identity (ctrl_t ctrl, estream_t request, estream_t response) { unsigned char *key_blob; u32 key_blob_size; gcry_sexp_t key; gpg_error_t ret_err; gpg_error_t err; (void)ctrl; /* Receive key. */ key_blob = NULL; key = NULL; err = stream_read_string (request, 0, &key_blob, &key_blob_size); if (err) goto out; err = ssh_read_key_public_from_blob (key_blob, key_blob_size, &key, NULL); if (err) goto out; err = ssh_identity_drop (key); out: xfree (key_blob); gcry_sexp_release (key); if (! err) ret_err = stream_write_byte (response, SSH_RESPONSE_SUCCESS); else ret_err = stream_write_byte (response, SSH_RESPONSE_FAILURE); return ret_err; } /* FIXME: stub function. Actually useful? */ static gpg_error_t ssh_identities_remove_all (void) { gpg_error_t err; err = 0; /* FIXME: shall we remove _all_ cache entries or only those registered through the ssh-agent protocol? */ return err; } /* Handler for the "remove_all_identities" command. */ static gpg_error_t ssh_handler_remove_all_identities (ctrl_t ctrl, estream_t request, estream_t response) { gpg_error_t ret_err; gpg_error_t err; (void)ctrl; (void)request; err = ssh_identities_remove_all (); if (! err) ret_err = stream_write_byte (response, SSH_RESPONSE_SUCCESS); else ret_err = stream_write_byte (response, SSH_RESPONSE_FAILURE); return ret_err; } /* Lock agent? FIXME: stub function. */ static gpg_error_t ssh_lock (void) { gpg_error_t err; /* FIXME */ log_error ("ssh-agent's lock command is not implemented\n"); err = 0; return err; } /* Unock agent? FIXME: stub function. */ static gpg_error_t ssh_unlock (void) { gpg_error_t err; log_error ("ssh-agent's unlock command is not implemented\n"); err = 0; return err; } /* Handler for the "lock" command. */ static gpg_error_t ssh_handler_lock (ctrl_t ctrl, estream_t request, estream_t response) { gpg_error_t ret_err; gpg_error_t err; (void)ctrl; (void)request; err = ssh_lock (); if (! err) ret_err = stream_write_byte (response, SSH_RESPONSE_SUCCESS); else ret_err = stream_write_byte (response, SSH_RESPONSE_FAILURE); return ret_err; } /* Handler for the "unlock" command. */ static gpg_error_t ssh_handler_unlock (ctrl_t ctrl, estream_t request, estream_t response) { gpg_error_t ret_err; gpg_error_t err; (void)ctrl; (void)request; err = ssh_unlock (); if (! err) ret_err = stream_write_byte (response, SSH_RESPONSE_SUCCESS); else ret_err = stream_write_byte (response, SSH_RESPONSE_FAILURE); return ret_err; } /* Return the request specification for the request identified by TYPE or NULL in case the requested request specification could not be found. */ static ssh_request_spec_t * request_spec_lookup (int type) { ssh_request_spec_t *spec; unsigned int i; for (i = 0; i < DIM (request_specs); i++) if (request_specs[i].type == type) break; if (i == DIM (request_specs)) { if (opt.verbose) log_info ("ssh request %u is not supported\n", type); spec = NULL; } else spec = request_specs + i; return spec; } /* Process a single request. The request is read from and the response is written to STREAM_SOCK. Uses CTRL as context. Returns zero in case of success, non zero in case of failure. */ static int ssh_request_process (ctrl_t ctrl, estream_t stream_sock) { ssh_request_spec_t *spec; estream_t response = NULL; estream_t request = NULL; unsigned char request_type; gpg_error_t err; int send_err = 0; int ret; unsigned char *request_data = NULL; u32 request_data_size; u32 response_size; /* Create memory streams for request/response data. The entire request will be stored in secure memory, since it might contain secret key material. The response does not have to be stored in secure memory, since we never give out secret keys. Note: we only have little secure memory, but there is NO possibility of DoS here; only trusted clients are allowed to connect to the agent. What could happen is that the agent returns out-of-secure-memory errors on requests in case the agent's owner floods his own agent with many large messages. -moritz */ /* Retrieve request. */ err = stream_read_string (stream_sock, 1, &request_data, &request_data_size); if (err) goto out; if (opt.verbose > 1) log_info ("received ssh request of length %u\n", (unsigned int)request_data_size); if (! request_data_size) { send_err = 1; goto out; /* Broken request; FIXME. */ } request_type = request_data[0]; spec = request_spec_lookup (request_type); if (! spec) { send_err = 1; goto out; /* Unknown request; FIXME. */ } if (spec->secret_input) request = es_mopen (NULL, 0, 0, 1, realloc_secure, gcry_free, "r+b"); else request = es_mopen (NULL, 0, 0, 1, gcry_realloc, gcry_free, "r+b"); if (! request) { err = gpg_error_from_syserror (); goto out; } ret = es_setvbuf (request, NULL, _IONBF, 0); if (ret) { err = gpg_error_from_syserror (); goto out; } err = stream_write_data (request, request_data + 1, request_data_size - 1); if (err) goto out; es_rewind (request); response = es_fopenmem (0, "r+b"); if (! response) { err = gpg_error_from_syserror (); goto out; } if (opt.verbose) log_info ("ssh request handler for %s (%u) started\n", spec->identifier, spec->type); err = (*spec->handler) (ctrl, request, response); if (opt.verbose) { if (err) log_info ("ssh request handler for %s (%u) failed: %s\n", spec->identifier, spec->type, gpg_strerror (err)); else log_info ("ssh request handler for %s (%u) ready\n", spec->identifier, spec->type); } if (err) { send_err = 1; goto out; } response_size = es_ftell (response); if (opt.verbose > 1) log_info ("sending ssh response of length %u\n", (unsigned int)response_size); err = es_fseek (response, 0, SEEK_SET); if (err) { send_err = 1; goto out; } err = stream_write_uint32 (stream_sock, response_size); if (err) { send_err = 1; goto out; } err = stream_copy (stream_sock, response); if (err) goto out; err = es_fflush (stream_sock); if (err) goto out; out: if (err && es_feof (stream_sock)) log_error ("error occurred while processing request: %s\n", gpg_strerror (err)); if (send_err) { if (opt.verbose > 1) log_info ("sending ssh error response\n"); err = stream_write_uint32 (stream_sock, 1); if (err) goto leave; err = stream_write_byte (stream_sock, SSH_RESPONSE_FAILURE); if (err) goto leave; } leave: es_fclose (request); es_fclose (response); xfree (request_data); return !!err; } /* Return the peer's pid. */ static unsigned long get_client_pid (int fd) { pid_t client_pid = (pid_t)0; #ifdef SO_PEERCRED { #ifdef HAVE_STRUCT_SOCKPEERCRED_PID struct sockpeercred cr; #else struct ucred cr; #endif socklen_t cl = sizeof cr; if (!getsockopt (fd, SOL_SOCKET, SO_PEERCRED, &cr, &cl)) { #if defined (HAVE_STRUCT_SOCKPEERCRED_PID) || defined (HAVE_STRUCT_UCRED_PID) client_pid = cr.pid; #elif defined (HAVE_STRUCT_UCRED_CR_PID) client_pid = cr.cr_pid; #else #error "Unknown SO_PEERCRED struct" #endif } } #elif defined (LOCAL_PEERPID) { socklen_t len = sizeof (pid_t); getsockopt (fd, SOL_LOCAL, LOCAL_PEERPID, &client_pid, &len); } #elif defined (LOCAL_PEEREID) { struct unpcbid unp; socklen_t unpl = sizeof unp; if (getsockopt (fd, 0, LOCAL_PEEREID, &unp, &unpl) != -1) client_pid = unp.unp_pid; } #elif defined (HAVE_GETPEERUCRED) { ucred_t *ucred = NULL; if (getpeerucred (fd, &ucred) != -1) { client_pid= ucred_getpid (ucred); ucred_free (ucred); } } #else (void)fd; #endif return (unsigned long)client_pid; } /* Start serving client on SOCK_CLIENT. */ void start_command_handler_ssh (ctrl_t ctrl, gnupg_fd_t sock_client) { estream_t stream_sock = NULL; gpg_error_t err; int ret; err = agent_copy_startup_env (ctrl); if (err) goto out; ctrl->client_pid = get_client_pid (FD2INT(sock_client)); /* Create stream from socket. */ stream_sock = es_fdopen (FD2INT(sock_client), "r+"); if (!stream_sock) { err = gpg_error_from_syserror (); log_error (_("failed to create stream from socket: %s\n"), gpg_strerror (err)); goto out; } /* We have to disable the estream buffering, because the estream core doesn't know about secure memory. */ ret = es_setvbuf (stream_sock, NULL, _IONBF, 0); if (ret) { err = gpg_error_from_syserror (); log_error ("failed to disable buffering " "on socket stream: %s\n", gpg_strerror (err)); goto out; } /* Main processing loop. */ while ( !ssh_request_process (ctrl, stream_sock) ) { /* Check wether we have reached EOF before trying to read another request. */ int c; c = es_fgetc (stream_sock); if (c == EOF) break; es_ungetc (c, stream_sock); } /* Reset the SCD in case it has been used. */ agent_reset_scd (ctrl); out: if (stream_sock) es_fclose (stream_sock); } #ifdef HAVE_W32_SYSTEM /* Serve one ssh-agent request. This is used for the Putty support. REQUEST is the mmapped memory which may be accessed up to a length of MAXREQLEN. Returns 0 on success which also indicates that a valid SSH response message is now in REQUEST. */ int serve_mmapped_ssh_request (ctrl_t ctrl, unsigned char *request, size_t maxreqlen) { gpg_error_t err; int send_err = 0; int valid_response = 0; ssh_request_spec_t *spec; u32 msglen; estream_t request_stream, response_stream; if (agent_copy_startup_env (ctrl)) goto leave; /* Error setting up the environment. */ if (maxreqlen < 5) goto leave; /* Caller error. */ msglen = uint32_construct (request[0], request[1], request[2], request[3]); if (msglen < 1 || msglen > maxreqlen - 4) { log_error ("ssh message len (%u) out of range", (unsigned int)msglen); goto leave; } spec = request_spec_lookup (request[4]); if (!spec) { send_err = 1; /* Unknown request type. */ goto leave; } /* Create a stream object with the data part of the request. */ if (spec->secret_input) request_stream = es_mopen (NULL, 0, 0, 1, realloc_secure, gcry_free, "r+"); else request_stream = es_mopen (NULL, 0, 0, 1, gcry_realloc, gcry_free, "r+"); if (!request_stream) { err = gpg_error_from_syserror (); goto leave; } /* We have to disable the estream buffering, because the estream core doesn't know about secure memory. */ if (es_setvbuf (request_stream, NULL, _IONBF, 0)) { err = gpg_error_from_syserror (); goto leave; } /* Copy the request to the stream but omit the request type. */ err = stream_write_data (request_stream, request + 5, msglen - 1); if (err) goto leave; es_rewind (request_stream); response_stream = es_fopenmem (0, "r+b"); if (!response_stream) { err = gpg_error_from_syserror (); goto leave; } if (opt.verbose) log_info ("ssh request handler for %s (%u) started\n", spec->identifier, spec->type); err = (*spec->handler) (ctrl, request_stream, response_stream); if (opt.verbose) { if (err) log_info ("ssh request handler for %s (%u) failed: %s\n", spec->identifier, spec->type, gpg_strerror (err)); else log_info ("ssh request handler for %s (%u) ready\n", spec->identifier, spec->type); } es_fclose (request_stream); request_stream = NULL; if (err) { send_err = 1; goto leave; } /* Put the response back into the mmapped buffer. */ { void *response_data; size_t response_size; /* NB: In contrast to the request-stream, the response stream includes the message type byte. */ if (es_fclose_snatch (response_stream, &response_data, &response_size)) { log_error ("snatching ssh response failed: %s", gpg_strerror (gpg_error_from_syserror ())); send_err = 1; /* Ooops. */ goto leave; } if (opt.verbose > 1) log_info ("sending ssh response of length %u\n", (unsigned int)response_size); if (response_size > maxreqlen - 4) { log_error ("invalid length of the ssh response: %s", gpg_strerror (GPG_ERR_INTERNAL)); es_free (response_data); send_err = 1; goto leave; } request[0] = response_size >> 24; request[1] = response_size >> 16; request[2] = response_size >> 8; request[3] = response_size >> 0; memcpy (request+4, response_data, response_size); es_free (response_data); valid_response = 1; } leave: if (send_err) { request[0] = 0; request[1] = 0; request[2] = 0; request[3] = 1; request[4] = SSH_RESPONSE_FAILURE; valid_response = 1; } /* Reset the SCD in case it has been used. */ agent_reset_scd (ctrl); return valid_response? 0 : -1; } #endif /*HAVE_W32_SYSTEM*/ diff --git a/agent/findkey.c b/agent/findkey.c index 0b2ddf1f9..f3c8ca986 100644 --- a/agent/findkey.c +++ b/agent/findkey.c @@ -1,1593 +1,1591 @@ /* findkey.c - Locate the secret key * Copyright (C) 2001, 2002, 2003, 2004, 2005, 2007, * 2010, 2011 Free Software Foundation, Inc. * Copyright (C) 2014 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 #include #include #include #include /* (we use pth_sleep) */ #include "agent.h" #include "../common/i18n.h" #include "../common/ssh-utils.h" #include "../common/name-value.h" #ifndef O_BINARY #define O_BINARY 0 #endif /* Helper to pass data to the check callback of the unprotect function. */ struct try_unprotect_arg_s { ctrl_t ctrl; const unsigned char *protected_key; unsigned char *unprotected_key; int change_required; /* Set by the callback to indicate that the user should change the passphrase. */ }; /* Note: Ownership of FNAME and FP are moved to this function. */ static gpg_error_t write_extended_private_key (char *fname, estream_t fp, int update, const void *buf, size_t len) { gpg_error_t err; nvc_t pk = NULL; gcry_sexp_t key = NULL; int remove = 0; if (update) { int line; err = nvc_parse_private_key (&pk, &line, fp); if (err && gpg_err_code (err) != GPG_ERR_ENOENT) { log_error ("error parsing '%s' line %d: %s\n", fname, line, gpg_strerror (err)); goto leave; } } else { pk = nvc_new_private_key (); if (!pk) { err = gpg_error_from_syserror (); goto leave; } } es_clearerr (fp); err = gcry_sexp_sscan (&key, NULL, buf, len); if (err) goto leave; err = nvc_set_private_key (pk, key); if (err) goto leave; err = es_fseek (fp, 0, SEEK_SET); if (err) goto leave; err = nvc_write (pk, fp); if (err) { log_error ("error writing '%s': %s\n", fname, gpg_strerror (err)); remove = 1; goto leave; } if (ftruncate (es_fileno (fp), es_ftello (fp))) { err = gpg_error_from_syserror (); log_error ("error truncating '%s': %s\n", fname, gpg_strerror (err)); remove = 1; goto leave; } if (es_fclose (fp)) { err = gpg_error_from_syserror (); log_error ("error closing '%s': %s\n", fname, gpg_strerror (err)); remove = 1; goto leave; } else fp = NULL; bump_key_eventcounter (); leave: es_fclose (fp); if (remove) gnupg_remove (fname); xfree (fname); gcry_sexp_release (key); nvc_release (pk); return err; } /* Write an S-expression formatted key to our key storage. With FORCE passed as true an existing key with the given GRIP will get overwritten. */ int agent_write_private_key (const unsigned char *grip, const void *buffer, size_t length, int force) { char *fname; estream_t fp; char hexgrip[40+4+1]; bin2hex (grip, 20, hexgrip); strcpy (hexgrip+40, ".key"); fname = make_filename (gnupg_homedir (), GNUPG_PRIVATE_KEYS_DIR, hexgrip, NULL); /* FIXME: Write to a temp file first so that write failures during key updates won't lead to a key loss. */ if (!force && !access (fname, F_OK)) { log_error ("secret key file '%s' already exists\n", fname); xfree (fname); return gpg_error (GPG_ERR_EEXIST); } fp = es_fopen (fname, force? "rb+,mode=-rw" : "wbx,mode=-rw"); if (!fp) { gpg_error_t tmperr = gpg_error_from_syserror (); if (force && gpg_err_code (tmperr) == GPG_ERR_ENOENT) { fp = es_fopen (fname, "wbx,mode=-rw"); if (!fp) tmperr = gpg_error_from_syserror (); } if (!fp) { log_error ("can't create '%s': %s\n", fname, gpg_strerror (tmperr)); xfree (fname); return tmperr; } } else if (force) { gpg_error_t rc; char first; /* See if an existing key is in extended format. */ if (es_fread (&first, 1, 1, fp) != 1) { rc = gpg_error_from_syserror (); log_error ("error reading first byte from '%s': %s\n", fname, strerror (errno)); xfree (fname); es_fclose (fp); return rc; } rc = es_fseek (fp, 0, SEEK_SET); if (rc) { log_error ("error seeking in '%s': %s\n", fname, strerror (errno)); xfree (fname); es_fclose (fp); return rc; } if (first != '(') { /* Key is already in the extended format. */ return write_extended_private_key (fname, fp, 1, buffer, length); } if (first == '(' && opt.enable_extended_key_format) { /* Key is in the old format - but we want the extended format. */ return write_extended_private_key (fname, fp, 0, buffer, length); } } if (opt.enable_extended_key_format) return write_extended_private_key (fname, fp, 0, buffer, length); if (es_fwrite (buffer, length, 1, fp) != 1) { gpg_error_t tmperr = gpg_error_from_syserror (); log_error ("error writing '%s': %s\n", fname, gpg_strerror (tmperr)); es_fclose (fp); gnupg_remove (fname); xfree (fname); return tmperr; } /* When force is given, the file might have to be truncated. */ if (force && ftruncate (es_fileno (fp), es_ftello (fp))) { gpg_error_t tmperr = gpg_error_from_syserror (); log_error ("error truncating '%s': %s\n", fname, gpg_strerror (tmperr)); es_fclose (fp); gnupg_remove (fname); xfree (fname); return tmperr; } if (es_fclose (fp)) { gpg_error_t tmperr = gpg_error_from_syserror (); log_error ("error closing '%s': %s\n", fname, gpg_strerror (tmperr)); gnupg_remove (fname); xfree (fname); return tmperr; } bump_key_eventcounter (); xfree (fname); return 0; } /* Callback function to try the unprotection from the passphrase query code. */ static gpg_error_t try_unprotect_cb (struct pin_entry_info_s *pi) { struct try_unprotect_arg_s *arg = pi->check_cb_arg; ctrl_t ctrl = arg->ctrl; size_t dummy; gpg_error_t err; gnupg_isotime_t now, protected_at, tmptime; char *desc = NULL; assert (!arg->unprotected_key); arg->change_required = 0; err = agent_unprotect (ctrl, arg->protected_key, pi->pin, protected_at, &arg->unprotected_key, &dummy); if (err) return err; if (!opt.max_passphrase_days || ctrl->in_passwd) return 0; /* No regular passphrase change required. */ if (!*protected_at) { /* No protection date known - must force passphrase change. */ desc = xtrystrdup (L_("Note: This passphrase has never been changed.%0A" "Please change it now.")); if (!desc) return gpg_error_from_syserror (); } else { gnupg_get_isotime (now); gnupg_copy_time (tmptime, protected_at); err = add_days_to_isotime (tmptime, opt.max_passphrase_days); if (err) return err; if (strcmp (now, tmptime) > 0 ) { /* Passphrase "expired". */ desc = xtryasprintf (L_("This passphrase has not been changed%%0A" "since %.4s-%.2s-%.2s. Please change it now."), protected_at, protected_at+4, protected_at+6); if (!desc) return gpg_error_from_syserror (); } } if (desc) { /* Change required. */ if (opt.enforce_passphrase_constraints) { err = agent_get_confirmation (ctrl, desc, L_("Change passphrase"), NULL, 0); if (!err) arg->change_required = 1; } else { err = agent_get_confirmation (ctrl, desc, L_("Change passphrase"), L_("I'll change it later"), 0); if (!err) arg->change_required = 1; else if (gpg_err_code (err) == GPG_ERR_CANCELED || gpg_err_code (err) == GPG_ERR_FULLY_CANCELED) err = 0; } xfree (desc); } - return 0; + return err; } /* Modify a Key description, replacing certain special format characters. List of currently supported replacements: %% - Replaced by a single % %c - Replaced by the content of COMMENT. %C - Same as %c but put into parentheses. %F - Replaced by an ssh style fingerprint computed from KEY. The functions returns 0 on success or an error code. On success a newly allocated string is stored at the address of RESULT. */ gpg_error_t agent_modify_description (const char *in, const char *comment, const gcry_sexp_t key, char **result) { size_t comment_length; size_t in_len; size_t out_len; char *out; size_t i; int special, pass; char *ssh_fpr = NULL; char *p; *result = NULL; if (!comment) comment = ""; comment_length = strlen (comment); in_len = strlen (in); /* First pass calculates the length, second pass does the actual copying. */ /* FIXME: This can be simplified by using es_fopenmem. */ out = NULL; out_len = 0; for (pass=0; pass < 2; pass++) { special = 0; for (i = 0; i < in_len; i++) { if (special) { special = 0; switch (in[i]) { case '%': if (out) *out++ = '%'; else out_len++; break; case 'c': /* Comment. */ if (out) { memcpy (out, comment, comment_length); out += comment_length; } else out_len += comment_length; break; case 'C': /* Comment. */ if (!comment_length) ; else if (out) { *out++ = '('; memcpy (out, comment, comment_length); out += comment_length; *out++ = ')'; } else out_len += comment_length + 2; break; case 'F': /* SSH style fingerprint. */ if (!ssh_fpr && key) ssh_get_fingerprint_string (key, &ssh_fpr); if (ssh_fpr) { if (out) out = stpcpy (out, ssh_fpr); else out_len += strlen (ssh_fpr); } break; default: /* Invalid special sequences are kept as they are. */ if (out) { *out++ = '%'; *out++ = in[i]; } else out_len+=2; break; } } else if (in[i] == '%') special = 1; else { if (out) *out++ = in[i]; else out_len++; } } if (!pass) { *result = out = xtrymalloc (out_len + 1); if (!out) { xfree (ssh_fpr); return gpg_error_from_syserror (); } } } *out = 0; log_assert (*result + out_len == out); xfree (ssh_fpr); /* The ssh prompt may sometimes end in * "...%0A ()" * The empty parentheses doesn't look very good. We use this hack * here to remove them as well as the indentation spaces. */ p = *result; i = strlen (p); if (i > 2 && !strcmp (p + i - 2, "()")) { p += i - 2; *p-- = 0; while (p > *result && spacep (p)) *p-- = 0; } return 0; } /* Unprotect the canconical encoded S-expression key in KEYBUF. GRIP should be the hex encoded keygrip of that key to be used with the caching mechanism. DESC_TEXT may be set to override the default description used for the pinentry. If LOOKUP_TTL is given this function is used to lookup the default ttl. If R_PASSPHRASE is not NULL, the function succeeded and the key was protected the used passphrase (entered or from the cache) is stored there; if not NULL will be stored. The caller needs to free the returned passphrase. */ static int unprotect (ctrl_t ctrl, const char *cache_nonce, const char *desc_text, unsigned char **keybuf, const unsigned char *grip, cache_mode_t cache_mode, lookup_ttl_t lookup_ttl, char **r_passphrase) { struct pin_entry_info_s *pi; struct try_unprotect_arg_s arg; int rc; unsigned char *result; size_t resultlen; char hexgrip[40+1]; if (r_passphrase) *r_passphrase = NULL; bin2hex (grip, 20, hexgrip); /* Initially try to get it using a cache nonce. */ if (cache_nonce) { char *pw; pw = agent_get_cache (cache_nonce, CACHE_MODE_NONCE); if (pw) { rc = agent_unprotect (ctrl, *keybuf, pw, NULL, &result, &resultlen); if (!rc) { if (r_passphrase) *r_passphrase = pw; else xfree (pw); xfree (*keybuf); *keybuf = result; return 0; } xfree (pw); } } /* First try to get it from the cache - if there is none or we can't unprotect it, we fall back to ask the user */ if (cache_mode != CACHE_MODE_IGNORE) { char *pw; retry: pw = agent_get_cache (hexgrip, cache_mode); if (pw) { rc = agent_unprotect (ctrl, *keybuf, pw, NULL, &result, &resultlen); if (!rc) { if (cache_mode == CACHE_MODE_NORMAL) agent_store_cache_hit (hexgrip); if (r_passphrase) *r_passphrase = pw; else xfree (pw); xfree (*keybuf); *keybuf = result; return 0; } xfree (pw); - rc = 0; } else if (cache_mode == CACHE_MODE_NORMAL) { /* The standard use of GPG keys is to have a signing and an encryption subkey. Commonly both use the same passphrase. We try to help the user to enter the passphrase only once by silently trying the last correctly entered passphrase. Checking one additional passphrase should be acceptable; despite the S2K introduced delays. The assumed workflow is: 1. Read encrypted message in a MUA and thus enter a passphrase for the encryption subkey. 2. Reply to that mail with an encrypted and signed mail, thus entering the passphrase for the signing subkey. We can often avoid the passphrase entry in the second step. We do this only in normal mode, so not to interfere with unrelated cache entries. */ pw = agent_get_cache (NULL, cache_mode); if (pw) { rc = agent_unprotect (ctrl, *keybuf, pw, NULL, &result, &resultlen); if (!rc) { if (r_passphrase) *r_passphrase = pw; else xfree (pw); xfree (*keybuf); *keybuf = result; return 0; } xfree (pw); - rc = 0; } } /* If the pinentry is currently in use, we wait up to 60 seconds for it to close and check the cache again. This solves a common situation where several requests for unprotecting a key have been made but the user is still entering the passphrase for the first request. Because all requests to agent_askpin are serialized they would then pop up one after the other to request the passphrase - despite that the user has already entered it and is then available in the cache. This implementation is not race free but in the worst case the user has to enter the passphrase only once more. */ if (pinentry_active_p (ctrl, 0)) { /* Active - wait */ if (!pinentry_active_p (ctrl, 60)) { /* We need to give the other thread a chance to actually put it into the cache. */ npth_sleep (1); goto retry; } /* Timeout - better call pinentry now the plain way. */ } } pi = gcry_calloc_secure (1, sizeof (*pi) + MAX_PASSPHRASE_LEN + 1); if (!pi) return gpg_error_from_syserror (); pi->max_length = MAX_PASSPHRASE_LEN + 1; pi->min_digits = 0; /* we want a real passphrase */ pi->max_digits = 16; pi->max_tries = 3; pi->check_cb = try_unprotect_cb; arg.ctrl = ctrl; arg.protected_key = *keybuf; arg.unprotected_key = NULL; arg.change_required = 0; pi->check_cb_arg = &arg; rc = agent_askpin (ctrl, desc_text, NULL, NULL, pi, hexgrip, cache_mode); if (!rc) { assert (arg.unprotected_key); if (arg.change_required) { /* The callback told as that the user should change their passphrase. Present the dialog to do. */ size_t canlen, erroff; gcry_sexp_t s_skey; assert (arg.unprotected_key); canlen = gcry_sexp_canon_len (arg.unprotected_key, 0, NULL, NULL); rc = gcry_sexp_sscan (&s_skey, &erroff, (char*)arg.unprotected_key, canlen); if (rc) { log_error ("failed to build S-Exp (off=%u): %s\n", (unsigned int)erroff, gpg_strerror (rc)); wipememory (arg.unprotected_key, canlen); xfree (arg.unprotected_key); xfree (pi); return rc; } rc = agent_protect_and_store (ctrl, s_skey, NULL); gcry_sexp_release (s_skey); if (rc) { log_error ("changing the passphrase failed: %s\n", gpg_strerror (rc)); wipememory (arg.unprotected_key, canlen); xfree (arg.unprotected_key); xfree (pi); return rc; } } else { /* Passphrase is fine. */ agent_put_cache (hexgrip, cache_mode, pi->pin, lookup_ttl? lookup_ttl (hexgrip) : 0); agent_store_cache_hit (hexgrip); if (r_passphrase && *pi->pin) *r_passphrase = xtrystrdup (pi->pin); } xfree (*keybuf); *keybuf = arg.unprotected_key; } xfree (pi); return rc; } /* Read the key identified by GRIP from the private key directory and return it as an gcrypt S-expression object in RESULT. On failure returns an error code and stores NULL at RESULT. */ static gpg_error_t read_key_file (const unsigned char *grip, gcry_sexp_t *result) { int rc; char *fname; estream_t fp; struct stat st; unsigned char *buf; size_t buflen, erroff; gcry_sexp_t s_skey; char hexgrip[40+4+1]; char first; *result = NULL; bin2hex (grip, 20, hexgrip); strcpy (hexgrip+40, ".key"); fname = make_filename (gnupg_homedir (), GNUPG_PRIVATE_KEYS_DIR, hexgrip, NULL); fp = es_fopen (fname, "rb"); if (!fp) { rc = gpg_error_from_syserror (); if (gpg_err_code (rc) != GPG_ERR_ENOENT) log_error ("can't open '%s': %s\n", fname, strerror (errno)); xfree (fname); return rc; } if (es_fread (&first, 1, 1, fp) != 1) { rc = gpg_error_from_syserror (); log_error ("error reading first byte from '%s': %s\n", fname, strerror (errno)); xfree (fname); es_fclose (fp); return rc; } rc = es_fseek (fp, 0, SEEK_SET); if (rc) { log_error ("error seeking in '%s': %s\n", fname, strerror (errno)); xfree (fname); es_fclose (fp); return rc; } if (first != '(') { /* Key is in extended format. */ nvc_t pk; int line; rc = nvc_parse_private_key (&pk, &line, fp); es_fclose (fp); if (rc) log_error ("error parsing '%s' line %d: %s\n", fname, line, gpg_strerror (rc)); else { rc = nvc_get_private_key (pk, result); nvc_release (pk); if (rc) log_error ("error getting private key from '%s': %s\n", fname, gpg_strerror (rc)); } xfree (fname); return rc; } if (fstat (es_fileno (fp), &st)) { rc = gpg_error_from_syserror (); log_error ("can't stat '%s': %s\n", fname, strerror (errno)); xfree (fname); es_fclose (fp); return rc; } buflen = st.st_size; buf = xtrymalloc (buflen+1); if (!buf) { rc = gpg_error_from_syserror (); log_error ("error allocating %zu bytes for '%s': %s\n", buflen, fname, strerror (errno)); xfree (fname); es_fclose (fp); xfree (buf); return rc; } if (es_fread (buf, buflen, 1, fp) != 1) { rc = gpg_error_from_syserror (); log_error ("error reading %zu bytes from '%s': %s\n", buflen, fname, strerror (errno)); xfree (fname); es_fclose (fp); xfree (buf); return rc; } /* Convert the file into a gcrypt S-expression object. */ rc = gcry_sexp_sscan (&s_skey, &erroff, (char*)buf, buflen); xfree (fname); es_fclose (fp); xfree (buf); if (rc) { log_error ("failed to build S-Exp (off=%u): %s\n", (unsigned int)erroff, gpg_strerror (rc)); return rc; } *result = s_skey; return 0; } /* Remove the key identified by GRIP from the private key directory. */ static gpg_error_t remove_key_file (const unsigned char *grip) { gpg_error_t err = 0; char *fname; char hexgrip[40+4+1]; bin2hex (grip, 20, hexgrip); strcpy (hexgrip+40, ".key"); fname = make_filename (gnupg_homedir (), GNUPG_PRIVATE_KEYS_DIR, hexgrip, NULL); if (gnupg_remove (fname)) err = gpg_error_from_syserror (); xfree (fname); return err; } /* Return the secret key as an S-Exp in RESULT after locating it using the GRIP. If the operation shall be diverted to a token, an allocated S-expression with the shadow_info part from the file is stored at SHADOW_INFO; if not NULL will be stored at SHADOW_INFO. CACHE_MODE defines now the cache shall be used. DESC_TEXT may be set to present a custom description for the pinentry. LOOKUP_TTL is an optional function to convey a TTL to the cache manager; we do not simply pass the TTL value because the value is only needed if an unprotect action was needed and looking up the TTL may have some overhead (e.g. scanning the sshcontrol file). If a CACHE_NONCE is given that cache item is first tried to get a passphrase. If R_PASSPHRASE is not NULL, the function succeeded and the key was protected the used passphrase (entered or from the cache) is stored there; if not NULL will be stored. The caller needs to free the returned passphrase. */ gpg_error_t agent_key_from_file (ctrl_t ctrl, const char *cache_nonce, const char *desc_text, const unsigned char *grip, unsigned char **shadow_info, cache_mode_t cache_mode, lookup_ttl_t lookup_ttl, gcry_sexp_t *result, char **r_passphrase) { int rc; unsigned char *buf; size_t len, buflen, erroff; gcry_sexp_t s_skey; *result = NULL; if (shadow_info) *shadow_info = NULL; if (r_passphrase) *r_passphrase = NULL; rc = read_key_file (grip, &s_skey); if (rc) { if (gpg_err_code (rc) == GPG_ERR_ENOENT) rc = gpg_error (GPG_ERR_NO_SECKEY); return rc; } /* For use with the protection functions we also need the key as an canonical encoded S-expression in a buffer. Create this buffer now. */ rc = make_canon_sexp (s_skey, &buf, &len); if (rc) return rc; switch (agent_private_key_type (buf)) { case PRIVATE_KEY_CLEAR: break; /* no unprotection needed */ case PRIVATE_KEY_OPENPGP_NONE: { unsigned char *buf_new; size_t buf_newlen; rc = agent_unprotect (ctrl, buf, "", NULL, &buf_new, &buf_newlen); if (rc) log_error ("failed to convert unprotected openpgp key: %s\n", gpg_strerror (rc)); else { xfree (buf); buf = buf_new; } } break; case PRIVATE_KEY_PROTECTED: { char *desc_text_final; char *comment = NULL; /* Note, that we will take the comment as a C string for display purposes; i.e. all stuff beyond a Nul character is ignored. */ { gcry_sexp_t comment_sexp; comment_sexp = gcry_sexp_find_token (s_skey, "comment", 0); if (comment_sexp) comment = gcry_sexp_nth_string (comment_sexp, 1); gcry_sexp_release (comment_sexp); } desc_text_final = NULL; if (desc_text) rc = agent_modify_description (desc_text, comment, s_skey, &desc_text_final); gcry_free (comment); if (!rc) { rc = unprotect (ctrl, cache_nonce, desc_text_final, &buf, grip, cache_mode, lookup_ttl, r_passphrase); if (rc) log_error ("failed to unprotect the secret key: %s\n", gpg_strerror (rc)); } xfree (desc_text_final); } break; case PRIVATE_KEY_SHADOWED: if (shadow_info) { const unsigned char *s; size_t n; rc = agent_get_shadow_info (buf, &s); if (!rc) { n = gcry_sexp_canon_len (s, 0, NULL,NULL); assert (n); *shadow_info = xtrymalloc (n); if (!*shadow_info) rc = out_of_core (); else { memcpy (*shadow_info, s, n); rc = 0; } } if (rc) log_error ("get_shadow_info failed: %s\n", gpg_strerror (rc)); } else rc = gpg_error (GPG_ERR_UNUSABLE_SECKEY); break; default: log_error ("invalid private key format\n"); rc = gpg_error (GPG_ERR_BAD_SECKEY); break; } gcry_sexp_release (s_skey); s_skey = NULL; if (rc) { xfree (buf); if (r_passphrase) { xfree (*r_passphrase); *r_passphrase = NULL; } return rc; } buflen = gcry_sexp_canon_len (buf, 0, NULL, NULL); rc = gcry_sexp_sscan (&s_skey, &erroff, (char*)buf, buflen); wipememory (buf, buflen); xfree (buf); if (rc) { log_error ("failed to build S-Exp (off=%u): %s\n", (unsigned int)erroff, gpg_strerror (rc)); if (r_passphrase) { xfree (*r_passphrase); *r_passphrase = NULL; } return rc; } *result = s_skey; return 0; } /* Return the string name from the S-expression S_KEY as well as a string describing the names of the parameters. ALGONAMESIZE and ELEMSSIZE give the allocated size of the provided buffers. The buffers may be NULL if not required. If R_LIST is not NULL the top level list will be stored there; the caller needs to release it in this case. */ static gpg_error_t key_parms_from_sexp (gcry_sexp_t s_key, gcry_sexp_t *r_list, char *r_algoname, size_t algonamesize, char *r_elems, size_t elemssize) { gcry_sexp_t list, l2; const char *name, *algoname, *elems; size_t n; if (r_list) *r_list = NULL; list = gcry_sexp_find_token (s_key, "shadowed-private-key", 0 ); if (!list) list = gcry_sexp_find_token (s_key, "protected-private-key", 0 ); if (!list) list = gcry_sexp_find_token (s_key, "private-key", 0 ); if (!list) { log_error ("invalid private key format\n"); return gpg_error (GPG_ERR_BAD_SECKEY); } l2 = gcry_sexp_cadr (list); gcry_sexp_release (list); list = l2; name = gcry_sexp_nth_data (list, 0, &n); if (n==3 && !memcmp (name, "rsa", 3)) { algoname = "rsa"; elems = "ne"; } else if (n==3 && !memcmp (name, "dsa", 3)) { algoname = "dsa"; elems = "pqgy"; } else if (n==3 && !memcmp (name, "ecc", 3)) { algoname = "ecc"; elems = "pabgnq"; } else if (n==5 && !memcmp (name, "ecdsa", 5)) { algoname = "ecdsa"; elems = "pabgnq"; } else if (n==4 && !memcmp (name, "ecdh", 4)) { algoname = "ecdh"; elems = "pabgnq"; } else if (n==3 && !memcmp (name, "elg", 3)) { algoname = "elg"; elems = "pgy"; } else { log_error ("unknown private key algorithm\n"); gcry_sexp_release (list); return gpg_error (GPG_ERR_BAD_SECKEY); } if (r_algoname) { if (strlen (algoname) >= algonamesize) return gpg_error (GPG_ERR_BUFFER_TOO_SHORT); strcpy (r_algoname, algoname); } if (r_elems) { if (strlen (elems) >= elemssize) return gpg_error (GPG_ERR_BUFFER_TOO_SHORT); strcpy (r_elems, elems); } if (r_list) *r_list = list; else gcry_sexp_release (list); return 0; } /* Return true if KEYPARMS holds an EdDSA key. */ static int is_eddsa (gcry_sexp_t keyparms) { int result = 0; gcry_sexp_t list; const char *s; size_t n; int i; list = gcry_sexp_find_token (keyparms, "flags", 0); for (i = list ? gcry_sexp_length (list)-1 : 0; i > 0; i--) { s = gcry_sexp_nth_data (list, i, &n); if (!s) continue; /* Not a data element. */ if (n == 5 && !memcmp (s, "eddsa", 5)) { result = 1; break; } } gcry_sexp_release (list); return result; } /* Return the public key algorithm number if S_KEY is a DSA style key. If it is not a DSA style key, return 0. */ int agent_is_dsa_key (gcry_sexp_t s_key) { int result; gcry_sexp_t list; char algoname[6]; if (!s_key) return 0; if (key_parms_from_sexp (s_key, &list, algoname, sizeof algoname, NULL, 0)) return 0; /* Error - assume it is not an DSA key. */ if (!strcmp (algoname, "dsa")) result = GCRY_PK_DSA; else if (!strcmp (algoname, "ecc")) { if (is_eddsa (list)) result = 0; else result = GCRY_PK_ECDSA; } else if (!strcmp (algoname, "ecdsa")) result = GCRY_PK_ECDSA; else result = 0; gcry_sexp_release (list); return result; } /* Return true if S_KEY is an EdDSA key as used with curve Ed25519. */ int agent_is_eddsa_key (gcry_sexp_t s_key) { int result; gcry_sexp_t list; char algoname[6]; if (!s_key) return 0; if (key_parms_from_sexp (s_key, &list, algoname, sizeof algoname, NULL, 0)) return 0; /* Error - assume it is not an EdDSA key. */ if (!strcmp (algoname, "ecc") && is_eddsa (list)) result = 1; else if (!strcmp (algoname, "eddsa")) /* backward compatibility. */ result = 1; else result = 0; gcry_sexp_release (list); return result; } /* Return the key for the keygrip GRIP. The result is stored at RESULT. This function extracts the key from the private key database and returns it as an S-expression object as it is. On failure an error code is returned and NULL stored at RESULT. */ gpg_error_t agent_raw_key_from_file (ctrl_t ctrl, const unsigned char *grip, gcry_sexp_t *result) { gpg_error_t err; gcry_sexp_t s_skey; (void)ctrl; *result = NULL; err = read_key_file (grip, &s_skey); if (!err) *result = s_skey; return err; } /* Return the public key for the keygrip GRIP. The result is stored at RESULT. This function extracts the public key from the private key database. On failure an error code is returned and NULL stored at RESULT. */ gpg_error_t agent_public_key_from_file (ctrl_t ctrl, const unsigned char *grip, gcry_sexp_t *result) { gpg_error_t err; int i, idx; gcry_sexp_t s_skey; const char *algoname, *elems; int npkey; gcry_mpi_t array[10]; gcry_sexp_t curve = NULL; gcry_sexp_t flags = NULL; gcry_sexp_t uri_sexp, comment_sexp; const char *uri, *comment; size_t uri_length, comment_length; char *format, *p; void *args[2+7+2+2+1]; /* Size is 2 + max. # of elements + 2 for uri + 2 for comment + end-of-list. */ int argidx; gcry_sexp_t list = NULL; const char *s; (void)ctrl; *result = NULL; err = read_key_file (grip, &s_skey); if (err) return err; for (i=0; i < DIM (array); i++) array[i] = NULL; err = extract_private_key (s_skey, 0, &algoname, &npkey, NULL, &elems, array, DIM (array), &curve, &flags); if (err) { gcry_sexp_release (s_skey); return err; } uri = NULL; uri_length = 0; uri_sexp = gcry_sexp_find_token (s_skey, "uri", 0); if (uri_sexp) uri = gcry_sexp_nth_data (uri_sexp, 1, &uri_length); comment = NULL; comment_length = 0; comment_sexp = gcry_sexp_find_token (s_skey, "comment", 0); if (comment_sexp) comment = gcry_sexp_nth_data (comment_sexp, 1, &comment_length); gcry_sexp_release (s_skey); s_skey = NULL; /* FIXME: The following thing is pretty ugly code; we should investigate how to make it cleaner. Probably code to handle canonical S-expressions in a memory buffer is better suited for such a task. After all that is what we do in protect.c. Neeed to find common patterns and write a straightformward API to use them. */ assert (sizeof (size_t) <= sizeof (void*)); format = xtrymalloc (15+4+7*npkey+10+15+1+1); if (!format) { err = gpg_error_from_syserror (); for (i=0; array[i]; i++) gcry_mpi_release (array[i]); gcry_sexp_release (curve); gcry_sexp_release (flags); gcry_sexp_release (uri_sexp); gcry_sexp_release (comment_sexp); return err; } argidx = 0; p = stpcpy (stpcpy (format, "(public-key("), algoname); p = stpcpy (p, "%S%S"); /* curve name and flags. */ args[argidx++] = &curve; args[argidx++] = &flags; for (idx=0, s=elems; idx < npkey; idx++) { *p++ = '('; *p++ = *s++; p = stpcpy (p, " %m)"); assert (argidx < DIM (args)); args[argidx++] = &array[idx]; } *p++ = ')'; if (uri) { p = stpcpy (p, "(uri %b)"); assert (argidx+1 < DIM (args)); args[argidx++] = (void *)&uri_length; args[argidx++] = (void *)&uri; } if (comment) { p = stpcpy (p, "(comment %b)"); assert (argidx+1 < DIM (args)); args[argidx++] = (void *)&comment_length; args[argidx++] = (void*)&comment; } *p++ = ')'; *p = 0; assert (argidx < DIM (args)); args[argidx] = NULL; err = gcry_sexp_build_array (&list, NULL, format, args); xfree (format); for (i=0; array[i]; i++) gcry_mpi_release (array[i]); gcry_sexp_release (curve); gcry_sexp_release (flags); gcry_sexp_release (uri_sexp); gcry_sexp_release (comment_sexp); if (!err) *result = list; return err; } /* Check whether the secret key identified by GRIP is available. Returns 0 is the key is available. */ int agent_key_available (const unsigned char *grip) { int result; char *fname; char hexgrip[40+4+1]; bin2hex (grip, 20, hexgrip); strcpy (hexgrip+40, ".key"); fname = make_filename (gnupg_homedir (), GNUPG_PRIVATE_KEYS_DIR, hexgrip, NULL); result = !access (fname, R_OK)? 0 : -1; xfree (fname); return result; } /* Return the information about the secret key specified by the binary keygrip GRIP. If the key is a shadowed one the shadow information will be stored at the address R_SHADOW_INFO as an allocated S-expression. */ gpg_error_t agent_key_info_from_file (ctrl_t ctrl, const unsigned char *grip, int *r_keytype, unsigned char **r_shadow_info) { gpg_error_t err; unsigned char *buf; size_t len; int keytype; (void)ctrl; if (r_keytype) *r_keytype = PRIVATE_KEY_UNKNOWN; if (r_shadow_info) *r_shadow_info = NULL; { gcry_sexp_t sexp; err = read_key_file (grip, &sexp); if (err) { if (gpg_err_code (err) == GPG_ERR_ENOENT) return gpg_error (GPG_ERR_NOT_FOUND); else return err; } err = make_canon_sexp (sexp, &buf, &len); gcry_sexp_release (sexp); if (err) return err; } keytype = agent_private_key_type (buf); switch (keytype) { case PRIVATE_KEY_CLEAR: case PRIVATE_KEY_OPENPGP_NONE: break; case PRIVATE_KEY_PROTECTED: /* If we ever require it we could retrieve the comment fields from such a key. */ break; case PRIVATE_KEY_SHADOWED: if (r_shadow_info) { const unsigned char *s; size_t n; err = agent_get_shadow_info (buf, &s); if (!err) { n = gcry_sexp_canon_len (s, 0, NULL, NULL); assert (n); *r_shadow_info = xtrymalloc (n); if (!*r_shadow_info) err = gpg_error_from_syserror (); else memcpy (*r_shadow_info, s, n); } } break; default: err = gpg_error (GPG_ERR_BAD_SECKEY); break; } if (!err && r_keytype) *r_keytype = keytype; xfree (buf); return err; } /* Delete the key with GRIP from the disk after having asked for * confirmation using DESC_TEXT. If FORCE is set the function won't * require a confirmation via Pinentry or warns if the key is also * used by ssh. If ONLY_STUBS is set only stub keys (references to * smartcards) will be affected. * * Common error codes are: * GPG_ERR_NO_SECKEY * GPG_ERR_KEY_ON_CARD * GPG_ERR_NOT_CONFIRMED * GPG_ERR_FORBIDDEN - Not a stub key and ONLY_STUBS requested. */ gpg_error_t agent_delete_key (ctrl_t ctrl, const char *desc_text, const unsigned char *grip, int force, int only_stubs) { gpg_error_t err; gcry_sexp_t s_skey = NULL; unsigned char *buf = NULL; size_t len; char *desc_text_final = NULL; char *comment = NULL; ssh_control_file_t cf = NULL; char hexgrip[40+4+1]; char *default_desc = NULL; int key_type; err = read_key_file (grip, &s_skey); if (gpg_err_code (err) == GPG_ERR_ENOENT) err = gpg_error (GPG_ERR_NO_SECKEY); if (err) goto leave; err = make_canon_sexp (s_skey, &buf, &len); if (err) goto leave; key_type = agent_private_key_type (buf); if (only_stubs && key_type != PRIVATE_KEY_SHADOWED) { err = gpg_error (GPG_ERR_FORBIDDEN); goto leave; } switch (key_type) { case PRIVATE_KEY_CLEAR: case PRIVATE_KEY_OPENPGP_NONE: case PRIVATE_KEY_PROTECTED: bin2hex (grip, 20, hexgrip); if (!force) { if (!desc_text) { default_desc = xtryasprintf (L_("Do you really want to delete the key identified by keygrip%%0A" " %s%%0A %%C%%0A?"), hexgrip); desc_text = default_desc; } /* Note, that we will take the comment as a C string for display purposes; i.e. all stuff beyond a Nul character is ignored. */ { gcry_sexp_t comment_sexp; comment_sexp = gcry_sexp_find_token (s_skey, "comment", 0); if (comment_sexp) comment = gcry_sexp_nth_string (comment_sexp, 1); gcry_sexp_release (comment_sexp); } if (desc_text) err = agent_modify_description (desc_text, comment, s_skey, &desc_text_final); if (err) goto leave; err = agent_get_confirmation (ctrl, desc_text_final, L_("Delete key"), L_("No"), 0); if (err) goto leave; cf = ssh_open_control_file (); if (cf) { if (!ssh_search_control_file (cf, hexgrip, NULL, NULL, NULL)) { err = agent_get_confirmation (ctrl, L_("Warning: This key is also listed for use with SSH!\n" "Deleting the key might remove your ability to " "access remote machines."), L_("Delete key"), L_("No"), 0); if (err) goto leave; } } } err = remove_key_file (grip); break; case PRIVATE_KEY_SHADOWED: err = remove_key_file (grip); break; default: log_error ("invalid private key format\n"); err = gpg_error (GPG_ERR_BAD_SECKEY); break; } leave: ssh_close_control_file (cf); gcry_free (comment); xfree (desc_text_final); xfree (default_desc); xfree (buf); gcry_sexp_release (s_skey); return err; } /* Write an S-expression formatted shadow key to our key storage. Shadow key is created by an S-expression public key in PKBUF and card's SERIALNO and the IDSTRING. With FORCE passed as true an existing key with the given GRIP will get overwritten. */ gpg_error_t agent_write_shadow_key (const unsigned char *grip, const char *serialno, const char *keyid, const unsigned char *pkbuf, int force) { gpg_error_t err; unsigned char *shadow_info; unsigned char *shdkey; size_t len; shadow_info = make_shadow_info (serialno, keyid); if (!shadow_info) return gpg_error_from_syserror (); err = agent_shadow_key (pkbuf, shadow_info, &shdkey); xfree (shadow_info); if (err) { log_error ("shadowing the key failed: %s\n", gpg_strerror (err)); return err; } len = gcry_sexp_canon_len (shdkey, 0, NULL, NULL); err = agent_write_private_key (grip, shdkey, len, force); xfree (shdkey); if (err) log_error ("error writing key: %s\n", gpg_strerror (err)); return err; } diff --git a/agent/gpg-agent.c b/agent/gpg-agent.c index e23f43858..c764be887 100644 --- a/agent/gpg-agent.c +++ b/agent/gpg-agent.c @@ -1,3125 +1,3124 @@ /* gpg-agent.c - The GnuPG Agent * Copyright (C) 2000-2007, 2009-2010 Free Software Foundation, Inc. * Copyright (C) 2000-2016 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 #include #include #include #ifdef HAVE_W32_SYSTEM # ifndef WINVER # define WINVER 0x0500 /* Same as in common/sysutils.c */ # endif # ifdef HAVE_WINSOCK2_H # include # endif # include # include #else /*!HAVE_W32_SYSTEM*/ # include # include #endif /*!HAVE_W32_SYSTEM*/ #include #ifdef HAVE_SIGNAL_H # include #endif #include #define GNUPG_COMMON_NEED_AFLOCAL #include "agent.h" #include /* Malloc hooks and socket wrappers. */ #include "../common/i18n.h" #include "../common/sysutils.h" #include "../common/gc-opt-flags.h" #include "../common/exechelp.h" #include "../common/asshelp.h" #include "../common/init.h" enum cmd_and_opt_values { aNull = 0, oCsh = 'c', oQuiet = 'q', oSh = 's', oVerbose = 'v', oNoVerbose = 500, aGPGConfList, aGPGConfTest, aUseStandardSocketP, oOptions, oDebug, oDebugAll, oDebugLevel, oDebugWait, oDebugQuickRandom, oDebugPinentry, oNoGreeting, oNoOptions, oHomedir, oNoDetach, oNoGrab, oLogFile, oServer, oDaemon, oSupervised, oBatch, oPinentryProgram, oPinentryTouchFile, oPinentryInvisibleChar, oPinentryTimeout, oDisplay, oTTYname, oTTYtype, oLCctype, oLCmessages, oXauthority, oScdaemonProgram, oDefCacheTTL, oDefCacheTTLSSH, oMaxCacheTTL, oMaxCacheTTLSSH, oEnforcePassphraseConstraints, oMinPassphraseLen, oMinPassphraseNonalpha, oCheckPassphrasePattern, oMaxPassphraseDays, oEnablePassphraseHistory, oEnableExtendedKeyFormat, oUseStandardSocket, oNoUseStandardSocket, oExtraSocket, oBrowserSocket, oFakedSystemTime, oIgnoreCacheForSigning, oAllowMarkTrusted, oNoAllowMarkTrusted, oAllowPresetPassphrase, oAllowLoopbackPinentry, oNoAllowLoopbackPinentry, oNoAllowExternalCache, oAllowEmacsPinentry, oKeepTTY, oKeepDISPLAY, oSSHSupport, oPuttySupport, oDisableScdaemon, oDisableCheckOwnSocket, oWriteEnvFile }; #ifndef ENAMETOOLONG # define ENAMETOOLONG EINVAL #endif static ARGPARSE_OPTS opts[] = { ARGPARSE_c (aGPGConfList, "gpgconf-list", "@"), ARGPARSE_c (aGPGConfTest, "gpgconf-test", "@"), ARGPARSE_c (aUseStandardSocketP, "use-standard-socket-p", "@"), ARGPARSE_group (301, N_("@Options:\n ")), ARGPARSE_s_n (oDaemon, "daemon", N_("run in daemon mode (background)")), ARGPARSE_s_n (oServer, "server", N_("run in server mode (foreground)")), #ifndef HAVE_W32_SYSTEM ARGPARSE_s_n (oSupervised, "supervised", N_("run in supervised mode")), #endif ARGPARSE_s_n (oVerbose, "verbose", N_("verbose")), ARGPARSE_s_n (oQuiet, "quiet", N_("be somewhat more quiet")), ARGPARSE_s_n (oSh, "sh", N_("sh-style command output")), ARGPARSE_s_n (oCsh, "csh", N_("csh-style command output")), ARGPARSE_s_s (oOptions, "options", N_("|FILE|read options from FILE")), ARGPARSE_s_s (oDebug, "debug", "@"), ARGPARSE_s_n (oDebugAll, "debug-all", "@"), ARGPARSE_s_s (oDebugLevel, "debug-level", "@"), ARGPARSE_s_i (oDebugWait," debug-wait", "@"), ARGPARSE_s_n (oDebugQuickRandom, "debug-quick-random", "@"), ARGPARSE_s_n (oDebugPinentry, "debug-pinentry", "@"), ARGPARSE_s_n (oNoDetach, "no-detach", N_("do not detach from the console")), ARGPARSE_s_n (oNoGrab, "no-grab", N_("do not grab keyboard and mouse")), ARGPARSE_s_s (oLogFile, "log-file", N_("use a log file for the server")), ARGPARSE_s_s (oPinentryProgram, "pinentry-program", /* */ N_("|PGM|use PGM as the PIN-Entry program")), ARGPARSE_s_s (oPinentryTouchFile, "pinentry-touch-file", "@"), ARGPARSE_s_s (oPinentryInvisibleChar, "pinentry-invisible-char", "@"), ARGPARSE_s_u (oPinentryTimeout, "pinentry-timeout", "@"), ARGPARSE_s_s (oScdaemonProgram, "scdaemon-program", /* */ N_("|PGM|use PGM as the SCdaemon program") ), ARGPARSE_s_n (oDisableScdaemon, "disable-scdaemon", /* */ N_("do not use the SCdaemon") ), ARGPARSE_s_n (oDisableCheckOwnSocket, "disable-check-own-socket", "@"), ARGPARSE_s_s (oExtraSocket, "extra-socket", /* */ N_("|NAME|accept some commands via NAME")), ARGPARSE_s_s (oBrowserSocket, "browser-socket", "@"), ARGPARSE_s_s (oFakedSystemTime, "faked-system-time", "@"), ARGPARSE_s_n (oBatch, "batch", "@"), ARGPARSE_s_s (oHomedir, "homedir", "@"), ARGPARSE_s_s (oDisplay, "display", "@"), ARGPARSE_s_s (oTTYname, "ttyname", "@"), ARGPARSE_s_s (oTTYtype, "ttytype", "@"), ARGPARSE_s_s (oLCctype, "lc-ctype", "@"), ARGPARSE_s_s (oLCmessages, "lc-messages", "@"), ARGPARSE_s_s (oXauthority, "xauthority", "@"), ARGPARSE_s_n (oKeepTTY, "keep-tty", /* */ N_("ignore requests to change the TTY")), ARGPARSE_s_n (oKeepDISPLAY, "keep-display", /* */ N_("ignore requests to change the X display")), ARGPARSE_s_u (oDefCacheTTL, "default-cache-ttl", N_("|N|expire cached PINs after N seconds")), ARGPARSE_s_u (oDefCacheTTLSSH, "default-cache-ttl-ssh", "@" ), ARGPARSE_s_u (oMaxCacheTTL, "max-cache-ttl", "@" ), ARGPARSE_s_u (oMaxCacheTTLSSH, "max-cache-ttl-ssh", "@" ), ARGPARSE_s_n (oEnforcePassphraseConstraints, "enforce-passphrase-constraints", /* */ "@"), ARGPARSE_s_u (oMinPassphraseLen, "min-passphrase-len", "@"), ARGPARSE_s_u (oMinPassphraseNonalpha, "min-passphrase-nonalpha", "@"), ARGPARSE_s_s (oCheckPassphrasePattern, "check-passphrase-pattern", "@"), ARGPARSE_s_u (oMaxPassphraseDays, "max-passphrase-days", "@"), ARGPARSE_s_n (oEnablePassphraseHistory, "enable-passphrase-history", "@"), ARGPARSE_s_n (oIgnoreCacheForSigning, "ignore-cache-for-signing", /* */ N_("do not use the PIN cache when signing")), ARGPARSE_s_n (oNoAllowExternalCache, "no-allow-external-cache", /* */ N_("disallow the use of an external password cache")), ARGPARSE_s_n (oNoAllowMarkTrusted, "no-allow-mark-trusted", /* */ N_("disallow clients to mark keys as \"trusted\"")), ARGPARSE_s_n (oAllowMarkTrusted, "allow-mark-trusted", "@"), ARGPARSE_s_n (oAllowPresetPassphrase, "allow-preset-passphrase", /* */ N_("allow presetting passphrase")), ARGPARSE_s_n (oNoAllowLoopbackPinentry, "no-allow-loopback-pinentry", N_("disallow caller to override the pinentry")), ARGPARSE_s_n (oAllowLoopbackPinentry, "allow-loopback-pinentry", "@"), ARGPARSE_s_n (oAllowEmacsPinentry, "allow-emacs-pinentry", /* */ N_("allow passphrase to be prompted through Emacs")), ARGPARSE_s_n (oSSHSupport, "enable-ssh-support", N_("enable ssh support")), ARGPARSE_s_n (oPuttySupport, "enable-putty-support", #ifdef HAVE_W32_SYSTEM /* */ N_("enable putty support") #else /* */ "@" #endif ), ARGPARSE_s_n (oEnableExtendedKeyFormat, "enable-extended-key-format", "@"), /* Dummy options for backward compatibility. */ ARGPARSE_o_s (oWriteEnvFile, "write-env-file", "@"), ARGPARSE_s_n (oUseStandardSocket, "use-standard-socket", "@"), ARGPARSE_s_n (oNoUseStandardSocket, "no-use-standard-socket", "@"), {0} /* End of list */ }; /* The list of supported debug flags. */ static struct debug_flags_s debug_flags [] = { { DBG_MPI_VALUE , "mpi" }, { DBG_CRYPTO_VALUE , "crypto" }, { DBG_MEMORY_VALUE , "memory" }, { DBG_CACHE_VALUE , "cache" }, { DBG_MEMSTAT_VALUE, "memstat" }, { DBG_HASHING_VALUE, "hashing" }, { DBG_IPC_VALUE , "ipc" }, { 77, NULL } /* 77 := Do not exit on "help" or "?". */ }; #define DEFAULT_CACHE_TTL (10*60) /* 10 minutes */ #define DEFAULT_CACHE_TTL_SSH (30*60) /* 30 minutes */ #define MAX_CACHE_TTL (120*60) /* 2 hours */ #define MAX_CACHE_TTL_SSH (120*60) /* 2 hours */ #define MIN_PASSPHRASE_LEN (8) #define MIN_PASSPHRASE_NONALPHA (1) #define MAX_PASSPHRASE_DAYS (0) /* The timer tick used for housekeeping stuff. For Windows we use a longer period as the SetWaitableTimer seems to signal earlier than the 2 seconds. CHECK_OWN_SOCKET_INTERVAL defines how often we check our own socket in standard socket mode. If that value is 0 we don't check at all. All values are in seconds. */ #if defined(HAVE_W32CE_SYSTEM) # define TIMERTICK_INTERVAL (60) # define CHECK_OWN_SOCKET_INTERVAL (0) /* Never */ #elif defined(HAVE_W32_SYSTEM) # define TIMERTICK_INTERVAL (4) # define CHECK_OWN_SOCKET_INTERVAL (60) #else # define TIMERTICK_INTERVAL (2) # define CHECK_OWN_SOCKET_INTERVAL (60) #endif /* Flag indicating that the ssh-agent subsystem has been enabled. */ static int ssh_support; #ifdef HAVE_W32_SYSTEM /* Flag indicating that support for Putty has been enabled. */ static int putty_support; /* A magic value used with WM_COPYDATA. */ #define PUTTY_IPC_MAGIC 0x804e50ba /* To avoid surprises we limit the size of the mapped IPC file to this value. Putty currently (0.62) uses 8k, thus 16k should be enough for the foreseeable future. */ #define PUTTY_IPC_MAXLEN 16384 #endif /*HAVE_W32_SYSTEM*/ /* The list of open file descriptors at startup. Note that this list * has been allocated using the standard malloc. */ #ifndef HAVE_W32_SYSTEM static int *startup_fd_list; #endif /* The signal mask at startup and a flag telling whether it is valid. */ #ifdef HAVE_SIGPROCMASK static sigset_t startup_signal_mask; static int startup_signal_mask_valid; #endif /* Flag to indicate that a shutdown was requested. */ static int shutdown_pending; /* Counter for the currently running own socket checks. */ static int check_own_socket_running; /* Flags to indicate that check_own_socket shall not be called. */ static int disable_check_own_socket; /* Flag indicating that we are in supervised mode. */ static int is_supervised; /* Flag to inhibit socket removal in cleanup. */ static int inhibit_socket_removal; /* It is possible that we are currently running under setuid permissions */ static int maybe_setuid = 1; /* Name of the communication socket used for native gpg-agent requests. The second variable is either NULL or a malloced string with the real socket name in case it has been redirected. */ static char *socket_name; static char *redir_socket_name; /* Name of the optional extra socket used for native gpg-agent requests. */ static char *socket_name_extra; static char *redir_socket_name_extra; /* Name of the optional browser socket used for native gpg-agent requests. */ static char *socket_name_browser; static char *redir_socket_name_browser; /* Name of the communication socket used for ssh-agent protocol. */ static char *socket_name_ssh; static char *redir_socket_name_ssh; /* We need to keep track of the server's nonces (these are dummies for POSIX systems). */ static assuan_sock_nonce_t socket_nonce; static assuan_sock_nonce_t socket_nonce_extra; static assuan_sock_nonce_t socket_nonce_browser; static assuan_sock_nonce_t socket_nonce_ssh; /* Default values for options passed to the pinentry. */ static char *default_display; static char *default_ttyname; static char *default_ttytype; static char *default_lc_ctype; static char *default_lc_messages; static char *default_xauthority; /* Name of a config file, which will be reread on a HUP if it is not NULL. */ static char *config_filename; /* Helper to implement --debug-level */ static const char *debug_level; /* Keep track of the current log file so that we can avoid updating the log file after a SIGHUP if it didn't changed. Malloced. */ static char *current_logfile; /* The handle_tick() function may test whether a parent is still running. We record the PID of the parent here or -1 if it should be watched. */ static pid_t parent_pid = (pid_t)(-1); /* Number of active connections. */ static int active_connections; /* This object is used to dispatch progress messages from Libgcrypt to * the right thread. Given that we will have at max only a few dozen * connections at a time, using a linked list is the easiest way to * handle this. */ struct progress_dispatch_s { struct progress_dispatch_s *next; /* The control object of the connection. If this is NULL no * connection is associated with this item and it is free for reuse * by new connections. */ ctrl_t ctrl; /* The thread id of (npth_self) of the connection. */ npth_t tid; /* The callback set by the connection. This is similar to the * Libgcrypt callback but with the control object passed as the * first argument. */ void (*cb)(ctrl_t ctrl, const char *what, int printchar, int current, int total); }; struct progress_dispatch_s *progress_dispatch_list; /* Local prototypes. */ static char *create_socket_name (char *standard_name, int with_homedir); static gnupg_fd_t create_server_socket (char *name, int primary, int cygwin, char **r_redir_name, assuan_sock_nonce_t *nonce); static void create_directories (void); static void agent_libgcrypt_progress_cb (void *data, const char *what, int printchar, int current, int total); static void agent_init_default_ctrl (ctrl_t ctrl); static void agent_deinit_default_ctrl (ctrl_t ctrl); static void handle_connections (gnupg_fd_t listen_fd, gnupg_fd_t listen_fd_extra, gnupg_fd_t listen_fd_browser, gnupg_fd_t listen_fd_ssh); static void check_own_socket (void); static int check_for_running_agent (int silent); /* Pth wrapper function definitions. */ ASSUAN_SYSTEM_NPTH_IMPL; /* Functions. */ /* Allocate a string describing a library version by calling a GETFNC. This function is expected to be called only once. GETFNC is expected to have a semantic like gcry_check_version (). */ static char * make_libversion (const char *libname, const char *(*getfnc)(const char*)) { const char *s; char *result; if (maybe_setuid) { gcry_control (GCRYCTL_INIT_SECMEM, 0, 0); /* Drop setuid. */ maybe_setuid = 0; } s = getfnc (NULL); result = xmalloc (strlen (libname) + 1 + strlen (s) + 1); strcpy (stpcpy (stpcpy (result, libname), " "), s); return result; } /* Return strings describing this program. The case values are described in common/argparse.c:strusage. The values here override the default values given by strusage. */ static const char * my_strusage (int level) { static char *ver_gcry; const char *p; switch (level) { case 11: p = "@GPG_AGENT@ (@GNUPG@)"; break; case 13: p = VERSION; break; case 17: p = PRINTABLE_OS_NAME; break; /* TRANSLATORS: @EMAIL@ will get replaced by the actual bug reporting address. This is so that we can change the reporting address without breaking the translations. */ case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break; case 20: if (!ver_gcry) ver_gcry = make_libversion ("libgcrypt", gcry_check_version); p = ver_gcry; break; case 1: case 40: p = _("Usage: @GPG_AGENT@ [options] (-h for help)"); break; case 41: p = _("Syntax: @GPG_AGENT@ [options] [command [args]]\n" "Secret key management for @GNUPG@\n"); break; default: p = NULL; } return p; } /* Setup the debugging. With the global variable DEBUG_LEVEL set to NULL only the active debug flags are propagated to the subsystems. With DEBUG_LEVEL set, a specific set of debug flags is set; thus overriding all flags already set. Note that we don't fail here, because it is important to keep gpg-agent running even after re-reading the options due to a SIGHUP. */ static void set_debug (void) { int numok = (debug_level && digitp (debug_level)); int numlvl = numok? atoi (debug_level) : 0; if (!debug_level) ; else if (!strcmp (debug_level, "none") || (numok && numlvl < 1)) opt.debug = 0; else if (!strcmp (debug_level, "basic") || (numok && numlvl <= 2)) opt.debug = DBG_IPC_VALUE; else if (!strcmp (debug_level, "advanced") || (numok && numlvl <= 5)) opt.debug = DBG_IPC_VALUE; else if (!strcmp (debug_level, "expert") || (numok && numlvl <= 8)) opt.debug = (DBG_IPC_VALUE | DBG_CACHE_VALUE); else if (!strcmp (debug_level, "guru") || numok) { opt.debug = ~0; /* Unless the "guru" string has been used we don't want to allow hashing debugging. The rationale is that people tend to select the highest debug value and would then clutter their disk with debug files which may reveal confidential data. */ if (numok) opt.debug &= ~(DBG_HASHING_VALUE); } else { log_error (_("invalid debug-level '%s' given\n"), debug_level); opt.debug = 0; /* Reset debugging, so that prior debug statements won't have an undesired effect. */ } if (opt.debug && !opt.verbose) opt.verbose = 1; if (opt.debug && opt.quiet) opt.quiet = 0; if (opt.debug & DBG_MPI_VALUE) gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 2); if (opt.debug & DBG_CRYPTO_VALUE ) gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 1); gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose); if (opt.debug) parse_debug_flag (NULL, &opt.debug, debug_flags); } /* Helper for cleanup to remove one socket with NAME. REDIR_NAME is the corresponding real name if the socket has been redirected. */ static void remove_socket (char *name, char *redir_name) { if (name && *name) { if (redir_name) name = redir_name; gnupg_remove (name); *name = 0; } } /* Discover which inherited file descriptors correspond to which * services/sockets offered by gpg-agent, using the LISTEN_FDS and * LISTEN_FDNAMES convention. The understood labels are "ssh", * "extra", and "browser". "std" or other labels will be interpreted * as the standard socket. * * This function is designed to log errors when the expected file * descriptors don't make sense, but to do its best to continue to * work even in the face of minor misconfigurations. * * For more information on the LISTEN_FDS convention, see * sd_listen_fds(3) on certain Linux distributions. */ #ifndef HAVE_W32_SYSTEM static void map_supervised_sockets (gnupg_fd_t *r_fd, gnupg_fd_t *r_fd_extra, gnupg_fd_t *r_fd_browser, gnupg_fd_t *r_fd_ssh) { struct { const char *label; int **fdaddr; char **nameaddr; } tbl[] = { { "ssh", &r_fd_ssh, &socket_name_ssh }, { "browser", &r_fd_browser, &socket_name_browser }, { "extra", &r_fd_extra, &socket_name_extra }, { "std", &r_fd, &socket_name } /* (Must be the last item.) */ }; const char *envvar; char **fdnames; int nfdnames; int fd_count; *r_fd = *r_fd_extra = *r_fd_browser = *r_fd_ssh = -1; /* Print a warning if LISTEN_PID does not match outr pid. */ envvar = getenv ("LISTEN_PID"); if (!envvar) log_error ("no LISTEN_PID environment variable found in " "--supervised mode (ignoring)\n"); else if (strtoul (envvar, NULL, 10) != (unsigned long)getpid ()) log_error ("environment variable LISTEN_PID (%lu) does not match" " our pid (%lu) in --supervised mode (ignoring)\n", (unsigned long)strtoul (envvar, NULL, 10), (unsigned long)getpid ()); /* Parse LISTEN_FDNAMES into the array FDNAMES. */ envvar = getenv ("LISTEN_FDNAMES"); if (envvar) { fdnames = strtokenize (envvar, ":"); if (!fdnames) { log_error ("strtokenize failed: %s\n", gpg_strerror (gpg_error_from_syserror ())); agent_exit (1); } for (nfdnames=0; fdnames[nfdnames]; nfdnames++) ; } else { fdnames = NULL; nfdnames = 0; } /* Parse LISTEN_FDS into fd_count or provide a replacement. */ envvar = getenv ("LISTEN_FDS"); if (envvar) fd_count = atoi (envvar); else if (fdnames) { log_error ("no LISTEN_FDS environment variable found in --supervised" " mode (relying on LISTEN_FDNAMES instead)\n"); fd_count = nfdnames; } else { log_error ("no LISTEN_FDS or LISTEN_FDNAMES environment variables " "found in --supervised mode" " (assuming 1 active descriptor)\n"); fd_count = 1; } if (fd_count < 1) { log_error ("--supervised mode expects at least one file descriptor" " (was told %d, carrying on as though it were 1)\n", fd_count); fd_count = 1; } /* Assign the descriptors to the return values. */ if (!fdnames) { struct stat statbuf; if (fd_count != 1) log_error ("no LISTEN_FDNAMES and LISTEN_FDS (%d) != 1" " in --supervised mode." " (ignoring all sockets but the first one)\n", fd_count); if (fstat (3, &statbuf) == -1 && errno ==EBADF) log_fatal ("file descriptor 3 must be valid in --supervised mode" " if LISTEN_FDNAMES is not set\n"); *r_fd = 3; socket_name = gnupg_get_socket_name (3); } else if (fd_count != nfdnames) { log_fatal ("number of items in LISTEN_FDNAMES (%d) does not match " "LISTEN_FDS (%d) in --supervised mode\n", nfdnames, fd_count); } else { int i, j, fd; char *name; for (i = 0; i < nfdnames; i++) { for (j = 0; j < DIM (tbl); j++) { if (!strcmp (fdnames[i], tbl[j].label) || j == DIM(tbl)-1) { fd = 3 + i; if (**tbl[j].fdaddr == -1) { name = gnupg_get_socket_name (fd); if (name) { **tbl[j].fdaddr = fd; *tbl[j].nameaddr = name; log_info ("using fd %d for %s socket (%s)\n", fd, tbl[j].label, name); } else { log_error ("cannot listen on fd %d for %s socket\n", fd, tbl[j].label); close (fd); } } else { log_error ("cannot listen on more than one %s socket\n", tbl[j].label); close (fd); } break; } } } } xfree (fdnames); } #endif /*!HAVE_W32_SYSTEM*/ /* Cleanup code for this program. This is either called has an atexit handler or directly. */ static void cleanup (void) { static int done; if (done) return; done = 1; deinitialize_module_cache (); if (!is_supervised && !inhibit_socket_removal) { remove_socket (socket_name, redir_socket_name); if (opt.extra_socket > 1) remove_socket (socket_name_extra, redir_socket_name_extra); if (opt.browser_socket > 1) remove_socket (socket_name_browser, redir_socket_name_browser); remove_socket (socket_name_ssh, redir_socket_name_ssh); } } /* Handle options which are allowed to be reset after program start. Return true when the current option in PARGS could be handled and false if not. As a special feature, passing a value of NULL for PARGS, resets the options to the default. REREAD should be set true if it is not the initial option parsing. */ static int parse_rereadable_options (ARGPARSE_ARGS *pargs, int reread) { if (!pargs) { /* reset mode */ opt.quiet = 0; opt.verbose = 0; opt.debug = 0; opt.no_grab = 0; opt.debug_pinentry = 0; opt.pinentry_program = NULL; opt.pinentry_touch_file = NULL; xfree (opt.pinentry_invisible_char); opt.pinentry_invisible_char = NULL; opt.pinentry_timeout = 0; opt.scdaemon_program = NULL; opt.def_cache_ttl = DEFAULT_CACHE_TTL; opt.def_cache_ttl_ssh = DEFAULT_CACHE_TTL_SSH; opt.max_cache_ttl = MAX_CACHE_TTL; opt.max_cache_ttl_ssh = MAX_CACHE_TTL_SSH; opt.enforce_passphrase_constraints = 0; opt.min_passphrase_len = MIN_PASSPHRASE_LEN; opt.min_passphrase_nonalpha = MIN_PASSPHRASE_NONALPHA; opt.check_passphrase_pattern = NULL; opt.max_passphrase_days = MAX_PASSPHRASE_DAYS; opt.enable_passphrase_history = 0; opt.enable_extended_key_format = 0; opt.ignore_cache_for_signing = 0; opt.allow_mark_trusted = 1; opt.allow_external_cache = 1; opt.allow_loopback_pinentry = 1; opt.allow_emacs_pinentry = 0; opt.disable_scdaemon = 0; disable_check_own_socket = 0; return 1; } switch (pargs->r_opt) { case oQuiet: opt.quiet = 1; break; case oVerbose: opt.verbose++; break; case oDebug: parse_debug_flag (pargs->r.ret_str, &opt.debug, debug_flags); break; case oDebugAll: opt.debug = ~0; break; case oDebugLevel: debug_level = pargs->r.ret_str; break; case oDebugPinentry: opt.debug_pinentry = 1; break; case oLogFile: if (!reread) return 0; /* not handeld */ if (!current_logfile || !pargs->r.ret_str || strcmp (current_logfile, pargs->r.ret_str)) { log_set_file (pargs->r.ret_str); xfree (current_logfile); current_logfile = xtrystrdup (pargs->r.ret_str); } break; case oNoGrab: opt.no_grab = 1; break; case oPinentryProgram: opt.pinentry_program = pargs->r.ret_str; break; case oPinentryTouchFile: opt.pinentry_touch_file = pargs->r.ret_str; break; case oPinentryInvisibleChar: xfree (opt.pinentry_invisible_char); opt.pinentry_invisible_char = xtrystrdup (pargs->r.ret_str); break; break; case oPinentryTimeout: opt.pinentry_timeout = pargs->r.ret_ulong; break; case oScdaemonProgram: opt.scdaemon_program = pargs->r.ret_str; break; case oDisableScdaemon: opt.disable_scdaemon = 1; break; case oDisableCheckOwnSocket: disable_check_own_socket = 1; break; case oDefCacheTTL: opt.def_cache_ttl = pargs->r.ret_ulong; break; case oDefCacheTTLSSH: opt.def_cache_ttl_ssh = pargs->r.ret_ulong; break; case oMaxCacheTTL: opt.max_cache_ttl = pargs->r.ret_ulong; break; case oMaxCacheTTLSSH: opt.max_cache_ttl_ssh = pargs->r.ret_ulong; break; case oEnforcePassphraseConstraints: opt.enforce_passphrase_constraints=1; break; case oMinPassphraseLen: opt.min_passphrase_len = pargs->r.ret_ulong; break; case oMinPassphraseNonalpha: opt.min_passphrase_nonalpha = pargs->r.ret_ulong; break; case oCheckPassphrasePattern: opt.check_passphrase_pattern = pargs->r.ret_str; break; case oMaxPassphraseDays: opt.max_passphrase_days = pargs->r.ret_ulong; break; case oEnablePassphraseHistory: opt.enable_passphrase_history = 1; break; case oEnableExtendedKeyFormat: opt.enable_extended_key_format = 1; break; case oIgnoreCacheForSigning: opt.ignore_cache_for_signing = 1; break; case oAllowMarkTrusted: opt.allow_mark_trusted = 1; break; case oNoAllowMarkTrusted: opt.allow_mark_trusted = 0; break; case oAllowPresetPassphrase: opt.allow_preset_passphrase = 1; break; case oAllowLoopbackPinentry: opt.allow_loopback_pinentry = 1; break; case oNoAllowLoopbackPinentry: opt.allow_loopback_pinentry = 0; break; case oNoAllowExternalCache: opt.allow_external_cache = 0; break; case oAllowEmacsPinentry: opt.allow_emacs_pinentry = 1; break; default: return 0; /* not handled */ } return 1; /* handled */ } /* Fixup some options after all have been processed. */ static void finalize_rereadable_options (void) { } static void thread_init_once (void) { static int npth_initialized = 0; if (!npth_initialized) { npth_initialized++; npth_init (); } gpgrt_set_syscall_clamp (npth_unprotect, npth_protect); /* Now that we have set the syscall clamp we need to tell Libgcrypt * that it should get them from libgpg-error. Note that Libgcrypt * has already been initialized but at that point nPth was not * initialized and thus Libgcrypt could not set its system call * clamp. */ #if GCRYPT_VERSION_NUMBER >= 0x010800 /* 1.8.0 */ gcry_control (GCRYCTL_REINIT_SYSCALL_CLAMP, 0, 0); #endif } static void initialize_modules (void) { thread_init_once (); assuan_set_system_hooks (ASSUAN_SYSTEM_NPTH); initialize_module_cache (); initialize_module_call_pinentry (); initialize_module_call_scd (); initialize_module_trustlist (); } /* The main entry point. */ int main (int argc, char **argv ) { ARGPARSE_ARGS pargs; int orig_argc; char **orig_argv; FILE *configfp = NULL; char *configname = NULL; const char *shell; unsigned configlineno; int parse_debug = 0; int default_config =1; int pipe_server = 0; int is_daemon = 0; int nodetach = 0; int csh_style = 0; char *logfile = NULL; int debug_wait = 0; int gpgconf_list = 0; gpg_error_t err; struct assuan_malloc_hooks malloc_hooks; early_system_init (); /* Before we do anything else we save the list of currently open file descriptors and the signal mask. This info is required to do the exec call properly. We don't need it on Windows. */ #ifndef HAVE_W32_SYSTEM startup_fd_list = get_all_open_fds (); #endif /*!HAVE_W32_SYSTEM*/ #ifdef HAVE_SIGPROCMASK if (!sigprocmask (SIG_UNBLOCK, NULL, &startup_signal_mask)) startup_signal_mask_valid = 1; #endif /*HAVE_SIGPROCMASK*/ /* Set program name etc. */ set_strusage (my_strusage); gcry_control (GCRYCTL_SUSPEND_SECMEM_WARN); /* Please note that we may running SUID(ROOT), so be very CAREFUL when adding any stuff between here and the call to INIT_SECMEM() somewhere after the option parsing */ log_set_prefix (GPG_AGENT_NAME, GPGRT_LOG_WITH_PREFIX|GPGRT_LOG_WITH_PID); /* Make sure that our subsystems are ready. */ i18n_init (); init_common_subsystems (&argc, &argv); malloc_hooks.malloc = gcry_malloc; malloc_hooks.realloc = gcry_realloc; malloc_hooks.free = gcry_free; assuan_set_malloc_hooks (&malloc_hooks); assuan_set_gpg_err_source (GPG_ERR_SOURCE_DEFAULT); assuan_sock_init (); setup_libassuan_logging (&opt.debug, NULL); setup_libgcrypt_logging (); gcry_control (GCRYCTL_USE_SECURE_RNDPOOL); gcry_set_progress_handler (agent_libgcrypt_progress_cb, NULL); disable_core_dumps (); /* Set default options. */ parse_rereadable_options (NULL, 0); /* Reset them to default values. */ shell = getenv ("SHELL"); if (shell && strlen (shell) >= 3 && !strcmp (shell+strlen (shell)-3, "csh") ) csh_style = 1; /* Record some of the original environment strings. */ { const char *s; int idx; static const char *names[] = { "DISPLAY", "TERM", "XAUTHORITY", "PINENTRY_USER_DATA", NULL }; err = 0; opt.startup_env = session_env_new (); if (!opt.startup_env) err = gpg_error_from_syserror (); for (idx=0; !err && names[idx]; idx++) { s = getenv (names[idx]); if (s) err = session_env_setenv (opt.startup_env, names[idx], s); } if (!err) { s = gnupg_ttyname (0); if (s) err = session_env_setenv (opt.startup_env, "GPG_TTY", s); } if (err) log_fatal ("error recording startup environment: %s\n", gpg_strerror (err)); /* Fixme: Better use the locale function here. */ opt.startup_lc_ctype = getenv ("LC_CTYPE"); if (opt.startup_lc_ctype) opt.startup_lc_ctype = xstrdup (opt.startup_lc_ctype); opt.startup_lc_messages = getenv ("LC_MESSAGES"); if (opt.startup_lc_messages) opt.startup_lc_messages = xstrdup (opt.startup_lc_messages); } /* Check whether we have a config file on the commandline */ orig_argc = argc; orig_argv = argv; pargs.argc = &argc; pargs.argv = &argv; pargs.flags= 1|(1<<6); /* do not remove the args, ignore version */ while (arg_parse( &pargs, opts)) { if (pargs.r_opt == oDebug || pargs.r_opt == oDebugAll) parse_debug++; else if (pargs.r_opt == oOptions) { /* yes there is one, so we do not try the default one, but read the option file when it is encountered at the commandline */ default_config = 0; } else if (pargs.r_opt == oNoOptions) default_config = 0; /* --no-options */ else if (pargs.r_opt == oHomedir) gnupg_set_homedir (pargs.r.ret_str); else if (pargs.r_opt == oDebugQuickRandom) { gcry_control (GCRYCTL_ENABLE_QUICK_RANDOM, 0); } } /* Initialize the secure memory. */ gcry_control (GCRYCTL_INIT_SECMEM, SECMEM_BUFFER_SIZE, 0); maybe_setuid = 0; /* Now we are now working under our real uid */ if (default_config) configname = make_filename (gnupg_homedir (), GPG_AGENT_NAME EXTSEP_S "conf", NULL); argc = orig_argc; argv = orig_argv; pargs.argc = &argc; pargs.argv = &argv; pargs.flags= 1; /* do not remove the args */ next_pass: if (configname) { configlineno = 0; configfp = fopen (configname, "r"); if (!configfp) { if (default_config) { if( parse_debug ) log_info (_("Note: no default option file '%s'\n"), configname ); /* Save the default conf file name so that reread_configuration is able to test whether the config file has been created in the meantime. */ xfree (config_filename); config_filename = configname; configname = NULL; } else { log_error (_("option file '%s': %s\n"), configname, strerror(errno) ); exit(2); } xfree (configname); configname = NULL; } if (parse_debug && configname ) log_info (_("reading options from '%s'\n"), configname ); default_config = 0; } while (optfile_parse( configfp, configname, &configlineno, &pargs, opts) ) { if (parse_rereadable_options (&pargs, 0)) continue; /* Already handled */ switch (pargs.r_opt) { case aGPGConfList: gpgconf_list = 1; break; case aGPGConfTest: gpgconf_list = 2; break; case aUseStandardSocketP: gpgconf_list = 3; break; case oBatch: opt.batch=1; break; case oDebugWait: debug_wait = pargs.r.ret_int; break; case oOptions: /* config files may not be nested (silently ignore them) */ if (!configfp) { xfree(configname); configname = xstrdup(pargs.r.ret_str); goto next_pass; } break; case oNoGreeting: /* Dummy option. */ break; case oNoVerbose: opt.verbose = 0; break; case oNoOptions: break; /* no-options */ case oHomedir: gnupg_set_homedir (pargs.r.ret_str); break; case oNoDetach: nodetach = 1; break; case oLogFile: logfile = pargs.r.ret_str; break; case oCsh: csh_style = 1; break; case oSh: csh_style = 0; break; case oServer: pipe_server = 1; break; case oDaemon: is_daemon = 1; break; case oSupervised: is_supervised = 1; break; case oDisplay: default_display = xstrdup (pargs.r.ret_str); break; case oTTYname: default_ttyname = xstrdup (pargs.r.ret_str); break; case oTTYtype: default_ttytype = xstrdup (pargs.r.ret_str); break; case oLCctype: default_lc_ctype = xstrdup (pargs.r.ret_str); break; case oLCmessages: default_lc_messages = xstrdup (pargs.r.ret_str); break; case oXauthority: default_xauthority = xstrdup (pargs.r.ret_str); break; case oUseStandardSocket: case oNoUseStandardSocket: obsolete_option (configname, configlineno, "use-standard-socket"); break; case oFakedSystemTime: { time_t faked_time = isotime2epoch (pargs.r.ret_str); if (faked_time == (time_t)(-1)) faked_time = (time_t)strtoul (pargs.r.ret_str, NULL, 10); gnupg_set_time (faked_time, 0); } break; case oKeepTTY: opt.keep_tty = 1; break; case oKeepDISPLAY: opt.keep_display = 1; break; case oSSHSupport: ssh_support = 1; break; case oPuttySupport: # ifdef HAVE_W32_SYSTEM putty_support = 1; # endif break; case oExtraSocket: opt.extra_socket = 1; /* (1 = points into argv) */ socket_name_extra = pargs.r.ret_str; break; case oBrowserSocket: opt.browser_socket = 1; /* (1 = points into argv) */ socket_name_browser = pargs.r.ret_str; break; case oDebugQuickRandom: /* Only used by the first stage command line parser. */ break; case oWriteEnvFile: obsolete_option (configname, configlineno, "write-env-file"); break; default : pargs.err = configfp? 1:2; break; } } if (configfp) { fclose( configfp ); configfp = NULL; /* Keep a copy of the name so that it can be read on SIGHUP. */ if (config_filename != configname) { xfree (config_filename); config_filename = configname; } configname = NULL; goto next_pass; } xfree (configname); configname = NULL; if (log_get_errorcount(0)) exit(2); finalize_rereadable_options (); /* Print a warning if an argument looks like an option. */ if (!opt.quiet && !(pargs.flags & ARGPARSE_FLAG_STOP_SEEN)) { int i; for (i=0; i < argc; i++) if (argv[i][0] == '-' && argv[i][1] == '-') log_info (_("Note: '%s' is not considered an option\n"), argv[i]); } #ifdef ENABLE_NLS /* gpg-agent usually does not output any messages because it runs in the background. For log files it is acceptable to have messages always encoded in utf-8. We switch here to utf-8, so that commands like --help still give native messages. It is far easier to switch only once instead of for every message and it actually helps when more then one thread is active (avoids an extra copy step). */ bind_textdomain_codeset (PACKAGE_GT, "UTF-8"); #endif if (!pipe_server && !is_daemon && !gpgconf_list && !is_supervised) { /* We have been called without any command and thus we merely check whether an agent is already running. We do this right here so that we don't clobber a logfile with this check but print the status directly to stderr. */ opt.debug = 0; set_debug (); check_for_running_agent (0); agent_exit (0); } if (is_supervised) ; else if (!opt.extra_socket) opt.extra_socket = 1; else if (socket_name_extra && (!strcmp (socket_name_extra, "none") || !strcmp (socket_name_extra, "/dev/null"))) { /* User requested not to create this socket. */ opt.extra_socket = 0; socket_name_extra = NULL; } if (is_supervised) ; else if (!opt.browser_socket) opt.browser_socket = 1; else if (socket_name_browser && (!strcmp (socket_name_browser, "none") || !strcmp (socket_name_browser, "/dev/null"))) { /* User requested not to create this socket. */ opt.browser_socket = 0; socket_name_browser = NULL; } set_debug (); if (atexit (cleanup)) { log_error ("atexit failed\n"); cleanup (); exit (1); } /* Try to create missing directories. */ create_directories (); if (debug_wait && pipe_server) { thread_init_once (); log_debug ("waiting for debugger - my pid is %u .....\n", (unsigned int)getpid()); gnupg_sleep (debug_wait); log_debug ("... okay\n"); } if (gpgconf_list == 3) { /* We now use the standard socket always - return true for backward compatibility. */ agent_exit (0); } else if (gpgconf_list == 2) agent_exit (0); else if (gpgconf_list) { char *filename; char *filename_esc; /* List options and default values in the GPG Conf format. */ filename = make_filename (gnupg_homedir (), GPG_AGENT_NAME EXTSEP_S "conf", NULL); filename_esc = percent_escape (filename, NULL); es_printf ("%s-%s.conf:%lu:\"%s\n", GPGCONF_NAME, GPG_AGENT_NAME, GC_OPT_FLAG_DEFAULT, filename_esc); xfree (filename); xfree (filename_esc); es_printf ("verbose:%lu:\n" "quiet:%lu:\n" "debug-level:%lu:\"none:\n" "log-file:%lu:\n", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME, GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME, GC_OPT_FLAG_DEFAULT|GC_OPT_FLAG_RUNTIME, GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME ); es_printf ("default-cache-ttl:%lu:%d:\n", GC_OPT_FLAG_DEFAULT|GC_OPT_FLAG_RUNTIME, DEFAULT_CACHE_TTL ); es_printf ("default-cache-ttl-ssh:%lu:%d:\n", GC_OPT_FLAG_DEFAULT|GC_OPT_FLAG_RUNTIME, DEFAULT_CACHE_TTL_SSH ); es_printf ("max-cache-ttl:%lu:%d:\n", GC_OPT_FLAG_DEFAULT|GC_OPT_FLAG_RUNTIME, MAX_CACHE_TTL ); es_printf ("max-cache-ttl-ssh:%lu:%d:\n", GC_OPT_FLAG_DEFAULT|GC_OPT_FLAG_RUNTIME, MAX_CACHE_TTL_SSH ); es_printf ("enforce-passphrase-constraints:%lu:\n", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME); es_printf ("min-passphrase-len:%lu:%d:\n", GC_OPT_FLAG_DEFAULT|GC_OPT_FLAG_RUNTIME, MIN_PASSPHRASE_LEN ); es_printf ("min-passphrase-nonalpha:%lu:%d:\n", GC_OPT_FLAG_DEFAULT|GC_OPT_FLAG_RUNTIME, MIN_PASSPHRASE_NONALPHA); es_printf ("check-passphrase-pattern:%lu:\n", GC_OPT_FLAG_DEFAULT|GC_OPT_FLAG_RUNTIME); es_printf ("max-passphrase-days:%lu:%d:\n", GC_OPT_FLAG_DEFAULT|GC_OPT_FLAG_RUNTIME, MAX_PASSPHRASE_DAYS); es_printf ("enable-passphrase-history:%lu:\n", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME); es_printf ("no-grab:%lu:\n", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME); es_printf ("ignore-cache-for-signing:%lu:\n", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME); es_printf ("no-allow-external-cache:%lu:\n", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME); es_printf ("no-allow-mark-trusted:%lu:\n", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME); es_printf ("disable-scdaemon:%lu:\n", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME); es_printf ("enable-ssh-support:%lu:\n", GC_OPT_FLAG_NONE); #ifdef HAVE_W32_SYSTEM es_printf ("enable-putty-support:%lu:\n", GC_OPT_FLAG_NONE); #endif es_printf ("no-allow-loopback-pinentry:%lu:\n", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME); es_printf ("allow-emacs-pinentry:%lu:\n", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME); es_printf ("pinentry-timeout:%lu:0:\n", GC_OPT_FLAG_DEFAULT|GC_OPT_FLAG_RUNTIME); es_printf ("enable-extended-key-format:%lu:\n", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME); agent_exit (0); } /* Now start with logging to a file if this is desired. */ if (logfile) { log_set_file (logfile); log_set_prefix (NULL, (GPGRT_LOG_WITH_PREFIX | GPGRT_LOG_WITH_TIME | GPGRT_LOG_WITH_PID)); current_logfile = xstrdup (logfile); } /* Make sure that we have a default ttyname. */ if (!default_ttyname && gnupg_ttyname (1)) default_ttyname = xstrdup (gnupg_ttyname (1)); if (!default_ttytype && getenv ("TERM")) default_ttytype = xstrdup (getenv ("TERM")); if (pipe_server) { /* This is the simple pipe based server */ ctrl_t ctrl; initialize_modules (); ctrl = xtrycalloc (1, sizeof *ctrl); if (!ctrl) { log_error ("error allocating connection control data: %s\n", strerror (errno) ); agent_exit (1); } ctrl->session_env = session_env_new (); if (!ctrl->session_env) { log_error ("error allocating session environment block: %s\n", strerror (errno) ); xfree (ctrl); agent_exit (1); } agent_init_default_ctrl (ctrl); start_command_handler (ctrl, GNUPG_INVALID_FD, GNUPG_INVALID_FD); agent_deinit_default_ctrl (ctrl); xfree (ctrl); } else if (is_supervised) { #ifndef HAVE_W32_SYSTEM gnupg_fd_t fd, fd_extra, fd_browser, fd_ssh; initialize_modules (); /* when supervised and sending logs to stderr, the process supervisor should handle log entry metadata (pid, name, timestamp) */ if (!logfile) log_set_prefix (NULL, 0); log_info ("%s %s starting in supervised mode.\n", strusage(11), strusage(13) ); /* See below in "regular server mode" on why we remove certain * envvars. */ if (!opt.keep_display) gnupg_unsetenv ("DISPLAY"); gnupg_unsetenv ("INSIDE_EMACS"); /* Virtually create the sockets. Note that we use -1 here * because the whole thing works only on Unix. */ map_supervised_sockets (&fd, &fd_extra, &fd_browser, &fd_ssh); if (fd == -1) log_fatal ("no standard socket provided\n"); #ifdef HAVE_SIGPROCMASK if (startup_signal_mask_valid) { if (sigprocmask (SIG_SETMASK, &startup_signal_mask, NULL)) log_error ("error restoring signal mask: %s\n", strerror (errno)); } else log_info ("no saved signal mask\n"); #endif /*HAVE_SIGPROCMASK*/ log_info ("listening on: std=%d extra=%d browser=%d ssh=%d\n", fd, fd_extra, fd_browser, fd_ssh); handle_connections (fd, fd_extra, fd_browser, fd_ssh); #endif /*!HAVE_W32_SYSTEM*/ } else if (!is_daemon) ; /* NOTREACHED */ else { /* Regular server mode */ gnupg_fd_t fd; gnupg_fd_t fd_extra = GNUPG_INVALID_FD; gnupg_fd_t fd_browser = GNUPG_INVALID_FD; gnupg_fd_t fd_ssh = GNUPG_INVALID_FD; #ifndef HAVE_W32_SYSTEM pid_t pid; #endif /* Remove the DISPLAY variable so that a pinentry does not default to a specific display. There is still a default display when gpg-agent was started using --display or a client requested this using an OPTION command. Note, that we don't do this when running in reverse daemon mode (i.e. when exec the program given as arguments). */ #ifndef HAVE_W32_SYSTEM if (!opt.keep_display && !argc) gnupg_unsetenv ("DISPLAY"); #endif /* Remove the INSIDE_EMACS variable so that a pinentry does not always try to interact with Emacs. The variable is set when a client requested this using an OPTION command. */ gnupg_unsetenv ("INSIDE_EMACS"); /* Create the sockets. */ socket_name = create_socket_name (GPG_AGENT_SOCK_NAME, 1); fd = create_server_socket (socket_name, 1, 0, &redir_socket_name, &socket_nonce); if (opt.extra_socket) { if (socket_name_extra) socket_name_extra = create_socket_name (socket_name_extra, 0); else socket_name_extra = create_socket_name /**/ (GPG_AGENT_EXTRA_SOCK_NAME, 1); opt.extra_socket = 2; /* Indicate that it has been malloced. */ fd_extra = create_server_socket (socket_name_extra, 0, 0, &redir_socket_name_extra, &socket_nonce_extra); } if (opt.browser_socket) { if (socket_name_browser) socket_name_browser = create_socket_name (socket_name_browser, 0); else socket_name_browser= create_socket_name /**/ (GPG_AGENT_BROWSER_SOCK_NAME, 1); opt.browser_socket = 2; /* Indicate that it has been malloced. */ fd_browser = create_server_socket (socket_name_browser, 0, 0, &redir_socket_name_browser, &socket_nonce_browser); } socket_name_ssh = create_socket_name (GPG_AGENT_SSH_SOCK_NAME, 1); fd_ssh = create_server_socket (socket_name_ssh, 0, 1, &redir_socket_name_ssh, &socket_nonce_ssh); /* If we are going to exec a program in the parent, we record the PID, so that the child may check whether the program is still alive. */ if (argc) parent_pid = getpid (); fflush (NULL); #ifdef HAVE_W32_SYSTEM (void)csh_style; (void)nodetach; initialize_modules (); #else /*!HAVE_W32_SYSTEM*/ pid = fork (); if (pid == (pid_t)-1) { log_fatal ("fork failed: %s\n", strerror (errno) ); exit (1); } else if (pid) { /* We are the parent */ char *infostr_ssh_sock, *infostr_ssh_valid; /* Close the socket FD. */ close (fd); /* The signal mask might not be correct right now and thus we restore it. That is not strictly necessary but some programs falsely assume a cleared signal mask. */ #ifdef HAVE_SIGPROCMASK if (startup_signal_mask_valid) { if (sigprocmask (SIG_SETMASK, &startup_signal_mask, NULL)) log_error ("error restoring signal mask: %s\n", strerror (errno)); } else log_info ("no saved signal mask\n"); #endif /*HAVE_SIGPROCMASK*/ /* Create the SSH info string if enabled. */ if (ssh_support) { if (asprintf (&infostr_ssh_sock, "SSH_AUTH_SOCK=%s", socket_name_ssh) < 0) { log_error ("out of core\n"); kill (pid, SIGTERM); exit (1); } if (asprintf (&infostr_ssh_valid, "gnupg_SSH_AUTH_SOCK_by=%lu", (unsigned long)getpid()) < 0) { log_error ("out of core\n"); kill (pid, SIGTERM); exit (1); } } *socket_name = 0; /* Don't let cleanup() remove the socket - the child should do this from now on */ if (opt.extra_socket) *socket_name_extra = 0; if (opt.browser_socket) *socket_name_browser = 0; *socket_name_ssh = 0; if (argc) { /* Run the program given on the commandline. */ if (ssh_support && (putenv (infostr_ssh_sock) || putenv (infostr_ssh_valid))) { log_error ("failed to set environment: %s\n", strerror (errno) ); kill (pid, SIGTERM ); exit (1); } /* Close all the file descriptors except the standard ones and those open at startup. We explicitly don't close 0,1,2 in case something went wrong collecting them at startup. */ close_all_fds (3, startup_fd_list); /* Run the command. */ execvp (argv[0], argv); log_error ("failed to run the command: %s\n", strerror (errno)); kill (pid, SIGTERM); exit (1); } else { /* Print the environment string, so that the caller can use shell's eval to set it */ if (csh_style) { if (ssh_support) { *strchr (infostr_ssh_sock, '=') = ' '; es_printf ("setenv %s;\n", infostr_ssh_sock); } } else { if (ssh_support) { es_printf ("%s; export SSH_AUTH_SOCK;\n", infostr_ssh_sock); } } if (ssh_support) { xfree (infostr_ssh_sock); xfree (infostr_ssh_valid); } exit (0); } /*NOTREACHED*/ } /* End parent */ /* This is the child */ initialize_modules (); /* Detach from tty and put process into a new session */ if (!nodetach ) { int i; unsigned int oldflags; /* Close stdin, stdout and stderr unless it is the log stream */ for (i=0; i <= 2; i++) { if (!log_test_fd (i) && i != fd ) { if ( ! close (i) && open ("/dev/null", i? O_WRONLY : O_RDONLY) == -1) { log_error ("failed to open '%s': %s\n", "/dev/null", strerror (errno)); cleanup (); exit (1); } } } if (setsid() == -1) { log_error ("setsid() failed: %s\n", strerror(errno) ); cleanup (); exit (1); } log_get_prefix (&oldflags); log_set_prefix (NULL, oldflags | GPGRT_LOG_RUN_DETACHED); opt.running_detached = 1; } if (chdir("/")) { log_error ("chdir to / failed: %s\n", strerror (errno)); exit (1); } { struct sigaction sa; sa.sa_handler = SIG_IGN; sigemptyset (&sa.sa_mask); sa.sa_flags = 0; sigaction (SIGPIPE, &sa, NULL); } #endif /*!HAVE_W32_SYSTEM*/ log_info ("%s %s started\n", strusage(11), strusage(13) ); handle_connections (fd, fd_extra, fd_browser, fd_ssh); assuan_sock_close (fd); } return 0; } /* Exit entry point. This function should be called instead of a plain exit. */ void agent_exit (int rc) { /*FIXME: update_random_seed_file();*/ /* We run our cleanup handler because that may close cipher contexts stored in secure memory and thus this needs to be done before we explicitly terminate secure memory. */ cleanup (); #if 1 /* at this time a bit annoying */ if (opt.debug & DBG_MEMSTAT_VALUE) { gcry_control( GCRYCTL_DUMP_MEMORY_STATS ); gcry_control( GCRYCTL_DUMP_RANDOM_STATS ); } if (opt.debug) gcry_control (GCRYCTL_DUMP_SECMEM_STATS ); #endif gcry_control (GCRYCTL_TERM_SECMEM ); rc = rc? rc : log_get_errorcount(0)? 2 : 0; exit (rc); } /* This is our callback function for gcrypt progress messages. It is set once at startup and dispatches progress messages to the corresponding threads of the agent. */ static void agent_libgcrypt_progress_cb (void *data, const char *what, int printchar, int current, int total) { struct progress_dispatch_s *dispatch; npth_t mytid = npth_self (); (void)data; for (dispatch = progress_dispatch_list; dispatch; dispatch = dispatch->next) if (dispatch->ctrl && dispatch->tid == mytid) break; if (dispatch && dispatch->cb) dispatch->cb (dispatch->ctrl, what, printchar, current, total); /* Libgcrypt < 1.8 does not know about nPth and thus when it reads * from /dev/random this will block the process. To mitigate this * problem we yield the thread when Libgcrypt tells us that it needs * more entropy. This way other threads have chance to run. */ #if GCRYPT_VERSION_NUMBER < 0x010800 /* 1.8.0 */ if (what && !strcmp (what, "need_entropy")) { #if GPGRT_VERSION_NUMBER < 0x011900 /* 1.25 */ /* In older gpg-error versions gpgrt_yield is buggy for use with * nPth and thus we need to resort to a sleep call. */ npth_usleep (1000); /* 1ms */ #else gpgrt_yield (); #endif } #endif } /* If a progress dispatcher callback has been associated with the * current connection unregister it. */ static void unregister_progress_cb (void) { struct progress_dispatch_s *dispatch; npth_t mytid = npth_self (); for (dispatch = progress_dispatch_list; dispatch; dispatch = dispatch->next) if (dispatch->ctrl && dispatch->tid == mytid) break; if (dispatch) { dispatch->ctrl = NULL; dispatch->cb = NULL; } } /* Setup a progress callback CB for the current connection. Using a * CB of NULL disables the callback. */ void agent_set_progress_cb (void (*cb)(ctrl_t ctrl, const char *what, int printchar, int current, int total), ctrl_t ctrl) { struct progress_dispatch_s *dispatch, *firstfree; npth_t mytid = npth_self (); firstfree = NULL; for (dispatch = progress_dispatch_list; dispatch; dispatch = dispatch->next) { if (dispatch->ctrl && dispatch->tid == mytid) break; if (!dispatch->ctrl && !firstfree) firstfree = dispatch; } if (!dispatch) /* None allocated: Reuse or allocate a new one. */ { if (firstfree) { dispatch = firstfree; } else if ((dispatch = xtrycalloc (1, sizeof *dispatch))) { dispatch->next = progress_dispatch_list; progress_dispatch_list = dispatch; } else { log_error ("error allocating new progress dispatcher slot: %s\n", gpg_strerror (gpg_error_from_syserror ())); return; } dispatch->ctrl = ctrl; dispatch->tid = mytid; } dispatch->cb = cb; } /* Each thread has its own local variables conveyed by a control structure usually identified by an argument named CTRL. This function is called immediately after allocating the control structure. Its purpose is to setup the default values for that structure. Note that some values may have already been set. */ static void agent_init_default_ctrl (ctrl_t ctrl) { assert (ctrl->session_env); /* Note we ignore malloc errors because we can't do much about it and the request will fail anyway shortly after this initialization. */ session_env_setenv (ctrl->session_env, "DISPLAY", default_display); session_env_setenv (ctrl->session_env, "GPG_TTY", default_ttyname); session_env_setenv (ctrl->session_env, "TERM", default_ttytype); session_env_setenv (ctrl->session_env, "XAUTHORITY", default_xauthority); session_env_setenv (ctrl->session_env, "PINENTRY_USER_DATA", NULL); if (ctrl->lc_ctype) xfree (ctrl->lc_ctype); ctrl->lc_ctype = default_lc_ctype? xtrystrdup (default_lc_ctype) : NULL; if (ctrl->lc_messages) xfree (ctrl->lc_messages); ctrl->lc_messages = default_lc_messages? xtrystrdup (default_lc_messages) /**/ : NULL; ctrl->cache_ttl_opt_preset = CACHE_TTL_OPT_PRESET; } /* Release all resources allocated by default in the control structure. This is the counterpart to agent_init_default_ctrl. */ static void agent_deinit_default_ctrl (ctrl_t ctrl) { unregister_progress_cb (); session_env_release (ctrl->session_env); if (ctrl->lc_ctype) xfree (ctrl->lc_ctype); if (ctrl->lc_messages) xfree (ctrl->lc_messages); } /* Because the ssh protocol does not send us information about the current TTY setting, we use this function to use those from startup or those explicitly set. This is also used for the restricted mode where we ignore requests to change the environment. */ gpg_error_t agent_copy_startup_env (ctrl_t ctrl) { static const char *names[] = {"GPG_TTY", "DISPLAY", "TERM", "XAUTHORITY", "PINENTRY_USER_DATA", NULL}; gpg_error_t err = 0; int idx; const char *value; for (idx=0; !err && names[idx]; idx++) if ((value = session_env_getenv (opt.startup_env, names[idx]))) err = session_env_setenv (ctrl->session_env, names[idx], value); if (!err && !ctrl->lc_ctype && opt.startup_lc_ctype) if (!(ctrl->lc_ctype = xtrystrdup (opt.startup_lc_ctype))) err = gpg_error_from_syserror (); if (!err && !ctrl->lc_messages && opt.startup_lc_messages) if (!(ctrl->lc_messages = xtrystrdup (opt.startup_lc_messages))) err = gpg_error_from_syserror (); if (err) log_error ("error setting default session environment: %s\n", gpg_strerror (err)); return err; } /* Reread parts of the configuration. Note, that this function is obviously not thread-safe and should only be called from the PTH signal handler. Fixme: Due to the way the argument parsing works, we create a memory leak here for all string type arguments. There is currently no clean way to tell whether the memory for the argument has been allocated or points into the process' original arguments. Unless we have a mechanism to tell this, we need to live on with this. */ static void reread_configuration (void) { ARGPARSE_ARGS pargs; FILE *fp; unsigned int configlineno = 0; int dummy; if (!config_filename) return; /* No config file. */ fp = fopen (config_filename, "r"); if (!fp) { log_info (_("option file '%s': %s\n"), config_filename, strerror(errno) ); return; } parse_rereadable_options (NULL, 1); /* Start from the default values. */ memset (&pargs, 0, sizeof pargs); dummy = 0; pargs.argc = &dummy; pargs.flags = 1; /* do not remove the args */ while (optfile_parse (fp, config_filename, &configlineno, &pargs, opts) ) { if (pargs.r_opt < -1) pargs.err = 1; /* Print a warning. */ else /* Try to parse this option - ignore unchangeable ones. */ parse_rereadable_options (&pargs, 1); } fclose (fp); finalize_rereadable_options (); set_debug (); } /* Return the file name of the socket we are using for native requests. */ const char * get_agent_socket_name (void) { const char *s = socket_name; return (s && *s)? s : NULL; } /* Return the file name of the socket we are using for SSH requests. */ const char * get_agent_ssh_socket_name (void) { const char *s = socket_name_ssh; return (s && *s)? s : NULL; } /* Return the number of active connections. */ int get_agent_active_connection_count (void) { return active_connections; } /* Under W32, this function returns the handle of the scdaemon notification event. Calling it the first time creates that event. */ #if defined(HAVE_W32_SYSTEM) && !defined(HAVE_W32CE_SYSTEM) void * get_agent_scd_notify_event (void) { static HANDLE the_event = INVALID_HANDLE_VALUE; if (the_event == INVALID_HANDLE_VALUE) { HANDLE h, h2; SECURITY_ATTRIBUTES sa = { sizeof (SECURITY_ATTRIBUTES), NULL, TRUE}; /* We need to use a manual reset event object due to the way our w32-pth wait function works: If we would use an automatic reset event we are not able to figure out which handle has been signaled because at the time we single out the signaled handles using WFSO the event has already been reset due to the WFMO. */ h = CreateEvent (&sa, TRUE, FALSE, NULL); if (!h) log_error ("can't create scd notify event: %s\n", w32_strerror (-1) ); else if (!DuplicateHandle (GetCurrentProcess(), h, GetCurrentProcess(), &h2, EVENT_MODIFY_STATE|SYNCHRONIZE, TRUE, 0)) { log_error ("setting syncronize for scd notify event failed: %s\n", w32_strerror (-1) ); CloseHandle (h); } else { CloseHandle (h); the_event = h2; } } return the_event; } #endif /*HAVE_W32_SYSTEM && !HAVE_W32CE_SYSTEM*/ /* Create a name for the socket in the home directory as using STANDARD_NAME. We also check for valid characters as well as against a maximum allowed length for a unix domain socket is done. The function terminates the process in case of an error. Returns: Pointer to an allocated string with the absolute name of the socket used. */ static char * create_socket_name (char *standard_name, int with_homedir) { char *name; if (with_homedir) name = make_filename (gnupg_socketdir (), standard_name, NULL); else name = make_filename (standard_name, NULL); if (strchr (name, PATHSEP_C)) { log_error (("'%s' are not allowed in the socket name\n"), PATHSEP_S); agent_exit (2); } return name; } /* Create a Unix domain socket with NAME. Returns the file descriptor or terminates the process in case of an error. Note that this function needs to be used for the regular socket first (indicated by PRIMARY) and only then for the extra and the ssh sockets. If the socket has been redirected the name of the real socket is stored as a malloced string at R_REDIR_NAME. If CYGWIN is set a Cygwin compatible socket is created (Windows only). */ static gnupg_fd_t create_server_socket (char *name, int primary, int cygwin, char **r_redir_name, assuan_sock_nonce_t *nonce) { struct sockaddr *addr; struct sockaddr_un *unaddr; socklen_t len; gnupg_fd_t fd; int rc; xfree (*r_redir_name); *r_redir_name = NULL; fd = assuan_sock_new (AF_UNIX, SOCK_STREAM, 0); if (fd == ASSUAN_INVALID_FD) { log_error (_("can't create socket: %s\n"), strerror (errno)); *name = 0; /* Inhibit removal of the socket by cleanup(). */ agent_exit (2); } if (cygwin) assuan_sock_set_flag (fd, "cygwin", 1); unaddr = xmalloc (sizeof *unaddr); addr = (struct sockaddr*)unaddr; { int redirected; if (assuan_sock_set_sockaddr_un (name, addr, &redirected)) { if (errno == ENAMETOOLONG) log_error (_("socket name '%s' is too long\n"), name); else log_error ("error preparing socket '%s': %s\n", name, gpg_strerror (gpg_error_from_syserror ())); *name = 0; /* Inhibit removal of the socket by cleanup(). */ agent_exit (2); } if (redirected) { *r_redir_name = xstrdup (unaddr->sun_path); if (opt.verbose) log_info ("redirecting socket '%s' to '%s'\n", name, *r_redir_name); } } len = SUN_LEN (unaddr); rc = assuan_sock_bind (fd, addr, len); /* Our error code mapping on W32CE returns EEXIST thus we also test for this. */ if (rc == -1 && (errno == EADDRINUSE #ifdef HAVE_W32_SYSTEM || errno == EEXIST #endif )) { /* Check whether a gpg-agent is already running. We do this test only if this is the primary socket. For secondary sockets we assume that a test for gpg-agent has already been done and reuse the requested socket. Testing the ssh-socket is not possible because at this point, though we know the new Assuan socket, the Assuan server and thus the ssh-agent server is not yet operational; this would lead to a hang. */ if (primary && !check_for_running_agent (1)) { log_set_prefix (NULL, GPGRT_LOG_WITH_PREFIX); log_set_file (NULL); log_error (_("a gpg-agent is already running - " "not starting a new one\n")); *name = 0; /* Inhibit removal of the socket by cleanup(). */ assuan_sock_close (fd); agent_exit (2); } gnupg_remove (unaddr->sun_path); rc = assuan_sock_bind (fd, addr, len); } if (rc != -1 && (rc=assuan_sock_get_nonce (addr, len, nonce))) log_error (_("error getting nonce for the socket\n")); if (rc == -1) { /* We use gpg_strerror here because it allows us to get strings for some W32 socket error codes. */ log_error (_("error binding socket to '%s': %s\n"), unaddr->sun_path, gpg_strerror (gpg_error_from_syserror ())); assuan_sock_close (fd); *name = 0; /* Inhibit removal of the socket by cleanup(). */ agent_exit (2); } if (gnupg_chmod (unaddr->sun_path, "-rwx")) log_error (_("can't set permissions of '%s': %s\n"), unaddr->sun_path, strerror (errno)); if (listen (FD2INT(fd), 5 ) == -1) { log_error (_("listen() failed: %s\n"), strerror (errno)); *name = 0; /* Inhibit removal of the socket by cleanup(). */ assuan_sock_close (fd); agent_exit (2); } if (opt.verbose) log_info (_("listening on socket '%s'\n"), unaddr->sun_path); return fd; } /* Check that the directory for storing the private keys exists and create it if not. This function won't fail as it is only a convenience function and not strictly necessary. */ static void create_private_keys_directory (const char *home) { char *fname; struct stat statbuf; fname = make_filename (home, GNUPG_PRIVATE_KEYS_DIR, NULL); if (stat (fname, &statbuf) && errno == ENOENT) { if (gnupg_mkdir (fname, "-rwx")) log_error (_("can't create directory '%s': %s\n"), fname, strerror (errno) ); else if (!opt.quiet) log_info (_("directory '%s' created\n"), fname); } if (gnupg_chmod (fname, "-rwx")) log_error (_("can't set permissions of '%s': %s\n"), fname, strerror (errno)); xfree (fname); } /* Create the directory only if the supplied directory name is the same as the default one. This way we avoid to create arbitrary directories when a non-default home directory is used. To cope with HOME, we compare only the suffix if we see that the default homedir does start with a tilde. We don't stop here in case of problems because other functions will throw an error anyway.*/ static void create_directories (void) { struct stat statbuf; const char *defhome = standard_homedir (); char *home; home = make_filename (gnupg_homedir (), NULL); if ( stat (home, &statbuf) ) { if (errno == ENOENT) { if ( #ifdef HAVE_W32_SYSTEM ( !compare_filenames (home, defhome) ) #else (*defhome == '~' && (strlen (home) >= strlen (defhome+1) && !strcmp (home + strlen(home) - strlen (defhome+1), defhome+1))) || (*defhome != '~' && !strcmp (home, defhome) ) #endif ) { if (gnupg_mkdir (home, "-rwx")) log_error (_("can't create directory '%s': %s\n"), home, strerror (errno) ); else { if (!opt.quiet) log_info (_("directory '%s' created\n"), home); create_private_keys_directory (home); } } } else log_error (_("stat() failed for '%s': %s\n"), home, strerror (errno)); } else if ( !S_ISDIR(statbuf.st_mode)) { log_error (_("can't use '%s' as home directory\n"), home); } else /* exists and is a directory. */ { create_private_keys_directory (home); } xfree (home); } /* This is the worker for the ticker. It is called every few seconds and may only do fast operations. */ static void handle_tick (void) { static time_t last_minute; if (!last_minute) last_minute = time (NULL); /* Check whether the scdaemon has died and cleanup in this case. */ agent_scd_check_aliveness (); /* If we are running as a child of another process, check whether the parent is still alive and shutdown if not. */ #ifndef HAVE_W32_SYSTEM if (parent_pid != (pid_t)(-1)) { if (kill (parent_pid, 0)) { shutdown_pending = 2; log_info ("parent process died - shutting down\n"); log_info ("%s %s stopped\n", strusage(11), strusage(13) ); cleanup (); agent_exit (0); } } #endif /*HAVE_W32_SYSTEM*/ /* Code to be run from time to time. */ #if CHECK_OWN_SOCKET_INTERVAL > 0 if (last_minute + CHECK_OWN_SOCKET_INTERVAL <= time (NULL)) { check_own_socket (); last_minute = time (NULL); } #endif } /* A global function which allows us to call the reload stuff from other places too. This is only used when build for W32. */ void agent_sighup_action (void) { log_info ("SIGHUP received - " "re-reading configuration and flushing cache\n"); agent_flush_cache (); reread_configuration (); agent_reload_trustlist (); /* We flush the module name cache so that after installing a "pinentry" binary that one can be used in case the "pinentry-basic" fallback was in use. */ gnupg_module_name_flush_some (); } /* A helper function to handle SIGUSR2. */ static void agent_sigusr2_action (void) { if (opt.verbose) log_info ("SIGUSR2 received - updating card event counter\n"); /* Nothing to check right now. We only increment a counter. */ bump_card_eventcounter (); } #ifndef HAVE_W32_SYSTEM /* The signal handler for this program. It is expected to be run in its own trhead and not in the context of a signal handler. */ static void handle_signal (int signo) { switch (signo) { #ifndef HAVE_W32_SYSTEM case SIGHUP: agent_sighup_action (); break; case SIGUSR1: log_info ("SIGUSR1 received - printing internal information:\n"); /* Fixme: We need to see how to integrate pth dumping into our logging system. */ /* pth_ctrl (PTH_CTRL_DUMPSTATE, log_get_stream ()); */ agent_query_dump_state (); agent_scd_dump_state (); break; case SIGUSR2: agent_sigusr2_action (); break; case SIGTERM: if (!shutdown_pending) log_info ("SIGTERM received - shutting down ...\n"); else log_info ("SIGTERM received - still %i open connections\n", active_connections); shutdown_pending++; if (shutdown_pending > 2) { log_info ("shutdown forced\n"); log_info ("%s %s stopped\n", strusage(11), strusage(13) ); cleanup (); agent_exit (0); } break; case SIGINT: log_info ("SIGINT received - immediate shutdown\n"); log_info( "%s %s stopped\n", strusage(11), strusage(13)); cleanup (); agent_exit (0); break; #endif default: log_info ("signal %d received - no action defined\n", signo); } } #endif /* Check the nonce on a new connection. This is a NOP unless we are using our Unix domain socket emulation under Windows. */ static int check_nonce (ctrl_t ctrl, assuan_sock_nonce_t *nonce) { if (assuan_sock_check_nonce (ctrl->thread_startup.fd, nonce)) { log_info (_("error reading nonce on fd %d: %s\n"), FD2INT(ctrl->thread_startup.fd), strerror (errno)); assuan_sock_close (ctrl->thread_startup.fd); xfree (ctrl); return -1; } else return 0; } #ifdef HAVE_W32_SYSTEM /* The window message processing function for Putty. Warning: This code runs as a native Windows thread. Use of our own functions needs to be bracket with pth_leave/pth_enter. */ static LRESULT CALLBACK putty_message_proc (HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { int ret = 0; int w32rc; COPYDATASTRUCT *cds; const char *mapfile; HANDLE maphd; PSID mysid = NULL; PSID mapsid = NULL; void *data = NULL; PSECURITY_DESCRIPTOR psd = NULL; ctrl_t ctrl = NULL; if (msg != WM_COPYDATA) { return DefWindowProc (hwnd, msg, wparam, lparam); } cds = (COPYDATASTRUCT*)lparam; if (cds->dwData != PUTTY_IPC_MAGIC) return 0; /* Ignore data with the wrong magic. */ mapfile = cds->lpData; if (!cds->cbData || mapfile[cds->cbData - 1]) return 0; /* Ignore empty and non-properly terminated strings. */ if (DBG_IPC) { npth_protect (); log_debug ("ssh map file '%s'", mapfile); npth_unprotect (); } maphd = OpenFileMapping (FILE_MAP_ALL_ACCESS, FALSE, mapfile); if (DBG_IPC) { npth_protect (); log_debug ("ssh map handle %p\n", maphd); npth_unprotect (); } if (!maphd || maphd == INVALID_HANDLE_VALUE) return 0; npth_protect (); mysid = w32_get_user_sid (); if (!mysid) { log_error ("error getting my sid\n"); goto leave; } w32rc = GetSecurityInfo (maphd, SE_KERNEL_OBJECT, OWNER_SECURITY_INFORMATION, &mapsid, NULL, NULL, NULL, &psd); if (w32rc) { log_error ("error getting sid of ssh map file: rc=%d", w32rc); goto leave; } if (DBG_IPC) { char *sidstr; if (!ConvertSidToStringSid (mysid, &sidstr)) sidstr = NULL; log_debug (" my sid: '%s'", sidstr? sidstr: "[error]"); LocalFree (sidstr); if (!ConvertSidToStringSid (mapsid, &sidstr)) sidstr = NULL; log_debug ("ssh map file sid: '%s'", sidstr? sidstr: "[error]"); LocalFree (sidstr); } if (!EqualSid (mysid, mapsid)) { log_error ("ssh map file has a non-matching sid\n"); goto leave; } data = MapViewOfFile (maphd, FILE_MAP_ALL_ACCESS, 0, 0, 0); if (DBG_IPC) log_debug ("ssh IPC buffer at %p\n", data); if (!data) goto leave; /* log_printhex ("request:", data, 20); */ ctrl = xtrycalloc (1, sizeof *ctrl); if (!ctrl) { log_error ("error allocating connection control data: %s\n", strerror (errno) ); goto leave; } ctrl->session_env = session_env_new (); if (!ctrl->session_env) { log_error ("error allocating session environment block: %s\n", strerror (errno) ); goto leave; } agent_init_default_ctrl (ctrl); if (!serve_mmapped_ssh_request (ctrl, data, PUTTY_IPC_MAXLEN)) ret = 1; /* Valid ssh message has been constructed. */ agent_deinit_default_ctrl (ctrl); /* log_printhex (" reply:", data, 20); */ leave: xfree (ctrl); if (data) UnmapViewOfFile (data); xfree (mapsid); if (psd) LocalFree (psd); xfree (mysid); CloseHandle (maphd); npth_unprotect (); return ret; } #endif /*HAVE_W32_SYSTEM*/ #ifdef HAVE_W32_SYSTEM /* The thread handling Putty's IPC requests. */ static void * putty_message_thread (void *arg) { WNDCLASS wndwclass = {0, putty_message_proc, 0, 0, NULL, NULL, NULL, NULL, NULL, "Pageant"}; HWND hwnd; MSG msg; (void)arg; if (opt.verbose) log_info ("putty message loop thread started\n"); /* The message loop runs as thread independent from our nPth system. This also means that we need to make sure that we switch back to our system before calling any no-windows function. */ npth_unprotect (); /* First create a window to make sure that a message queue exists for this thread. */ if (!RegisterClass (&wndwclass)) { npth_protect (); log_error ("error registering Pageant window class"); return NULL; } hwnd = CreateWindowEx (0, "Pageant", "Pageant", 0, 0, 0, 0, 0, HWND_MESSAGE, /* hWndParent */ NULL, /* hWndMenu */ NULL, /* hInstance */ NULL); /* lpParm */ if (!hwnd) { npth_protect (); log_error ("error creating Pageant window"); return NULL; } while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } /* Back to nPth. */ npth_protect (); if (opt.verbose) log_info ("putty message loop thread stopped\n"); return NULL; } #endif /*HAVE_W32_SYSTEM*/ static void * do_start_connection_thread (ctrl_t ctrl) { active_connections++; agent_init_default_ctrl (ctrl); if (opt.verbose && !DBG_IPC) log_info (_("handler 0x%lx for fd %d started\n"), (unsigned long) npth_self(), FD2INT(ctrl->thread_startup.fd)); start_command_handler (ctrl, GNUPG_INVALID_FD, ctrl->thread_startup.fd); if (opt.verbose && !DBG_IPC) log_info (_("handler 0x%lx for fd %d terminated\n"), (unsigned long) npth_self(), FD2INT(ctrl->thread_startup.fd)); agent_deinit_default_ctrl (ctrl); xfree (ctrl); active_connections--; return NULL; } /* This is the standard connection thread's main function. */ static void * start_connection_thread_std (void *arg) { ctrl_t ctrl = arg; if (check_nonce (ctrl, &socket_nonce)) { log_error ("handler 0x%lx nonce check FAILED\n", (unsigned long) npth_self()); return NULL; } return do_start_connection_thread (ctrl); } /* This is the extra socket connection thread's main function. */ static void * start_connection_thread_extra (void *arg) { ctrl_t ctrl = arg; if (check_nonce (ctrl, &socket_nonce_extra)) { log_error ("handler 0x%lx nonce check FAILED\n", (unsigned long) npth_self()); return NULL; } ctrl->restricted = 1; return do_start_connection_thread (ctrl); } /* This is the browser socket connection thread's main function. */ static void * start_connection_thread_browser (void *arg) { ctrl_t ctrl = arg; if (check_nonce (ctrl, &socket_nonce_browser)) { log_error ("handler 0x%lx nonce check FAILED\n", (unsigned long) npth_self()); return NULL; } ctrl->restricted = 2; return do_start_connection_thread (ctrl); } /* This is the ssh connection thread's main function. */ static void * start_connection_thread_ssh (void *arg) { ctrl_t ctrl = arg; if (check_nonce (ctrl, &socket_nonce_ssh)) return NULL; active_connections++; agent_init_default_ctrl (ctrl); if (opt.verbose) log_info (_("ssh handler 0x%lx for fd %d started\n"), (unsigned long) npth_self(), FD2INT(ctrl->thread_startup.fd)); start_command_handler_ssh (ctrl, ctrl->thread_startup.fd); if (opt.verbose) log_info (_("ssh handler 0x%lx for fd %d terminated\n"), (unsigned long) npth_self(), FD2INT(ctrl->thread_startup.fd)); agent_deinit_default_ctrl (ctrl); xfree (ctrl); active_connections--; return NULL; } /* Connection handler loop. Wait for connection requests and spawn a thread after accepting a connection. */ static void handle_connections (gnupg_fd_t listen_fd, gnupg_fd_t listen_fd_extra, gnupg_fd_t listen_fd_browser, gnupg_fd_t listen_fd_ssh) { gpg_error_t err; npth_attr_t tattr; struct sockaddr_un paddr; socklen_t plen; fd_set fdset, read_fdset; int ret; gnupg_fd_t fd; int nfd; int saved_errno; struct timespec abstime; struct timespec curtime; struct timespec timeout; #ifdef HAVE_W32_SYSTEM HANDLE events[2]; unsigned int events_set; #endif int my_inotify_fd = -1; struct { const char *name; void *(*func) (void *arg); gnupg_fd_t l_fd; } listentbl[] = { { "std", start_connection_thread_std }, { "extra", start_connection_thread_extra }, { "browser", start_connection_thread_browser }, { "ssh", start_connection_thread_ssh } }; ret = npth_attr_init(&tattr); if (ret) log_fatal ("error allocating thread attributes: %s\n", strerror (ret)); npth_attr_setdetachstate (&tattr, NPTH_CREATE_DETACHED); #ifndef HAVE_W32_SYSTEM npth_sigev_init (); npth_sigev_add (SIGHUP); npth_sigev_add (SIGUSR1); npth_sigev_add (SIGUSR2); npth_sigev_add (SIGINT); npth_sigev_add (SIGTERM); npth_sigev_fini (); #else # ifdef HAVE_W32CE_SYSTEM /* Use a dummy event. */ sigs = 0; ev = pth_event (PTH_EVENT_SIGS, &sigs, &signo); # else events[0] = get_agent_scd_notify_event (); events[1] = INVALID_HANDLE_VALUE; # endif #endif if (disable_check_own_socket) my_inotify_fd = -1; else if ((err = gnupg_inotify_watch_socket (&my_inotify_fd, socket_name))) { if (gpg_err_code (err) != GPG_ERR_NOT_SUPPORTED) log_info ("error enabling fast daemon termination: %s\n", gpg_strerror (err)); } /* On Windows we need to fire up a separate thread to listen for requests from Putty (an SSH client), so we can replace Putty's Pageant (its ssh-agent implementation). */ #ifdef HAVE_W32_SYSTEM if (putty_support) { npth_t thread; ret = npth_create (&thread, &tattr, putty_message_thread, NULL); if (ret) { log_error ("error spawning putty message loop: %s\n", strerror (ret)); } } #endif /*HAVE_W32_SYSTEM*/ /* Set a flag to tell call-scd.c that it may enable event notifications. */ opt.sigusr2_enabled = 1; FD_ZERO (&fdset); FD_SET (FD2INT (listen_fd), &fdset); nfd = FD2INT (listen_fd); if (listen_fd_extra != GNUPG_INVALID_FD) { FD_SET ( FD2INT(listen_fd_extra), &fdset); if (FD2INT (listen_fd_extra) > nfd) nfd = FD2INT (listen_fd_extra); } if (listen_fd_browser != GNUPG_INVALID_FD) { FD_SET ( FD2INT(listen_fd_browser), &fdset); if (FD2INT (listen_fd_browser) > nfd) nfd = FD2INT (listen_fd_browser); } if (listen_fd_ssh != GNUPG_INVALID_FD) { FD_SET ( FD2INT(listen_fd_ssh), &fdset); if (FD2INT (listen_fd_ssh) > nfd) nfd = FD2INT (listen_fd_ssh); } if (my_inotify_fd != -1) { FD_SET (my_inotify_fd, &fdset); if (my_inotify_fd > nfd) nfd = my_inotify_fd; } listentbl[0].l_fd = listen_fd; listentbl[1].l_fd = listen_fd_extra; listentbl[2].l_fd = listen_fd_browser; listentbl[3].l_fd = listen_fd_ssh; npth_clock_gettime (&abstime); abstime.tv_sec += TIMERTICK_INTERVAL; for (;;) { /* Shutdown test. */ if (shutdown_pending) { if (active_connections == 0) break; /* ready */ /* Do not accept new connections but keep on running the * loop to cope with the timer events. * * Note that we do not close the listening socket because a * client trying to connect to that socket would instead * restart a new dirmngr instance - which is unlikely the * intention of a shutdown. */ FD_ZERO (&fdset); nfd = -1; if (my_inotify_fd != -1) { FD_SET (my_inotify_fd, &fdset); nfd = my_inotify_fd; } } /* POSIX says that fd_set should be implemented as a structure, thus a simple assignment is fine to copy the entire set. */ read_fdset = fdset; npth_clock_gettime (&curtime); if (!(npth_timercmp (&curtime, &abstime, <))) { /* Timeout. */ handle_tick (); npth_clock_gettime (&abstime); abstime.tv_sec += TIMERTICK_INTERVAL; } npth_timersub (&abstime, &curtime, &timeout); #ifndef HAVE_W32_SYSTEM ret = npth_pselect (nfd+1, &read_fdset, NULL, NULL, &timeout, npth_sigev_sigmask ()); saved_errno = errno; { int signo; while (npth_sigev_get_pending (&signo)) handle_signal (signo); } #else ret = npth_eselect (nfd+1, &read_fdset, NULL, NULL, &timeout, events, &events_set); saved_errno = errno; /* This is valid even if npth_eselect returns an error. */ if (events_set & 1) agent_sigusr2_action (); #endif if (ret == -1 && saved_errno != EINTR) { log_error (_("npth_pselect failed: %s - waiting 1s\n"), strerror (saved_errno)); npth_sleep (1); continue; } if (ret <= 0) /* Interrupt or timeout. Will be handled when calculating the next timeout. */ continue; if (!shutdown_pending) { int idx; ctrl_t ctrl; npth_t thread; if (my_inotify_fd != -1 && FD_ISSET (my_inotify_fd, &read_fdset) && gnupg_inotify_has_name (my_inotify_fd, GPG_AGENT_SOCK_NAME)) { shutdown_pending = 1; log_info ("socket file has been removed - shutting down\n"); } for (idx=0; idx < DIM(listentbl); idx++) { if (listentbl[idx].l_fd == GNUPG_INVALID_FD) continue; if (!FD_ISSET (FD2INT (listentbl[idx].l_fd), &read_fdset)) continue; plen = sizeof paddr; fd = INT2FD (npth_accept (FD2INT(listentbl[idx].l_fd), (struct sockaddr *)&paddr, &plen)); if (fd == GNUPG_INVALID_FD) { log_error ("accept failed for %s: %s\n", listentbl[idx].name, strerror (errno)); } else if ( !(ctrl = xtrycalloc (1, sizeof *ctrl))) { log_error ("error allocating connection data for %s: %s\n", listentbl[idx].name, strerror (errno) ); assuan_sock_close (fd); } else if ( !(ctrl->session_env = session_env_new ())) { log_error ("error allocating session env block for %s: %s\n", listentbl[idx].name, strerror (errno) ); xfree (ctrl); assuan_sock_close (fd); } else { ctrl->thread_startup.fd = fd; ret = npth_create (&thread, &tattr, listentbl[idx].func, ctrl); if (ret) { log_error ("error spawning connection handler for %s:" " %s\n", listentbl[idx].name, strerror (ret)); assuan_sock_close (fd); xfree (ctrl); } } - fd = GNUPG_INVALID_FD; } } } if (my_inotify_fd != -1) close (my_inotify_fd); cleanup (); log_info (_("%s %s stopped\n"), strusage(11), strusage(13)); npth_attr_destroy (&tattr); } /* Helper for check_own_socket. */ static gpg_error_t check_own_socket_pid_cb (void *opaque, const void *buffer, size_t length) { membuf_t *mb = opaque; put_membuf (mb, buffer, length); return 0; } /* The thread running the actual check. We need to run this in a separate thread so that check_own_thread can be called from the timer tick. */ static void * check_own_socket_thread (void *arg) { int rc; char *sockname = arg; assuan_context_t ctx = NULL; membuf_t mb; char *buffer; check_own_socket_running++; rc = assuan_new (&ctx); if (rc) { log_error ("can't allocate assuan context: %s\n", gpg_strerror (rc)); goto leave; } assuan_set_flag (ctx, ASSUAN_NO_LOGGING, 1); rc = assuan_socket_connect (ctx, sockname, (pid_t)(-1), 0); if (rc) { log_error ("can't connect my own socket: %s\n", gpg_strerror (rc)); goto leave; } init_membuf (&mb, 100); rc = assuan_transact (ctx, "GETINFO pid", check_own_socket_pid_cb, &mb, NULL, NULL, NULL, NULL); put_membuf (&mb, "", 1); buffer = get_membuf (&mb, NULL); if (rc || !buffer) { log_error ("sending command \"%s\" to my own socket failed: %s\n", "GETINFO pid", gpg_strerror (rc)); rc = 1; } else if ( (pid_t)strtoul (buffer, NULL, 10) != getpid ()) { log_error ("socket is now serviced by another server\n"); rc = 1; } else if (opt.verbose > 1) log_error ("socket is still served by this server\n"); xfree (buffer); leave: xfree (sockname); if (ctx) assuan_release (ctx); if (rc) { /* We may not remove the socket as it is now in use by another server. */ inhibit_socket_removal = 1; shutdown_pending = 2; log_info ("this process is useless - shutting down\n"); } check_own_socket_running--; return NULL; } /* Check whether we are still listening on our own socket. In case another gpg-agent process started after us has taken ownership of our socket, we would linger around without any real task. Thus we better check once in a while whether we are really needed. */ static void check_own_socket (void) { char *sockname; npth_t thread; npth_attr_t tattr; int err; if (disable_check_own_socket) return; if (check_own_socket_running || shutdown_pending) return; /* Still running or already shutting down. */ sockname = make_filename_try (gnupg_socketdir (), GPG_AGENT_SOCK_NAME, NULL); if (!sockname) return; /* Out of memory. */ err = npth_attr_init (&tattr); if (err) return; npth_attr_setdetachstate (&tattr, NPTH_CREATE_DETACHED); err = npth_create (&thread, &tattr, check_own_socket_thread, sockname); if (err) log_error ("error spawning check_own_socket_thread: %s\n", strerror (err)); npth_attr_destroy (&tattr); } /* Figure out whether an agent is available and running. Prints an error if not. If SILENT is true, no messages are printed. Returns 0 if the agent is running. */ static int check_for_running_agent (int silent) { gpg_error_t err; char *sockname; assuan_context_t ctx = NULL; sockname = make_filename_try (gnupg_socketdir (), GPG_AGENT_SOCK_NAME, NULL); if (!sockname) return gpg_error_from_syserror (); err = assuan_new (&ctx); if (!err) err = assuan_socket_connect (ctx, sockname, (pid_t)(-1), 0); xfree (sockname); if (err) { if (!silent) log_error (_("no gpg-agent running in this session\n")); if (ctx) assuan_release (ctx); return -1; } if (!opt.quiet && !silent) log_info ("gpg-agent running and available\n"); assuan_release (ctx); return 0; }