From 37b1c5c2004c1147a13b388863aaa8f0caf7d71f Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Fri, 19 Mar 2021 18:26:03 +0100 Subject: [PATCH] scd:openpgp: Allow reading and writing user certs for keys 1 and 2 * scd/iso7816.c (CMD_SELECT_DATA): New. (iso7816_select_data): New. * scd/app-openpgp.c (do_readcert): Allow OpenPGP.1 and OPENPGP.2 (do_writecert): Ditto. (do_setattr): Add CERT-1 and CERT-2. -- This has been tested with a Zeitcontrol 3.4 card. A test with a Yubikey 5 (firmware 5.2.6) claiming to support 3.4 failed. Signed-off-by: Werner Koch --- scd/app-openpgp.c | 108 ++++++++++++++++++++++++++++++++++++---------- scd/iso7816.c | 39 +++++++++++++++++ scd/iso7816.h | 1 + 3 files changed, 126 insertions(+), 22 deletions(-) diff --git a/scd/app-openpgp.c b/scd/app-openpgp.c index 228a3dbbf..6fe13354c 100644 --- a/scd/app-openpgp.c +++ b/scd/app-openpgp.c @@ -2245,39 +2245,73 @@ do_readkey (app_t app, ctrl_t ctrl, const char *keyid, unsigned int flags, /* Read the standard certificate of an OpenPGP v2 card. It is returned in a freshly allocated buffer with that address stored at - CERT and the length of the certificate stored at CERTLEN. CERTID - needs to be set to "OPENPGP.3". */ + CERT and the length of the certificate stored at CERTLEN. */ static gpg_error_t do_readcert (app_t app, const char *certid, unsigned char **cert, size_t *certlen) { gpg_error_t err; - unsigned char *buffer; - size_t buflen; - void *relptr; + int occurrence = 0; *cert = NULL; *certlen = 0; - if (strcmp (certid, "OPENPGP.3")) + if (!ascii_strcasecmp (certid, "OPENPGP.3")) + ; + else if (!ascii_strcasecmp (certid, "OPENPGP.2")) + occurrence = 1; + else if (!ascii_strcasecmp (certid, "OPENPGP.1")) + occurrence = 2; + else return gpg_error (GPG_ERR_INV_ID); + if (!app->app_local->extcap.is_v3 && occurrence) + return gpg_error (GPG_ERR_NOT_SUPPORTED); if (!app->app_local->extcap.is_v2) return gpg_error (GPG_ERR_NOT_FOUND); - relptr = get_one_do (app, 0x7F21, &buffer, &buflen, NULL); - if (!relptr) - return gpg_error (GPG_ERR_NOT_FOUND); + if (occurrence) + { + int exmode; - if (!buflen) - err = gpg_error (GPG_ERR_NOT_FOUND); - else if (!(*cert = xtrymalloc (buflen))) - err = gpg_error_from_syserror (); + err = iso7816_select_data (app_get_slot (app), occurrence, 0x7F21); + if (!err) + { + if (app->app_local->cardcap.ext_lc_le) + exmode = app->app_local->extcap.max_certlen; + else + exmode = 0; + + err = iso7816_get_data (app_get_slot (app), exmode, 0x7F21, + cert, certlen); + /* We reset the curDO even for an error. */ + iso7816_select_data (app_get_slot (app), 0, 0x7F21); + } + + if (err) + err = gpg_error (GPG_ERR_NOT_FOUND); + } else { - memcpy (*cert, buffer, buflen); - *certlen = buflen; - err = 0; + unsigned char *buffer; + size_t buflen; + void *relptr; + + relptr = get_one_do (app, 0x7F21, &buffer, &buflen, NULL); + if (!relptr) + return gpg_error (GPG_ERR_NOT_FOUND); + + if (!buflen) + err = gpg_error (GPG_ERR_NOT_FOUND); + else if (!(*cert = xtrymalloc (buflen))) + err = gpg_error_from_syserror (); + else + { + memcpy (*cert, buffer, buflen); + *certlen = buflen; + err = 0; + } + xfree (relptr); } - xfree (relptr); + return err; } @@ -2909,6 +2943,7 @@ do_setattr (app_t app, ctrl_t ctrl, const char *name, int need_chv; int special; unsigned int need_v2:1; + unsigned int need_v3:1; } table[] = { { "DISP-NAME", 0x005B, 0, 3 }, { "LOGIN-DATA", 0x005E, 0, 3, 2 }, @@ -2923,6 +2958,8 @@ do_setattr (app_t app, ctrl_t ctrl, const char *name, { "PRIVATE-DO-2", 0x0102, 0, 3 }, { "PRIVATE-DO-3", 0x0103, 0, 2 }, { "PRIVATE-DO-4", 0x0104, 0, 3 }, + { "CERT-1", 0x7F21, 0, 3,11, 1, 1 }, + { "CERT-2", 0x7F21, 0, 3,12, 1, 1 }, { "CERT-3", 0x7F21, 0, 3, 0, 1 }, { "SM-KEY-ENC", 0x00D1, 0, 3, 0, 1 }, { "SM-KEY-MAC", 0x00D2, 0, 3, 0, 1 }, @@ -2941,7 +2978,9 @@ do_setattr (app_t app, ctrl_t ctrl, const char *name, if (!table[idx].name) return gpg_error (GPG_ERR_INV_NAME); if (table[idx].need_v2 && !app->app_local->extcap.is_v2) - return gpg_error (GPG_ERR_NOT_SUPPORTED); /* Not yet supported. */ + return gpg_error (GPG_ERR_NOT_SUPPORTED); + if (table[idx].need_v3 && !app->app_local->extcap.is_v3) + return gpg_error (GPG_ERR_NOT_SUPPORTED); if (table[idx].special == 5 && app->app_local->extcap.has_button == 0) return gpg_error (GPG_ERR_INV_OBJ); @@ -3066,8 +3105,23 @@ do_setattr (app_t app, ctrl_t ctrl, const char *name, flush_cache_item (app, 0x00F9); } - rc = iso7816_put_data (app_get_slot (app), - exmode, table[idx].tag, value, valuelen); + + if (table[idx].special == 11 || table[idx].special == 12) /* CERT-1 or -2 */ + { + rc = iso7816_select_data (app_get_slot (app), + table[idx].special == 11? 2 : 1, + table[idx].tag); + if (!rc) + { + rc = iso7816_put_data (app_get_slot (app), + exmode, table[idx].tag, value, valuelen); + /* We better reset the curDO. */ + iso7816_select_data (app_get_slot (app), 0, table[idx].tag); + } + } + else /* Standard. */ + rc = iso7816_put_data (app_get_slot (app), + exmode, table[idx].tag, value, valuelen); if (rc) log_error ("failed to set '%s': %s\n", table[idx].name, gpg_strerror (rc)); @@ -3103,15 +3157,25 @@ do_writecert (app_t app, ctrl_t ctrl, void *pincb_arg, const unsigned char *certdata, size_t certdatalen) { - if (strcmp (certidstr, "OPENPGP.3")) + const char *name; + if (!ascii_strcasecmp (certidstr, "OPENPGP.3")) + name = "CERT-3"; + else if (!ascii_strcasecmp (certidstr, "OPENPGP.2")) + name = "CERT-2"; + else if (!ascii_strcasecmp (certidstr, "OPENPGP.1")) + name = "CERT-1"; + else return gpg_error (GPG_ERR_INV_ID); + if (!certdata || !certdatalen) return gpg_error (GPG_ERR_INV_ARG); if (!app->app_local->extcap.is_v2) return gpg_error (GPG_ERR_NOT_SUPPORTED); + /* do_setattr checks that CERT-2 and CERT-1 requires a v3 card. */ + if (certdatalen > app->app_local->extcap.max_certlen) return gpg_error (GPG_ERR_TOO_LARGE); - return do_setattr (app, ctrl, "CERT-3", pincb, pincb_arg, + return do_setattr (app, ctrl, name, pincb, pincb_arg, certdata, certdatalen); } diff --git a/scd/iso7816.c b/scd/iso7816.c index 19464eab7..8896486b8 100644 --- a/scd/iso7816.c +++ b/scd/iso7816.c @@ -32,6 +32,7 @@ #define CMD_SELECT_FILE 0xA4 +#define CMD_SELECT_DATA 0xA5 #define CMD_VERIFY ISO7816_VERIFY #define CMD_CHANGE_REFERENCE_DATA ISO7816_CHANGE_REFERENCE_DATA #define CMD_RESET_RETRY_COUNTER ISO7816_RESET_RETRY_COUNTER @@ -470,6 +471,44 @@ iso7816_reset_retry_counter (int slot, int chvno, } +/* Perform a SELECT DATA command to OCCURANCE of TAG. */ +gpg_error_t +iso7816_select_data (int slot, int occurrence, int tag) +{ + int sw; + int datalen; + unsigned char data[7]; + + data[0] = 0x60; + data[2] = 0x5c; + if (tag <= 0xff) + { + data[3] = 1; + data[4] = tag; + datalen = 5; + } + else if (tag <= 0xffff) + { + data[3] = 2; + data[4] = (tag >> 8); + data[5] = tag; + datalen = 6; + } + else + { + data[3] = 3; + data[4] = (tag >> 16); + data[5] = (tag >> 8); + data[6] = tag; + datalen = 7; + } + data[1] = datalen - 2; + + sw = apdu_send_le (slot, 0, 0x00, CMD_SELECT_DATA, + occurrence, 0x04, datalen, data, 0, NULL, NULL); + return map_sw (sw); +} + /* Perform a GET DATA command requesting TAG and storing the result in a newly allocated buffer at the address passed by RESULT. Return diff --git a/scd/iso7816.h b/scd/iso7816.h index d9fd3eb69..3464798c0 100644 --- a/scd/iso7816.h +++ b/scd/iso7816.h @@ -98,6 +98,7 @@ gpg_error_t iso7816_reset_retry_counter (int slot, int chvno, gpg_error_t iso7816_reset_retry_counter_with_rc (int slot, int chvno, const char *data, size_t datalen); +gpg_error_t iso7816_select_data (int slot, int occurrence, int tag); gpg_error_t iso7816_get_data (int slot, int extended_mode, int tag, unsigned char **result, size_t *resultlen); gpg_error_t iso7816_get_data_odd (int slot, int extended_mode, unsigned int tag,