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 <wk@gnupg.org>
This commit is contained in:
Werner Koch 2019-02-06 20:47:07 +01:00
parent 9a9cb0257a
commit b5b1f72158
No known key found for this signature in database
GPG Key ID: E3FDFF218E45B72B
1 changed files with 355 additions and 7 deletions

View File

@ -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; */