diff --git a/src/agent.c b/src/agent.c index b1035e4..ab64b70 100644 --- a/src/agent.c +++ b/src/agent.c @@ -1,1435 +1,1458 @@ /* agent.c - Talking to gpg-agent. * Copyright (C) 2006, 2007, 2008, 2015, 2019 g10 Code GmbH * * This file is part of Scute. * * Scute is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * Scute 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this program; if not, see . * SPDX-License-Identifier: LGPL-2.1-or-later */ #if HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #ifdef HAVE_W32_SYSTEM # define PATHSEP_C ';' # define WINVER 0x0500 /* Required for AllowSetForegroundWindow. */ # include # include #else # define PATHSEP_C ':' #endif #include #include #include "debug.h" #include "support.h" #include "sexp-parse.h" #include "cert.h" #include "agent.h" /* The global agent context. */ static assuan_context_t agent_ctx; -/* The version number of the agent. */ -static int agent_version_major; -static int agent_version_minor; - /* Hack required for Windows. */ void gnupg_allow_set_foregound_window (pid_t pid) { if (!pid || pid == (pid_t)(-1)) return; #ifdef HAVE_W32_SYSTEM else if (!AllowSetForegroundWindow (pid)) DEBUG (DBG_CRIT, "AllowSetForegroundWindow(%lu) failed: %u\n", (unsigned long)pid, (unsigned int)GetLastError ()); #endif } /* Establish a connection to a running GPG agent. */ static gpg_error_t agent_connect (assuan_context_t *ctx_r) { gpg_error_t err = 0; assuan_context_t ctx = NULL; char buffer[512]; - FILE *fp; - /* Use gpgconf to obtain the socket name. */ - snprintf (buffer, sizeof buffer, "%s --null --list-dirs agent-socket", + /* Use gpgconf to make sure that gpg-agent is started and to obtain + * the socket name. For older version of gnupg we will fallback to + * using two gpgconf commands with the same effect. */ + snprintf (buffer, sizeof buffer, "%s --show-socket --launch gpg-agent", get_gpgconf_path ()); -#ifdef HAVE_W32_SYSTEM - fp = _popen (buffer, "r"); -#else - fp = popen (buffer, "r"); -#endif - if (fp) + err = read_first_line (buffer, buffer, sizeof buffer); + if (gpg_err_code (err) == GPG_ERR_NO_AGENT && is_gnupg_older_than (2, 2, 14)) { - int i, c; - - for (i=0; i < sizeof buffer - 1 && (c = getc (fp)) != EOF; i++) - buffer[i] = c; - if (c == EOF && ferror (fp)) /* I/O error? */ - err = gpg_error_from_syserror (); - else if (!(i < sizeof buffer - 1)) - err = gpg_error (GPG_ERR_NO_AGENT); /* Path too long. */ - else if (!i || buffer[i-1]) - err = gpg_error (GPG_ERR_NO_AGENT); /* No terminating nul. */ - - pclose (fp); + snprintf (buffer, sizeof buffer, "%s --launch gpg-agent", + get_gpgconf_path ()); + err = read_first_line (buffer, NULL, 0); + if (!err) + { + snprintf (buffer, sizeof buffer, "%s --list-dirs agent-socket", + get_gpgconf_path ()); + err = read_first_line (buffer, buffer, sizeof buffer); + } } - else - err = gpg_error_from_syserror (); /* Then connect to the socket we got. */ if (!err) { err = assuan_new (&ctx); if (!err) { err = assuan_socket_connect (ctx, buffer, 0, 0); if (!err) { *ctx_r = ctx; if (_scute_debug_flags & DBG_ASSUAN) assuan_set_log_stream (*ctx_r, _scute_debug_stream); } else assuan_release (ctx); } } /* We do not try any harder. If gpg-connect-agent somehow failed * to give us a suitable socket, we probably cannot do better. */ if (err) DEBUG (DBG_CRIT, "cannot connect to GPG agent: %s", gpg_strerror (err)); return err; } /* This is the default inquiry callback. It mainly handles the Pinentry notifications. */ static gpg_error_t default_inq_cb (void *opaque, const char *line) { (void)opaque; if (!strncmp (line, "PINENTRY_LAUNCHED", 17) && (line[17]==' '||!line[17])) { gnupg_allow_set_foregound_window ((pid_t)strtoul (line+17, NULL, 10)); /* We do not pass errors to avoid breaking other code. */ } else DEBUG (DBG_CRIT, "ignoring gpg-agent inquiry `%s'\n", line); return 0; } /* Send a simple command to the agent. */ static gpg_error_t agent_simple_cmd (assuan_context_t ctx, const char *fmt, ...) { gpg_error_t err; char *optstr; va_list arg; int res; va_start (arg, fmt); res = vasprintf (&optstr, fmt, arg); va_end (arg); if (res < 0) return gpg_error_from_errno (errno); err = assuan_transact (ctx, optstr, NULL, NULL, default_inq_cb, NULL, NULL, NULL); if (err) DEBUG (DBG_CRIT, "gpg-agent command '%s' failed: %s", optstr, gpg_strerror (err)); free (optstr); return err; } -/* Read and stroe the agent's version number. */ -static gpg_error_t -read_version_cb (void *opaque, const void *buffer, size_t length) -{ - char version[20]; - const char *s; - - (void) opaque; - - if (length > sizeof (version) -1) - length = sizeof (version) - 1; - strncpy (version, buffer, length); - version[length] = 0; - - agent_version_major = atoi (version); - s = strchr (version, '.'); - agent_version_minor = s? atoi (s+1) : 0; - - return 0; -} - - /* Configure the GPG agent at connection CTX. */ static gpg_error_t agent_configure (assuan_context_t ctx) { gpg_error_t err = 0; char *dft_display = NULL; char *dft_ttyname = NULL; char *dft_ttytype = NULL; #if defined(HAVE_SETLOCALE) && (defined(LC_CTYPE) || defined(LC_MESSAGES)) char *old_lc = NULL; char *dft_lc = NULL; #endif char *dft_xauthority = NULL; char *dft_pinentry_user_data = NULL; err = agent_simple_cmd (ctx, "RESET"); if (err) return err; /* Set up display, terminal and locale options. */ dft_display = getenv ("DISPLAY"); if (dft_display) err = agent_simple_cmd (ctx, "OPTION display=%s", dft_display); if (err) return err; dft_ttyname = getenv ("GPG_TTY"); if ((!dft_ttyname || !*dft_ttyname) && ttyname (0)) dft_ttyname = ttyname (0); if (dft_ttyname) { err = agent_simple_cmd (ctx, "OPTION ttyname=%s", dft_ttyname); if (err) return err; } dft_ttytype = getenv ("TERM"); if (dft_ttytype) err = agent_simple_cmd (ctx, "OPTION ttytype=%s", dft_ttytype); if (err) return err; #if defined(HAVE_SETLOCALE) && defined(LC_CTYPE) old_lc = setlocale (LC_CTYPE, NULL); if (old_lc) { old_lc = strdup (old_lc); if (!old_lc) return gpg_error_from_errno (errno); } dft_lc = setlocale (LC_CTYPE, ""); if (dft_lc) err = agent_simple_cmd ("OPTION lc-ctype=%s", dft_lc); if (old_lc) { setlocale (LC_CTYPE, old_lc); free (old_lc); } #endif if (err) return err; #if defined(HAVE_SETLOCALE) && defined(LC_MESSAGES) old_lc = setlocale (LC_MESSAGES, NULL); if (old_lc) { old_lc = strdup (old_lc); if (!old_lc) err = gpg_error_from_errno (errno); } dft_lc = setlocale (LC_MESSAGES, ""); if (dft_lc) err = agent_simple_cmd ("OPTION lc-messages=%s", dft_lc); if (old_lc) { setlocale (LC_MESSAGES, old_lc); free (old_lc); } #endif dft_xauthority = getenv ("XAUTHORITY"); if (dft_xauthority) err = agent_simple_cmd (ctx, "OPTION xauthority=%s", dft_xauthority); if (gpg_err_code (err) == GPG_ERR_UNKNOWN_OPTION) err = 0; else if (err) return err; dft_pinentry_user_data = getenv ("PINENTRY_USER_DATA"); if (dft_pinentry_user_data) err = agent_simple_cmd (ctx, "OPTION pinentry_user_data=%s", dft_pinentry_user_data); if (err && gpg_err_code (err) != GPG_ERR_UNKNOWN_OPTION) return err; err = agent_simple_cmd (ctx, "OPTION allow-pinentry-notify"); if (err && gpg_err_code (err) != GPG_ERR_UNKNOWN_OPTION) return err; - err = assuan_transact (ctx, "GETINFO version", - read_version_cb, NULL, - NULL, NULL, NULL, NULL); - if (gpg_err_code (err) == GPG_ERR_UNKNOWN_OPTION) - err = 0; - else if (err) - return err; + return err; +} +/* Check for a broken pipe, that is a lost connection to the agent. + * Update the gloabls so that a re-connect is done the next time. + * Returns ERR or the modified code GPG_ERR_NO_AGENT. */ +static gpg_error_t +check_broken_pipe (gpg_error_t err) +{ + /* Note that Scute _currently_ uses GPG_ERR_SOURCE_ANY. */ + if (gpg_err_code (err) == GPG_ERR_EPIPE + && gpg_err_source (err) == GPG_ERR_SOURCE_ANY) + { + DEBUG (DBG_INFO, "Broken connection to the gpg-agent"); + scute_agent_finalize (); + err = gpg_error (GPG_ERR_NO_AGENT); + } return err; } +/* If the connection to the agent was lost earlier and detected by + * check_broken_pipe we try to reconnect. */ +static gpg_error_t +ensure_agent_connection (void) +{ + gpg_error_t err; + + if (agent_ctx) + return 0; /* Connection still known. */ + + DEBUG (DBG_INFO, "Re-connecting to gpg-agent"); + err = agent_connect (&agent_ctx); + if (err) + return err; + + err = agent_configure (agent_ctx); + return check_broken_pipe (err); +} + + /* Try to connect to the agent via socket. Handle the server's - initial greeting. */ + initial greeting. This is used only once when SCute is loaded. + Re-connection is done using ensure_agent_connection. */ gpg_error_t scute_agent_initialize (void) { gpg_error_t err = 0; if (agent_ctx) { DEBUG (DBG_CRIT, "GPG Agent connection already established"); return 0; } DEBUG (DBG_INFO, "Establishing connection to gpg-agent"); err = agent_connect (&agent_ctx); if (err) return err; err = agent_configure (agent_ctx); if (err) scute_agent_finalize (); return err; } -int -scute_agent_get_agent_version (int *minor) -{ - *minor = agent_version_minor; - return agent_version_major; -} - - /* 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. */ static char * unescape_status_string (const unsigned char *src) { char *buffer; char *dst; buffer = malloc (strlen (src) + 1); if (!buffer) return NULL; dst = buffer; while (*src) { if (*src == '%' && src[1] && src[2]) { src++; *dst = xtoi_2 (src); if (*dst == '\0') *dst = '\xff'; dst++; src += 2; } else if (*src == '+') { *(dst++) = ' '; src++; } else *(dst++) = *(src++); } *dst = 0; return buffer; } /* Take a 20 byte hexencoded string and put it into the provided 20 byte buffer FPR in binary format. Returns true if successful, and false otherwise. */ static int unhexify_fpr (const char *hexstr, unsigned char *fpr) { const char *src; int cnt; /* Check for invalid or wrong length. */ for (src = hexstr, cnt = 0; hexdigitp (src); src++, cnt++) ; if ((*src && !spacep (src)) || (cnt != 40)) return 0; for (src = hexstr, cnt = 0; *src && !spacep (src); src += 2, cnt++) fpr[cnt] = xtoi_2 (src); return 1; } /* Return true if HEXSTR is a valid keygrip. */ static unsigned int hexgrip_valid_p (const char *hexstr) { const char *s; int n; for (s=hexstr, n=0; hexdigitp (s); s++, n++) ; if ((*s && *s != ' ') || n != 40) return 0; /* Bad keygrip */ else return 1; /* Valid. */ } /* Take the serial number from LINE and return it verbatim in a newly allocated string. We make sure that only hex characters are returned. */ static char * store_serialno (const char *line) { const char *src; char *ptr; for (src = line; hexdigitp (src); src++) ; ptr = malloc (src + 1 - line); if (ptr) { memcpy (ptr, line, src - line); ptr[src - line] = 0; } return ptr; } /* Release the card info structure INFO. */ void scute_agent_release_card_info (struct agent_card_info_s *info) { if (!info) return; free (info->serialno); free (info->dispserialno); free (info->cardtype); free (info->disp_name); free (info->disp_lang); free (info->pubkey_url); free (info->login_data); while (info->kinfo) { key_info_t ki = info->kinfo->next; free (info->kinfo); info->kinfo = ki; } memset (info, 0, sizeof (*info)); } /* Return the key info object for the key KEYREF. If it is not found * NULL is returned. */ key_info_t scute_find_kinfo (agent_card_info_t info, const char *keyref) { key_info_t kinfo; for (kinfo = info->kinfo; kinfo; kinfo = kinfo->next) if (!strcmp (kinfo->keyref, keyref)) return kinfo; return NULL; } /* Create a new key info object with KEYREF. All fields but the * keyref are zeroed out. The created object is appended to the list * at INFO. */ static key_info_t create_kinfo (agent_card_info_t info, const char *keyref) { key_info_t kinfo, ki; kinfo = calloc (1, sizeof *kinfo + strlen (keyref)); if (!kinfo) return NULL; strcpy (kinfo->keyref, keyref); if (!info->kinfo) info->kinfo = kinfo; else { for (ki=info->kinfo; ki->next; ki = ki->next) ; ki->next = kinfo; } return kinfo; } /* FIXME: We are not returning out of memory errors. */ static gpg_error_t learn_status_cb (void *opaque, const char *line) { agent_card_info_t parm = opaque; const char *keyword = line; int keywordlen; key_info_t kinfo; const char *keyref; int i; for (keywordlen = 0; *line && !spacep (line); line++, keywordlen++) ; while (spacep (line)) line++; if (keywordlen == 8 && !memcmp (keyword, "SERIALNO", keywordlen)) { free (parm->serialno); parm->serialno = store_serialno (line); } else if (keywordlen == 13 && !memcmp (keyword, "$DISPSERIALNO", keywordlen)) { free (parm->dispserialno); parm->dispserialno = unescape_status_string (line); } else if (keywordlen == 7 && !memcmp (keyword, "APPTYPE", keywordlen)) { parm->is_piv = !strcmp (line, "PIV"); } else if (keywordlen == 8 && !memcmp (keyword, "CARDTYPE", keywordlen)) { free (parm->cardtype); parm->cardtype = unescape_status_string (line); } else if (keywordlen == 9 && !memcmp (keyword, "DISP-NAME", keywordlen)) { if (parm->disp_name) free (parm->disp_name); parm->disp_name = unescape_status_string (line); } else if (keywordlen == 9 && !memcmp (keyword, "DISP-LANG", keywordlen)) { if (parm->disp_lang) free (parm->disp_lang); parm->disp_lang = unescape_status_string (line); } else if (keywordlen == 8 && !memcmp (keyword, "DISP-SEX", keywordlen)) { parm->disp_sex = *line == '1'? 1 : *line == '2' ? 2: 0; } else if (keywordlen == 10 && !memcmp (keyword, "PUBKEY-URL", keywordlen)) { if (parm->pubkey_url) free (parm->pubkey_url); parm->pubkey_url = unescape_status_string (line); } else if (keywordlen == 10 && !memcmp (keyword, "LOGIN-DATA", keywordlen)) { if (parm->login_data) free (parm->login_data); parm->login_data = unescape_status_string (line); } else if (keywordlen == 11 && !memcmp (keyword, "SIG-COUNTER", keywordlen)) { parm->sig_counter = strtoul (line, NULL, 0); } else if (keywordlen == 10 && !memcmp (keyword, "CHV-STATUS", keywordlen)) { char *p, *buf; buf = p = unescape_status_string (line); if (buf) { while (spacep (p)) p++; parm->chv1_cached = atoi (p); while (*p && !spacep (p)) p++; while (spacep (p)) p++; for (i = 0; *p && i < 3; i++) { parm->chvmaxlen[i] = atoi (p); while (*p && !spacep (p)) p++; while (spacep (p)) p++; } for (i=0; *p && i < 3; i++) { parm->chvretry[i] = atoi (p); while (*p && !spacep (p)) p++; while (spacep (p)) p++; } free (buf); } } else if (keywordlen == 7 && !memcmp (keyword, "KEY-FPR", keywordlen)) { int no = atoi (line); while (*line && !spacep (line)) line++; while (spacep (line)) line++; if (no == 1) parm->fpr1valid = unhexify_fpr (line, parm->fpr1); else if (no == 2) parm->fpr2valid = unhexify_fpr (line, parm->fpr2); else if (no == 3) parm->fpr3valid = unhexify_fpr (line, parm->fpr3); } else if (keywordlen == 6 && !memcmp (keyword, "CA-FPR", keywordlen)) { int no = atoi (line); while (*line && !spacep (line)) line++; while (spacep (line)) line++; if (no == 1) parm->cafpr1valid = unhexify_fpr (line, parm->cafpr1); else if (no == 2) parm->cafpr2valid = unhexify_fpr (line, parm->cafpr2); else if (no == 3) parm->cafpr3valid = unhexify_fpr (line, parm->cafpr3); } else if (keywordlen == 11 && !memcmp (keyword, "KEYPAIRINFO", keywordlen)) { /* The format of such a line is: * KEYPAIRINFO [] */ const char *hexgrip = line; char *line_buffer, *p; const char *usage; while (*line && !spacep (line)) line++; while (spacep (line)) line++; p = line_buffer = strdup (line); if (!line_buffer) goto no_core; keyref = line_buffer; while (*p && !spacep (p)) p++; if (*p) { *p++ = 0; while (spacep (p)) p++; usage = p; while (*p && !spacep (p)) p++; *p = 0; } else usage = ""; if (hexgrip_valid_p (hexgrip)) { /* Check whether we already have an item for the keyref. */ kinfo = scute_find_kinfo (parm, keyref); if (!kinfo) /* New entry. */ { kinfo = create_kinfo (parm, keyref); if (!kinfo) goto no_core; } else /* Existing entry - clear grip and usage. */ { *kinfo->grip = 0; memset (&kinfo->usage, 0, sizeof kinfo->usage); } strncpy (kinfo->grip, hexgrip, sizeof kinfo->grip); kinfo->grip[sizeof kinfo->grip -1] = 0; for (; *usage; usage++) { switch (*usage) { case 's': kinfo->usage.sign = 1; break; case 'c': kinfo->usage.cert = 1; break; case 'a': kinfo->usage.auth = 1; break; case 'e': kinfo->usage.encr = 1; break; } } } free (line_buffer); } else if (keywordlen == 6 && !memcmp (keyword, "EXTCAP", keywordlen)) { char *p, *p2, *buf; int abool; buf = p = unescape_status_string (line); if (buf) { for (p = strtok (buf, " "); p; p = strtok (NULL, " ")) { p2 = strchr (p, '='); if (p2) { *p2++ = 0; abool = (*p2 == '1'); if (!strcmp (p, "gc")) parm->rng_available = abool; /* We're currently not interested in the * other capabilities. */ } } free (buf); } } return 0; no_core: return gpg_error_from_syserror (); } /* Call the agent to learn about a smartcard. */ gpg_error_t scute_agent_learn (struct agent_card_info_s *info) { gpg_error_t err; memset (info, 0, sizeof (*info)); - err = assuan_transact (agent_ctx, "SCD LEARN --force", - NULL, NULL, - default_inq_cb, NULL, - learn_status_cb, info); + err = ensure_agent_connection (); + if (!err) + err = assuan_transact (agent_ctx, "SCD LEARN --force", + NULL, NULL, + default_inq_cb, NULL, + learn_status_cb, info); if (gpg_err_source(err) == GPG_ERR_SOURCE_SCD && gpg_err_code (err) == GPG_ERR_CARD_REMOVED) { /* SCD session is in card removed state. clear that state. */ err = assuan_transact (agent_ctx, "SCD SERIALNO", NULL, NULL, NULL, NULL, NULL, NULL); if (!err) { memset (info, 0, sizeof (*info)); err = assuan_transact (agent_ctx, "SCD LEARN --force", NULL, NULL, default_inq_cb, NULL, learn_status_cb, info); } } if (!err) { - /* Also try to get the human readabale serial number. */ + /* Also try to get the human readable serial number. */ err = assuan_transact (agent_ctx, "SCD GETATTR $DISPSERIALNO", NULL, NULL, default_inq_cb, NULL, learn_status_cb, info); if (gpg_err_code (err) == GPG_ERR_INV_NAME || gpg_err_code (err) == GPG_ERR_UNSUPPORTED_OPERATION) err = 0; /* Not implemented or GETATTR not supported. */ } - - return err; + return check_broken_pipe (err); } static gpg_error_t geteventcounter_status_cb (void *opaque, const char *line) { int *result = opaque; const char *keyword = line; int keywordlen; for (keywordlen=0; *line && !spacep (line); line++, keywordlen++) ; while (spacep (line)) line++; if (keywordlen == 12 && !memcmp (keyword, "EVENTCOUNTER", keywordlen)) { static int any_count; static unsigned int last_count; unsigned int count; if (sscanf (line, "%*u %*u %u ", &count) == 1) { if (any_count && last_count != count) *result = 1; any_count = 1; last_count = count; } } return 0; } static gpg_error_t read_status_cb (void *opaque, const void *buffer, size_t length) { char *flag = opaque; if (length == 0) *flag = 'r'; else *flag = *((char *) buffer); return 0; } /* Check the agent status. This returns 0 if a token is present, GPG_ERR_CARD_REMOVED if no token is present, and an error code otherwise. */ gpg_error_t scute_agent_check_status (void) { static char last_flag; gpg_error_t err; int any = 0; char flag = '-'; + err = ensure_agent_connection (); + if (err) + return err; + /* First we look at the eventcounter to see if anything happened at all. This is a low overhead function which won't even clutter a gpg-agent log file. There is no need for error checking here. */ if (last_flag) - assuan_transact (agent_ctx, "GETEVENTCOUNTER", - NULL, NULL, - NULL, NULL, - geteventcounter_status_cb, &any); + { + err = assuan_transact (agent_ctx, "GETEVENTCOUNTER", + NULL, NULL, + NULL, NULL, + geteventcounter_status_cb, &any); + check_broken_pipe (err); + } if (any || !last_flag) { err = assuan_transact (agent_ctx, "SCD GETINFO status", read_status_cb, &flag, default_inq_cb, NULL, NULL, NULL); + err = check_broken_pipe (err); if (err) return err; last_flag = flag; } else flag = last_flag; if (flag == 'r') return gpg_error (GPG_ERR_CARD_REMOVED); return 0; } /* We only support RSA signatures up to 4096 bits. */ #define MAX_SIGNATURE_BITS 4096 /* Enough space to hold a 4096 bit RSA signature in an S-expression. */ #define MAX_SIGNATURE_LEN 640 /* FIXME: magic value */ struct signature { unsigned char data[MAX_SIGNATURE_LEN]; int len; }; static gpg_error_t pksign_cb (void *opaque, const void *buffer, size_t length) { struct signature *sig = opaque; if (sig->len + length > MAX_SIGNATURE_LEN) { DEBUG (DBG_INFO, "maximum signature length exceeded"); return gpg_error (GPG_ERR_BAD_DATA); } memcpy (&sig->data[sig->len], buffer, length); sig->len += length; return 0; } /* Parse the result of an pksign operation which is a s-expression in canonical form that looks like (7:sig-val(3:rsa(1:s:))). The raw result is stored in RESULT of size *LEN, and *LEN is adjusted to the actual size. */ static gpg_error_t pksign_parse_result (const struct signature *sig, unsigned char *result, unsigned int *len) { gpg_error_t err; const unsigned char *s = sig->data; size_t n; int depth; if (*s++ != '(') gpg_error (GPG_ERR_INV_SEXP); n = snext (&s); if (! n) return gpg_error (GPG_ERR_INV_SEXP); if (! smatch (&s, n, "sig-val")) return gpg_error (GPG_ERR_UNKNOWN_SEXP); if (*s++ != '(') gpg_error (GPG_ERR_UNKNOWN_SEXP); n = snext (&s); if (! n) return gpg_error (GPG_ERR_INV_SEXP); if (! smatch (&s, n, "rsa")) return gpg_error (GPG_ERR_UNKNOWN_SEXP); if (*s++ != '(') gpg_error (GPG_ERR_UNKNOWN_SEXP); n = snext (&s); if (! n) return gpg_error (GPG_ERR_INV_SEXP); if (! smatch (&s, n, "s")) return gpg_error (GPG_ERR_UNKNOWN_SEXP); n = snext (&s); if (! n) return gpg_error (GPG_ERR_INV_SEXP); /* Remove a possible prepended zero byte. */ if (!*s && n > 1) { n -= 1; s += 1; } if (*len < (unsigned int) n) return gpg_error (GPG_ERR_INV_LENGTH); *len = (unsigned int) n; memcpy (result, s, n); s += n; depth = 3; err = sskip (&s, &depth); if (err) return err; if (s - sig->data != sig->len || depth != 0) return gpg_error (GPG_ERR_INV_SEXP); return 0; } /* Decodes the hash DATA of size LEN (if necessary). Returns a pointer to the raw hash data in R_DATA, the size in R_LEN, and the name of the hash function in R_HASH. Prior to TLSv1.2, the hash function was the concatenation of MD5 and SHA1 applied to the data respectively, and no encoding was applied. From TLSv1.2 on, the hash value is prefixed with an hash identifier and encoded using ASN1. FIXME: Reference. */ static gpg_error_t decode_hash (const unsigned char *data, int len, const unsigned char **r_data, size_t *r_len, const char **r_hash) { static unsigned char rmd160_prefix[15] = /* Object ID is 1.3.36.3.2.1 */ { 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x24, 0x03, 0x02, 0x01, 0x05, 0x00, 0x04, 0x14 }; static unsigned char sha1_prefix[15] = /* (1.3.14.3.2.26) */ { 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1a, 0x05, 0x00, 0x04, 0x14 }; static unsigned char sha224_prefix[19] = /* (2.16.840.1.101.3.4.2.4) */ { 0x30, 0x2D, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x04, 0x05, 0x00, 0x04, 0x1C }; static unsigned char sha256_prefix[19] = /* (2.16.840.1.101.3.4.2.1) */ { 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0x04, 0x20 }; static unsigned char sha384_prefix[19] = /* (2.16.840.1.101.3.4.2.2) */ { 0x30, 0x41, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02, 0x05, 0x00, 0x04, 0x30 }; static unsigned char sha512_prefix[19] = /* (2.16.840.1.101.3.4.2.3) */ { 0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05, 0x00, 0x04, 0x40 }; #define HANDLE(hash,hashlen) \ if (len == sizeof hash ## _prefix + (hashlen) \ && !memcmp (data, hash ## _prefix, sizeof hash ## _prefix)) \ { \ *r_data = data + sizeof hash ## _prefix; \ *r_len = hashlen; \ *r_hash = #hash; \ } if (len == 36) { /* Prior to TLSv1.2, a combination of MD5 and SHA1 was used. */ *r_data = data; *r_len = 36; *r_hash = "tls-md5sha1"; } /* TLSv1.2 encodes the hash value using ASN1. */ else HANDLE (sha1, 20) else HANDLE (rmd160, 20) else HANDLE (sha224, 28) else HANDLE (sha256, 32) else HANDLE (sha384, 48) else HANDLE (sha512, 64) else return gpg_error (GPG_ERR_INV_ARG); #undef HANDLE return 0; } /* Call the agent to sign (DATA,LEN) using the key described by * HEXGRIP. Stores the signature in SIG_RESULT and its length at * SIG_LEN; SIGLEN must initially point to the allocated size of * SIG_RESULT. */ gpg_error_t scute_agent_sign (const char *hexgrip, unsigned char *data, int len, unsigned char *sig_result, unsigned int *sig_len) { char cmd[150]; gpg_error_t err; const char *hash; const unsigned char *raw_data; size_t raw_len; #define MAX_DATA_LEN 64 /* Size of an SHA512 sum. */ unsigned char pretty_data[2 * MAX_DATA_LEN + 1]; int i; struct signature sig; sig.len = 0; if (sig_len == NULL) return gpg_error (GPG_ERR_INV_ARG); err = decode_hash (data, len, &raw_data, &raw_len, &hash); if (err) return err; if (sig_result == NULL) { *sig_len = raw_len; return 0; } if (!hexgrip || !sig_result) return gpg_error (GPG_ERR_INV_ARG); snprintf (cmd, sizeof (cmd), "SIGKEY %s", hexgrip); + err = ensure_agent_connection (); + if (err) + return err; err = assuan_transact (agent_ctx, cmd, NULL, NULL, default_inq_cb, - NULL, NULL, NULL); + NULL, NULL, NULL); + err = check_broken_pipe (err); if (err) return err; for (i = 0; i < raw_len; i++) snprintf (&pretty_data[2 * i], 3, "%02X", raw_data[i]); pretty_data[2 * raw_len] = '\0'; snprintf (cmd, sizeof (cmd), "SETHASH --hash=%s %s", hash, pretty_data); err = assuan_transact (agent_ctx, cmd, NULL, NULL, default_inq_cb, NULL, NULL, NULL); + err = check_broken_pipe (err); if (err) return err; err = assuan_transact (agent_ctx, "PKSIGN", pksign_cb, &sig, default_inq_cb, NULL, NULL, NULL); + err = check_broken_pipe (err); if (err) return err; err = pksign_parse_result (&sig, sig_result, sig_len); return err; } struct pkdecrypt_parm_s { unsigned int len; unsigned char data[512]; assuan_context_t ctx; const unsigned char *ciphertext; size_t ciphertextlen; }; static gpg_error_t pkdecrypt_data_cb (void *opaque, const void *buffer, size_t length) { struct pkdecrypt_parm_s *parm = opaque; if (parm->len + length > sizeof parm->data) { DEBUG (DBG_INFO, "maximum decryption result length exceeded"); return gpg_error (GPG_ERR_BAD_DATA); } memcpy (parm->data + parm->len, buffer, length); parm->len += length; return 0; } /* Handle the inquiries from pkdecrypt. Note, we only send the data, * assuan_transact takes care of flushing and writing the "END". */ static gpg_error_t pkdecrypt_inq_cb (void *opaque, const char *line) { struct pkdecrypt_parm_s *parm = opaque; gpg_error_t err; const char *keyword = line; int keywordlen; for (keywordlen = 0; *line && !spacep (line); line++, keywordlen++) ; while (spacep (line)) line++; if (keywordlen == 10 && !memcmp (keyword, "CIPHERTEXT", 10)) err = assuan_send_data (parm->ctx, parm->ciphertext, parm->ciphertextlen); else err = default_inq_cb (NULL, line); return err; } /* Parse the result of a pkdecrypt operation which is an s-expression * in canonical form that looks like * (5:value:). * * The raw result is stored in RESULT which has a size of *R_LEN, and * *R_LEN is adjusted to the actual size. */ static gpg_error_t pkdecrypt_parse_result (struct pkdecrypt_parm_s *ctx, unsigned char *result, unsigned int *r_len) { char *buf = ctx->data; size_t len = ctx->len; char *endp, *raw; size_t n, rawlen; if (len < 13 || memcmp (buf, "(5:value", 8) ) return gpg_error (GPG_ERR_INV_SEXP); len -= 8; buf += 8; n = strtoul (buf, &endp, 10); if (!n || *endp != ':') return gpg_error (GPG_ERR_INV_SEXP); endp++; if ((endp-buf)+n > len) return gpg_error (GPG_ERR_INV_SEXP); /* Oops: Inconsistent S-Exp. */ /* Let (RAW,RAWLEN) describe the pkcs#1 block and remove that padding. */ raw = endp; rawlen = n; if (rawlen < 10) /* 0x00 + 0x02 + <1_random> + 0x00 + <16-session> */ return gpg_error (GPG_ERR_INV_SESSION_KEY); if (raw[0] || raw[1] != 2 ) /* Wrong block type version. */ return gpg_error (GPG_ERR_INV_SESSION_KEY); for (n=2; n < rawlen && raw[n]; n++) /* Skip the random bytes. */ ; if (n+1 >= rawlen || raw[n] ) return gpg_error (GPG_ERR_INV_SESSION_KEY); n++; /* Skip the zero byte */ if (*r_len < (rawlen - n)) return gpg_error (GPG_ERR_TOO_LARGE); memcpy (result, raw + n, rawlen - n); *r_len = rawlen - n; return 0; } /* Call the agent to decrypt (ENCDATA,ENCDATALEN) using the key * described by HEXGRIP. Stores the plaintext at R_PLAINDATA and its * length at R_PLAINDATALEN; R_PLAINDATALEN must initially point to * the allocated size of R_PLAINDATA and is updated to the actual used * size on return. */ gpg_error_t scute_agent_decrypt (const char *hexgrip, unsigned char *encdata, int encdatalen, unsigned char *r_plaindata, unsigned int *r_plaindatalen) { char cmd[150]; gpg_error_t err; struct pkdecrypt_parm_s pkdecrypt; char *s_data; size_t s_datalen; if (!hexgrip || !encdata || !encdatalen || !r_plaindatalen) return gpg_error (GPG_ERR_INV_ARG); if (!r_plaindata) { /* Fixme: We do not return the minimal required length but our * internal buffer size. */ pkdecrypt.len = *r_plaindatalen; *r_plaindatalen = sizeof pkdecrypt.data - 1; if (pkdecrypt.len > sizeof pkdecrypt.data - 1) return gpg_error (GPG_ERR_INV_LENGTH); return 0; } + err = ensure_agent_connection (); + if (err) + return err; + snprintf (cmd, sizeof (cmd), "SETKEY %s", hexgrip); err = assuan_transact (agent_ctx, cmd, NULL, NULL, default_inq_cb, NULL, NULL, NULL); + err = check_broken_pipe (err); if (err) return err; /* Convert the input into an appropriate s-expression as expected by * gpg-agent which is: * * (enc-val * (flags pkcs1) * (rsa * (a VALUE))) * * Out of convenience we append a non-counted extra nul to the * created canonical s-expression. */ s_data = malloc (100 + encdatalen); if (!s_data) return gpg_error_from_syserror (); snprintf (s_data, 50, "(7:enc-val(5:flags5:pkcs1)(3:rsa(1:a%d:", encdatalen); s_datalen = strlen (s_data); memcpy (s_data + s_datalen, encdata, encdatalen); s_datalen += encdatalen; memcpy (s_data + s_datalen, ")))", 4); s_datalen += 3; pkdecrypt.len = 0; pkdecrypt.ctx = agent_ctx; pkdecrypt.ciphertext = s_data; pkdecrypt.ciphertextlen = s_datalen; err = assuan_transact (agent_ctx, "PKDECRYPT", pkdecrypt_data_cb, &pkdecrypt, pkdecrypt_inq_cb, &pkdecrypt, NULL, NULL); + err = check_broken_pipe (err); if (!err) err = pkdecrypt_parse_result (&pkdecrypt, r_plaindata, r_plaindatalen); free (s_data); return err; } /* Determine if FPR is trusted. */ gpg_error_t scute_agent_is_trusted (const char *fpr, bool *is_trusted) { gpg_error_t err; bool trusted = false; char cmd[150]; + err = ensure_agent_connection (); + if (err) + return err; + snprintf (cmd, sizeof (cmd), "ISTRUSTED %s", fpr); err = assuan_transact (agent_ctx, cmd, NULL, NULL, default_inq_cb, NULL, NULL, NULL); + err = check_broken_pipe (err); if (err && gpg_err_code (err) != GPG_ERR_NOT_TRUSTED) return err; else if (!err) trusted = true; *is_trusted = trusted; return 0; } #define GET_CERT_INIT_SIZE 2048 struct get_cert_s { unsigned char *cert_der; int cert_der_len; int cert_der_size; }; gpg_error_t get_cert_data_cb (void *opaque, const void *data, size_t data_len) { struct get_cert_s *cert_s = opaque; int needed_size; needed_size = cert_s->cert_der_len + data_len; if (needed_size > cert_s->cert_der_size) { unsigned char *new_cert_der; int new_cert_der_size = cert_s->cert_der_size; if (new_cert_der_size == 0) new_cert_der_size = GET_CERT_INIT_SIZE; while (new_cert_der_size < needed_size) new_cert_der_size *= 2; if (cert_s->cert_der == NULL) new_cert_der = malloc (new_cert_der_size); else new_cert_der = realloc (cert_s->cert_der, new_cert_der_size); if (new_cert_der == NULL) return gpg_error_from_syserror (); cert_s->cert_der = new_cert_der; cert_s->cert_der_size = new_cert_der_size; } memcpy (cert_s->cert_der + cert_s->cert_der_len, data, data_len); cert_s->cert_der_len += data_len; return 0; } /* Try to get certificate for CERTREF. */ gpg_error_t scute_agent_get_cert (const char *certref, struct cert *cert) { gpg_error_t err; char cmd[150]; struct get_cert_s cert_s; cert_s.cert_der = NULL; cert_s.cert_der_len = 0; cert_s.cert_der_size = 0; + err = ensure_agent_connection (); + if (err) + return err; + snprintf (cmd, sizeof (cmd), "SCD READCERT %s", certref); err = assuan_transact (agent_ctx, cmd, get_cert_data_cb, &cert_s, NULL, NULL, NULL, NULL); + err = check_broken_pipe (err); /* Just to be safe... */ if (!err && (cert_s.cert_der_len <= 16 || cert_s.cert_der[0] != 0x30)) { DEBUG (DBG_INFO, "bad card certificate rejected"); err = gpg_error (GPG_ERR_BAD_CERT); } if (err) { if (cert_s.cert_der) free (cert_s.cert_der); return err; } DEBUG (DBG_INFO, "got certificate from card with length %i", cert_s.cert_der_len); cert->cert_der = cert_s.cert_der; cert->cert_der_len = cert_s.cert_der_len; strncpy (cert->certref, certref, sizeof cert->certref -1); cert->certref[sizeof cert->certref - 1] = 0; return 0; } struct random_request { unsigned char *buffer; size_t len; }; gpg_error_t get_challenge_data_cb (void *opaque, const void *line, size_t len) { struct random_request *request = opaque; if (len != request->len) return gpg_error (GPG_ERR_INV_LENGTH); memcpy (request->buffer, line, len); return 0; } gpg_error_t scute_agent_get_random (unsigned char *data, size_t len) { char command[16]; gpg_error_t err; struct random_request request; + err = ensure_agent_connection (); + if (err) + return err; + snprintf (command, sizeof(command), "SCD RANDOM %zu", len); request.buffer = data; request.len = len; err = assuan_transact (agent_ctx, command, get_challenge_data_cb, &request, NULL, NULL, NULL, NULL); - + err = check_broken_pipe (err); return err; } void scute_agent_finalize (void) { if (!agent_ctx) { DEBUG (DBG_CRIT, "no GPG Agent connection established"); return; } DEBUG (DBG_INFO, "releasing agent context"); assuan_release (agent_ctx); agent_ctx = NULL; } diff --git a/src/agent.h b/src/agent.h index 0dfce0e..710f14c 100644 --- a/src/agent.h +++ b/src/agent.h @@ -1,129 +1,126 @@ /* agent.h - Interface for talking to gpg-agent. Copyright (C) 2006, 2007 g10 Code GmbH This file is part of Scute. Scute is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Scute 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 Scute; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA In addition, as a special exception, g10 Code GmbH gives permission to link this library: with the Mozilla Foundation's code for Mozilla (or with modified versions of it that use the same license as the "Mozilla" code), and distribute the linked executables. You must obey the GNU General Public License in all respects for all of the code used other than "Mozilla". If you modify this file, you may extend this exception to your version of the file, but you are not obligated to do so. If you do not wish to do so, delete this exception statement from your version. */ #ifndef AGENT_H #define AGENT_H 1 #include #include #include "cert.h" /* The information structure for a smart card. */ struct agent_card_info_s { char *serialno; /* Malloced hex string. */ char *dispserialno; /* NULL or malloced human readable S/N. */ char *cardtype; /* Null or malloced string with the card type. */ char *disp_name; /* Malloced. */ char *disp_lang; /* Malloced. */ int disp_sex; /* 0 = unspecified, 1 = male, 2 = female. */ char *pubkey_url; /* Malloced. */ char *login_data; /* Malloced. */ char *private_do[4]; /* Malloced. */ char cafpr1valid; char cafpr2valid; char cafpr3valid; char cafpr1[20]; char cafpr2[20]; char cafpr3[20]; key_info_t kinfo; /* Linked list with all keypair related data. */ char fpr1valid; /* Duplicated info for the legacy parts of the code. */ char fpr2valid; char fpr3valid; char fpr1[20]; char fpr2[20]; char fpr3[20]; unsigned int fpr1time; unsigned int fpr2time; unsigned int fpr3time; unsigned long sig_counter; int chv1_cached; /* True if a PIN is not required for each signing. Note that the gpg-agent might cache it anyway. */ int chvmaxlen[3]; /* Maximum allowed length of a CHV. */ int chvretry[3]; /* Allowed retries for the CHV; 0 = blocked. */ int rng_available; /* True if the GET CHALLENGE operation is supported. */ int is_piv; /* True if this is a PIV card. */ }; typedef struct agent_card_info_s *agent_card_info_t; /* Try to connect to the agent via socket. Handle the server's initial greeting. */ gpg_error_t scute_agent_initialize (void); -/* Return the major and minor version of the agent. */ -int scute_agent_get_agent_version (int *minor); - /* Tear down the agent connection and release all associated resources. */ void scute_agent_finalize (void); /* Check the agent status. This returns 0 if a token is present, GPG_ERR_CARD_REMOVED if no token is present, and an error code otherwise. */ gpg_error_t scute_agent_check_status (void); /* Call the agent to learn about a smartcard. */ gpg_error_t scute_agent_learn (struct agent_card_info_s *info); /* Release the card info structure INFO. */ void scute_agent_release_card_info (struct agent_card_info_s *info); key_info_t scute_find_kinfo (agent_card_info_t info, const char *keyref); /* Sign the data DATA of length LEN with the key HEXGRIP and return * the signature in SIG_RESULT and SIG_LEN. */ gpg_error_t scute_agent_sign (const char *hexgrip, unsigned char *data, int len, unsigned char *sig_result, unsigned int *sig_len); /* Decrypt data. */ gpg_error_t scute_agent_decrypt (const char *hexgrip, unsigned char *encdata, int encdatalen, unsigned char *r_plaindata, unsigned int *r_plaindatalen); /* Determine if FPR is trusted. */ gpg_error_t scute_agent_is_trusted (const char *fpr, bool *is_trusted); /* Try to get certificate for key numer NO. */ gpg_error_t scute_agent_get_cert (const char *certref, struct cert *cert); /* Get random bytes from the card. */ gpg_error_t scute_agent_get_random (unsigned char *data, size_t len); #endif /* AGENT_H */ diff --git a/src/get-path.c b/src/get-path.c index 3647b30..9768c11 100644 --- a/src/get-path.c +++ b/src/get-path.c @@ -1,384 +1,551 @@ /* agent.c - Talking to gpg-agent. * Copyright (C) 2008 g10 Code GmbH * * This file is part of Scute. * * Scute is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * Scute 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this program; if not, see . * SPDX-License-Identifier: LGPL-2.1-or-later */ #if HAVE_CONFIG_H #include #endif #include #include #include #ifdef HAVE_W32_SYSTEM # include # include # include #endif #include #include "debug.h" #include "support.h" +/* Malloced string with GnuPG's version. NULL if gnupg is notproperly + * installed. */ +static char *gnupg_version_string; + + + + #ifndef HAVE_STPCPY static char * my_stpcpy (char *a, const char *b) { while( *b ) *a++ = *b++; *a = 0; return (char*)a; } # undef stpcpy # define stpcpy(a,b) my_stpcpy ((a), (b)) #endif /* !HAVE_STPCPY */ #ifdef HAVE_W32_SYSTEM #define RTLD_LAZY 0 static __inline__ void * dlopen (const char * name, int flag) { void * hd = LoadLibrary (name); (void)flag; return hd; } static __inline__ void * dlsym (void * hd, const char * sym) { if (hd && sym) { void * fnc = GetProcAddress (hd, sym); if (!fnc) return NULL; return fnc; } return NULL; } static __inline__ int dlclose (void * hd) { if (hd) { FreeLibrary (hd); return 0; } return -1; } /* Return a string from the W32 Registry or NULL in case of error. Caller must release the return value. A NULL for root is an alias for HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE in turn. */ static char * read_w32_registry_string (const char *root, const char *dir, const char *name) { HKEY root_key, key_handle; DWORD n1, nbytes, type; char *result = NULL; if ( !root ) root_key = HKEY_CURRENT_USER; else if ( !strcmp( root, "HKEY_CLASSES_ROOT" ) ) root_key = HKEY_CLASSES_ROOT; else if ( !strcmp( root, "HKEY_CURRENT_USER" ) ) root_key = HKEY_CURRENT_USER; else if ( !strcmp( root, "HKEY_LOCAL_MACHINE" ) ) root_key = HKEY_LOCAL_MACHINE; else if ( !strcmp( root, "HKEY_USERS" ) ) root_key = HKEY_USERS; else if ( !strcmp( root, "HKEY_PERFORMANCE_DATA" ) ) root_key = HKEY_PERFORMANCE_DATA; else if ( !strcmp( root, "HKEY_CURRENT_CONFIG" ) ) root_key = HKEY_CURRENT_CONFIG; else return NULL; if ( RegOpenKeyEx ( root_key, dir, 0, KEY_READ, &key_handle ) ) { if (root) return NULL; /* no need for a RegClose, so return direct */ /* It seems to be common practise to fall back to HKLM. */ if (RegOpenKeyEx (HKEY_LOCAL_MACHINE, dir, 0, KEY_READ, &key_handle) ) return NULL; /* still no need for a RegClose, so return direct */ } nbytes = 1; if ( RegQueryValueEx( key_handle, name, 0, NULL, NULL, &nbytes ) ) { if (root) goto leave; /* Try to fallback to HKLM also vor a missing value. */ RegCloseKey (key_handle); if (RegOpenKeyEx (HKEY_LOCAL_MACHINE, dir, 0, KEY_READ, &key_handle) ) return NULL; /* Nope. */ if (RegQueryValueEx ( key_handle, name, 0, NULL, NULL, &nbytes)) goto leave; } result = malloc ( (n1=nbytes+1) ); if ( !result ) goto leave; if ( RegQueryValueEx ( key_handle, name, 0, &type, result, &n1 ) ) { free(result); result = NULL; goto leave; } result[nbytes] = 0; /* Make sure it is really a string. */ if (type == REG_EXPAND_SZ && strchr (result, '%')) { char *tmp; n1 += 1000; tmp = malloc (n1+1); if (!tmp) goto leave; nbytes = ExpandEnvironmentStrings (result, tmp, n1); if (nbytes && nbytes > n1) { free (tmp); n1 = nbytes; tmp = malloc (n1 + 1); if (!tmp) goto leave; nbytes = ExpandEnvironmentStrings (result, tmp, n1); if (nbytes && nbytes > n1) { free (tmp); /* Oops - truncated, better don't expand at all. */ goto leave; } tmp[nbytes] = 0; free (result); result = tmp; } else if (nbytes) /* Okay, reduce the length. */ { tmp[nbytes] = 0; free (result); result = malloc (strlen (tmp)+1); if (!result) result = tmp; else { strcpy (result, tmp); free (tmp); } } else /* Error - don't expand. */ { free (tmp); } } leave: RegCloseKey( key_handle ); return result; } /* This is a helper function to load and run a Windows function from either of one DLLs. */ static HRESULT w32_shgetfolderpath (HWND a, int b, HANDLE c, DWORD d, LPSTR e) { static int initialized; static HRESULT (WINAPI * func)(HWND,int,HANDLE,DWORD,LPSTR); if (!initialized) { static char *dllnames[] = { "shell32.dll", "shfolder.dll", NULL }; void *handle; int i; initialized = 1; for (i=0, handle = NULL; !handle && dllnames[i]; i++) { handle = dlopen (dllnames[i], RTLD_LAZY); if (handle) { func = dlsym (handle, "SHGetFolderPathA"); if (!func) { dlclose (handle); handle = NULL; } } } } if (func) return func (a,b,c,d,e); else return -1; } static char * find_program_in_inst_dir (const char *name) { char *result = NULL; char *tmp; tmp = read_w32_registry_string ("HKEY_LOCAL_MACHINE", "Software\\GNU\\GnuPG", "Install Directory"); if (!tmp) return NULL; result = malloc (strlen (tmp) + 1 + strlen (name) + 1); if (!result) { free (tmp); return NULL; } strcpy (stpcpy (stpcpy (result, tmp), "\\"), name); free (tmp); if (access (result, F_OK)) { free (result); return NULL; } return result; } static char * find_program_at_standard_place (const char *name) { char path[MAX_PATH]; char *result = NULL; if (w32_shgetfolderpath (NULL, CSIDL_PROGRAM_FILES, NULL, 0, path) >= 0) { result = malloc (strlen (path) + 1 + strlen (name) + 1); if (result) { strcpy (stpcpy (stpcpy (result, path), "\\"), name); if (access (result, F_OK)) { free (result); result = NULL; } } } return result; } #endif -/* Return the file name of the gpgconf utility. */ +/* Read a line form the output of COMMAND via popen and return that + * line at BUFFER which has been allocated by the caller with BUFSIZE + * bytes. On success BUFFER contains a string with the first line. + * Command and buffer may have the same address. If no output is + * expected BUFFER can be given as NULL. */ +gpg_error_t +read_first_line (const char *command, char *buffer, size_t bufsize) +{ + gpg_error_t err; + FILE *fp; + + if (buffer && bufsize < 2) + return gpg_error (GPG_ERR_BUFFER_TOO_SHORT); + +#ifdef HAVE_W32_SYSTEM + fp = _popen (command, "r"); +#else + fp = popen (command, "r"); +#endif + if (fp) + { + int i, c; + + if (buffer) + { + for (i=0; i < bufsize - 1 && (c=getc(fp)) != EOF && c != '\n'; i++) + buffer[i] = c; + buffer [i] = 0; /* Terminate string. */ + if (c == EOF && ferror (fp)) + err = gpg_error_from_syserror (); /* Read error. */ + else if (!(i < bufsize - 1)) + err = gpg_error (GPG_ERR_NO_AGENT); /* Path too long. */ + else if (!i || c != '\n') + err = gpg_error (GPG_ERR_NO_AGENT); /* No terminating LF. */ + else + err = 0; + } + else + err = 0; + pclose (fp); + } + else + { + err = gpg_error_from_syserror (); + DEBUG (DBG_CRIT, "popen(%s) failed: %s", + command, gpg_strerror (err)); + } + + return err; +} + + + +/* Extract the version string of a program from STRING. The version + * number is expected to be in GNU style format: + * + * foo 1.2.3 + * foo (bar system) 1.2.3 + * foo 1.2.3 cruft + * foo (bar system) 1.2.3 cruft. + * + * Spaces and tabs are skipped and used as delimiters, a term in + * (nested) parenthesis before the version string is skipped, the + * version string may consist of any non-space and non-tab characters + * but needs to start with a digit. + */ +static const char * +extract_version_string (const char *string, size_t *r_len) +{ + const char *s; + int count, len; + + for (s=string; *s; s++) + if (*s == ' ' || *s == '\t') + break; + while (*s == ' ' || *s == '\t') + s++; + if (*s == '(') + { + for (count=1, s++; count && *s; s++) + if (*s == '(') + count++; + else if (*s == ')') + count--; + } + /* For robustness we look for a digit. */ + while ( *s && !(*s >= '0' && *s <= '9') ) + s++; + if (*s >= '0' && *s <= '9') + { + for (len=0; s[len]; len++) + if (s[len] == ' ' || s[len] == '\t') + break; + } + else + len = 0; + + *r_len = len; + return s; +} + + +/* Return the file name of the gpgconf utility. As a side-effect the + * version number of gnupg is also figured out the first time this + * function is called. */ const char * get_gpgconf_path (void) { static const char *pgmname; #ifdef HAVE_W32_SYSTEM if (!pgmname) pgmname = find_program_in_inst_dir ("gpgconf.exe"); if (!pgmname) pgmname = find_program_at_standard_place ("GNU\\GnuPG\\gpgconf.exe"); #endif if (!pgmname) pgmname = "gpgconf"; + if (!gnupg_version_string) + { + char buffer[512]; + const char *s; + size_t n; + + snprintf (buffer, sizeof buffer, "%s --version", pgmname); + if (!read_first_line (buffer, buffer, sizeof buffer)) + { + s = extract_version_string (buffer, &n); + gnupg_version_string = malloc (n+1); + if (gnupg_version_string) + { + memcpy (gnupg_version_string, s, n); + gnupg_version_string[n] = 0; + } + } + } return pgmname; } +/* Return the version of GnuPG. */ +int +get_gnupg_version (int *minor) +{ + int major; + const char *s; + + if (!gnupg_version_string) + { + *minor = 0; + return 0; + } + + major = atoi (gnupg_version_string); + s = strchr (gnupg_version_string, '.'); + *minor = s? atoi (s+1) : 0; + + return major; +} + + +/* Return true if GnuPG is older than MAJOR.MINOR.MICRO. */ +int +is_gnupg_older_than (int major, int minor, int micro) +{ + int my_major, my_minor, my_micro; + const char *s; + + if (!gnupg_version_string) + return 1; + + my_minor = my_micro = 0; + my_major = atoi (gnupg_version_string); + s = strchr (gnupg_version_string, '.'); + if (s) + { + my_minor = atoi (++s); + s = strchr (s, '.'); + if (s) + my_micro = atoi (++s); + } + if (my_major < major) + return 1; + if (my_major > major) + return 0; + + if (my_minor < minor) + return 1; + if (my_minor > minor) + return 0; + + if (my_micro < micro) + return 1; + return 0; +} + + /* Return the bindir where the main binaries are installed. This may * return NULL. */ static const char * get_bindir (void) { static char *bindir; gpg_error_t err = 0; char buffer[512]; - FILE *fp; if (!bindir) { - snprintf (buffer, sizeof buffer, "%s --null --list-dirs bindir", + snprintf (buffer, sizeof buffer, "%s --list-dirs bindir", get_gpgconf_path ()); -#ifdef HAVE_W32_SYSTEM - fp = _popen (buffer, "r"); -#else - fp = popen (buffer, "r"); -#endif - if (fp) + err = read_first_line (buffer, buffer, sizeof buffer); + if (!err) { - int i, c; - - for (i=0; i < sizeof buffer - 1 && (c = getc (fp)) != EOF; i++) - buffer[i] = c; - if (c == EOF && ferror (fp)) + bindir = strdup (buffer); + if (!bindir) err = gpg_error_from_syserror (); - else if (!(i < sizeof buffer - 1)) - err = gpg_error (GPG_ERR_NO_AGENT); /* Path too long. */ - else if (!i || buffer[i-1]) - err = gpg_error (GPG_ERR_NO_AGENT); /* No terminating nul. */ - else if (!(bindir = strdup (buffer))) - err = gpg_error_from_syserror (); - - pclose (fp); } - else - err = gpg_error_from_syserror (); - if (err) DEBUG (DBG_CRIT, "error locating GnuPG's installation directory: %s", gpg_strerror (err)); } return bindir; } const char * get_gpgsm_path (void) { static char *pgmname; #ifdef HAVE_W32_SYSTEM static const char gpgsm[] = "gpgsm.exe"; #else static const char gpgsm[] = "gpgsm"; #endif if (!pgmname) { char *buffer; const char *bindir = get_bindir (); if (!bindir) return gpgsm; /* Error fallback without any path component. */ buffer = malloc (strlen (bindir) + 1 + strlen (gpgsm) + 1); if (!buffer) return gpgsm; /* Error fallback. */ strcpy (stpcpy (stpcpy (buffer, bindir), "/"), gpgsm); pgmname = buffer; } return pgmname; } diff --git a/src/p11-getslotinfo.c b/src/p11-getslotinfo.c index c5bd8ed..27c7784 100644 --- a/src/p11-getslotinfo.c +++ b/src/p11-getslotinfo.c @@ -1,78 +1,78 @@ /* p11-getslotinfo.c - Cryptoki implementation. * Copyright (C) 2006 g10 Code GmbH * * This file is part of Scute. * * Scute is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * Scute 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this program; if not, see . * SPDX-License-Identifier: LGPL-2.1-or-later */ #if HAVE_CONFIG_H #include #endif #include #include #include "cryptoki.h" #include "agent.h" #include "locking.h" #include "support.h" #include "settings.h" #include "slots.h" CK_RV CK_SPEC C_GetSlotInfo (CK_SLOT_ID slotID, CK_SLOT_INFO_PTR pInfo) { CK_RV err = CKR_OK; slot_iterator_t slot; const char *s; int minor; err = scute_global_lock (); if (err) return err; err = slots_lookup (slotID, &slot); if (err) goto out; err = slots_update_slot (slot); if (err) goto out; /* FIXME: Query some of this from SCD. */ scute_copy_string (pInfo->slotDescription, SLOT_DESCRIPTION, 64); scute_copy_string (pInfo->manufacturerID, SLOT_MANUFACTURER_ID, 32); pInfo->flags = CKF_REMOVABLE_DEVICE | CKF_HW_SLOT; if (slot_token_present (slot)) pInfo->flags |= CKF_TOKEN_PRESENT; /* Use the gpg-agent version for the hardware version.. */ - pInfo->hardwareVersion.major = scute_agent_get_agent_version (&minor); + pInfo->hardwareVersion.major = get_gnupg_version (&minor); pInfo->hardwareVersion.minor = minor; /* Use Scute version as Firmware version. */ s = PACKAGE_VERSION; pInfo->firmwareVersion.major = atoi (s); s = strchr (s, '.'); pInfo->firmwareVersion.minor = s? atoi (s+1): 0; out: scute_global_unlock (); return err; } diff --git a/src/support.h b/src/support.h index 4cd222f..4b437fa 100644 --- a/src/support.h +++ b/src/support.h @@ -1,79 +1,84 @@ /* support.h - Cryptoki implementation support functions. Copyright (C) 2006 g10 Code GmbH This file is part of Scute. Scute is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Scute 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 Scute; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA In addition, as a special exception, g10 Code GmbH gives permission to link this library: with the Mozilla Foundation's code for Mozilla (or with modified versions of it that use the same license as the "Mozilla" code), and distribute the linked executables. You must obey the GNU General Public License in all respects for all of the code used other than "Mozilla". If you modify this file, you may extend this exception to your version of the file, but you are not obligated to do so. If you do not wish to do so, delete this exception statement from your version. */ #ifndef SUPPORT_H #define SUPPORT_H 1 +#include + #define spacep(p) (*(p) == ' ' || *(p) == '\t') #define digitp(p) (*(p) >= '0' && *(p) <= '9') #define hexdigitp(a) (digitp (a) \ || (*(a) >= 'A' && *(a) <= 'F') \ || (*(a) >= 'a' && *(a) <= 'f')) #define xtoi_1(p) (*(p) <= '9'? (*(p)- '0'): \ *(p) <= 'F'? (*(p)-'A'+10):(*(p)-'a'+10)) #define xtoi_2(p) ((xtoi_1(p) * 16) + xtoi_1((p)+1)) #define DIM(x) (sizeof (x) / sizeof (x[0])) /* Copy a string into its location, with blank character padding. */ static inline void scute_copy_string (char *dest, const char *src, int max_len) { int i; for (i = 0; (i < max_len) && (*src != '\0'); i++) *(dest++) = *(src++); while (i++ < max_len) *(dest++) = ' '; } /* Use gpg-errors printf functions for better portability. We also replace the standard snprintf with our implementation due to a bug in some mingw32 versions related to the 'l' format modifier. */ #define asprintf gpgrt_asprintf #define vasprintf gpgrt_vasprintf #define snprintf gpgrt_snprintf /*-- Simple replacement functions. */ #ifndef HAVE_TTYNAME /* Systems without ttyname (W32) will merely return NULL. */ static inline char * ttyname (int fd) { (void)fd; return NULL; } #endif /* !HAVE_TTYNAME */ +gpg_error_t read_first_line (const char *command, char *buffer, size_t bufsize); const char *get_gpgconf_path (void); +int get_gnupg_version (int *minor); +int is_gnupg_older_than (int major, int minor, int micro); const char *get_gpgsm_path (void); #endif /* !SUPPORT_H */