diff --git a/scd/app-piv.c b/scd/app-piv.c index cfc4a27b3..59f2725fe 100644 --- a/scd/app-piv.c +++ b/scd/app-piv.c @@ -1,2170 +1,2353 @@ /* app-piv.c - The OpenPGP card application. * Copyright (C) 2019 g10 Code GmbH * * 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 . */ /* Some notes: * - Specs for PIV are at http://dx.doi.org/10.6028/NIST.SP.800-73-4 * * - Access control matrix: * | Action | 9B | PIN | PUK | | * |--------------+-----+-----+-----+------------------------------| * | Generate key | yes | | | | * | Change 9B | yes | | | | * | Change retry | yes | yes | | Yubikey only | * | Import key | yes | | | | * | Import cert | yes | | | | * | Change CHUID | yes | | | | * | Reset card | | | | PIN and PUK in blocked state | * | Verify PIN | | yes | | | * | Sign data | | yes | | | * | Decrypt data | | yes | | | * | Change PIN | | yes | | | * | Change PUK | | | yes | | * | Unblock PIN | | | yes | New PIN required | * |---------------------------------------------------------------| * (9B indicates the 24 byte PIV Card Application Administration Key) * */ #include #include #include #include #include #include #include #include "scdaemon.h" #include "../common/util.h" #include "../common/i18n.h" #include "iso7816.h" #include "app-common.h" #include "../common/tlv.h" #include "../common/host2net.h" #include "apdu.h" /* We use apdu_send_direct. */ #define PIV_ALGORITHM_3DES_ECB_0 0x00 #define PIV_ALGORITHM_2DES_ECB 0x01 #define PIV_ALGORITHM_2DES_CBC 0x02 #define PIV_ALGORITHM_3DES_ECB 0x03 #define PIV_ALGORITHM_3DES_CBC 0x04 #define PIV_ALGORITHM_RSA 0x07 #define PIV_ALGORITHM_AES128_ECB 0x08 #define PIV_ALGORITHM_AES128_CBC 0x09 #define PIV_ALGORITHM_AES192_ECB 0x0A #define PIV_ALGORITHM_AES192_CBC 0x0B #define PIV_ALGORITHM_AES256_ECB 0x0C #define PIV_ALGORITHM_AES256_CBC 0x0D #define PIV_ALGORITHM_ECC_P256 0x11 #define PIV_ALGORITHM_ECC_P384 0x14 /* A table describing the DOs of a PIV card. */ struct data_object_s { unsigned int tag; unsigned int mandatory:1; unsigned int acr_contact:2; /* 0=always, 1=VCI, 2=PIN, 3=PINorOCC */ unsigned int acr_contactless:2; /* 0=always, 1=VCI, 2=VCIandPIN, 3=VCIand(PINorOCC) */ unsigned int binary:1; /* Data is not human readable. */ unsigned int dont_cache:1; /* Data item will not be cached. */ unsigned int flush_on_error:1; /* Flush cached item on error. */ unsigned int keypair:1; /* Has a public key for a keypair. */ char keyref[3]; /* The key reference. */ char *oidsuffix; /* Suffix of the OID, prefix is "2.16.840.1.101.3.7." */ char *desc; /* Description of the DO. */ }; typedef struct data_object_s *data_object_t; static struct data_object_s data_objects[] = { { 0x5FC107, 1, 0,1, 1, 0,0, 0, "", "1.219.0", "Card Capability Container"}, { 0x5FC102, 1, 0,0, 1, 0,0, 0, "", "2.48.0", "Cardholder Unique Id" }, { 0x5FC105, 1, 0,1, 1, 0,0, 1, "9A", "2.1.1", "Cert PIV Authentication" }, { 0x5FC103, 1, 2,2, 1, 0,0, 0, "", "2.96.16", "Cardholder Fingerprints" }, { 0x5FC106, 1, 0,1, 1, 0,0, 0, "", "2.144.0", "Security Object" }, { 0x5FC108, 1, 2,2, 1, 0,0, 0, "", "2.96.48", "Cardholder Facial Image" }, { 0x5FC101, 1, 0,0, 1, 0,0, 1, "9E", "2.5.0", "Cert Card Authentication"}, { 0x5FC10A, 0, 0,1, 1, 0,0, 1, "9C", "2.1.0", "Cert Digital Signature" }, { 0x5FC10B, 0, 0,1, 1, 0,0, 1, "9D", "2.1.2", "Cert Key Management" }, { 0x5FC109, 0, 3,3, 0, 0,0, 0, "", "2.48.1", "Printed Information" }, { 0x7E, 0, 0,0, 1, 0,0, 0, "", "2.96.80", "Discovery Object" }, { 0x5FC10C, 0, 0,1, 1, 0,0, 0, "", "2.96.96", "Key History Object" }, { 0x5FC10D, 0, 0,1, 1, 0,0, 0, "82", "2.16.1", "Retired Cert Key Mgm 1" }, { 0x5FC10E, 0, 0,1, 1, 0,0, 0, "83", "2.16.2", "Retired Cert Key Mgm 2" }, { 0x5FC10F, 0, 0,1, 1, 0,0, 0, "84", "2.16.3", "Retired Cert Key Mgm 3" }, { 0x5FC110, 0, 0,1, 1, 0,0, 0, "85", "2.16.4", "Retired Cert Key Mgm 4" }, { 0x5FC111, 0, 0,1, 1, 0,0, 0, "86", "2.16.5", "Retired Cert Key Mgm 5" }, { 0x5FC112, 0, 0,1, 1, 0,0, 0, "87", "2.16.6", "Retired Cert Key Mgm 6" }, { 0x5FC113, 0, 0,1, 1, 0,0, 0, "88", "2.16.7", "Retired Cert Key Mgm 7" }, { 0x5FC114, 0, 0,1, 1, 0,0, 0, "89", "2.16.8", "Retired Cert Key Mgm 8" }, { 0x5FC115, 0, 0,1, 1, 0,0, 0, "8A", "2.16.9", "Retired Cert Key Mgm 9" }, { 0x5FC116, 0, 0,1, 1, 0,0, 0, "8B", "2.16.10", "Retired Cert Key Mgm 10" }, { 0x5FC117, 0, 0,1, 1, 0,0, 0, "8C", "2.16.11", "Retired Cert Key Mgm 11" }, { 0x5FC118, 0, 0,1, 1, 0,0, 0, "8D", "2.16.12", "Retired Cert Key Mgm 12" }, { 0x5FC119, 0, 0,1, 1, 0,0, 0, "8E", "2.16.13", "Retired Cert Key Mgm 13" }, { 0x5FC11A, 0, 0,1, 1, 0,0, 0, "8F", "2.16.14", "Retired Cert Key Mgm 14" }, { 0x5FC11B, 0, 0,1, 1, 0,0, 0, "90", "2.16.15", "Retired Cert Key Mgm 15" }, { 0x5FC11C, 0, 0,1, 1, 0,0, 0, "91", "2.16.16", "Retired Cert Key Mgm 16" }, { 0x5FC11D, 0, 0,1, 1, 0,0, 0, "92", "2.16.17", "Retired Cert Key Mgm 17" }, { 0x5FC11E, 0, 0,1, 1, 0,0, 0, "93", "2.16.18", "Retired Cert Key Mgm 18" }, { 0x5FC11F, 0, 0,1, 1, 0,0, 0, "94", "2.16.19", "Retired Cert Key Mgm 19" }, { 0x5FC120, 0, 0,1, 1, 0,0, 0, "95", "2.16.20", "Retired Cert Key Mgm 20" }, { 0x5FC121, 0, 2,2, 1, 0,0, 0, "", "2.16.21", "Cardholder Iris Images" }, { 0x7F61, 0, 0,0, 1, 0,0, 0, "", "2.16.22", "BIT Group Template" }, { 0x5FC122, 0, 0,0, 1, 0,0, 0, "", "2.16.23", "SM Cert Signer" }, { 0x5FC123, 0, 3,3, 1, 0,0, 0, "", "2.16.24", "Pairing Code Ref Data" }, { 0 } /* Other key reference values without a data object: * "00" Global PIN (not cleared by application switching) * "04" PIV Secure Messaging Key * "80" PIV Application PIN * "81" PIN Unblocking Key * "96" Primary Finger OCC * "97" Secondary Finger OCC * "98" Pairing Code * "9B" PIV Card Application Administration Key */ }; /* One cache item for DOs. */ struct cache_s { struct cache_s *next; int tag; size_t length; unsigned char data[1]; }; /* A cache item used by genkey. */ struct genkey_result_s { struct genkey_result_s *next; int keyref; gcry_sexp_t s_pkey; }; /* Object with application specific data. */ struct app_local_s { /* A linked list with cached DOs. */ struct cache_s *cache; /* A list with results from recent genkey operations. */ struct genkey_result_s *genkey_results; /* Various flags. */ struct { unsigned int yubikey:1; /* This is on a Yubikey. */ } flags; }; /***** Local prototypes *****/ static gpg_error_t get_keygrip_by_tag (app_t app, unsigned int tag, char **r_keygripstr); /* Deconstructor. */ static void do_deinit (app_t app) { if (app && app->app_local) { struct cache_s *c, *c2; struct genkey_result_s *gr, *gr2; for (c = app->app_local->cache; c; c = c2) { c2 = c->next; xfree (c); } for (gr = app->app_local->genkey_results; gr; gr = gr2) { gr2 = gr->next; gcry_sexp_release (gr->s_pkey); xfree (gr); } xfree (app->app_local); app->app_local = NULL; } } /* Wrapper around iso7816_get_data which first tries to get the data * from the cache. With GET_IMMEDIATE passed as true, the cache is * bypassed. The tag-53 container is also removed. */ static gpg_error_t get_cached_data (app_t app, int tag, unsigned char **result, size_t *resultlen, int get_immediate) { gpg_error_t err; int i; unsigned char *p; const unsigned char *s; size_t len, n; struct cache_s *c; *result = NULL; *resultlen = 0; if (!get_immediate) { for (c=app->app_local->cache; c; c = c->next) if (c->tag == tag) { if(c->length) { p = xtrymalloc (c->length); if (!p) return gpg_error_from_syserror (); memcpy (p, c->data, c->length); *result = p; } *resultlen = c->length; return 0; } } err = iso7816_get_data_odd (app->slot, 0, tag, &p, &len); if (err) return err; /* Unless the Discovery Object or the BIT Group Template is * requested, remove the outer container. * (SP800-73.4 Part 2, section 3.1.2) */ if (tag == 0x7E || tag == 0x7F61) ; else if (len && *p == 0x53 && (s = find_tlv (p, len, 0x53, &n))) { memmove (p, s, n); len = n; } if (len) *result = p; *resultlen = len; /* Check whether we should cache this object. */ if (get_immediate) return 0; for (i=0; data_objects[i].tag; i++) if (data_objects[i].tag == tag) { if (data_objects[i].dont_cache) return 0; break; } /* Okay, cache it. */ for (c=app->app_local->cache; c; c = c->next) log_assert (c->tag != tag); c = xtrymalloc (sizeof *c + len); if (c) { if (len) memcpy (c->data, p, len); else xfree (p); c->length = len; c->tag = tag; c->next = app->app_local->cache; app->app_local->cache = c; } return 0; } /* Remove data object described by TAG from the cache. If TAG is 0 * all cache iterms are flushed. */ static void flush_cached_data (app_t app, int tag) { struct cache_s *c, *cprev; for (c=app->app_local->cache, cprev=NULL; c; cprev=c, c = c->next) if (c->tag == tag || !tag) { if (cprev) cprev->next = c->next; else app->app_local->cache = c->next; xfree (c); for (c=app->app_local->cache; c ; c = c->next) { log_assert (c->tag != tag); /* Oops: duplicated entry. */ } return; } } /* Get the DO identified by TAG from the card in SLOT and return a * buffer with its content in RESULT and NBYTES. The return value is * NULL if not found or a pointer which must be used to release the * buffer holding value. */ static void * get_one_do (app_t app, int tag, unsigned char **result, size_t *nbytes, int *r_err) { gpg_error_t err; int i; unsigned char *buffer; size_t buflen; unsigned char *value; size_t valuelen; gpg_error_t dummyerr; if (!r_err) r_err = &dummyerr; *result = NULL; *nbytes = 0; *r_err = 0; for (i=0; data_objects[i].tag && data_objects[i].tag != tag; i++) ; value = NULL; err = gpg_error (GPG_ERR_ENOENT); if (!value) /* Not in a constructed DO, try simple. */ { err = get_cached_data (app, tag, &buffer, &buflen, data_objects[i].dont_cache); if (!err) { value = buffer; valuelen = buflen; } } if (!err) { *nbytes = valuelen; *result = value; return buffer; } *r_err = err; return NULL; } static void dump_all_do (int slot) { gpg_error_t err; int i; unsigned char *buffer; size_t buflen; for (i=0; data_objects[i].tag; i++) { /* We don't try extended length APDU because such large DO would be pretty useless in a log file. */ err = iso7816_get_data_odd (slot, 0, data_objects[i].tag, &buffer, &buflen); if (err) { if (gpg_err_code (err) == GPG_ERR_ENOENT && !data_objects[i].mandatory) ; else log_info ("DO '%s' not available: %s\n", data_objects[i].desc, gpg_strerror (err)); } else { if (data_objects[i].binary) { log_info ("DO '%s': ", data_objects[i].desc); if (buflen > 16 && opt.verbose < 2) { log_printhex (buffer, 16, NULL); log_printf ("[...]\n"); } else log_printhex (buffer, buflen, ""); } else log_info ("DO '%s': '%.*s'\n", data_objects[i].desc, (int)buflen, buffer); } xfree (buffer); buffer = NULL; } } +/* Create a TLV tag and value and store it at BUFFER. Return the + * length of tag and length. A LENGTH greater than 65535 is + * truncated. TAG must be less or equal to 2^16. If BUFFER is NULL, + * only the required length is computed. */ +static size_t +add_tlv (unsigned char *buffer, unsigned int tag, size_t length) +{ + if (length > 0xffff) + length = 0xffff; + + if (buffer) + { + unsigned char *p = buffer; + + if (tag > 0xff) + *p++ = tag >> 8; + *p++ = tag; + if (length < 128) + *p++ = length; + else if (length < 256) + { + *p++ = 0x81; + *p++ = length; + } + else + { + *p++ = 0x82; + *p++ = length >> 8; + *p++ = length; + } + + return p - buffer; + } + else + { + size_t n = 0; + + if (tag > 0xff) + n++; + n++; + if (length < 128) + n++; + else if (length < 256) + n += 2; + else + n += 3; + return n; + } +} + + +/* Wrapper around iso7816_put_data_odd which also sets the tag into + * the '5C' data object. The varargs are tuples of (int,size_t,void) + * with the tag, the length and the actual data. A (0,0,NULL) tuple + * terminates the list. Up to 10 tuples are supported. */ +static gpg_error_t +put_data (int slot, unsigned int tag, ...) +{ + gpg_error_t err; + va_list arg_ptr; + struct { + int tag; + size_t len; + const void *data; + } argv[10]; + int i, argc; + unsigned char data5c[5]; + size_t data5clen; + unsigned char *data = NULL; + size_t datalen; + unsigned char *p; + size_t n; + + /* Collect all args. Check that length is <= 2^16 to match the + * behaviour of add_tlv. */ + va_start (arg_ptr, tag); + argc = 0; + while (((argv[argc].tag = va_arg (arg_ptr, int)))) + { + argv[argc].len = va_arg (arg_ptr, size_t); + argv[argc].data = va_arg (arg_ptr, const void *); + if (argc >= DIM (argv)-1 || argv[argc].len > 0xffff) + { + va_end (arg_ptr); + return GPG_ERR_EINVAL; + } + argc++; + } + va_end (arg_ptr); + + /* Build the TLV with the tag to be updated. */ + data5c[0] = 0x5c; /* Tag list */ + if (tag <= 0xff) + { + data5c[1] = 1; + data5c[2] = tag; + data5clen = 3; + } + else if (tag <= 0xffff) + { + data5c[1] = 2; + data5c[2] = (tag >> 8); + data5c[3] = tag; + data5clen = 4; + } + else + { + data5c[1] = 3; + data5c[2] = (tag >> 16); + data5c[3] = (tag >> 8); + data5c[4] = tag; + data5clen = 5; + } + + /* Compute the required buffer length and allocate the buffer. */ + n = 0; + for (i=0; i < argc; i++) + { + n += add_tlv (NULL, argv[i].tag, argv[i].len); + n += argv[i].len; + } + datalen = data5clen + add_tlv (NULL, 0x53, n) + n; + data = xtrymalloc (datalen); + if (!data) + { + err = gpg_error_from_syserror (); + goto leave; + } + + /* Copy that data to the buffer. */ + p = data; + memcpy (p, data5c, data5clen); + p += data5clen; + p += add_tlv (p, 0x53, n); + for (i=0; i < argc; i++) + { + p += add_tlv (p, argv[i].tag, argv[i].len); + memcpy (p, argv[i].data, argv[i].len); + p += argv[i].len; + } + log_assert ( data + datalen == p ); + log_printhex (data, datalen, "Put data"); + err = iso7816_put_data_odd (slot, -1 /* use command chaining */, + 0x3fff, data, datalen); + + leave: + xfree (data); + return err; +} + + /* Parse the key reference KEYREFSTR which is expected to hold a key * reference for a CHV object. Return the one octet keyref or -1 for * an invalid reference. */ static int parse_chv_keyref (const char *keyrefstr) { if (!keyrefstr) return -1; else if (!ascii_strcasecmp (keyrefstr, "PIV.00")) return 0x00; else if (!ascii_strcasecmp (keyrefstr, "PIV.80")) return 0x80; else if (!ascii_strcasecmp (keyrefstr, "PIV.81")) return 0x81; else return -1; } /* Return an allocated string with the serial number in a format to be * show to the user. With FAILMODE is true return NULL if such an * abbreviated S/N is not available, else return the full serial * number as a hex string. May return NULL on malloc problem. */ static char * get_dispserialno (app_t app, int failmode) { char *result; if (app->serialno && app->serialnolen == 3+1+4 && !memcmp (app->serialno, "\xff\x02\x00", 3)) { /* This is a 4 byte S/N of a Yubikey which seems to be printed * on the token in decimal. Maybe they will print larger S/N * also in decimal but we can't be sure, thus do it only for * these 32 bit numbers. */ unsigned long sn; sn = app->serialno[4] * 16777216; sn += app->serialno[5] * 65536; sn += app->serialno[6] * 256; sn += app->serialno[7]; result = xtryasprintf ("yk-%lu", sn); } else if (failmode) result = NULL; /* No Abbreviated S/N. */ else result = app_get_serialno (app); return result; } /* The verify command can be used to retrieve the security status of * the card. Given the PIN name (e.g. "PIV.80" for thge application * pin, a status is returned: * * -1 = Error retrieving the data, * -2 = No such PIN, * -3 = PIN blocked, * -5 = Verify still valid, * n >= 0 = Number of verification attempts left. */ static int get_chv_status (app_t app, const char *keyrefstr) { unsigned char apdu[4]; unsigned int sw; int result; int keyref; keyref = parse_chv_keyref (keyrefstr); if (!keyrefstr) return -1; apdu[0] = 0x00; apdu[1] = ISO7816_VERIFY; apdu[2] = 0x00; apdu[3] = keyref; if (!iso7816_apdu_direct (app->slot, apdu, 4, 0, &sw, NULL, NULL)) result = -5; /* No need to verification. */ else if (sw == 0x6a88 || sw == 0x6a80) result = -2; /* No such PIN. */ else if (sw == 0x6983) result = -3; /* PIN is blocked. */ else if ((sw & 0xfff0) == 0x63C0) result = (sw & 0x000f); else result = -1; /* Error. */ return result; } /* Implementation of the GETATTR command. This is similar to the * LEARN command but returns only one value via status lines. */ static gpg_error_t do_getattr (app_t app, ctrl_t ctrl, const char *name) { static struct { const char *name; int tag; int special; } table[] = { { "SERIALNO", 0x0000, -1 }, { "$AUTHKEYID", 0x0000, -2 }, /* Default key for ssh. */ { "$DISPSERIALNO",0x0000, -3 }, { "CHV-STATUS", 0x0000, -4 }, { "CHV-USAGE", 0x007E, -5 } }; gpg_error_t err = 0; int idx; void *relptr; unsigned char *value; size_t valuelen; const unsigned char *s; size_t n; for (idx=0; (idx < DIM (table) && ascii_strcasecmp (table[idx].name, name)); idx++) ; if (!(idx < DIM (table))) err = gpg_error (GPG_ERR_INV_NAME); else if (table[idx].special == -1) { char *serial = app_get_serialno (app); if (serial) { send_status_direct (ctrl, "SERIALNO", serial); xfree (serial); } } else if (table[idx].special == -2) { char const tmp[] = "PIV.9A"; /* Cert PIV Authenticate. */ send_status_info (ctrl, table[idx].name, tmp, strlen (tmp), NULL, 0); } else if (table[idx].special == -3) { char *tmp = get_dispserialno (app, 1); if (tmp) { send_status_info (ctrl, table[idx].name, tmp, strlen (tmp), NULL, (size_t)0); xfree (tmp); } else err = gpg_error (GPG_ERR_INV_NAME); /* No Abbreviated S/N. */ } else if (table[idx].special == -4) /* CHV-STATUS */ { int tmp[4]; tmp[0] = get_chv_status (app, "PIV.00"); tmp[1] = get_chv_status (app, "PIV.80"); tmp[2] = get_chv_status (app, "PIV.81"); err = send_status_printf (ctrl, table[idx].name, "%d %d %d", tmp[0], tmp[1], tmp[2]); } else if (table[idx].special == -5) /* CHV-USAGE (aka PIN Usage Policy) */ { /* We return 2 hex bytes or nothing in case the discovery object * is not supported. */ relptr = get_one_do (app, table[idx].tag, &value, &valuelen, &err); if (relptr) { s = find_tlv (value, valuelen, 0x7E, &n); if (s && n && (s = find_tlv (s, n, 0x5F2F, &n)) && n >=2 ) err = send_status_printf (ctrl, table[idx].name, "%02X %02X", s[0], s[1]); xfree (relptr); } } else { relptr = get_one_do (app, table[idx].tag, &value, &valuelen, &err); if (relptr) { send_status_info (ctrl, table[idx].name, value, valuelen, NULL, 0); xfree (relptr); } } return err; } /* Authenticate the card using the Card Application Administration * Key. (VALUE,VALUELEN) has that 24 byte key. */ static gpg_error_t auth_adm_key (app_t app, const unsigned char *value, size_t valuelen) { gpg_error_t err; unsigned char tmpl[4+24]; size_t tmpllen; unsigned char *outdata = NULL; size_t outdatalen; const unsigned char *s; char witness[8]; size_t n; gcry_cipher_hd_t cipher = NULL; /* Prepare decryption. */ err = gcry_cipher_open (&cipher, GCRY_CIPHER_3DES, GCRY_CIPHER_MODE_ECB, 0); if (err) goto leave; err = gcry_cipher_setkey (cipher, value, valuelen); if (err) goto leave; /* Request a witness. */ tmpl[0] = 0x7c; tmpl[1] = 0x02; tmpl[2] = 0x80; tmpl[3] = 0; /* (Empty witness requests a witness.) */ tmpllen = 4; err = iso7816_general_authenticate (app->slot, 0, PIV_ALGORITHM_3DES_ECB_0, 0x9B, tmpl, tmpllen, 0, &outdata, &outdatalen); if (err) goto leave; if (!(outdatalen && *outdata == 0x7c && (s = find_tlv (outdata, outdatalen, 0x80, &n)) && n == 8)) { err = gpg_error (GPG_ERR_CARD); log_error ("piv: improper witness received\n"); goto leave; } err = gcry_cipher_decrypt (cipher, witness, 8, s, 8); if (err) goto leave; /* Return decrypted witness and send our challenge. */ tmpl[0] = 0x7c; tmpl[1] = 22; tmpl[2] = 0x80; tmpl[3] = 8; memcpy (tmpl+4, witness, 8); tmpl[12] = 0x81; tmpl[13] = 8; gcry_create_nonce (tmpl+14, 8); tmpl[22] = 0x80; tmpl[23] = 0; tmpllen = 24; xfree (outdata); err = iso7816_general_authenticate (app->slot, 0, PIV_ALGORITHM_3DES_ECB_0, 0x9B, tmpl, tmpllen, 0, &outdata, &outdatalen); if (err) goto leave; if (!(outdatalen && *outdata == 0x7c && (s = find_tlv (outdata, outdatalen, 0x82, &n)) && n == 8)) { err = gpg_error (GPG_ERR_CARD); log_error ("piv: improper challenge received\n"); goto leave; } /* (We reuse the witness buffer.) */ err = gcry_cipher_decrypt (cipher, witness, 8, s, 8); if (err) goto leave; if (memcmp (witness, tmpl+14, 8)) { err = gpg_error (GPG_ERR_BAD_SIGNATURE); goto leave; } leave: xfree (outdata); gcry_cipher_close (cipher); return err; } /* Set a new admin key. */ static gpg_error_t set_adm_key (app_t app, const unsigned char *value, size_t valuelen) { gpg_error_t err; unsigned char apdu[8+24]; unsigned int sw; /* Check whether it is a weak key and that it is of proper length. */ { gcry_cipher_hd_t cipher; err = gcry_cipher_open (&cipher, GCRY_CIPHER_3DES, GCRY_CIPHER_MODE_ECB, 0); if (!err) { err = gcry_cipher_setkey (cipher, value, valuelen); gcry_cipher_close (cipher); } if (err) goto leave; } if (app->app_local->flags.yubikey) { /* This is a Yubikey. */ if (valuelen != 24) { err = gpg_error (GPG_ERR_INV_LENGTH); goto leave; } /* We use a proprietary Yubikey command. */ apdu[0] = 0; apdu[1] = 0xff; apdu[2] = 0xff; apdu[3] = 0xff; /* touch policy: 0xff=never, 0xfe = always. */ apdu[4] = 3 + 24; apdu[5] = PIV_ALGORITHM_3DES_ECB; apdu[6] = 0x9b; apdu[7] = 24; memcpy (apdu+8, value, 24); err = iso7816_apdu_direct (app->slot, apdu, 8+24, 0, &sw, NULL, NULL); wipememory (apdu+8, 24); if (err) log_error ("piv: setting admin key failed; sw=%04x\n", sw); } else err = gpg_error (GPG_ERR_NOT_SUPPORTED); leave: return err; } /* Handle the SETATTR operation. All arguments are already basically * checked. */ static gpg_error_t do_setattr (app_t app, 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; static struct { const char *name; unsigned short tag; unsigned short flush_tag; /* The tag which needs to be flushed or 0. */ int special; /* Special mode to use for thus NAME. */ } table[] = { /* Authenticate using the PIV Card Application Administration Key * (0x0B). Note that Yubico calls this key the "management key" * which we don't do because that term is too similar to "Cert * Management Key" (0x9D). */ { "AUTH-ADM-KEY", 0x0000, 0x0000, 1 }, { "SET-ADM-KEY", 0x0000, 0x0000, 2 } }; int idx; (void)pincb; (void)pincb_arg; for (idx=0; (idx < DIM (table) && ascii_strcasecmp (table[idx].name, name)); idx++) ; if (!(idx < DIM (table))) return gpg_error (GPG_ERR_INV_NAME); /* Flush the cache before writing it, so that the next get operation * will reread the data from the card and thus get synced in case of * errors (e.g. data truncated by the card). */ if (table[idx].tag) flush_cached_data (app, table[idx].flush_tag? table[idx].flush_tag /* */ : table[idx].tag); switch (table[idx].special) { - case 0: - err = iso7816_put_data (app->slot, 0, table[idx].tag, value, valuelen); - if (err) - log_error ("failed to set '%s': %s\n", - table[idx].name, gpg_strerror (err)); - break; - case 1: err = auth_adm_key (app, value, valuelen); break; case 2: err = set_adm_key (app, value, valuelen); break; default: err = gpg_error (GPG_ERR_BUG); break; } return err; } /* Send the KEYPAIRINFO back. DOBJ describes the data object carrying * the key. This is used by the LEARN command. */ static gpg_error_t send_keypair_and_cert_info (app_t app, ctrl_t ctrl, data_object_t dobj, int only_keypair) { gpg_error_t err = 0; char *keygripstr = NULL; char idbuf[50]; err = get_keygrip_by_tag (app, dobj->tag, &keygripstr); if (err) goto leave; snprintf (idbuf, sizeof idbuf, "PIV.%s", dobj->keyref); send_status_info (ctrl, "KEYPAIRINFO", keygripstr, strlen (keygripstr), idbuf, strlen (idbuf), NULL, (size_t)0); if (!only_keypair) { /* All certificates are of type 100 (Regular X.509 Cert). */ send_status_info (ctrl, "CERTINFO", "100", 3, idbuf, strlen (idbuf), NULL, (size_t)0); } leave: xfree (keygripstr); return err; } /* Handle the LEARN command. */ static gpg_error_t do_learn_status (app_t app, ctrl_t ctrl, unsigned int flags) { int i; (void)flags; do_getattr (app, ctrl, "CHV-USAGE"); do_getattr (app, ctrl, "CHV-STATUS"); for (i=0; data_objects[i].tag; i++) if (data_objects[i].keypair) send_keypair_and_cert_info (app, ctrl, data_objects + i, !!(flags & 1)); return 0; } /* Core of do_readcert which fetches the certificate based on the * given tag and returns it in a freshly allocated buffer stored at * R_CERT and the length of the certificate stored at R_CERTLEN. */ static gpg_error_t readcert_by_tag (app_t app, unsigned int tag, unsigned char **r_cert, size_t *r_certlen) { gpg_error_t err; unsigned char *buffer; size_t buflen; void *relptr; const unsigned char *s; size_t n; *r_cert = NULL; *r_certlen = 0; relptr = get_one_do (app, tag, &buffer, &buflen, NULL); if (!relptr || !buflen) { err = gpg_error (GPG_ERR_NOT_FOUND); goto leave; } s = find_tlv (buffer, buflen, 0x71, &n); if (!s || n != 1) { log_error ("piv: no or invalid CertInfo in 0x%X\n", tag); err = gpg_error (GPG_ERR_INV_CERT_OBJ); goto leave; } if (*s == 0x01) { log_error ("piv: gzip compression not yet supported (tag 0x%X)\n", tag); err = gpg_error (GPG_ERR_UNSUPPORTED_ENCODING); goto leave; } if (*s) { log_error ("piv: invalid CertInfo 0x%02x in 0x%X\n", *s, tag); err = gpg_error (GPG_ERR_INV_CERT_OBJ); goto leave; } /* Note: We don't check that the LRC octet has a length of zero as * required by the specs. */ /* Get the cert from the container. */ s = find_tlv (buffer, buflen, 0x70, &n); if (!s || !n) { err = gpg_error (GPG_ERR_NOT_FOUND); goto leave; } if (!(*r_cert = xtrymalloc (n))) { err = gpg_error_from_syserror (); goto leave; } memcpy (*r_cert, s, n); *r_certlen = n; err = 0; leave: xfree (relptr); return err; } /* Get the keygrip of a key from the certificate stored at TAG. * Caller must free the string at R_KEYGRIPSTR. */ static gpg_error_t get_keygrip_by_tag (app_t app, unsigned int tag, char **r_keygripstr) { gpg_error_t err; unsigned char *certbuf = NULL; size_t certbuflen; ksba_cert_t cert = NULL; *r_keygripstr = xtrymalloc (40+1); if (!r_keygripstr) { err = gpg_error_from_syserror (); goto leave; } /* We need to get the public key from the certificate. */ err = readcert_by_tag (app, tag, &certbuf, &certbuflen); if (err) goto leave; /* Compute the keygrip. */ err = ksba_cert_new (&cert); if (err) goto leave; err = ksba_cert_init_from_mem (cert, certbuf, certbuflen); if (err) goto leave; err = app_help_get_keygrip_string (cert, *r_keygripstr); leave: ksba_cert_release (cert); xfree (certbuf); if (err) { xfree (*r_keygripstr); *r_keygripstr = NULL; } return err; } /* Locate the data object from the given KEYREF. The KEYREF may also * be the corresponding OID of the key object. Returns the data * object or NULL if not found. */ static data_object_t find_dobj_by_keyref (app_t app, const char *keyref) { int i; (void)app; if (!ascii_strncasecmp (keyref, "PIV.", 4)) { keyref += 4; for (i=0; data_objects[i].tag; i++) if (*data_objects[i].keyref && !ascii_strcasecmp (keyref, data_objects[i].keyref)) { return data_objects + i; } } else if (!strncmp (keyref, "2.16.840.1.101.3.7.", 19)) { keyref += 19; for (i=0; data_objects[i].tag; i++) if (*data_objects[i].keyref && !strcmp (keyref, data_objects[i].oidsuffix)) { return data_objects + i; } } return NULL; } /* Return the keyref from DOBJ as an integer. If it does not exist, * return -1. */ static int keyref_from_dobj (data_object_t dobj) { if (!dobj || !hexdigitp (dobj->keyref) || !hexdigitp (dobj->keyref+1)) return -1; return xtoi_2 (dobj->keyref); } /* Read a certificate from the card and returned in a freshly * allocated buffer stored at R_CERT and the length of the certificate * stored at R_CERTLEN. CERTID is either the OID of the cert's * container or of the form "PIV." */ static gpg_error_t do_readcert (app_t app, const char *certid, unsigned char **r_cert, size_t *r_certlen) { data_object_t dobj; *r_cert = NULL; *r_certlen = 0; dobj = find_dobj_by_keyref (app, certid); if (!dobj) return gpg_error (GPG_ERR_INV_ID); return readcert_by_tag (app, dobj->tag, r_cert, r_certlen); } /* Return a public key in a freshly allocated buffer. This will only * work for a freshly generated key as long as no reset of the * application has been performed. This is because we return a cached * result from key generation. If no cached result is available, the * error GPG_ERR_UNSUPPORTED_OPERATION is returned so that the higher * layer can then to get the key by reading the matching certificate. * On success a canonical encoded S-expression with the public key is * stored at (R_PK,R_PKLEN); the caller must release that buffer. On * error R_PK and R_PKLEN are not changed and an error code is * returned. */ static gpg_error_t do_readkey (app_t app, int advanced, const char *keyrefstr, unsigned char **r_pk, size_t *r_pklen) { gpg_error_t err; data_object_t dobj; int keyref; struct genkey_result_s *gres; unsigned char *pk = NULL; size_t pklen; dobj = find_dobj_by_keyref (app, keyrefstr); if ((keyref = keyref_from_dobj (dobj)) == -1) { err = gpg_error (GPG_ERR_INV_ID); goto leave; } for (gres = app->app_local->genkey_results; gres; gres = gres->next) if (gres->keyref == keyref) break; if (!gres || !gres->s_pkey) { err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); goto leave; } err = make_canon_sexp (gres->s_pkey, &pk, &pklen); if (err) goto leave; if (advanced) { /* FIXME: How ugly - we should move that to command.c */ char *p = canon_sexp_to_string (pk, pklen); if (!p) { err = gpg_error_from_syserror (); goto leave; } xfree (pk); pk = p; pklen = strlen (pk); } *r_pk = pk; pk = NULL; *r_pklen = pklen; leave: xfree (pk); return err; } /* Given a data object DOBJ return the corresponding PIV algorithm and * store it at R_ALGO. The algorithm is taken from the corresponding * certificate or from a cache. */ static gpg_error_t get_key_algorithm_by_dobj (app_t app, data_object_t dobj, int *r_algo) { gpg_error_t err; unsigned char *certbuf = NULL; size_t certbuflen; ksba_cert_t cert = NULL; ksba_sexp_t k_pkey = NULL; gcry_sexp_t s_pkey = NULL; gcry_sexp_t l1 = NULL; char *algoname = NULL; int algo; size_t n; const char *curve_name; *r_algo = 0; err = readcert_by_tag (app, dobj->tag, &certbuf, &certbuflen); if (err) goto leave; err = ksba_cert_new (&cert); if (err) goto leave; err = ksba_cert_init_from_mem (cert, certbuf, certbuflen); if (err) { log_error ("piv: failed to parse the certificate %s: %s\n", dobj->keyref, gpg_strerror (err)); goto leave; } xfree (certbuf); certbuf = NULL; k_pkey = ksba_cert_get_public_key (cert); if (!k_pkey) { err = gpg_error (GPG_ERR_NO_PUBKEY); goto leave; } n = gcry_sexp_canon_len (k_pkey, 0, NULL, NULL); err = gcry_sexp_new (&s_pkey, k_pkey, n, 0); if (err) goto leave; l1 = gcry_sexp_find_token (s_pkey, "public-key", 0); if (!l1) { err = gpg_error (GPG_ERR_NO_PUBKEY); goto leave; } { gcry_sexp_t l_tmp = gcry_sexp_cadr (l1); gcry_sexp_release (l1); l1 = l_tmp; } algoname = gcry_sexp_nth_string (l1, 0); if (!algoname) { err = gpg_error_from_syserror (); goto leave; } algo = gcry_pk_map_name (algoname); switch (algo) { case GCRY_PK_RSA: algo = PIV_ALGORITHM_RSA; break; case GCRY_PK_ECC: case GCRY_PK_ECDSA: case GCRY_PK_ECDH: curve_name = gcry_pk_get_curve (s_pkey, 0, NULL); if (curve_name && !strcmp (curve_name, "NIST P-256")) algo = PIV_ALGORITHM_ECC_P256; else if (curve_name && !strcmp (curve_name, "NIST P-384")) algo = PIV_ALGORITHM_ECC_P384; else { err = gpg_error (GPG_ERR_UNKNOWN_CURVE); log_error ("piv: certificate %s, curve '%s': %s\n", dobj->keyref, curve_name, gpg_strerror (err)); goto leave; } break; default: err = gpg_error (GPG_ERR_PUBKEY_ALGO); log_error ("piv: certificate %s, pubkey algo '%s': %s\n", dobj->keyref, algoname, gpg_strerror (err)); goto leave; } *r_algo = algo; leave: gcry_free (algoname); gcry_sexp_release (l1); gcry_sexp_release (s_pkey); ksba_free (k_pkey); xfree (certbuf); return err; } /* Return an allocated string to be used as prompt. Returns NULL on * malloc error. */ static char * make_prompt (app_t app, int remaining, const char *firstline) { char *serial, *tmpbuf, *result; serial = get_dispserialno (app, 0); if (!serial) return NULL; /* TRANSLATORS: Put a \x1f right before a colon. This can be * used by pinentry to nicely align the names and values. Keep * the %s at the start and end of the string. */ result = xtryasprintf (_("%s" "Number\x1f: %s%%0A" "Holder\x1f: %s" "%s"), "\x1e", serial, "Unknown", /* Fixme */ ""); xfree (serial); /* Append a "remaining attempts" info if needed. */ if (remaining != -1 && remaining < 3) { char *rembuf; /* TRANSLATORS: This is the number of remaining attempts to * enter a PIN. Use %%0A (double-percent,0A) for a linefeed. */ rembuf = xtryasprintf (_("Remaining attempts: %d"), remaining); if (rembuf) { tmpbuf = strconcat (firstline, "%0A%0A", result, "%0A%0A", rembuf, NULL); xfree (rembuf); } else tmpbuf = NULL; xfree (result); result = tmpbuf; } else { tmpbuf = strconcat (firstline, "%0A%0A", result, NULL); xfree (result); result = tmpbuf; } return result; } /* Helper for verify_chv to ask for the PIN and to prepare/pad it. On * success the result is stored at (R_PIN,R_PINLEN). */ static gpg_error_t ask_and_prepare_chv (app_t app, int keyref, int ask_new, int remaining, gpg_error_t (*pincb)(void*,const char *,char **), void *pincb_arg, char **r_pin, unsigned int *r_pinlen) { gpg_error_t err; const char *label; char *prompt; char *pinvalue = NULL; unsigned int pinlen; char *pinbuffer = NULL; int minlen, maxlen, padding, onlydigits; *r_pin = NULL; *r_pinlen = 0; if (ask_new) remaining = -1; if (remaining != -1) log_debug ("piv: CHV %02X has %d attempts left\n", keyref, remaining); switch (keyref) { case 0x00: minlen = 6; maxlen = 8; padding = 1; onlydigits = 1; label = (ask_new? _("|N|Please enter the new Global-PIN") /**/ : _("||Please enter the Global-PIN of your PIV card")); break; case 0x80: minlen = 6; maxlen = 8; padding = 1; onlydigits = 1; label = (ask_new? _("|N|Please enter the new PIN") /**/ : _("||Please enter the PIN of your PIV card")); break; case 0x81: minlen = 8; maxlen = 8; padding = 0; onlydigits = 0; label = (ask_new? _("|N|Please enter the new Unblocking Key") /**/ :_("||Please enter the Unblocking Key of your PIV card")); break; case 0x96: case 0x97: case 0x98: case 0x9B: return gpg_error (GPG_ERR_NOT_IMPLEMENTED); default: return gpg_error (GPG_ERR_INV_ID); } /* Ask for the PIN. */ prompt = make_prompt (app, remaining, label); err = pincb (pincb_arg, prompt, &pinvalue); xfree (prompt); prompt = NULL; if (err) { log_info (_("PIN callback returned error: %s\n"), gpg_strerror (err)); return err; } pinlen = pinvalue? strlen (pinvalue) : 0; if (pinlen < minlen) { log_error (_("PIN for is too short; minimum length is %d\n"), minlen); if (pinvalue) wipememory (pinvalue, pinlen); xfree (pinvalue); return gpg_error (GPG_ERR_BAD_PIN); } if (pinlen > maxlen) { log_error (_("PIN for is too long; maximum length is %d\n"), maxlen); wipememory (pinvalue, pinlen); xfree (pinvalue); return gpg_error (GPG_ERR_BAD_PIN); } if (onlydigits && strspn (pinvalue, "0123456789") != pinlen) { log_error (_("PIN has invalid characters; only digits are allowed\n")); wipememory (pinvalue, pinlen); xfree (pinvalue); return gpg_error (GPG_ERR_BAD_PIN); } pinbuffer = xtrymalloc_secure (maxlen); if (!pinbuffer) { err = gpg_error_from_syserror (); wipememory (pinvalue, pinlen); xfree (pinvalue); return err; } memcpy (pinbuffer, pinvalue, pinlen); wipememory (pinvalue, pinlen); xfree (pinvalue); if (padding) { memset (pinbuffer + pinlen, 0xff, maxlen - pinlen); pinlen = maxlen; } *r_pin = pinbuffer; *r_pinlen = pinlen; return 0; } /* Verify the card holder verification identified by KEYREF. This is * either the Appication PIN or the Global PIN. */ static gpg_error_t verify_chv (app_t app, int keyref, gpg_error_t (*pincb)(void*,const char *,char **), void *pincb_arg) { gpg_error_t err; unsigned char apdu[4]; unsigned int sw; int remaining; char *pin = NULL; unsigned int pinlen; /* First check whether a verify is at all needed. This is done with * P1 being 0 and no Lc and command data send. */ apdu[0] = 0x00; apdu[1] = ISO7816_VERIFY; apdu[2] = 0x00; apdu[3] = keyref; if (!iso7816_apdu_direct (app->slot, apdu, 4, 0, &sw, NULL, NULL)) { /* No need to verification. */ return 0; /* All fine. */ } if ((sw & 0xfff0) == 0x63C0) remaining = (sw & 0x000f); /* PIN has REMAINING tries left. */ else remaining = -1; err = ask_and_prepare_chv (app, keyref, 0, remaining, pincb, pincb_arg, &pin, &pinlen); if (err) return err; err = iso7816_verify (app->slot, keyref, pin, pinlen); wipememory (pin, pinlen); xfree (pin); if (err) log_error ("CHV %02X verification failed: %s\n", keyref, gpg_strerror (err)); return err; } /* Handle the PASSWD command. Valid values for PWIDSTR are * key references related to PINs; in particular: * PIV.00 - The Global PIN * PIV.80 - The Application PIN * PIV.81 - The PIN Unblocking key * The supported flags are: * APP_CHANGE_FLAG_CLEAR Clear the PIN verification state. * APP_CHANGE_FLAG_RESET Reset a PIN using the PUK. Only * allowed with PIV.80. */ static gpg_error_t do_change_chv (app_t app, ctrl_t ctrl, const char *pwidstr, unsigned int flags, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg) { gpg_error_t err; int keyref, targetkeyref; unsigned char apdu[4]; unsigned int sw; int remaining; char *oldpin = NULL; unsigned int oldpinlen; char *newpin = NULL; unsigned int newpinlen; (void)ctrl; /* Check for unknown flags. */ if ((flags & ~(APP_CHANGE_FLAG_CLEAR|APP_CHANGE_FLAG_RESET))) { err = gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); goto leave; } /* Parse the keyref. */ targetkeyref = keyref = parse_chv_keyref (pwidstr); if (keyref == -1) { err = gpg_error (GPG_ERR_INV_ID); goto leave; } /* First see whether the special --clear mode has been requested. */ if ((flags & APP_CHANGE_FLAG_CLEAR)) { apdu[0] = 0x00; apdu[1] = ISO7816_VERIFY; apdu[2] = 0xff; apdu[3] = keyref; err = iso7816_apdu_direct (app->slot, apdu, 4, 0, NULL, NULL, NULL); goto leave; } /* Prepare reset mode. */ if ((flags & APP_CHANGE_FLAG_RESET)) { if (keyref == 0x81) { err = gpg_error (GPG_ERR_INV_ID); /* Can't reset the PUK. */ goto leave; } /* Set the keyref to the PUK and keep the TARGETKEYREF. */ keyref = 0x81; } /* Get the remaining tries count. This is done by using the check * for verified state feature. */ apdu[0] = 0x00; apdu[1] = ISO7816_VERIFY; apdu[2] = 0x00; apdu[3] = keyref; if (!iso7816_apdu_direct (app->slot, apdu, 4, 0, &sw, NULL, NULL)) remaining = -1; /* Already verified, thus full number of tries. */ else if ((sw & 0xfff0) == 0x63C0) remaining = (sw & 0x000f); /* PIN has REMAINING tries left. */ else remaining = -1; /* Ask for the old pin or puk. */ err = ask_and_prepare_chv (app, keyref, 0, remaining, pincb, pincb_arg, &oldpin, &oldpinlen); if (err) return err; /* Verify the old pin so that we don't prompt for the new pin if the * old is wrong. This is not possible for the PUK, though. */ if (keyref != 0x81) { err = iso7816_verify (app->slot, keyref, oldpin, oldpinlen); if (err) { log_error ("CHV %02X verification failed: %s\n", keyref, gpg_strerror (err)); goto leave; } } /* Ask for the new pin. */ err = ask_and_prepare_chv (app, targetkeyref, 1, -1, pincb, pincb_arg, &newpin, &newpinlen); if (err) return err; if ((flags & APP_CHANGE_FLAG_RESET)) { char *buf = xtrymalloc_secure (oldpinlen + newpinlen); if (!buf) { err = gpg_error_from_syserror (); goto leave; } memcpy (buf, oldpin, oldpinlen); memcpy (buf+oldpinlen, newpin, newpinlen); err = iso7816_reset_retry_counter_with_rc (app->slot, targetkeyref, buf, oldpinlen+newpinlen); xfree (buf); if (err) log_error ("resetting CHV %02X using CHV %02X failed: %s\n", targetkeyref, keyref, gpg_strerror (err)); } else { err = iso7816_change_reference_data (app->slot, keyref, oldpin, oldpinlen, newpin, newpinlen); if (err) log_error ("CHV %02X changing PIN failed: %s\n", keyref, gpg_strerror (err)); } leave: xfree (oldpin); xfree (newpin); return err; } /* Perform a simple verify operation for the PIN specified by PWIDSTR. * For valid values see do_change_chv. */ static gpg_error_t do_check_chv (app_t app, const char *pwidstr, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg) { int keyref; keyref = parse_chv_keyref (pwidstr); if (keyref == -1) return gpg_error (GPG_ERR_INV_ID); return verify_chv (app, keyref, pincb, pincb_arg); } /* Compute a digital signature using the GENERAL AUTHENTICATE command * on INDATA which is expected to be the raw message digest. The * KEYIDSTR has the key reference or its OID (e.g. "PIV.9A"). The * result is stored at (R_OUTDATA,R_OUTDATALEN); on error (NULL,0) is * stored there and an error code returned. For ECDSA the result is * the simple concatenation of R and S without any DER encoding. R * and S are left extended with zeroes to make sure they have an equal * length. */ static gpg_error_t do_auth (app_t app, const char *keyidstr, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg, const void *indata_arg, size_t indatalen, unsigned char **r_outdata, size_t *r_outdatalen) { const unsigned char *indata = indata_arg; gpg_error_t err; data_object_t dobj; unsigned char tmpl[2+2+2+128]; size_t tmpllen; unsigned char *outdata = NULL; size_t outdatalen; const unsigned char *s; size_t n; int keyref, algo; if (!keyidstr || !*keyidstr) { err = gpg_error (GPG_ERR_INV_VALUE); goto leave; } /* Fixme: Shall we support the KEYID/FINGERPRINT syntax? Does it * make sense for X.509 certs? */ dobj = find_dobj_by_keyref (app, keyidstr); if ((keyref = keyref_from_dobj (dobj)) == -1) { err = gpg_error (GPG_ERR_INV_ID); goto leave; } err = get_key_algorithm_by_dobj (app, dobj, &algo); if (err) goto leave; /* We need to remove the ASN.1 prefix from INDATA. We use TEMPL as * a temporary buffer for the OID. */ if (algo == PIV_ALGORITHM_ECC_P256) { tmpllen = sizeof tmpl; err = gcry_md_get_asnoid (GCRY_MD_SHA256, &tmpl, &tmpllen); if (err) { err = gpg_error (GPG_ERR_INTERNAL); log_debug ("piv: no OID for hash algo %d\n", GCRY_MD_SHA256); goto leave; } if (indatalen != tmpllen + 32 || memcmp (indata, tmpl, tmpllen)) { err = GPG_ERR_INV_VALUE; log_error ("piv: bad formatted input for ECC-P256 auth\n"); goto leave; } indata +=tmpllen; indatalen -= tmpllen; } else if (algo == PIV_ALGORITHM_ECC_P384) { tmpllen = sizeof tmpl; err = gcry_md_get_asnoid (GCRY_MD_SHA384, &tmpl, &tmpllen); if (err) { err = gpg_error (GPG_ERR_INTERNAL); log_debug ("piv: no OID for hash algo %d\n", GCRY_MD_SHA384); goto leave; } if (indatalen != tmpllen + 48 || memcmp (indata, tmpl, tmpllen)) { err = GPG_ERR_INV_VALUE; log_error ("piv: bad formatted input for ECC-P384 auth\n"); goto leave; } indata += tmpllen; indatalen -= tmpllen; } else if (algo == PIV_ALGORITHM_RSA) { err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); log_error ("piv: FIXME: implement RSA authentication\n"); goto leave; } else { err = gpg_error (GPG_ERR_INTERNAL); log_debug ("piv: unknown PIV algo %d from helper function\n", algo); goto leave; } /* Because we don't have a dynamic template builder we make sure * that we can encode all lengths in one octet. FIXME: Use add_tls * from app-openpgp as a base for an strconcat like function. */ if (indatalen >= 100) { err = gpg_error (GPG_ERR_TOO_LARGE); goto leave; } /* Now verify the Application PIN. */ err = verify_chv (app, 0x80, pincb, pincb_arg); if (err) return err; /* Build the Dynamic Authentication Template. */ tmpl[0] = 0x7c; tmpl[1] = indatalen + 4; tmpl[2] = 0x82; /* Response. */ tmpl[3] = 0; /* Must be 0 to get the tag in the answer. */ tmpl[4] = 0x81; /* Challenge. */ tmpl[5] = indatalen; memcpy (tmpl+6, indata, indatalen); tmpllen = indatalen + 6; /* Note: the -1 requests command chaining. */ err = iso7816_general_authenticate (app->slot, -1, algo, keyref, tmpl, (int)tmpllen, 0, &outdata, &outdatalen); if (err) goto leave; /* Parse the response. */ if (outdatalen && *outdata == 0x7c && (s = find_tlv (outdata, outdatalen, 0x82, &n))) { const unsigned char *rval, *sval; size_t rlen, rlenx, slen, slenx, resultlen; char *result; /* The result of an ECDSA signature is * SEQUENCE { r INTEGER, s INTEGER } * We re-pack that by concatenating R and S and making sure that * both have the same length. We simplify parsing by using * find_tlv and not a proper DER parser. */ s = find_tlv (s, n, 0x30, &n); if (!s) goto bad_der; rval = find_tlv (s, n, 0x02, &rlen); if (!rval) goto bad_der; log_assert (n >= (rval-s)+rlen); sval = find_tlv (rval+rlen, n-((rval-s)+rlen), 0x02, &slen); if (!rval) goto bad_der; rlenx = slenx = 0; if (rlen > slen) slenx = rlen - slen; else if (slen > rlen) rlenx = slen - rlen; resultlen = rlen + rlenx + slen + slenx; result = xtrycalloc (1, resultlen); if (!result) { err = gpg_error_from_syserror (); goto leave; } memcpy (result + rlenx, rval, rlen); memcpy (result + rlenx + rlen + slenx, sval, slen); xfree (outdata); outdata = result; outdatalen = resultlen; } else { bad_der: err = gpg_error (GPG_ERR_CARD); log_error ("piv: response does not contain a proper result\n"); goto leave; } leave: if (err) { xfree (outdata); *r_outdata = NULL; *r_outdatalen = 0; } else { *r_outdata = outdata; *r_outdatalen = outdatalen; } return err; } /* Check whether a key for DOBJ already exists. We detect this by * reading the certificate described by DOBJ. If FORCE is TRUE a * diagnositic will be printed but no error returned if the key * already exists. The flag GENERATING is used to select a * diagnositic. */ static gpg_error_t does_key_exist (app_t app, data_object_t dobj, int generating, int force) { void *relptr; unsigned char *buffer; size_t buflen; int found; relptr = get_one_do (app, dobj->tag, &buffer, &buflen, NULL); found = (relptr && buflen); xfree (relptr); if (found && !force) { log_error ("piv: %s", _("key already exists\n")); return gpg_error (GPG_ERR_EEXIST); } if (found) log_info ("piv: %s", _("existing key will be replaced\n")); else if (generating) log_info ("piv: %s", _("generating new key\n")); else log_info ("piv: %s", _("writing new key\n")); return 0; } /* Parse an RSA response object, consisting of the content of tag * 0x7f49, into a gcrypt s-expresstion object and store that R_SEXP. * On error NULL is stored at R_SEXP. */ static gpg_error_t genkey_parse_rsa (const unsigned char *data, size_t datalen, gcry_sexp_t *r_sexp) { gpg_error_t err; const unsigned char *m, *e; unsigned char *mbuf = NULL; unsigned char *ebuf = NULL; size_t mlen, elen; *r_sexp = NULL; m = find_tlv (data, datalen, 0x0081, &mlen); if (!m) { log_error (_("response does not contain the RSA modulus\n")); err = gpg_error (GPG_ERR_CARD); goto leave; } e = find_tlv (data, datalen, 0x0082, &elen); if (!e) { log_error (_("response does not contain the RSA public exponent\n")); err = gpg_error (GPG_ERR_CARD); goto leave; } for (; mlen && !*m; mlen--, m++) /* Strip leading zeroes */ ; for (; elen && !*e; elen--, e++) /* Strip leading zeroes */ ; mbuf = xtrymalloc (mlen + 1); if (!mbuf) { err = gpg_error_from_syserror (); goto leave; } /* Prepend numbers with a 0 if needed. */ if (mlen && (*m & 0x80)) { *mbuf = 0; memcpy (mbuf+1, m, mlen); mlen++; } else memcpy (mbuf, m, mlen); ebuf = xtrymalloc (elen + 1); if (!ebuf) { err = gpg_error_from_syserror (); goto leave; } /* Prepend numbers with a 0 if needed. */ if (elen && (*e & 0x80)) { *ebuf = 0; memcpy (ebuf+1, e, elen); elen++; } else memcpy (ebuf, e, elen); err = gcry_sexp_build (r_sexp, NULL, "(public-key(rsa(n%b)(e%b)))", (int)mlen, mbuf, (int)elen, ebuf); leave: xfree (mbuf); xfree (ebuf); return err; } /* Create a new keypair for KEYREF. If KEYTYPE is NULL a default * keytype is selected, else it may be one of the strings: * "rsa2048", "nistp256, or "nistp384". * * Supported FLAGS are: * APP_GENKEY_FLAG_FORCE Overwrite existing key. * * Note that CREATETIME is not used for PIV cards. * * Because there seems to be no way to read the public key we need to * retrieve it from a certificate. The GnuPG system however requires * the use of app_readkey to fetch the public key from the card to * create the certificate; to support this we temporary store the * generated public key in the local context for use by app_readkey. */ static gpg_error_t do_genkey (app_t app, ctrl_t ctrl, const char *keyrefstr, const char *keytype, unsigned int flags, time_t createtime, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg) { gpg_error_t err; data_object_t dobj; unsigned char *buffer = NULL; size_t buflen; int force = !!(flags & APP_GENKEY_FLAG_FORCE); int mechanism; time_t start_at; int keyref; unsigned char tmpl[5]; size_t tmpllen; const unsigned char *keydata; size_t keydatalen; gcry_sexp_t s_pkey = NULL; struct genkey_result_s *gres; (void)ctrl; (void)createtime; (void)pincb; (void)pincb_arg; if (!keytype) keytype = "rsa2048"; if (!strcmp (keytype, "rsa2048")) mechanism = PIV_ALGORITHM_RSA; else if (!strcmp (keytype, "nistp256")) mechanism = PIV_ALGORITHM_ECC_P256; else if (!strcmp (keytype, "nistp384")) mechanism = PIV_ALGORITHM_ECC_P384; else return gpg_error (GPG_ERR_UNKNOWN_CURVE); /* We flush the cache to increase the I/O traffic before a key * generation. This _might_ help the card to gather more entropy * and is anyway a prerequisite for does_key_exist. */ flush_cached_data (app, 0); /* Check whether a key already exists. */ dobj = find_dobj_by_keyref (app, keyrefstr); if ((keyref = keyref_from_dobj (dobj)) == -1) { err = gpg_error (GPG_ERR_INV_ID); goto leave; } err = does_key_exist (app, dobj, 1, force); if (err) goto leave; /* FIXME: Check that the authentication has already been done. */ /* Create the key. */ log_info (_("please wait while key is being generated ...\n")); start_at = time (NULL); tmpl[0] = 0xac; tmpl[1] = 3; tmpl[2] = 0x80; tmpl[3] = 1; tmpl[4] = mechanism; tmpllen = 5; err = iso7816_generate_keypair (app->slot, 0, 0, keyref, tmpl, tmpllen, 0, &buffer, &buflen); if (err) { log_error (_("generating key failed\n")); return gpg_error (GPG_ERR_CARD); } { int nsecs = (int)(time (NULL) - start_at); log_info (ngettext("key generation completed (%d second)\n", "key generation completed (%d seconds)\n", nsecs), nsecs); } /* Parse the result and store it as an s-expression in a dedicated * cache for later retrieval by app_readkey. */ keydata = find_tlv (buffer, buflen, 0x7F49, &keydatalen); if (!keydata || !keydatalen) { err = gpg_error (GPG_ERR_CARD); log_error (_("response does not contain the public key data\n")); goto leave; } if (mechanism == PIV_ALGORITHM_RSA) err = genkey_parse_rsa (keydata, keydatalen, &s_pkey); else err = gpg_error (GPG_ERR_BUG); if (err) goto leave; for (gres = app->app_local->genkey_results; gres; gres = gres->next) if (gres->keyref == keyref) break; if (!gres) { gres = xtrycalloc (1, sizeof *gres); if (!gres) { err = gpg_error_from_syserror (); goto leave; } gres->keyref = keyref; gres->next = app->app_local->genkey_results; app->app_local->genkey_results = gres; } else gcry_sexp_release (gres->s_pkey); gres->s_pkey = s_pkey; s_pkey = NULL; leave: gcry_sexp_release (s_pkey); xfree (buffer); return err; } +/* Write the certificate (CERT,CERTLEN) to the card at CERTREFSTR. + * CERTREFSTR is either the OID of the certificate's container data + * object or of the form "PIV.". */ +static gpg_error_t +do_writecert (app_t app, ctrl_t ctrl, + const char *certrefstr, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg, + const unsigned char *cert, size_t certlen) +{ + gpg_error_t err; + data_object_t dobj; + + (void)ctrl; + (void)pincb; /* Not used; instead authentication is needed. */ + (void)pincb_arg; + + dobj = find_dobj_by_keyref (app, certrefstr); + if (!dobj || !*dobj->keyref) + return gpg_error (GPG_ERR_INV_ID); + + /* FIXME: Check that the authentication has already been done. */ + + flush_cached_data (app, dobj->tag); + + err = put_data (app->slot, dobj->tag, + (int)0x70, (size_t)certlen, cert,/* Certificate */ + (int)0x71, (size_t)1, "", /* No compress */ + (int)0xfe, (size_t)0, "", /* Empty LRC. */ + (int)0, (size_t)0, NULL); + if (err) + log_error ("piv: failed to write cert to %s: %s\n", + dobj->keyref, gpg_strerror (err)); + + + 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->slot; gpg_error_t err; unsigned char *apt = NULL; size_t aptlen; const unsigned char *s; size_t n; /* 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, &apt, &aptlen); if (err) goto leave; app->apptype = "PIV"; app->did_chv1 = 0; app->did_chv2 = 0; app->did_chv3 = 0; app->app_local = NULL; /* Check the Application Property Template. */ if (opt.verbose) { /* We use a separate log_info to avoid the "DBG:" prefix. */ log_info ("piv: APT="); log_printhex (apt, aptlen, ""); } s = find_tlv (apt, aptlen, 0x4F, &n); if (!s || n != 6 || memcmp (s, aid+5, 4)) { /* The PIX does not match. */ log_error ("piv: missing or invalid DO 0x4F in APT\n"); err = gpg_error (GPG_ERR_CARD); goto leave; } if (s[4] != 1 || s[5] != 0) { log_error ("piv: unknown PIV version %u.%u\n", s[4], s[5]); err = gpg_error (GPG_ERR_CARD); goto leave; } app->card_version = ((s[4] << 8) | s[5]); s = find_tlv (apt, aptlen, 0x79, &n); if (!s || n < 7) { log_error ("piv: missing or invalid DO 0x79 in APT\n"); err = gpg_error (GPG_ERR_CARD); goto leave; } s = find_tlv (s, n, 0x4F, &n); if (!s || n != 5 || memcmp (s, aid, 5)) { /* The RID does not match. */ log_error ("piv: missing or invalid DO 0x79.4F in APT\n"); err = gpg_error (GPG_ERR_CARD); goto leave; } app->app_local = xtrycalloc (1, sizeof *app->app_local); if (!app->app_local) { err = gpg_error_from_syserror (); goto leave; } if (app->cardtype && !strcmp (app->cardtype, "yubikey")) app->app_local->flags.yubikey = 1; /* FIXME: Parse the optional and conditional DOs in the APT. */ if (opt.verbose) dump_all_do (slot); app->fnc.deinit = do_deinit; app->fnc.learn_status = do_learn_status; app->fnc.readcert = do_readcert; app->fnc.readkey = do_readkey; app->fnc.getattr = do_getattr; app->fnc.setattr = do_setattr; - /* app->fnc.writecert = do_writecert; */ + app->fnc.writecert = do_writecert; /* app->fnc.writekey = do_writekey; */ app->fnc.genkey = do_genkey; /* app->fnc.sign = do_sign; */ app->fnc.auth = do_auth; /* app->fnc.decipher = do_decipher; */ app->fnc.change_pin = do_change_chv; app->fnc.check_pin = do_check_chv; leave: xfree (apt); if (err) do_deinit (app); return err; } diff --git a/tools/gpg-card-tool.c b/tools/gpg-card-tool.c index 08248f766..917013247 100644 --- a/tools/gpg-card-tool.c +++ b/tools/gpg-card-tool.c @@ -1,3263 +1,3275 @@ /* gpg-card-tool.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 "card-tool.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-tool"; 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-tool" " [options] [{[--] command [args]}] (-h for help)"); break; case 41: p = ("Syntax: gpg-card-tool" " [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-tool 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-tool"); set_strusage (my_strusage); gnupg_rl_initialize (); log_set_prefix ("gpg-card-tool", 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; 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; 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); 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); } /* 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); 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->serialno && info->dispserialno && strcmp (info->serialno, 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; - int do_no; + char *certref_buffer = NULL; + char *certref; char *data = NULL; size_t datalen; if (!info) return print_help - ("WRITECERT [--clear] 3 < FILE\n\n" + ("WRITECERT [--clear] CERTREF < FILE\n\n" "Write a certificate for key 3. Unless --clear is given\n" - "the file argement is mandatory. The option --clear removes\n" + "the file argument is mandatory. The option --clear removes\n" "the certificate from the card.", - APP_TYPE_OPENPGP, 0); + APP_TYPE_OPENPGP, APP_TYPE_PIV, 0); opt_clear = has_leading_option (argstr, "--clear"); argstr = skip_options (argstr); - if (digitp (argstr)) + certref = argstr; + if ((argstr = strchr (certref, ' '))) { - do_no = atoi (argstr); - while (digitp (argstr)) - argstr++; - while (spacep (argstr)) - argstr++; + *argstr++ = 0; + trim_spaces (certref); + trim_spaces (argstr); } - else - do_no = 0; + else /* Let argstr point to an empty string. */ + argstr = certref + strlen (certref); - if (do_no != 3) + if (info->apptype == APP_TYPE_OPENPGP) { - err = gpg_error (GPG_ERR_INV_ARG); - goto leave; + 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 (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 ("OPENPGP.3", data, datalen); + 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; - int do_no; + char *certref_buffer = NULL; + char *certref; void *data = NULL; size_t datalen; const char *fname; if (!info) return print_help - ("READCERT 3 > FILE\n\n" + ("READCERT CERTREF > FILE\n\n" "Read the certificate for key 3 and store it in FILE.", - APP_TYPE_OPENPGP, 0); + APP_TYPE_OPENPGP, APP_TYPE_PIV, 0); argstr = skip_options (argstr); - if (digitp (argstr)) + certref = argstr; + if ((argstr = strchr (certref, ' '))) { - do_no = atoi (argstr); - while (digitp (argstr)) - argstr++; - while (spacep (argstr)) - argstr++; + *argstr++ = 0; + trim_spaces (certref); + trim_spaces (argstr); } - else - do_no = 0; + else /* Let argstr point to an empty string. */ + argstr = certref + strlen (certref); - if (do_no != 3) + if (info->apptype == APP_TYPE_OPENPGP) { - err = gpg_error (GPG_ERR_INV_ARG); - goto leave; + 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 == '>') /* Read it from a file */ + 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 ("OPENPGP.3", &data, &datalen); + 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_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. 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. */ 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; } } static gpg_error_t cmd_generate (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) return print_help ("GENERATE\n\n" "Menu to generate a new keys.", 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.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); */ err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); leave: restore_forced_chv1 (&forced_chv1); xfree (answer); return err; } /* Sub-menu to change a PIN. The presented options may depend on the * the ALLOW_ADMIN flag. */ static gpg_error_t cmd_passwd (card_info_t info, int allow_admin, 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 (!allow_admin || info->apptype != APP_TYPE_OPENPGP) { if (*argstr) pinref = argstr; else if (info->apptype == APP_TYPE_OPENPGP) pinref = "OPENPGP.1"; else if (info->apptype == APP_TYPE_PIV) pinref = "PIV.80"; else { 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"); } else if (info->apptype == APP_TYPE_OPENPGP) { 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 { log_info ("Admin related passwd options not yet supported for '%s'\n", app_type_string (info->apptype)); err = gpg_error (GPG_ERR_NOT_SUPPORTED); } 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; } /* Direct sending of an hex encoded APDU with error printing. */ static gpg_error_t send_apdu (const char *hexapdu, const char *desc, unsigned int ignore) { gpg_error_t err; unsigned int sw; err = scd_apdu (hexapdu, &sw); if (err) log_error ("sending card command %s failed: %s\n", desc, gpg_strerror (err)); else if (!hexapdu || !strcmp (hexapdu, "undefined")) ; else if (ignore == 0xffff) ; /* Ignore all status words. */ else if (sw != 0x9000) { switch (sw) { case 0x6285: err = gpg_error (GPG_ERR_OBJ_TERM_STATE); break; case 0x6982: err = gpg_error (GPG_ERR_BAD_PIN); break; case 0x6985: err = gpg_error (GPG_ERR_USE_CONDITIONS); break; default: err = gpg_error (GPG_ERR_CARD); } if (!(ignore && ignore == sw)) log_error ("card command %s failed: %s (0x%04x)\n", desc, gpg_strerror (err), sw); } 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); for (i=0; i < 5; i++) send_apdu ("002C008010FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", "RESET RETRY COUNTER", 0xffff); err = send_apdu ("00FB000001FF", "YUBIKEY RESET", 0); 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); if (err) goto leave; err = send_apdu ("undefined", "dummy select ", 0); if (err) goto leave; /* Select the OpenPGP application. */ err = send_apdu ("00A4040006D27600012401", "SELECT AID", 0); 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); for (i=0; i < 4; i++) send_apdu ("0020008320" "40404040404040404040404040404040" "40404040404040404040404040404040", "VERIFY", 0xffff); /* Send terminate datafile command. */ err = send_apdu ("00e60000", "TERMINATE DF", 0x6985); 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); if (err) goto leave; } /* Finally we reset the card reader once more. */ err = send_apdu (NULL, "RESET", 0); 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; } err = GPG_ERR_NOT_IMPLEMENTED; goto leave; /* FIXME: We need to mve 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 opnepgp 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; } /* 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, cmdADMIN, cmdHELP, cmdLIST, cmdRESET, cmdVERIFY, cmdNAME, cmdURL, cmdFETCH, cmdLOGIN, cmdLANG, cmdSALUT, cmdCAFPR, cmdFORCESIG, cmdGENERATE, cmdPASSWD, cmdPRIVATEDO, cmdWRITECERT, cmdREADCERT, cmdUNBLOCK, cmdFACTORYRESET, cmdKDFSETUP, cmdKEYATTR, cmdUIF, cmdAUTHENTICATE, cmdINVCMD }; static struct { const char *name; enum cmdids id; int admin_only; const char *desc; } cmds[] = { { "quit" , cmdQUIT , 0, N_("quit this menu")}, { "q" , cmdQUIT , 0, NULL }, { "admin" , cmdADMIN , 0, N_("show admin commands")}, { "help" , cmdHELP , 0, N_("show this help")}, { "?" , cmdHELP , 0, NULL }, { "list" , cmdLIST , 0, N_("list all available data")}, { "l" , cmdLIST , 0, NULL }, { "name" , cmdNAME , 1, N_("change card holder's name")}, { "url" , cmdURL , 1, N_("change URL to retrieve key")}, { "fetch" , cmdFETCH , 0, N_("fetch the key specified in the card URL")}, { "login" , cmdLOGIN , 1, N_("change the login name")}, { "lang" , cmdLANG , 1, N_("change the language preferences")}, { "salutation",cmdSALUT, 1, N_("change card holder's salutation")}, { "salut" , cmdSALUT, 1, NULL }, { "cafpr" , cmdCAFPR , 1, N_("change a CA fingerprint")}, { "forcesig", cmdFORCESIG, 1, N_("toggle the signature force PIN flag")}, { "generate", cmdGENERATE, 1, N_("generate new keys")}, { "passwd" , cmdPASSWD, 0, N_("menu to change or unblock the PIN")}, { "verify" , cmdVERIFY, 0, N_("verify the PIN and list all data")}, { "unblock" , cmdUNBLOCK,0, N_("unblock the PIN using a Reset Code")}, { "authenticate",cmdAUTHENTICATE, 0,N_("authenticate to the card")}, { "auth" , cmdAUTHENTICATE, 0, NULL }, { "reset" , cmdRESET, 0, N_("send a reset to the card daemon")}, { "factory-reset", cmdFACTORYRESET, 1, N_("destroy all keys and data")}, { "kdf-setup", cmdKDFSETUP, 1, N_("setup KDF for PIN authentication")}, { "key-attr", cmdKEYATTR, 1, N_("change the key attribute")}, { "uif", cmdUIF, 1, N_("change the User Interaction Flag")}, /* Note, that we do not announce these command yet. */ { "privatedo", cmdPRIVATEDO, 0, N_("change a private data object")}, { "readcert", cmdREADCERT, 0, N_("read a certificate from a data object")}, { "writecert", cmdWRITECERT, 1, N_("store a certificate to a data object")}, { NULL, cmdINVCMD, 0, 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); } break; case cmdADMIN: /* This is a NOP in non-interactive mode. */ break; case cmdVERIFY: err = cmd_verify (info, argstr); break; case cmdAUTHENTICATE: 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 cmdFORCESIG: err = cmd_forcesig (info); break; case cmdGENERATE: err = cmd_generate (info); break; case cmdPASSWD: err = cmd_passwd (info, 1, argstr); break; case cmdUNBLOCK: err = cmd_unblock (info); break; case cmdFACTORYRESET: 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 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. */ int cmd_admin_only; /* The command is an admin only command. */ char *argstr; /* The argument as a string. */ int redisplay = 1; /* Whether to redisplay the main info. */ int allow_admin = 0; /* Whether admin commands are allowed. */ char *help_arg = NULL; /* Argument of the HELP command. */ struct card_info_s info_buffer; 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; cmd_admin_only = 0; 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; cmd_admin_only = cmds[i].admin_only; } /* 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; cmd_admin_only = 0; } 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; } else if (!allow_admin && cmd_admin_only) { tty_printf ("\n"); tty_printf (_("Admin-only command\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 && (!cmds[i].admin_only || (cmds[i].admin_only && allow_admin))) 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); } break; case cmdADMIN: if ( !strcmp (argstr, "on") ) allow_admin = 1; else if ( !strcmp (argstr, "off") ) allow_admin = 0; else if ( !strcmp (argstr, "verify") ) { /* Force verification of the Admin Command. However, this is only done if the retry counter is at initial state. */ /* FIXME: Must depend on the type of the card. */ /* char *tmp = xmalloc (strlen (serialnobuf) + 6 + 1); */ /* strcpy (stpcpy (tmp, serialnobuf), "[CHV3]"); */ /* allow_admin = !agent_scd_checkpin (tmp); */ /* xfree (tmp); */ } else /* Toggle. */ allow_admin=!allow_admin; if(allow_admin) tty_printf(_("Admin commands are allowed\n")); else tty_printf(_("Admin commands are not allowed\n")); break; case cmdVERIFY: err = cmd_verify (info, argstr); if (!err) redisplay = 1; break; case cmdAUTHENTICATE: 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 cmdFORCESIG: err = cmd_forcesig (info); break; case cmdGENERATE: err = cmd_generate (info); break; case cmdPASSWD: err = cmd_passwd (info, allow_admin, argstr); break; case cmdUNBLOCK: err = cmd_unblock (info); break; case cmdFACTORYRESET: 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 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*/