From 43b14b4cc227311aa77b1fc1d9577c5f7d3eda86 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Mon, 11 Feb 2019 15:32:54 +0100 Subject: [PATCH] scd: Implement decryption for PIV cards. * scd/app-piv.c (do_decipher): New. -- Note that ECDH decryption has not been tested due to the lack of ECC support in gpgsm. Signed-off-by: Werner Koch --- scd/app-piv.c | 147 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 146 insertions(+), 1 deletion(-) diff --git a/scd/app-piv.c b/scd/app-piv.c index 36086f546..9e355b4c9 100644 --- a/scd/app-piv.c +++ b/scd/app-piv.c @@ -2259,6 +2259,151 @@ do_auth (app_t app, const char *keyidstr, } +/* Decrypt the data in (INDATA,INDATALEN) and on success store the + * mallocated result at (R_OUTDATA,R_OUTDATALEN). */ +static gpg_error_t +do_decipher (app_t app, const char *keyidstr, + gpg_error_t (*pincb)(void*, const char *, char **), + void *pincb_arg, + const void *indata_arg, size_t indatalen, + unsigned char **r_outdata, size_t *r_outdatalen, + unsigned int *r_info) +{ + const unsigned char *indata = indata_arg; + gpg_error_t err; + data_object_t dobj; + unsigned char *outdata = NULL; + size_t outdatalen; + const unsigned char *s; + size_t n; + int keyref, mechanism; + unsigned int framelen; + unsigned char *indata_buffer = NULL; /* Malloced helper. */ + unsigned char *apdudata = NULL; + size_t apdudatalen; + + if (!keyidstr || !*keyidstr) + { + err = gpg_error (GPG_ERR_INV_VALUE); + goto leave; + } + + dobj = find_dobj_by_keyref (app, keyidstr); + if ((keyref = keyref_from_dobj (dobj)) == -1) + { + err = gpg_error (GPG_ERR_INV_ID); + goto leave; + } + if (keyref == 0x9A || keyref == 0x9C || keyref == 0x9E) + { + /* Signing only reference. We only allow '9D' and the retired + * cert key management DOs. */ + err = gpg_error (GPG_ERR_INV_ID); + goto leave; + } + + err = get_key_algorithm_by_dobj (app, dobj, &mechanism); + if (err) + goto leave; + + switch (mechanism) + { + case PIV_ALGORITHM_ECC_P256: + framelen = 1+32+32; + break; + case PIV_ALGORITHM_ECC_P384: + framelen = 1+48+48; + break; + case PIV_ALGORITHM_RSA: + framelen = 2048 / 8; + break; + default: + err = gpg_error (GPG_ERR_INTERNAL); + log_debug ("piv: unknown PIV mechanism %d while decrypting\n", mechanism); + goto leave; + } + + /* Check that the ciphertext has the right length; due to internal + * convey mechanism using MPIs leading zero bytes might have been + * lost. Adjust for this. Note that for ECC this actually + * superfluous because the first octet is always '04' to indicate an + * uncompressed point. */ + if (indatalen > framelen) + { + err = gpg_error (GPG_ERR_INV_VALUE); + log_error ("piv: input of %zu octets too large for mechanism %d\n", + indatalen, mechanism); + goto leave; + } + if (indatalen < framelen) + { + indata_buffer = xtrycalloc (1, framelen); + if (!indata_buffer) + { + err = gpg_error_from_syserror (); + goto leave; + } + memcpy (indata_buffer+(framelen-indatalen), indata, indatalen); + indata = indata_buffer; + indatalen = framelen; + } + + /* Now verify the Application PIN. */ + err = verify_chv (app, 0x80, pincb, pincb_arg); + if (err) + return err; + + /* Build the Dynamic Authentication Template. */ + err = concat_tlv_list (&apdudata, &apdudatalen, + (int)0x7c, (size_t)0, NULL, /* Constructed. */ + (int)0x82, (size_t)0, "", + mechanism == PIV_ALGORITHM_RSA? + (int)0x81 : (int)0x85, (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, + mechanism, keyref, + apdudata, (int)apdudatalen, 0, + &outdata, &outdatalen); + if (err) + goto leave; + + /* Parse the response. */ + if (outdatalen && *outdata == 0x7c + && (s = find_tlv (outdata, outdatalen, 0x82, &n))) + { + memmove (outdata, outdata + (s - outdata), n); + outdatalen = n; + } + else + { + err = gpg_error (GPG_ERR_CARD); + log_error ("piv: response does not contain a proper result\n"); + goto leave; + } + + leave: + if (err) + { + xfree (outdata); + *r_outdata = NULL; + *r_outdatalen = 0; + } + else + { + *r_outdata = outdata; + *r_outdatalen = outdatalen; + } + *r_info = 0; + xfree (apdudata); + xfree (indata_buffer); + return err; +} + + /* 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 @@ -2679,7 +2824,7 @@ app_select_piv (app_t app) app->fnc.genkey = do_genkey; app->fnc.sign = do_sign; app->fnc.auth = do_auth; - /* app->fnc.decipher = do_decipher; */ + app->fnc.decipher = do_decipher; app->fnc.change_pin = do_change_chv; app->fnc.check_pin = do_check_chv;