diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 16:14:06 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 16:14:06 +0000 |
commit | eee068778cb28ecf3c14e1bf843a95547d72c42d (patch) | |
tree | 0e07b30ddc5ea579d682d5dbe57998200d1c9ab7 /scd/app.c | |
parent | Initial commit. (diff) | |
download | gnupg2-eee068778cb28ecf3c14e1bf843a95547d72c42d.tar.xz gnupg2-eee068778cb28ecf3c14e1bf843a95547d72c42d.zip |
Adding upstream version 2.2.40.upstream/2.2.40
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'scd/app.c')
-rw-r--r-- | scd/app.c | 1356 |
1 files changed, 1356 insertions, 0 deletions
diff --git a/scd/app.c b/scd/app.c new file mode 100644 index 0000000..846fc77 --- /dev/null +++ b/scd/app.c @@ -0,0 +1,1356 @@ +/* app.c - Application selection. + * Copyright (C) 2003, 2004, 2005 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <npth.h> + +#include "scdaemon.h" +#include "../common/exechelp.h" +#include "iso7816.h" +#include "apdu.h" +#include "../common/tlv.h" + +static npth_mutex_t app_list_lock; +static app_t app_top; + + +/* List of all supported apps. */ +static struct +{ + apptype_t apptype; + char const *name; +} supported_app_list[] = + {{ APPTYPE_OPENPGP , "openpgp" }, + { APPTYPE_NKS , "nks" }, + { APPTYPE_P15 , "p15" }, + { APPTYPE_GELDKARTE, "geldkarte" }, + { APPTYPE_DINSIG , "dinsig" }, + { APPTYPE_SC_HSM , "sc-hsm" }, + { APPTYPE_NONE , NULL } + /* APPTYPE_UNDEFINED is special and not listed here. */ + }; + + + +static void +print_progress_line (void *opaque, const char *what, int pc, int cur, int tot) +{ + ctrl_t ctrl = opaque; + char line[100]; + + if (ctrl) + { + snprintf (line, sizeof line, "%s %c %d %d", what, pc, cur, tot); + send_status_direct (ctrl, "PROGRESS", line); + } +} + + +/* Map an application type to a string. Never returns NULL. */ +const char * +strapptype (apptype_t t) +{ + int i; + + for (i=0; supported_app_list[i].apptype; i++) + if (supported_app_list[i].apptype == t) + return supported_app_list[i].name; + return t == APPTYPE_UNDEFINED? "undefined" : t? "?" : "none"; +} + + +/* Return the apptype for NAME. */ +static apptype_t +apptype_from_name (const char *name) +{ + int i; + + if (!name) + return APPTYPE_NONE; + + for (i=0; supported_app_list[i].apptype; i++) + if (!ascii_strcasecmp (supported_app_list[i].name, name)) + return supported_app_list[i].apptype; + if (!ascii_strcasecmp ("undefined", name)) + return APPTYPE_UNDEFINED; + return APPTYPE_NONE; +} + + +/* Lock the reader SLOT. This function shall be used right before + calling any of the actual application functions to serialize access + to the reader. We do this always even if the reader is not + actually used. This allows an actual connection to assume that it + never shares a reader (while performing one command). Returns 0 on + success; only then the unlock_reader function must be called after + returning from the handler. */ +static gpg_error_t +lock_app (app_t app, ctrl_t ctrl) +{ + if (npth_mutex_lock (&app->lock)) + { + gpg_error_t err = gpg_error_from_syserror (); + log_error ("failed to acquire APP lock for %p: %s\n", + app, gpg_strerror (err)); + return err; + } + + apdu_set_progress_cb (app->slot, print_progress_line, ctrl); + apdu_set_prompt_cb (app->slot, popup_prompt, ctrl); + + return 0; +} + +/* Release a lock on the reader. See lock_reader(). */ +static void +unlock_app (app_t app) +{ + apdu_set_progress_cb (app->slot, NULL, NULL); + apdu_set_prompt_cb (app->slot, NULL, NULL); + + if (npth_mutex_unlock (&app->lock)) + { + gpg_error_t err = gpg_error_from_syserror (); + log_error ("failed to release APP lock for %p: %s\n", + app, gpg_strerror (err)); + } +} + + +/* This function may be called to print information pertaining to the + current state of this module to the log. */ +void +app_dump_state (void) +{ + app_t a; + + npth_mutex_lock (&app_list_lock); + for (a = app_top; a; a = a->next) + log_info ("app_dump_state: app=%p type='%s'\n", a, strapptype (a->apptype)); + npth_mutex_unlock (&app_list_lock); +} + +/* Check whether the application NAME is allowed. This does not mean + we have support for it though. */ +static int +is_app_allowed (const char *name) +{ + strlist_t l; + + for (l=opt.disabled_applications; l; l = l->next) + if (!strcmp (l->d, name)) + return 0; /* no */ + return 1; /* yes */ +} + + +static gpg_error_t +check_conflict (app_t app, const char *name) +{ + if (!app || !name + || (app->apptype && app->apptype == apptype_from_name (name))) + return 0; + + if (app->apptype && app->apptype == APPTYPE_UNDEFINED) + return 0; + + log_info ("application '%s' in use - can't switch\n", + strapptype (app->apptype)); + + return gpg_error (GPG_ERR_CONFLICT); +} + +/* This function is used by the serialno command to check for an + application conflict which may appear if the serialno command is + used to request a specific application and the connection has + already done a select_application. */ +gpg_error_t +check_application_conflict (const char *name, app_t app) +{ + return check_conflict (app, name); +} + + +gpg_error_t +app_reset (app_t app, ctrl_t ctrl, int send_reset) +{ + gpg_error_t err = 0; + + if (send_reset) + { + int sw; + + lock_app (app, ctrl); + sw = apdu_reset (app->slot); + if (sw) + err = gpg_error (GPG_ERR_CARD_RESET); + + app->reset_requested = 1; + unlock_app (app); + + scd_kick_the_loop (); + gnupg_sleep (1); + } + else + { + ctrl->app_ctx = NULL; + release_application (app, 0); + } + + return err; +} + +static gpg_error_t +app_new_register (int slot, ctrl_t ctrl, const char *name, + int periodical_check_needed) +{ + gpg_error_t err = 0; + app_t app = NULL; + unsigned char *result = NULL; + size_t resultlen; + int want_undefined; + + /* Need to allocate a new one. */ + app = xtrycalloc (1, sizeof *app); + if (!app) + { + err = gpg_error_from_syserror (); + log_info ("error allocating context: %s\n", gpg_strerror (err)); + return err; + } + + app->slot = slot; + app->card_status = (unsigned int)-1; + + if (npth_mutex_init (&app->lock, NULL)) + { + err = gpg_error_from_syserror (); + log_error ("error initializing mutex: %s\n", gpg_strerror (err)); + xfree (app); + return err; + } + + err = lock_app (app, ctrl); + if (err) + { + xfree (app); + return err; + } + + want_undefined = (name && !strcmp (name, "undefined")); + + /* Try to read the GDO file first to get a default serial number. + We skip this if the undefined application has been requested. */ + if (!want_undefined) + { + err = iso7816_select_file (slot, 0x3F00, 1); + if (gpg_err_code (err) == GPG_ERR_CARD) + { + /* Might be SW==0x7D00. Let's test whether it is a Yubikey + * by selecting its manager application and then reading the + * config. */ + static char const yk_aid[] = + { 0xA0, 0x00, 0x00, 0x05, 0x27, 0x47, 0x11, 0x17 }; /*MGR*/ + static char const otp_aid[] = + { 0xA0, 0x00, 0x00, 0x05, 0x27, 0x20, 0x01 }; /*OTP*/ + unsigned char *buf; + size_t buflen; + const unsigned char *s0; + unsigned char formfactor; + size_t n; + + if (!iso7816_select_application (slot, yk_aid, sizeof yk_aid, + 0x0001) + && !iso7816_apdu_direct (slot, "\x00\x1d\x00\x00\x00", 5, 0, + NULL, &buf, &buflen)) + { + app->cardtype = CARDTYPE_YUBIKEY; + if (opt.verbose) + { + log_info ("Yubico: config="); + log_printhex (buf, buflen, ""); + } + + /* We skip the first byte which seems to be the total + * length of the config data. */ + if (buflen > 1) + { + s0 = find_tlv (buf+1, buflen-1, 0x04, &n); /* Form factor */ + formfactor = (s0 && n == 1)? *s0 : 0; + + s0 = find_tlv (buf+1, buflen-1, 0x02, &n); /* Serial */ + if (s0 && n <= 4) + { + app->serialno = xtrymalloc (3 + 1 + 4); + if (app->serialno) + { + app->serialnolen = 3 + 1 + 4; + app->serialno[0] = 0xff; + app->serialno[1] = 0x02; + app->serialno[2] = 0x0; + app->serialno[3] = formfactor; + memset (app->serialno + 4, 0, 4 - n); + memcpy (app->serialno + 4 + 4 - n, s0, n); + err = app_munge_serialno (app); + } + } + + s0 = find_tlv (buf+1, buflen-1, 0x05, &n); /* version */ + if (s0 && n == 3) + app->cardversion = ((s0[0]<<16)|(s0[1]<<8)|s0[2]); + else if (!s0) + { + /* No version - this is not a Yubikey 5. We now + * switch to the OTP app and take the first + * three bytes of the response as version + * number. */ + xfree (buf); + buf = NULL; + if (!iso7816_select_application_ext (slot, + otp_aid, sizeof otp_aid, + 1, &buf, &buflen) + && buflen > 3) + app->cardversion = ((buf[0]<<16)|(buf[1]<<8)|buf[2]); + } + } + xfree (buf); + } + } + else + { + unsigned char *atr; + size_t atrlen; + + /* This is heuristics to identify different implementations. */ + atr = apdu_get_atr (slot, &atrlen); + if (atr) + { + if (atrlen == 21 && atr[2] == 0x11) + app->cardtype = CARDTYPE_GNUK; + else if (atrlen == 21 && atr[7] == 0x75) + app->cardtype = CARDTYPE_ZEITCONTROL; + xfree (atr); + } + } + + if (!err && app->cardtype != CARDTYPE_YUBIKEY) + err = iso7816_select_file (slot, 0x2F02, 0); + if (!err && app->cardtype != CARDTYPE_YUBIKEY) + err = iso7816_read_binary (slot, 0, 0, &result, &resultlen); + if (!err && app->cardtype != CARDTYPE_YUBIKEY) + { + size_t n; + const unsigned char *p; + + p = find_tlv_unchecked (result, resultlen, 0x5A, &n); + if (p) + resultlen -= (p-result); + if (p && n > resultlen && n == 0x0d && resultlen+1 == n) + { + /* The object it does not fit into the buffer. This is an + invalid encoding (or the buffer is too short. However, I + have some test cards with such an invalid encoding and + therefore I use this ugly workaround to return something + I can further experiment with. */ + log_info ("enabling BMI testcard workaround\n"); + n--; + } + + if (p && n <= resultlen) + { + /* The GDO file is pretty short, thus we simply reuse it for + storing the serial number. */ + memmove (result, p, n); + app->serialno = result; + app->serialnolen = n; + err = app_munge_serialno (app); + if (err) + goto leave; + } + else + xfree (result); + result = NULL; + } + } + + /* For certain error codes, there is no need to try more. */ + if (gpg_err_code (err) == GPG_ERR_CARD_NOT_PRESENT + || gpg_err_code (err) == GPG_ERR_ENODEV) + goto leave; + + /* Figure out the application to use. */ + if (want_undefined) + { + /* We switch to the "undefined" application only if explicitly + requested. */ + app->apptype = APPTYPE_UNDEFINED; + err = 0; + } + else + err = gpg_error (GPG_ERR_NOT_FOUND); + + /* Fixme: Use a table like we do in 2.3. */ + if (err && is_app_allowed ("openpgp") + && (!name || !strcmp (name, "openpgp"))) + err = app_select_openpgp (app); + if (err && is_app_allowed ("nks") && (!name || !strcmp (name, "nks"))) + err = app_select_nks (app); + if (err && is_app_allowed ("p15") && (!name || !strcmp (name, "p15"))) + err = app_select_p15 (app); + if (err && is_app_allowed ("geldkarte") + && (!name || !strcmp (name, "geldkarte"))) + err = app_select_geldkarte (app); + if (err && is_app_allowed ("dinsig") && (!name || !strcmp (name, "dinsig"))) + err = app_select_dinsig (app); + if (err && is_app_allowed ("sc-hsm") && (!name || !strcmp (name, "sc-hsm"))) + err = app_select_sc_hsm (app); + if (err && name && gpg_err_code (err) != GPG_ERR_OBJ_TERM_STATE) + err = gpg_error (GPG_ERR_NOT_SUPPORTED); + + leave: + if (err) + { + if (name) + log_info ("can't select application '%s': %s\n", + name, gpg_strerror (err)); + else + log_info ("no supported card application found: %s\n", + gpg_strerror (err)); + unlock_app (app); + xfree (app); + return err; + } + + app->periodical_check_needed = periodical_check_needed; + + npth_mutex_lock (&app_list_lock); + app->next = app_top; + app_top = app; + npth_mutex_unlock (&app_list_lock); + unlock_app (app); + return 0; +} + +/* If called with NAME as NULL, select the best fitting application + and return a context; otherwise select the application with NAME + and return a context. Returns an error code and stores NULL at + R_APP if no application was found or no card is present. */ +gpg_error_t +select_application (ctrl_t ctrl, const char *name, app_t *r_app, + int scan, const unsigned char *serialno_bin, + size_t serialno_bin_len) +{ + gpg_error_t err = 0; + app_t a, a_prev = NULL; + + *r_app = NULL; + + if (scan || !app_top) + { + struct dev_list *l; + int new_app = 0; + + /* Scan the devices to find new device(s). */ + err = apdu_dev_list_start (opt.reader_port, &l); + if (err) + return err; + + while (1) + { + int slot; + int periodical_check_needed_this; + + slot = apdu_open_reader (l, !app_top); + if (slot < 0) + break; + + periodical_check_needed_this = apdu_connect (slot); + if (periodical_check_needed_this < 0) + { + /* We close a reader with no card. */ + err = gpg_error (GPG_ERR_ENODEV); + } + else + { + err = app_new_register (slot, ctrl, name, + periodical_check_needed_this); + new_app++; + } + + if (err) + apdu_close_reader (slot); + } + + apdu_dev_list_finish (l); + + /* If new device(s), kick the scdaemon loop. */ + if (new_app) + scd_kick_the_loop (); + } + + npth_mutex_lock (&app_list_lock); + for (a = app_top; a; a = a->next) + { + lock_app (a, ctrl); + if (serialno_bin == NULL) + break; + if (a->serialnolen == serialno_bin_len + && !memcmp (a->serialno, serialno_bin, a->serialnolen)) + break; + unlock_app (a); + a_prev = a; + } + + if (a) + { + err = check_conflict (a, name); + if (!err) + { + a->ref_count++; + *r_app = a; + if (a_prev) + { + a_prev->next = a->next; + a->next = app_top; + app_top = a; + } + } + unlock_app (a); + } + else + err = gpg_error (GPG_ERR_ENODEV); + + npth_mutex_unlock (&app_list_lock); + + return err; +} + + +char * +get_supported_applications (void) +{ + const char *list[] = { + "openpgp", + "nks", + "p15", + "geldkarte", + "dinsig", + "sc-hsm", + /* Note: "undefined" is not listed here because it needs special + treatment by the client. */ + NULL + }; + int idx; + size_t nbytes; + char *buffer, *p; + + for (nbytes=1, idx=0; list[idx]; idx++) + nbytes += strlen (list[idx]) + 1 + 1; + + buffer = xtrymalloc (nbytes); + if (!buffer) + return NULL; + + for (p=buffer, idx=0; list[idx]; idx++) + if (is_app_allowed (list[idx])) + p = stpcpy (stpcpy (p, list[idx]), ":\n"); + *p = 0; + + return buffer; +} + + +/* Deallocate the application. */ +static void +deallocate_app (app_t app) +{ + app_t a, a_prev = NULL; + + for (a = app_top; a; a = a->next) + if (a == app) + { + if (a_prev == NULL) + app_top = a->next; + else + a_prev->next = a->next; + break; + } + else + a_prev = a; + + if (app->ref_count) + log_error ("trying to release context used yet (%d)\n", app->ref_count); + + if (app->fnc.deinit) + { + app->fnc.deinit (app); + app->fnc.deinit = NULL; + } + + xfree (app->serialno); + + unlock_app (app); + xfree (app); +} + +/* Free the resources associated with the application APP. APP is + allowed to be NULL in which case this is a no-op. Note that we are + using reference counting to track the users of the application and + actually deferring the deallocation to allow for a later reuse by + a new connection. */ +void +release_application (app_t app, int locked_already) +{ + if (!app) + return; + + /* We don't deallocate app here. Instead, we keep it. This is + useful so that a card does not get reset even if only one session + is using the card - this way the PIN cache and other cached data + are preserved. */ + + if (!locked_already) + lock_app (app, NULL); + + if (!app->ref_count) + log_bug ("trying to release an already released context\n"); + + --app->ref_count; + if (!locked_already) + unlock_app (app); +} + + + +/* The serial number may need some cosmetics. Do it here. This + function shall only be called once after a new serial number has + been put into APP->serialno. + + Prefixes we use: + + FF 00 00 = For serial numbers starting with an FF + FF 01 00 = Some german p15 cards return an empty serial number so the + serial number from the EF(TokenInfo) is used instead. + FF 7F 00 = No serialno. + + All other serial number not starting with FF are used as they are. +*/ +gpg_error_t +app_munge_serialno (app_t app) +{ + if (app->serialnolen && app->serialno[0] == 0xff) + { + /* The serial number starts with our special prefix. This + requires that we put our default prefix "FF0000" in front. */ + unsigned char *p = xtrymalloc (app->serialnolen + 3); + if (!p) + return gpg_error_from_syserror (); + memcpy (p, "\xff\0", 3); + memcpy (p+3, app->serialno, app->serialnolen); + app->serialnolen += 3; + xfree (app->serialno); + app->serialno = p; + } + else if (!app->serialnolen) + { + unsigned char *p = xtrymalloc (3); + if (!p) + return gpg_error_from_syserror (); + memcpy (p, "\xff\x7f", 3); + app->serialnolen = 3; + xfree (app->serialno); + app->serialno = p; + } + return 0; +} + + + +/* Retrieve the serial number of the card. The serial number is + returned as a malloced string (hex encoded) in SERIAL. Caller must + free SERIAL unless the function returns an error. */ +char * +app_get_serialno (app_t app) +{ + char *serial; + + if (!app) + return NULL; + + if (!app->serialnolen) + serial = xtrystrdup ("FF7F00"); + else + serial = bin2hex (app->serialno, app->serialnolen, NULL); + + return serial; +} + + +/* Return an allocated string with the serial number in a format to be + * show to the user. With NOFALLBACK set to true return NULL if such an + * abbreviated S/N is not available, else return the full serial + * number as a hex string. May return NULL on malloc problem. */ +char * +app_get_dispserialno (app_t app, int nofallback) +{ + char *result, *p; + unsigned long sn; + + if (app && app->serialno && app->serialnolen == 3+1+4 + && !memcmp (app->serialno, "\xff\x02\x00", 3)) + { + /* This is a 4 byte S/N of a Yubikey which seems to be printed + * on the token in decimal. Maybe they will print larger S/N + * also in decimal but we can't be sure, thus do it only for + * these 32 bit numbers. */ + sn = app->serialno[4] * 16777216; + sn += app->serialno[5] * 65536; + sn += app->serialno[6] * 256; + sn += app->serialno[7]; + if ((app->cardversion >> 16) >= 5) + result = xtryasprintf ("%lu %03lu %03lu", + (sn/1000000ul), + (sn/1000ul % 1000ul), + (sn % 1000ul)); + else + result = xtryasprintf ("%lu", sn); + } + else if (app && app->cardtype == CARDTYPE_YUBIKEY) + { + /* Get back the printed Yubikey number from the OpenPGP AID + * Example: D2760001240100000006120808620000 + */ + result = app_get_serialno (app); + if (result && strlen (result) >= 28 && !strncmp (result+16, "0006", 4)) + { + sn = atoi_4 (result+20) * 10000; + sn += atoi_4 (result+24); + if ((app->cardversion >> 16) >= 5) + p = xtryasprintf ("%lu %03lu %03lu", + (sn/1000000ul), + (sn/1000ul % 1000ul), + (sn % 1000ul)); + else + p = xtryasprintf ("%lu", sn); + if (p) + { + xfree (result); + result = p; + } + } + else if (nofallback) + { + xfree (result); + result = NULL; + } + } + else if (app && app->apptype == APPTYPE_OPENPGP) + { + /* Extract number from standard OpenPGP AID. */ + result = app_get_serialno (app); + if (result && strlen (result) > 16+12) + { + memcpy (result, result+16, 4); + result[4] = ' '; + memcpy (result+5, result+20, 8); + result[13] = 0; + } + else if (nofallback) + { + xfree (result); + result = NULL; + } + } + else if (nofallback) + result = NULL; /* No Abbreviated S/N. */ + else + result = app_get_serialno (app); + + return result; +} + + +/* Write out the application specifig status lines for the LEARN + command. */ +gpg_error_t +app_write_learn_status (app_t app, ctrl_t ctrl, unsigned int flags) +{ + gpg_error_t err; + + if (!app) + return gpg_error (GPG_ERR_INV_VALUE); + if (!app->fnc.learn_status) + return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); + + /* We do not send APPTYPE if only keypairinfo is requested. */ + if (app->apptype && !(flags & 1)) + send_status_direct (ctrl, "APPTYPE", strapptype (app->apptype)); + err = lock_app (app, ctrl); + if (err) + return err; + err = app->fnc.learn_status (app, ctrl, flags); + unlock_app (app); + return err; +} + + +/* Read the certificate with id CERTID (as returned by learn_status in + the CERTINFO status lines) and return it in the freshly allocated + buffer put into CERT and the length of the certificate put into + CERTLEN. */ +gpg_error_t +app_readcert (app_t app, ctrl_t ctrl, const char *certid, + unsigned char **cert, size_t *certlen) +{ + gpg_error_t err; + + if (!app) + return gpg_error (GPG_ERR_INV_VALUE); + if (!app->ref_count) + return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); + if (!app->fnc.readcert) + return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); + err = lock_app (app, ctrl); + if (err) + return err; + err = app->fnc.readcert (app, certid, cert, certlen); + unlock_app (app); + return err; +} + + +/* Read the key with ID KEYID. On success a canonical encoded + S-expression with the public key will get stored at PK and its + length (for assertions) at PKLEN; the caller must release that + buffer. On error NULL will be stored at PK and PKLEN and an error + code returned. + + This function might not be supported by all applications. */ +gpg_error_t +app_readkey (app_t app, ctrl_t ctrl, int advanced, const char *keyid, + unsigned char **pk, size_t *pklen) +{ + gpg_error_t err; + + if (pk) + *pk = NULL; + if (pklen) + *pklen = 0; + + if (!app || !keyid || !pk || !pklen) + return gpg_error (GPG_ERR_INV_VALUE); + if (!app->ref_count) + return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); + if (!app->fnc.readkey) + return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); + err = lock_app (app, ctrl); + if (err) + return err; + err= app->fnc.readkey (app, ctrl, keyid, + advanced? APP_READKEY_FLAG_ADVANCED : 0, + pk, pklen); + unlock_app (app); + return err; +} + + +/* Perform a GETATTR operation. */ +gpg_error_t +app_getattr (app_t app, ctrl_t ctrl, const char *name) +{ + gpg_error_t err; + + if (!app || !name || !*name) + return gpg_error (GPG_ERR_INV_VALUE); + if (!app->ref_count) + return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); + + if (app->apptype && name && !strcmp (name, "APPTYPE")) + { + send_status_direct (ctrl, "APPTYPE", strapptype (app->apptype)); + return 0; + } + if (name && !strcmp (name, "SERIALNO")) + { + char *serial; + + serial = app_get_serialno (app); + if (!serial) + return gpg_error (GPG_ERR_INV_VALUE); + + send_status_direct (ctrl, "SERIALNO", serial); + xfree (serial); + return 0; + } + + if (!app->fnc.getattr) + return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); + err = lock_app (app, ctrl); + if (err) + return err; + err = app->fnc.getattr (app, ctrl, name); + unlock_app (app); + return err; +} + +/* Perform a SETATTR operation. */ +gpg_error_t +app_setattr (app_t app, ctrl_t ctrl, const char *name, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg, + const unsigned char *value, size_t valuelen) +{ + gpg_error_t err; + + if (!app || !name || !*name || !value) + return gpg_error (GPG_ERR_INV_VALUE); + if (!app->ref_count) + return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); + if (!app->fnc.setattr) + return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); + err = lock_app (app, ctrl); + if (err) + return err; + err = app->fnc.setattr (app, ctrl, name, pincb, pincb_arg, value, valuelen); + unlock_app (app); + return err; +} + +/* Create the signature and return the allocated result in OUTDATA. + If a PIN is required the PINCB will be used to ask for the PIN; it + should return the PIN in an allocated buffer and put it into PIN. */ +gpg_error_t +app_sign (app_t app, ctrl_t ctrl, const char *keyidstr, int hashalgo, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg, + const void *indata, size_t indatalen, + unsigned char **outdata, size_t *outdatalen ) +{ + gpg_error_t err; + + if (!app || !indata || !indatalen || !outdata || !outdatalen || !pincb) + return gpg_error (GPG_ERR_INV_VALUE); + if (!app->ref_count) + return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); + if (!app->fnc.sign) + return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); + err = lock_app (app, ctrl); + if (err) + return err; + err = app->fnc.sign (app, ctrl, keyidstr, hashalgo, + pincb, pincb_arg, + indata, indatalen, + outdata, outdatalen); + unlock_app (app); + if (opt.verbose) + log_info ("operation sign result: %s\n", gpg_strerror (err)); + return err; +} + +/* Create the signature using the INTERNAL AUTHENTICATE command and + return the allocated result in OUTDATA. If a PIN is required the + PINCB will be used to ask for the PIN; it should return the PIN in + an allocated buffer and put it into PIN. */ +gpg_error_t +app_auth (app_t app, ctrl_t ctrl, const char *keyidstr, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg, + const void *indata, size_t indatalen, + unsigned char **outdata, size_t *outdatalen ) +{ + gpg_error_t err; + + if (!app || !indata || !indatalen || !outdata || !outdatalen || !pincb) + return gpg_error (GPG_ERR_INV_VALUE); + if (!app->ref_count) + return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); + if (!app->fnc.auth) + return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); + err = lock_app (app, ctrl); + if (err) + return err; + err = app->fnc.auth (app, ctrl, keyidstr, + pincb, pincb_arg, + indata, indatalen, + outdata, outdatalen); + unlock_app (app); + if (opt.verbose) + log_info ("operation auth result: %s\n", gpg_strerror (err)); + return err; +} + + +/* Decrypt the data in INDATA and return the allocated result in OUTDATA. + If a PIN is required the PINCB will be used to ask for the PIN; it + should return the PIN in an allocated buffer and put it into PIN. */ +gpg_error_t +app_decipher (app_t app, ctrl_t ctrl, const char *keyidstr, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg, + const void *indata, size_t indatalen, + unsigned char **outdata, size_t *outdatalen, + unsigned int *r_info) +{ + gpg_error_t err; + + *r_info = 0; + + if (!app || !indata || !indatalen || !outdata || !outdatalen || !pincb) + return gpg_error (GPG_ERR_INV_VALUE); + if (!app->ref_count) + return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); + if (!app->fnc.decipher) + return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); + err = lock_app (app, ctrl); + if (err) + return err; + err = app->fnc.decipher (app, ctrl, keyidstr, + pincb, pincb_arg, + indata, indatalen, + outdata, outdatalen, + r_info); + unlock_app (app); + if (opt.verbose) + log_info ("operation decipher result: %s\n", gpg_strerror (err)); + return err; +} + + +/* Perform the WRITECERT operation. */ +gpg_error_t +app_writecert (app_t app, ctrl_t ctrl, + const char *certidstr, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg, + const unsigned char *data, size_t datalen) +{ + gpg_error_t err; + + if (!app || !certidstr || !*certidstr || !pincb) + return gpg_error (GPG_ERR_INV_VALUE); + if (!app->ref_count) + return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); + if (!app->fnc.writecert) + return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); + err = lock_app (app, ctrl); + if (err) + return err; + err = app->fnc.writecert (app, ctrl, certidstr, + pincb, pincb_arg, data, datalen); + unlock_app (app); + if (opt.verbose) + log_info ("operation writecert result: %s\n", gpg_strerror (err)); + return err; +} + + +/* Perform the WRITEKEY operation. */ +gpg_error_t +app_writekey (app_t app, ctrl_t ctrl, + const char *keyidstr, unsigned int flags, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg, + const unsigned char *keydata, size_t keydatalen) +{ + gpg_error_t err; + + if (!app || !keyidstr || !*keyidstr || !pincb) + return gpg_error (GPG_ERR_INV_VALUE); + if (!app->ref_count) + return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); + if (!app->fnc.writekey) + return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); + err = lock_app (app, ctrl); + if (err) + return err; + err = app->fnc.writekey (app, ctrl, keyidstr, flags, + pincb, pincb_arg, keydata, keydatalen); + unlock_app (app); + if (opt.verbose) + log_info ("operation writekey result: %s\n", gpg_strerror (err)); + return err; +} + + +/* Perform a SETATTR operation. */ +gpg_error_t +app_genkey (app_t app, ctrl_t ctrl, const char *keynostr, + const char *keytype, unsigned int flags, time_t createtime, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg) +{ + gpg_error_t err; + + if (!app || !keynostr || !*keynostr || !pincb) + return gpg_error (GPG_ERR_INV_VALUE); + if (!app->ref_count) + return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); + if (!app->fnc.genkey) + return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); + err = lock_app (app, ctrl); + if (err) + return err; + err = app->fnc.genkey (app, ctrl, keynostr, keytype, flags, + createtime, pincb, pincb_arg); + unlock_app (app); + if (opt.verbose) + log_info ("operation genkey result: %s\n", gpg_strerror (err)); + return err; +} + + +/* Perform a GET CHALLENGE operation. This function is special as it + directly accesses the card without any application specific + wrapper. */ +gpg_error_t +app_get_challenge (app_t app, ctrl_t ctrl, size_t nbytes, unsigned char *buffer) +{ + gpg_error_t err; + + if (!app || !nbytes || !buffer) + return gpg_error (GPG_ERR_INV_VALUE); + if (!app->ref_count) + return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); + err = lock_app (app, ctrl); + if (err) + return err; + err = iso7816_get_challenge (app->slot, nbytes, buffer); + unlock_app (app); + return err; +} + + + +/* Perform a CHANGE REFERENCE DATA or RESET RETRY COUNTER operation. */ +gpg_error_t +app_change_pin (app_t app, ctrl_t ctrl, const char *chvnostr, + unsigned int flags, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg) +{ + gpg_error_t err; + + if (!app || !chvnostr || !*chvnostr || !pincb) + return gpg_error (GPG_ERR_INV_VALUE); + if (!app->ref_count) + return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); + if (!app->fnc.change_pin) + return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); + err = lock_app (app, ctrl); + if (err) + return err; + err = app->fnc.change_pin (app, ctrl, chvnostr, flags, pincb, pincb_arg); + unlock_app (app); + if (opt.verbose) + log_info ("operation change_pin result: %s\n", gpg_strerror (err)); + return err; +} + + +/* Perform a VERIFY operation without doing anything else. This may + be used to initialize a the PIN cache for long lasting other + operations. Its use is highly application dependent. */ +gpg_error_t +app_check_pin (app_t app, ctrl_t ctrl, const char *keyidstr, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg) +{ + gpg_error_t err; + + if (!app || !keyidstr || !*keyidstr || !pincb) + return gpg_error (GPG_ERR_INV_VALUE); + if (!app->ref_count) + return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); + if (!app->fnc.check_pin) + return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); + err = lock_app (app, ctrl); + if (err) + return err; + err = app->fnc.check_pin (app, ctrl, keyidstr, pincb, pincb_arg); + unlock_app (app); + if (opt.verbose) + log_info ("operation check_pin result: %s\n", gpg_strerror (err)); + return err; +} + +static void +report_change (int slot, int old_status, int cur_status) +{ + char *homestr, *envstr; + char *fname; + char templ[50]; + estream_t fp; + + snprintf (templ, sizeof templ, "reader_%d.status", slot); + fname = make_filename (gnupg_homedir (), templ, NULL ); + fp = es_fopen (fname, "w"); + if (fp) + { + es_fprintf (fp, "%s\n", + (cur_status & 1)? "USABLE": + (cur_status & 4)? "ACTIVE": + (cur_status & 2)? "PRESENT": "NOCARD"); + es_fclose (fp); + } + xfree (fname); + + homestr = make_filename (gnupg_homedir (), NULL); + if (gpgrt_asprintf (&envstr, "GNUPGHOME=%s", homestr) < 0) + log_error ("out of core while building environment\n"); + else + { + gpg_error_t err; + const char *args[9], *envs[2]; + char numbuf1[30], numbuf2[30], numbuf3[30]; + + envs[0] = envstr; + envs[1] = NULL; + + sprintf (numbuf1, "%d", slot); + sprintf (numbuf2, "0x%04X", old_status); + sprintf (numbuf3, "0x%04X", cur_status); + args[0] = "--reader-port"; + args[1] = numbuf1; + args[2] = "--old-code"; + args[3] = numbuf2; + args[4] = "--new-code"; + args[5] = numbuf3; + args[6] = "--status"; + args[7] = ((cur_status & 1)? "USABLE": + (cur_status & 4)? "ACTIVE": + (cur_status & 2)? "PRESENT": "NOCARD"); + args[8] = NULL; + + fname = make_filename (gnupg_homedir (), "scd-event", NULL); + err = gnupg_spawn_process_detached (fname, args, envs); + if (err && gpg_err_code (err) != GPG_ERR_ENOENT) + log_error ("failed to run event handler '%s': %s\n", + fname, gpg_strerror (err)); + xfree (fname); + xfree (envstr); + } + xfree (homestr); +} + +int +scd_update_reader_status_file (void) +{ + app_t a, app_next; + int periodical_check_needed = 0; + + npth_mutex_lock (&app_list_lock); + for (a = app_top; a; a = app_next) + { + int sw; + unsigned int status; + + lock_app (a, NULL); + app_next = a->next; + + if (a->reset_requested) + status = 0; + else + { + sw = apdu_get_status (a->slot, 0, &status); + if (sw == SW_HOST_NO_READER) + { + /* Most likely the _reader_ has been unplugged. */ + status = 0; + } + else if (sw) + { + /* Get status failed. Ignore that. */ + if (a->periodical_check_needed) + periodical_check_needed = 1; + unlock_app (a); + continue; + } + } + + if (a->card_status != status) + { + report_change (a->slot, a->card_status, status); + send_client_notifications (a, status == 0); + + if (status == 0) + { + log_debug ("Removal of a card: %d\n", a->slot); + apdu_close_reader (a->slot); + deallocate_app (a); + } + else + { + a->card_status = status; + if (a->periodical_check_needed) + periodical_check_needed = 1; + unlock_app (a); + } + } + else + { + if (a->periodical_check_needed) + periodical_check_needed = 1; + unlock_app (a); + } + } + npth_mutex_unlock (&app_list_lock); + + return periodical_check_needed; +} + +/* This function must be called once to initialize this module. This + has to be done before a second thread is spawned. We can't do the + static initialization because Pth emulation code might not be able + to do a static init; in particular, it is not possible for W32. */ +gpg_error_t +initialize_module_command (void) +{ + gpg_error_t err; + + if (npth_mutex_init (&app_list_lock, NULL)) + { + err = gpg_error_from_syserror (); + log_error ("app: error initializing mutex: %s\n", gpg_strerror (err)); + return err; + } + + return apdu_init (); +} + +void +app_send_card_list (ctrl_t ctrl) +{ + app_t a; + char buf[65]; + + npth_mutex_lock (&app_list_lock); + for (a = app_top; a; a = a->next) + { + if (DIM (buf) < 2 * a->serialnolen + 1) + continue; + + bin2hex (a->serialno, a->serialnolen, buf); + send_status_direct (ctrl, "SERIALNO", buf); + } + npth_mutex_unlock (&app_list_lock); +} |