From 44621120a269ba67408fe1ea067af8cbd1cbb35e Mon Sep 17 00:00:00 2001 From: NIIBE Yutaka Date: Thu, 3 Mar 2022 17:45:49 +0900 Subject: [PATCH] scd: Add --challenge-response option to PK_AUTH for OpenPGP card. * scd/app-openpgp.c (rmd160_prefix, sha1_prefix, sha224_prefix) (sha256_prefix, sha384_prefix, sha512_prefix): Move the scope up. (gen_challenge): New. (do_auth): Support challenge-response check if it signs correctly. * scd/app.c (app_auth): Remove the check INDATA and INDATALEN. * scd/command.c (cmd_pkauth): Support --challenge-response option. -- GnuPG-bug-id: 5862 Signed-off-by: NIIBE Yutaka --- scd/app-openpgp.c | 207 ++++++++++++++++++++++++++++++++++++++++------ scd/app.c | 2 +- scd/command.c | 18 +++- 3 files changed, 201 insertions(+), 26 deletions(-) diff --git a/scd/app-openpgp.c b/scd/app-openpgp.c index 76a15d00e..05e1f3977 100644 --- a/scd/app-openpgp.c +++ b/scd/app-openpgp.c @@ -256,7 +256,7 @@ struct app_local_s { rsa_key_format_t format; } rsa; struct { - const char *curve; + const char *curve; /* Canonical name defined in openpgp-oid.c */ int algo; unsigned int flags; } ecc; @@ -5156,6 +5156,29 @@ check_keyidstr (app_t app, const char *keyidstr, int keyno, int *r_use_auth) } +static const unsigned char rmd160_prefix[15] = /* Object ID is 1.3.36.3.2.1 */ + { 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x24, 0x03, + 0x02, 0x01, 0x05, 0x00, 0x04, 0x14 }; +static const unsigned char sha1_prefix[15] = /* (1.3.14.3.2.26) */ + { 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e, 0x03, + 0x02, 0x1a, 0x05, 0x00, 0x04, 0x14 }; +static const unsigned char sha224_prefix[19] = /* (2.16.840.1.101.3.4.2.4) */ + { 0x30, 0x2D, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, + 0x01, 0x65, 0x03, 0x04, 0x02, 0x04, 0x05, 0x00, 0x04, + 0x1C }; +static const unsigned char sha256_prefix[19] = /* (2.16.840.1.101.3.4.2.1) */ + { 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, + 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, + 0x00, 0x04, 0x20 }; +static const unsigned char sha384_prefix[19] = /* (2.16.840.1.101.3.4.2.2) */ + { 0x30, 0x41, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, + 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02, 0x05, + 0x00, 0x04, 0x30 }; +static const unsigned char sha512_prefix[19] = /* (2.16.840.1.101.3.4.2.3) */ + { 0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, + 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05, + 0x00, 0x04, 0x40 }; + /* Compute a digital signature on INDATA which is expected to be the raw message digest. For this application the KEYIDSTR consists of the serialnumber and the fingerprint delimited by a slash. @@ -5175,28 +5198,6 @@ do_sign (app_t app, ctrl_t ctrl, const char *keyidstr, int hashalgo, const void *indata, size_t indatalen, unsigned char **outdata, size_t *outdatalen ) { - static unsigned char rmd160_prefix[15] = /* Object ID is 1.3.36.3.2.1 */ - { 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x24, 0x03, - 0x02, 0x01, 0x05, 0x00, 0x04, 0x14 }; - static unsigned char sha1_prefix[15] = /* (1.3.14.3.2.26) */ - { 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e, 0x03, - 0x02, 0x1a, 0x05, 0x00, 0x04, 0x14 }; - static unsigned char sha224_prefix[19] = /* (2.16.840.1.101.3.4.2.4) */ - { 0x30, 0x2D, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, - 0x01, 0x65, 0x03, 0x04, 0x02, 0x04, 0x05, 0x00, 0x04, - 0x1C }; - static unsigned char sha256_prefix[19] = /* (2.16.840.1.101.3.4.2.1) */ - { 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, - 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, - 0x00, 0x04, 0x20 }; - static unsigned char sha384_prefix[19] = /* (2.16.840.1.101.3.4.2.2) */ - { 0x30, 0x41, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, - 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02, 0x05, - 0x00, 0x04, 0x30 }; - static unsigned char sha512_prefix[19] = /* (2.16.840.1.101.3.4.2.3) */ - { 0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, - 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05, - 0x00, 0x04, 0x40 }; int rc; unsigned char data[19+64]; size_t datalen; @@ -5354,6 +5355,60 @@ do_sign (app_t app, ctrl_t ctrl, const char *keyidstr, int hashalgo, return rc; } +/* Generate data to be signed for PKAUTH with --challenge-response. */ +static gpg_error_t +gen_challenge (app_t app, const void **r_data, size_t *r_datalen) +{ + void *data; + size_t datalen; + int header_size; + const unsigned char *hash_prefix = NULL; + + if (app->app_local->keyattr[2].key_type == KEY_TYPE_ECC) + { + unsigned int n; + + openpgp_curve_to_oid (app->app_local->keyattr[2].ecc.curve, &n, NULL); + /* No hash algo header, and appropriate length of random octets, + determined by field size of the curve. */ + datalen = (n+7)/8; + header_size = 0; + } + else + { + /* Hash algo header, and random octets of hash size, the hash + algo is determined by size of key. */ + if (app->app_local->keyattr[2].rsa.n_bits <= 2048) + { + datalen = 32; + hash_prefix = sha256_prefix; + } + else if (app->app_local->keyattr[2].rsa.n_bits <= 3072) + { + datalen = 48; + hash_prefix = sha384_prefix; + } + else + { + datalen = 64; + hash_prefix = sha512_prefix; + } + header_size = 19; + } + + data = xtrymalloc (datalen+header_size); + if (!data) + return gpg_error_from_syserror (); + + if (hash_prefix) + memcpy (data, hash_prefix, header_size); + + gcry_create_nonce ((char *)data+header_size, datalen); + *r_data = data; + *r_datalen = datalen+header_size; + return 0; +} + /* Compute a digital signature using the INTERNAL AUTHENTICATE command on INDATA which is expected to be the raw message digest. For this application the KEYIDSTR consists of the serialnumber and the @@ -5372,9 +5427,24 @@ do_auth (app_t app, ctrl_t ctrl, const char *keyidstr, unsigned char **outdata, size_t *outdatalen ) { int rc; + int challenge_generated = 0; if (!keyidstr || !*keyidstr) return gpg_error (GPG_ERR_INV_VALUE); + + if (indatalen == 0) + { + rc = get_public_key (app, 2); + if (rc) + return rc; + + rc = gen_challenge (app, &indata, &indatalen); + if (rc) + return rc; + challenge_generated = 1; + goto indata_ready; + } + if (app->app_local->keyattr[2].key_type == KEY_TYPE_RSA && indatalen > 101) /* For a 2048 bit key. */ return gpg_error (GPG_ERR_INV_VALUE); @@ -5396,6 +5466,8 @@ do_auth (app_t app, ctrl_t ctrl, const char *keyidstr, } } + indata_ready: + /* Check whether an OpenPGP card of any version has been requested. */ if (!ascii_strcasecmp (keyidstr, "OPENPGP.3")) ; @@ -5436,6 +5508,95 @@ do_auth (app_t app, ctrl_t ctrl, const char *keyidstr, outdata, outdatalen); if (gpg_err_code (rc) == GPG_ERR_TIMEOUT) clear_chv_status (app, ctrl, 1); + + /* Verify the result, when CHALLENGE_GENERATED */ + if (challenge_generated) + { + gcry_sexp_t s_pkey, s_sig, s_hash; + const char *fmt; + + if (app->app_local->keyattr[2].key_type == KEY_TYPE_ECC) + { + if (!strcmp (app->app_local->keyattr[2].ecc.curve, "Ed25519")) + fmt = "(data(flags eddsa)(hash-algo sha512)(value %b))"; + else + fmt = "(data(value %b))"; + } + else + { + void *old_indata = (void *)indata; + unsigned char *new_indata; + size_t new_indatalen; + + /* For RSA, it's PKCS#1 padding. */ + new_indatalen = app->app_local->keyattr[2].rsa.n_bits / 8; + new_indata = xtrymalloc (new_indatalen); + if (!new_indata) + { + rc = gpg_error_from_syserror (); + xfree (old_indata); + return rc; + } + memset (new_indata, 0xff, new_indatalen); + new_indata[0] = 0x00; + new_indata[1] = 0x01; + new_indata[new_indatalen - indatalen -1] = 0x00; + memcpy (new_indata + new_indatalen - indatalen, + indata, indatalen); + + xfree (old_indata); + indata = new_indata; + indatalen = new_indatalen; + fmt = "%b"; /* Old style data format. */ + } + + rc = gcry_sexp_build (&s_hash, NULL, fmt, (int)indatalen, indata); + if (rc) + { + xfree ((void *)indata); + return rc; + } + + if (app->app_local->keyattr[2].key_type == KEY_TYPE_ECC) + { + if (!strcmp (app->app_local->keyattr[2].ecc.curve, "Ed25519") + || !strcmp (app->app_local->keyattr[2].ecc.curve, "Ed448")) + fmt = "(sig-val(eddsa(r %b)(s %b)))"; + else + fmt = "(sig-val(ecdsa(r %b)(s %b)))"; + rc = gcry_sexp_build (&s_sig, NULL, fmt, + (int)*outdatalen/2, *outdata, + (int)*outdatalen/2, *outdata+*outdatalen/2); + } + else + { + fmt = "(sig-val(rsa(s %b)))"; + rc = gcry_sexp_build (&s_sig, NULL, fmt, + (int)*outdatalen, *outdata); + } + if (rc) + { + gcry_sexp_release (s_hash); + xfree ((void *)indata); + return rc; + } + + rc = gcry_sexp_new (&s_pkey, app->app_local->pk[2].key, + app->app_local->pk[2].keylen, 0); + if (rc) + { + gcry_sexp_release (s_hash); + gcry_sexp_release (s_sig); + xfree ((void *)indata); + return rc; + } + + rc = gcry_pk_verify (s_sig, s_hash, s_pkey); + gcry_sexp_release (s_hash); + gcry_sexp_release (s_sig); + gcry_sexp_release (s_pkey); + xfree ((void *)indata); + } } return rc; } diff --git a/scd/app.c b/scd/app.c index 5fcf8fb87..2de4f129c 100644 --- a/scd/app.c +++ b/scd/app.c @@ -2053,7 +2053,7 @@ app_auth (card_t card, ctrl_t ctrl, const char *keyidstr, { gpg_error_t err; - if (!indata || !indatalen || !outdata || !outdatalen || !pincb) + if (!outdata || !outdatalen || !pincb) return gpg_error (GPG_ERR_INV_VALUE); if ((err = maybe_switch_app (ctrl, card, keyidstr))) diff --git a/scd/command.c b/scd/command.c index dfd1ee538..392b678c4 100644 --- a/scd/command.c +++ b/scd/command.c @@ -41,6 +41,7 @@ #endif #include "../common/asshelp.h" #include "../common/server-help.h" +#include "../common/ssh-utils.h" /* Maximum length allowed as a PIN; used for INQUIRE NEEDPIN. That * length needs to small compared to the maximum Assuan line length. */ @@ -1074,7 +1075,7 @@ cmd_pksign (assuan_context_t ctx, char *line) static const char hlp_pkauth[] = - "PKAUTH "; + "PKAUTH [--challenge-response] "; static gpg_error_t cmd_pkauth (assuan_context_t ctx, char *line) { @@ -1085,11 +1086,17 @@ cmd_pkauth (assuan_context_t ctx, char *line) char *keyidstr; card_t card; const char *keygrip = NULL; + int challenge_response = 0; if ((rc = open_card (ctrl))) return rc; - /* We have to use a copy of the key ID because the function may use + if (has_option (line, "--challenge-response")) + challenge_response = 1; + + line = skip_options (line); + + /* We have to use a copy of the key ID because the function may use the pin_cb which in turn uses the assuan line buffer and thus overwriting the original line with the keyid */ keyidstr = xtrystrdup (line); @@ -1101,6 +1108,13 @@ cmd_pkauth (assuan_context_t ctx, char *line) if (strlen (keyidstr) == 40) keygrip = keyidstr; + if (challenge_response) + { + xfree (ctrl->in_data.value); + ctrl->in_data.value = NULL; + ctrl->in_data.valuelen = 0; + } + card = card_get (ctrl, keygrip); if (card) {