From 8de1ee1b2b676b0d07586f0752750dd6b0fb7511 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sat, 27 Apr 2024 11:59:15 +0200 Subject: Adding upstream version 2.2.27. Signed-off-by: Daniel Baumann --- g10/call-agent.c | 2668 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 2668 insertions(+) create mode 100644 g10/call-agent.c (limited to 'g10/call-agent.c') diff --git a/g10/call-agent.c b/g10/call-agent.c new file mode 100644 index 0000000..c7f1c29 --- /dev/null +++ b/g10/call-agent.c @@ -0,0 +1,2668 @@ +/* call-agent.c - Divert GPG operations to the agent. + * Copyright (C) 2001-2003, 2006-2011, 2013 Free Software Foundation, Inc. + * Copyright (C) 2013-2015 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 +#ifdef HAVE_LOCALE_H +#include +#endif + +#include "gpg.h" +#include +#include "../common/util.h" +#include "../common/membuf.h" +#include "options.h" +#include "../common/i18n.h" +#include "../common/asshelp.h" +#include "../common/sysutils.h" +#include "call-agent.h" +#include "../common/status.h" +#include "../common/shareddefs.h" +#include "../common/host2net.h" + +#define CONTROL_D ('D' - 'A' + 1) + + +static assuan_context_t agent_ctx = NULL; +static int did_early_card_test; + +struct default_inq_parm_s +{ + ctrl_t ctrl; + assuan_context_t ctx; + struct { + u32 *keyid; + u32 *mainkeyid; + int pubkey_algo; + } keyinfo; +}; + +struct cipher_parm_s +{ + struct default_inq_parm_s *dflt; + assuan_context_t ctx; + unsigned char *ciphertext; + size_t ciphertextlen; +}; + +struct writecert_parm_s +{ + struct default_inq_parm_s *dflt; + const unsigned char *certdata; + size_t certdatalen; +}; + +struct writekey_parm_s +{ + struct default_inq_parm_s *dflt; + const unsigned char *keydata; + size_t keydatalen; +}; + +struct genkey_parm_s +{ + struct default_inq_parm_s *dflt; + const char *keyparms; + const char *passphrase; +}; + +struct import_key_parm_s +{ + struct default_inq_parm_s *dflt; + const void *key; + size_t keylen; +}; + + +struct cache_nonce_parm_s +{ + char **cache_nonce_addr; + char **passwd_nonce_addr; +}; + + +static gpg_error_t learn_status_cb (void *opaque, const char *line); + + + +/* If RC is not 0, write an appropriate status message. */ +static void +status_sc_op_failure (int rc) +{ + switch (gpg_err_code (rc)) + { + case 0: + break; + case GPG_ERR_CANCELED: + case GPG_ERR_FULLY_CANCELED: + write_status_text (STATUS_SC_OP_FAILURE, "1"); + break; + case GPG_ERR_BAD_PIN: + write_status_text (STATUS_SC_OP_FAILURE, "2"); + break; + default: + write_status (STATUS_SC_OP_FAILURE); + break; + } +} + + +/* This is the default inquiry callback. It mainly handles the + Pinentry notifications. */ +static gpg_error_t +default_inq_cb (void *opaque, const char *line) +{ + gpg_error_t err = 0; + struct default_inq_parm_s *parm = opaque; + + if (has_leading_keyword (line, "PINENTRY_LAUNCHED")) + { + err = gpg_proxy_pinentry_notify (parm->ctrl, line); + if (err) + log_error (_("failed to proxy %s inquiry to client\n"), + "PINENTRY_LAUNCHED"); + /* We do not pass errors to avoid breaking other code. */ + } + else if ((has_leading_keyword (line, "PASSPHRASE") + || has_leading_keyword (line, "NEW_PASSPHRASE")) + && opt.pinentry_mode == PINENTRY_MODE_LOOPBACK) + { + if (have_static_passphrase ()) + { + const char *s = get_static_passphrase (); + err = assuan_send_data (parm->ctx, s, strlen (s)); + } + else + { + char *pw; + char buf[32]; + + if (parm->keyinfo.keyid) + emit_status_need_passphrase (parm->ctrl, + parm->keyinfo.keyid, + parm->keyinfo.mainkeyid, + parm->keyinfo.pubkey_algo); + + snprintf (buf, sizeof (buf), "%u", 100); + write_status_text (STATUS_INQUIRE_MAXLEN, buf); + pw = cpr_get_hidden ("passphrase.enter", _("Enter passphrase: ")); + cpr_kill_prompt (); + if (*pw == CONTROL_D && !pw[1]) + err = gpg_error (GPG_ERR_CANCELED); + else + err = assuan_send_data (parm->ctx, pw, strlen (pw)); + xfree (pw); + } + } + else + log_debug ("ignoring gpg-agent inquiry '%s'\n", line); + + return err; +} + + +/* Print a warning if the server's version number is less than our + version number. Returns an error code on a connection problem. */ +static gpg_error_t +warn_version_mismatch (assuan_context_t ctx, const char *servername, int mode) +{ + gpg_error_t err; + char *serverversion; + const char *myversion = strusage (13); + + err = get_assuan_server_version (ctx, mode, &serverversion); + if (err) + log_log (gpg_err_code (err) == GPG_ERR_NOT_SUPPORTED? + GPGRT_LOG_INFO : GPGRT_LOG_ERROR, + _("error getting version from '%s': %s\n"), + servername, gpg_strerror (err)); + else if (compare_version_strings (serverversion, myversion) < 0) + { + char *warn; + + warn = xtryasprintf (_("server '%s' is older than us (%s < %s)"), + servername, serverversion, myversion); + if (!warn) + err = gpg_error_from_syserror (); + else + { + log_info (_("WARNING: %s\n"), warn); + if (!opt.quiet) + { + log_info (_("Note: Outdated servers may lack important" + " security fixes.\n")); + log_info (_("Note: Use the command \"%s\" to restart them.\n"), + "gpgconf --kill all"); + } + write_status_strings (STATUS_WARNING, "server_version_mismatch 0", + " ", warn, NULL); + xfree (warn); + } + } + xfree (serverversion); + return err; +} + + +#define FLAG_FOR_CARD_SUPPRESS_ERRORS 2 + +/* Try to connect to the agent via socket or fork it off and work by + pipes. Handle the server's initial greeting */ +static int +start_agent (ctrl_t ctrl, int flag_for_card) +{ + int rc; + + (void)ctrl; /* Not yet used. */ + + /* Fixme: We need a context for each thread or serialize the access + to the agent. */ + if (agent_ctx) + rc = 0; + else + { + rc = start_new_gpg_agent (&agent_ctx, + GPG_ERR_SOURCE_DEFAULT, + opt.agent_program, + opt.lc_ctype, opt.lc_messages, + opt.session_env, + opt.autostart, opt.verbose, DBG_IPC, + NULL, NULL); + if (!opt.autostart && gpg_err_code (rc) == GPG_ERR_NO_AGENT) + { + static int shown; + + if (!shown) + { + shown = 1; + log_info (_("no gpg-agent running in this session\n")); + } + } + else if (!rc + && !(rc = warn_version_mismatch (agent_ctx, GPG_AGENT_NAME, 0))) + { + /* Tell the agent that we support Pinentry notifications. + No error checking so that it will work also with older + agents. */ + assuan_transact (agent_ctx, "OPTION allow-pinentry-notify", + NULL, NULL, NULL, NULL, NULL, NULL); + /* Tell the agent about what version we are aware. This is + here used to indirectly enable GPG_ERR_FULLY_CANCELED. */ + assuan_transact (agent_ctx, "OPTION agent-awareness=2.1.0", + NULL, NULL, NULL, NULL, NULL, NULL); + /* Pass on the pinentry mode. */ + if (opt.pinentry_mode) + { + char *tmp = xasprintf ("OPTION pinentry-mode=%s", + str_pinentry_mode (opt.pinentry_mode)); + rc = assuan_transact (agent_ctx, tmp, + NULL, NULL, NULL, NULL, NULL, NULL); + xfree (tmp); + if (rc) + { + log_error ("setting pinentry mode '%s' failed: %s\n", + str_pinentry_mode (opt.pinentry_mode), + gpg_strerror (rc)); + write_status_error ("set_pinentry_mode", rc); + } + } + + /* Pass on the request origin. */ + if (opt.request_origin) + { + char *tmp = xasprintf ("OPTION pretend-request-origin=%s", + str_request_origin (opt.request_origin)); + rc = assuan_transact (agent_ctx, tmp, + NULL, NULL, NULL, NULL, NULL, NULL); + xfree (tmp); + if (rc) + { + log_error ("setting request origin '%s' failed: %s\n", + str_request_origin (opt.request_origin), + gpg_strerror (rc)); + write_status_error ("set_request_origin", rc); + } + } + + /* In DE_VS mode under Windows we require that the JENT RNG + * is active. */ +#ifdef HAVE_W32_SYSTEM + if (!rc && opt.compliance == CO_DE_VS) + { + if (assuan_transact (agent_ctx, "GETINFO jent_active", + NULL, NULL, NULL, NULL, NULL, NULL)) + { + rc = gpg_error (GPG_ERR_FORBIDDEN); + log_error (_("%s is not compliant with %s mode\n"), + GPG_AGENT_NAME, + gnupg_compliance_option_string (opt.compliance)); + write_status_error ("random-compliance", rc); + } + } +#endif /*HAVE_W32_SYSTEM*/ + + } + } + + if (!rc && flag_for_card && !did_early_card_test) + { + /* Request the serial number of the card for an early test. */ + struct agent_card_info_s info; + + memset (&info, 0, sizeof info); + + if (!(flag_for_card & FLAG_FOR_CARD_SUPPRESS_ERRORS)) + rc = warn_version_mismatch (agent_ctx, SCDAEMON_NAME, 2); + if (!rc) + rc = assuan_transact (agent_ctx, + opt.flags.use_only_openpgp_card? + "SCD SERIALNO openpgp" : "SCD SERIALNO", + NULL, NULL, NULL, NULL, + learn_status_cb, &info); + if (rc && !(flag_for_card & FLAG_FOR_CARD_SUPPRESS_ERRORS)) + { + switch (gpg_err_code (rc)) + { + case GPG_ERR_NOT_SUPPORTED: + case GPG_ERR_NO_SCDAEMON: + write_status_text (STATUS_CARDCTRL, "6"); + break; + case GPG_ERR_OBJ_TERM_STATE: + write_status_text (STATUS_CARDCTRL, "7"); + break; + default: + write_status_text (STATUS_CARDCTRL, "4"); + log_info ("selecting card failed: %s\n", gpg_strerror (rc)); + break; + } + } + + if (!rc && is_status_enabled () && info.serialno) + { + char *buf; + + buf = xasprintf ("3 %s", info.serialno); + write_status_text (STATUS_CARDCTRL, buf); + xfree (buf); + } + + agent_release_card_info (&info); + + if (!rc) + did_early_card_test = 1; + } + + + return rc; +} + + +/* 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 *s) +{ + return percent_plus_unescape (s, 0xff); +} + + +/* Take a 20 byte hexencoded string and put it into the provided + 20 byte buffer FPR in binary format. */ +static int +unhexify_fpr (const char *hexstr, unsigned char *fpr) +{ + const char *s; + int n; + + for (s=hexstr, n=0; hexdigitp (s); s++, n++) + ; + if ((*s && *s != ' ') || (n != 40)) + return 0; /* no fingerprint (invalid or wrong length). */ + for (s=hexstr, n=0; *s && n < 20; s += 2, n++) + fpr[n] = xtoi_2 (s); + + return 1; /* okay */ +} + +/* 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 *s; + char *p; + + for (s=line; hexdigitp (s); s++) + ; + p = xtrymalloc (s + 1 - line); + if (p) + { + memcpy (p, line, s-line); + p[s-line] = 0; + } + return p; +} + + + +/* This is a dummy data line callback. */ +static gpg_error_t +dummy_data_cb (void *opaque, const void *buffer, size_t length) +{ + (void)opaque; + (void)buffer; + (void)length; + return 0; +} + +/* A simple callback used to return the serialnumber of a card. */ +static gpg_error_t +get_serialno_cb (void *opaque, const char *line) +{ + char **serialno = opaque; + const char *keyword = line; + const char *s; + int keywordlen, n; + + for (keywordlen=0; *line && !spacep (line); line++, keywordlen++) + ; + while (spacep (line)) + line++; + + if (keywordlen == 8 && !memcmp (keyword, "SERIALNO", keywordlen)) + { + if (*serialno) + return gpg_error (GPG_ERR_CONFLICT); /* Unexpected status line. */ + for (n=0,s=line; hexdigitp (s); s++, n++) + ; + if (!n || (n&1)|| !(spacep (s) || !*s) ) + return gpg_error (GPG_ERR_ASS_PARAMETER); + *serialno = xtrymalloc (n+1); + if (!*serialno) + return out_of_core (); + memcpy (*serialno, line, n); + (*serialno)[n] = 0; + } + + return 0; +} + + + +/* Release the card info structure INFO. */ +void +agent_release_card_info (struct agent_card_info_s *info) +{ + int i; + + if (!info) + return; + + xfree (info->reader); info->reader = NULL; + xfree (info->manufacturer_name); info->manufacturer_name = NULL; + xfree (info->serialno); info->serialno = NULL; + xfree (info->apptype); info->apptype = NULL; + xfree (info->disp_name); info->disp_name = NULL; + xfree (info->disp_lang); info->disp_lang = NULL; + xfree (info->pubkey_url); info->pubkey_url = NULL; + xfree (info->login_data); info->login_data = NULL; + info->cafpr1valid = info->cafpr2valid = info->cafpr3valid = 0; + info->fpr1valid = info->fpr2valid = info->fpr3valid = 0; + for (i=0; i < DIM(info->private_do); i++) + { + xfree (info->private_do[i]); + info->private_do[i] = NULL; + } +} + + +static gpg_error_t +learn_status_cb (void *opaque, const char *line) +{ + struct agent_card_info_s *parm = opaque; + const char *keyword = line; + int keywordlen; + int i; + char *endp; + + for (keywordlen=0; *line && !spacep (line); line++, keywordlen++) + ; + while (spacep (line)) + line++; + + if (keywordlen == 6 && !memcmp (keyword, "READER", keywordlen)) + { + xfree (parm->reader); + parm->reader = unescape_status_string (line); + } + else if (keywordlen == 8 && !memcmp (keyword, "SERIALNO", keywordlen)) + { + xfree (parm->serialno); + parm->serialno = store_serialno (line); + parm->is_v2 = (strlen (parm->serialno) >= 16 + && xtoi_2 (parm->serialno+12) >= 2 ); + } + else if (keywordlen == 7 && !memcmp (keyword, "APPTYPE", keywordlen)) + { + xfree (parm->apptype); + parm->apptype = unescape_status_string (line); + } + else if (keywordlen == 9 && !memcmp (keyword, "DISP-NAME", keywordlen)) + { + xfree (parm->disp_name); + parm->disp_name = unescape_status_string (line); + } + else if (keywordlen == 9 && !memcmp (keyword, "DISP-LANG", keywordlen)) + { + xfree (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)) + { + xfree (parm->pubkey_url); + parm->pubkey_url = unescape_status_string (line); + } + else if (keywordlen == 10 && !memcmp (keyword, "LOGIN-DATA", keywordlen)) + { + xfree (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++; + } + xfree (buf); + } + } + 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, "ki")) + parm->extcap.ki = abool; + else if (!strcmp (p, "aac")) + parm->extcap.aac = abool; + else if (!strcmp (p, "kdf")) + parm->extcap.kdf = abool; + else if (!strcmp (p, "si")) + parm->status_indicator = strtoul (p2, NULL, 10); + } + } + xfree (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 == 8 && !memcmp (keyword, "KEY-TIME", keywordlen)) + { + int no = atoi (line); + while (* line && !spacep (line)) + line++; + while (spacep (line)) + line++; + if (no == 1) + parm->fpr1time = strtoul (line, NULL, 10); + else if (no == 2) + parm->fpr2time = strtoul (line, NULL, 10); + else if (no == 3) + parm->fpr3time = strtoul (line, NULL, 10); + } + else if (keywordlen == 11 && !memcmp (keyword, "KEYPAIRINFO", keywordlen)) + { + const char *hexgrp = line; + int no; + + while (*line && !spacep (line)) + line++; + while (spacep (line)) + line++; + if (strncmp (line, "OPENPGP.", 8)) + ; + else if ((no = atoi (line+8)) == 1) + unhexify_fpr (hexgrp, parm->grp1); + else if (no == 2) + unhexify_fpr (hexgrp, parm->grp2); + else if (no == 3) + unhexify_fpr (hexgrp, parm->grp3); + } + 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 == 8 && !memcmp (keyword, "KEY-ATTR", keywordlen)) + { + int keyno = 0; + int algo = PUBKEY_ALGO_RSA; + int n = 0; + + sscanf (line, "%d %d %n", &keyno, &algo, &n); + keyno--; + if (keyno < 0 || keyno >= DIM (parm->key_attr)) + return 0; + + parm->key_attr[keyno].algo = algo; + if (algo == PUBKEY_ALGO_RSA) + parm->key_attr[keyno].nbits = strtoul (line+n+3, NULL, 10); + else if (algo == PUBKEY_ALGO_ECDH || algo == PUBKEY_ALGO_ECDSA + || algo == PUBKEY_ALGO_EDDSA) + parm->key_attr[keyno].curve = openpgp_is_curve_supported (line + n, + NULL, NULL); + } + else if (keywordlen == 12 && !memcmp (keyword, "PRIVATE-DO-", 11) + && strchr("1234", keyword[11])) + { + int no = keyword[11] - '1'; + log_assert (no >= 0 && no <= 3); + xfree (parm->private_do[no]); + parm->private_do[no] = unescape_status_string (line); + } + else if (keywordlen == 12 && !memcmp (keyword, "MANUFACTURER", 12)) + { + xfree (parm->manufacturer_name); + parm->manufacturer_name = NULL; + parm->manufacturer_id = strtoul (line, &endp, 0); + while (endp && spacep (endp)) + endp++; + if (endp && *endp) + parm->manufacturer_name = xstrdup (endp); + } + else if (keywordlen == 3 && !memcmp (keyword, "KDF", 3)) + { + unsigned char *data = unescape_status_string (line); + + if (data[2] != 0x03) + parm->kdf_do_enabled = 0; + else if (data[22] != 0x85) + parm->kdf_do_enabled = 1; + else + parm->kdf_do_enabled = 2; + xfree (data); + } + + return 0; +} + + +/* Call the scdaemon to learn about a smartcard. Note that in + * contradiction to the function's name, gpg-agent's LEARN command is + * used and not the low-level "SCD LEARN". + * Used by: + * card-util.c + * keyedit_menu + * card_store_key_with_backup (Woth force to remove secret key data) + */ +int +agent_scd_learn (struct agent_card_info_s *info, int force) +{ + int rc; + struct default_inq_parm_s parm; + struct agent_card_info_s dummyinfo; + + if (!info) + info = &dummyinfo; + memset (info, 0, sizeof *info); + memset (&parm, 0, sizeof parm); + + rc = start_agent (NULL, 1); + if (rc) + return rc; + + parm.ctx = agent_ctx; + rc = assuan_transact (agent_ctx, + force ? "LEARN --sendinfo --force" : "LEARN --sendinfo", + dummy_data_cb, NULL, default_inq_cb, &parm, + learn_status_cb, info); + /* Also try to get the key attributes. */ + if (!rc) + agent_scd_getattr ("KEY-ATTR", info); + + if (info == &dummyinfo) + agent_release_card_info (info); + + return rc; +} + + + +/* Callback for the agent_scd_keypairinfo function. */ +static gpg_error_t +scd_keypairinfo_status_cb (void *opaque, const char *line) +{ + strlist_t *listaddr = opaque; + const char *keyword = line; + int keywordlen; + strlist_t sl; + char *p; + + for (keywordlen=0; *line && !spacep (line); line++, keywordlen++) + ; + while (spacep (line)) + line++; + + if (keywordlen == 11 && !memcmp (keyword, "KEYPAIRINFO", keywordlen)) + { + sl = append_to_strlist (listaddr, line); + p = sl->d; + /* Make sure that we only have two tokens so that future + * extensions of the format won't change the format expected by + * the caller. */ + while (*p && !spacep (p)) + p++; + if (*p) + { + while (spacep (p)) + p++; + while (*p && !spacep (p)) + p++; + if (*p) + { + *p++ = 0; + while (spacep (p)) + p++; + while (*p && !spacep (p)) + { + switch (*p++) + { + case 'c': sl->flags |= GCRY_PK_USAGE_CERT; break; + case 's': sl->flags |= GCRY_PK_USAGE_SIGN; break; + case 'e': sl->flags |= GCRY_PK_USAGE_ENCR; break; + case 'a': sl->flags |= GCRY_PK_USAGE_AUTH; break; + } + } + } + } + } + + return 0; +} + + +/* Read the keypairinfo lines of the current card directly from + * scdaemon. The list is returned as a string made up of the keygrip, + * a space and the keyref. The flags of the string carry the usage + * bits. */ +gpg_error_t +agent_scd_keypairinfo (ctrl_t ctrl, strlist_t *r_list) +{ + gpg_error_t err; + strlist_t list = NULL; + struct default_inq_parm_s inq_parm; + + *r_list = NULL; + err= start_agent (ctrl, 1); + if (err) + return err; + memset (&inq_parm, 0, sizeof inq_parm); + inq_parm.ctx = agent_ctx; + + err = assuan_transact (agent_ctx, "SCD LEARN --keypairinfo", + NULL, NULL, + default_inq_cb, &inq_parm, + scd_keypairinfo_status_cb, &list); + if (!err && !list) + err = gpg_error (GPG_ERR_NO_DATA); + if (err) + { + free_strlist (list); + return err; + } + *r_list = list; + return 0; +} + + + +/* Send an APDU to the current card. On success the status word is + * stored at R_SW. With HEXAPDU being NULL only a RESET command is + * send to scd. HEXAPDU may also be one of these special strings: + * + * "undefined" :: Send the command "SCD SERIALNO undefined" + * "lock" :: Send the command "SCD LOCK --wait" + * "trylock" :: Send the command "SCD LOCK" + * "unlock" :: Send the command "SCD UNLOCK" + * "reset-keep-lock" :: Send the command "SCD RESET --keep-lock" + * + * Used by: + * card-util.c + */ +gpg_error_t +agent_scd_apdu (const char *hexapdu, unsigned int *r_sw) +{ + gpg_error_t err; + + /* Start the agent but not with the card flag so that we do not + autoselect the openpgp application. */ + err = start_agent (NULL, 0); + if (err) + return err; + + if (!hexapdu) + { + err = assuan_transact (agent_ctx, "SCD RESET", + NULL, NULL, NULL, NULL, NULL, NULL); + + } + else if (!strcmp (hexapdu, "reset-keep-lock")) + { + err = assuan_transact (agent_ctx, "SCD RESET --keep-lock", + NULL, NULL, NULL, NULL, NULL, NULL); + } + else if (!strcmp (hexapdu, "lock")) + { + err = assuan_transact (agent_ctx, "SCD LOCK --wait", + NULL, NULL, NULL, NULL, NULL, NULL); + } + else if (!strcmp (hexapdu, "trylock")) + { + err = assuan_transact (agent_ctx, "SCD LOCK", + NULL, NULL, NULL, NULL, NULL, NULL); + } + else if (!strcmp (hexapdu, "unlock")) + { + err = assuan_transact (agent_ctx, "SCD UNLOCK", + NULL, NULL, NULL, NULL, NULL, NULL); + } + else if (!strcmp (hexapdu, "undefined")) + { + err = assuan_transact (agent_ctx, "SCD SERIALNO undefined", + NULL, NULL, NULL, NULL, NULL, NULL); + } + else + { + char line[ASSUAN_LINELENGTH]; + membuf_t mb; + unsigned char *data; + size_t datalen; + + init_membuf (&mb, 256); + + snprintf (line, DIM(line), "SCD APDU %s", hexapdu); + err = assuan_transact (agent_ctx, line, + put_membuf_cb, &mb, NULL, NULL, NULL, NULL); + if (!err) + { + data = get_membuf (&mb, &datalen); + if (!data) + err = gpg_error_from_syserror (); + else if (datalen < 2) /* Ooops */ + err = gpg_error (GPG_ERR_CARD); + else + { + *r_sw = buf16_to_uint (data+datalen-2); + } + xfree (data); + } + } + + return err; +} + + +/* Used by: + * card_store_subkey + * card_store_key_with_backup + */ +int +agent_keytocard (const char *hexgrip, int keyno, int force, + const char *serialno, const char *timestamp) +{ + int rc; + char line[ASSUAN_LINELENGTH]; + struct default_inq_parm_s parm; + + memset (&parm, 0, sizeof parm); + + snprintf (line, DIM(line), "KEYTOCARD %s%s %s OPENPGP.%d %s", + force?"--force ": "", hexgrip, serialno, keyno, timestamp); + + rc = start_agent (NULL, 1); + if (rc) + return rc; + parm.ctx = agent_ctx; + + rc = assuan_transact (agent_ctx, line, NULL, NULL, default_inq_cb, &parm, + NULL, NULL); + if (rc) + return rc; + + return rc; +} + +/* Object used with the agent_scd_getattr_one. */ +struct getattr_one_parm_s { + const char *keyword; /* Keyword to look for. */ + char *data; /* Malloced and unescaped data. */ + gpg_error_t err; /* Error code or 0 on success. */ +}; + + +/* Callback for agent_scd_getattr_one. */ +static gpg_error_t +getattr_one_status_cb (void *opaque, const char *line) +{ + struct getattr_one_parm_s *parm = opaque; + const char *s; + + if (parm->data) + return 0; /* We want only the first occurrence. */ + + if ((s=has_leading_keyword (line, parm->keyword))) + { + parm->data = percent_plus_unescape (s, 0xff); + if (!parm->data) + parm->err = gpg_error_from_syserror (); + } + + return 0; +} + + +/* Simplified version of agent_scd_getattr. This function returns + * only the first occurance of the attribute NAME and stores it at + * R_VALUE. A nul in the result is silennly replaced by 0xff. On + * error NULL is stored at R_VALUE. */ +gpg_error_t +agent_scd_getattr_one (const char *name, char **r_value) +{ + gpg_error_t err; + char line[ASSUAN_LINELENGTH]; + struct default_inq_parm_s inqparm; + struct getattr_one_parm_s parm; + + *r_value = NULL; + + if (!*name) + return gpg_error (GPG_ERR_INV_VALUE); + + memset (&inqparm, 0, sizeof inqparm); + inqparm.ctx = agent_ctx; + + memset (&parm, 0, sizeof parm); + parm.keyword = name; + + /* We assume that NAME does not need escaping. */ + if (12 + strlen (name) > DIM(line)-1) + return gpg_error (GPG_ERR_TOO_LARGE); + stpcpy (stpcpy (line, "SCD GETATTR "), name); + + err = start_agent (NULL, 1); + if (err) + return err; + + err = assuan_transact (agent_ctx, line, + NULL, NULL, + default_inq_cb, &inqparm, + getattr_one_status_cb, &parm); + if (!err && parm.err) + err = parm.err; + else if (!err && !parm.data) + err = gpg_error (GPG_ERR_NO_DATA); + + if (!err) + *r_value = parm.data; + else + xfree (parm.data); + + return err; +} + + + +/* Call the agent to retrieve a data object. This function returns + * the data in the same structure as used by the learn command. It is + * allowed to update such a structure using this command. + * + * Used by: + * build_sk_list + * enum_secret_keys + * get_signature_count + * card-util.c + * generate_keypair (KEY-ATTR) + * card_store_key_with_backup (SERIALNO) + * generate_card_subkeypair (KEY-ATTR) + */ +int +agent_scd_getattr (const char *name, struct agent_card_info_s *info) +{ + int rc; + char line[ASSUAN_LINELENGTH]; + struct default_inq_parm_s parm; + + memset (&parm, 0, sizeof parm); + + if (!*name) + return gpg_error (GPG_ERR_INV_VALUE); + + /* We assume that NAME does not need escaping. */ + if (12 + strlen (name) > DIM(line)-1) + return gpg_error (GPG_ERR_TOO_LARGE); + stpcpy (stpcpy (line, "SCD GETATTR "), name); + + rc = start_agent (NULL, 1); + if (rc) + return rc; + + parm.ctx = agent_ctx; + rc = assuan_transact (agent_ctx, line, NULL, NULL, default_inq_cb, &parm, + learn_status_cb, info); + + return rc; +} + + + +/* Send an setattr command to the SCdaemon. + * Used by: + * card-util.c + */ +gpg_error_t +agent_scd_setattr (const char *name, const void *value_arg, size_t valuelen) +{ + gpg_error_t err; + const unsigned char *value = value_arg; + char line[ASSUAN_LINELENGTH]; + char *p; + struct default_inq_parm_s parm; + + memset (&parm, 0, sizeof parm); + + if (!*name || !valuelen) + return gpg_error (GPG_ERR_INV_VALUE); + + /* We assume that NAME does not need escaping. */ + if (12 + strlen (name) > DIM(line)-1) + return gpg_error (GPG_ERR_TOO_LARGE); + + p = stpcpy (stpcpy (line, "SCD SETATTR "), name); + *p++ = ' '; + for (; valuelen; value++, valuelen--) + { + if (p >= line + DIM(line)-5 ) + return gpg_error (GPG_ERR_TOO_LARGE); + if (*value < ' ' || *value == '+' || *value == '%') + { + sprintf (p, "%%%02X", *value); + p += 3; + } + else if (*value == ' ') + *p++ = '+'; + else + *p++ = *value; + } + *p = 0; + + err = start_agent (NULL, 1); + if (!err) + { + parm.ctx = agent_ctx; + err = assuan_transact (agent_ctx, line, NULL, NULL, + default_inq_cb, &parm, NULL, NULL); + } + + status_sc_op_failure (err); + return err; +} + + + +/* Handle a CERTDATA inquiry. Note, we only send the data, + assuan_transact takes care of flushing and writing the END + command. */ +static gpg_error_t +inq_writecert_parms (void *opaque, const char *line) +{ + int rc; + struct writecert_parm_s *parm = opaque; + + if (has_leading_keyword (line, "CERTDATA")) + { + rc = assuan_send_data (parm->dflt->ctx, + parm->certdata, parm->certdatalen); + } + else + rc = default_inq_cb (parm->dflt, line); + + return rc; +} + + +/* Send a WRITECERT command to the SCdaemon. + * Used by: + * card-util.c + */ +int +agent_scd_writecert (const char *certidstr, + const unsigned char *certdata, size_t certdatalen) +{ + int rc; + char line[ASSUAN_LINELENGTH]; + struct writecert_parm_s parms; + struct default_inq_parm_s dfltparm; + + memset (&dfltparm, 0, sizeof dfltparm); + + rc = start_agent (NULL, 1); + if (rc) + return rc; + + memset (&parms, 0, sizeof parms); + + snprintf (line, DIM(line), "SCD WRITECERT %s", certidstr); + dfltparm.ctx = agent_ctx; + parms.dflt = &dfltparm; + parms.certdata = certdata; + parms.certdatalen = certdatalen; + + rc = assuan_transact (agent_ctx, line, NULL, NULL, + inq_writecert_parms, &parms, NULL, NULL); + + return rc; +} + + + +/* Status callback for the SCD GENKEY command. */ +static gpg_error_t +scd_genkey_cb (void *opaque, const char *line) +{ + u32 *createtime = opaque; + const char *keyword = line; + int keywordlen; + + for (keywordlen=0; *line && !spacep (line); line++, keywordlen++) + ; + while (spacep (line)) + line++; + + if (keywordlen == 14 && !memcmp (keyword,"KEY-CREATED-AT", keywordlen)) + { + *createtime = (u32)strtoul (line, NULL, 10); + } + else if (keywordlen == 8 && !memcmp (keyword, "PROGRESS", keywordlen)) + { + write_status_text (STATUS_PROGRESS, line); + } + + return 0; +} + +/* Send a GENKEY command to the SCdaemon. If *CREATETIME is not 0, + * the value will be passed to SCDAEMON with --timestamp option so that + * the key is created with this. Otherwise, timestamp was generated by + * SCDEAMON. On success, creation time is stored back to + * CREATETIME. + * Used by: + * gen_card_key + */ +int +agent_scd_genkey (int keyno, int force, u32 *createtime) +{ + int rc; + char line[ASSUAN_LINELENGTH]; + gnupg_isotime_t tbuf; + struct default_inq_parm_s dfltparm; + + memset (&dfltparm, 0, sizeof dfltparm); + + rc = start_agent (NULL, 1); + if (rc) + return rc; + + if (*createtime) + epoch2isotime (tbuf, *createtime); + else + *tbuf = 0; + + snprintf (line, DIM(line), "SCD GENKEY %s%s %s %d", + *tbuf? "--timestamp=":"", tbuf, + force? "--force":"", + keyno); + + dfltparm.ctx = agent_ctx; + rc = assuan_transact (agent_ctx, line, + NULL, NULL, default_inq_cb, &dfltparm, + scd_genkey_cb, createtime); + + status_sc_op_failure (rc); + return rc; +} + + + +/* Return the serial number of the card or an appropriate error. The + * serial number is returned as a hexstring. With DEMAND the active + * card is switched to the card with that serialno. + * Used by: + * card-util.c + * build_sk_list + * enum_secret_keys + */ +int +agent_scd_serialno (char **r_serialno, const char *demand) +{ + int err; + char *serialno = NULL; + char line[ASSUAN_LINELENGTH]; + + err = start_agent (NULL, (1 | FLAG_FOR_CARD_SUPPRESS_ERRORS)); + if (err) + return err; + + if (!demand) + strcpy (line, "SCD SERIALNO"); + else + snprintf (line, DIM(line), "SCD SERIALNO --demand=%s", demand); + + err = assuan_transact (agent_ctx, line, + NULL, NULL, NULL, NULL, + get_serialno_cb, &serialno); + if (err) + { + xfree (serialno); + return err; + } + + *r_serialno = serialno; + return 0; +} + + + +/* Send a READCERT command to the SCdaemon. + * Used by: + * card-util.c + */ +int +agent_scd_readcert (const char *certidstr, + void **r_buf, size_t *r_buflen) +{ + int rc; + char line[ASSUAN_LINELENGTH]; + membuf_t data; + size_t len; + struct default_inq_parm_s dfltparm; + + memset (&dfltparm, 0, sizeof dfltparm); + + *r_buf = NULL; + rc = start_agent (NULL, 1); + if (rc) + return rc; + + dfltparm.ctx = agent_ctx; + + init_membuf (&data, 2048); + + snprintf (line, DIM(line), "SCD READCERT %s", certidstr); + rc = assuan_transact (agent_ctx, line, + put_membuf_cb, &data, + default_inq_cb, &dfltparm, + NULL, NULL); + if (rc) + { + xfree (get_membuf (&data, &len)); + return rc; + } + *r_buf = get_membuf (&data, r_buflen); + if (!*r_buf) + return gpg_error (GPG_ERR_ENOMEM); + + return 0; +} + + +/* This is a variant of agent_readkey which sends a READKEY command + * directly Scdaemon. On success a new s-expression is stored at + * R_RESULT. */ +gpg_error_t +agent_scd_readkey (const char *keyrefstr, gcry_sexp_t *r_result) +{ + gpg_error_t err; + char line[ASSUAN_LINELENGTH]; + membuf_t data; + unsigned char *buf; + size_t len, buflen; + struct default_inq_parm_s dfltparm; + + memset (&dfltparm, 0, sizeof dfltparm); + dfltparm.ctx = agent_ctx; + + *r_result = NULL; + err = start_agent (NULL, 1); + if (err) + return err; + + init_membuf (&data, 1024); + snprintf (line, DIM(line), "SCD READKEY %s", keyrefstr); + err = assuan_transact (agent_ctx, line, + put_membuf_cb, &data, + default_inq_cb, &dfltparm, + NULL, NULL); + if (err) + { + xfree (get_membuf (&data, &len)); + return err; + } + buf = get_membuf (&data, &buflen); + if (!buf) + return gpg_error_from_syserror (); + + err = gcry_sexp_new (r_result, buf, buflen, 0); + xfree (buf); + + return err; +} + + + +struct card_cardlist_parm_s { + int error; + strlist_t list; +}; + + +/* Callback function for agent_card_cardlist. */ +static gpg_error_t +card_cardlist_cb (void *opaque, const char *line) +{ + struct card_cardlist_parm_s *parm = opaque; + const char *keyword = line; + int keywordlen; + + for (keywordlen=0; *line && !spacep (line); line++, keywordlen++) + ; + while (spacep (line)) + line++; + + if (keywordlen == 8 && !memcmp (keyword, "SERIALNO", keywordlen)) + { + const char *s; + int n; + + for (n=0,s=line; hexdigitp (s); s++, n++) + ; + + if (!n || (n&1) || *s) + parm->error = gpg_error (GPG_ERR_ASS_PARAMETER); + else + add_to_strlist (&parm->list, line); + } + + return 0; +} + + +/* Return a list of currently available cards. + * Used by: + * card-util.c + * skclist.c + */ +int +agent_scd_cardlist (strlist_t *result) +{ + int err; + char line[ASSUAN_LINELENGTH]; + struct card_cardlist_parm_s parm; + + memset (&parm, 0, sizeof parm); + *result = NULL; + err = start_agent (NULL, 1); + if (err) + return err; + + strcpy (line, "SCD GETINFO card_list"); + + err = assuan_transact (agent_ctx, line, + NULL, NULL, NULL, NULL, + card_cardlist_cb, &parm); + if (!err && parm.error) + err = parm.error; + + if (!err) + *result = parm.list; + else + free_strlist (parm.list); + + return 0; +} + + + +/* Change the PIN of an OpenPGP card or reset the retry counter. + * CHVNO 1: Change the PIN + * 2: For v1 cards: Same as 1. + * For v2 cards: Reset the PIN using the Reset Code. + * 3: Change the admin PIN + * 101: Set a new PIN and reset the retry counter + * 102: For v1 cars: Same as 101. + * For v2 cards: Set a new Reset Code. + * SERIALNO is not used. + * Used by: + * card-util.c + */ +int +agent_scd_change_pin (int chvno, const char *serialno) +{ + int rc; + char line[ASSUAN_LINELENGTH]; + const char *reset = ""; + struct default_inq_parm_s dfltparm; + + memset (&dfltparm, 0, sizeof dfltparm); + + (void)serialno; + + if (chvno >= 100) + reset = "--reset"; + chvno %= 100; + + rc = start_agent (NULL, 1); + if (rc) + return rc; + dfltparm.ctx = agent_ctx; + + snprintf (line, DIM(line), "SCD PASSWD %s %d", reset, chvno); + rc = assuan_transact (agent_ctx, line, + NULL, NULL, + default_inq_cb, &dfltparm, + NULL, NULL); + status_sc_op_failure (rc); + return rc; +} + + +/* Perform a CHECKPIN operation. SERIALNO should be the serial + * number of the card - optionally followed by the fingerprint; + * however the fingerprint is ignored here. + * Used by: + * card-util.c + */ +int +agent_scd_checkpin (const char *serialno) +{ + int rc; + char line[ASSUAN_LINELENGTH]; + struct default_inq_parm_s dfltparm; + + memset (&dfltparm, 0, sizeof dfltparm); + + rc = start_agent (NULL, 1); + if (rc) + return rc; + dfltparm.ctx = agent_ctx; + + snprintf (line, DIM(line), "SCD CHECKPIN %s", serialno); + rc = assuan_transact (agent_ctx, line, + NULL, NULL, + default_inq_cb, &dfltparm, + NULL, NULL); + status_sc_op_failure (rc); + return rc; +} + + + +/* Note: All strings shall be UTF-8. On success the caller needs to + free the string stored at R_PASSPHRASE. On error NULL will be + stored at R_PASSPHRASE and an appropriate error code returned. + Only called from passphrase.c:passphrase_get - see there for more + comments on this ugly API. */ +gpg_error_t +agent_get_passphrase (const char *cache_id, + const char *err_msg, + const char *prompt, + const char *desc_msg, + int newsymkey, + int repeat, + int check, + char **r_passphrase) +{ + int rc; + char line[ASSUAN_LINELENGTH]; + char *arg1 = NULL; + char *arg2 = NULL; + char *arg3 = NULL; + char *arg4 = NULL; + membuf_t data; + struct default_inq_parm_s dfltparm; + int have_newsymkey; + + memset (&dfltparm, 0, sizeof dfltparm); + + *r_passphrase = NULL; + + rc = start_agent (NULL, 0); + if (rc) + return rc; + dfltparm.ctx = agent_ctx; + + /* Check that the gpg-agent understands the repeat option. */ + if (assuan_transact (agent_ctx, + "GETINFO cmd_has_option GET_PASSPHRASE repeat", + NULL, NULL, NULL, NULL, NULL, NULL)) + return gpg_error (GPG_ERR_NOT_SUPPORTED); + have_newsymkey = !(assuan_transact + (agent_ctx, + "GETINFO cmd_has_option GET_PASSPHRASE newsymkey", + NULL, NULL, NULL, NULL, NULL, NULL)); + + if (cache_id && *cache_id) + if (!(arg1 = percent_plus_escape (cache_id))) + goto no_mem; + if (err_msg && *err_msg) + if (!(arg2 = percent_plus_escape (err_msg))) + goto no_mem; + if (prompt && *prompt) + if (!(arg3 = percent_plus_escape (prompt))) + goto no_mem; + if (desc_msg && *desc_msg) + if (!(arg4 = percent_plus_escape (desc_msg))) + goto no_mem; + + /* CHECK && REPEAT or NEWSYMKEY is here an indication that a new + * passphrase for symmetric encryption is requested; if the agent + * supports this we enable the modern API by also passing --newsymkey. */ + snprintf (line, DIM(line), + "GET_PASSPHRASE --data --repeat=%d%s%s -- %s %s %s %s", + repeat, + ((repeat && check) || newsymkey)? " --check":"", + (have_newsymkey && newsymkey)? " --newsymkey":"", + arg1? arg1:"X", + arg2? arg2:"X", + arg3? arg3:"X", + arg4? arg4:"X"); + xfree (arg1); + xfree (arg2); + xfree (arg3); + xfree (arg4); + + init_membuf_secure (&data, 64); + rc = assuan_transact (agent_ctx, line, + put_membuf_cb, &data, + default_inq_cb, &dfltparm, + NULL, NULL); + + if (rc) + xfree (get_membuf (&data, NULL)); + else + { + put_membuf (&data, "", 1); + *r_passphrase = get_membuf (&data, NULL); + if (!*r_passphrase) + rc = gpg_error_from_syserror (); + } + return rc; + no_mem: + rc = gpg_error_from_syserror (); + xfree (arg1); + xfree (arg2); + xfree (arg3); + xfree (arg4); + return rc; +} + + +gpg_error_t +agent_clear_passphrase (const char *cache_id) +{ + int rc; + char line[ASSUAN_LINELENGTH]; + struct default_inq_parm_s dfltparm; + + memset (&dfltparm, 0, sizeof dfltparm); + + if (!cache_id || !*cache_id) + return 0; + + rc = start_agent (NULL, 0); + if (rc) + return rc; + dfltparm.ctx = agent_ctx; + + snprintf (line, DIM(line), "CLEAR_PASSPHRASE %s", cache_id); + return assuan_transact (agent_ctx, line, + NULL, NULL, + default_inq_cb, &dfltparm, + NULL, NULL); +} + + +/* Ask the agent to pop up a confirmation dialog with the text DESC + and an okay and cancel button. */ +gpg_error_t +gpg_agent_get_confirmation (const char *desc) +{ + int rc; + char *tmp; + char line[ASSUAN_LINELENGTH]; + struct default_inq_parm_s dfltparm; + + memset (&dfltparm, 0, sizeof dfltparm); + + rc = start_agent (NULL, 0); + if (rc) + return rc; + dfltparm.ctx = agent_ctx; + + tmp = percent_plus_escape (desc); + if (!tmp) + return gpg_error_from_syserror (); + snprintf (line, DIM(line), "GET_CONFIRMATION %s", tmp); + xfree (tmp); + + rc = assuan_transact (agent_ctx, line, + NULL, NULL, + default_inq_cb, &dfltparm, + NULL, NULL); + return rc; +} + + +/* Return the S2K iteration count as computed by gpg-agent. */ +gpg_error_t +agent_get_s2k_count (unsigned long *r_count) +{ + gpg_error_t err; + membuf_t data; + char *buf; + + *r_count = 0; + + err = start_agent (NULL, 0); + if (err) + return err; + + init_membuf (&data, 32); + err = assuan_transact (agent_ctx, "GETINFO s2k_count", + put_membuf_cb, &data, + NULL, NULL, NULL, NULL); + if (err) + xfree (get_membuf (&data, NULL)); + else + { + put_membuf (&data, "", 1); + buf = get_membuf (&data, NULL); + if (!buf) + err = gpg_error_from_syserror (); + else + { + *r_count = strtoul (buf, NULL, 10); + xfree (buf); + } + } + return err; +} + + + +/* Ask the agent whether a secret key for the given public key is + available. Returns 0 if available. */ +gpg_error_t +agent_probe_secret_key (ctrl_t ctrl, PKT_public_key *pk) +{ + gpg_error_t err; + char line[ASSUAN_LINELENGTH]; + char *hexgrip; + + err = start_agent (ctrl, 0); + if (err) + return err; + + err = hexkeygrip_from_pk (pk, &hexgrip); + if (err) + return err; + + snprintf (line, sizeof line, "HAVEKEY %s", hexgrip); + xfree (hexgrip); + + err = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); + return err; +} + +/* Ask the agent whether a secret key is available for any of the + keys (primary or sub) in KEYBLOCK. Returns 0 if available. */ +gpg_error_t +agent_probe_any_secret_key (ctrl_t ctrl, kbnode_t keyblock) +{ + gpg_error_t err; + char line[ASSUAN_LINELENGTH]; + char *p; + kbnode_t kbctx, node; + int nkeys; + unsigned char grip[20]; + + err = start_agent (ctrl, 0); + if (err) + return err; + + err = gpg_error (GPG_ERR_NO_SECKEY); /* Just in case no key was + found in KEYBLOCK. */ + p = stpcpy (line, "HAVEKEY"); + for (kbctx=NULL, nkeys=0; (node = walk_kbnode (keyblock, &kbctx, 0)); ) + if (node->pkt->pkttype == PKT_PUBLIC_KEY + || node->pkt->pkttype == PKT_PUBLIC_SUBKEY + || node->pkt->pkttype == PKT_SECRET_KEY + || node->pkt->pkttype == PKT_SECRET_SUBKEY) + { + if (nkeys && ((p - line) + 41) > (ASSUAN_LINELENGTH - 2)) + { + err = assuan_transact (agent_ctx, line, + NULL, NULL, NULL, NULL, NULL, NULL); + if (err != gpg_err_code (GPG_ERR_NO_SECKEY)) + break; /* Seckey available or unexpected error - ready. */ + p = stpcpy (line, "HAVEKEY"); + nkeys = 0; + } + + err = keygrip_from_pk (node->pkt->pkt.public_key, grip); + if (err) + return err; + *p++ = ' '; + bin2hex (grip, 20, p); + p += 40; + nkeys++; + } + + if (!err && nkeys) + err = assuan_transact (agent_ctx, line, + NULL, NULL, NULL, NULL, NULL, NULL); + + return err; +} + + + +struct keyinfo_data_parm_s +{ + char *serialno; + int cleartext; +}; + + +static gpg_error_t +keyinfo_status_cb (void *opaque, const char *line) +{ + struct keyinfo_data_parm_s *data = opaque; + int is_smartcard; + char *s; + + if ((s = has_leading_keyword (line, "KEYINFO")) && data) + { + /* Parse the arguments: + * 0 1 2 3 4 5 + * + */ + char *fields[6]; + + if (split_fields (s, fields, DIM (fields)) == 6) + { + is_smartcard = (fields[1][0] == 'T'); + if (is_smartcard && !data->serialno && strcmp (fields[2], "-")) + data->serialno = xtrystrdup (fields[2]); + /* 'P' for protected, 'C' for clear */ + data->cleartext = (fields[5][0] == 'C'); + } + } + return 0; +} + + +/* Return the serial number for a secret key. If the returned serial + number is NULL, the key is not stored on a smartcard. Caller needs + to free R_SERIALNO. + + if r_cleartext is not NULL, the referenced int will be set to 1 if + the agent's copy of the key is stored in the clear, or 0 otherwise +*/ +gpg_error_t +agent_get_keyinfo (ctrl_t ctrl, const char *hexkeygrip, + char **r_serialno, int *r_cleartext) +{ + gpg_error_t err; + char line[ASSUAN_LINELENGTH]; + struct keyinfo_data_parm_s keyinfo; + + memset (&keyinfo, 0,sizeof keyinfo); + + *r_serialno = NULL; + + err = start_agent (ctrl, 0); + if (err) + return err; + + if (!hexkeygrip || strlen (hexkeygrip) != 40) + return gpg_error (GPG_ERR_INV_VALUE); + + snprintf (line, DIM(line), "KEYINFO %s", hexkeygrip); + + err = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, + keyinfo_status_cb, &keyinfo); + if (!err && keyinfo.serialno) + { + /* Sanity check for bad characters. */ + if (strpbrk (keyinfo.serialno, ":\n\r")) + err = GPG_ERR_INV_VALUE; + } + if (err) + xfree (keyinfo.serialno); + else + { + *r_serialno = keyinfo.serialno; + if (r_cleartext) + *r_cleartext = keyinfo.cleartext; + } + return err; +} + + +/* Status callback for agent_import_key, agent_export_key and + agent_genkey. */ +static gpg_error_t +cache_nonce_status_cb (void *opaque, const char *line) +{ + struct cache_nonce_parm_s *parm = opaque; + const char *s; + + if ((s = has_leading_keyword (line, "CACHE_NONCE"))) + { + if (parm->cache_nonce_addr) + { + xfree (*parm->cache_nonce_addr); + *parm->cache_nonce_addr = xtrystrdup (s); + } + } + else if ((s = has_leading_keyword (line, "PASSWD_NONCE"))) + { + if (parm->passwd_nonce_addr) + { + xfree (*parm->passwd_nonce_addr); + *parm->passwd_nonce_addr = xtrystrdup (s); + } + } + else if ((s = has_leading_keyword (line, "PROGRESS"))) + { + if (opt.enable_progress_filter) + write_status_text (STATUS_PROGRESS, s); + } + + return 0; +} + + + +/* Handle a KEYPARMS inquiry. Note, we only send the data, + assuan_transact takes care of flushing and writing the end */ +static gpg_error_t +inq_genkey_parms (void *opaque, const char *line) +{ + struct genkey_parm_s *parm = opaque; + gpg_error_t err; + + if (has_leading_keyword (line, "KEYPARAM")) + { + err = assuan_send_data (parm->dflt->ctx, + parm->keyparms, strlen (parm->keyparms)); + } + else if (has_leading_keyword (line, "NEWPASSWD") && parm->passphrase) + { + err = assuan_send_data (parm->dflt->ctx, + parm->passphrase, strlen (parm->passphrase)); + } + else + err = default_inq_cb (parm->dflt, line); + + return err; +} + + +/* Call the agent to generate a new key. KEYPARMS is the usual + S-expression giving the parameters of the key. gpg-agent passes it + gcry_pk_genkey. If NO_PROTECTION is true the agent is advised not + to protect the generated key. If NO_PROTECTION is not set and + PASSPHRASE is not NULL the agent is requested to protect the key + with that passphrase instead of asking for one. TIMESTAMP is the + creation time of the key or zero. */ +gpg_error_t +agent_genkey (ctrl_t ctrl, char **cache_nonce_addr, char **passwd_nonce_addr, + const char *keyparms, int no_protection, + const char *passphrase, time_t timestamp, gcry_sexp_t *r_pubkey) +{ + gpg_error_t err; + struct genkey_parm_s gk_parm; + struct cache_nonce_parm_s cn_parm; + struct default_inq_parm_s dfltparm; + membuf_t data; + size_t len; + unsigned char *buf; + char timestamparg[16 + 16]; /* The 2nd 16 is sizeof(gnupg_isotime_t) */ + char line[ASSUAN_LINELENGTH]; + + memset (&dfltparm, 0, sizeof dfltparm); + dfltparm.ctrl = ctrl; + + *r_pubkey = NULL; + err = start_agent (ctrl, 0); + if (err) + return err; + dfltparm.ctx = agent_ctx; + + if (timestamp) + { + strcpy (timestamparg, " --timestamp="); + epoch2isotime (timestamparg+13, timestamp); + } + else + *timestamparg = 0; + + if (passwd_nonce_addr && *passwd_nonce_addr) + ; /* A RESET would flush the passwd nonce cache. */ + else + { + err = assuan_transact (agent_ctx, "RESET", + NULL, NULL, NULL, NULL, NULL, NULL); + if (err) + return err; + } + + init_membuf (&data, 1024); + gk_parm.dflt = &dfltparm; + gk_parm.keyparms = keyparms; + gk_parm.passphrase = passphrase; + snprintf (line, sizeof line, "GENKEY%s%s%s%s%s%s", + *timestamparg? timestamparg : "", + no_protection? " --no-protection" : + passphrase ? " --inq-passwd" : + /* */ "", + passwd_nonce_addr && *passwd_nonce_addr? " --passwd-nonce=":"", + passwd_nonce_addr && *passwd_nonce_addr? *passwd_nonce_addr:"", + cache_nonce_addr && *cache_nonce_addr? " ":"", + cache_nonce_addr && *cache_nonce_addr? *cache_nonce_addr:""); + cn_parm.cache_nonce_addr = cache_nonce_addr; + cn_parm.passwd_nonce_addr = NULL; + err = assuan_transact (agent_ctx, line, + put_membuf_cb, &data, + inq_genkey_parms, &gk_parm, + cache_nonce_status_cb, &cn_parm); + if (err) + { + xfree (get_membuf (&data, &len)); + return err; + } + + buf = get_membuf (&data, &len); + if (!buf) + err = gpg_error_from_syserror (); + else + { + err = gcry_sexp_sscan (r_pubkey, NULL, buf, len); + xfree (buf); + } + return err; +} + + + +/* Call the agent to read the public key part for a given keygrip. If + FROMCARD is true, the key is directly read from the current + smartcard. In this case HEXKEYGRIP should be the keyID + (e.g. OPENPGP.3). */ +gpg_error_t +agent_readkey (ctrl_t ctrl, int fromcard, const char *hexkeygrip, + unsigned char **r_pubkey) +{ + gpg_error_t err; + membuf_t data; + size_t len; + unsigned char *buf; + char line[ASSUAN_LINELENGTH]; + struct default_inq_parm_s dfltparm; + + memset (&dfltparm, 0, sizeof dfltparm); + dfltparm.ctrl = ctrl; + + *r_pubkey = NULL; + err = start_agent (ctrl, 0); + if (err) + return err; + dfltparm.ctx = agent_ctx; + + err = assuan_transact (agent_ctx, "RESET",NULL, NULL, NULL, NULL, NULL, NULL); + if (err) + return err; + + snprintf (line, DIM(line), "READKEY %s%s", fromcard? "--card ":"", + hexkeygrip); + + init_membuf (&data, 1024); + err = assuan_transact (agent_ctx, line, + put_membuf_cb, &data, + default_inq_cb, &dfltparm, + NULL, NULL); + if (err) + { + xfree (get_membuf (&data, &len)); + return err; + } + buf = get_membuf (&data, &len); + if (!buf) + return gpg_error_from_syserror (); + if (!gcry_sexp_canon_len (buf, len, NULL, NULL)) + { + xfree (buf); + return gpg_error (GPG_ERR_INV_SEXP); + } + *r_pubkey = buf; + return 0; +} + + + +/* Call the agent to do a sign operation using the key identified by + the hex string KEYGRIP. DESC is a description of the key to be + displayed if the agent needs to ask for the PIN. DIGEST and + DIGESTLEN is the hash value to sign and DIGESTALGO the algorithm id + used to compute the digest. If CACHE_NONCE is used the agent is + advised to first try a passphrase associated with that nonce. */ +gpg_error_t +agent_pksign (ctrl_t ctrl, const char *cache_nonce, + const char *keygrip, const char *desc, + u32 *keyid, u32 *mainkeyid, int pubkey_algo, + unsigned char *digest, size_t digestlen, int digestalgo, + gcry_sexp_t *r_sigval) +{ + gpg_error_t err; + char line[ASSUAN_LINELENGTH]; + membuf_t data; + struct default_inq_parm_s dfltparm; + + memset (&dfltparm, 0, sizeof dfltparm); + dfltparm.ctrl = ctrl; + dfltparm.keyinfo.keyid = keyid; + dfltparm.keyinfo.mainkeyid = mainkeyid; + dfltparm.keyinfo.pubkey_algo = pubkey_algo; + + *r_sigval = NULL; + err = start_agent (ctrl, 0); + if (err) + return err; + dfltparm.ctx = agent_ctx; + + if (digestlen*2 + 50 > DIM(line)) + return gpg_error (GPG_ERR_GENERAL); + + err = assuan_transact (agent_ctx, "RESET", + NULL, NULL, NULL, NULL, NULL, NULL); + if (err) + return err; + + snprintf (line, DIM(line), "SIGKEY %s", keygrip); + err = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); + if (err) + return err; + + if (desc) + { + snprintf (line, DIM(line), "SETKEYDESC %s", desc); + err = assuan_transact (agent_ctx, line, + NULL, NULL, NULL, NULL, NULL, NULL); + if (err) + return err; + } + + snprintf (line, sizeof line, "SETHASH %d ", digestalgo); + bin2hex (digest, digestlen, line + strlen (line)); + err = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); + if (err) + return err; + + init_membuf (&data, 1024); + + snprintf (line, sizeof line, "PKSIGN%s%s", + cache_nonce? " -- ":"", + cache_nonce? cache_nonce:""); + err = assuan_transact (agent_ctx, line, + put_membuf_cb, &data, + default_inq_cb, &dfltparm, + NULL, NULL); + if (err) + xfree (get_membuf (&data, NULL)); + else + { + unsigned char *buf; + size_t len; + + buf = get_membuf (&data, &len); + if (!buf) + err = gpg_error_from_syserror (); + else + { + err = gcry_sexp_sscan (r_sigval, NULL, buf, len); + xfree (buf); + } + } + return err; +} + + + +/* Handle a CIPHERTEXT inquiry. Note, we only send the data, + assuan_transact takes care of flushing and writing the END. */ +static gpg_error_t +inq_ciphertext_cb (void *opaque, const char *line) +{ + struct cipher_parm_s *parm = opaque; + int rc; + + if (has_leading_keyword (line, "CIPHERTEXT")) + { + assuan_begin_confidential (parm->ctx); + rc = assuan_send_data (parm->dflt->ctx, + parm->ciphertext, parm->ciphertextlen); + assuan_end_confidential (parm->ctx); + } + else + rc = default_inq_cb (parm->dflt, line); + + return rc; +} + + +/* Check whether there is any padding info from the agent. */ +static gpg_error_t +padding_info_cb (void *opaque, const char *line) +{ + int *r_padding = opaque; + const char *s; + + if ((s=has_leading_keyword (line, "PADDING"))) + { + *r_padding = atoi (s); + } + + return 0; +} + + +/* Call the agent to do a decrypt operation using the key identified + by the hex string KEYGRIP and the input data S_CIPHERTEXT. On the + success the decoded value is stored verbatim at R_BUF and its + length at R_BUF; the callers needs to release it. KEYID, MAINKEYID + and PUBKEY_ALGO are used to construct additional promots or status + messages. The padding information is stored at R_PADDING with -1 + for not known. */ +gpg_error_t +agent_pkdecrypt (ctrl_t ctrl, const char *keygrip, const char *desc, + u32 *keyid, u32 *mainkeyid, int pubkey_algo, + gcry_sexp_t s_ciphertext, + unsigned char **r_buf, size_t *r_buflen, int *r_padding) +{ + gpg_error_t err; + char line[ASSUAN_LINELENGTH]; + membuf_t data; + size_t n, len; + char *p, *buf, *endp; + struct default_inq_parm_s dfltparm; + + memset (&dfltparm, 0, sizeof dfltparm); + dfltparm.ctrl = ctrl; + dfltparm.keyinfo.keyid = keyid; + dfltparm.keyinfo.mainkeyid = mainkeyid; + dfltparm.keyinfo.pubkey_algo = pubkey_algo; + + if (!keygrip || strlen(keygrip) != 40 + || !s_ciphertext || !r_buf || !r_buflen || !r_padding) + return gpg_error (GPG_ERR_INV_VALUE); + + *r_buf = NULL; + *r_padding = -1; + + err = start_agent (ctrl, 0); + if (err) + return err; + dfltparm.ctx = agent_ctx; + + err = assuan_transact (agent_ctx, "RESET", + NULL, NULL, NULL, NULL, NULL, NULL); + if (err) + return err; + + snprintf (line, sizeof line, "SETKEY %s", keygrip); + err = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); + if (err) + return err; + + if (desc) + { + snprintf (line, DIM(line), "SETKEYDESC %s", desc); + err = assuan_transact (agent_ctx, line, + NULL, NULL, NULL, NULL, NULL, NULL); + if (err) + return err; + } + + init_membuf_secure (&data, 1024); + { + struct cipher_parm_s parm; + + parm.dflt = &dfltparm; + parm.ctx = agent_ctx; + err = make_canon_sexp (s_ciphertext, &parm.ciphertext, &parm.ciphertextlen); + if (err) + return err; + err = assuan_transact (agent_ctx, "PKDECRYPT", + put_membuf_cb, &data, + inq_ciphertext_cb, &parm, + padding_info_cb, r_padding); + xfree (parm.ciphertext); + } + if (err) + { + xfree (get_membuf (&data, &len)); + return err; + } + + buf = get_membuf (&data, &len); + if (!buf) + return gpg_error_from_syserror (); + + if (len == 0 || *buf != '(') + { + xfree (buf); + return gpg_error (GPG_ERR_INV_SEXP); + } + + if (len < 12 || memcmp (buf, "(5:value", 8) ) /* "(5:valueN:D)" */ + { + xfree (buf); + return gpg_error (GPG_ERR_INV_SEXP); + } + while (buf[len-1] == 0) + len--; + if (buf[len-1] != ')') + return gpg_error (GPG_ERR_INV_SEXP); + len--; /* Drop the final close-paren. */ + p = buf + 8; /* Skip leading parenthesis and the value tag. */ + len -= 8; /* Count only the data of the second part. */ + + n = strtoul (p, &endp, 10); + if (!n || *endp != ':') + { + xfree (buf); + return gpg_error (GPG_ERR_INV_SEXP); + } + endp++; + if (endp-p+n > len) + { + xfree (buf); + return gpg_error (GPG_ERR_INV_SEXP); /* Oops: Inconsistent S-Exp. */ + } + + memmove (buf, endp, n); + + *r_buflen = n; + *r_buf = buf; + return 0; +} + + + +/* Retrieve a key encryption key from the agent. With FOREXPORT true + the key shall be used for export, with false for import. On success + the new key is stored at R_KEY and its length at R_KEKLEN. */ +gpg_error_t +agent_keywrap_key (ctrl_t ctrl, int forexport, void **r_kek, size_t *r_keklen) +{ + gpg_error_t err; + membuf_t data; + size_t len; + unsigned char *buf; + char line[ASSUAN_LINELENGTH]; + struct default_inq_parm_s dfltparm; + + memset (&dfltparm, 0, sizeof dfltparm); + dfltparm.ctrl = ctrl; + + *r_kek = NULL; + err = start_agent (ctrl, 0); + if (err) + return err; + dfltparm.ctx = agent_ctx; + + snprintf (line, DIM(line), "KEYWRAP_KEY %s", + forexport? "--export":"--import"); + + init_membuf_secure (&data, 64); + err = assuan_transact (agent_ctx, line, + put_membuf_cb, &data, + default_inq_cb, &dfltparm, + NULL, NULL); + if (err) + { + xfree (get_membuf (&data, &len)); + return err; + } + buf = get_membuf (&data, &len); + if (!buf) + return gpg_error_from_syserror (); + *r_kek = buf; + *r_keklen = len; + return 0; +} + + + +/* Handle the inquiry for an IMPORT_KEY command. */ +static gpg_error_t +inq_import_key_parms (void *opaque, const char *line) +{ + struct import_key_parm_s *parm = opaque; + gpg_error_t err; + + if (has_leading_keyword (line, "KEYDATA")) + { + err = assuan_send_data (parm->dflt->ctx, parm->key, parm->keylen); + } + else + err = default_inq_cb (parm->dflt, line); + + return err; +} + + +/* Call the agent to import a key into the agent. */ +gpg_error_t +agent_import_key (ctrl_t ctrl, const char *desc, char **cache_nonce_addr, + const void *key, size_t keylen, int unattended, int force, + u32 *keyid, u32 *mainkeyid, int pubkey_algo, u32 timestamp) +{ + gpg_error_t err; + struct import_key_parm_s parm; + struct cache_nonce_parm_s cn_parm; + char timestamparg[16 + 16]; /* The 2nd 16 is sizeof(gnupg_isotime_t) */ + char line[ASSUAN_LINELENGTH]; + struct default_inq_parm_s dfltparm; + + memset (&dfltparm, 0, sizeof dfltparm); + dfltparm.ctrl = ctrl; + dfltparm.keyinfo.keyid = keyid; + dfltparm.keyinfo.mainkeyid = mainkeyid; + dfltparm.keyinfo.pubkey_algo = pubkey_algo; + + err = start_agent (ctrl, 0); + if (err) + return err; + dfltparm.ctx = agent_ctx; + + if (timestamp) + { + strcpy (timestamparg, " --timestamp="); + epoch2isotime (timestamparg+13, timestamp); + } + else + *timestamparg = 0; + + if (desc) + { + snprintf (line, DIM(line), "SETKEYDESC %s", desc); + err = assuan_transact (agent_ctx, line, + NULL, NULL, NULL, NULL, NULL, NULL); + if (err) + return err; + } + + parm.dflt = &dfltparm; + parm.key = key; + parm.keylen = keylen; + + snprintf (line, sizeof line, "IMPORT_KEY%s%s%s%s%s", + *timestamparg? timestamparg : "", + unattended? " --unattended":"", + force? " --force":"", + cache_nonce_addr && *cache_nonce_addr? " ":"", + cache_nonce_addr && *cache_nonce_addr? *cache_nonce_addr:""); + cn_parm.cache_nonce_addr = cache_nonce_addr; + cn_parm.passwd_nonce_addr = NULL; + err = assuan_transact (agent_ctx, line, + NULL, NULL, + inq_import_key_parms, &parm, + cache_nonce_status_cb, &cn_parm); + return err; +} + + + +/* Receive a secret key from the agent. HEXKEYGRIP is the hexified + keygrip, DESC a prompt to be displayed with the agent's passphrase + question (needs to be plus+percent escaped). if OPENPGP_PROTECTED + is not zero, ensure that the key material is returned in RFC + 4880-compatible passphrased-protected form. If CACHE_NONCE_ADDR is + not NULL the agent is advised to first try a passphrase associated + with that nonce. On success the key is stored as a canonical + S-expression at R_RESULT and R_RESULTLEN. */ +gpg_error_t +agent_export_key (ctrl_t ctrl, const char *hexkeygrip, const char *desc, + int openpgp_protected, char **cache_nonce_addr, + unsigned char **r_result, size_t *r_resultlen, + u32 *keyid, u32 *mainkeyid, int pubkey_algo) +{ + gpg_error_t err; + struct cache_nonce_parm_s cn_parm; + membuf_t data; + size_t len; + unsigned char *buf; + char line[ASSUAN_LINELENGTH]; + struct default_inq_parm_s dfltparm; + + memset (&dfltparm, 0, sizeof dfltparm); + dfltparm.ctrl = ctrl; + dfltparm.keyinfo.keyid = keyid; + dfltparm.keyinfo.mainkeyid = mainkeyid; + dfltparm.keyinfo.pubkey_algo = pubkey_algo; + + *r_result = NULL; + + err = start_agent (ctrl, 0); + if (err) + return err; + dfltparm.ctx = agent_ctx; + + if (desc) + { + snprintf (line, DIM(line), "SETKEYDESC %s", desc); + err = assuan_transact (agent_ctx, line, + NULL, NULL, NULL, NULL, NULL, NULL); + if (err) + return err; + } + + snprintf (line, DIM(line), "EXPORT_KEY %s%s%s %s", + openpgp_protected ? "--openpgp ":"", + cache_nonce_addr && *cache_nonce_addr? "--cache-nonce=":"", + cache_nonce_addr && *cache_nonce_addr? *cache_nonce_addr:"", + hexkeygrip); + + init_membuf_secure (&data, 1024); + cn_parm.cache_nonce_addr = cache_nonce_addr; + cn_parm.passwd_nonce_addr = NULL; + err = assuan_transact (agent_ctx, line, + put_membuf_cb, &data, + default_inq_cb, &dfltparm, + cache_nonce_status_cb, &cn_parm); + if (err) + { + xfree (get_membuf (&data, &len)); + return err; + } + buf = get_membuf (&data, &len); + if (!buf) + return gpg_error_from_syserror (); + *r_result = buf; + *r_resultlen = len; + return 0; +} + + + +/* Ask the agent to delete the key identified by HEXKEYGRIP. If DESC + is not NULL, display DESC instead of the default description + message. If FORCE is true the agent is advised not to ask for + confirmation. */ +gpg_error_t +agent_delete_key (ctrl_t ctrl, const char *hexkeygrip, const char *desc, + int force) +{ + gpg_error_t err; + char line[ASSUAN_LINELENGTH]; + struct default_inq_parm_s dfltparm; + + memset (&dfltparm, 0, sizeof dfltparm); + dfltparm.ctrl = ctrl; + + err = start_agent (ctrl, 0); + if (err) + return err; + + if (!hexkeygrip || strlen (hexkeygrip) != 40) + return gpg_error (GPG_ERR_INV_VALUE); + + if (desc) + { + snprintf (line, DIM(line), "SETKEYDESC %s", desc); + err = assuan_transact (agent_ctx, line, + NULL, NULL, NULL, NULL, NULL, NULL); + if (err) + return err; + } + + snprintf (line, DIM(line), "DELETE_KEY%s %s", + force? " --force":"", hexkeygrip); + err = assuan_transact (agent_ctx, line, NULL, NULL, + default_inq_cb, &dfltparm, + NULL, NULL); + return err; +} + + + +/* Ask the agent to change the passphrase of the key identified by + * HEXKEYGRIP. If DESC is not NULL, display DESC instead of the + * default description message. If CACHE_NONCE_ADDR is not NULL the + * agent is advised to first try a passphrase associated with that + * nonce. If PASSWD_NONCE_ADDR is not NULL the agent will try to use + * the passphrase associated with that nonce for the new passphrase. + * If VERIFY is true the passphrase is only verified. */ +gpg_error_t +agent_passwd (ctrl_t ctrl, const char *hexkeygrip, const char *desc, int verify, + char **cache_nonce_addr, char **passwd_nonce_addr) +{ + gpg_error_t err; + struct cache_nonce_parm_s cn_parm; + char line[ASSUAN_LINELENGTH]; + struct default_inq_parm_s dfltparm; + + memset (&dfltparm, 0, sizeof dfltparm); + dfltparm.ctrl = ctrl; + + err = start_agent (ctrl, 0); + if (err) + return err; + dfltparm.ctx = agent_ctx; + + if (!hexkeygrip || strlen (hexkeygrip) != 40) + return gpg_error (GPG_ERR_INV_VALUE); + + if (desc) + { + snprintf (line, DIM(line), "SETKEYDESC %s", desc); + err = assuan_transact (agent_ctx, line, + NULL, NULL, NULL, NULL, NULL, NULL); + if (err) + return err; + } + + if (verify) + snprintf (line, DIM(line), "PASSWD %s%s --verify %s", + cache_nonce_addr && *cache_nonce_addr? "--cache-nonce=":"", + cache_nonce_addr && *cache_nonce_addr? *cache_nonce_addr:"", + hexkeygrip); + else + snprintf (line, DIM(line), "PASSWD %s%s %s%s %s", + cache_nonce_addr && *cache_nonce_addr? "--cache-nonce=":"", + cache_nonce_addr && *cache_nonce_addr? *cache_nonce_addr:"", + passwd_nonce_addr && *passwd_nonce_addr? "--passwd-nonce=":"", + passwd_nonce_addr && *passwd_nonce_addr? *passwd_nonce_addr:"", + hexkeygrip); + cn_parm.cache_nonce_addr = cache_nonce_addr; + cn_parm.passwd_nonce_addr = passwd_nonce_addr; + err = assuan_transact (agent_ctx, line, NULL, NULL, + default_inq_cb, &dfltparm, + cache_nonce_status_cb, &cn_parm); + return err; +} + + +/* Return the version reported by gpg-agent. */ +gpg_error_t +agent_get_version (ctrl_t ctrl, char **r_version) +{ + gpg_error_t err; + + err = start_agent (ctrl, 0); + if (err) + return err; + + err = get_assuan_server_version (agent_ctx, 0, r_version); + return err; +} -- cgit v1.2.3