mirror of
git://git.gnupg.org/gnupg.git
synced 2024-12-22 10:19:57 +01:00
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:
parent
9a9cb0257a
commit
b5b1f72158
362
scd/app-piv.c
362
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; */
|
||||
|
Loading…
x
Reference in New Issue
Block a user