diff --git a/scd/app-common.h b/scd/app-common.h index a13623fde..97b9b39ef 100644 --- a/scd/app-common.h +++ b/scd/app-common.h @@ -132,6 +132,7 @@ struct app_ctx_s { struct app_local_s *app_local; /* Local to the application. */ struct { void (*deinit) (app_t app); + gpg_error_t (*reselect) (app_t app, ctrl_t ctrl); gpg_error_t (*learn_status) (app_t app, ctrl_t ctrl, unsigned int flags); gpg_error_t (*readcert) (app_t app, const char *certid, unsigned char **cert, size_t *certlen); @@ -233,6 +234,7 @@ gpg_error_t card_reset (card_t card, ctrl_t ctrl, int send_reset); gpg_error_t select_application (ctrl_t ctrl, const char *name, card_t *r_app, int scan, const unsigned char *serialno_bin, size_t serialno_bin_len); +gpg_error_t select_additional_application (ctrl_t ctrl, const char *name); char *get_supported_applications (void); card_t card_ref (card_t card); diff --git a/scd/app-dinsig.c b/scd/app-dinsig.c index 16a82ade4..9993b68ff 100644 --- a/scd/app-dinsig.c +++ b/scd/app-dinsig.c @@ -557,6 +557,7 @@ app_select_dinsig (app_t app) { app->apptype = APPTYPE_DINSIG; + app->fnc.reselect = NULL; app->fnc.learn_status = do_learn_status; app->fnc.readcert = do_readcert; app->fnc.getattr = NULL; diff --git a/scd/app-geldkarte.c b/scd/app-geldkarte.c index 15b38194d..141985932 100644 --- a/scd/app-geldkarte.c +++ b/scd/app-geldkarte.c @@ -313,6 +313,7 @@ app_select_geldkarte (app_t app) app->apptype = APPTYPE_GELDKARTE; app->fnc.deinit = do_deinit; + app->fnc.reselect = NULL; /* If we don't have a serialno yet construct it from the EF_ID. */ if (!app->card->serialno) diff --git a/scd/app-nks.c b/scd/app-nks.c index 0651e5d57..d12720cf6 100644 --- a/scd/app-nks.c +++ b/scd/app-nks.c @@ -1424,6 +1424,7 @@ app_select_nks (app_t app) log_info ("Detected NKS version: %d\n", app->app_local->nks_version); app->fnc.deinit = do_deinit; + app->fnc.reselect = NULL; app->fnc.learn_status = do_learn_status; app->fnc.readcert = do_readcert; app->fnc.readkey = do_readkey; diff --git a/scd/app-openpgp.c b/scd/app-openpgp.c index c301f8218..767f29d26 100644 --- a/scd/app-openpgp.c +++ b/scd/app-openpgp.c @@ -59,6 +59,11 @@ #include "../common/openpgpdefs.h" + +/* The AID of this application. */ +static char const openpgp_aid[] = { 0xD2, 0x76, 0x00, 0x01, 0x24, 0x01 }; + + /* A table describing the DOs of the card. */ static struct { int tag; @@ -5204,12 +5209,35 @@ parse_algorithm_attribute (app_t app, int keyno) xfree (relptr); } + +/* Reselect the application. This is used by cards which support + * on-the-fly switching between applications. */ +static gpg_error_t +do_reselect (app_t app, ctrl_t ctrl) +{ + gpg_error_t err; + + (void)ctrl; + + /* An extra check which should not be necessary because the caller + * should have made sure that a re-select is only called for + * approriate cards. */ + if (app->card->cardtype != CARDTYPE_YUBIKEY) + return gpg_error (GPG_ERR_NOT_SUPPORTED); + + /* Note that the card can't cope with P2=0xCO, thus we need to pass + * a special flag value. */ + err = iso7816_select_application (app_get_slot (app), + openpgp_aid, sizeof openpgp_aid, 0x0001); + return err; +} + + /* Select the OpenPGP application on the card in SLOT. This function must be used before any other OpenPGP application functions. */ gpg_error_t app_select_openpgp (app_t app) { - static char const aid[] = { 0xD2, 0x76, 0x00, 0x01, 0x24, 0x01 }; int slot = app_get_slot (app); int rc; unsigned char *buffer; @@ -5218,7 +5246,8 @@ app_select_openpgp (app_t app) /* Note that the card can't cope with P2=0xCO, thus we need to pass a special flag value. */ - rc = iso7816_select_application (slot, aid, sizeof aid, 0x0001); + rc = iso7816_select_application (slot, + openpgp_aid, sizeof openpgp_aid, 0x0001); if (!rc) { unsigned int manufacturer; @@ -5353,6 +5382,7 @@ app_select_openpgp (app_t app) dump_all_do (slot); app->fnc.deinit = do_deinit; + app->fnc.reselect = do_reselect; app->fnc.learn_status = do_learn_status; app->fnc.readcert = do_readcert; app->fnc.readkey = do_readkey; diff --git a/scd/app-p15.c b/scd/app-p15.c index 9cd423ee6..ce82b34a9 100644 --- a/scd/app-p15.c +++ b/scd/app-p15.c @@ -3415,6 +3415,7 @@ app_select_p15 (app_t app) } app->fnc.deinit = do_deinit; + app->fnc.reselect = NULL; app->fnc.learn_status = do_learn_status; app->fnc.readcert = do_readcert; app->fnc.getattr = do_getattr; diff --git a/scd/app-piv.c b/scd/app-piv.c index c1bc79435..3b94a28e4 100644 --- a/scd/app-piv.c +++ b/scd/app-piv.c @@ -81,6 +81,10 @@ #define PIV_ALGORITHM_ECC_P384 0x14 +/* The AID for PIV. */ +static char const piv_aid[] = { 0xA0, 0x00, 0x00, 0x03, 0x08, /* RID=NIST */ + 0x00, 0x00, 0x10, 0x00 /* PIX=PIV */ }; + /* A table describing the DOs of a PIV card. */ struct data_object_s @@ -3390,13 +3394,32 @@ do_with_keygrip (app_t app, ctrl_t ctrl, int action, } +/* Reselect the application. This is used by cards which support + * on-the-fly switching between applications. */ +static gpg_error_t +do_reselect (app_t app, ctrl_t ctrl) +{ + gpg_error_t err; + + (void)ctrl; + + /* An extra check which should not be necessary because the caller + * should have made sure that a re-select is only called for + * approriate cards. */ + if (!app->app_local->flags.yubikey) + return gpg_error (GPG_ERR_NOT_SUPPORTED); + + err = iso7816_select_application (app_get_slot (app), + piv_aid, sizeof piv_aid, 0x0001); + return err; +} + + /* Select the PIV application on the card in SLOT. This function must * be used before any other PIV application functions. */ gpg_error_t app_select_piv (app_t app) { - static char const aid[] = { 0xA0, 0x00, 0x00, 0x03, 0x08, /* RID=NIST */ - 0x00, 0x00, 0x10, 0x00 /* PIX=PIV */ }; int slot = app_get_slot (app); gpg_error_t err; unsigned char *apt = NULL; @@ -3407,7 +3430,7 @@ app_select_piv (app_t app) /* Note that we select using the AID without the 2 octet version * number. This allows for better reporting of future specs. We * need to use the use-zero-for-P2-flag. */ - err = iso7816_select_application_ext (slot, aid, sizeof aid, 0x0001, + err = iso7816_select_application_ext (slot, piv_aid, sizeof piv_aid, 0x0001, &apt, &aptlen); if (err) goto leave; @@ -3427,7 +3450,7 @@ app_select_piv (app_t app) } s = find_tlv (apt, aptlen, 0x4F, &n); - if (!s || n != 6 || memcmp (s, aid+5, 4)) + if (!s || n != 6 || memcmp (s, piv_aid+5, 4)) { /* The PIX does not match. */ log_error ("piv: missing or invalid DO 0x4F in APT\n"); @@ -3450,7 +3473,7 @@ app_select_piv (app_t app) goto leave; } s = find_tlv (s, n, 0x4F, &n); - if (!s || n != 5 || memcmp (s, aid, 5)) + if (!s || n != 5 || memcmp (s, piv_aid, 5)) { /* The RID does not match. */ log_error ("piv: missing or invalid DO 0x79.4F in APT\n"); @@ -3475,6 +3498,7 @@ app_select_piv (app_t app) dump_all_do (slot); app->fnc.deinit = do_deinit; + app->fnc.reselect = do_reselect; app->fnc.learn_status = do_learn_status; app->fnc.readcert = do_readcert; app->fnc.readkey = do_readkey; diff --git a/scd/app-sc-hsm.c b/scd/app-sc-hsm.c index 87e58d984..16d25b581 100644 --- a/scd/app-sc-hsm.c +++ b/scd/app-sc-hsm.c @@ -2069,6 +2069,7 @@ app_select_sc_hsm (app_t app) goto leave; app->fnc.deinit = do_deinit; + app->fnc.reselect = NULL; app->fnc.learn_status = do_learn_status; app->fnc.readcert = do_readcert; app->fnc.getattr = do_getattr; diff --git a/scd/app.c b/scd/app.c index ccba75c89..c568b636b 100644 --- a/scd/app.c +++ b/scd/app.c @@ -91,6 +91,24 @@ strapptype (apptype_t t) } +/* Return the apptype for NAME. */ +static apptype_t +apptype_from_name (const char *name) +{ + int i; + + if (!name) + return APPTYPE_NONE; + + for (i=0; app_priority_list[i].apptype; i++) + if (!ascii_strcasecmp (app_priority_list[i].name, name)) + return app_priority_list[i].apptype; + if (!ascii_strcasecmp ("undefined", name)) + return APPTYPE_UNDEFINED; + return APPTYPE_NONE; +} + + /* Initialization function to change the default app_priority_list. * LIST is a list of comma or space separated strings with application * names. Unknown names will only result in warning message. @@ -247,14 +265,18 @@ is_app_allowed (const char *name) gpg_error_t check_application_conflict (card_t card, const char *name) { + app_t app; + if (!card || !name) return 0; if (!card->app) return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); /* Should not happen. */ - if (!card->app->apptype - || !ascii_strcasecmp (strapptype (card->app->apptype), name)) - return 0; + /* Check whether the requested NAME matches any already selected + * application. */ + for (app = card->app; app; app = app->next) + if (!ascii_strcasecmp (strapptype (app->apptype), name)) + return 0; if (card->app->apptype == APPTYPE_UNDEFINED) return 0; @@ -535,10 +557,6 @@ app_new_register (int slot, ctrl_t ctrl, const char *name, card->next = card_top; card_top = card; - /* If no current apptype is known for this session, set it now. */ - if (!ctrl->current_apptype) - ctrl->current_apptype = app->apptype; - unlock_card (card); return 0; } @@ -632,7 +650,9 @@ select_application (ctrl_t ctrl, const char *name, card_t *r_card, card->next = card_top; card_top = card; } - } + + ctrl->current_apptype = card->app ? card->app->apptype : 0; + } unlock_card (card); } else @@ -644,6 +664,82 @@ select_application (ctrl_t ctrl, const char *name, card_t *r_card, } +/* This function needs to be called with the NAME of the new + * application to be selected on CARD. On success the application is + * added to the list of the card's active applications as currently + * active application. On error no new application is allocated. + * Selecting an already selected application has no effect. */ +gpg_error_t +select_additional_application (ctrl_t ctrl, const char *name) +{ + gpg_error_t err = 0; + apptype_t req_apptype; + card_t card; + app_t app = NULL; + int i; + + req_apptype = apptype_from_name (name); + if (!req_apptype) + err = gpg_error (GPG_ERR_NOT_FOUND); + + card = ctrl->card_ctx; + if (!card) + return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); + + err = lock_card (card, ctrl); + if (err) + return err; + + /* Check that the requested app has not yet been put onto the list. */ + for (app = card->app; app; app = app->next) + if (app->apptype == req_apptype) + { + /* We already got this one. Note that in this case we don't + * make it the current one but it doesn't matter because + * maybe_switch_app will do that anyway. */ + err = 0; + app = NULL; + goto leave; + } + app = NULL; + + /* Allocate a new app object. */ + app = xtrycalloc (1, sizeof *app); + if (!app) + { + err = gpg_error_from_syserror (); + log_info ("error allocating app context: %s\n", gpg_strerror (err)); + goto leave; + } + + /* Find the app and run the select. */ + for (i=0; app_priority_list[i].apptype; i++) + { + if (app_priority_list[i].apptype == req_apptype + && is_app_allowed (app_priority_list[i].name)) + err = app_priority_list[i].select_func (app); + } + if (!app_priority_list[i].apptype + || (err && gpg_err_code (err) != GPG_ERR_OBJ_TERM_STATE)) + err = gpg_error (GPG_ERR_NOT_SUPPORTED); + + if (err) + goto leave; + + /* Add this app. We make it the current one to avoid an extra + * reselect by maybe_switch_app after the select we just did. */ + app->next = card->app; + card->app = app; + ctrl->current_apptype = app->apptype; + log_error ("added app '%s' to the card context\n", strapptype(app->apptype)); + + leave: + unlock_card (card); + xfree (app); + return err; +} + + char * get_supported_applications (void) { @@ -831,6 +927,63 @@ app_get_serialno (app_t app) } +/* Check that the card has been initialized and whether we need to + * switch to another application on the same card. Switching means + * that the new active app will be moved to the head of the list at + * CARD->app. Thus function must be called with the card lock held. */ +static gpg_error_t +maybe_switch_app (ctrl_t ctrl, card_t card) +{ + gpg_error_t err; + app_t app, app_prev; + + if (!card->ref_count || !card->app) + return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); + if (!ctrl->current_apptype) + { + /* For whatever reasons the current apptype has not been set - + * fix that and use the current app. */ + ctrl->current_apptype = card->app->apptype; + return 0; + } + log_debug ("card=%p want=%s card->app=%s\n", + card, strapptype (ctrl->current_apptype), + strapptype (card->app->apptype)); + app_dump_state (); + if (ctrl->current_apptype == card->app->apptype) + return 0; /* No need to switch. */ + app_prev = card->app; + for (app = app_prev->next; app; app_prev = app, app = app->next) + if (app->apptype == ctrl->current_apptype) + break; + if (!app) + return gpg_error (GPG_ERR_WRONG_CARD); + + if (!app->fnc.reselect) + { + log_error ("oops: reselect function missing for '%s'\n", + strapptype(app->apptype)); + return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); + } + err = app->fnc.reselect (app, ctrl); + if (err) + { + log_error ("error re-selecting '%s': %s\n", + strapptype(app->apptype), gpg_strerror (err)); + return err; + } + /* Swap APP with the head of the app list. Note that APP is not the + * head of the list. */ + app_prev->next = app->next; + app->next = card->app; + card->app = app; + ctrl->current_apptype = app->apptype; + log_error ("switched to '%s'\n", strapptype(app->apptype)); + + return 0; +} + + /* Write out the application specific status lines for the LEARN command. */ gpg_error_t @@ -846,13 +999,14 @@ app_write_learn_status (card_t card, ctrl_t ctrl, unsigned int flags) if (err) return err; - app = card->app; - if (!app) - err = gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); - else if (!app->fnc.learn_status) + if ((err = maybe_switch_app (ctrl, card))) + ; + else if (!card->app->fnc.learn_status) err = gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); else { + app = card->app; + /* We do not send CARD and APPTYPE if only keypairinfo is requested. */ if (!(flags &1)) { @@ -891,8 +1045,8 @@ app_readcert (card_t card, ctrl_t ctrl, const char *certid, if (err) return err; - if (!card->ref_count || !card->app) - err = gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); + if ((err = maybe_switch_app (ctrl, card))) + ; else if (!card->app->fnc.readcert) err = gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); else @@ -928,8 +1082,8 @@ app_readkey (card_t card, ctrl_t ctrl, const char *keyid, unsigned int flags, if (err) return err; - if (!card->ref_count || !card->app) - err = gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); + if ((err = maybe_switch_app (ctrl, card))) + ; else if (!card->app->fnc.readkey) err = gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); else @@ -952,8 +1106,8 @@ app_getattr (card_t card, ctrl_t ctrl, const char *name) if (err) return err; - if (!card->ref_count || !card->app) - err = gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); + if ((err = maybe_switch_app (ctrl, card))) + ; else if (name && !strcmp (name, "CARDTYPE")) { send_status_direct (ctrl, "CARDTYPE", strcardtype (card->cardtype)); @@ -1000,8 +1154,8 @@ app_setattr (card_t card, ctrl_t ctrl, const char *name, if (err) return err; - if (!card->ref_count || !card->app) - err = gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); + if ((err = maybe_switch_app (ctrl, card))) + ; else if (!card->app->fnc.setattr) err = gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); else @@ -1031,8 +1185,8 @@ app_sign (card_t card, ctrl_t ctrl, const char *keyidstr, int hashalgo, if (err) return err; - if (!card->ref_count || !card->app) - err = gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); + if ((err = maybe_switch_app (ctrl, card))) + ; else if (!card->app->fnc.sign) err = gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); else @@ -1067,8 +1221,8 @@ app_auth (card_t card, ctrl_t ctrl, const char *keyidstr, if (err) return err; - if (!card->ref_count || !card->app) - err = gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); + if ((err = maybe_switch_app (ctrl, card))) + ; else if (!card->app->fnc.auth) err = gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); else @@ -1105,8 +1259,8 @@ app_decipher (card_t card, ctrl_t ctrl, const char *keyidstr, if (err) return err; - if (!card->ref_count || !card->app) - err = gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); + if ((err = maybe_switch_app (ctrl, card))) + ; else if (!card->app->fnc.decipher) err = gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); else @@ -1139,8 +1293,8 @@ app_writecert (card_t card, ctrl_t ctrl, if (err) return err; - if (!card->ref_count || !card->app) - err = gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); + if ((err = maybe_switch_app (ctrl, card))) + ; else if (!card->app->fnc.writecert) err = gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); else @@ -1170,8 +1324,8 @@ app_writekey (card_t card, ctrl_t ctrl, if (err) return err; - if (!card->ref_count || !card->app) - err = gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); + if ((err = maybe_switch_app (ctrl, card))) + ; else if (!card->app->fnc.writekey) err = gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); else @@ -1200,8 +1354,8 @@ app_genkey (card_t card, ctrl_t ctrl, const char *keynostr, if (err) return err; - if (!card->ref_count || !card->app) - err = gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); + if ((err = maybe_switch_app (ctrl, card))) + ; else if (!card->app->fnc.genkey) err = gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); else @@ -1255,8 +1409,8 @@ app_change_pin (card_t card, ctrl_t ctrl, const char *chvnostr, if (err) return err; - if (!card->ref_count || !card->app) - err = gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); + if ((err = maybe_switch_app (ctrl, card))) + ; else if (!card->app->fnc.change_pin) err = gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); else @@ -1286,8 +1440,8 @@ app_check_pin (card_t card, ctrl_t ctrl, const char *keyidstr, if (err) return err; - if (!card->ref_count || !card->app) - err = gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); + if ((err = maybe_switch_app (ctrl, card))) + ; else if (!card->app->fnc.check_pin) err = gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); else @@ -1513,8 +1667,12 @@ app_do_with_keygrip (ctrl_t ctrl, int action, const char *keygrip_str) /* FIXME: Add app switching logic. The above code assumes that the * actions can be performend without switching. This needs to be - * checked. For a lookup we also need to reorder the apps so that - * the selected one will be used. */ + * checked. */ + + /* Force switching of the app if the selected one is not the current + * one. Changing the current apptype is sufficient to do this. */ + if (c && c->app && c->app->apptype != a->apptype) + ctrl->current_apptype = a->apptype; npth_mutex_unlock (&card_list_lock); return c; diff --git a/scd/command.c b/scd/command.c index 3156aa9ef..2b851c5fd 100644 --- a/scd/command.c +++ b/scd/command.c @@ -231,9 +231,16 @@ open_card_with_request (ctrl_t ctrl, /* 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. */ - /* FIXME: Extend to allow switching between apps. */ if (apptypestr && ctrl->card_ctx) - return check_application_conflict (ctrl->card_ctx, apptypestr); + { + err = check_application_conflict (ctrl->card_ctx, apptypestr); + if (gpg_err_code (err) == GPG_ERR_FALSE) + { + /* Different application but switching is supported. */ + err = select_additional_application (ctrl, apptypestr); + } + return err; + } /* Re-scan USB devices. Release CARD, before the scan. */ /* FIXME: Is a card_unref sufficient or do we need to deallocate? */ @@ -2084,7 +2091,6 @@ scd_clear_current_app (card_t card) } } - /* 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). */ diff --git a/scd/scdaemon.h b/scd/scdaemon.h index 7ebf6b58d..b709b1cbc 100644 --- a/scd/scdaemon.h +++ b/scd/scdaemon.h @@ -107,7 +107,7 @@ struct server_control_s struct card_ctx_s *card_ctx; /* The currently active application for this context. We need to - * knw this for cards which are abale to swicth on the fly between + * know this for cards which are able to switch on the fly between * apps. */ apptype_t current_apptype;