mirror of
git://git.gnupg.org/gnupg.git
synced 2024-12-22 10:19:57 +01:00
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:
parent
0328976c94
commit
53beea56af
398
scd/app-piv.c
398
scd/app-piv.c
@ -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
|
||||
* 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
|
||||
@ -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
|
||||
* certificate or from a cache. */
|
||||
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;
|
||||
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;
|
||||
const char *curve_name;
|
||||
|
||||
*r_algo = 0;
|
||||
*r_mechanism = 0;
|
||||
|
||||
err = readcert_by_tag (app, dobj->tag, &certbuf, &certbuflen, &mechanism);
|
||||
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_ECC_P256:
|
||||
case PIV_ALGORITHM_ECC_P384:
|
||||
*r_algo = mechanism;
|
||||
*r_mechanism = mechanism;
|
||||
break;
|
||||
|
||||
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));
|
||||
goto leave;
|
||||
}
|
||||
*r_algo = algo;
|
||||
*r_mechanism = algo;
|
||||
|
||||
leave:
|
||||
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
|
||||
* 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
|
||||
* 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
|
||||
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 **),
|
||||
void *pincb_arg,
|
||||
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;
|
||||
gpg_error_t err;
|
||||
data_object_t dobj;
|
||||
unsigned char tmpl[2+2+2+128];
|
||||
size_t tmpllen;
|
||||
unsigned char oidbuf[64];
|
||||
size_t oidbuflen;
|
||||
unsigned char *outdata = NULL;
|
||||
size_t outdatalen;
|
||||
const unsigned char *s;
|
||||
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)
|
||||
{
|
||||
@ -1888,9 +1993,6 @@ do_auth (app_t app, const char *keyidstr,
|
||||
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);
|
||||
if ((keyref = keyref_from_dobj (dobj)) == -1)
|
||||
{
|
||||
@ -1898,69 +2000,141 @@ do_auth (app_t app, const char *keyidstr,
|
||||
goto leave;
|
||||
}
|
||||
|
||||
err = get_key_algorithm_by_dobj (app, dobj, &algo);
|
||||
err = get_key_algorithm_by_dobj (app, dobj, &mechanism);
|
||||
if (err)
|
||||
goto leave;
|
||||
|
||||
/* We need to remove the ASN.1 prefix from INDATA. We use TEMPL as
|
||||
* a temporary buffer for the OID. */
|
||||
if (algo == PIV_ALGORITHM_ECC_P256)
|
||||
/* For ECC we need to remove the ASN.1 prefix from INDATA. For RSA
|
||||
* we need to add the padding and possible also the ASN.1 prefix. */
|
||||
if (mechanism == PIV_ALGORITHM_ECC_P256
|
||||
|| mechanism == PIV_ALGORITHM_ECC_P384)
|
||||
{
|
||||
tmpllen = sizeof tmpl;
|
||||
err = gcry_md_get_asnoid (GCRY_MD_SHA256, &tmpl, &tmpllen);
|
||||
if (err)
|
||||
int need_algo, need_digestlen;
|
||||
|
||||
if (mechanism == PIV_ALGORITHM_ECC_P256)
|
||||
{
|
||||
err = gpg_error (GPG_ERR_INTERNAL);
|
||||
log_debug ("piv: no OID for hash algo %d\n", GCRY_MD_SHA256);
|
||||
need_algo = 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;
|
||||
}
|
||||
if (indatalen != tmpllen + 32 || memcmp (indata, tmpl, tmpllen))
|
||||
|
||||
if (indatalen > need_digestlen)
|
||||
{
|
||||
err = GPG_ERR_INV_VALUE;
|
||||
log_error ("piv: bad formatted input for ECC-P256 auth\n");
|
||||
goto leave;
|
||||
oidbuflen = sizeof oidbuf;
|
||||
err = gcry_md_get_asnoid (need_algo, &oidbuf, &oidbuflen);
|
||||
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;
|
||||
err = gcry_md_get_asnoid (GCRY_MD_SHA384, &tmpl, &tmpllen);
|
||||
if (err)
|
||||
/* PIV requires 2048 bit RSA. */
|
||||
unsigned int framelen = 2048 / 8;
|
||||
unsigned char *frame;
|
||||
int i;
|
||||
|
||||
oidbuflen = sizeof oidbuf;
|
||||
if (!hashalgo)
|
||||
{
|
||||
err = gpg_error (GPG_ERR_INTERNAL);
|
||||
log_debug ("piv: no OID for hash algo %d\n", GCRY_MD_SHA384);
|
||||
/* We assume that indata already has the required
|
||||
* 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;
|
||||
}
|
||||
if (indatalen != tmpllen + 48 || memcmp (indata, tmpl, tmpllen))
|
||||
else
|
||||
{
|
||||
err = GPG_ERR_INV_VALUE;
|
||||
log_error ("piv: bad formatted input for ECC-P384 auth\n");
|
||||
unsigned int digestlen = gcry_md_get_algo_dlen (hashalgo);
|
||||
|
||||
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;
|
||||
}
|
||||
indata += tmpllen;
|
||||
indatalen -= tmpllen;
|
||||
}
|
||||
else if (algo == PIV_ALGORITHM_RSA)
|
||||
{
|
||||
err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
|
||||
log_error ("piv: FIXME: implement RSA authentication\n");
|
||||
goto leave;
|
||||
frame = xtrymalloc (framelen);
|
||||
if (!frame)
|
||||
{
|
||||
err = gpg_error_from_syserror ();
|
||||
goto leave;
|
||||
}
|
||||
n = 0;
|
||||
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
|
||||
{
|
||||
err = gpg_error (GPG_ERR_INTERNAL);
|
||||
log_debug ("piv: unknown PIV algo %d from helper function\n", algo);
|
||||
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);
|
||||
log_debug ("piv: unknown PIV mechanism %d while signing\n", mechanism);
|
||||
goto leave;
|
||||
}
|
||||
|
||||
@ -1970,19 +2144,18 @@ do_auth (app_t app, const char *keyidstr,
|
||||
return err;
|
||||
|
||||
/* Build the Dynamic Authentication Template. */
|
||||
tmpl[0] = 0x7c;
|
||||
tmpl[1] = indatalen + 4;
|
||||
tmpl[2] = 0x82; /* Response. */
|
||||
tmpl[3] = 0; /* Must be 0 to get the tag in the answer. */
|
||||
tmpl[4] = 0x81; /* Challenge. */
|
||||
tmpl[5] = indatalen;
|
||||
memcpy (tmpl+6, indata, indatalen);
|
||||
tmpllen = indatalen + 6;
|
||||
err = concat_tlv_list (&apdudata, &apdudatalen,
|
||||
(int)0x7c, (size_t)0, NULL, /* Constructed. */
|
||||
(int)0x82, (size_t)0, "",
|
||||
(int)0x81, (size_t)indatalen, indata,
|
||||
(int)0, (size_t)0, NULL);
|
||||
if (err)
|
||||
goto leave;
|
||||
|
||||
/* Note: the -1 requests command chaining. */
|
||||
err = iso7816_general_authenticate (app->slot, -1,
|
||||
algo, keyref,
|
||||
tmpl, (int)tmpllen, 0,
|
||||
mechanism, keyref,
|
||||
apdudata, (int)apdudatalen, 0,
|
||||
&outdata, &outdatalen);
|
||||
if (err)
|
||||
goto leave;
|
||||
@ -1991,42 +2164,50 @@ do_auth (app_t app, const char *keyidstr,
|
||||
if (outdatalen && *outdata == 0x7c
|
||||
&& (s = find_tlv (outdata, outdatalen, 0x82, &n)))
|
||||
{
|
||||
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)
|
||||
if (mechanism == PIV_ALGORITHM_RSA)
|
||||
{
|
||||
err = gpg_error_from_syserror ();
|
||||
goto leave;
|
||||
memmove (outdata, outdata + (s - outdata), n);
|
||||
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
|
||||
{
|
||||
@ -2048,10 +2229,29 @@ do_auth (app_t app, const char *keyidstr,
|
||||
*r_outdata = outdata;
|
||||
*r_outdatalen = outdatalen;
|
||||
}
|
||||
xfree (apdudata);
|
||||
xfree (indata_buffer);
|
||||
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
|
||||
* reading the certificate described by DOBJ. If FORCE is TRUE a
|
||||
* 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.writekey = do_writekey; */
|
||||
app->fnc.genkey = do_genkey;
|
||||
/* app->fnc.sign = do_sign; */
|
||||
app->fnc.sign = do_sign;
|
||||
app->fnc.auth = do_auth;
|
||||
/* app->fnc.decipher = do_decipher; */
|
||||
app->fnc.change_pin = do_change_chv;
|
||||
|
Loading…
x
Reference in New Issue
Block a user