scd: Implement RSA signing for PIV cards.

* scd/app-piv.c (concat_tlv_list): New.
(get_key_algorithm_by_dobj): Rename args for clarity.
(do_auth): factor all code out to ...
(do_sign): new.  Implement RSA signing.

Signed-off-by: Werner Koch <wk@gnupg.org>
This commit is contained in:
Werner Koch 2019-02-08 16:46:52 +01:00
parent 0328976c94
commit 53beea56af
No known key found for this signature in database
GPG Key ID: E3FDFF218E45B72B
1 changed files with 299 additions and 99 deletions

View File

@ -469,6 +469,107 @@ add_tlv (unsigned char *buffer, unsigned int tag, size_t length)
} }
/* Function to build a list of TLV and return the result in a mallcoed
* buffer. The varargs are tuples of (int,size_t,void) each 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
concat_tlv_list (unsigned char **r_result, size_t *r_resultlen, ...)
{
gpg_error_t err;
va_list arg_ptr;
struct {
int tag;
unsigned int len;
unsigned int contlen;
const void *data;
} argv[10];
int i, j, argc;
unsigned char *data = NULL;
size_t datalen;
unsigned char *p;
size_t n;
*r_result = NULL;
*r_resultlen = 0;
/* Collect all args. Check that length is <= 2^16 to match the
* behaviour of add_tlv. */
va_start (arg_ptr, r_resultlen);
argc = 0;
while (((argv[argc].tag = va_arg (arg_ptr, int))))
{
argv[argc].len = va_arg (arg_ptr, size_t);
argv[argc].contlen = 0;
argv[argc].data = va_arg (arg_ptr, const void *);
if (argc >= DIM (argv)-1 || argv[argc].len > 0xffff)
{
va_end (arg_ptr);
err = gpg_error (GPG_ERR_EINVAL);
goto leave;
}
argc++;
}
va_end (arg_ptr);
/* Compute the required buffer length and allocate the buffer. */
datalen = 0;
for (i=0; i < argc; i++)
{
if (!argv[i].len && !argv[i].data)
{
/* Constructed tag. Compute its length. Note that we
* currently allow only one constructed tag in the list. */
for (n=0, j = i + 1; j < argc; j++)
{
log_assert (!(!argv[j].len && !argv[j].data));
n += add_tlv (NULL, argv[j].tag, argv[j].len);
n += argv[j].len;
}
argv[i].contlen = n;
datalen += add_tlv (NULL, argv[i].tag, n);
}
else
{
datalen += add_tlv (NULL, argv[i].tag, argv[i].len);
datalen += argv[i].len;
}
}
data = xtrymalloc (datalen);
if (!data)
{
err = gpg_error_from_syserror ();
goto leave;
}
/* Copy that data to the buffer. */
p = data;
for (i=0; i < argc; i++)
{
if (!argv[i].len && !argv[i].data)
{
/* Constructed tag. */
p += add_tlv (p, argv[i].tag, argv[i].contlen);
}
else
{
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 );
*r_result = data;
data = NULL;
*r_resultlen = datalen;
err = 0;
leave:
xfree (data);
return err;
}
/* Wrapper around iso7816_put_data_odd which also sets the tag into /* 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) * 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 * with the tag, the length and the actual data. A (0,0,NULL) tuple
@ -1354,7 +1455,7 @@ do_readkey (app_t app, int advanced, const char *keyrefstr,
* store it at R_ALGO. The algorithm is taken from the corresponding * store it at R_ALGO. The algorithm is taken from the corresponding
* certificate or from a cache. */ * certificate or from a cache. */
static gpg_error_t static gpg_error_t
get_key_algorithm_by_dobj (app_t app, data_object_t dobj, int *r_algo) get_key_algorithm_by_dobj (app_t app, data_object_t dobj, int *r_mechanism)
{ {
gpg_error_t err; gpg_error_t err;
unsigned char *certbuf = NULL; unsigned char *certbuf = NULL;
@ -1369,7 +1470,7 @@ get_key_algorithm_by_dobj (app_t app, data_object_t dobj, int *r_algo)
size_t n; size_t n;
const char *curve_name; const char *curve_name;
*r_algo = 0; *r_mechanism = 0;
err = readcert_by_tag (app, dobj->tag, &certbuf, &certbuflen, &mechanism); err = readcert_by_tag (app, dobj->tag, &certbuf, &certbuflen, &mechanism);
if (err) if (err)
@ -1382,7 +1483,7 @@ get_key_algorithm_by_dobj (app_t app, data_object_t dobj, int *r_algo)
case PIV_ALGORITHM_RSA: case PIV_ALGORITHM_RSA:
case PIV_ALGORITHM_ECC_P256: case PIV_ALGORITHM_ECC_P256:
case PIV_ALGORITHM_ECC_P384: case PIV_ALGORITHM_ECC_P384:
*r_algo = mechanism; *r_mechanism = mechanism;
break; break;
default: default:
@ -1468,7 +1569,7 @@ get_key_algorithm_by_dobj (app_t app, data_object_t dobj, int *r_algo)
dobj->keyref, algoname, gpg_strerror (err)); dobj->keyref, algoname, gpg_strerror (err));
goto leave; goto leave;
} }
*r_algo = algo; *r_mechanism = algo;
leave: leave:
gcry_free (algoname); gcry_free (algoname);
@ -1862,10 +1963,11 @@ do_check_chv (app_t app, const char *pwidstr,
* stored there and an error code returned. For ECDSA the result 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 * 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 * and S are left extended with zeroes to make sure they have an equal
* length. * length. If HASHALGO is not zero, the function prepends the hash's
* OID to the indata or checks that it is consistent.
*/ */
static gpg_error_t static gpg_error_t
do_auth (app_t app, const char *keyidstr, do_sign (app_t app, const char *keyidstr, int hashalgo,
gpg_error_t (*pincb)(void*, const char *, char **), gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg, void *pincb_arg,
const void *indata_arg, size_t indatalen, const void *indata_arg, size_t indatalen,
@ -1874,13 +1976,16 @@ do_auth (app_t app, const char *keyidstr,
const unsigned char *indata = indata_arg; const unsigned char *indata = indata_arg;
gpg_error_t err; gpg_error_t err;
data_object_t dobj; data_object_t dobj;
unsigned char tmpl[2+2+2+128]; unsigned char oidbuf[64];
size_t tmpllen; size_t oidbuflen;
unsigned char *outdata = NULL; unsigned char *outdata = NULL;
size_t outdatalen; size_t outdatalen;
const unsigned char *s; const unsigned char *s;
size_t n; size_t n;
int keyref, algo; int keyref, mechanism;
unsigned char *indata_buffer = NULL; /* Malloced helper. */
unsigned char *apdudata = NULL;
size_t apdudatalen;
if (!keyidstr || !*keyidstr) if (!keyidstr || !*keyidstr)
{ {
@ -1888,9 +1993,6 @@ do_auth (app_t app, const char *keyidstr,
goto leave; 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); dobj = find_dobj_by_keyref (app, keyidstr);
if ((keyref = keyref_from_dobj (dobj)) == -1) if ((keyref = keyref_from_dobj (dobj)) == -1)
{ {
@ -1898,69 +2000,141 @@ do_auth (app_t app, const char *keyidstr,
goto leave; goto leave;
} }
err = get_key_algorithm_by_dobj (app, dobj, &algo); err = get_key_algorithm_by_dobj (app, dobj, &mechanism);
if (err) if (err)
goto leave; goto leave;
/* We need to remove the ASN.1 prefix from INDATA. We use TEMPL as /* For ECC we need to remove the ASN.1 prefix from INDATA. For RSA
* a temporary buffer for the OID. */ * we need to add the padding and possible also the ASN.1 prefix. */
if (algo == PIV_ALGORITHM_ECC_P256) if (mechanism == PIV_ALGORITHM_ECC_P256
|| mechanism == PIV_ALGORITHM_ECC_P384)
{ {
tmpllen = sizeof tmpl; int need_algo, need_digestlen;
err = gcry_md_get_asnoid (GCRY_MD_SHA256, &tmpl, &tmpllen);
if (err) if (mechanism == PIV_ALGORITHM_ECC_P256)
{ {
err = gpg_error (GPG_ERR_INTERNAL); need_algo = GCRY_MD_SHA256;
log_debug ("piv: no OID for hash algo %d\n", GCRY_MD_SHA256); need_digestlen = 32;
}
else
{
need_algo = GCRY_MD_SHA384;
need_digestlen = 48;
}
if (hashalgo && hashalgo != need_algo)
{
err = gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM);
log_error ("piv: hash algo %d does not match mechanism %d\n",
need_algo, mechanism);
goto leave; goto leave;
} }
if (indatalen != tmpllen + 32 || memcmp (indata, tmpl, tmpllen))
if (indatalen > need_digestlen)
{ {
err = GPG_ERR_INV_VALUE; oidbuflen = sizeof oidbuf;
log_error ("piv: bad formatted input for ECC-P256 auth\n"); err = gcry_md_get_asnoid (need_algo, &oidbuf, &oidbuflen);
goto leave; if (err)
{
err = gpg_error (GPG_ERR_INTERNAL);
log_debug ("piv: no OID for hash algo %d\n", need_algo);
goto leave;
}
if (indatalen != oidbuflen + need_digestlen
|| memcmp (indata, oidbuf, oidbuflen))
{
err = gpg_error (GPG_ERR_INV_VALUE);
log_error ("piv: bad input for signing with mechanism %d\n",
mechanism);
goto leave;
}
indata += oidbuflen;
indatalen -= oidbuflen;
} }
indata +=tmpllen;
indatalen -= tmpllen;
} }
else if (algo == PIV_ALGORITHM_ECC_P384) else if (mechanism == PIV_ALGORITHM_RSA)
{ {
tmpllen = sizeof tmpl; /* PIV requires 2048 bit RSA. */
err = gcry_md_get_asnoid (GCRY_MD_SHA384, &tmpl, &tmpllen); unsigned int framelen = 2048 / 8;
if (err) unsigned char *frame;
int i;
oidbuflen = sizeof oidbuf;
if (!hashalgo)
{ {
err = gpg_error (GPG_ERR_INTERNAL); /* We assume that indata already has the required
log_debug ("piv: no OID for hash algo %d\n", GCRY_MD_SHA384); * digestinfo; thus merely prepend the padding below. */
}
else if ((err = gcry_md_get_asnoid (hashalgo, &oidbuf, &oidbuflen)))
{
log_debug ("piv: no OID for hash algo %d\n", hashalgo);
goto leave; goto leave;
} }
if (indatalen != tmpllen + 48 || memcmp (indata, tmpl, tmpllen)) else
{ {
err = GPG_ERR_INV_VALUE; unsigned int digestlen = gcry_md_get_algo_dlen (hashalgo);
log_error ("piv: bad formatted input for ECC-P384 auth\n");
if (indatalen == digestlen)
{
/* Plain hash in INDATA; prepend the digestinfo. */
indata_buffer = xtrymalloc (oidbuflen + indatalen);
if (!indata_buffer)
{
err = gpg_error_from_syserror ();
goto leave;
}
memcpy (indata_buffer, oidbuf, oidbuflen);
memcpy (indata_buffer+oidbuflen, indata, indatalen);
indata = indata_buffer;
indatalen = oidbuflen + indatalen;
}
else if (indatalen == oidbuflen + digestlen
&& !memcmp (indata, oidbuf, oidbuflen))
; /* Correct prefix. */
else
{
err = gpg_error (GPG_ERR_INV_VALUE);
log_error ("piv: bad input for signing with RSA and hash %d\n",
hashalgo);
goto leave;
}
}
/* Now prepend the pkcs#v1.5 padding. We require at least 8
* byte of padding and 3 extra bytes for the prefix and the
* delimiting nul. */
if (!indatalen || indatalen + 8 + 4 > framelen)
{
err = gpg_error (GPG_ERR_INV_VALUE);
log_error ("piv: input does not fit into a %u bit PKCS#v1.5 frame\n",
8*framelen);
goto leave; goto leave;
} }
indata += tmpllen; frame = xtrymalloc (framelen);
indatalen -= tmpllen; if (!frame)
} {
else if (algo == PIV_ALGORITHM_RSA) err = gpg_error_from_syserror ();
{ goto leave;
err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); }
log_error ("piv: FIXME: implement RSA authentication\n"); n = 0;
goto leave; frame[n++] = 0;
frame[n++] = 1; /* Block type. */
i = framelen - indatalen - 3 ;
memset (frame+n, 0xff, i);
n += i;
frame[n++] = 0; /* Delimiter. */
memcpy (frame+n, indata, indatalen);
n += indatalen;
log_assert (n == framelen);
/* And now put it into the indata_buffer. */
xfree (indata_buffer);
indata_buffer = frame;
indata = indata_buffer;
indatalen = framelen;
} }
else else
{ {
err = gpg_error (GPG_ERR_INTERNAL); err = gpg_error (GPG_ERR_INTERNAL);
log_debug ("piv: unknown PIV algo %d from helper function\n", algo); log_debug ("piv: unknown PIV mechanism %d while signing\n", mechanism);
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; goto leave;
} }
@ -1970,19 +2144,18 @@ do_auth (app_t app, const char *keyidstr,
return err; return err;
/* Build the Dynamic Authentication Template. */ /* Build the Dynamic Authentication Template. */
tmpl[0] = 0x7c; err = concat_tlv_list (&apdudata, &apdudatalen,
tmpl[1] = indatalen + 4; (int)0x7c, (size_t)0, NULL, /* Constructed. */
tmpl[2] = 0x82; /* Response. */ (int)0x82, (size_t)0, "",
tmpl[3] = 0; /* Must be 0 to get the tag in the answer. */ (int)0x81, (size_t)indatalen, indata,
tmpl[4] = 0x81; /* Challenge. */ (int)0, (size_t)0, NULL);
tmpl[5] = indatalen; if (err)
memcpy (tmpl+6, indata, indatalen); goto leave;
tmpllen = indatalen + 6;
/* Note: the -1 requests command chaining. */ /* Note: the -1 requests command chaining. */
err = iso7816_general_authenticate (app->slot, -1, err = iso7816_general_authenticate (app->slot, -1,
algo, keyref, mechanism, keyref,
tmpl, (int)tmpllen, 0, apdudata, (int)apdudatalen, 0,
&outdata, &outdatalen); &outdata, &outdatalen);
if (err) if (err)
goto leave; goto leave;
@ -1991,42 +2164,50 @@ do_auth (app_t app, const char *keyidstr,
if (outdatalen && *outdata == 0x7c if (outdatalen && *outdata == 0x7c
&& (s = find_tlv (outdata, outdatalen, 0x82, &n))) && (s = find_tlv (outdata, outdatalen, 0x82, &n)))
{ {
const unsigned char *rval, *sval; if (mechanism == PIV_ALGORITHM_RSA)
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 (); memmove (outdata, outdata + (s - outdata), n);
goto leave; outdatalen = n;
}
else /* ECC */
{
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;
} }
memcpy (result + rlenx, rval, rlen);
memcpy (result + rlenx + rlen + slenx, sval, slen);
xfree (outdata);
outdata = result;
outdatalen = resultlen;
} }
else else
{ {
@ -2048,10 +2229,29 @@ do_auth (app_t app, const char *keyidstr,
*r_outdata = outdata; *r_outdata = outdata;
*r_outdatalen = outdatalen; *r_outdatalen = outdatalen;
} }
xfree (apdudata);
xfree (indata_buffer);
return err; return err;
} }
/* AUTH for PIV cards is actually the same as SIGN. The difference
* between AUTH and SIGN is that AUTH expects that pkcs#1.5 padding
* for RSA has already been done (digestInfo part w/o the padding)
* whereas SIGN may accept a plain digest and does the padding if
* needed. This is also the reason why SIGN takes a hashalgo. */
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, size_t indatalen,
unsigned char **r_outdata, size_t *r_outdatalen)
{
return do_sign (app, keyidstr, 0, pincb, pincb_arg, indata, indatalen,
r_outdata, r_outdatalen);
}
/* Check whether a key for DOBJ already exists. We detect this by /* Check whether a key for DOBJ already exists. We detect this by
* reading the certificate described by DOBJ. If FORCE is TRUE a * reading the certificate described by DOBJ. If FORCE is TRUE a
* diagnositic will be printed but no error returned if the key * diagnositic will be printed but no error returned if the key
@ -2464,7 +2664,7 @@ app_select_piv (app_t app)
app->fnc.writecert = do_writecert; app->fnc.writecert = do_writecert;
/* app->fnc.writekey = do_writekey; */ /* app->fnc.writekey = do_writekey; */
app->fnc.genkey = do_genkey; app->fnc.genkey = do_genkey;
/* app->fnc.sign = do_sign; */ app->fnc.sign = do_sign;
app->fnc.auth = do_auth; app->fnc.auth = do_auth;
/* app->fnc.decipher = do_decipher; */ /* app->fnc.decipher = do_decipher; */
app->fnc.change_pin = do_change_chv; app->fnc.change_pin = do_change_chv;