aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorWerner Koch <[email protected]>2019-02-07 10:05:22 +0000
committerWerner Koch <[email protected]>2019-02-07 10:05:22 +0000
commitfcec5b40e589b2ef201efb89f22a952feb4a9069 (patch)
tree9f76f41ae8cf80a592cf7aaef67eccd7290f9121
parentcard: Add readline completion for help arguments (diff)
downloadgnupg-fcec5b40e589b2ef201efb89f22a952feb4a9069.tar.gz
gnupg-fcec5b40e589b2ef201efb89f22a952feb4a9069.zip
card: Support reading and writing PIV certificates
* scd/app-piv.c (add_tlv): New. (put_data): New. (do_writecert): New. (do_setattr): Remove usused special mode 0. * tools/gpg-card-tool.c (cmd_writecert): Allow other cards than OPENPGP. (cmd_readcert): Ditto. Signed-off-by: Werner Koch <[email protected]>
-rw-r--r--scd/app-piv.c199
-rw-r--r--tools/gpg-card-tool.c76
2 files changed, 235 insertions, 40 deletions
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
@@ -426,6 +426,157 @@ dump_all_do (int slot)
}
+/* 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. */
@@ -802,13 +953,6 @@ do_setattr (app_t app, const char *name,
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;
@@ -2062,6 +2206,45 @@ do_genkey (app_t app, ctrl_t ctrl, const char *keyrefstr, const char *keytype,
}
+/* 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.<two_hexdigit_keyref>". */
+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
@@ -2152,7 +2335,7 @@ app_select_piv (app_t app)
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; */
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
@@ -1551,36 +1551,41 @@ 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)
@@ -1602,10 +1607,11 @@ cmd_writecert (card_info_t info, char *argstr)
goto leave;
}
- err = scd_writecert ("OPENPGP.3", data, datalen);
+ err = scd_writecert (certref, data, datalen);
leave:
xfree (data);
+ xfree (certref_buffer);
return err;
}
@@ -1614,37 +1620,42 @@ 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++)
;
@@ -1656,7 +1667,7 @@ cmd_readcert (card_info_t info, char *argstr)
goto leave;
}
- err = scd_readcert ("OPENPGP.3", &data, &datalen);
+ err = scd_readcert (certref, &data, &datalen);
if (err)
goto leave;
@@ -1664,6 +1675,7 @@ cmd_readcert (card_info_t info, char *argstr)
leave:
xfree (data);
+ xfree (certref_buffer);
return err;
}