aboutsummaryrefslogtreecommitdiffstats
path: root/scd/app-piv.c
diff options
context:
space:
mode:
authorWerner Koch <[email protected]>2019-02-06 19:47:07 +0000
committerWerner Koch <[email protected]>2019-02-06 19:47:07 +0000
commitb5b1f721582df9d0379cb68b4faeceed32a56e49 (patch)
tree16867642b0b4b0322b0c61268aea7783fd561735 /scd/app-piv.c
parentscd: Make app_genkey and supporting ISO function more flexible. (diff)
downloadgnupg-b5b1f721582df9d0379cb68b4faeceed32a56e49.tar.gz
gnupg-b5b1f721582df9d0379cb68b4faeceed32a56e49.zip
scd: Add genkey command to app-piv (rsa-only)
* scd/app-piv.c (struct genkey_result_s): new. (struct app_local_s): add member genkey_results. (do_deinit): Free that one. (flush_cached_data): Extend to delete all items. (keyref_from_dobj): New. (do_readkey): New. (do_auth): Use keyref_from_dobj. (does_key_exist): New. (genkey_parse_rsa): New. (do_genkey): New. -- We need to extend the GENKEY in command.c to support other algos. Signed-off-by: Werner Koch <[email protected]>
Diffstat (limited to 'scd/app-piv.c')
-rw-r--r--scd/app-piv.c362
1 files changed, 355 insertions, 7 deletions
diff --git a/scd/app-piv.c b/scd/app-piv.c
index 42f16de40..cfc4a27b3 100644
--- a/scd/app-piv.c
+++ b/scd/app-piv.c
@@ -38,6 +38,7 @@
* | Unblock PIN | | | yes | New PIN required |
* |---------------------------------------------------------------|
* (9B indicates the 24 byte PIV Card Application Administration Key)
+ *
*/
#include <config.h>
@@ -152,11 +153,22 @@ struct cache_s {
};
+/* 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
{
@@ -181,12 +193,19 @@ 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;
@@ -284,14 +303,15 @@ get_cached_data (app_t app, int tag,
}
-/* Remove data object described by TAG from the cache. */
+/* 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)
+ if (c->tag == tag || !tag)
{
if (cprev)
cprev->next = c->next;
@@ -860,7 +880,7 @@ do_learn_status (app_t app, ctrl_t ctrl, unsigned int flags)
}
-/* Core of do-readcert which fetches the certificate based on the
+/* 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
@@ -1010,6 +1030,17 @@ find_dobj_by_keyref (app_t app, const char *keyref)
}
+/* 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
@@ -1031,6 +1062,70 @@ do_readcert (app_t app, const char *certid,
}
+/* 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. */
@@ -1553,12 +1648,11 @@ do_auth (app_t app, const char *keyidstr,
* make sense for X.509 certs? */
dobj = find_dobj_by_keyref (app, keyidstr);
- if (!dobj)
+ if ((keyref = keyref_from_dobj (dobj)) == -1)
{
err = gpg_error (GPG_ERR_INV_ID);
goto leave;
}
- keyref = xtoi_2 (dobj->keyref);
err = get_key_algorithm_by_dobj (app, dobj, &algo);
if (err)
@@ -1714,6 +1808,260 @@ do_auth (app_t app, const char *keyidstr,
}
+/* 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;
+}
+
+
/* Select the PIV application on the card in SLOT. This function must
* be used before any other PIV application functions. */
gpg_error_t
@@ -1801,12 +2149,12 @@ app_select_piv (app_t app)
app->fnc.deinit = do_deinit;
app->fnc.learn_status = do_learn_status;
app->fnc.readcert = do_readcert;
- app->fnc.readkey = NULL;
+ app->fnc.readkey = do_readkey;
app->fnc.getattr = do_getattr;
app->fnc.setattr = do_setattr;
/* app->fnc.writecert = do_writecert; */
/* app->fnc.writekey = do_writekey; */
- /* app->fnc.genkey = do_genkey; */
+ app->fnc.genkey = do_genkey;
/* app->fnc.sign = do_sign; */
app->fnc.auth = do_auth;
/* app->fnc.decipher = do_decipher; */