diff --git a/scd/app.c b/scd/app.c index ada1eb963..2ee104d9e 100644 --- a/scd/app.c +++ b/scd/app.c @@ -1,1204 +1,1214 @@ /* 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 . */ #include #include #include #include #include #include #include "scdaemon.h" #include "../common/exechelp.h" #include "app-common.h" #include "iso7816.h" #include "apdu.h" #include "../common/tlv.h" static npth_mutex_t app_list_lock; static app_t app_top; 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); } } /* 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, 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 && !ascii_strcasecmp (app->apptype, name))) return 0; if (app->apptype && !strcmp (app->apptype, "UNDEFINED")) return 0; log_info ("application '%s' in use - can't switch\n", app->apptype? 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*/ unsigned char *buf; size_t buflen; const unsigned char *s0, *s1; 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 = "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 */ if (s0 && n == 1) { s1 = find_tlv (buf+1, buflen-1, 0x02, &n); /* Serial */ if (s1 && n >= 4) { app->serialno = xtrymalloc (3 + 1 + n); if (app->serialno) { app->serialnolen = 3 + 1 + n; app->serialno[0] = 0xff; app->serialno[1] = 0x02; app->serialno[2] = 0x0; app->serialno[3] = *s0; memcpy (app->serialno + 4, s1, n); /* Note that we do not clear the error * so that no further serial number * testing is done. After all we just * set the serial number. */ } } + s1 = find_tlv (buf+1, buflen-1, 0x05, &n); /* version */ + if (s1 && n == 3) + app->cardversion = ((s1[0]<<16)|(s1[1]<<8)|s1[2]); } } xfree (buf); } } if (!err) err = iso7816_select_file (slot, 0x2F02, 0); if (!err) err = iso7816_read_binary (slot, 0, 0, &result, &resultlen); if (!err) { 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; } } /* Figure out the application to use. */ if (want_undefined) { /* We switch to the "undefined" application only if explicitly requested. */ app->apptype = "UNDEFINED"; /* Clear the error so that we don't run through the application * selection chain. */ err = 0; } else { /* 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; /* Set a default error so that we run through the application * selecion chain. */ err = gpg_error (GPG_ERR_NOT_FOUND); } if (err && is_app_allowed ("openpgp") && (!name || !strcmp (name, "openpgp"))) err = app_select_openpgp (app); if (err && is_app_allowed ("piv") && (!name || !strcmp (name, "piv"))) err = app_select_piv (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; app->next = app_top; app_top = app; 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; npth_mutex_lock (&app_list_lock); 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) { npth_mutex_unlock (&app_list_lock); 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 (); } 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", "piv", "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 02 00 = Serial number from Yubikey config 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; } -/* Write out the application specifig status lines for the LEARN +/* Write out the application specific 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 CARD and APPTYPE if only keypairinfo is requested. */ - if (app->cardtype && !(flags & 1)) - send_status_direct (ctrl, "CARDTYPE", app->cardtype); - if (app->apptype && !(flags & 1)) - send_status_direct (ctrl, "APPTYPE", app->apptype); + if (!(flags &1)) + { + if (app->cardtype) + send_status_direct (ctrl, "CARDTYPE", app->cardtype); + if (app->cardversion) + send_status_printf (ctrl, "CARDVERSION", "%X", app->cardversion); + if (app->apptype) + send_status_direct (ctrl, "APPTYPE", app->apptype); + if (app->appversion) + send_status_printf (ctrl, "APPVERSION", "%X", app->appversion); + } 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, 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, keyid, 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->cardtype && name && !strcmp (name, "CARDTYPE")) { send_status_direct (ctrl, "CARDTYPE", app->cardtype); return 0; } if (app->apptype && name && !strcmp (name, "APPTYPE")) { send_status_direct (ctrl, "APPTYPE", 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, 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, 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, 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, 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, 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]; FILE *fp; snprintf (templ, sizeof templ, "reader_%d.status", slot); fname = make_filename (gnupg_homedir (), templ, NULL ); fp = fopen (fname, "w"); if (fp) { fprintf (fp, "%s\n", (cur_status & 1)? "USABLE": (cur_status & 4)? "ACTIVE": (cur_status & 2)? "PRESENT": "NOCARD"); 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); } diff --git a/scd/command.c b/scd/command.c index 5b2ca6c29..0d1a5cd3f 100644 --- a/scd/command.c +++ b/scd/command.c @@ -1,2092 +1,2092 @@ /* command.c - SCdaemon command handler * Copyright (C) 2001, 2002, 2003, 2004, 2005, * 2007, 2008, 2009, 2011 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * GnuPG is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include #include #include #include #include #include #include #include #ifdef USE_NPTH # include #endif #include "scdaemon.h" #include #include #include "app-common.h" #include "iso7816.h" #include "apdu.h" /* Required for apdu_*_reader (). */ #include "atr.h" #ifdef HAVE_LIBUSB #include "ccid-driver.h" #endif #include "../common/asshelp.h" #include "../common/server-help.h" /* Maximum length allowed as a PIN; used for INQUIRE NEEDPIN */ #define MAXLEN_PIN 100 /* Maximum allowed size of key data as used in inquiries. */ #define MAXLEN_KEYDATA 4096 /* Maximum allowed total data size for SETDATA. */ #define MAXLEN_SETDATA 4096 /* Maximum allowed size of certificate data as used in inquiries. */ #define MAXLEN_CERTDATA 16384 /* Maximum allowed size for "SETATTR --inquire". */ #define MAXLEN_SETATTRDATA 16384 #define set_error(e,t) assuan_set_error (ctx, gpg_error (e), (t)) #define IS_LOCKED(c) (locked_session && locked_session != (c)->server_local) /* Data used to associate an Assuan context with local server data. This object describes the local properties of one session. */ struct server_local_s { /* We keep a list of all active sessions with the anchor at SESSION_LIST (see below). This field is used for linking. */ struct server_local_s *next_session; /* This object is usually assigned to a CTRL object (which is globally visible). While enumerating all sessions we sometimes need to access data of the CTRL object; thus we keep a backpointer here. */ ctrl_t ctrl_backlink; /* The Assuan context used by this session/server. */ assuan_context_t assuan_ctx; #ifdef HAVE_W32_SYSTEM void *event_signal; /* Or NULL if not used. */ #else int event_signal; /* Or 0 if not used. */ #endif /* True if the card has been removed and a reset is required to continue operation. */ int card_removed; /* If set to true we will be terminate ourself at the end of the this session. */ int stopme; }; /* To keep track of all running sessions, we link all active server contexts and the anchor in this variable. */ static struct server_local_s *session_list; /* If a session has been locked we store a link to its server object in this variable. */ static struct server_local_s *locked_session; /* Convert the STRING into a newly allocated buffer while translating the hex numbers. Stops at the first invalid character. Blanks and colons are allowed to separate the hex digits. Returns NULL on error or a newly malloced buffer and its length in LENGTH. */ static unsigned char * hex_to_buffer (const char *string, size_t *r_length) { unsigned char *buffer; const char *s; size_t n; buffer = xtrymalloc (strlen (string)+1); if (!buffer) return NULL; for (s=string, n=0; *s; s++) { if (spacep (s) || *s == ':') continue; if (hexdigitp (s) && hexdigitp (s+1)) { buffer[n++] = xtoi_2 (s); s++; } else break; } *r_length = n; return buffer; } /* Reset the card and free the application context. With SEND_RESET set to true actually send a RESET to the reader; this is the normal way of calling the function. */ static void do_reset (ctrl_t ctrl, int send_reset) { app_t app = ctrl->app_ctx; if (app) app_reset (app, ctrl, IS_LOCKED (ctrl)? 0: send_reset); /* If we hold a lock, unlock now. */ if (locked_session && ctrl->server_local == locked_session) { locked_session = NULL; log_info ("implicitly unlocking due to RESET\n"); } } static gpg_error_t reset_notify (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); (void) line; do_reset (ctrl, 1); return 0; } static gpg_error_t option_handler (assuan_context_t ctx, const char *key, const char *value) { ctrl_t ctrl = assuan_get_pointer (ctx); if (!strcmp (key, "event-signal")) { /* A value of 0 is allowed to reset the event signal. */ #ifdef HAVE_W32_SYSTEM if (!*value) return gpg_error (GPG_ERR_ASS_PARAMETER); #ifdef _WIN64 ctrl->server_local->event_signal = (void *)strtoull (value, NULL, 16); #else ctrl->server_local->event_signal = (void *)strtoul (value, NULL, 16); #endif #else int i = *value? atoi (value) : -1; if (i < 0) return gpg_error (GPG_ERR_ASS_PARAMETER); ctrl->server_local->event_signal = i; #endif } return 0; } /* If the card has not yet been opened, do it. */ static gpg_error_t open_card (ctrl_t ctrl) { /* If we ever got a card not present error code, return that. Only the SERIALNO command and a reset are able to clear from that state. */ if (ctrl->server_local->card_removed) return gpg_error (GPG_ERR_CARD_REMOVED); if ( IS_LOCKED (ctrl) ) return gpg_error (GPG_ERR_LOCKED); if (ctrl->app_ctx) return 0; return select_application (ctrl, NULL, &ctrl->app_ctx, 0, NULL, 0); } /* Explicitly open a card for a specific use of APPTYPE or SERIALNO. */ static gpg_error_t open_card_with_request (ctrl_t ctrl, const char *apptype, const char *serialno) { gpg_error_t err; unsigned char *serialno_bin = NULL; size_t serialno_bin_len = 0; app_t app = ctrl->app_ctx; /* If we are already initialized for one specific application we need to check that the client didn't requested a specific application different from the one in use before we continue. */ if (apptype && ctrl->app_ctx) return check_application_conflict (apptype, ctrl->app_ctx); /* Re-scan USB devices. Release APP, before the scan. */ ctrl->app_ctx = NULL; release_application (app, 0); if (serialno) serialno_bin = hex_to_buffer (serialno, &serialno_bin_len); err = select_application (ctrl, apptype, &ctrl->app_ctx, 1, serialno_bin, serialno_bin_len); xfree (serialno_bin); return err; } static const char hlp_serialno[] = "SERIALNO [--demand=] []\n" "\n" "Return the serial number of the card using a status response. This\n" "function should be used to check for the presence of a card.\n" "\n" "If --demand is given, an application on the card with SERIALNO is\n" "selected and an error is returned if no such card available.\n" "\n" "If APPTYPE is given, an application of that type is selected and an\n" "error is returned if the application is not supported or available.\n" "The default is to auto-select the application using a hardwired\n" "preference system. Note, that a future extension to this function\n" "may enable specifying a list and order of applications to try.\n" "\n" "This function is special in that it can be used to reset the card.\n" "Most other functions will return an error when a card change has\n" "been detected and the use of this function is therefore required.\n" "\n" "Background: We want to keep the client clear of handling card\n" "changes between operations; i.e. the client can assume that all\n" "operations are done on the same card unless he calls this function."; static gpg_error_t cmd_serialno (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); struct server_local_s *sl; int rc = 0; char *serial; const char *demand; if ( IS_LOCKED (ctrl) ) return gpg_error (GPG_ERR_LOCKED); if ((demand = has_option_name (line, "--demand"))) { if (*demand != '=') return set_error (GPG_ERR_ASS_PARAMETER, "missing value for option"); line = (char *)++demand; for (; *line && !spacep (line); line++) ; if (*line) *line++ = 0; } else demand = NULL; /* Clear the remove flag so that the open_card is able to reread it. */ if (ctrl->server_local->card_removed) ctrl->server_local->card_removed = 0; if ((rc = open_card_with_request (ctrl, *line? line:NULL, demand))) { ctrl->server_local->card_removed = 1; return rc; } /* Success, clear the card_removed flag for all sessions. */ for (sl=session_list; sl; sl = sl->next_session) { ctrl_t c = sl->ctrl_backlink; if (c != ctrl) c->server_local->card_removed = 0; } serial = app_get_serialno (ctrl->app_ctx); if (!serial) return gpg_error (GPG_ERR_INV_VALUE); rc = assuan_write_status (ctx, "SERIALNO", serial); xfree (serial); return rc; } static const char hlp_learn[] = "LEARN [--force] [--keypairinfo]\n" "\n" "Learn all useful information of the currently inserted card. When\n" "used without the force options, the command might do an INQUIRE\n" "like this:\n" "\n" " INQUIRE KNOWNCARDP \n" "\n" "The client should just send an \"END\" if the processing should go on\n" "or a \"CANCEL\" to force the function to terminate with a Cancel\n" "error message.\n" "\n" "With the option --keypairinfo only KEYPARIINFO status lines are\n" "returned.\n" "\n" "The response of this command is a list of status lines formatted as\n" "this:\n" "\n" " S APPTYPE \n" "\n" "This returns the type of the application, currently the strings:\n" "\n" " P15 = PKCS-15 structure used\n" " DINSIG = DIN SIG\n" " OPENPGP = OpenPGP card\n" " PIV = PIV card\n" " NKS = NetKey card\n" "\n" "are implemented. These strings are aliases for the AID\n" "\n" " S KEYPAIRINFO []\n" "\n" "If there is no certificate yet stored on the card a single 'X' is\n" "returned as the keygrip. In addition to the keypair info, information\n" "about all certificates stored on the card is also returned:\n" "\n" " S CERTINFO \n" "\n" "Where CERTTYPE is a number indicating the type of certificate:\n" " 0 := Unknown\n" " 100 := Regular X.509 cert\n" " 101 := Trusted X.509 cert\n" " 102 := Useful X.509 cert\n" " 110 := Root CA cert in a special format (e.g. DINSIG)\n" " 111 := Root CA cert as standard X509 cert.\n" "\n" "For certain cards, more information will be returned:\n" "\n" " S KEY-FPR \n" "\n" "For OpenPGP cards this returns the stored fingerprints of the\n" "keys. This can be used check whether a key is available on the\n" "card. NO may be 1, 2 or 3.\n" "\n" " S CA-FPR \n" "\n" "Similar to above, these are the fingerprints of keys assumed to be\n" "ultimately trusted.\n" "\n" " S DISP-NAME \n" "\n" "The name of the card holder as stored on the card; percent\n" "escaping takes place, spaces are encoded as '+'\n" "\n" " S PUBKEY-URL \n" "\n" "The URL to be used for locating the entire public key.\n" " \n" "Note, that this function may even be used on a locked card."; static gpg_error_t cmd_learn (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc = 0; int only_keypairinfo = has_option (line, "--keypairinfo"); if ((rc = open_card (ctrl))) return rc; /* Unless the force option is used we try a shortcut by identifying the card using a serial number and inquiring the client with that. The client may choose to cancel the operation if he already knows about this card */ if (!only_keypairinfo) { const char *reader; char *serial; app_t app = ctrl->app_ctx; if (!app) return gpg_error (GPG_ERR_CARD_NOT_PRESENT); reader = apdu_get_reader_name (app->slot); if (!reader) return out_of_core (); send_status_direct (ctrl, "READER", reader); /* No need to free the string of READER. */ serial = app_get_serialno (ctrl->app_ctx); if (!serial) return gpg_error (GPG_ERR_INV_VALUE); rc = assuan_write_status (ctx, "SERIALNO", serial); if (rc < 0) { xfree (serial); return out_of_core (); } if (!has_option (line, "--force")) { char *command; rc = gpgrt_asprintf (&command, "KNOWNCARDP %s", serial); if (rc < 0) { xfree (serial); return out_of_core (); } rc = assuan_inquire (ctx, command, NULL, NULL, 0); xfree (command); if (rc) { if (gpg_err_code (rc) != GPG_ERR_ASS_CANCELED) log_error ("inquire KNOWNCARDP failed: %s\n", gpg_strerror (rc)); xfree (serial); return rc; } /* Not canceled, so we have to proceed. */ } xfree (serial); } /* Let the application print out its collection of useful status information. */ if (!rc) rc = app_write_learn_status (ctrl->app_ctx, ctrl, only_keypairinfo); return rc; } static const char hlp_readcert[] = "READCERT ||\n" "\n" "Note, that this function may even be used on a locked card."; static gpg_error_t cmd_readcert (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc; unsigned char *cert; size_t ncert; if ((rc = open_card (ctrl))) return rc; line = xstrdup (line); /* Need a copy of the line. */ rc = app_readcert (ctrl->app_ctx, ctrl, line, &cert, &ncert); if (rc) log_error ("app_readcert failed: %s\n", gpg_strerror (rc)); xfree (line); line = NULL; if (!rc) { rc = assuan_send_data (ctx, cert, ncert); xfree (cert); if (rc) return rc; } return rc; } static const char hlp_readkey[] = "READKEY [--advanced] |\n" "\n" "Return the public key for the given cert or key ID as a standard\n" "S-expression.\n" "In --advanced mode it returns the S-expression in advanced format.\n" "\n" "Note that this function may even be used on a locked card."; static gpg_error_t cmd_readkey (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc; int advanced = 0; unsigned char *cert = NULL; unsigned char *pk = NULL; size_t ncert, pklen; if ((rc = open_card (ctrl))) return rc; if (has_option (line, "--advanced")) advanced = 1; line = skip_options (line); line = xstrdup (line); /* Need a copy of the line. */ /* If the application supports the READKEY function we use that. Otherwise we use the old way by extracting it from the certificate. */ rc = app_readkey (ctrl->app_ctx, ctrl, line, &pk, &pklen); if (!rc) ; /* Okay, got that key. */ else if (gpg_err_code (rc) == GPG_ERR_UNSUPPORTED_OPERATION || gpg_err_code (rc) == GPG_ERR_NOT_FOUND) { /* Fall back to certificate reading. */ rc = app_readcert (ctrl->app_ctx, ctrl, line, &cert, &ncert); if (rc) { log_error ("app_readcert failed: %s\n", gpg_strerror (rc)); goto leave; } rc = app_help_pubkey_from_cert (cert, ncert, &pk, &pklen); if (rc) { log_error ("failed to parse the certificate: %s\n", gpg_strerror (rc)); goto leave; } } else { log_error ("app_readkey failed: %s\n", gpg_strerror (rc)); goto leave; } if (advanced) { gcry_sexp_t s_key; unsigned char *pkadv; size_t pkadvlen; rc = gcry_sexp_new (&s_key, pk, pklen, 0); if (rc) goto leave; pkadvlen = gcry_sexp_sprint (s_key, GCRYSEXP_FMT_ADVANCED, NULL, 0); pkadv = xtrymalloc (pkadvlen); if (!pkadv) { rc = gpg_error_from_syserror (); goto leave; } log_assert (pkadvlen); gcry_sexp_sprint (s_key, GCRYSEXP_FMT_ADVANCED, pkadv, pkadvlen); gcry_sexp_release (s_key); /* (One less to adjust for the trailing '\0') */ rc = assuan_send_data (ctx, pkadv, pkadvlen-1); xfree (pkadv); } else rc = assuan_send_data (ctx, pk, pklen); leave: xfree (pk); xfree (cert); return rc; } static const char hlp_setdata[] = "SETDATA [--append] \n" "\n" "The client should use this command to tell us the data he want to sign.\n" "With the option --append, the data is appended to the data set by a\n" "previous SETDATA command."; static gpg_error_t cmd_setdata (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int append; int n, i, off; char *p; unsigned char *buf; append = (ctrl->in_data.value && has_option (line, "--append")); line = skip_options (line); if (locked_session && locked_session != ctrl->server_local) return gpg_error (GPG_ERR_LOCKED); /* Parse the hexstring. */ for (p=line,n=0; hexdigitp (p); p++, n++) ; if (*p) return set_error (GPG_ERR_ASS_PARAMETER, "invalid hexstring"); if (!n) return set_error (GPG_ERR_ASS_PARAMETER, "no data given"); if ((n&1)) return set_error (GPG_ERR_ASS_PARAMETER, "odd number of digits"); n /= 2; if (append) { if (ctrl->in_data.valuelen + n > MAXLEN_SETDATA) return set_error (GPG_ERR_TOO_LARGE, "limit on total size of data reached"); buf = xtrymalloc (ctrl->in_data.valuelen + n); } else buf = xtrymalloc (n); if (!buf) return out_of_core (); if (append) { memcpy (buf, ctrl->in_data.value, ctrl->in_data.valuelen); off = ctrl->in_data.valuelen; } else off = 0; for (p=line, i=0; i < n; p += 2, i++) buf[off+i] = xtoi_2 (p); xfree (ctrl->in_data.value); ctrl->in_data.value = buf; ctrl->in_data.valuelen = off+n; return 0; } static gpg_error_t pin_cb (void *opaque, const char *info, char **retstr) { assuan_context_t ctx = opaque; char *command; int rc; unsigned char *value; size_t valuelen; if (!retstr) { /* We prompt for pinpad entry. To make sure that the popup has been show we use an inquire and not just a status message. We ignore any value returned. */ if (info) { log_debug ("prompting for pinpad entry '%s'\n", info); rc = gpgrt_asprintf (&command, "POPUPPINPADPROMPT %s", info); if (rc < 0) return gpg_error (gpg_err_code_from_errno (errno)); rc = assuan_inquire (ctx, command, &value, &valuelen, MAXLEN_PIN); xfree (command); } else { log_debug ("dismiss pinpad entry prompt\n"); rc = assuan_inquire (ctx, "DISMISSPINPADPROMPT", &value, &valuelen, MAXLEN_PIN); } if (!rc) xfree (value); return rc; } *retstr = NULL; log_debug ("asking for PIN '%s'\n", info); rc = gpgrt_asprintf (&command, "NEEDPIN %s", info); if (rc < 0) return gpg_error (gpg_err_code_from_errno (errno)); /* Fixme: Write an inquire function which returns the result in secure memory and check all further handling of the PIN. */ rc = assuan_inquire (ctx, command, &value, &valuelen, MAXLEN_PIN); xfree (command); if (rc) return rc; if (!valuelen || value[valuelen-1]) { /* We require that the returned value is an UTF-8 string */ xfree (value); return gpg_error (GPG_ERR_INV_RESPONSE); } *retstr = (char*)value; return 0; } static const char hlp_pksign[] = "PKSIGN [--hash=[rmd160|sha{1,224,256,384,512}|md5]] \n" "\n" "The --hash option is optional; the default is SHA1."; static gpg_error_t cmd_pksign (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc; unsigned char *outdata; size_t outdatalen; char *keyidstr; int hash_algo; if (has_option (line, "--hash=rmd160")) hash_algo = GCRY_MD_RMD160; else if (has_option (line, "--hash=sha1")) hash_algo = GCRY_MD_SHA1; else if (has_option (line, "--hash=sha224")) hash_algo = GCRY_MD_SHA224; else if (has_option (line, "--hash=sha256")) hash_algo = GCRY_MD_SHA256; else if (has_option (line, "--hash=sha384")) hash_algo = GCRY_MD_SHA384; else if (has_option (line, "--hash=sha512")) hash_algo = GCRY_MD_SHA512; else if (has_option (line, "--hash=md5")) hash_algo = GCRY_MD_MD5; else if (!strstr (line, "--")) hash_algo = GCRY_MD_SHA1; else return set_error (GPG_ERR_ASS_PARAMETER, "invalid hash algorithm"); line = skip_options (line); if ((rc = open_card (ctrl))) return rc; /* We have to use a copy of the key ID because the function may use the pin_cb which in turn uses the assuan line buffer and thus overwriting the original line with the keyid */ keyidstr = xtrystrdup (line); if (!keyidstr) return out_of_core (); rc = app_sign (ctrl->app_ctx, ctrl, keyidstr, hash_algo, pin_cb, ctx, ctrl->in_data.value, ctrl->in_data.valuelen, &outdata, &outdatalen); xfree (keyidstr); if (rc) { log_error ("app_sign failed: %s\n", gpg_strerror (rc)); } else { rc = assuan_send_data (ctx, outdata, outdatalen); xfree (outdata); if (rc) return rc; /* that is already an assuan error code */ } return rc; } static const char hlp_pkauth[] = "PKAUTH "; static gpg_error_t cmd_pkauth (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc; unsigned char *outdata; size_t outdatalen; char *keyidstr; if ((rc = open_card (ctrl))) return rc; if (!ctrl->app_ctx) return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); /* We have to use a copy of the key ID because the function may use the pin_cb which in turn uses the assuan line buffer and thus overwriting the original line with the keyid */ keyidstr = xtrystrdup (line); if (!keyidstr) return out_of_core (); rc = app_auth (ctrl->app_ctx, ctrl, keyidstr, pin_cb, ctx, ctrl->in_data.value, ctrl->in_data.valuelen, &outdata, &outdatalen); xfree (keyidstr); if (rc) { log_error ("app_auth failed: %s\n", gpg_strerror (rc)); } else { rc = assuan_send_data (ctx, outdata, outdatalen); xfree (outdata); if (rc) return rc; /* that is already an assuan error code */ } return rc; } static const char hlp_pkdecrypt[] = "PKDECRYPT "; static gpg_error_t cmd_pkdecrypt (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc; unsigned char *outdata; size_t outdatalen; char *keyidstr; unsigned int infoflags; if ((rc = open_card (ctrl))) return rc; keyidstr = xtrystrdup (line); if (!keyidstr) return out_of_core (); rc = app_decipher (ctrl->app_ctx, ctrl, keyidstr, pin_cb, ctx, ctrl->in_data.value, ctrl->in_data.valuelen, &outdata, &outdatalen, &infoflags); xfree (keyidstr); if (rc) { log_error ("app_decipher failed: %s\n", gpg_strerror (rc)); } else { /* If the card driver told us that there is no padding, send a status line. If there is a padding it is assumed that the caller knows what padding is used. It would have been better to always send that information but for backward compatibility we can't do that. */ if ((infoflags & APP_DECIPHER_INFO_NOPAD)) send_status_direct (ctrl, "PADDING", "0"); rc = assuan_send_data (ctx, outdata, outdatalen); xfree (outdata); if (rc) return rc; /* that is already an assuan error code */ } return rc; } static const char hlp_getattr[] = "GETATTR \n" "\n" "This command is used to retrieve data from a smartcard. The\n" "allowed names depend on the currently selected smartcard\n" "application. NAME must be percent and '+' escaped. The value is\n" "returned through status message, see the LEARN command for details.\n" "\n" "However, the current implementation assumes that Name is not escaped;\n" "this works as long as no one uses arbitrary escaping. \n" "\n" "Note, that this function may even be used on a locked card."; static gpg_error_t cmd_getattr (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc; const char *keyword; if ((rc = open_card (ctrl))) return rc; keyword = line; for (; *line && !spacep (line); line++) ; if (*line) *line++ = 0; /* (We ignore any garbage for now.) */ /* FIXME: Applications should not return sensitive data if the card is locked. */ rc = app_getattr (ctrl->app_ctx, ctrl, keyword); return rc; } static const char hlp_setattr[] = "SETATTR [--inquire] \n" "\n" "This command is used to store data on a smartcard. The allowed\n" "names and values are depend on the currently selected smartcard\n" "application. NAME and VALUE must be percent and '+' escaped.\n" "\n" "However, the current implementation assumes that NAME is not\n" "escaped; this works as long as no one uses arbitrary escaping.\n" "\n" "If the option --inquire is used, VALUE shall not be given; instead\n" "an inquiry using the keyword \"VALUE\" is used to retrieve it. The\n" "value is in this case considered to be confidential and not logged.\n" "\n" "A PIN will be requested for most NAMEs. See the corresponding\n" "setattr function of the actually used application (app-*.c) for\n" "details."; static gpg_error_t cmd_setattr (assuan_context_t ctx, char *orig_line) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err; char *keyword; int keywordlen; size_t nbytes; char *line, *linebuf; int opt_inquire; opt_inquire = has_option (orig_line, "--inquire"); orig_line = skip_options (orig_line); if ((err = open_card (ctrl))) return err; /* We need to use a copy of LINE, because PIN_CB uses the same context and thus reuses the Assuan provided LINE. */ line = linebuf = xtrystrdup (orig_line); if (!line) return out_of_core (); keyword = line; for (keywordlen=0; *line && !spacep (line); line++, keywordlen++) ; if (*line) *line++ = 0; while (spacep (line)) line++; if (opt_inquire) { unsigned char *value; assuan_begin_confidential (ctx); err = assuan_inquire (ctx, "VALUE", &value, &nbytes, MAXLEN_SETATTRDATA); assuan_end_confidential (ctx); if (!err) { err = app_setattr (ctrl->app_ctx, ctrl, keyword, pin_cb, ctx, value, nbytes); wipememory (value, nbytes); xfree (value); } } else { nbytes = percent_plus_unescape_inplace (line, 0); err = app_setattr (ctrl->app_ctx, ctrl, keyword, pin_cb, ctx, (const unsigned char*)line, nbytes); } xfree (linebuf); return err; } static const char hlp_writecert[] = "WRITECERT \n" "\n" "This command is used to store a certificate on a smartcard. The\n" "allowed certids depend on the currently selected smartcard\n" "application. The actual certifciate is requested using the inquiry\n" "\"CERTDATA\" and needs to be provided in its raw (e.g. DER) form.\n" "\n" "In almost all cases a PIN will be requested. See the related\n" "writecert function of the actually used application (app-*.c) for\n" "details."; static gpg_error_t cmd_writecert (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc; char *certid; unsigned char *certdata; size_t certdatalen; line = skip_options (line); if (!*line) return set_error (GPG_ERR_ASS_PARAMETER, "no certid given"); certid = line; while (*line && !spacep (line)) line++; *line = 0; if ((rc = open_card (ctrl))) return rc; if (!ctrl->app_ctx) return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); certid = xtrystrdup (certid); if (!certid) return out_of_core (); /* Now get the actual keydata. */ rc = assuan_inquire (ctx, "CERTDATA", &certdata, &certdatalen, MAXLEN_CERTDATA); if (rc) { xfree (certid); return rc; } /* Write the certificate to the card. */ rc = app_writecert (ctrl->app_ctx, ctrl, certid, pin_cb, ctx, certdata, certdatalen); xfree (certid); xfree (certdata); return rc; } static const char hlp_writekey[] = "WRITEKEY [--force] \n" "\n" "This command is used to store a secret key on a smartcard. The\n" "allowed keyids depend on the currently selected smartcard\n" "application. The actual keydata is requested using the inquiry\n" "\"KEYDATA\" and need to be provided without any protection. With\n" "--force set an existing key under this KEYID will get overwritten.\n" "The keydata is expected to be the usual canonical encoded\n" "S-expression.\n" "\n" "A PIN will be requested for most NAMEs. See the corresponding\n" "writekey function of the actually used application (app-*.c) for\n" "details."; static gpg_error_t cmd_writekey (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc; char *keyid; int force = has_option (line, "--force"); unsigned char *keydata; size_t keydatalen; line = skip_options (line); if (!*line) return set_error (GPG_ERR_ASS_PARAMETER, "no keyid given"); keyid = line; while (*line && !spacep (line)) line++; *line = 0; if ((rc = open_card (ctrl))) return rc; if (!ctrl->app_ctx) return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); keyid = xtrystrdup (keyid); if (!keyid) return out_of_core (); /* Now get the actual keydata. */ assuan_begin_confidential (ctx); rc = assuan_inquire (ctx, "KEYDATA", &keydata, &keydatalen, MAXLEN_KEYDATA); assuan_end_confidential (ctx); if (rc) { xfree (keyid); return rc; } /* Write the key to the card. */ rc = app_writekey (ctrl->app_ctx, ctrl, keyid, force? 1:0, pin_cb, ctx, keydata, keydatalen); xfree (keyid); xfree (keydata); return rc; } static const char hlp_genkey[] = "GENKEY [--force] [--timestamp=] \n" "\n" "Generate a key on-card identified by , which is application\n" "specific. Return values are also application specific. For OpenPGP\n" "cards 3 status lines are returned:\n" "\n" " S KEY-FPR \n" " S KEY-CREATED-AT \n" " S KEY-DATA [-|p|n] \n" "\n" " 'p' and 'n' are the names of the RSA parameters; '-' is used to\n" " indicate that HEXDATA is the first chunk of a parameter given\n" - " by the next KEY-DATA.\n" + " by the next KEY-DATA. Only used by GnuPG version < 2.1.\n" "\n" "--force is required to overwrite an already existing key. The\n" "KEY-CREATED-AT is required for further processing because it is\n" "part of the hashed key material for the fingerprint.\n" "\n" "If --timestamp is given an OpenPGP key will be created using this\n" "value. The value needs to be in ISO Format; e.g.\n" "\"--timestamp=20030316T120000\" and after 1970-01-01 00:00:00.\n" "\n" "The public part of the key can also later be retrieved using the\n" "READKEY command."; static gpg_error_t cmd_genkey (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err; char *keyref_buffer = NULL; char *keyref; int force; const char *s; char *opt_algo = NULL; time_t timestamp; force = has_option (line, "--force"); if ((s=has_option_name (line, "--timestamp"))) { if (*s != '=') return set_error (GPG_ERR_ASS_PARAMETER, "missing value for option"); timestamp = isotime2epoch (s+1); if (timestamp < 1) return set_error (GPG_ERR_ASS_PARAMETER, "invalid time value"); } else timestamp = 0; err = get_option_value (line, "--algo", &opt_algo); if (err) goto leave; line = skip_options (line); if (!*line) return set_error (GPG_ERR_ASS_PARAMETER, "no key number given"); keyref = line; while (*line && !spacep (line)) line++; *line = 0; if ((err = open_card (ctrl))) goto leave; if (!ctrl->app_ctx) return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); keyref = keyref_buffer = xtrystrdup (keyref); if (!keyref) { err = gpg_error_from_syserror (); goto leave; } err = app_genkey (ctrl->app_ctx, ctrl, keyref, opt_algo, force? APP_GENKEY_FLAG_FORCE : 0, timestamp, pin_cb, ctx); leave: xfree (keyref_buffer); xfree (opt_algo); return err; } static const char hlp_random[] = "RANDOM \n" "\n" "Get NBYTES of random from the card and send them back as data.\n" "This usually involves EEPROM write on the card and thus excessive\n" "use of this command may destroy the card.\n" "\n" "Note, that this function may be even be used on a locked card."; static gpg_error_t cmd_random (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc; size_t nbytes; unsigned char *buffer; if (!*line) return set_error (GPG_ERR_ASS_PARAMETER, "number of requested bytes missing"); nbytes = strtoul (line, NULL, 0); if ((rc = open_card (ctrl))) return rc; if (!ctrl->app_ctx) return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); buffer = xtrymalloc (nbytes); if (!buffer) return out_of_core (); rc = app_get_challenge (ctrl->app_ctx, ctrl, nbytes, buffer); if (!rc) { rc = assuan_send_data (ctx, buffer, nbytes); xfree (buffer); return rc; /* that is already an assuan error code */ } xfree (buffer); return rc; } static const char hlp_passwd[] = "PASSWD [--reset] [--nullpin] [--clear] \n" "\n" "Change the PIN or, if --reset is given, reset the retry counter of\n" "the card holder verification vector CHVNO. The option --nullpin is\n" "used for TCOS cards to set the initial PIN. The option --clear clears\n" "the security status associated with the PIN so that the PIN needs to\n" "be presented again. The format of CHVNO depends on the card application."; static gpg_error_t cmd_passwd (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc; char *chvnostr; unsigned int flags = 0; if (has_option (line, "--reset")) flags |= APP_CHANGE_FLAG_RESET; if (has_option (line, "--nullpin")) flags |= APP_CHANGE_FLAG_NULLPIN; if (has_option (line, "--clear")) flags |= APP_CHANGE_FLAG_CLEAR; line = skip_options (line); if (!*line) return set_error (GPG_ERR_ASS_PARAMETER, "no CHV number given"); chvnostr = line; while (*line && !spacep (line)) line++; *line = 0; /* Do not allow other flags aside of --clear. */ if ((flags & APP_CHANGE_FLAG_CLEAR) && (flags & ~APP_CHANGE_FLAG_CLEAR)) return set_error (GPG_ERR_UNSUPPORTED_OPERATION, "--clear used with other options"); if ((rc = open_card (ctrl))) return rc; if (!ctrl->app_ctx) return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); chvnostr = xtrystrdup (chvnostr); if (!chvnostr) return out_of_core (); rc = app_change_pin (ctrl->app_ctx, ctrl, chvnostr, flags, pin_cb, ctx); if (rc) log_error ("command passwd failed: %s\n", gpg_strerror (rc)); xfree (chvnostr); return rc; } static const char hlp_checkpin[] = "CHECKPIN \n" "\n" "Perform a VERIFY operation without doing anything else. This may\n" "be used to initialize a the PIN cache earlier to long lasting\n" "operations. Its use is highly application dependent.\n" "\n" "For OpenPGP:\n" "\n" " Perform a simple verify operation for CHV1 and CHV2, so that\n" " further operations won't ask for CHV2 and it is possible to do a\n" " cheap check on the PIN: If there is something wrong with the PIN\n" " entry system, only the regular CHV will get blocked and not the\n" " dangerous CHV3. IDSTR is the usual card's serial number in hex\n" " notation; an optional fingerprint part will get ignored. There\n" " is however a special mode if the IDSTR is sffixed with the\n" " literal string \"[CHV3]\": In this case the Admin PIN is checked\n" " if and only if the retry counter is still at 3.\n" "\n" "For Netkey:\n" "\n" " Any of the valid PIN Ids may be used. These are the strings:\n" "\n" " PW1.CH - Global password 1\n" " PW2.CH - Global password 2\n" " PW1.CH.SIG - SigG password 1\n" " PW2.CH.SIG - SigG password 2\n" "\n" " For a definitive list, see the implementation in app-nks.c.\n" " Note that we call a PW2.* PIN a \"PUK\" despite that since TCOS\n" " 3.0 they are technically alternative PINs used to mutally\n" " unblock each other."; static gpg_error_t cmd_checkpin (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc; char *idstr; if ((rc = open_card (ctrl))) return rc; if (!ctrl->app_ctx) return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); /* We have to use a copy of the key ID because the function may use the pin_cb which in turn uses the assuan line buffer and thus overwriting the original line with the keyid. */ idstr = xtrystrdup (line); if (!idstr) return out_of_core (); rc = app_check_pin (ctrl->app_ctx, ctrl, idstr, pin_cb, ctx); xfree (idstr); if (rc) log_error ("app_check_pin failed: %s\n", gpg_strerror (rc)); return rc; } static const char hlp_lock[] = "LOCK [--wait]\n" "\n" "Grant exclusive card access to this session. Note that there is\n" "no lock counter used and a second lock from the same session will\n" "be ignored. A single unlock (or RESET) unlocks the session.\n" "Return GPG_ERR_LOCKED if another session has locked the reader.\n" "\n" "If the option --wait is given the command will wait until a\n" "lock has been released."; static gpg_error_t cmd_lock (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc = 0; retry: if (locked_session) { if (locked_session != ctrl->server_local) rc = gpg_error (GPG_ERR_LOCKED); } else locked_session = ctrl->server_local; #ifdef USE_NPTH if (rc && has_option (line, "--wait")) { rc = 0; npth_sleep (1); /* Better implement an event mechanism. However, for card operations this should be sufficient. */ /* FIXME: Need to check that the connection is still alive. This can be done by issuing status messages. */ goto retry; } #endif /*USE_NPTH*/ if (rc) log_error ("cmd_lock failed: %s\n", gpg_strerror (rc)); return rc; } static const char hlp_unlock[] = "UNLOCK\n" "\n" "Release exclusive card access."; static gpg_error_t cmd_unlock (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int rc = 0; (void)line; if (locked_session) { if (locked_session != ctrl->server_local) rc = gpg_error (GPG_ERR_LOCKED); else locked_session = NULL; } else rc = gpg_error (GPG_ERR_NOT_LOCKED); if (rc) log_error ("cmd_unlock failed: %s\n", gpg_strerror (rc)); return rc; } static const char hlp_getinfo[] = "GETINFO \n" "\n" "Multi purpose command to return certain information. \n" "Supported values of WHAT are:\n" "\n" " version - Return the version of the program.\n" " pid - Return the process id of the server.\n" " socket_name - Return the name of the socket.\n" " connections - Return number of active connections.\n" " status - Return the status of the current reader (in the future,\n" " may also return the status of all readers). The status\n" " is a list of one-character flags. The following flags\n" " are currently defined:\n" " 'u' Usable card present.\n" " 'r' Card removed. A reset is necessary.\n" " These flags are exclusive.\n" " reader_list - Return a list of detected card readers. Does\n" " currently only work with the internal CCID driver.\n" " deny_admin - Returns OK if admin commands are not allowed or\n" " GPG_ERR_GENERAL if admin commands are allowed.\n" " app_list - Return a list of supported applications. One\n" " application per line, fields delimited by colons,\n" " first field is the name.\n" " card_list - Return a list of serial numbers of active cards,\n" " using a status response."; static gpg_error_t cmd_getinfo (assuan_context_t ctx, char *line) { int rc = 0; if (!strcmp (line, "version")) { const char *s = VERSION; rc = assuan_send_data (ctx, s, strlen (s)); } else if (!strcmp (line, "pid")) { char numbuf[50]; snprintf (numbuf, sizeof numbuf, "%lu", (unsigned long)getpid ()); rc = assuan_send_data (ctx, numbuf, strlen (numbuf)); } else if (!strcmp (line, "socket_name")) { const char *s = scd_get_socket_name (); if (s) rc = assuan_send_data (ctx, s, strlen (s)); else rc = gpg_error (GPG_ERR_NO_DATA); } else if (!strcmp (line, "connections")) { char numbuf[20]; snprintf (numbuf, sizeof numbuf, "%d", get_active_connection_count ()); rc = assuan_send_data (ctx, numbuf, strlen (numbuf)); } else if (!strcmp (line, "status")) { ctrl_t ctrl = assuan_get_pointer (ctx); char flag; if (open_card (ctrl)) flag = 'r'; else flag = 'u'; rc = assuan_send_data (ctx, &flag, 1); } else if (!strcmp (line, "reader_list")) { #ifdef HAVE_LIBUSB char *s = ccid_get_reader_list (); #else char *s = NULL; #endif if (s) rc = assuan_send_data (ctx, s, strlen (s)); else rc = gpg_error (GPG_ERR_NO_DATA); xfree (s); } else if (!strcmp (line, "deny_admin")) rc = opt.allow_admin? gpg_error (GPG_ERR_GENERAL) : 0; else if (!strcmp (line, "app_list")) { char *s = get_supported_applications (); if (s) rc = assuan_send_data (ctx, s, strlen (s)); else rc = 0; xfree (s); } else if (!strcmp (line, "card_list")) { ctrl_t ctrl = assuan_get_pointer (ctx); app_send_card_list (ctrl); } else rc = set_error (GPG_ERR_ASS_PARAMETER, "unknown value for WHAT"); return rc; } static const char hlp_restart[] = "RESTART\n" "\n" "Restart the current connection; this is a kind of warm reset. It\n" "deletes the context used by this connection but does not send a\n" "RESET to the card. Thus the card itself won't get reset. \n" "\n" "This is used by gpg-agent to reuse a primary pipe connection and\n" "may be used by clients to backup from a conflict in the serial\n" "command; i.e. to select another application."; static gpg_error_t cmd_restart (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); app_t app = ctrl->app_ctx; (void)line; if (app) { ctrl->app_ctx = NULL; release_application (app, 0); } if (locked_session && ctrl->server_local == locked_session) { locked_session = NULL; log_info ("implicitly unlocking due to RESTART\n"); } return 0; } static const char hlp_disconnect[] = "DISCONNECT\n" "\n" "Disconnect the card if the backend supports a disconnect operation."; static gpg_error_t cmd_disconnect (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); (void)line; if (!ctrl->app_ctx) return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); apdu_disconnect (ctrl->app_ctx->slot); return 0; } static const char hlp_apdu[] = "APDU [--[dump-]atr] [--more] [--exlen[=N]] [hexstring]\n" "\n" "Send an APDU to the current reader. This command bypasses the high\n" "level functions and sends the data directly to the card. HEXSTRING\n" "is expected to be a proper APDU. If HEXSTRING is not given no\n" "commands are set to the card but the command will implictly check\n" "whether the card is ready for use. \n" "\n" "Using the option \"--atr\" returns the ATR of the card as a status\n" "message before any data like this:\n" " S CARD-ATR 3BFA1300FF813180450031C173C00100009000B1\n" "\n" "Using the option --more handles the card status word MORE_DATA\n" "(61xx) and concatenates all responses to one block.\n" "\n" "Using the option \"--exlen\" the returned APDU may use extended\n" "length up to N bytes. If N is not given a default value is used\n" "(currently 4096)."; static gpg_error_t cmd_apdu (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); app_t app; int rc; unsigned char *apdu; size_t apdulen; int with_atr; int handle_more; const char *s; size_t exlen; if (has_option (line, "--dump-atr")) with_atr = 2; else with_atr = has_option (line, "--atr"); handle_more = has_option (line, "--more"); if ((s=has_option_name (line, "--exlen"))) { if (*s == '=') exlen = strtoul (s+1, NULL, 0); else exlen = 4096; } else exlen = 0; line = skip_options (line); if ((rc = open_card (ctrl))) return rc; app = ctrl->app_ctx; if (!app) return gpg_error (GPG_ERR_CARD_NOT_PRESENT); if (with_atr) { unsigned char *atr; size_t atrlen; char hexbuf[400]; atr = apdu_get_atr (app->slot, &atrlen); if (!atr || atrlen > sizeof hexbuf - 2 ) { rc = gpg_error (GPG_ERR_INV_CARD); goto leave; } if (with_atr == 2) { char *string, *p, *pend; string = atr_dump (atr, atrlen); if (string) { for (rc=0, p=string; !rc && (pend = strchr (p, '\n')); p = pend+1) { rc = assuan_send_data (ctx, p, pend - p + 1); if (!rc) rc = assuan_send_data (ctx, NULL, 0); } if (!rc && *p) rc = assuan_send_data (ctx, p, strlen (p)); es_free (string); if (rc) goto leave; } } else { bin2hex (atr, atrlen, hexbuf); send_status_info (ctrl, "CARD-ATR", hexbuf, strlen (hexbuf), NULL, 0); } xfree (atr); } apdu = hex_to_buffer (line, &apdulen); if (!apdu) { rc = gpg_error_from_syserror (); goto leave; } if (apdulen) { unsigned char *result = NULL; size_t resultlen; rc = apdu_send_direct (app->slot, exlen, apdu, apdulen, handle_more, NULL, &result, &resultlen); if (rc) log_error ("apdu_send_direct failed: %s\n", gpg_strerror (rc)); else { rc = assuan_send_data (ctx, result, resultlen); xfree (result); } } xfree (apdu); leave: return rc; } static const char hlp_killscd[] = "KILLSCD\n" "\n" "Commit suicide."; static gpg_error_t cmd_killscd (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); (void)line; ctrl->server_local->stopme = 1; assuan_set_flag (ctx, ASSUAN_FORCE_CLOSE, 1); return 0; } /* Tell the assuan library about our commands */ static int register_commands (assuan_context_t ctx) { static struct { const char *name; assuan_handler_t handler; const char * const help; } table[] = { { "SERIALNO", cmd_serialno, hlp_serialno }, { "LEARN", cmd_learn, hlp_learn }, { "READCERT", cmd_readcert, hlp_readcert }, { "READKEY", cmd_readkey, hlp_readkey }, { "SETDATA", cmd_setdata, hlp_setdata }, { "PKSIGN", cmd_pksign, hlp_pksign }, { "PKAUTH", cmd_pkauth, hlp_pkauth }, { "PKDECRYPT", cmd_pkdecrypt,hlp_pkdecrypt }, { "INPUT", NULL }, { "OUTPUT", NULL }, { "GETATTR", cmd_getattr, hlp_getattr }, { "SETATTR", cmd_setattr, hlp_setattr }, { "WRITECERT", cmd_writecert,hlp_writecert }, { "WRITEKEY", cmd_writekey, hlp_writekey }, { "GENKEY", cmd_genkey, hlp_genkey }, { "RANDOM", cmd_random, hlp_random }, { "PASSWD", cmd_passwd, hlp_passwd }, { "CHECKPIN", cmd_checkpin, hlp_checkpin }, { "LOCK", cmd_lock, hlp_lock }, { "UNLOCK", cmd_unlock, hlp_unlock }, { "GETINFO", cmd_getinfo, hlp_getinfo }, { "RESTART", cmd_restart, hlp_restart }, { "DISCONNECT", cmd_disconnect,hlp_disconnect }, { "APDU", cmd_apdu, hlp_apdu }, { "KILLSCD", cmd_killscd, hlp_killscd }, { NULL } }; int i, rc; for (i=0; table[i].name; i++) { rc = assuan_register_command (ctx, table[i].name, table[i].handler, table[i].help); if (rc) return rc; } assuan_set_hello_line (ctx, "GNU Privacy Guard's Smartcard server ready"); assuan_register_reset_notify (ctx, reset_notify); assuan_register_option_handler (ctx, option_handler); return 0; } /* Startup the server. If FD is given as -1 this is simple pipe server, otherwise it is a regular server. Returns true if there are no more active asessions. */ int scd_command_handler (ctrl_t ctrl, int fd) { int rc; assuan_context_t ctx = NULL; int stopme; rc = assuan_new (&ctx); if (rc) { log_error ("failed to allocate assuan context: %s\n", gpg_strerror (rc)); scd_exit (2); } if (fd == -1) { assuan_fd_t filedes[2]; filedes[0] = assuan_fdopen (0); filedes[1] = assuan_fdopen (1); rc = assuan_init_pipe_server (ctx, filedes); } else { rc = assuan_init_socket_server (ctx, INT2FD(fd), ASSUAN_SOCKET_SERVER_ACCEPTED); } if (rc) { log_error ("failed to initialize the server: %s\n", gpg_strerror(rc)); scd_exit (2); } rc = register_commands (ctx); if (rc) { log_error ("failed to register commands with Assuan: %s\n", gpg_strerror(rc)); scd_exit (2); } assuan_set_pointer (ctx, ctrl); /* Allocate and initialize the server object. Put it into the list of active sessions. */ ctrl->server_local = xcalloc (1, sizeof *ctrl->server_local); ctrl->server_local->next_session = session_list; session_list = ctrl->server_local; ctrl->server_local->ctrl_backlink = ctrl; ctrl->server_local->assuan_ctx = ctx; /* Command processing loop. */ for (;;) { rc = assuan_accept (ctx); if (rc == -1) { break; } else if (rc) { log_info ("Assuan accept problem: %s\n", gpg_strerror (rc)); break; } rc = assuan_process (ctx); if (rc) { log_info ("Assuan processing failed: %s\n", gpg_strerror (rc)); continue; } } /* Cleanup. We don't send an explicit reset to the card. */ do_reset (ctrl, 0); /* Release the server object. */ if (session_list == ctrl->server_local) session_list = ctrl->server_local->next_session; else { struct server_local_s *sl; for (sl=session_list; sl->next_session; sl = sl->next_session) if (sl->next_session == ctrl->server_local) break; if (!sl->next_session) BUG (); sl->next_session = ctrl->server_local->next_session; } stopme = ctrl->server_local->stopme; xfree (ctrl->server_local); ctrl->server_local = NULL; /* Release the Assuan context. */ assuan_release (ctx); if (stopme) scd_exit (0); /* If there are no more sessions return true. */ return !session_list; } /* Send a line with status information via assuan and escape all given buffers. The variable elements are pairs of (char *, size_t), terminated with a (NULL, 0). */ void send_status_info (ctrl_t ctrl, const char *keyword, ...) { va_list arg_ptr; const unsigned char *value; size_t valuelen; char buf[950], *p; size_t n; assuan_context_t ctx = ctrl->server_local->assuan_ctx; va_start (arg_ptr, keyword); p = buf; n = 0; while ( (value = va_arg (arg_ptr, const unsigned char *)) && n < DIM (buf)-2 ) { valuelen = va_arg (arg_ptr, size_t); if (!valuelen) continue; /* empty buffer */ if (n) { *p++ = ' '; n++; } for ( ; valuelen && n < DIM (buf)-2; n++, valuelen--, value++) { if (*value == '+' || *value == '\"' || *value == '%' || *value < ' ') { sprintf (p, "%%%02X", *value); p += 3; n += 2; } else if (*value == ' ') *p++ = '+'; else *p++ = *value; } } *p = 0; assuan_write_status (ctx, keyword, buf); va_end (arg_ptr); } /* Send a ready formatted status line via assuan. */ void send_status_direct (ctrl_t ctrl, const char *keyword, const char *args) { assuan_context_t ctx = ctrl->server_local->assuan_ctx; if (strchr (args, '\n')) log_error ("error: LF detected in status line - not sending\n"); else assuan_write_status (ctx, keyword, args); } /* This status functions expects a printf style format string. No * filtering of the data is done instead the orintf formatted data is * send using assuan_send_status. */ gpg_error_t send_status_printf (ctrl_t ctrl, const char *keyword, const char *format, ...) { gpg_error_t err; va_list arg_ptr; assuan_context_t ctx; if (!ctrl || !ctrl->server_local || !(ctx = ctrl->server_local->assuan_ctx)) return 0; va_start (arg_ptr, format); err = vprint_assuan_status (ctx, keyword, format, arg_ptr); va_end (arg_ptr); return err; } void popup_prompt (void *opaque, int on) { ctrl_t ctrl = opaque; if (ctrl) { assuan_context_t ctx = ctrl->server_local->assuan_ctx; if (ctx) { const char *cmd; gpg_error_t err; unsigned char *value; size_t valuelen; if (on) cmd = "POPUPPINPADPROMPT --ack"; else cmd = "DISMISSPINPADPROMPT"; err = assuan_inquire (ctx, cmd, &value, &valuelen, 100); if (!err) xfree (value); } } } /* Helper to send the clients a status change notification. */ void send_client_notifications (app_t app, int removal) { struct { pid_t pid; #ifdef HAVE_W32_SYSTEM HANDLE handle; #else int signo; #endif } killed[50]; int killidx = 0; int kidx; struct server_local_s *sl; for (sl=session_list; sl; sl = sl->next_session) if (sl->ctrl_backlink && sl->ctrl_backlink->app_ctx == app) { pid_t pid; #ifdef HAVE_W32_SYSTEM HANDLE handle; #else int signo; #endif if (removal) { sl->ctrl_backlink->app_ctx = NULL; sl->card_removed = 1; release_application (app, 1); } if (!sl->event_signal || !sl->assuan_ctx) continue; pid = assuan_get_pid (sl->assuan_ctx); #ifdef HAVE_W32_SYSTEM handle = sl->event_signal; for (kidx=0; kidx < killidx; kidx++) if (killed[kidx].pid == pid && killed[kidx].handle == handle) break; if (kidx < killidx) log_info ("event %p (%p) already triggered for client %d\n", sl->event_signal, handle, (int)pid); else { log_info ("triggering event %p (%p) for client %d\n", sl->event_signal, handle, (int)pid); if (!SetEvent (handle)) log_error ("SetEvent(%p) failed: %s\n", sl->event_signal, w32_strerror (-1)); if (killidx < DIM (killed)) { killed[killidx].pid = pid; killed[killidx].handle = handle; killidx++; } } #else /*!HAVE_W32_SYSTEM*/ signo = sl->event_signal; if (pid != (pid_t)(-1) && pid && signo > 0) { for (kidx=0; kidx < killidx; kidx++) if (killed[kidx].pid == pid && killed[kidx].signo == signo) break; if (kidx < killidx) log_info ("signal %d already sent to client %d\n", signo, (int)pid); else { log_info ("sending signal %d to client %d\n", signo, (int)pid); kill (pid, signo); if (killidx < DIM (killed)) { killed[killidx].pid = pid; killed[killidx].signo = signo; killidx++; } } } #endif /*!HAVE_W32_SYSTEM*/ } } diff --git a/tools/card-call-scd.c b/tools/card-call-scd.c index f7dbfd6ec..0a01bf5ca 100644 --- a/tools/card-call-scd.c +++ b/tools/card-call-scd.c @@ -1,1536 +1,1550 @@ /* card-call-scd.c - IPC calls to scdaemon. * Copyright (C) 2019 g10 Code GmbH * 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 . * SPDX-License-Identifier: GPL-3.0-or-later */ #include #include #include #include #include #include #include #ifdef HAVE_LOCALE_H #include #endif #include "../common/util.h" #include "../common/membuf.h" #include "../common/i18n.h" #include "../common/asshelp.h" #include "../common/sysutils.h" #include "../common/status.h" #include "../common/host2net.h" #include "../common/openpgpdefs.h" #include "gpg-card.h" #define CONTROL_D ('D' - 'A' + 1) #define START_AGENT_NO_STARTUP_CMDS 1 #define START_AGENT_SUPPRESS_ERRORS 2 struct default_inq_parm_s { 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 card_cardlist_parm_s { gpg_error_t error; strlist_t list; }; 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; }; /* * File local variables */ /* The established context to the agent. Note that all calls to * scdaemon are routed via the agent and thus we only need to care * about the IPC with the agent. */ static assuan_context_t agent_ctx; /* * Local prototypes */ static gpg_error_t learn_status_cb (void *opaque, const char *line); /* Release the card info structure INFO. */ void release_card_info (card_info_t info) { int i; if (!info) return; xfree (info->reader); info->reader = NULL; xfree (info->cardtype); info->cardtype = NULL; xfree (info->serialno); info->serialno = NULL; xfree (info->dispserialno); info->dispserialno = NULL; xfree (info->apptypestr); info->apptypestr = NULL; info->apptype = APP_TYPE_NONE; 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->cafpr1len = info->cafpr2len = info->cafpr3len = 0; for (i=0; i < DIM(info->private_do); i++) { xfree (info->private_do[i]); info->private_do[i] = NULL; } while (info->kinfo) { key_info_t kinfo = info->kinfo->next; xfree (info->kinfo); info->kinfo = kinfo; } info->chvusage[0] = info->chvusage[1] = 0; } /* Map an application type string to an integer. */ static app_type_t map_apptypestr (const char *string) { app_type_t result; if (!string) result = APP_TYPE_NONE; else if (!ascii_strcasecmp (string, "OPENPGP")) result = APP_TYPE_OPENPGP; else if (!ascii_strcasecmp (string, "NKS")) result = APP_TYPE_NKS; else if (!ascii_strcasecmp (string, "DINSIG")) result = APP_TYPE_DINSIG; else if (!ascii_strcasecmp (string, "P15")) result = APP_TYPE_P15; else if (!ascii_strcasecmp (string, "GELDKARTE")) result = APP_TYPE_GELDKARTE; else if (!ascii_strcasecmp (string, "SC-HSM")) result = APP_TYPE_SC_HSM; else if (!ascii_strcasecmp (string, "PIV")) result = APP_TYPE_PIV; else result = APP_TYPE_UNKNOWN; return result; } /* Return a string representation of the application type. */ const char * app_type_string (app_type_t app_type) { const char *result = "?"; switch (app_type) { case APP_TYPE_NONE: result = "None"; break; case APP_TYPE_OPENPGP: result = "OpenPGP"; break; case APP_TYPE_NKS: result = "NetKey"; break; case APP_TYPE_DINSIG: result = "DINSIG"; break; case APP_TYPE_P15: result = "P15"; break; case APP_TYPE_GELDKARTE: result = "Geldkarte"; break; case APP_TYPE_SC_HSM: result = "SC-HSM"; break; case APP_TYPE_PIV: result = "PIV"; break; case APP_TYPE_UNKNOWN: result = "Unknown"; break; } return result; } /* If RC is not 0, write an appropriate status message. */ static gpg_error_t status_sc_op_failure (gpg_error_t err) { switch (gpg_err_code (err)) { case 0: break; case GPG_ERR_CANCELED: case GPG_ERR_FULLY_CANCELED: gnupg_status_printf (STATUS_SC_OP_FAILURE, "1"); break; case GPG_ERR_BAD_PIN: gnupg_status_printf (STATUS_SC_OP_FAILURE, "2"); break; default: gnupg_status_printf (STATUS_SC_OP_FAILURE, NULL); break; } return err; } /* This is the default inquiry callback. It mainly handles the Pinentry notifications. */ static gpg_error_t default_inq_cb (void *opaque, const char *line) { gpg_error_t err = 0; struct default_inq_parm_s *parm = opaque; (void)parm; 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 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_LOGLVL_INFO : GPGRT_LOGLVL_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"); } gnupg_status_printf (STATUS_WARNING, "server_version_mismatch 0 %s", warn); xfree (warn); } } xfree (serverversion); return err; } /* Try to connect to the agent via socket or fork it off and work by * pipes. Handle the server's initial greeting. */ static gpg_error_t start_agent (unsigned int flags) { gpg_error_t err; if (agent_ctx) err = 0; else { err = 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 (err) == GPG_ERR_NO_AGENT) { static int shown; if (!shown) { shown = 1; log_info (_("no gpg-agent running in this session\n")); } } else if (!err && !(err = 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); } } if (!err && !(flags & START_AGENT_NO_STARTUP_CMDS)) { /* Request the serial number of the card for an early test. */ struct card_info_s info; memset (&info, 0, sizeof info); if (!(flags & START_AGENT_SUPPRESS_ERRORS)) err = warn_version_mismatch (agent_ctx, SCDAEMON_NAME, 2); if (!err) err = assuan_transact (agent_ctx, "SCD SERIALNO", NULL, NULL, NULL, NULL, learn_status_cb, &info); if (err && !(flags & START_AGENT_SUPPRESS_ERRORS)) { switch (gpg_err_code (err)) { case GPG_ERR_NOT_SUPPORTED: case GPG_ERR_NO_SCDAEMON: gnupg_status_printf (STATUS_CARDCTRL, "6"); /* No card support. */ break; case GPG_ERR_OBJ_TERM_STATE: /* Card is in termination state. */ gnupg_status_printf (STATUS_CARDCTRL, "7"); break; default: gnupg_status_printf (STATUS_CARDCTRL, "4"); /* No card. */ break; } } if (!err && info.serialno) gnupg_status_printf (STATUS_CARDCTRL, "3 %s", info.serialno); release_card_info (&info); } return err; } /* 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 or 32 byte hexencoded string and put it into the provided * FPRLEN byte long buffer FPR in binary format. Returns the actual * used length of the FPR buffer or 0 on error. */ static unsigned int unhexify_fpr (const char *hexstr, unsigned char *fpr, unsigned int fprlen) { const char *s; int n; for (s=hexstr, n=0; hexdigitp (s); s++, n++) ; if ((*s && *s != ' ') || !(n == 40 || n == 64)) return 0; /* no fingerprint (invalid or wrong length). */ for (s=hexstr, n=0; *s && n < fprlen; s += 2, n++) fpr[n] = xtoi_2 (s); return (n == 20 || n == 32)? n : 0; } /* Take the serial number from LINE and return it verbatim in a newly * allocated string. We make sure that only hex characters are * returned. Returns NULL on error. */ 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; } /* Send an APDU to the current card. On success the status word is * stored at R_SW inless R_SW is NULL. With HEXAPDU being NULL only a * RESET command is send to scd. With HEXAPDU being the string * "undefined" the command "SERIALNO undefined" is send to scd. If * R_DATA is not NULL the data is without the status code is stored * there. Caller must release it. */ gpg_error_t scd_apdu (const char *hexapdu, unsigned int *r_sw, unsigned char **r_data, size_t *r_datalen) { gpg_error_t err; if (r_data) *r_data = NULL; if (r_datalen) *r_datalen = 0; err = start_agent (START_AGENT_NO_STARTUP_CMDS); if (err) return err; if (!hexapdu) { err = assuan_transact (agent_ctx, "SCD RESET", 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 { if (r_sw) *r_sw = buf16_to_uint (data+datalen-2); if (r_data && r_datalen) { *r_data = data; *r_datalen = datalen - 2; data = NULL; } } xfree (data); } } return err; } /* 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++; /* FIXME: Should we use has_leading_keyword? */ 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; } /* For historical reasons OpenPGP cards simply use the numbers 1 to 3 * for the . Other cards and future versions of * scd/app-openpgp.c may print the full keyref; i.e. "OpenPGP.1" * instead of "1". This is a helper to cope with that. */ static const char * parse_keyref_helper (const char *string) { if (*string == '1' && spacep (string+1)) return "OPENPGP.1"; else if (*string == '2' && spacep (string+1)) return "OPENPGP.2"; else if (*string == '3' && spacep (string+1)) return "OPENPGP.3"; else return string; } /* Create a new key info object with KEYREF. All fields but the * keyref are zeroed out. Never returns NULL. The created object is * appended to the list at INFO. */ static key_info_t create_kinfo (card_info_t info, const char *keyref) { key_info_t kinfo, ki; kinfo = xcalloc (1, sizeof *kinfo + strlen (keyref)); strcpy (kinfo->keyref, keyref); if (!info->kinfo) info->kinfo = kinfo; else { for (ki=info->kinfo; ki->next; ki = ki->next) ; ki->next = kinfo; } return kinfo; } /* The status callback to handle the LEARN and GETATTR commands. */ static gpg_error_t learn_status_cb (void *opaque, const char *line) { struct card_info_s *parm = opaque; const char *keyword = line; int keywordlen; char *line_buffer = NULL; /* In case we need a copy. */ char *pline; key_info_t kinfo; const char *keyref; int i; for (keywordlen=0; *line && !spacep (line); line++, keywordlen++) ; while (spacep (line)) line++; switch (keywordlen) { case 3: if (!memcmp (keyword, "KDF", 3)) { parm->kdf_do_enabled = 1; } break; case 5: if (!memcmp (keyword, "UIF-", 4) && strchr("123", keyword[4])) { unsigned char *data; int no = keyword[4] - '1'; log_assert (no >= 0 && no <= 2); data = unescape_status_string (line); parm->uif[no] = (data[0] != 0xff); xfree (data); } break; case 6: if (!memcmp (keyword, "READER", keywordlen)) { xfree (parm->reader); parm->reader = unescape_status_string (line); } else if (!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, "bt")) parm->extcap.bt = 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 (!memcmp (keyword, "CA-FPR", keywordlen)) { int no = atoi (line); while (*line && !spacep (line)) line++; while (spacep (line)) line++; if (no == 1) parm->cafpr1len = unhexify_fpr (line, parm->cafpr1, sizeof parm->cafpr1); else if (no == 2) parm->cafpr2len = unhexify_fpr (line, parm->cafpr2, sizeof parm->cafpr2); else if (no == 3) parm->cafpr3len = unhexify_fpr (line, parm->cafpr3, sizeof parm->cafpr3); } break; case 7: if (!memcmp (keyword, "APPTYPE", keywordlen)) { xfree (parm->apptypestr); parm->apptypestr = unescape_status_string (line); parm->apptype = map_apptypestr (parm->apptypestr); } else if (!memcmp (keyword, "KEY-FPR", keywordlen)) { /* The format of such a line is: * KEY-FPR */ const char *fpr; line_buffer = pline = xstrdup (line); keyref = parse_keyref_helper (pline); while (*pline && !spacep (pline)) pline++; if (*pline) *pline++ = 0; /* Terminate keyref. */ while (spacep (pline)) /* Skip to the fingerprint. */ pline++; fpr = pline; /* Check whether we already have an item for the keyref. */ kinfo = find_kinfo (parm, keyref); if (!kinfo) /* No: new entry. */ kinfo = create_kinfo (parm, keyref); else /* Existing entry - clear the fpr. */ memset (kinfo->fpr, 0, sizeof kinfo->fpr); /* Set or update or the fingerprint. */ kinfo->fprlen = unhexify_fpr (fpr, kinfo->fpr, sizeof kinfo->fpr); } break; case 8: if (!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 (!memcmp (keyword, "CARDTYPE", keywordlen)) { xfree (parm->cardtype); parm->cardtype = unescape_status_string (line); } else if (!memcmp (keyword, "DISP-SEX", keywordlen)) { parm->disp_sex = *line == '1'? 1 : *line == '2' ? 2: 0; } else if (!memcmp (keyword, "KEY-TIME", keywordlen)) { /* The format of such a line is: * KEY-TIME */ const char *timestamp; line_buffer = pline = xstrdup (line); keyref = parse_keyref_helper (pline); while (*pline && !spacep (pline)) pline++; if (*pline) *pline++ = 0; /* Terminate keyref. */ while (spacep (pline)) /* Skip to the timestamp. */ pline++; timestamp = pline; /* Check whether we already have an item for the keyref. */ kinfo = find_kinfo (parm, keyref); if (!kinfo) /* No: new entry. */ kinfo = create_kinfo (parm, keyref); kinfo->created = strtoul (timestamp, NULL, 10); } else if (!memcmp (keyword, "KEY-ATTR", keywordlen)) { int keyno = 0; int algo = GCRY_PK_RSA; int n = 0; sscanf (line, "%d %d %n", &keyno, &algo, &n); keyno--; if (keyno < 0 || keyno >= DIM (parm->key_attr)) ; /* Out of range - ignore. */ else { 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); } } } break; case 9: if (!memcmp (keyword, "DISP-NAME", keywordlen)) { xfree (parm->disp_name); parm->disp_name = unescape_status_string (line); } else if (!memcmp (keyword, "DISP-LANG", keywordlen)) { xfree (parm->disp_lang); parm->disp_lang = unescape_status_string (line); } else if (!memcmp (keyword, "CHV-USAGE", keywordlen)) { unsigned int byte1, byte2; byte1 = byte2 = 0; sscanf (line, "%x %x", &byte1, &byte2); parm->chvusage[0] = byte1; parm->chvusage[1] = byte2; } - break; + break; case 10: if (!memcmp (keyword, "PUBKEY-URL", keywordlen)) { xfree (parm->pubkey_url); parm->pubkey_url = unescape_status_string (line); } else if (!memcmp (keyword, "LOGIN-DATA", keywordlen)) { xfree (parm->login_data); parm->login_data = unescape_status_string (line); } else if (!memcmp (keyword, "CHV-STATUS", keywordlen)) { char *p, *buf; buf = p = unescape_status_string (line); if (buf) while (spacep (p)) p++; if (!buf) ; else if (parm->apptype == APP_TYPE_OPENPGP) { 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->chvinfo[i] = atoi (p); while (*p && !spacep (p)) p++; while (spacep (p)) p++; } } else if (parm->apptype == APP_TYPE_PIV) { for (i=0; *p && i < DIM (parm->chvinfo); i++) { parm->chvinfo[i] = atoi (p); while (*p && !spacep (p)) p++; while (spacep (p)) p++; } } xfree (buf); } + else if (!memcmp (keyword, "APPVERSION", keywordlen)) + { + unsigned int val = 0; + + sscanf (line, "%x", &val); + parm->appversion = val; + } break; case 11: if (!memcmp (keyword, "SIG-COUNTER", keywordlen)) { parm->sig_counter = strtoul (line, NULL, 0); } else if (!memcmp (keyword, "KEYPAIRINFO", keywordlen)) { /* The format of such a line is: * KEYPAIRINFO [usage] */ char *hexgrp, *usage; line_buffer = pline = xstrdup (line); hexgrp = pline; while (*pline && !spacep (pline)) pline++; while (spacep (pline)) pline++; keyref = pline; while (*pline && !spacep (pline)) pline++; if (*pline) { *pline++ = 0; while (spacep (pline)) pline++; usage = pline; while (*pline && !spacep (pline)) pline++; *pline = 0; } else usage = ""; /* Check whether we already have an item for the keyref. */ kinfo = find_kinfo (parm, keyref); if (!kinfo) /* New entry. */ kinfo = create_kinfo (parm, keyref); else /* Existing entry - clear grip and usage */ { memset (kinfo->grip, 0, sizeof kinfo->grip); kinfo->usage = 0; } /* Set or update the grip. Note that due to the * calloc/memset an erroneous too short grip will be nul * padded on the right. */ unhexify_fpr (hexgrp, kinfo->grip, sizeof kinfo->grip); /* Parse and set the usage. */ for (; *usage; usage++) { switch (*usage) { case 's': kinfo->usage |= GCRY_PK_USAGE_SIGN; break; case 'c': kinfo->usage |= GCRY_PK_USAGE_CERT; break; case 'a': kinfo->usage |= GCRY_PK_USAGE_AUTH; break; case 'e': kinfo->usage |= GCRY_PK_USAGE_ENCR; break; } } } + else if (!memcmp (keyword, "CARDVERSION", keywordlen)) + { + unsigned int val = 0; + + sscanf (line, "%x", &val); + parm->cardversion = val; + } break; case 12: if (!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); } break; case 13: if (!memcmp (keyword, "$DISPSERIALNO", keywordlen)) { xfree (parm->dispserialno); parm->dispserialno = unescape_status_string (line); } break; default: /* Unknown. */ break; } xfree (line_buffer); return 0; } /* Call the scdaemon to learn about a smartcard. This fills INFO * wioth data from the card. */ gpg_error_t scd_learn (card_info_t info) { gpg_error_t err; struct default_inq_parm_s parm; struct card_info_s dummyinfo; if (!info) info = &dummyinfo; memset (info, 0, sizeof *info); memset (&parm, 0, sizeof parm); err = start_agent (0); if (err) return err; parm.ctx = agent_ctx; err = assuan_transact (agent_ctx, "SCD LEARN --force", dummy_data_cb, NULL, default_inq_cb, &parm, learn_status_cb, info); /* Also try to get some other key attributes. */ if (!err) { info->initialized = 1; err = scd_getattr ("KEY-ATTR", info); if (gpg_err_code (err) == GPG_ERR_INV_NAME || gpg_err_code (err) == GPG_ERR_UNSUPPORTED_OPERATION) err = 0; /* Not implemented or GETATTR not supported. */ err = scd_getattr ("$DISPSERIALNO", info); if (gpg_err_code (err) == GPG_ERR_INV_NAME || gpg_err_code (err) == GPG_ERR_UNSUPPORTED_OPERATION) err = 0; /* Not implemented or GETATTR not supported. */ } if (info == &dummyinfo) release_card_info (info); 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. */ gpg_error_t scd_getattr (const char *name, struct card_info_s *info) { gpg_error_t err; 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); err = start_agent (0); if (err) return err; parm.ctx = agent_ctx; err = assuan_transact (agent_ctx, line, NULL, NULL, default_inq_cb, &parm, learn_status_cb, info); return err; } /* Send an setattr command to the SCdaemon. */ gpg_error_t scd_setattr (const char *name, const unsigned char *value, size_t valuelen) { gpg_error_t err; char *tmp; char *line = NULL; struct default_inq_parm_s parm; if (!*name || !valuelen) return gpg_error (GPG_ERR_INV_VALUE); tmp = strconcat ("SCD SETATTR ", name, " ", NULL); if (!tmp) { err = gpg_error_from_syserror (); goto leave; } line = percent_data_escape (1, tmp, value, valuelen); xfree (tmp); if (!line) { err = gpg_error_from_syserror (); goto leave; } if (strlen (line) + 10 > ASSUAN_LINELENGTH) { err = gpg_error (GPG_ERR_TOO_LARGE); goto leave; } err = start_agent (0); if (err ) goto leave; memset (&parm, 0, sizeof parm); parm.ctx = agent_ctx; err = assuan_transact (agent_ctx, line, NULL, NULL, default_inq_cb, &parm, NULL, NULL); leave: xfree (line); return status_sc_op_failure (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) { gpg_error_t err; struct writecert_parm_s *parm = opaque; if (has_leading_keyword (line, "CERTDATA")) { err = assuan_send_data (parm->dflt->ctx, parm->certdata, parm->certdatalen); } else err = default_inq_cb (parm->dflt, line); return err; } /* Send a WRITECERT command to the SCdaemon. */ gpg_error_t scd_writecert (const char *certidstr, const unsigned char *certdata, size_t certdatalen) { gpg_error_t err; char line[ASSUAN_LINELENGTH]; struct writecert_parm_s parms; struct default_inq_parm_s dfltparm; memset (&dfltparm, 0, sizeof dfltparm); err = start_agent (0); if (err) return err; memset (&parms, 0, sizeof parms); snprintf (line, sizeof line, "SCD WRITECERT %s", certidstr); dfltparm.ctx = agent_ctx; parms.dflt = &dfltparm; parms.certdata = certdata; parms.certdatalen = certdatalen; err = assuan_transact (agent_ctx, line, NULL, NULL, inq_writecert_parms, &parms, NULL, NULL); return status_sc_op_failure (err); } /* Send a WRITEKEY command to the agent (so that the agent can fetch * the key to write). KEYGRIP is the hexified keygrip of the source * key which will be written to tye slot KEYREF. FORCE must be true * to overwrite an existing key. */ gpg_error_t scd_writekey (const char *keyref, int force, const char *keygrip) { gpg_error_t err; struct default_inq_parm_s parm; char line[ASSUAN_LINELENGTH]; memset (&parm, 0, sizeof parm); err = start_agent (0); if (err) return err; /* Note: We don't send the s/n but "-" because gpg-agent has * currently no use for it. */ /* FIXME: For OpenPGP we should provide the creation time. */ snprintf (line, sizeof line, "KEYTOCARD%s %s - %s", force? " --force":"", keygrip, keyref); err = assuan_transact (agent_ctx, line, NULL, NULL, default_inq_cb, &parm, NULL, NULL); return status_sc_op_failure (err); } /* 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)) { if (createtime) *createtime = (u32)strtoul (line, NULL, 10); } else if (keywordlen == 8 && !memcmp (keyword, "PROGRESS", keywordlen)) { gnupg_status_printf (STATUS_PROGRESS, "%s", 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. */ gpg_error_t scd_genkey (const char *keyref, int force, const char *algo, u32 *createtime) { gpg_error_t err; char line[ASSUAN_LINELENGTH]; gnupg_isotime_t tbuf; struct default_inq_parm_s dfltparm; memset (&dfltparm, 0, sizeof dfltparm); err = start_agent (0); if (err) return err; if (createtime && *createtime) epoch2isotime (tbuf, *createtime); else *tbuf = 0; snprintf (line, sizeof line, "SCD GENKEY %s%s %s %s%s -- %s", *tbuf? "--timestamp=":"", tbuf, force? "--force":"", algo? "--algo=":"", algo? algo:"", keyref); dfltparm.ctx = agent_ctx; err = assuan_transact (agent_ctx, line, NULL, NULL, default_inq_cb, &dfltparm, scd_genkey_cb, createtime); return status_sc_op_failure (err); } /* Return the serial number of the card or an appropriate error. The * serial number is returned as a hexstring. If DEMAND is not NULL * the reader with the a card of the serilanumber DEMAND is * requested. */ gpg_error_t scd_serialno (char **r_serialno, const char *demand) { int err; char *serialno = NULL; char line[ASSUAN_LINELENGTH]; err = start_agent (START_AGENT_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. */ gpg_error_t scd_readcert (const char *certidstr, void **r_buf, size_t *r_buflen) { gpg_error_t err; char line[ASSUAN_LINELENGTH]; membuf_t data; size_t len; struct default_inq_parm_s dfltparm; memset (&dfltparm, 0, sizeof dfltparm); *r_buf = NULL; err = start_agent (0); if (err) return err; dfltparm.ctx = agent_ctx; init_membuf (&data, 2048); snprintf (line, sizeof line, "SCD READCERT %s", certidstr); 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; } *r_buf = get_membuf (&data, r_buflen); if (!*r_buf) return gpg_error_from_syserror (); return 0; } /* Send a READKEY command to the SCdaemon. On success a new * s-expression is stored at R_RESULT. */ gpg_error_t 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; *r_result = NULL; err = start_agent (0); 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, NULL, NULL, 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; } /* Callback function for 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 the serial numbers of all cards currently inserted. */ gpg_error_t scd_cardlist (strlist_t *result) { gpg_error_t err; struct card_cardlist_parm_s parm; memset (&parm, 0, sizeof parm); *result = NULL; err = start_agent (START_AGENT_SUPPRESS_ERRORS); if (err) return err; err = assuan_transact (agent_ctx, "SCD GETINFO card_list", 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 err; } /* 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. */ gpg_error_t scd_change_pin (const char *pinref, int reset_mode) { gpg_error_t err; char line[ASSUAN_LINELENGTH]; struct default_inq_parm_s dfltparm; memset (&dfltparm, 0, sizeof dfltparm); err = start_agent (0); if (err) return err; dfltparm.ctx = agent_ctx; snprintf (line, sizeof line, "SCD PASSWD%s %s", reset_mode? " --reset":"", pinref); err = assuan_transact (agent_ctx, line, NULL, NULL, default_inq_cb, &dfltparm, NULL, NULL); return status_sc_op_failure (err); } /* Perform a CHECKPIN operation. SERIALNO should be the serial * number of the card - optionally followed by the fingerprint; * however the fingerprint is ignored here. */ gpg_error_t scd_checkpin (const char *serialno) { gpg_error_t err; char line[ASSUAN_LINELENGTH]; struct default_inq_parm_s dfltparm; memset (&dfltparm, 0, sizeof dfltparm); err = start_agent (0); if (err) return err; dfltparm.ctx = agent_ctx; snprintf (line, sizeof line, "SCD CHECKPIN %s", serialno); err = assuan_transact (agent_ctx, line, NULL, NULL, default_inq_cb, &dfltparm, NULL, NULL); return status_sc_op_failure (err); } /* Return the S2K iteration count as computed by gpg-agent. On error * print a warning and return a default value. */ unsigned long agent_get_s2k_count (void) { gpg_error_t err; membuf_t data; char *buf; unsigned long count = 0; err = start_agent (0); if (err) goto leave; 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 { count = strtoul (buf, NULL, 10); xfree (buf); } } leave: if (err || count < 65536) { /* Don't print an error if an older agent is used. */ if (err && gpg_err_code (err) != GPG_ERR_ASS_PARAMETER) log_error (_("problem with the agent: %s\n"), gpg_strerror (err)); /* Default to 65536 which was used up to 2.0.13. */ count = 65536; } return count; } diff --git a/tools/gpg-card.c b/tools/gpg-card.c index bd450c0bb..89cefdeb6 100644 --- a/tools/gpg-card.c +++ b/tools/gpg-card.c @@ -1,3464 +1,3492 @@ /* gpg-card.c - An interactive tool to work with cards. * Copyright (C) 2019 g10 Code GmbH * * This file is part of GnuPG. * * This file 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. * * This file is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . * SPDX-License-Identifier: GPL-3.0-or-later */ #include #include #include #include #ifdef HAVE_LIBREADLINE # define GNUPG_LIBREADLINE_H_INCLUDED # include #endif /*HAVE_LIBREADLINE*/ #include "../common/util.h" #include "../common/status.h" #include "../common/i18n.h" #include "../common/init.h" #include "../common/sysutils.h" #include "../common/asshelp.h" #include "../common/userids.h" #include "../common/ccparray.h" #include "../common/exectool.h" #include "../common/ttyio.h" #include "../common/server-help.h" #include "../common/openpgpdefs.h" #include "gpg-card.h" #define CONTROL_D ('D' - 'A' + 1) /* Constants to identify the commands and options. */ enum opt_values { aNull = 0, oQuiet = 'q', oVerbose = 'v', oDebug = 500, oGpgProgram, oGpgsmProgram, oStatusFD, oWithColons, oNoAutostart, oAgentProgram, oDisplay, oTTYname, oTTYtype, oXauthority, oLCctype, oLCmessages, oDummy }; /* The list of commands and options. */ static ARGPARSE_OPTS opts[] = { ARGPARSE_group (301, ("@\nOptions:\n ")), ARGPARSE_s_n (oVerbose, "verbose", ("verbose")), ARGPARSE_s_n (oQuiet, "quiet", ("be somewhat more quiet")), ARGPARSE_s_s (oDebug, "debug", "@"), ARGPARSE_s_s (oGpgProgram, "gpg", "@"), ARGPARSE_s_s (oGpgsmProgram, "gpgsm", "@"), ARGPARSE_s_i (oStatusFD, "status-fd", N_("|FD|write status info to this FD")), ARGPARSE_s_n (oWithColons, "with-colons", "@"), ARGPARSE_s_n (oNoAutostart, "no-autostart", "@"), ARGPARSE_s_s (oAgentProgram, "agent-program", "@"), ARGPARSE_s_s (oDisplay, "display", "@"), ARGPARSE_s_s (oTTYname, "ttyname", "@"), ARGPARSE_s_s (oTTYtype, "ttytype", "@"), ARGPARSE_s_s (oXauthority, "xauthority", "@"), ARGPARSE_s_s (oLCctype, "lc-ctype", "@"), ARGPARSE_s_s (oLCmessages, "lc-messages","@"), ARGPARSE_end () }; /* The list of supported debug flags. */ static struct debug_flags_s debug_flags [] = { { DBG_IPC_VALUE , "ipc" }, { DBG_EXTPROG_VALUE, "extprog" }, { 0, NULL } }; /* An object to create lists of labels and keyrefs. */ struct keyinfolabel_s { const char *label; const char *keyref; }; typedef struct keyinfolabel_s *keyinfolabel_t; /* Limit of size of data we read from a file for certain commands. */ #define MAX_GET_DATA_FROM_FILE 16384 /* Constants for OpenPGP cards. */ #define OPENPGP_USER_PIN_DEFAULT "123456" #define OPENPGP_ADMIN_PIN_DEFAULT "12345678" #define OPENPGP_KDF_DATA_LENGTH_MIN 90 #define OPENPGP_KDF_DATA_LENGTH_MAX 110 /* Local prototypes. */ static gpg_error_t dispatch_command (card_info_t info, const char *command); static void interactive_loop (void); #ifdef HAVE_LIBREADLINE static char **command_completion (const char *text, int start, int end); #endif /*HAVE_LIBREADLINE*/ /* Print usage information and provide strings for help. */ static const char * my_strusage( int level ) { const char *p; switch (level) { case 11: p = "gpg-card"; break; case 12: p = "@GNUPG@"; break; case 13: p = VERSION; break; case 17: p = PRINTABLE_OS_NAME; break; case 19: p = ("Please report bugs to <@EMAIL@>.\n"); break; case 1: case 40: p = ("Usage: gpg-card" " [options] [{[--] command [args]}] (-h for help)"); break; case 41: p = ("Syntax: gpg-card" " [options] [command [args] {-- command [args]}]\n\n" "Tool to manage cards and tokens. With a command an interactive\n" "mode is used. Use command \"help\" to list all commands."); break; default: p = NULL; break; } return p; } static void set_opt_session_env (const char *name, const char *value) { gpg_error_t err; err = session_env_setenv (opt.session_env, name, value); if (err) log_fatal ("error setting session environment: %s\n", gpg_strerror (err)); } /* Command line parsing. */ static void parse_arguments (ARGPARSE_ARGS *pargs, ARGPARSE_OPTS *popts) { while (optfile_parse (NULL, NULL, NULL, pargs, popts)) { switch (pargs->r_opt) { case oQuiet: opt.quiet = 1; break; case oVerbose: opt.verbose++; break; case oDebug: if (parse_debug_flag (pargs->r.ret_str, &opt.debug, debug_flags)) { pargs->r_opt = ARGPARSE_INVALID_ARG; pargs->err = ARGPARSE_PRINT_ERROR; } break; case oGpgProgram: opt.gpg_program = pargs->r.ret_str; break; case oGpgsmProgram: opt.gpgsm_program = pargs->r.ret_str; break; case oAgentProgram: opt.agent_program = pargs->r.ret_str; break; case oStatusFD: gnupg_set_status_fd (translate_sys2libc_fd_int (pargs->r.ret_int, 1)); break; case oWithColons: opt.with_colons = 1; break; case oNoAutostart: opt.autostart = 0; break; case oDisplay: set_opt_session_env ("DISPLAY", pargs->r.ret_str); break; case oTTYname: set_opt_session_env ("GPG_TTY", pargs->r.ret_str); break; case oTTYtype: set_opt_session_env ("TERM", pargs->r.ret_str); break; case oXauthority: set_opt_session_env ("XAUTHORITY", pargs->r.ret_str); break; case oLCctype: opt.lc_ctype = pargs->r.ret_str; break; case oLCmessages: opt.lc_messages = pargs->r.ret_str; break; default: pargs->err = 2; break; } } } /* gpg-card main. */ int main (int argc, char **argv) { gpg_error_t err; ARGPARSE_ARGS pargs; char **command_list = NULL; int cmdidx; char *command; gnupg_reopen_std ("gpg-card"); set_strusage (my_strusage); gnupg_rl_initialize (); log_set_prefix ("gpg-card", GPGRT_LOG_WITH_PREFIX); /* Make sure that our subsystems are ready. */ i18n_init(); init_common_subsystems (&argc, &argv); assuan_set_gpg_err_source (GPG_ERR_SOURCE_DEFAULT); setup_libassuan_logging (&opt.debug, NULL); /* Setup default options. */ opt.autostart = 1; opt.session_env = session_env_new (); if (!opt.session_env) log_fatal ("error allocating session environment block: %s\n", gpg_strerror (gpg_error_from_syserror ())); /* Parse the command line. */ pargs.argc = &argc; pargs.argv = &argv; pargs.flags = ARGPARSE_FLAG_KEEP; parse_arguments (&pargs, opts); if (log_get_errorcount (0)) exit (2); /* Set defaults for non given options. */ if (!opt.gpg_program) opt.gpg_program = gnupg_module_name (GNUPG_MODULE_NAME_GPG); if (!opt.gpgsm_program) opt.gpgsm_program = gnupg_module_name (GNUPG_MODULE_NAME_GPGSM); /* Now build the list of commands. We guess the size of the array * by assuming each item is a complete command. Obviously this will * be rarely the case, but it is less code to allocate a possible * too large array. */ command_list = xcalloc (argc+1, sizeof *command_list); cmdidx = 0; command = NULL; while (argc) { for ( ; argc && strcmp (*argv, "--"); argc--, argv++) { if (!command) command = xstrdup (*argv); else { char *tmp = xstrconcat (command, " ", *argv, NULL); xfree (command); command = tmp; } } if (argc) { /* Skip the double dash. */ argc--; argv++; } if (command) { command_list[cmdidx++] = command; command = NULL; } } opt.interactive = !cmdidx; if (opt.interactive) { interactive_loop (); err = 0; } else { struct card_info_s info_buffer = { 0 }; card_info_t info = &info_buffer; err = 0; for (cmdidx=0; (command = command_list[cmdidx]); cmdidx++) { err = dispatch_command (info, command); if (err) break; } if (gpg_err_code (err) == GPG_ERR_EOF) err = 0; /* This was a "quit". */ else if (command && !opt.quiet) log_info ("stopped at command '%s'\n", command); } flush_keyblock_cache (); if (command_list) { for (cmdidx=0; command_list[cmdidx]; cmdidx++) xfree (command_list[cmdidx]); xfree (command_list); } if (err) gnupg_status_printf (STATUS_FAILURE, "- %u", err); else if (log_get_errorcount (0)) gnupg_status_printf (STATUS_FAILURE, "- %u", GPG_ERR_GENERAL); else gnupg_status_printf (STATUS_SUCCESS, NULL); return log_get_errorcount (0)? 1:0; } /* Read data from file FNAME up to MAX_GET_DATA_FROM_FILE characters. * On error return an error code and stores NULL at R_BUFFER; on * success returns 0 and stores the number of bytes read at R_BUFLEN * and the address of a newly allocated buffer at R_BUFFER. A * complementary nul byte is always appended to the data but not * counted; this allows to pass NULL for R-BUFFER and consider the * returned data as a string. */ static gpg_error_t get_data_from_file (const char *fname, char **r_buffer, size_t *r_buflen) { gpg_error_t err; estream_t fp; char *data; int n; *r_buffer = NULL; if (r_buflen) *r_buflen = 0; fp = es_fopen (fname, "rb"); if (!fp) { err = gpg_error_from_syserror (); log_error (_("can't open '%s': %s\n"), fname, gpg_strerror (err)); return err; } data = xtrymalloc (MAX_GET_DATA_FROM_FILE); if (!data) { err = gpg_error_from_syserror (); log_error (_("error allocating enough memory: %s\n"), gpg_strerror (err)); es_fclose (fp); return err; } n = es_fread (data, 1, MAX_GET_DATA_FROM_FILE - 1, fp); es_fclose (fp); if (n < 0) { err = gpg_error_from_syserror (); tty_printf (_("error reading '%s': %s\n"), fname, gpg_strerror (err)); xfree (data); return err; } data[n] = 0; *r_buffer = data; if (r_buflen) *r_buflen = n; return 0; } /* Write LENGTH bytes from BUFFER to file FNAME. Return 0 on * success. */ static gpg_error_t put_data_to_file (const char *fname, const void *buffer, size_t length) { gpg_error_t err; estream_t fp; fp = es_fopen (fname, "wb"); if (!fp) { err = gpg_error_from_syserror (); log_error (_("can't create '%s': %s\n"), fname, gpg_strerror (err)); return err; } if (length && es_fwrite (buffer, length, 1, fp) != 1) { err = gpg_error_from_syserror (); log_error (_("error writing '%s': %s\n"), fname, gpg_strerror (err)); es_fclose (fp); return err; } if (es_fclose (fp)) { err = gpg_error_from_syserror (); log_error (_("error writing '%s': %s\n"), fname, gpg_strerror (err)); return err; } return 0; } /* Simply prints TEXT to the output. Returns 0 as a convenience. * This is a separate fucntion so that it can be extended to run * less(1) or so. The extra arguments are int values terminated by a * 0 to indicate card application types supported with this command. * If none are given (just teh final 0), this is a general * command. */ static gpg_error_t print_help (const char *text, ...) { estream_t fp; va_list arg_ptr; int value; int any = 0; fp = opt.interactive? NULL : es_stdout; tty_fprintf (fp, "%s\n", text); va_start (arg_ptr, text); while ((value = va_arg (arg_ptr, int))) { if (!any) tty_fprintf (fp, "[Supported by: "); tty_fprintf (fp, "%s%s", any?", ":"", app_type_string (value)); any = 1; } if (any) tty_fprintf (fp, "]\n"); va_end (arg_ptr); return 0; } /* Return the OpenPGP card manufacturer name. */ static const char * get_manufacturer (unsigned int no) { /* Note: Make sure that there is no colon or linefeed in the string. */ switch (no) { case 0x0001: return "PPC Card Systems"; case 0x0002: return "Prism"; case 0x0003: return "OpenFortress"; case 0x0004: return "Wewid"; case 0x0005: return "ZeitControl"; case 0x0006: return "Yubico"; case 0x0007: return "OpenKMS"; case 0x0008: return "LogoEmail"; case 0x0009: return "Fidesmo"; case 0x000A: return "Dangerous Things"; case 0x002A: return "Magrathea"; case 0x0042: return "GnuPG e.V."; case 0x1337: return "Warsaw Hackerspace"; case 0x2342: return "warpzone"; /* hackerspace Muenster. */ case 0x4354: return "Confidential Technologies"; /* cotech.de */ case 0x63AF: return "Trustica"; case 0xBD0E: return "Paranoidlabs"; case 0xF517: return "FSIJ"; /* 0x0000 and 0xFFFF are defined as test cards per spec, * 0xFF00 to 0xFFFE are assigned for use with randomly created * serial numbers. */ case 0x0000: case 0xffff: return "test card"; default: return (no & 0xff00) == 0xff00? "unmanaged S/N range":"unknown"; } } /* Print an (OpenPGP) fingerprint. */ static void print_shax_fpr (estream_t fp, const unsigned char *fpr, unsigned int fprlen) { int i; if (fpr) { /* FIXME: Fix formatting for FPRLEN != 20 */ for (i=0; i < fprlen ; i+=2, fpr += 2 ) { if (i == 10 ) tty_fprintf (fp, " "); tty_fprintf (fp, " %02X%02X", *fpr, fpr[1]); } } else tty_fprintf (fp, " [none]"); tty_fprintf (fp, "\n"); } /* Print the keygrip GRP. */ static void print_keygrip (estream_t fp, const unsigned char *grp) { int i; for (i=0; i < 20 ; i++, grp++) tty_fprintf (fp, "%02X", *grp); tty_fprintf (fp, "\n"); } /* Print a string but avoid printing control characters. */ static void print_string (estream_t fp, const char *text, const char *name) { tty_fprintf (fp, "%s", text); /* FIXME: tty_printf_utf8_string2 eats everything after and including an @ - e.g. when printing an url. */ if (name && *name) { if (fp) print_utf8_buffer2 (fp, name, strlen (name), '\n'); else tty_print_utf8_string2 (NULL, name, strlen (name), 0); } else tty_fprintf (fp, _("[not set]")); tty_fprintf (fp, "\n"); } /* Print an ISO formatted name or "[not set]". */ static void print_isoname (estream_t fp, const char *name) { if (name && *name) { char *p, *given, *buf; buf = xstrdup (name); given = strstr (buf, "<<"); for (p=buf; *p; p++) if (*p == '<') *p = ' '; if (given && given[2]) { *given = 0; given += 2; if (fp) print_utf8_buffer2 (fp, given, strlen (given), '\n'); else tty_print_utf8_string2 (NULL, given, strlen (given), 0); if (*buf) tty_fprintf (fp, " "); } if (fp) print_utf8_buffer2 (fp, buf, strlen (buf), '\n'); else tty_print_utf8_string2 (NULL, buf, strlen (buf), 0); xfree (buf); } else { tty_fprintf (fp, _("[not set]")); } tty_fprintf (fp, "\n"); } /* Return true if the buffer MEM of length memlen consists only of zeroes. */ static int mem_is_zero (const char *mem, unsigned int memlen) { int i; for (i=0; i < memlen && !mem[i]; i++) ; return (i == memlen); } /* Helper to list a single keyref. */ static void list_one_kinfo (key_info_t firstkinfo, key_info_t kinfo, estream_t fp) { gpg_error_t err; keyblock_t keyblock = NULL; keyblock_t kb; pubkey_t pubkey; userid_t uid; key_info_t ki; const char *s; gcry_sexp_t s_pkey; int any; if (firstkinfo && kinfo) { tty_fprintf (fp, " "); if (mem_is_zero (kinfo->grip, sizeof kinfo->grip)) { tty_fprintf (fp, "[none]\n"); goto leave; } print_keygrip (fp, kinfo->grip); tty_fprintf (fp, " keyref .....: %s", kinfo->keyref); if (kinfo->usage) { any = 0; tty_fprintf (fp, " ("); if ((kinfo->usage & GCRY_PK_USAGE_SIGN)) { tty_fprintf (fp, "sign"); any=1; } if ((kinfo->usage & GCRY_PK_USAGE_CERT)) { tty_fprintf (fp, "%scert", any?",":""); any=1; } if ((kinfo->usage & GCRY_PK_USAGE_AUTH)) { tty_fprintf (fp, "%sauth", any?",":""); any=1; } if ((kinfo->usage & GCRY_PK_USAGE_ENCR)) { tty_fprintf (fp, "%sencr", any?",":""); any=1; } tty_fprintf (fp, ")"); } tty_fprintf (fp, "\n"); if (!scd_readkey (kinfo->keyref, &s_pkey)) { char *tmp = pubkey_algo_string (s_pkey); tty_fprintf (fp, " algorithm ..: %s\n", tmp); xfree (tmp); gcry_sexp_release (s_pkey); s_pkey = NULL; } if (kinfo->fprlen && kinfo->created) { tty_fprintf (fp, " fingerprint :"); print_shax_fpr (fp, kinfo->fpr, kinfo->fprlen); tty_fprintf (fp, " created ....: %s\n", isotimestamp (kinfo->created)); } err = get_matching_keys (kinfo->grip, (GNUPG_PROTOCOL_OPENPGP | GNUPG_PROTOCOL_CMS), &keyblock); if (err) { if (gpg_err_code (err) != GPG_ERR_NO_PUBKEY) tty_fprintf (fp, " error ......: %s\n", gpg_strerror (err)); goto leave; } for (kb = keyblock; kb; kb = kb->next) { tty_fprintf (fp, " used for ...: %s\n", kb->protocol == GNUPG_PROTOCOL_OPENPGP? "OpenPGP" : kb->protocol == GNUPG_PROTOCOL_CMS? "X.509" : "?"); pubkey = kb->keys; /* If this is not the primary key print the primary key's * fingerprint or a reference to it. */ if (kb->protocol == GNUPG_PROTOCOL_OPENPGP) { tty_fprintf (fp, " main key .:"); for (ki=firstkinfo; ki; ki = ki->next) if (pubkey->grip_valid && !memcmp (ki->grip, pubkey->grip, KEYGRIP_LEN)) break; if (ki) { /* Fixme: Replace mapping by a table lookup. */ if (!memcmp (kinfo->grip, pubkey->grip, KEYGRIP_LEN)) s = "this"; else if (!strcmp (ki->keyref, "OPENPGP.1")) s = "Signature key"; else if (!strcmp (ki->keyref, "OPENPGP.2")) s = "Encryption key"; else if (!strcmp (ki->keyref, "OPENPGP.3")) s = "Authentication key"; else s = NULL; if (s) tty_fprintf (fp, " <%s>\n", s); else tty_fprintf (fp, " \n", ki->keyref); } else print_shax_fpr (fp, pubkey->fpr, pubkey->fprlen); } for (uid = kb->uids; uid; uid = uid->next) { print_string (fp, " user id ..: ", uid->value); } } } else tty_fprintf (fp, " [none]\n"); leave: release_keyblock (keyblock); } /* List all keyinfo in INFO using the list of LABELS. */ static void list_all_kinfo (card_info_t info, keyinfolabel_t labels, estream_t fp) { key_info_t kinfo; int idx, i; /* Print the keyinfo. We first print those we known and then all * remaining item. */ for (kinfo = info->kinfo; kinfo; kinfo = kinfo->next) kinfo->xflag = 0; if (labels) { for (idx=0; labels[idx].label; idx++) { tty_fprintf (fp, "%s", labels[idx].label); kinfo = find_kinfo (info, labels[idx].keyref); list_one_kinfo (info->kinfo, kinfo, fp); if (kinfo) kinfo->xflag = 1; } } for (kinfo = info->kinfo; kinfo; kinfo = kinfo->next) { if (kinfo->xflag) continue; tty_fprintf (fp, "Key %s ", kinfo->keyref); for (i=5+strlen (kinfo->keyref); i < 18; i++) tty_fprintf (fp, "."); tty_fprintf (fp, ":"); list_one_kinfo (info->kinfo, kinfo, fp); } } /* List OpenPGP card specific data. */ static void list_openpgp (card_info_t info, estream_t fp) { static struct keyinfolabel_s keyinfolabels[] = { { "Signature key ....:", "OPENPGP.1" }, { "Encryption key....:", "OPENPGP.2" }, { "Authentication key:", "OPENPGP.3" }, { NULL, NULL } }; int i; if (!info->serialno || strncmp (info->serialno, "D27600012401", 12) || strlen (info->serialno) != 32 ) { tty_fprintf (fp, "invalid OpenPGP card\n"); return; } - tty_fprintf (fp, "Version ..........: %.1s%c.%.1s%c\n", - info->serialno[12] == '0'?"":info->serialno+12, - info->serialno[13], - info->serialno[14] == '0'?"":info->serialno+14, - info->serialno[15]); tty_fprintf (fp, "Manufacturer .....: %s\n", get_manufacturer (xtoi_2(info->serialno+16)*256 + xtoi_2 (info->serialno+18))); tty_fprintf (fp, "Name of cardholder: "); print_isoname (fp, info->disp_name); print_string (fp, "Language prefs ...: ", info->disp_lang); tty_fprintf (fp, "Salutation .......: %s\n", info->disp_sex == 1? _("Mr."): info->disp_sex == 2? _("Mrs.") : ""); print_string (fp, "URL of public key : ", info->pubkey_url); print_string (fp, "Login data .......: ", info->login_data); if (info->private_do[0]) print_string (fp, "Private DO 1 .....: ", info->private_do[0]); if (info->private_do[1]) print_string (fp, "Private DO 2 .....: ", info->private_do[1]); if (info->private_do[2]) print_string (fp, "Private DO 3 .....: ", info->private_do[2]); if (info->private_do[3]) print_string (fp, "Private DO 4 .....: ", info->private_do[3]); if (info->cafpr1len) { tty_fprintf (fp, "CA fingerprint %d .:", 1); print_shax_fpr (fp, info->cafpr1, info->cafpr1len); } if (info->cafpr2len) { tty_fprintf (fp, "CA fingerprint %d .:", 2); print_shax_fpr (fp, info->cafpr2, info->cafpr2len); } if (info->cafpr3len) { tty_fprintf (fp, "CA fingerprint %d .:", 3); print_shax_fpr (fp, info->cafpr3, info->cafpr3len); } tty_fprintf (fp, "Signature PIN ....: %s\n", info->chv1_cached? _("not forced"): _("forced")); if (info->key_attr[0].algo) { tty_fprintf (fp, "Key attributes ...:"); for (i=0; i < DIM (info->key_attr); i++) if (info->key_attr[i].algo == PUBKEY_ALGO_RSA) tty_fprintf (fp, " rsa%u", info->key_attr[i].nbits); else if (info->key_attr[i].algo == PUBKEY_ALGO_ECDH || info->key_attr[i].algo == PUBKEY_ALGO_ECDSA || info->key_attr[i].algo == PUBKEY_ALGO_EDDSA) { const char *curve_for_print = "?"; const char *oid; if (info->key_attr[i].curve && (oid = openpgp_curve_to_oid (info->key_attr[i].curve, NULL))) curve_for_print = openpgp_oid_to_curve (oid, 0); tty_fprintf (fp, " %s", curve_for_print); } tty_fprintf (fp, "\n"); } tty_fprintf (fp, "Max. PIN lengths .: %d %d %d\n", info->chvmaxlen[0], info->chvmaxlen[1], info->chvmaxlen[2]); tty_fprintf (fp, "PIN retry counter : %d %d %d\n", info->chvinfo[0], info->chvinfo[1], info->chvinfo[2]); tty_fprintf (fp, "Signature counter : %lu\n", info->sig_counter); if (info->extcap.kdf) { tty_fprintf (fp, "KDF setting ......: %s\n", info->kdf_do_enabled ? "on" : "off"); } if (info->extcap.bt) { tty_fprintf (fp, "UIF setting ......: Sign=%s Decrypt=%s Auth=%s\n", info->uif[0] ? "on" : "off", info->uif[1] ? "on" : "off", info->uif[2] ? "on" : "off"); } list_all_kinfo (info, keyinfolabels, fp); /* tty_fprintf (fp, "General key info->.: "); */ /* thefpr = (info->fpr1len? info->fpr1 : info->fpr2len? info->fpr2 : */ /* info->fpr3len? info->fpr3 : NULL); */ /* thefprlen = (info->fpr1len? info->fpr1len : info->fpr2len? info->fpr2len : */ /* info->fpr3len? info->fpr3len : 0); */ /* If the fingerprint is all 0xff, the key has no associated OpenPGP certificate. */ /* if ( thefpr && !mem_is_ff (thefpr, thefprlen) */ /* && !get_pubkey_byfprint (ctrl, pk, &keyblock, thefpr, thefprlen)) */ /* { */ /* print_pubkey_info (ctrl, fp, pk); */ /* if (keyblock) */ /* print_card_key_info (fp, keyblock); */ /* } */ /* else */ /* tty_fprintf (fp, "[none]\n"); */ } /* List PIV card specific data. */ static void list_piv (card_info_t info, estream_t fp) { static struct keyinfolabel_s keyinfolabels[] = { { "PIV authentication:", "PIV.9A" }, { "Card authenticat. :", "PIV.9E" }, { "Digital signature :", "PIV.9C" }, { "Key management ...:", "PIV.9D" }, { NULL, NULL } }; const char *s; int i; if (info->chvusage[0] || info->chvusage[1]) { tty_fprintf (fp, "PIN usage policy .:"); if ((info->chvusage[0] & 0x40)) tty_fprintf (fp, " app-pin"); if ((info->chvusage[0] & 0x20)) tty_fprintf (fp, " global-pin"); if ((info->chvusage[0] & 0x10)) tty_fprintf (fp, " occ"); if ((info->chvusage[0] & 0x08)) tty_fprintf (fp, " vci"); if ((info->chvusage[0] & 0x08) && !(info->chvusage[0] & 0x04)) tty_fprintf (fp, " pairing"); if (info->chvusage[1] == 0x10) tty_fprintf (fp, " primary:card"); else if (info->chvusage[1] == 0x20) tty_fprintf (fp, " primary:global"); tty_fprintf (fp, "\n"); } tty_fprintf (fp, "PIN retry counter :"); for (i=0; i < DIM (info->chvinfo); i++) { if (info->chvinfo[i] > 0) tty_fprintf (fp, " %d", info->chvinfo[i]); else { switch (info->chvinfo[i]) { case -1: s = "[error]"; break; case -2: s = "-"; break; /* No such PIN or info not available. */ case -3: s = "[blocked]"; break; case -5: s = "[verified]"; break; default: s = "[?]"; break; } tty_fprintf (fp, " %s", s); } } tty_fprintf (fp, "\n"); list_all_kinfo (info, keyinfolabels, fp); } + +static void +print_a_version (estream_t fp, const char *prefix, unsigned int value) +{ + unsigned int a, b, c, d; + a = ((value >> 24) & 0xff); + b = ((value >> 16) & 0xff); + c = ((value >> 8) & 0xff); + d = ((value ) & 0xff); + + if (a) + tty_fprintf (fp, "%s %u.%u.%u.%u\n", prefix, a, b, c, d); + else if (b) + tty_fprintf (fp, "%s %u.%u.%u\n", prefix, b, c, d); + else + tty_fprintf (fp, "%s %u.%u\n", prefix, c, d); +} + + /* Print all available information about the current card. */ static void list_card (card_info_t info) { estream_t fp = opt.interactive? NULL : es_stdout; tty_fprintf (fp, "Reader ...........: %s\n", info->reader? info->reader : "[none]"); if (info->cardtype) tty_fprintf (fp, "Card type ........: %s\n", info->cardtype); + if (info->cardversion) + print_a_version (fp, "Card firmware ....:", info->cardversion); tty_fprintf (fp, "Serial number ....: %s\n", info->serialno? info->serialno : "[none]"); tty_fprintf (fp, "Application type .: %s%s%s%s\n", app_type_string (info->apptype), info->apptype == APP_TYPE_UNKNOWN && info->apptypestr? "(":"", info->apptype == APP_TYPE_UNKNOWN && info->apptypestr ? info->apptypestr:"", info->apptype == APP_TYPE_UNKNOWN && info->apptypestr? ")":""); + if (info->appversion) + print_a_version (fp, "Version ..........:", info->appversion); if (info->serialno && info->dispserialno && strcmp (info->serialno, info->dispserialno)) - tty_fprintf (fp, "Displayed S/N ....: %s\n", info->dispserialno); + tty_fprintf (fp, "Displayed s/n ....: %s\n", info->dispserialno); switch (info->apptype) { case APP_TYPE_OPENPGP: list_openpgp (info, fp); break; case APP_TYPE_PIV: list_piv (info, fp); break; default: break; } } /* The VERIFY command. */ static gpg_error_t cmd_verify (card_info_t info, char *argstr) { gpg_error_t err; const char *pinref; if (!info) return print_help ("verify [chvid]", 0); if (*argstr) pinref = argstr; else if (info->apptype == APP_TYPE_OPENPGP) pinref = info->serialno; else if (info->apptype == APP_TYPE_PIV) pinref = "PIV.80"; else return gpg_error (GPG_ERR_MISSING_VALUE); err = scd_checkpin (pinref); if (err) log_error ("verify failed: %s <%s>\n", gpg_strerror (err), gpg_strsource (err)); return err; } static gpg_error_t cmd_authenticate (card_info_t info, char *argstr) { gpg_error_t err; int opt_setkey; int opt_raw; char *string = NULL; char *key = NULL; size_t keylen; if (!info) return print_help ("AUTHENTICATE [--setkey] [--raw] [< FILE]|KEY\n\n" "Perform a mutual autentication either by reading the key\n" "from FILE or by taking it from the command line. Without\n" "the option --raw the key is expected to be hex encoded.\n" "To install a new administration key --setkey is used; this\n" "requires a prior authentication with the old key.", APP_TYPE_PIV, 0); if (info->apptype != APP_TYPE_PIV) { log_info ("Note: This is a PIV only command.\n"); return gpg_error (GPG_ERR_NOT_SUPPORTED); } opt_setkey = has_leading_option (argstr, "--setkey"); opt_raw = has_leading_option (argstr, "--raw"); argstr = skip_options (argstr); if (*argstr == '<') /* Read key from a file. */ { for (argstr++; spacep (argstr); argstr++) ; err = get_data_from_file (argstr, &string, NULL); if (err) goto leave; } if (opt_raw) { key = string? string : xstrdup (argstr); string = NULL; keylen = strlen (key); } else { key = hex_to_buffer (string? string: argstr, &keylen); if (!key) { err = gpg_error_from_syserror (); goto leave; } } err = scd_setattr (opt_setkey? "SET-ADM-KEY":"AUTH-ADM-KEY", key, keylen); leave: if (key) { wipememory (key, keylen); xfree (key); } xfree (string); return err; } /* Helper for cmd_name to qyery a part of name. */ static char * ask_one_name (const char *prompt) { char *name; int i; for (;;) { name = tty_get (prompt); trim_spaces (name); tty_kill_prompt (); if (!*name || *name == CONTROL_D) { if (*name == CONTROL_D) tty_fprintf (NULL, "\n"); xfree (name); return NULL; } for (i=0; name[i] && name[i] >= ' ' && name[i] <= 126; i++) ; /* The name must be in Latin-1 and not UTF-8 - lacking the code * to ensure this we restrict it to ASCII. */ if (name[i]) tty_printf (_("Error: Only plain ASCII is currently allowed.\n")); else if (strchr (name, '<')) tty_printf (_("Error: The \"<\" character may not be used.\n")); else if (strstr (name, " ")) tty_printf (_("Error: Double spaces are not allowed.\n")); else return name; xfree (name); } } /* The NAME command. */ static gpg_error_t cmd_name (card_info_t info, const char *argstr) { gpg_error_t err; char *surname, *givenname; char *isoname, *p; if (!info) return print_help ("name [--clear]\n\n" "Set the name field of an OpenPGP card. With --clear the stored\n" "name is cleared off the card.", APP_TYPE_OPENPGP, APP_TYPE_NKS, 0); if (info->apptype != APP_TYPE_OPENPGP) { log_info ("Note: This is an OpenPGP only command.\n"); return gpg_error (GPG_ERR_NOT_SUPPORTED); } again: if (!strcmp (argstr, "--clear")) isoname = xstrdup (" "); /* No real way to clear; set to space instead. */ else { surname = ask_one_name (_("Cardholder's surname: ")); givenname = ask_one_name (_("Cardholder's given name: ")); if (!surname || !givenname || (!*surname && !*givenname)) { xfree (surname); xfree (givenname); return gpg_error (GPG_ERR_CANCELED); } isoname = xstrconcat (surname, "<<", givenname, NULL); xfree (surname); xfree (givenname); for (p=isoname; *p; p++) if (*p == ' ') *p = '<'; if (strlen (isoname) > 39 ) { log_info (_("Error: Combined name too long " "(limit is %d characters).\n"), 39); xfree (isoname); goto again; } } err = scd_setattr ("DISP-NAME", isoname, strlen (isoname)); xfree (isoname); return err; } static gpg_error_t cmd_url (card_info_t info, const char *argstr) { gpg_error_t err; char *url; if (!info) return print_help ("URL [--clear]\n\n" "Set the URL data object. That data object can be used by\n" "the FETCH command to retrieve the full public key. The\n" "option --clear deletes the content of that data object.", APP_TYPE_OPENPGP, 0); if (info->apptype != APP_TYPE_OPENPGP) { log_info ("Note: This is an OpenPGP only command.\n"); return gpg_error (GPG_ERR_NOT_SUPPORTED); } if (!strcmp (argstr, "--clear")) url = xstrdup (" "); /* No real way to clear; set to space instead. */ else { url = tty_get (_("URL to retrieve public key: ")); trim_spaces (url); tty_kill_prompt (); if (!*url || *url == CONTROL_D) { err = gpg_error (GPG_ERR_CANCELED); goto leave; } } err = scd_setattr ("PUBKEY-URL", url, strlen (url)); leave: xfree (url); return err; } /* Fetch the key from the URL given on the card or try to get it from * the default keyserver. */ static gpg_error_t cmd_fetch (card_info_t info) { gpg_error_t err; key_info_t kinfo; if (!info) return print_help ("FETCH\n\n" "Retrieve a key using the URL data object or if that is missing\n" "using the fingerprint.", APP_TYPE_OPENPGP, 0); if (info->pubkey_url && *info->pubkey_url) { /* strlist_t sl = NULL; */ /* add_to_strlist (&sl, info.pubkey_url); */ /* err = keyserver_fetch (ctrl, sl, KEYORG_URL); */ /* free_strlist (sl); */ err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); /* FIXME */ } else if ((kinfo = find_kinfo (info, "OPENPGP.1")) && kinfo->fprlen) { /* rc = keyserver_import_fprint (ctrl, info.fpr1, info.fpr1len, */ /* opt.keyserver, 0); */ err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); /* FIXME */ } else err = gpg_error (GPG_ERR_NO_DATA); return err; } static gpg_error_t cmd_login (card_info_t info, char *argstr) { gpg_error_t err; char *data; size_t datalen; if (!info) return print_help ("LOGIN [--clear] [< FILE]\n\n" "Set the login data object. If FILE is given the data is\n" "is read from that file. This allows for binary data.\n" "The option --clear deletes the login data.", APP_TYPE_OPENPGP, 0); if (!strcmp (argstr, "--clear")) { data = xstrdup (" "); /* kludge. */ datalen = 1; } else if (*argstr == '<') /* Read it from a file */ { for (argstr++; spacep (argstr); argstr++) ; err = get_data_from_file (argstr, &data, &datalen); if (err) goto leave; } else { data = tty_get (_("Login data (account name): ")); trim_spaces (data); tty_kill_prompt (); if (!*data || *data == CONTROL_D) { err = gpg_error (GPG_ERR_CANCELED); goto leave; } datalen = strlen (data); } err = scd_setattr ("LOGIN-DATA", data, datalen); leave: xfree (data); return err; } static gpg_error_t cmd_lang (card_info_t info, const char *argstr) { gpg_error_t err; char *data, *p; if (!info) return print_help ("LANG [--clear]\n\n" "Change the language info for the card. This info can be used\n" "by applications for a personalized greeting. Up to 4 two-digit\n" "language identifiers can be entered as a preference. The option\n" "--clear removes all identifiers. GnuPG does not use this info.", APP_TYPE_OPENPGP, 0); if (!strcmp (argstr, "--clear")) data = xstrdup (" "); /* Note that we need two spaces here. */ else { again: data = tty_get (_("Language preferences: ")); trim_spaces (data); tty_kill_prompt (); if (!*data || *data == CONTROL_D) { err = gpg_error (GPG_ERR_CANCELED); goto leave; } if (strlen (data) > 8 || (strlen (data) & 1)) { log_info (_("Error: invalid length of preference string.\n")); xfree (data); goto again; } for (p=data; *p && *p >= 'a' && *p <= 'z'; p++) ; if (*p) { log_info (_("Error: invalid characters in preference string.\n")); xfree (data); goto again; } } err = scd_setattr ("DISP-LANG", data, strlen (data)); leave: xfree (data); return err; } static gpg_error_t cmd_salut (card_info_t info, const char *argstr) { gpg_error_t err; char *data = NULL; const char *str; if (!info) return print_help ("SALUT [--clear]\n\n" "Change the salutation info for the card. This info can be used\n" "by applications for a personalized greeting. The option --clear\n" "removes this data object. GnuPG does not use this info.", APP_TYPE_OPENPGP, 0); again: if (!strcmp (argstr, "--clear")) str = "9"; else { data = tty_get (_("Salutation (M = Mr., F = Mrs., or space): ")); trim_spaces (data); tty_kill_prompt (); if (*data == CONTROL_D) { err = gpg_error (GPG_ERR_CANCELED); goto leave; } if (!*data) str = "9"; else if ((*data == 'M' || *data == 'm') && !data[1]) str = "1"; else if ((*data == 'F' || *data == 'f') && !data[1]) str = "2"; else { tty_printf (_("Error: invalid response.\n")); xfree (data); goto again; } } err = scd_setattr ("DISP-SEX", str, 1); leave: xfree (data); return err; } static gpg_error_t cmd_cafpr (card_info_t info, char *argstr) { gpg_error_t err; char *data = NULL; const char *s; int i, c; unsigned char fpr[32]; int fprlen; int fprno; int opt_clear = 0; if (!info) return print_help ("CAFPR [--clear] N\n\n" "Change the CA fingerprint number N. N must be in the\n" "range 1 to 3. The option --clear clears the specified\n" "CA fingerprint N or all of them if N is 0 or not given.", APP_TYPE_OPENPGP, 0); opt_clear = has_leading_option (argstr, "--clear"); argstr = skip_options (argstr); if (digitp (argstr)) { fprno = atoi (argstr); while (digitp (argstr)) argstr++; while (spacep (argstr)) argstr++; } else fprno = 0; if (opt_clear && !fprno) ; /* Okay: clear all fprs. */ else if (fprno < 1 || fprno > 3) { err = gpg_error (GPG_ERR_INV_ARG); goto leave; } again: if (opt_clear) { memset (fpr, 0, 20); fprlen = 20; } else { xfree (data); data = tty_get (_("CA fingerprint: ")); trim_spaces (data); tty_kill_prompt (); if (!*data || *data == CONTROL_D) { err = gpg_error (GPG_ERR_CANCELED); goto leave; } for (i=0, s=data; i < sizeof fpr && *s; ) { while (spacep(s)) s++; if (*s == ':') s++; while (spacep(s)) s++; c = hextobyte (s); if (c == -1) break; fpr[i++] = c; s += 2; } fprlen = i; if ((fprlen != 20 && fprlen != 32) || *s) { log_error (_("Error: invalid formatted fingerprint.\n")); goto again; } } if (!fprno) { log_assert (opt_clear); err = scd_setattr ("CA-FPR-1", fpr, fprlen); if (!err) err = scd_setattr ("CA-FPR-2", fpr, fprlen); if (!err) err = scd_setattr ("CA-FPR-3", fpr, fprlen); } else err = scd_setattr (fprno==1?"CA-FPR-1": fprno==2?"CA-FPR-2": fprno==3?"CA-FPR-3":"x", fpr, fprlen); leave: xfree (data); return err; } static gpg_error_t cmd_privatedo (card_info_t info, char *argstr) { gpg_error_t err; int opt_clear; char *do_name = NULL; char *data = NULL; size_t datalen; int do_no; if (!info) return print_help ("PRIVATEDO [--clear] N [< FILE]\n\n" "Change the private data object N. N must be in the\n" "range 1 to 4. If FILE is given the data is is read\n" "from that file. The option --clear clears the data.", APP_TYPE_OPENPGP, 0); opt_clear = has_leading_option (argstr, "--clear"); argstr = skip_options (argstr); if (digitp (argstr)) { do_no = atoi (argstr); while (digitp (argstr)) argstr++; while (spacep (argstr)) argstr++; } else do_no = 0; if (do_no < 1 || do_no > 4) { err = gpg_error (GPG_ERR_INV_ARG); goto leave; } do_name = xasprintf ("PRIVATE-DO-%d", do_no); if (opt_clear) { data = xstrdup (" "); datalen = 1; } else if (*argstr == '<') /* Read it from a file */ { for (argstr++; spacep (argstr); argstr++) ; err = get_data_from_file (argstr, &data, &datalen); if (err) goto leave; } else if (*argstr) { err = gpg_error (GPG_ERR_INV_ARG); goto leave; } else { data = tty_get (_("Private DO data: ")); trim_spaces (data); tty_kill_prompt (); datalen = strlen (data); if (!*data || *data == CONTROL_D) { err = gpg_error (GPG_ERR_CANCELED); goto leave; } } err = scd_setattr (do_name, data, datalen); leave: xfree (do_name); xfree (data); return err; } static gpg_error_t cmd_writecert (card_info_t info, char *argstr) { gpg_error_t err; int opt_clear; char *certref_buffer = NULL; char *certref; char *data = NULL; size_t datalen; if (!info) return print_help ("WRITECERT [--clear] CERTREF < FILE\n\n" "Write a certificate for key 3. Unless --clear is given\n" "the file argument is mandatory. The option --clear removes\n" "the certificate from the card.", APP_TYPE_OPENPGP, APP_TYPE_PIV, 0); opt_clear = has_leading_option (argstr, "--clear"); argstr = skip_options (argstr); certref = argstr; if ((argstr = strchr (certref, ' '))) { *argstr++ = 0; trim_spaces (certref); trim_spaces (argstr); } else /* Let argstr point to an empty string. */ argstr = certref + strlen (certref); if (info->apptype == APP_TYPE_OPENPGP) { if (ascii_strcasecmp (certref, "OPENPGP.3") && strcmp (certref, "3")) { err = gpg_error (GPG_ERR_INV_ID); log_error ("Error: CERTREF must be \"3\" or \"OPENPGP.3\"\n"); goto leave; } certref = certref_buffer = xstrdup ("OPENPGP.3"); } else /* Upcase the certref; prepend cardtype if needed. */ { if (!strchr (certref, '.')) certref_buffer = xstrconcat (app_type_string (info->apptype), ".", certref, NULL); else certref_buffer = xstrdup (certref); ascii_strupr (certref_buffer); certref = certref_buffer; } if (opt_clear) { data = xstrdup (" "); datalen = 1; } else if (*argstr == '<') /* Read it from a file */ { for (argstr++; spacep (argstr); argstr++) ; err = get_data_from_file (argstr, &data, &datalen); if (err) goto leave; } else { err = gpg_error (GPG_ERR_INV_ARG); goto leave; } err = scd_writecert (certref, data, datalen); leave: xfree (data); xfree (certref_buffer); return err; } static gpg_error_t cmd_readcert (card_info_t info, char *argstr) { gpg_error_t err; char *certref_buffer = NULL; char *certref; void *data = NULL; size_t datalen; const char *fname; if (!info) return print_help ("READCERT CERTREF > FILE\n\n" "Read the certificate for key CERTREF and store it in FILE.", APP_TYPE_OPENPGP, APP_TYPE_PIV, 0); argstr = skip_options (argstr); certref = argstr; if ((argstr = strchr (certref, ' '))) { *argstr++ = 0; trim_spaces (certref); trim_spaces (argstr); } else /* Let argstr point to an empty string. */ argstr = certref + strlen (certref); if (info->apptype == APP_TYPE_OPENPGP) { if (ascii_strcasecmp (certref, "OPENPGP.3") && strcmp (certref, "3")) { err = gpg_error (GPG_ERR_INV_ID); log_error ("Error: CERTREF must be \"3\" or \"OPENPGP.3\"\n"); goto leave; } certref = certref_buffer = xstrdup ("OPENPGP.3"); } if (*argstr == '>') /* Write it to a file */ { for (argstr++; spacep (argstr); argstr++) ; fname = argstr; } else { err = gpg_error (GPG_ERR_INV_ARG); goto leave; } err = scd_readcert (certref, &data, &datalen); if (err) goto leave; err = put_data_to_file (fname, data, datalen); leave: xfree (data); xfree (certref_buffer); return err; } static gpg_error_t cmd_writekey (card_info_t info, char *argstr) { gpg_error_t err; int opt_force; char *argv[2]; int argc; char *keyref_buffer = NULL; char *keyref; char *keygrip; if (!info) return print_help ("WRITEKEY [--force] KEYREF KEYGRIP\n\n" "Write a private key object identified by KEYGRIP to slot KEYREF.\n" "Use --force to overwrite an existing key.", APP_TYPE_OPENPGP, APP_TYPE_PIV, 0); opt_force = has_leading_option (argstr, "--force"); argstr = skip_options (argstr); argc = split_fields (argstr, argv, DIM (argv)); if (argc < 2) { err = gpg_error (GPG_ERR_INV_ARG); goto leave; } /* Upcase the keyref; prepend cardtype if needed. */ keyref = argv[0]; if (!strchr (keyref, '.')) keyref_buffer = xstrconcat (app_type_string (info->apptype), ".", keyref, NULL); else keyref_buffer = xstrdup (keyref); ascii_strupr (keyref_buffer); keyref = keyref_buffer; /* Get the keygrip. */ keygrip = argv[1]; if (strlen (keygrip) != 40 && !(keygrip[0] == '&' && strlen (keygrip+1) == 40)) { log_error (_("Not a valid keygrip (expecting 40 hex digits)\n")); err = gpg_error (GPG_ERR_INV_ARG); goto leave; } err = scd_writekey (keyref, opt_force, keygrip); leave: xfree (keyref_buffer); return err; } static gpg_error_t cmd_forcesig (card_info_t info) { gpg_error_t err; int newstate; if (!info) return print_help ("FORCESIG\n\n" "Toggle the forcesig flag of an OpenPGP card.", APP_TYPE_OPENPGP, 0); if (info->apptype != APP_TYPE_OPENPGP) { log_info ("Note: This is an OpenPGP only command.\n"); return gpg_error (GPG_ERR_NOT_SUPPORTED); } newstate = !info->chv1_cached; err = scd_setattr ("CHV-STATUS-1", newstate? "\x01":"", 1); if (err) goto leave; /* Read it back to be sure we have the right toggle state the next * time. */ err = scd_getattr ("CHV-STATUS", info); leave: return err; } /* Helper for cmd_generate_openpgp. Noe that either 0 or 1 is stored at * FORCED_CHV1. */ static gpg_error_t check_pin_for_key_operation (card_info_t info, int *forced_chv1) { gpg_error_t err = 0; *forced_chv1 = !info->chv1_cached; if (*forced_chv1) { /* Switch off the forced mode so that during key generation we * don't get bothered with PIN queries for each self-signature. */ err = scd_setattr ("CHV-STATUS-1", "\x01", 1); if (err) { log_error ("error clearing forced signature PIN flag: %s\n", gpg_strerror (err)); *forced_chv1 = -1; /* Not changed. */ goto leave; } } /* Check the PIN now, so that we won't get asked later for each * binding signature. */ err = scd_checkpin (info->serialno); if (err) log_error ("error checking the PIN: %s\n", gpg_strerror (err)); leave: return err; } /* Helper for cmd_generate_openpgp. */ static void restore_forced_chv1 (int *forced_chv1) { gpg_error_t err; /* Note the possible values stored at FORCED_CHV1: * 0 - forcesig was not enabled. * 1 - forcesig was enabled - enable it again. * -1 - We have not changed anything. */ if (*forced_chv1 == 1) { /* Switch back to forced state. */ err = scd_setattr ("CHV-STATUS-1", "", 1); if (err) log_error ("error setting forced signature PIN flag: %s\n", gpg_strerror (err)); *forced_chv1 = 0; } } /* Implementation of cmd_generate for OpenPGP cards. */ static gpg_error_t generate_openpgp (card_info_t info) { gpg_error_t err; int forced_chv1 = -1; int want_backup; char *answer = NULL; key_info_t kinfo1, kinfo2, kinfo3; if (info->extcap.ki) { xfree (answer); answer = tty_get (_("Make off-card backup of encryption key? (Y/n) ")); want_backup = answer_is_yes_no_default (answer, 1/*(default to Yes)*/); tty_kill_prompt (); if (*answer == CONTROL_D) { err = gpg_error (GPG_ERR_CANCELED); goto leave; } } else want_backup = 0; kinfo1 = find_kinfo (info, "OPENPGP.1"); kinfo2 = find_kinfo (info, "OPENPGP.2"); kinfo3 = find_kinfo (info, "OPENPGP.3"); if ((kinfo1 && kinfo1->fprlen && !mem_is_zero (kinfo1->fpr,kinfo1->fprlen)) || (kinfo2 && kinfo2->fprlen && !mem_is_zero (kinfo2->fpr,kinfo2->fprlen)) || (kinfo3 && kinfo3->fprlen && !mem_is_zero (kinfo3->fpr,kinfo3->fprlen)) ) { tty_printf ("\n"); log_info (_("Note: keys are already stored on the card!\n")); tty_printf ("\n"); answer = tty_get (_("Replace existing keys? (y/N) ")); tty_kill_prompt (); if (*answer == CONTROL_D) { err = gpg_error (GPG_ERR_CANCELED); goto leave; } if (!answer_is_yes_no_default (answer, 0/*(default to No)*/)) { err = gpg_error (GPG_ERR_CANCELED); goto leave; } } /* If no displayed name has been set, we assume that this is a fresh * card and print a hint about the default PINs. */ if (!info->disp_name || !*info->disp_name) { tty_printf ("\n"); tty_printf (_("Please note that the factory settings of the PINs are\n" " PIN = '%s' Admin PIN = '%s'\n" "You should change them using the command --change-pin\n"), OPENPGP_USER_PIN_DEFAULT, OPENPGP_ADMIN_PIN_DEFAULT); tty_printf ("\n"); } err = check_pin_for_key_operation (info, &forced_chv1); if (err) goto leave; /* FIXME: We need to divert to a function which spwans gpg which * will then create the key. This also requires new features in * gpg. We might also first create the keys on the card and then * tell gpg to use them to create the OpenPGP keyblock. */ /* generate_keypair (ctrl, 1, NULL, info.serialno, want_backup); */ (void)want_backup; err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); leave: restore_forced_chv1 (&forced_chv1); xfree (answer); return err; } /* Generic implementation of cmd_generate. */ static gpg_error_t generate_generic (card_info_t info, const char *keyref, int force, const char *algo) { gpg_error_t err; (void)info; err = scd_genkey (keyref, force, algo, NULL); return err; } static gpg_error_t cmd_generate (card_info_t info, char *argstr) { static char * const valid_algos[] = { "rsa2048", "rsa3072", "rsa4096", "nistp256", "nistp384", "nistp521", "ed25519", "cv25519", NULL }; gpg_error_t err; int opt_force; char *opt_algo = NULL; /* Malloced. */ char *keyref_buffer = NULL; /* Malloced. */ char *keyref; /* Points into argstr or keyref_buffer. */ int i; if (!info) return print_help ("GENERATE [--force] [--algo=ALGO] KEYREF\n\n" "Create a new key on a card. For OpenPGP cards are menu is used\n" "and KEYREF is ignored. Use --force to overwrite an existing key.", APP_TYPE_OPENPGP, APP_TYPE_PIV, 0); if (opt.interactive || opt.verbose) log_info (_("%s card no. %s detected\n"), app_type_string (info->apptype), info->dispserialno? info->dispserialno : info->serialno); opt_force = has_leading_option (argstr, "--force"); err = get_option_value (argstr, "--algo", &opt_algo); if (err) goto leave; argstr = skip_options (argstr); keyref = argstr; if ((argstr = strchr (keyref, ' '))) { *argstr++ = 0; trim_spaces (keyref); trim_spaces (argstr); } else /* Let argstr point to an empty string. */ argstr = keyref + strlen (keyref); if (!*keyref) keyref = NULL; if (*argstr) { /* Extra arguments found. */ err = gpg_error (GPG_ERR_INV_ARG); goto leave; } if (opt_algo) { for (i=0; valid_algos[i]; i++) if (!strcmp (valid_algos[i], opt_algo)) break; if (!valid_algos[i]) { err = gpg_error (GPG_ERR_PUBKEY_ALGO); log_info ("Invalid algorithm '%s' given. Use one:\n", opt_algo); for (i=0; valid_algos[i]; i++) if (!(i%5)) log_info (" %s%s", valid_algos[i], valid_algos[i+1]?",":"."); else log_printf (" %s%s", valid_algos[i], valid_algos[i+1]?",":"."); log_info ("Note that the card may not support all of them.\n"); goto leave; } } /* Upcase the keyref; if it misses the cardtype, prepend it. */ if (keyref) { if (!strchr (keyref, '.')) keyref_buffer = xstrconcat (app_type_string (info->apptype), ".", keyref, NULL); else keyref_buffer = xstrdup (keyref); ascii_strupr (keyref_buffer); keyref = keyref_buffer; } + /* Special checks. */ + if ((info->cardtype && !strcmp (info->cardtype, "yubikey")) + && info->cardversion >= 0x040200 && info->cardversion < 0x040305) + { + log_error ("On-chip key generation on this YubiKey has been blocked.\n"); + log_info ("Please see for details\n"); + err = gpg_error (GPG_ERR_NOT_SUPPORTED); + goto leave; + } + /* Divert to dedicated functions. */ if (info->apptype == APP_TYPE_OPENPGP) { if (opt_force || opt_algo || keyref) log_info ("Note: Options are ignored for OpenPGP cards.\n"); err = generate_openpgp (info); } else if (!keyref) err = gpg_error (GPG_ERR_INV_ID); else err = generate_generic (info, keyref, opt_force, opt_algo); leave: xfree (opt_algo); xfree (keyref_buffer); return err; } /* Sub-menu to change a PIN. */ static gpg_error_t cmd_passwd (card_info_t info, char *argstr) { gpg_error_t err; char *answer = NULL; const char *pinref; if (!info) return print_help ("PASSWD [PINREF]\n\n" "Menu to change or unblock the PINs. Note that the\n" "presented menu options depend on the type of card\n" "and whether the admin mode is enabled. For OpenPGP\n" "and PIV cards defaults for PINREF are available.", 0); if (opt.interactive || opt.verbose) log_info (_("%s card no. %s detected\n"), app_type_string (info->apptype), info->dispserialno? info->dispserialno : info->serialno); if (!*argstr && info->apptype == APP_TYPE_OPENPGP) { /* For an OpenPGP card we present the well known menu if no * argument is given. */ for (;;) { tty_printf ("\n"); tty_printf ("1 - change PIN\n" "2 - unblock and set new PIN\n" "3 - change Admin PIN\n" "4 - set the Reset Code\n" "Q - quit\n"); tty_printf ("\n"); err = 0; xfree (answer); answer = tty_get (_("Your selection? ")); tty_kill_prompt (); if (*answer == CONTROL_D) break; /* Quit. */ if (strlen (answer) != 1) continue; if (*answer == 'q' || *answer == 'Q') break; /* Quit. */ if (*answer == '1') { /* Change PIN (same as the direct thing in non-admin mode). */ err = scd_change_pin ("OPENPGP.1", 0); if (err) log_error ("Error changing the PIN: %s\n", gpg_strerror (err)); else log_info ("PIN changed.\n"); } else if (*answer == '2') { /* Unblock PIN by setting a new PIN. */ err = scd_change_pin ("OPENPGP.1", 1); if (err) log_error ("Error unblocking the PIN: %s\n", gpg_strerror(err)); else log_info ("PIN unblocked and new PIN set.\n"); } else if (*answer == '3') { /* Change Admin PIN. */ err = scd_change_pin ("OPENPGP.3", 0); if (err) log_error ("Error changing the PIN: %s\n", gpg_strerror (err)); else log_info ("PIN changed.\n"); } else if (*answer == '4') { /* Set a new Reset Code. */ err = scd_change_pin ("OPENPGP.2", 1); if (err) log_error ("Error setting the Reset Code: %s\n", gpg_strerror (err)); else log_info ("Reset Code set.\n"); } } /*end for loop*/ } else { if (*argstr) pinref = argstr; else if (info->apptype == APP_TYPE_PIV) pinref = "PIV.80"; else { /* Note that we do not have a default value for OpenPGP * because we want to be mostly compatible to "gpg * --card-edit" and show a menu in that case (above). */ err = gpg_error (GPG_ERR_MISSING_VALUE); goto leave; } err = scd_change_pin (pinref, 0); if (err) goto leave; if (info->apptype == APP_TYPE_PIV && !ascii_strcasecmp (pinref, "PIV.81")) log_info ("PUK changed.\n"); else log_info ("PIN changed.\n"); } leave: xfree (answer); return err; } static gpg_error_t cmd_unblock (card_info_t info) { gpg_error_t err = 0; if (!info) return print_help ("UNBLOCK\n\n" "Unblock a PIN using a PUK or Reset Code. Note that OpenPGP\n" "cards prior to version 2 can't use this; instead the PASSWD\n" "command can be used to set a new PIN.", 0); if (opt.interactive || opt.verbose) log_info (_("%s card no. %s detected\n"), app_type_string (info->apptype), info->dispserialno? info->dispserialno : info->serialno); if (info->apptype == APP_TYPE_OPENPGP) { if (!info->is_v2) { log_error (_("This command is only available for version 2 cards\n")); err = gpg_error (GPG_ERR_NOT_SUPPORTED); } else if (!info->chvinfo[1]) { log_error (_("Reset Code not or not anymore available\n")); err = gpg_error (GPG_ERR_PIN_BLOCKED); } else { err = scd_change_pin ("OPENPGP.2", 0); if (!err) log_info ("PIN changed.\n"); } } else if (info->apptype == APP_TYPE_PIV) { /* Unblock the Application PIN. */ err = scd_change_pin ("PIV.80", 1); if (!err) log_info ("PIN unblocked and changed.\n"); } else { log_info ("Unblocking not yet supported for '%s'\n", app_type_string (info->apptype)); err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); } return err; } /* Note: On successful execution a redisplay should be scheduled. If * this function fails the card may be in an unknown state. */ static gpg_error_t cmd_factoryreset (card_info_t info) { gpg_error_t err; char *answer = NULL; int termstate = 0; int any_apdu = 0; int is_yubikey = 0; int i; if (!info) return print_help ("FACTORY-RESET\n\n" "Do a complete reset of some OpenPGP and PIV cards. This\n" "deletes all data and keys and resets the PINs to their default.\n" "This is mainly used by developers with scratch cards. Don't\n" "worry, you need to confirm before the command proceeds.", APP_TYPE_OPENPGP, APP_TYPE_PIV, 0); /* We support the factory reset for most OpenPGP cards and Yubikeys * with the PIV application. */ if (info->apptype == APP_TYPE_OPENPGP) ; else if (info->apptype == APP_TYPE_PIV && info->cardtype && !strcmp (info->cardtype, "yubikey")) is_yubikey = 1; else return gpg_error (GPG_ERR_NOT_SUPPORTED); /* For an OpenPGP card the code below basically does the same what * this gpg-connect-agent script does: * * scd reset * scd serialno undefined * scd apdu 00 A4 04 00 06 D2 76 00 01 24 01 * scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40 * scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40 * scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40 * scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40 * scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40 * scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40 * scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40 * scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40 * scd apdu 00 e6 00 00 * scd apdu 00 44 00 00 * scd reset * /echo Card has been reset to factory defaults * * For a PIV application on a Yubikey it merely issues the Yubikey * specific resset command. */ err = scd_learn (info); if (gpg_err_code (err) == GPG_ERR_OBJ_TERM_STATE && gpg_err_source (err) == GPG_ERR_SOURCE_SCD) termstate = 1; else if (err) { log_error (_("OpenPGP card not available: %s\n"), gpg_strerror (err)); goto leave; } if (opt.interactive || opt.verbose) log_info (_("%s card no. %s detected\n"), app_type_string (info->apptype), info->dispserialno? info->dispserialno : info->serialno); if (!termstate || is_yubikey) { if (!is_yubikey) { if (!(info->status_indicator == 3 || info->status_indicator == 5)) { /* Note: We won't see status-indicator 3 here because it * is not possible to select a card application in * termination state. */ log_error (_("This command is not supported by this card\n")); err = gpg_error (GPG_ERR_NOT_SUPPORTED); goto leave; } } tty_printf ("\n"); log_info (_("Note: This command destroys all keys stored on the card!\n")); tty_printf ("\n"); xfree (answer); answer = tty_get (_("Continue? (y/N) ")); tty_kill_prompt (); trim_spaces (answer); if (*answer == CONTROL_D || !answer_is_yes_no_default (answer, 0/*(default to no)*/)) { err = gpg_error (GPG_ERR_CANCELED); goto leave; } xfree (answer); answer = tty_get (_("Really do a factory reset? (enter \"yes\") ")); tty_kill_prompt (); trim_spaces (answer); if (strcmp (answer, "yes") && strcmp (answer,_("yes"))) { err = gpg_error (GPG_ERR_CANCELED); goto leave; } if (is_yubikey) { /* The PIV application si already selected, we only need to * send the special reset APDU after having blocked PIN and * PUK. Note that blocking the PUK is done using the * unblock PIN command. */ any_apdu = 1; for (i=0; i < 5; i++) send_apdu ("0020008008FFFFFFFFFFFFFFFF", "VERIFY", 0xffff, NULL, NULL); for (i=0; i < 5; i++) send_apdu ("002C008010FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", "RESET RETRY COUNTER", 0xffff, NULL, NULL); err = send_apdu ("00FB000001FF", "YUBIKEY RESET", 0, NULL, NULL); if (err) goto leave; } else /* OpenPGP card. */ { any_apdu = 1; /* We need to select a card application before we can send APDUs * to the card without scdaemon doing anything on its own. */ err = send_apdu (NULL, "RESET", 0, NULL, NULL); if (err) goto leave; err = send_apdu ("undefined", "dummy select ", 0, NULL, NULL); if (err) goto leave; /* Select the OpenPGP application. */ err = send_apdu ("00A4040006D27600012401", "SELECT AID", 0, NULL, NULL); if (err) goto leave; /* Do some dummy verifies with wrong PINs to set the retry * counter to zero. We can't easily use the card version 2.1 * feature of presenting the admin PIN to allow the terminate * command because there is no machinery in scdaemon to catch * the verify command and ask for the PIN when the "APDU" * command is used. * Here, the length of dummy wrong PIN is 32-byte, also * supporting authentication with KDF DO. */ for (i=0; i < 4; i++) send_apdu ("0020008120" "40404040404040404040404040404040" "40404040404040404040404040404040", "VERIFY", 0xffff, NULL, NULL); for (i=0; i < 4; i++) send_apdu ("0020008320" "40404040404040404040404040404040" "40404040404040404040404040404040", "VERIFY", 0xffff, NULL, NULL); /* Send terminate datafile command. */ err = send_apdu ("00e60000", "TERMINATE DF", 0x6985, NULL, NULL); if (err) goto leave; } } if (!is_yubikey) { any_apdu = 1; /* Send activate datafile command. This is used without * confirmation if the card is already in termination state. */ err = send_apdu ("00440000", "ACTIVATE DF", 0, NULL, NULL); if (err) goto leave; } /* Finally we reset the card reader once more. */ err = send_apdu (NULL, "RESET", 0, NULL, NULL); if (err) goto leave; /* Then, connect the card again (answer used as a dummy). */ xfree (answer); answer = NULL; err = scd_serialno (&answer, NULL); leave: if (err && any_apdu && !is_yubikey) { log_info ("Due to an error the card might be in an inconsistent state\n" "You should run the LIST command to check this.\n"); /* FIXME: We need a better solution in the case that the card is * in a termination state, i.e. the card was removed before the * activate was sent. The best solution I found with v2.1 * Zeitcontrol card was to kill scdaemon and the issue this * sequence with gpg-connect-agent: * scd reset * scd serialno undefined * scd apdu 00A4040006D27600012401 (returns error) * scd apdu 00440000 * Then kill scdaemon again and issue: * scd reset * scd serialno openpgp */ } xfree (answer); return err; } /* Generate KDF data. This is a helper for cmd_kdfsetup. */ static gpg_error_t gen_kdf_data (unsigned char *data, int single_salt) { gpg_error_t err; const unsigned char h0[] = { 0x81, 0x01, 0x03, 0x82, 0x01, 0x08, 0x83, 0x04 }; const unsigned char h1[] = { 0x84, 0x08 }; const unsigned char h2[] = { 0x85, 0x08 }; const unsigned char h3[] = { 0x86, 0x08 }; const unsigned char h4[] = { 0x87, 0x20 }; const unsigned char h5[] = { 0x88, 0x20 }; unsigned char *p, *salt_user, *salt_admin; unsigned char s2k_char; unsigned int iterations; unsigned char count_4byte[4]; p = data; s2k_char = encode_s2k_iterations (agent_get_s2k_count ()); iterations = S2K_DECODE_COUNT (s2k_char); count_4byte[0] = (iterations >> 24) & 0xff; count_4byte[1] = (iterations >> 16) & 0xff; count_4byte[2] = (iterations >> 8) & 0xff; count_4byte[3] = (iterations & 0xff); memcpy (p, h0, sizeof h0); p += sizeof h0; memcpy (p, count_4byte, sizeof count_4byte); p += sizeof count_4byte; memcpy (p, h1, sizeof h1); salt_user = (p += sizeof h1); gcry_randomize (p, 8, GCRY_STRONG_RANDOM); p += 8; if (single_salt) salt_admin = salt_user; else { memcpy (p, h2, sizeof h2); p += sizeof h2; gcry_randomize (p, 8, GCRY_STRONG_RANDOM); p += 8; memcpy (p, h3, sizeof h3); salt_admin = (p += sizeof h3); gcry_randomize (p, 8, GCRY_STRONG_RANDOM); p += 8; } memcpy (p, h4, sizeof h4); p += sizeof h4; err = gcry_kdf_derive (OPENPGP_USER_PIN_DEFAULT, strlen (OPENPGP_USER_PIN_DEFAULT), GCRY_KDF_ITERSALTED_S2K, GCRY_MD_SHA256, salt_user, 8, iterations, 32, p); p += 32; if (!err) { memcpy (p, h5, sizeof h5); p += sizeof h5; err = gcry_kdf_derive (OPENPGP_ADMIN_PIN_DEFAULT, strlen (OPENPGP_ADMIN_PIN_DEFAULT), GCRY_KDF_ITERSALTED_S2K, GCRY_MD_SHA256, salt_admin, 8, iterations, 32, p); } return err; } static gpg_error_t cmd_kdfsetup (card_info_t info, char *argstr) { gpg_error_t err; unsigned char kdf_data[OPENPGP_KDF_DATA_LENGTH_MAX]; int single = (*argstr != 0); if (!info) return print_help ("KDF-SETUP\n\n" "Prepare the OpenPGP card KDF feature for this card.", APP_TYPE_OPENPGP, 0); if (info->apptype != APP_TYPE_OPENPGP) { log_info ("Note: This is an OpenPGP only command.\n"); return gpg_error (GPG_ERR_NOT_SUPPORTED); } if (!info->extcap.kdf) { log_error (_("This command is not supported by this card\n")); err = gpg_error (GPG_ERR_NOT_SUPPORTED); goto leave; } err = gen_kdf_data (kdf_data, single); if (err) goto leave; err = scd_setattr ("KDF", kdf_data, single ? OPENPGP_KDF_DATA_LENGTH_MIN /* */ : OPENPGP_KDF_DATA_LENGTH_MAX); if (err) goto leave; err = scd_getattr ("KDF", info); leave: return err; } static void show_keysize_warning (void) { static int shown; if (shown) return; shown = 1; tty_printf (_("Note: There is no guarantee that the card supports the requested\n" " key type or size. If the key generation does not succeed,\n" " please check the documentation of your card to see which\n" " key types and sizes are supported.\n") ); } /* Ask for the size of a card key. NBITS is the current size * configured for the card. Returns 0 on success and stored the * chosen key size at R_KEYSIZE; 0 is stored to indicate that the * default size shall be used. */ static gpg_error_t ask_card_rsa_keysize (unsigned int nbits, unsigned int *r_keysize) { unsigned int min_nbits = 1024; unsigned int max_nbits = 4096; char*answer; unsigned int req_nbits; for (;;) { answer = tty_getf (_("What keysize do you want? (%u) "), nbits); trim_spaces (answer); tty_kill_prompt (); if (*answer == CONTROL_D) { xfree (answer); return gpg_error (GPG_ERR_CANCELED); } req_nbits = *answer? atoi (answer): nbits; xfree (answer); if (req_nbits != nbits && (req_nbits % 32) ) { req_nbits = ((req_nbits + 31) / 32) * 32; tty_printf (_("rounded up to %u bits\n"), req_nbits); } if (req_nbits == nbits) { /* Use default. */ *r_keysize = 0; return 0; } if (req_nbits < min_nbits || req_nbits > max_nbits) { tty_printf (_("%s keysizes must be in the range %u-%u\n"), "RSA", min_nbits, max_nbits); } else { *r_keysize = req_nbits; return 0; } } } /* Ask for the key attribute of a card key. CURRENT is the current * attribute configured for the card. KEYNO is the number of the key * used to select the prompt. Stores NULL at result to use the * default attribute or stores the selected attribute structure at * RESULT. On error an error code is returned. */ static gpg_error_t ask_card_keyattr (int keyno, const struct key_attr *current, struct key_attr **result) { gpg_error_t err; struct key_attr *key_attr = NULL; char *answer = NULL; int selection; *result = NULL; key_attr = xcalloc (1, sizeof *key_attr); tty_printf (_("Changing card key attribute for: ")); if (keyno == 0) tty_printf (_("Signature key\n")); else if (keyno == 1) tty_printf (_("Encryption key\n")); else tty_printf (_("Authentication key\n")); tty_printf (_("Please select what kind of key you want:\n")); tty_printf (_(" (%d) RSA\n"), 1 ); tty_printf (_(" (%d) ECC\n"), 2 ); for (;;) { xfree (answer); answer = tty_get (_("Your selection? ")); trim_spaces (answer); tty_kill_prompt (); if (!*answer || *answer == CONTROL_D) { err = gpg_error (GPG_ERR_CANCELED); goto leave; } selection = *answer? atoi (answer) : 0; if (selection == 1 || selection == 2) break; else tty_printf (_("Invalid selection.\n")); } if (selection == 1) { unsigned int nbits, result_nbits; if (current->algo == PUBKEY_ALGO_RSA) nbits = current->nbits; else nbits = 2048; err = ask_card_rsa_keysize (nbits, &result_nbits); if (err) goto leave; if (result_nbits == 0) { if (current->algo == PUBKEY_ALGO_RSA) { xfree (key_attr); key_attr = NULL; } else result_nbits = nbits; } if (key_attr) { key_attr->algo = PUBKEY_ALGO_RSA; key_attr->nbits = result_nbits; } } else if (selection == 2) { const char *curve; /* const char *oid_str; */ int algo; if (current->algo == PUBKEY_ALGO_RSA) { if (keyno == 1) /* Encryption key */ algo = PUBKEY_ALGO_ECDH; else /* Signature key or Authentication key */ algo = PUBKEY_ALGO_ECDSA; curve = NULL; } else { algo = current->algo; curve = current->curve; } (void)curve; (void)algo; err = GPG_ERR_NOT_IMPLEMENTED; goto leave; /* FIXME: We need to move the ask_cure code out to common or * provide another sultion. */ /* curve = ask_curve (&algo, NULL, curve); */ /* if (curve) */ /* { */ /* key_attr->algo = algo; */ /* oid_str = openpgp_curve_to_oid (curve, NULL); */ /* key_attr->curve = openpgp_oid_to_curve (oid_str, 0); */ /* } */ /* else */ /* { */ /* xfree (key_attr); */ /* key_attr = NULL; */ /* } */ } else { err = gpg_error (GPG_ERR_BUG); goto leave; } /* Tell the user what we are going to do. */ if (key_attr->algo == PUBKEY_ALGO_RSA) { tty_printf (_("The card will now be re-configured" " to generate a key of %u bits\n"), key_attr->nbits); } else if (key_attr->algo == PUBKEY_ALGO_ECDH || key_attr->algo == PUBKEY_ALGO_ECDSA || key_attr->algo == PUBKEY_ALGO_EDDSA) { tty_printf (_("The card will now be re-configured" " to generate a key of type: %s\n"), key_attr->curve); } show_keysize_warning (); *result = key_attr; key_attr = NULL; leave: xfree (key_attr); xfree (answer); return err; } /* Change the key attribute of key KEYNO (0..2) and show an error * message if that fails. */ static gpg_error_t do_change_keyattr (int keyno, const struct key_attr *key_attr) { gpg_error_t err = 0; char args[100]; if (key_attr->algo == PUBKEY_ALGO_RSA) snprintf (args, sizeof args, "--force %d 1 rsa%u", keyno+1, key_attr->nbits); else if (key_attr->algo == PUBKEY_ALGO_ECDH || key_attr->algo == PUBKEY_ALGO_ECDSA || key_attr->algo == PUBKEY_ALGO_EDDSA) snprintf (args, sizeof args, "--force %d %d %s", keyno+1, key_attr->algo, key_attr->curve); else { /* FIXME: Above we use openpgp algo names but in the error * message we use the gcrypt names. We should settle for a * consistent solution. */ log_error (_("public key algorithm %d (%s) is not supported\n"), key_attr->algo, gcry_pk_algo_name (key_attr->algo)); err = gpg_error (GPG_ERR_PUBKEY_ALGO); goto leave; } err = scd_setattr ("KEY-ATTR", args, strlen (args)); if (err) log_error (_("error changing key attribute for key %d: %s\n"), keyno+1, gpg_strerror (err)); leave: return err; } static gpg_error_t cmd_keyattr (card_info_t info, char *argstr) { gpg_error_t err = 0; int keyno; struct key_attr *key_attr = NULL; (void)argstr; if (!info) return print_help ("KEY-ATTR\n\n" "Menu to change the key attributes of an OpenPGP card.", APP_TYPE_OPENPGP, 0); if (info->apptype != APP_TYPE_OPENPGP) { log_info ("Note: This is an OpenPGP only command.\n"); return gpg_error (GPG_ERR_NOT_SUPPORTED); } if (!(info->is_v2 && info->extcap.aac)) { log_error (_("This command is not supported by this card\n")); err = gpg_error (GPG_ERR_NOT_SUPPORTED); goto leave; } for (keyno = 0; keyno < DIM (info->key_attr); keyno++) { xfree (key_attr); key_attr = NULL; err = ask_card_keyattr (keyno, &info->key_attr[keyno], &key_attr); if (err) goto leave; err = do_change_keyattr (keyno, key_attr); if (err) { /* Error: Better read the default key attribute again. */ log_debug ("FIXME\n"); /* Ask again for this key. */ keyno--; } } leave: xfree (key_attr); return err; } static gpg_error_t cmd_uif (card_info_t info, char *argstr) { gpg_error_t err; int keyno; if (!info) return print_help ("UIF N [on|off|permanent]\n\n" "Change the User Interaction Flag. N must in the range 1 to 3.", APP_TYPE_OPENPGP, APP_TYPE_PIV, 0); argstr = skip_options (argstr); if (digitp (argstr)) { keyno = atoi (argstr); while (digitp (argstr)) argstr++; while (spacep (argstr)) argstr++; } else keyno = 0; if (keyno < 1 || keyno > 3) { err = gpg_error (GPG_ERR_INV_ARG); goto leave; } err = GPG_ERR_NOT_IMPLEMENTED; leave: return err; } static gpg_error_t cmd_yubikey (card_info_t info, char *argstr) { gpg_error_t err, err2; estream_t fp = opt.interactive? NULL : es_stdout; char *words[20]; int nwords; if (!info) return print_help ("YUBIKEY args\n\n" "Various commands pertaining to Yubikey tokens with being:\n" "\n" " LIST \n" "\n" "List supported and enabled applications.\n" "\n" " ENABLE usb|nfc|all [otp|u2f|opgp|piv|oath|fido2|all]\n" " DISABLE usb|nfc|all [otp|u2f|opgp|piv|oath|fido2|all]\n" "\n" "Enable or disable the specified or all applications on the\n" "given interface.", 0); argstr = skip_options (argstr); if (!info->cardtype || strcmp (info->cardtype, "yubikey")) { log_info ("This command can only be used with Yubikeys.\n"); err = gpg_error (GPG_ERR_NOT_SUPPORTED); goto leave; } nwords = split_fields (argstr, words, DIM (words)); if (nwords < 1) { err = gpg_error (GPG_ERR_SYNTAX); goto leave; } /* Note that we always do a learn to get a chance to the card back * into a usable state. */ err = yubikey_commands (fp, nwords, words); err2 = scd_learn (info); if (err2) log_error ("Error re-reading card: %s\n", gpg_strerror (err)); leave: return err; } /* Data used by the command parser. This needs to be outside of the * function scope to allow readline based command completion. */ enum cmdids { cmdNOP = 0, cmdQUIT, cmdHELP, cmdLIST, cmdRESET, cmdVERIFY, cmdNAME, cmdURL, cmdFETCH, cmdLOGIN, cmdLANG, cmdSALUT, cmdCAFPR, cmdFORCESIG, cmdGENERATE, cmdPASSWD, cmdPRIVATEDO, cmdWRITECERT, cmdREADCERT, cmdWRITEKEY, cmdUNBLOCK, cmdFACTRST, cmdKDFSETUP, cmdKEYATTR, cmdUIF, cmdAUTH, cmdYUBIKEY, cmdINVCMD }; static struct { const char *name; enum cmdids id; const char *desc; } cmds[] = { { "quit" , cmdQUIT, N_("quit this menu")}, { "q" , cmdQUIT, NULL }, { "help" , cmdHELP, N_("show this help")}, { "?" , cmdHELP, NULL }, { "list" , cmdLIST, N_("list all available data")}, { "l" , cmdLIST, NULL }, { "name" , cmdNAME, N_("change card holder's name")}, { "url" , cmdURL, N_("change URL to retrieve key")}, { "fetch" , cmdFETCH, N_("fetch the key specified in the card URL")}, { "login" , cmdLOGIN, N_("change the login name")}, { "lang" , cmdLANG, N_("change the language preferences")}, { "salutation",cmdSALUT, N_("change card holder's salutation")}, { "salut" , cmdSALUT, NULL }, { "cafpr" , cmdCAFPR , N_("change a CA fingerprint")}, { "forcesig", cmdFORCESIG, N_("toggle the signature force PIN flag")}, { "generate", cmdGENERATE, N_("generate new keys")}, { "passwd" , cmdPASSWD, N_("menu to change or unblock the PIN")}, { "verify" , cmdVERIFY, N_("verify the PIN and list all data")}, { "unblock" , cmdUNBLOCK, N_("unblock the PIN using a Reset Code")}, { "authenticate",cmdAUTH, N_("authenticate to the card")}, { "auth" , cmdAUTH, NULL }, { "reset" , cmdRESET, N_("send a reset to the card daemon")}, { "factory-reset",cmdFACTRST, N_("destroy all keys and data")}, { "kdf-setup", cmdKDFSETUP, N_("setup KDF for PIN authentication")}, { "key-attr", cmdKEYATTR, N_("change the key attribute")}, { "uif", cmdUIF, N_("change the User Interaction Flag")}, { "privatedo", cmdPRIVATEDO, N_("change a private data object")}, { "readcert", cmdREADCERT, N_("read a certificate from a data object")}, { "writecert", cmdWRITECERT, N_("store a certificate to a data object")}, { "writekey", cmdWRITEKEY, N_("store a private key to a data object")}, { "yubikey", cmdYUBIKEY, N_("Yubikey management commands")}, { NULL, cmdINVCMD, NULL } }; /* The command line command dispatcher. */ static gpg_error_t dispatch_command (card_info_t info, const char *orig_command) { gpg_error_t err = 0; enum cmdids cmd; /* The command. */ char *command; /* A malloced copy of ORIG_COMMAND. */ char *argstr; /* The argument as a string. */ int i; int ignore_error; if ((ignore_error = *orig_command == '-')) orig_command++; command = xstrdup (orig_command); argstr = NULL; if ((argstr = strchr (command, ' '))) { *argstr++ = 0; trim_spaces (command); trim_spaces (argstr); } for (i=0; cmds[i].name; i++ ) if (!ascii_strcasecmp (command, cmds[i].name )) break; cmd = cmds[i].id; /* (If not found this will be cmdINVCMD). */ /* Make sure we have valid strings for the args. They are allowed * to be modified and must thus point to a buffer. */ if (!argstr) argstr = command + strlen (command); /* For most commands we need to make sure that we have a card. */ if (!info) ; /* Help mode */ else if (!(cmd == cmdNOP || cmd == cmdQUIT || cmd == cmdHELP || cmd == cmdINVCMD) && !info->initialized) { err = scd_learn (info); if (err) { log_error ("Error reading card: %s\n", gpg_strerror (err)); goto leave; } } switch (cmd) { case cmdNOP: if (!info) print_help ("NOP\n\n" "Dummy command.", 0); break; case cmdQUIT: if (!info) print_help ("QUIT\n\n" "Stop processing.", 0); else { err = gpg_error (GPG_ERR_EOF); goto leave; } break; case cmdHELP: if (!info) print_help ("HELP [command]\n\n" "Show all commands. With an argument show help\n" "for that command.", 0); else if (*argstr) dispatch_command (NULL, argstr); else { es_printf ("List of commands (\"help \" for details):\n"); for (i=0; cmds[i].name; i++ ) if(cmds[i].desc) es_printf("%-14s %s\n", cmds[i].name, _(cmds[i].desc) ); es_printf ("Prefix a command with a dash to ignore its error.\n"); } break; case cmdLIST: if (!info) print_help ("LIST\n\n" "Show content of the card.", 0); else { err = scd_learn (info); if (err) log_error ("Error reading card: %s\n", gpg_strerror (err)); else list_card (info); } break; case cmdRESET: if (!info) print_help ("RESET\n\n" "Send a RESET to the card daemon.", 0); else { flush_keyblock_cache (); err = scd_apdu (NULL, NULL, NULL, NULL); } break; case cmdVERIFY: err = cmd_verify (info, argstr); break; case cmdAUTH: err = cmd_authenticate (info, argstr); break; case cmdNAME: err = cmd_name (info, argstr); break; case cmdURL: err = cmd_url (info, argstr); break; case cmdFETCH: err = cmd_fetch (info); break; case cmdLOGIN: err = cmd_login (info, argstr); break; case cmdLANG: err = cmd_lang (info, argstr); break; case cmdSALUT: err = cmd_salut (info, argstr); break; case cmdCAFPR: err = cmd_cafpr (info, argstr); break; case cmdPRIVATEDO: err = cmd_privatedo (info, argstr); break; case cmdWRITECERT: err = cmd_writecert (info, argstr); break; case cmdREADCERT: err = cmd_readcert (info, argstr); break; case cmdWRITEKEY: err = cmd_writekey (info, argstr); break; case cmdFORCESIG: err = cmd_forcesig (info); break; case cmdGENERATE: err = cmd_generate (info, argstr); break; case cmdPASSWD: err = cmd_passwd (info, argstr); break; case cmdUNBLOCK: err = cmd_unblock (info); break; case cmdFACTRST: err = cmd_factoryreset (info); break; case cmdKDFSETUP: err = cmd_kdfsetup (info, argstr); break; case cmdKEYATTR: err = cmd_keyattr (info, argstr); break; case cmdUIF: err = cmd_uif (info, argstr); break; case cmdYUBIKEY: err = cmd_yubikey (info, argstr); break; case cmdINVCMD: default: log_error (_("Invalid command (try \"help\")\n")); break; } /* End command switch. */ leave: /* Return GPG_ERR_EOF only if its origin was "quit". */ es_fflush (es_stdout); if (gpg_err_code (err) == GPG_ERR_EOF && cmd != cmdQUIT) err = gpg_error (GPG_ERR_GENERAL); if (err && gpg_err_code (err) != GPG_ERR_EOF) { if (ignore_error) { log_info ("Command '%s' failed: %s\n", command, gpg_strerror (err)); err = 0; } else log_error ("Command '%s' failed: %s\n", command, gpg_strerror (err)); } xfree (command); return err; } /* The interactive main loop. */ static void interactive_loop (void) { gpg_error_t err; char *answer = NULL; /* The input line. */ enum cmdids cmd = cmdNOP; /* The command. */ char *argstr; /* The argument as a string. */ int redisplay = 1; /* Whether to redisplay the main info. */ char *help_arg = NULL; /* Argument of the HELP command. */ struct card_info_s info_buffer = { 0 }; card_info_t info = &info_buffer; char *p; int i; /* In the interactive mode we do not want to print the program prefix. */ log_set_prefix (NULL, 0); for (;;) { if (help_arg) { /* Clear info to indicate helpmode */ info = NULL; } else if (!info) { /* Get out of help. */ info = &info_buffer; help_arg = NULL; redisplay = 0; } else if (redisplay) { err = scd_learn (info); if (err) { log_error ("Error reading card: %s\n", gpg_strerror (err)); } else { list_card (info); tty_printf("\n"); redisplay = 0; } } if (!info) { /* Copy the pending help arg into our answer. Noe that * help_arg points into answer. */ p = xstrdup (help_arg); help_arg = NULL; xfree (answer); answer = p; } else { do { xfree (answer); tty_enable_completion (command_completion); answer = tty_get (_("gpg/card> ")); tty_kill_prompt(); tty_disable_completion (); trim_spaces(answer); } while ( *answer == '#' ); } argstr = NULL; if (!*answer) cmd = cmdLIST; /* We default to the list command */ else if (*answer == CONTROL_D) cmd = cmdQUIT; else { if ((argstr = strchr (answer,' '))) { *argstr++ = 0; trim_spaces (answer); trim_spaces (argstr); } for (i=0; cmds[i].name; i++ ) if (!ascii_strcasecmp (answer, cmds[i].name )) break; cmd = cmds[i].id; } /* Make sure we have valid strings for the args. They are * allowed to be modified and must thus point to a buffer. */ if (!argstr) argstr = answer + strlen (answer); if (!(cmd == cmdNOP || cmd == cmdQUIT || cmd == cmdHELP || cmd == cmdINVCMD)) { /* If redisplay is set we know that there was an error reading * the card. In this case we force a LIST command to retry. */ if (!info) ; /* In help mode. */ else if (redisplay) { cmd = cmdLIST; } else if (!info->serialno) { /* Without a serial number most commands won't work. * Catch it here. */ tty_printf ("\n"); tty_printf ("Serial number missing\n"); continue; } } err = 0; switch (cmd) { case cmdNOP: if (!info) print_help ("NOP\n\n" "Dummy command.", 0); break; case cmdQUIT: if (!info) print_help ("QUIT\n\n" "Leave this tool.", 0); else { tty_printf ("\n"); goto leave; } break; case cmdHELP: if (!info) print_help ("HELP [command]\n\n" "Show all commands. With an argument show help\n" "for that command.", 0); else if (*argstr) help_arg = argstr; /* Trigger help for a command. */ else { tty_printf ("List of commands (\"help \" for details):\n"); for (i=0; cmds[i].name; i++ ) if(cmds[i].desc) tty_printf("%-14s %s\n", cmds[i].name, _(cmds[i].desc) ); } break; case cmdLIST: if (!info) print_help ("LIST\n\n" "Show content of the card.", 0); else { /* Actual work is done by the redisplay code block. */ redisplay = 1; } break; case cmdRESET: if (!info) print_help ("RESET\n\n" "Send a RESET to the card daemon.", 0); else { flush_keyblock_cache (); err = scd_apdu (NULL, NULL, NULL, NULL); } break; case cmdVERIFY: err = cmd_verify (info, argstr); if (!err) redisplay = 1; break; case cmdAUTH: err = cmd_authenticate (info, argstr); break; case cmdNAME: err = cmd_name (info, argstr); break; case cmdURL: err = cmd_url (info, argstr); break; case cmdFETCH: err = cmd_fetch (info); break; case cmdLOGIN: err = cmd_login (info, argstr); break; case cmdLANG: err = cmd_lang (info, argstr); break; case cmdSALUT: err = cmd_salut (info, argstr); break; case cmdCAFPR: err = cmd_cafpr (info, argstr); break; case cmdPRIVATEDO: err = cmd_privatedo (info, argstr); break; case cmdWRITECERT: err = cmd_writecert (info, argstr); break; case cmdREADCERT: err = cmd_readcert (info, argstr); break; case cmdWRITEKEY: err = cmd_writekey (info, argstr); break; case cmdFORCESIG: err = cmd_forcesig (info); break; case cmdGENERATE: err = cmd_generate (info, argstr); break; case cmdPASSWD: err = cmd_passwd (info, argstr); break; case cmdUNBLOCK: err = cmd_unblock (info); break; case cmdFACTRST: err = cmd_factoryreset (info); if (!err) redisplay = 1; break; case cmdKDFSETUP: err = cmd_kdfsetup (info, argstr); break; case cmdKEYATTR: err = cmd_keyattr (info, argstr); break; case cmdUIF: err = cmd_uif (info, argstr); break; case cmdYUBIKEY: err = cmd_yubikey (info, argstr); break; case cmdINVCMD: default: tty_printf ("\n"); tty_printf (_("Invalid command (try \"help\")\n")); break; } /* End command switch. */ if (gpg_err_code (err) == GPG_ERR_CANCELED) tty_fprintf (NULL, "\n"); else if (err) { const char *s = "?"; for (i=0; cmds[i].name; i++ ) if (cmd == cmds[i].id) { s = cmds[i].name; break; } log_error ("Command '%s' failed: %s\n", s, gpg_strerror (err)); } } /* End of main menu loop. */ leave: release_card_info (info); xfree (answer); } #ifdef HAVE_LIBREADLINE /* Helper function for readline's command completion. */ static char * command_generator (const char *text, int state) { static int list_index, len; const char *name; /* If this is a new word to complete, initialize now. This includes * saving the length of TEXT for efficiency, and initializing the index variable to 0. */ if (!state) { list_index = 0; len = strlen(text); } /* Return the next partial match */ while ((name = cmds[list_index].name)) { /* Only complete commands that have help text. */ if (cmds[list_index++].desc && !strncmp (name, text, len)) return strdup(name); } return NULL; } /* Second helper function for readline's command completion. */ static char ** command_completion (const char *text, int start, int end) { (void)end; /* If we are at the start of a line, we try and command-complete. * If not, just do nothing for now. The support for help completion * needs to be more smarter. */ if (!start) return rl_completion_matches (text, command_generator); else if (start == 5 && !ascii_strncasecmp (rl_line_buffer, "help ", 5)) return rl_completion_matches (text, command_generator); rl_attempted_completion_over = 1; return NULL; } #endif /*HAVE_LIBREADLINE*/ diff --git a/tools/gpg-card.h b/tools/gpg-card.h index 3a86a67ec..099ea5448 100644 --- a/tools/gpg-card.h +++ b/tools/gpg-card.h @@ -1,228 +1,230 @@ /* gpg-card.h - Common definitions for the gpg-card-tool * Copyright (C) 2019 g10 Code GmbH * * This file is part of GnuPG. * * This file 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. * * This file is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . * SPDX-License-Identifier: GPL-3.0-or-later */ #ifndef GNUPG_GPG_CARD_H #define GNUPG_GPG_CARD_H #include "../common/session-env.h" /* We keep all global options in the structure OPT. */ struct { int interactive; int verbose; unsigned int debug; int quiet; int with_colons; const char *gpg_program; const char *gpgsm_program; const char *agent_program; int autostart; /* Options passed to the gpg-agent: */ session_env_t session_env; char *lc_ctype; char *lc_messages; } opt; /* Debug values and macros. */ #define DBG_IPC_VALUE 1024 /* Debug assuan communication. */ #define DBG_EXTPROG_VALUE 16384 /* Debug external program calls */ #define DBG_IPC (opt.debug & DBG_IPC_VALUE) #define DBG_EXTPROG (opt.debug & DBG_EXTPROG_VALUE) /* The maximum length of a binary fingerprint. */ #define MAX_FINGERPRINT_LEN 32 /* * Data structures to store keyblocks (aka certificates). */ struct pubkey_s { struct pubkey_s *next; /* The next key. */ unsigned char grip[KEYGRIP_LEN]; unsigned char fpr[MAX_FINGERPRINT_LEN]; unsigned char fprlen; /* The used length of a FPR. */ unsigned int grip_valid:1;/* The grip is valid. */ unsigned int requested: 1;/* This is the requested grip. */ }; typedef struct pubkey_s *pubkey_t; struct userid_s { struct userid_s *next; char *value; /* Malloced. */ }; typedef struct userid_s *userid_t; struct keyblock_s { struct keyblock_s *next; /* Allow to link several keyblocks. */ int protocol; /* GPGME_PROTOCOL_OPENPGP or _CMS. */ pubkey_t keys; /* The key. For OpenPGP primary + list of subkeys. */ userid_t uids; /* The list of user ids. */ }; typedef struct keyblock_s *keyblock_t; /* Enumeration of the known card application types. */ typedef enum { APP_TYPE_NONE, /* Not yet known or for direct APDU sending. */ APP_TYPE_OPENPGP, APP_TYPE_NKS, APP_TYPE_DINSIG, APP_TYPE_P15, APP_TYPE_GELDKARTE, APP_TYPE_SC_HSM, APP_TYPE_PIV, APP_TYPE_UNKNOWN /* Unknown by this tool. */ } app_type_t; /* OpenPGP card key attributes. */ struct key_attr { int algo; /* Algorithm identifier. */ union { unsigned int nbits; /* Supported keysize. */ const char *curve; /* Name of curve. */ }; }; /* An object to store information pertaining to a key pair as stored * on a card. This is commonly used as a linked list with all keys * known for the current card. */ struct key_info_s { struct key_info_s *next; unsigned char grip[20];/* The keygrip. */ unsigned char xflag; /* Temporary flag to help processing a list. */ /* The three next items are mostly useful for OpenPGP cards. */ unsigned char fprlen; /* Use length of the next item. */ unsigned char fpr[32]; /* The binary fingerprint of length FPRLEN. */ u32 created; /* The time the key was created. */ unsigned int usage; /* Usage flags. (GCRY_PK_USAGE_*) */ char keyref[1]; /* String with the keyref (e.g. OPENPGP.1). */ }; typedef struct key_info_s *key_info_t; /* * The object used to store information about a card. */ struct card_info_s { int initialized; /* True if a learn command was successful. */ int error; /* private. */ char *reader; /* Reader information. */ char *cardtype; /* NULL or type of the card. */ + unsigned int cardversion; /* Firmware version of the card. */ char *apptypestr; /* Malloced application type string. */ app_type_t apptype;/* Translated from APPTYPESTR. */ + unsigned int appversion; /* Version of the application. */ char *serialno; /* malloced hex string. */ char *dispserialno;/* malloced string. */ char *disp_name; /* malloced. */ char *disp_lang; /* malloced. */ int disp_sex; /* 0 = unspecified, 1 = male, 2 = female */ char *pubkey_url; /* malloced. */ char *login_data; /* malloced. */ char *private_do[4]; /* malloced. */ char cafpr1len; /* Length of the CA-fingerprint or 0 if invalid. */ char cafpr2len; char cafpr3len; char cafpr1[20]; char cafpr2[20]; char cafpr3[20]; key_info_t kinfo; /* Linked list with all keypair related data. */ unsigned long sig_counter; int chv1_cached; /* For openpgp this is true if a PIN is not required for each signing. Note that the gpg-agent might cache it anyway. */ int is_v2; /* True if this is a v2 openpgp card. */ int chvmaxlen[3]; /* Maximum allowed length of a CHV. */ int chvinfo[3]; /* Allowed retries for the CHV; 0 = blocked. */ unsigned char chvusage[2]; /* Data object 5F2F */ struct key_attr key_attr[3]; /* OpenPGP card key attributes. */ struct { unsigned int ki:1; /* Key import available. */ unsigned int aac:1; /* Algorithm attributes are changeable. */ unsigned int kdf:1; /* KDF object to support PIN hashing available. */ unsigned int bt:1; /* Button for confirmation available. */ } extcap; unsigned int status_indicator; int kdf_do_enabled; /* True if card has a KDF object. */ int uif[3]; /* True if User Interaction Flag is on. */ }; typedef struct card_info_s *card_info_t; /*-- card-keys.c --*/ void release_keyblock (keyblock_t keyblock); void flush_keyblock_cache (void); gpg_error_t get_matching_keys (const unsigned char *keygrip, int protocol, keyblock_t *r_keyblock); gpg_error_t test_get_matching_keys (const char *hexgrip); /*-- card-misc.c --*/ key_info_t find_kinfo (card_info_t info, const char *keyref); void *hex_to_buffer (const char *string, size_t *r_length); gpg_error_t send_apdu (const char *hexapdu, const char *desc, unsigned int ignore, unsigned char **r_data, size_t *r_datalen); /*-- card-call-scd.c --*/ void release_card_info (card_info_t info); const char *app_type_string (app_type_t app_type); gpg_error_t scd_apdu (const char *hexapdu, unsigned int *r_sw, unsigned char **r_data, size_t *r_datalen); gpg_error_t scd_learn (card_info_t info); gpg_error_t scd_getattr (const char *name, struct card_info_s *info); gpg_error_t scd_setattr (const char *name, const unsigned char *value, size_t valuelen); gpg_error_t scd_writecert (const char *certidstr, const unsigned char *certdata, size_t certdatalen); gpg_error_t scd_writekey (const char *keyref, int force, const char *keygrip); gpg_error_t scd_genkey (const char *keyref, int force, const char *algo, u32 *createtime); gpg_error_t scd_serialno (char **r_serialno, const char *demand); gpg_error_t scd_readcert (const char *certidstr, void **r_buf, size_t *r_buflen); gpg_error_t scd_readkey (const char *keyrefstr, gcry_sexp_t *r_result); gpg_error_t scd_cardlist (strlist_t *result); gpg_error_t scd_change_pin (const char *pinref, int reset_mode); gpg_error_t scd_checkpin (const char *serialno); unsigned long agent_get_s2k_count (void); /*-- card-yubikey.c --*/ gpg_error_t yubikey_commands (estream_t fp, int argc, char *argv[]); #endif /*GNUPG_GPG_CARD_H*/